Compare commits
1228 Commits
work/featu
...
work/carl/
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6a062f8955 | ||
|
|
cbab810a2e | ||
|
|
e78dfaec34 | ||
|
|
3cfa773820 | ||
|
|
94c0e8b6cd | ||
|
|
35b1f24cb7 | ||
|
|
de3072125e | ||
|
|
f7d2ffac66 | ||
|
|
ff0990bb7c | ||
|
|
8285961c42 | ||
|
|
cd39d5b129 | ||
|
|
83b3fefbf5 | ||
|
|
3718bd716a | ||
|
|
a18257ee17 | ||
|
|
def46d90a8 | ||
|
|
8afd7b2892 | ||
|
|
deb11367cb | ||
|
|
ffd1b06a82 | ||
|
|
2597f0aec9 | ||
|
|
33ca72efd9 | ||
|
|
2fbf659eb4 | ||
|
|
763198b2c3 | ||
|
|
50551541ed | ||
|
|
323cd4962e | ||
|
|
b2f592afeb | ||
|
|
f7f98c43e2 | ||
|
|
06134f96eb | ||
|
|
bb62849a19 | ||
|
|
d05385caed | ||
|
|
209ca747b5 | ||
|
|
2c6ab498ac | ||
|
|
597633f824 | ||
|
|
65da416b8e | ||
|
|
c528929b30 | ||
|
|
8650ce1755 | ||
|
|
8936b23da9 | ||
|
|
cf72fbea2e | ||
|
|
0d5929b4bc | ||
|
|
d43cc9a044 | ||
|
|
682e3967ba | ||
|
|
ceebee3a56 | ||
|
|
52ab052aef | ||
|
|
e6a2b2d125 | ||
|
|
92dadc2cc9 | ||
|
|
6ca9380a51 | ||
|
|
51d41ab5bf | ||
|
|
f1047fa474 | ||
|
|
011b11681f | ||
|
|
8baeb236bc | ||
|
|
b9d173d0b5 | ||
|
|
3790955c56 | ||
|
|
199772a013 | ||
|
|
54cc3ac761 | ||
|
|
1675c14c92 | ||
|
|
56f5ef2611 | ||
|
|
7ba63eb680 | ||
|
|
ddd690f6d5 | ||
|
|
d9dc5f48bc | ||
|
|
a3b40a5e6d | ||
|
|
be71a4349b | ||
|
|
ede2707767 | ||
|
|
5871903529 | ||
|
|
87745c360d | ||
|
|
40d2eb5aba | ||
|
|
cfd9f36a97 | ||
|
|
48f7e06c01 | ||
|
|
43ecd188f1 | ||
|
|
063056bebd | ||
|
|
dfba655527 | ||
|
|
23178b6224 | ||
|
|
a9a6e31b8c | ||
|
|
0afd74d96a | ||
|
|
c5501f2b5d | ||
|
|
68602fc177 | ||
|
|
8ce5a57499 | ||
|
|
bf3c5708c7 | ||
|
|
8359c1fe90 | ||
|
|
774121ea8c | ||
|
|
370bbf8a4b | ||
|
|
4fd36fb221 | ||
|
|
167349b9d2 | ||
|
|
296aa9ddad | ||
|
|
94c1b67ec9 | ||
|
|
a9965acdd3 | ||
|
|
35313271a3 | ||
|
|
e40179f641 | ||
|
|
dacf3ff8a0 | ||
|
|
0274d70f71 | ||
|
|
0c19052f32 | ||
|
|
a3423e5724 | ||
|
|
e09f31f891 | ||
|
|
e05e0f5489 | ||
|
|
002961f638 | ||
|
|
e28419ef88 | ||
|
|
0e4df58fc9 | ||
|
|
21a55885b3 | ||
|
|
6a8ac8f6e5 | ||
|
|
9fcfad7058 | ||
|
|
cfcc1756dd | ||
|
|
e15bec2295 | ||
|
|
9071cf827f | ||
|
|
c7deaaba84 | ||
|
|
414035de8b | ||
|
|
442612d31d | ||
|
|
59164d3bb2 | ||
|
|
cc60dde62d | ||
|
|
cbed8148a3 | ||
|
|
50e8b9ebf6 | ||
|
|
f5ad2ad162 | ||
|
|
d11d6c74b3 | ||
|
|
63ed69a5d4 | ||
|
|
a8aa775575 | ||
|
|
6305359b3c | ||
|
|
7e859364af | ||
|
|
8abd0db012 | ||
|
|
dbc10685f0 | ||
|
|
96582a12bc | ||
|
|
0ac61854bd | ||
|
|
68298f038d | ||
|
|
405fd5841a | ||
|
|
5da9bba844 | ||
|
|
84373712ef | ||
|
|
50f4f96341 | ||
|
|
1b27b1a4e2 | ||
|
|
33811a4c49 | ||
|
|
43715486e5 | ||
|
|
5c72bd4ab7 | ||
|
|
092f1be99b | ||
|
|
550d55cb1a | ||
|
|
e9edb61245 | ||
|
|
89aae665b1 | ||
|
|
1e3c3dd1f4 | ||
|
|
6f4d2c0216 | ||
|
|
803cd2b4e4 | ||
|
|
9f3012061d | ||
|
|
c1604a9c4f | ||
|
|
3c7fcee244 | ||
|
|
d33a50a00d | ||
|
|
ed033a1c5e | ||
|
|
2b961703ae | ||
|
|
0539665779 | ||
|
|
df127b88e6 | ||
|
|
27c4b57f0f | ||
|
|
f875a23e83 | ||
|
|
7f5cfbf21c | ||
|
|
f7c7643c1c | ||
|
|
692edf52f1 | ||
|
|
e48cfaa41f | ||
|
|
ba116460d5 | ||
|
|
c71672dab3 | ||
|
|
932ef72311 | ||
|
|
7e53a2234f | ||
|
|
a0499e5140 | ||
|
|
093ef0a18c | ||
|
|
36bf862ab9 | ||
|
|
6d8c1d0780 | ||
|
|
7fe85066a4 | ||
|
|
1fefa228e6 | ||
|
|
4104e10d95 | ||
|
|
05f3c3ee0a | ||
|
|
0a1c489401 | ||
|
|
e53d63ad8b | ||
|
|
78541b32f0 | ||
|
|
3bd93996c0 | ||
|
|
61968aa475 | ||
|
|
24d0082048 | ||
|
|
2980af11b0 | ||
|
|
91d9406c38 | ||
|
|
280d1e38e2 | ||
|
|
7b520da4b4 | ||
|
|
419b4cea98 | ||
|
|
38824f30ac | ||
|
|
9c4d8ef823 | ||
|
|
fe3bf3a638 | ||
|
|
d678a446e2 | ||
|
|
6df60a39b0 | ||
|
|
6f9fa76ab7 | ||
|
|
6e8b0f001f | ||
|
|
0bfad95d8b | ||
|
|
1b43846196 | ||
|
|
4ea183b139 | ||
|
|
22fd24549b | ||
|
|
af793fb865 | ||
|
|
13414b5834 | ||
|
|
65a1df4a75 | ||
|
|
d15157703a | ||
|
|
2d21330a36 | ||
|
|
fbc4611d02 | ||
|
|
e257865b19 | ||
|
|
018154f555 | ||
|
|
80a5daa1f7 | ||
|
|
bf5ce049d9 | ||
|
|
58213ee3e6 | ||
|
|
2d1862a637 | ||
|
|
67453d9fb8 | ||
|
|
3ab04583ae | ||
|
|
794683b5d2 | ||
|
|
9f7f5409bd | ||
|
|
7d7b9d7ae0 | ||
|
|
c7c892c51c | ||
|
|
97903a7cb0 | ||
|
|
0c8f488ac4 | ||
|
|
88368cefaa | ||
|
|
ab4bb41979 | ||
|
|
c2398b19dc | ||
|
|
4b3dac025f | ||
|
|
9eeff06328 | ||
|
|
657a8291d0 | ||
|
|
bc4431462a | ||
|
|
fdd8a62873 | ||
|
|
c164228894 | ||
|
|
7d418f0883 | ||
|
|
e90aecbead | ||
|
|
221c27fbbe | ||
|
|
1907404124 | ||
|
|
81e953dd8c | ||
|
|
9311052e39 | ||
|
|
648fff20f2 | ||
|
|
980211f36a | ||
|
|
25e0e3fa8e | ||
|
|
403c1ed391 | ||
|
|
c2f76612cf | ||
|
|
2a651b8946 | ||
|
|
00c0d1c276 | ||
|
|
0162d91b60 | ||
|
|
5f41b7b4c1 | ||
|
|
d9691bac7c | ||
|
|
719b916837 | ||
|
|
f9514fe925 | ||
|
|
399b9e6944 | ||
|
|
00cf5c8a5b | ||
|
|
02f26424e2 | ||
|
|
3bd639775a | ||
|
|
163c1ef407 | ||
|
|
9997b1074a | ||
|
|
f52a25717b | ||
|
|
5746c24b73 | ||
|
|
dca1b22521 | ||
|
|
b15f1461d8 | ||
|
|
24fd7a0f5b | ||
|
|
0ac3932303 | ||
|
|
807a9ee779 | ||
|
|
4f32a168cd | ||
|
|
45d2da56c7 | ||
|
|
d794e26d62 | ||
|
|
42cc42642d | ||
|
|
ffd6205bd1 | ||
|
|
9ba6b2f0a5 | ||
|
|
f8d3e25f8e | ||
|
|
75cd77facb | ||
|
|
8546d76a54 | ||
|
|
00b7f68a03 | ||
|
|
b486cb905c | ||
|
|
cdd5c9f484 | ||
|
|
3e6c426397 | ||
|
|
847f41e1ad | ||
|
|
ed5bb270ff | ||
|
|
c8b4da2b96 | ||
|
|
ba5445e135 | ||
|
|
1cca39e105 | ||
|
|
dbf67b984e | ||
|
|
c9126cf38e | ||
|
|
13988da4fc | ||
|
|
0847839abc | ||
|
|
6b55e502a0 | ||
|
|
8f81629ac1 | ||
|
|
7f459cb90f | ||
|
|
420e195313 | ||
|
|
3263a69880 | ||
|
|
b060881f06 | ||
|
|
701e786c1f | ||
|
|
646c8ba8fe | ||
|
|
9b31fdea10 | ||
|
|
ce5dfdee16 | ||
|
|
918e805718 | ||
|
|
7debf47833 | ||
|
|
2c142c36e6 | ||
|
|
3279142498 | ||
|
|
0e08a1aa7e | ||
|
|
3855922ad2 | ||
|
|
89a094ce8f | ||
|
|
d4421aaecf | ||
|
|
80b451c27b | ||
|
|
32ff22ba89 | ||
|
|
b990e3fbcb | ||
|
|
8d1baab89f | ||
|
|
62ecbc8d6b | ||
|
|
0e1c7f8c47 | ||
|
|
c963966f1d | ||
|
|
8db2526153 | ||
|
|
7587a1a418 | ||
|
|
294812956a | ||
|
|
d01aac6fa3 | ||
|
|
5cda1ca3ce | ||
|
|
bf26a463e0 | ||
|
|
30180c1ac8 | ||
|
|
41c296061c | ||
|
|
461420af0a | ||
|
|
052a7ed571 | ||
|
|
98e86f887b | ||
|
|
63a17b5985 | ||
|
|
025b367a7e | ||
|
|
a8536b0634 | ||
|
|
0c43d3eeee | ||
|
|
bf1614256b | ||
|
|
99614d1b27 | ||
|
|
e1e57ef27a | ||
|
|
6d45d126f8 | ||
|
|
732b43cbd6 | ||
|
|
a5da17b000 | ||
|
|
10a294f99e | ||
|
|
882a0d4901 | ||
|
|
506d31f53f | ||
|
|
110f007b41 | ||
|
|
a90943d9ac | ||
|
|
cbe7d8c2c2 | ||
|
|
ae4943dd71 | ||
|
|
7bd84bf51e | ||
|
|
a6ce44eb24 | ||
|
|
b1c42c3d3d | ||
|
|
213aaf3ac4 | ||
|
|
c55b40c9c6 | ||
|
|
81928d8b93 | ||
|
|
b7bddba053 | ||
|
|
307a9370db | ||
|
|
2f65cbeb36 | ||
|
|
e0c0b1f0e8 | ||
|
|
74f767aa82 | ||
|
|
7176dd4476 | ||
|
|
f75fe31571 | ||
|
|
61bdb1ed5f | ||
|
|
527e9d93a5 | ||
|
|
7d22b30217 | ||
|
|
c0d2333a3d | ||
|
|
4a7e1d058c | ||
|
|
66974615f6 | ||
|
|
116c888686 | ||
|
|
f6d6a804d2 | ||
|
|
6d6d702b97 | ||
|
|
9be04c1272 | ||
|
|
6f5d88cf63 | ||
|
|
1103e80191 | ||
|
|
a5320397c2 | ||
|
|
53719b971d | ||
|
|
64a1316f9b | ||
|
|
a3b8168744 | ||
|
|
c06e69931a | ||
|
|
30f8573dfc | ||
|
|
7f067b698e | ||
|
|
b56ebdf149 | ||
|
|
a02dd4ab87 | ||
|
|
898f0c962a | ||
|
|
5b4ae764cf | ||
|
|
d14db326bb | ||
|
|
5c51e0d0fc | ||
|
|
f78b4af692 | ||
|
|
bd5ed0f46c | ||
|
|
529cfa8f7d | ||
|
|
5552cd60f6 | ||
|
|
c51a1f4851 | ||
|
|
a27f4765e4 | ||
|
|
402f99923c | ||
|
|
2afda78912 | ||
|
|
25f9c7e125 | ||
|
|
05082cb2bb | ||
|
|
59495a1452 | ||
|
|
d10460c45b | ||
|
|
b968c85de2 | ||
|
|
13efc08b07 | ||
|
|
718060c757 | ||
|
|
4092cd8b6a | ||
|
|
ea99c26556 | ||
|
|
7527fd47cd | ||
|
|
c72c9c5cba | ||
|
|
09ded20409 | ||
|
|
0fd3de6215 | ||
|
|
1de160cb19 | ||
|
|
921abac3c1 | ||
|
|
7c75a2fd06 | ||
|
|
105be518c7 | ||
|
|
9425f24315 | ||
|
|
47c28ce9a2 | ||
|
|
72c85af407 | ||
|
|
22694fe5e4 | ||
|
|
a02232dc19 | ||
|
|
252e099e75 | ||
|
|
76a697c3f6 | ||
|
|
5b23593fd2 | ||
|
|
c2580c1d2d | ||
|
|
3303d2c7db | ||
|
|
22107fc598 | ||
|
|
bc4c4f8519 | ||
|
|
16c63dbe93 | ||
|
|
9e78ab3328 | ||
|
|
e6dc1f54b3 | ||
|
|
09025fa16d | ||
|
|
357b148944 | ||
|
|
8b71e56a5f | ||
|
|
f5aa5ac7f4 | ||
|
|
075d2fda4d | ||
|
|
b5c781212c | ||
|
|
af136943c3 | ||
|
|
b2a29c8d45 | ||
|
|
5d16d78914 | ||
|
|
94e970e15a | ||
|
|
1f4b984664 | ||
|
|
a2a27e78d1 | ||
|
|
915a5c188f | ||
|
|
e8f0420ad5 | ||
|
|
7a01b3ea28 | ||
|
|
7cff2aaa97 | ||
|
|
9588c7d8ef | ||
|
|
d5a6c7683e | ||
|
|
1de4e2ecd3 | ||
|
|
fa37f28c94 | ||
|
|
f1be509d13 | ||
|
|
fed528c6d8 | ||
|
|
d09cd5d71b | ||
|
|
4e40f5c7d9 | ||
|
|
3b5bb06b4a | ||
|
|
8f4e5a41c5 | ||
|
|
9b1005efc3 | ||
|
|
e1d4b66479 | ||
|
|
f2aa375b43 | ||
|
|
ee53793a6d | ||
|
|
528d46be9f | ||
|
|
cc80e69644 | ||
|
|
09ced090f2 | ||
|
|
8ad23e7a40 | ||
|
|
a94f46f904 | ||
|
|
41a5d2557e | ||
|
|
0b36db0ecc | ||
|
|
aae5519bed | ||
|
|
df849accfa | ||
|
|
b098937d1b | ||
|
|
fbe4fc49e1 | ||
|
|
8741fd8fed | ||
|
|
ccc952b65e | ||
|
|
6a68375b10 | ||
|
|
4dcb19edb7 | ||
|
|
838abc9af3 | ||
|
|
7283b2dd4d | ||
|
|
2c1f00da03 | ||
|
|
c7caec894e | ||
|
|
b254b4aa9b | ||
|
|
67107959e4 | ||
|
|
26d3e16e6d | ||
|
|
bb8ee4ef44 | ||
|
|
ee65ad22e6 | ||
|
|
b823f2c6b9 | ||
|
|
0901c59de9 | ||
|
|
69bfa1394c | ||
|
|
5b69a3e07c | ||
|
|
37220ab872 | ||
|
|
9dd7ceb966 | ||
|
|
ae6056615d | ||
|
|
d74253d149 | ||
|
|
805332c599 | ||
|
|
e42c1c1f69 | ||
|
|
f7881d0661 | ||
|
|
5eb86bbc88 | ||
|
|
53dc32fac1 | ||
|
|
a5a2c0b03e | ||
|
|
4d2e64cb80 | ||
|
|
dee064a758 | ||
|
|
5bdd67dcc1 | ||
|
|
2ff92bea70 | ||
|
|
0fbb070bf2 | ||
|
|
1a1c7ef23e | ||
|
|
fabd3da5a7 | ||
|
|
6f90d33f3a | ||
|
|
3bb0ee17cd | ||
|
|
fa8164cdba | ||
|
|
c63f1f0452 | ||
|
|
e0df447998 | ||
|
|
a64d4d0989 | ||
|
|
4c575f18d1 | ||
|
|
b629961a70 | ||
|
|
571d9780b1 | ||
|
|
df2ed04f16 | ||
|
|
3d687a283e | ||
|
|
99479b3896 | ||
|
|
7fca14bce1 | ||
|
|
afb51497cd | ||
|
|
88fada89ea | ||
|
|
023c51ac62 | ||
|
|
c37fa92559 | ||
|
|
9dabb890dc | ||
|
|
e40c3b4c03 | ||
|
|
5be7105bb8 | ||
|
|
8dc9541d95 | ||
|
|
6e5b71888d | ||
|
|
b362365609 | ||
|
|
1db2e4a333 | ||
|
|
6e8e40c22f | ||
|
|
cc21f8cca9 | ||
|
|
b545a27bc6 | ||
|
|
c9d863335e | ||
|
|
334e3ac6b4 | ||
|
|
d424aba0bc | ||
|
|
8802753de1 | ||
|
|
40edfef046 | ||
|
|
11343e6bdf | ||
|
|
30c7d86045 | ||
|
|
58b7b46828 | ||
|
|
3d0c0e3ed5 | ||
|
|
77d8ec04ca | ||
|
|
72de7c6cfb | ||
|
|
b82d3ab5ad | ||
|
|
20443ba59f | ||
|
|
bfdca9ca9e | ||
|
|
a12a44b722 | ||
|
|
8d45d4404e | ||
|
|
42ca128d7f | ||
|
|
7cd8e70d55 | ||
|
|
2982d2526a | ||
|
|
d33a0c6644 | ||
|
|
4a29e0d0e1 | ||
|
|
a89e04b92f | ||
|
|
4b993775c6 | ||
|
|
e0983fcc8c | ||
|
|
c9ddf2f04a | ||
|
|
b5764c4d04 | ||
|
|
5f41d275e4 | ||
|
|
7f707ae042 | ||
|
|
270a1e3a96 | ||
|
|
65d919876c | ||
|
|
049ae3602f | ||
|
|
d4c8e558e0 | ||
|
|
0e4d87824e | ||
|
|
cd7f6d52dd | ||
|
|
96c1b98d02 | ||
|
|
443d709eb8 | ||
|
|
5107f0e79e | ||
|
|
5567f94535 | ||
|
|
8e566e7326 | ||
|
|
43c6172482 | ||
|
|
f453334f63 | ||
|
|
ac24e06ae2 | ||
|
|
eab5abec2f | ||
|
|
0fc962b147 | ||
|
|
10794628ed | ||
|
|
a6f108d3b8 | ||
|
|
c4e80c1d1c | ||
|
|
6f231fa916 | ||
|
|
5d5f5c4ab7 | ||
|
|
034453636d | ||
|
|
8e4da42a64 | ||
|
|
7156bf0711 | ||
|
|
bc317514e6 | ||
|
|
26b2380bb7 | ||
|
|
685b2a68ba | ||
|
|
9936c7a8b6 | ||
|
|
83da52582b | ||
|
|
fbf918c7d0 | ||
|
|
2281fe6f8a | ||
|
|
1bb03e115e | ||
|
|
9d6ebf53a9 | ||
|
|
9e780b8eb2 | ||
|
|
01b34c5b8c | ||
|
|
11233bee3a | ||
|
|
e94b3caf51 | ||
|
|
e8166f3433 | ||
|
|
d844945453 | ||
|
|
85b40ca536 | ||
|
|
ca805917de | ||
|
|
014185c4c9 | ||
|
|
2b06679be6 | ||
|
|
7d2edc7c52 | ||
|
|
224bc9441b | ||
|
|
5a7b17a31e | ||
|
|
eea8950afb | ||
|
|
9f15c7fc53 | ||
|
|
4b879be4ea | ||
|
|
af078f03d0 | ||
|
|
cdf2c390ca | ||
|
|
77b2186cb6 | ||
|
|
0664ef8ccb | ||
|
|
4af7537764 | ||
|
|
f47e7531fc | ||
|
|
e484a08e8d | ||
|
|
56f3d0e0d6 | ||
|
|
3797854b0c | ||
|
|
931e91ddba | ||
|
|
25754bbe22 | ||
|
|
53884949d1 | ||
|
|
abb2a340e2 | ||
|
|
ed874ed00a | ||
|
|
a8aec35884 | ||
|
|
0496f6552a | ||
|
|
970cd5cf7a | ||
|
|
2fb4fdd18f | ||
|
|
fc1dc5c1d6 | ||
|
|
800194c6f5 | ||
|
|
0cab7b1c85 | ||
|
|
983d4c9db2 | ||
|
|
88dfacbbb3 | ||
|
|
3f572c79fa | ||
|
|
a7ee1fac3c | ||
|
|
068bfc948b | ||
|
|
47470f5a6d | ||
|
|
144dc1f8f4 | ||
|
|
4002bb804c | ||
|
|
760ed24b37 | ||
|
|
489979af43 | ||
|
|
e482e12826 | ||
|
|
fa7b9d54e2 | ||
|
|
f60114c7f6 | ||
|
|
de55253e54 | ||
|
|
8479e51051 | ||
|
|
bb2fd7c9c4 | ||
|
|
a0b0a5d47f | ||
|
|
10bdc1d3d1 | ||
|
|
6ec7d8d6b4 | ||
|
|
0dca9588ff | ||
|
|
ca03c530b2 | ||
|
|
973ec24674 | ||
|
|
1da767ff0a | ||
|
|
89127876f9 | ||
|
|
47a738a703 | ||
|
|
516b1cff88 | ||
|
|
6438977964 | ||
|
|
d750263d39 | ||
|
|
3ed952db9e | ||
|
|
f8040a1bf6 | ||
|
|
d83b31fd86 | ||
|
|
e0dbb657f6 | ||
|
|
a807cc6143 | ||
|
|
fe064c0ef8 | ||
|
|
6cc773426f | ||
|
|
db94408ba6 | ||
|
|
333bd3cdb9 | ||
|
|
9a0d82eb31 | ||
|
|
0990c0507c | ||
|
|
9f76ce22c1 | ||
|
|
6acd6075ff | ||
|
|
57b6f00d8e | ||
|
|
280c9327cb | ||
|
|
05bcbb695f | ||
|
|
552f4e8f13 | ||
|
|
1223c5348d | ||
|
|
3ccff4f337 | ||
|
|
a67f3334ea | ||
|
|
52dafbb6c8 | ||
|
|
4341cc437d | ||
|
|
7bb7dd7bbb | ||
|
|
b4090d9671 | ||
|
|
3d2bcce99a | ||
|
|
f3a04635cf | ||
|
|
419cb07557 | ||
|
|
57fccaa076 | ||
|
|
4bf65339f8 | ||
|
|
e5f2e209a2 | ||
|
|
be3b5cdb8a | ||
|
|
abd56baa51 | ||
|
|
ff27a1d0bb | ||
|
|
8af2d4d273 | ||
|
|
f64c8e28da | ||
|
|
4570d6350b | ||
|
|
6dd51a35c5 | ||
|
|
2470990d75 | ||
|
|
1a87e605d6 | ||
|
|
e995740790 | ||
|
|
0fb8b740a4 | ||
|
|
5087161e4b | ||
|
|
918e9e492a | ||
|
|
f10805dddb | ||
|
|
55e4d03dfe | ||
|
|
9f9086b4b0 | ||
|
|
e00ce79d26 | ||
|
|
a13b2e6bd2 | ||
|
|
cefe5acdaa | ||
|
|
1438aea965 | ||
|
|
1b60e24c64 | ||
|
|
4f1c8f6f35 | ||
|
|
882945260a | ||
|
|
3677088104 | ||
|
|
8f141cd88d | ||
|
|
0d1b35b610 | ||
|
|
87c20bf03c | ||
|
|
77479ca22d | ||
|
|
34ad743e98 | ||
|
|
493e27622f | ||
|
|
b1b6c7ceed | ||
|
|
d0b1610a9f | ||
|
|
dcf520a7a9 | ||
|
|
a0ae8b28b2 | ||
|
|
78a6179a11 | ||
|
|
9c91557d8f | ||
|
|
fb24ffd20d | ||
|
|
0c985a0af1 | ||
|
|
787dc5ab66 | ||
|
|
f11abdeebd | ||
|
|
89141dddf2 | ||
|
|
69f08de0ff | ||
|
|
925e20ebb8 | ||
|
|
ee254a286d | ||
|
|
3cc8d32dd3 | ||
|
|
50cf6d9750 | ||
|
|
cedbb64932 | ||
|
|
b45898a5b6 | ||
|
|
ccb1748ab3 | ||
|
|
11e9af15f7 | ||
|
|
8ad41fec92 | ||
|
|
ac5212ebb2 | ||
|
|
4e16b91f54 | ||
|
|
343f4965ed | ||
|
|
ac775c5aaf | ||
|
|
db17888d42 | ||
|
|
f12d10baa6 | ||
|
|
4289c1345f | ||
|
|
0d6a83b063 | ||
|
|
76b04dcba9 | ||
|
|
64dee7eb12 | ||
|
|
bc84ad8d56 | ||
|
|
afe8a2a5e4 | ||
|
|
87213dc9dd | ||
|
|
300d2428eb | ||
|
|
abe7d70822 | ||
|
|
d8bf26158a | ||
|
|
741cb57105 | ||
|
|
81c73037ca | ||
|
|
f6ba4f2ecd | ||
|
|
23303c0483 | ||
|
|
5a02448326 | ||
|
|
411ae25e80 | ||
|
|
6e1b3fe860 | ||
|
|
4a38d83a68 | ||
|
|
280d90e191 | ||
|
|
b4b24430a1 | ||
|
|
b7b7f1817a | ||
|
|
3d67a682f4 | ||
|
|
da6df18367 | ||
|
|
0b1b1b8c8c | ||
|
|
2493a00ba4 | ||
|
|
d6ebb1308c | ||
|
|
332c311023 | ||
|
|
27d33d121a | ||
|
|
e954fc204f | ||
|
|
d1f7e7091e | ||
|
|
bd4eeb405b | ||
|
|
e6a060c192 | ||
|
|
f53a7a27f6 | ||
|
|
34384dced4 | ||
|
|
b68cfafab2 | ||
|
|
58b836fd1e | ||
|
|
90cad05bae | ||
|
|
072f7cec37 | ||
|
|
39388e204e | ||
|
|
6ccb201110 | ||
|
|
178b516c7c | ||
|
|
ac88e13e58 | ||
|
|
8d3e145e0b | ||
|
|
f40d1b9f4e | ||
|
|
e9cd165457 | ||
|
|
c0c86c67b6 | ||
|
|
12afa43d23 | ||
|
|
386f637b94 | ||
|
|
d7bd9f4609 | ||
|
|
33c9edc9a3 | ||
|
|
3b2dbc731e | ||
|
|
51a29ac528 | ||
|
|
cc8bf79a9b | ||
|
|
8d47e58861 | ||
|
|
47ce8a4846 | ||
|
|
71c9537c61 | ||
|
|
8825e6ec83 | ||
|
|
44ec93f0a0 | ||
|
|
ed7688e66f | ||
|
|
6b49854b12 | ||
|
|
e8484ebc7a | ||
|
|
17d01c68c4 | ||
|
|
dae2cbab90 | ||
|
|
b8e8fa3ee5 | ||
|
|
9c4a925171 | ||
|
|
8996806b05 | ||
|
|
2bd4579c10 | ||
|
|
0891f32c08 | ||
|
|
5287c2d529 | ||
|
|
084b89f3dc | ||
|
|
2dd3197beb | ||
|
|
6d9dca7da8 | ||
|
|
e0f16054fc | ||
|
|
fa70679439 | ||
|
|
949bd20873 | ||
|
|
79a9eb0de0 | ||
|
|
e1b9bc7d0e | ||
|
|
a1abf22174 | ||
|
|
fa27d993e2 | ||
|
|
d4b750433e | ||
|
|
9df534c72c | ||
|
|
f785e4d5b0 | ||
|
|
f186be7314 | ||
|
|
3cbcc2b597 | ||
|
|
61f3d6c9ae | ||
|
|
d8b2436f0d | ||
|
|
ea76edce74 | ||
|
|
5482aad7ba | ||
|
|
aaa26571d1 | ||
|
|
554e3576fd | ||
|
|
8d2c7ff8c2 | ||
|
|
92261eb94f | ||
|
|
4dfb7fc3e2 | ||
|
|
2728446692 | ||
|
|
dfb32fe687 | ||
|
|
55507112a9 | ||
|
|
d9aa77a9f4 | ||
|
|
c0ea52c331 | ||
|
|
f1b9d2329f | ||
|
|
0707fd8189 | ||
|
|
6bef2205db | ||
|
|
ace62b4df1 | ||
|
|
d76e512785 | ||
|
|
db2692c411 | ||
|
|
4c9bdd03ce | ||
|
|
efa2c012a7 | ||
|
|
072dc1b6a3 | ||
|
|
4066427168 | ||
|
|
b901c1bdf2 | ||
|
|
532310d739 | ||
|
|
4f7d32df2b | ||
|
|
833d2159e7 | ||
|
|
ccf8701395 | ||
|
|
5d696aabc4 | ||
|
|
ebd521e2ee | ||
|
|
9d5303113e | ||
|
|
3850fe15a5 | ||
|
|
5392481d06 | ||
|
|
0ce048517b | ||
|
|
c51e16e16c | ||
|
|
765a95050d | ||
|
|
38dcefffcb | ||
|
|
391b61dcb4 | ||
|
|
d76c9fcbcf | ||
|
|
f7b8ae2af2 | ||
|
|
ddbf9b60d4 | ||
|
|
8948ff5faa | ||
|
|
d270e202a8 | ||
|
|
61e1dd14ba | ||
|
|
eee93e0f1f | ||
|
|
826760a55c | ||
|
|
749398df8f | ||
|
|
150968d226 | ||
|
|
d81df24e7c | ||
|
|
594a5cf6ca | ||
|
|
0af420b824 | ||
|
|
31fed8362a | ||
|
|
dbcf8c6327 | ||
|
|
8012392400 | ||
|
|
3e48578b44 | ||
|
|
d7e656e57f | ||
|
|
8e83b923d9 | ||
|
|
a739f0f09f | ||
|
|
bc74737b0f | ||
|
|
e92fccf904 | ||
|
|
20cbedfff5 | ||
|
|
e0d508d3dd | ||
|
|
af318a2bae | ||
|
|
6b4e81c763 | ||
|
|
dcac63aa04 | ||
|
|
7818747e45 | ||
|
|
7cb1f856ca | ||
|
|
5955c8e7dc | ||
|
|
dee3c279e8 | ||
|
|
94427280d8 | ||
|
|
8b245b1cc9 | ||
|
|
0513cd10c4 | ||
|
|
28b5631d06 | ||
|
|
5f2cd92da7 | ||
|
|
4535125c54 | ||
|
|
8831da956a | ||
|
|
f2ec6e1d4c | ||
|
|
93f7def532 | ||
|
|
782f5517d3 | ||
|
|
f238c18ce8 | ||
|
|
4fe0ea373f | ||
|
|
982d21dd58 | ||
|
|
7002132bde | ||
|
|
1ef931f7e7 | ||
|
|
8797015c6a | ||
|
|
85a562d469 | ||
|
|
f50c62ba12 | ||
|
|
13f05a0995 | ||
|
|
1adddcc0d9 | ||
|
|
f03cd3f4c6 | ||
|
|
29a2e4eb99 | ||
|
|
7137a5808f | ||
|
|
fa6f451e11 | ||
|
|
666f247185 | ||
|
|
93dd25f954 | ||
|
|
8e2ba9552f | ||
|
|
1f02c5ea5e | ||
|
|
112b26c39d | ||
|
|
5e243ed01b | ||
|
|
706809d12a | ||
|
|
3fc06c2a74 | ||
|
|
4bc2c42982 | ||
|
|
6651fa4fa3 | ||
|
|
07d65a0046 | ||
|
|
4c9f062a70 | ||
|
|
893ee4a763 | ||
|
|
fa67d174d2 | ||
|
|
bb542521fb | ||
|
|
85d3cf2d77 | ||
|
|
ebf4cfb825 | ||
|
|
d7daa697df | ||
|
|
884484922d | ||
|
|
83ab751d4a | ||
|
|
fcee5bfa92 | ||
|
|
eb610ffe81 | ||
|
|
647cc25e57 | ||
|
|
3912b8e096 | ||
|
|
67f88416f1 | ||
|
|
8e3398df34 | ||
|
|
1a09405829 | ||
|
|
304054a4bb | ||
|
|
3c33bea7db | ||
|
|
15a7cc6e08 | ||
|
|
19b530d34b | ||
|
|
7fe8bc9f3b | ||
|
|
422fca4dc9 | ||
|
|
142dbe2c2c | ||
|
|
dced8ace1d | ||
|
|
de1fa71d8c | ||
|
|
158c99daef | ||
|
|
51e0023384 | ||
|
|
7f27056a34 | ||
|
|
cdbf5ea8e7 | ||
|
|
8bbd5e5a88 | ||
|
|
2e3c2c2424 | ||
|
|
80faa4bd4f | ||
|
|
de6f93b200 | ||
|
|
c46bfe05c1 | ||
|
|
0d50af6285 | ||
|
|
efb9ca5ac8 | ||
|
|
3ad5b62e27 | ||
|
|
8985aadcf1 | ||
|
|
907d52d693 | ||
|
|
eb5523a69c | ||
|
|
f475965cf7 | ||
|
|
1176cf029b | ||
|
|
d68fb81bcf | ||
|
|
81f7afe730 | ||
|
|
60e43a2794 | ||
|
|
76f686b580 | ||
|
|
cba4fdc397 | ||
|
|
62ea4bc67d | ||
|
|
25c7b7b780 | ||
|
|
6b3f44e923 | ||
|
|
9334585e0f | ||
|
|
1190511b54 | ||
|
|
b40d51841e | ||
|
|
f2ddee09c0 | ||
|
|
698cbceda3 | ||
|
|
66b1499fad | ||
|
|
e8748ce733 | ||
|
|
4bfa9c783c | ||
|
|
5cdfa086b2 | ||
|
|
e8824edfd4 | ||
|
|
507bd44bbf | ||
|
|
bfa08d178f | ||
|
|
9e01c96476 | ||
|
|
fe855f16f8 | ||
|
|
0fbc1b2121 | ||
|
|
b5d8acf9de | ||
|
|
1ab5bdb600 | ||
|
|
9060de1d60 | ||
|
|
1f83ab4450 | ||
|
|
e5680da5ce | ||
|
|
66bfcd6239 | ||
|
|
8f19c73908 | ||
|
|
716616210f | ||
|
|
cc414f71f4 | ||
|
|
d107dfcab1 | ||
|
|
875c03a0f6 | ||
|
|
4145987c65 | ||
|
|
232f3f624b | ||
|
|
2a2791c37f | ||
|
|
f1c9f5902a | ||
|
|
3f8d2a11d0 | ||
|
|
eb38741486 | ||
|
|
f207f57bd5 | ||
|
|
74b9f5fa4f | ||
|
|
6347c02d8b | ||
|
|
8dbfc093fa | ||
|
|
6acb847843 | ||
|
|
e270a46a36 | ||
|
|
e010116232 | ||
|
|
9bcbdb78fd | ||
|
|
85b0ec1e96 | ||
|
|
e4f42e2c2b | ||
|
|
8b5910773c | ||
|
|
1366158b45 | ||
|
|
d0dd86e6e8 | ||
|
|
af40860315 | ||
|
|
f3d7fbc483 | ||
|
|
d07066e540 | ||
|
|
dfb569c0f6 | ||
|
|
fda433706b | ||
|
|
a734be5f9e | ||
|
|
7ebd82d441 | ||
|
|
9bf5c1adf2 | ||
|
|
e813fc336d | ||
|
|
0c57765465 | ||
|
|
0ef1d4bfa3 | ||
|
|
252108c652 | ||
|
|
bad15cadd1 | ||
|
|
2b512087e0 | ||
|
|
3697146f44 | ||
|
|
a313522d08 | ||
|
|
e8668d52cd | ||
|
|
8944c0ac78 | ||
|
|
f2503723a4 | ||
|
|
405c2c3d2d | ||
|
|
99ad4130d9 | ||
|
|
c96d894714 | ||
|
|
dd0300d025 | ||
|
|
f27b64edef | ||
|
|
1f0e8dd87d | ||
|
|
0dfdad5f32 | ||
|
|
e729097df1 | ||
|
|
03f4234483 | ||
|
|
933bf1877f | ||
|
|
8895693dc4 | ||
|
|
2920247ab4 | ||
|
|
debf2e41d6 | ||
|
|
9d8cc5c907 | ||
|
|
054ad80d30 | ||
|
|
23548ef151 | ||
|
|
b488da4e33 | ||
|
|
1fb3d35021 | ||
|
|
eff994a9be | ||
|
|
10796b2359 | ||
|
|
60243c723a | ||
|
|
6e4fda2737 | ||
|
|
ab1b1f6c08 | ||
|
|
37e4d85978 | ||
|
|
ef9f17840d | ||
|
|
67980ea560 | ||
|
|
beef616caa | ||
|
|
7ce02ef0db | ||
|
|
eac2f30087 | ||
|
|
d647bae102 | ||
|
|
149ca05423 | ||
|
|
7ee79ee93a | ||
|
|
8463aafd7b | ||
|
|
42d728ac4b | ||
|
|
4e4f2fac6b | ||
|
|
edaf1005d4 | ||
|
|
236c6a2d04 | ||
|
|
ffa8fbf365 | ||
|
|
3e5421604b | ||
|
|
89471b797d | ||
|
|
7a6ea752e7 | ||
|
|
afa1493799 | ||
|
|
f47b5f5ba5 | ||
|
|
bbcbfb48d9 | ||
|
|
355985666e | ||
|
|
c789f4d4f7 | ||
|
|
699f38b575 | ||
|
|
2e6df89dc5 | ||
|
|
c0b29afc47 | ||
|
|
cd257137ba | ||
|
|
ab844fb075 | ||
|
|
869bb5ad87 | ||
|
|
be86eec99c | ||
|
|
53deae552e | ||
|
|
0a4e873110 | ||
|
|
8af406137e | ||
|
|
8d44dd948a | ||
|
|
6dab5c5936 | ||
|
|
e23d06ef70 | ||
|
|
57555a3f0b | ||
|
|
d70f77f622 | ||
|
|
a695c31b2b | ||
|
|
b282c335d4 | ||
|
|
74cf615e53 | ||
|
|
b8eb18d7bc | ||
|
|
de1833cc40 | ||
|
|
d7f3284aa2 | ||
|
|
7e82f75d14 | ||
|
|
757733e531 | ||
|
|
2cd01164a6 | ||
|
|
cae6479a3a | ||
|
|
bd236e89a3 | ||
|
|
e608e63d1c | ||
|
|
e5147b3c06 | ||
|
|
c58d8b58ff | ||
|
|
752e7f4d9a | ||
|
|
1946228d2b | ||
|
|
c3fcd280fb | ||
|
|
f6b2340016 | ||
|
|
ff98e378e6 | ||
|
|
73e7f3eaf1 | ||
|
|
5e15c38afb | ||
|
|
05c4d6d90c | ||
|
|
0c1efd03bb | ||
|
|
5a8cea6d44 | ||
|
|
cc7081cd19 | ||
|
|
5f93dca878 | ||
|
|
95bdaf7b3d | ||
|
|
29603313a5 | ||
|
|
0eeb7ffbc9 | ||
|
|
2f6f91e678 | ||
|
|
6992ed127b | ||
|
|
5f12e10fe9 | ||
|
|
4ed123fd52 | ||
|
|
ab5afa26ef | ||
|
|
f733a2edef | ||
|
|
b332993b77 | ||
|
|
d13c00bb20 | ||
|
|
089c6c1a46 | ||
|
|
c03b69cd17 | ||
|
|
42cd470e15 | ||
|
|
2145cfa9a6 | ||
|
|
cd461628ab | ||
|
|
0e782c4a93 | ||
|
|
425f2a4b85 | ||
|
|
90cbe37a92 | ||
|
|
cfe3825d63 | ||
|
|
7f403c18ea | ||
|
|
6ecc18d985 | ||
|
|
7a2c4f6c71 | ||
|
|
ded5c5d363 | ||
|
|
df951d354d | ||
|
|
815ec1b30e | ||
|
|
bb8db23057 | ||
|
|
6848cfa581 | ||
|
|
460997bca3 | ||
|
|
8addf0f078 | ||
|
|
957ebb5502 | ||
|
|
3eb1080129 | ||
|
|
efd78c3fc7 | ||
|
|
d02b4007c0 | ||
|
|
824fa469ef | ||
|
|
95345973cb | ||
|
|
962a1b228c | ||
|
|
35f4a4d1b4 | ||
|
|
7aed6d2e44 | ||
|
|
782b71c826 | ||
|
|
974d37f1e1 | ||
|
|
3619e4a66c | ||
|
|
b5a7ca96ee | ||
|
|
bd4c5edca4 | ||
|
|
a4c445d1a5 | ||
|
|
c71beb30f7 | ||
|
|
5bd184b297 | ||
|
|
364ce45668 | ||
|
|
a97761f65c | ||
|
|
c739f4f378 | ||
|
|
4705a323e3 | ||
|
|
7f353ca0fa | ||
|
|
b9d7e43fd1 | ||
|
|
58ed108a1b | ||
|
|
da9de1d2a8 | ||
|
|
31a038ce82 | ||
|
|
589dd66004 | ||
|
|
92ec441594 | ||
|
|
bba947e508 | ||
|
|
2be566668c | ||
|
|
49e2b2cfff | ||
|
|
08300b994c | ||
|
|
66fadd78a3 | ||
|
|
f0be045727 | ||
|
|
24202350ab | ||
|
|
4e6aca9a4e | ||
|
|
82ec92b209 | ||
|
|
7f11bfea63 | ||
|
|
2fbc38fa9a | ||
|
|
b53ecb4a1f | ||
|
|
e56920abd5 | ||
|
|
c34d99a234 | ||
|
|
a4c26371d6 | ||
|
|
c377534b62 | ||
|
|
2c3715aef6 | ||
|
|
ffcf5242ae | ||
|
|
34e0e0205b | ||
|
|
0e42042fa4 | ||
|
|
4443b58fc6 | ||
|
|
6b73a7a3cb | ||
|
|
7b9908664f | ||
|
|
834424eccc | ||
|
|
f873999652 | ||
|
|
9ae72058d9 | ||
|
|
0b9b3e3496 | ||
|
|
659f10c71a | ||
|
|
e2faba153f | ||
|
|
153ede4009 | ||
|
|
6efb814f3a | ||
|
|
88d38578e6 | ||
|
|
2817ce9d16 | ||
|
|
813a8003c6 | ||
|
|
510c6d4a90 | ||
|
|
a9c80b90f6 | ||
|
|
bbf76e1e64 | ||
|
|
67b53a898f | ||
|
|
ef255243ec | ||
|
|
10667f98ef | ||
|
|
a287e61b5a | ||
|
|
d42c831e80 | ||
|
|
7003dbdfc4 | ||
|
|
329768f874 | ||
|
|
7ef0bbc044 | ||
|
|
3a54088cf8 | ||
|
|
60732f8a60 | ||
|
|
6cc8955bce | ||
|
|
595ece8108 | ||
|
|
4bd1b2fab4 | ||
|
|
2ced0c902e | ||
|
|
1c9575ccfd | ||
|
|
785a82df93 | ||
|
|
b9f8ef2570 | ||
|
|
f64608f87c | ||
|
|
f1b9cbbf6c | ||
|
|
88001ef0eb | ||
|
|
d43cd368ee | ||
|
|
68a15913bd | ||
|
|
dc8b68931e | ||
|
|
47a4e5c447 | ||
|
|
4c093b2efc | ||
|
|
e248d32eb0 | ||
|
|
d2888bc479 | ||
|
|
d97448eb07 | ||
|
|
878369901c | ||
|
|
61f1e2481b | ||
|
|
9398c5004c | ||
|
|
6301dc4c7e | ||
|
|
4bfd857093 |
@@ -2,7 +2,7 @@
|
||||
"id": "org.kde.neochat",
|
||||
"branch": "master",
|
||||
"runtime": "org.kde.Platform",
|
||||
"runtime-version": "5.15-21.08",
|
||||
"runtime-version": "5.15-22.08",
|
||||
"sdk": "org.kde.Sdk",
|
||||
"command": "neochat",
|
||||
"tags": [
|
||||
@@ -22,6 +22,12 @@
|
||||
"--own-name=org.kde.StatusNotifierItem-2-2"
|
||||
],
|
||||
"modules": [
|
||||
{
|
||||
"name": "kirigamiaddons",
|
||||
"config-opts": [ "-DBUILD_TESTING=OFF" ],
|
||||
"buildsystem": "cmake-ninja",
|
||||
"sources": [ { "type": "git", "url": "https://invent.kde.org/libraries/kirigami-addons.git" } ]
|
||||
},
|
||||
{
|
||||
"name": "kquickimageeditor",
|
||||
"buildsystem": "cmake-ninja",
|
||||
@@ -35,6 +41,7 @@
|
||||
{
|
||||
"name": "olm",
|
||||
"buildsystem": "cmake-ninja",
|
||||
"config-opts": [ "-DOLM_TESTS=OFF" ],
|
||||
"sources": [
|
||||
{
|
||||
"type": "git",
|
||||
@@ -100,16 +107,19 @@
|
||||
{
|
||||
"type": "git",
|
||||
"url": "https://github.com/quotient-im/libQuotient.git",
|
||||
"branch": "dev"
|
||||
"branch": "dev",
|
||||
"disable-submodules": true
|
||||
}
|
||||
],
|
||||
"config-opts": [
|
||||
"-DQuotient_ENABLE_E2EE=ON"
|
||||
"-DQuotient_ENABLE_E2EE=ON",
|
||||
"-DBUILD_TESTING=OFF"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "cmark",
|
||||
"buildsystem": "cmake-ninja",
|
||||
"config-opts": [ "-DCMARK_TESTS=OFF" ],
|
||||
"sources": [
|
||||
{
|
||||
"type": "git",
|
||||
@@ -126,11 +136,12 @@
|
||||
{
|
||||
"name": "qcoro",
|
||||
"buildsystem": "cmake-ninja",
|
||||
"config-opts": [ "-DQCORO_BUILD_EXAMPLES=OFF", "-DBUILD_TESTING=OFF" ],
|
||||
"sources": [
|
||||
{
|
||||
"type": "archive",
|
||||
"url": "https://github.com/danvratil/qcoro/archive/refs/tags/v0.4.0.tar.gz",
|
||||
"sha256": "0e68b3f0ce7bf521ffbdd731464d2d60d8d7a39a749b551ed26855a1707d86d1",
|
||||
"url": "https://github.com/danvratil/qcoro/archive/refs/tags/v0.7.0.tar.gz",
|
||||
"sha256": "23ef0217926e67c8d2eb861cf91617da2f7d8d5a9ae6c62321b21448b1669210",
|
||||
"x-checker-data": {
|
||||
"type": "anitya",
|
||||
"project-id": 236236,
|
||||
|
||||
7
.gitignore
vendored
7
.gitignore
vendored
@@ -1,4 +1,4 @@
|
||||
build
|
||||
build*
|
||||
.clang-format
|
||||
.DS_Store
|
||||
.kdev4/
|
||||
@@ -7,3 +7,8 @@ compile_commands.json
|
||||
.cache/
|
||||
.vscode/
|
||||
kate.project.ctags.*
|
||||
*.user
|
||||
.flatpak-builder/
|
||||
.idea/
|
||||
cmake-build-*
|
||||
src/res.generated.qrc
|
||||
|
||||
@@ -4,9 +4,11 @@
|
||||
include:
|
||||
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/reuse-lint.yml
|
||||
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/android.yml
|
||||
# - https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/android-qt6.yml
|
||||
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/linux.yml
|
||||
# TODO enable once we can have qt6 libQuotient on the CI
|
||||
# - https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/linux-qt6.yml
|
||||
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/linux-qt6.yml
|
||||
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/windows.yml
|
||||
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/freebsd.yml
|
||||
# - https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/windows-qt6.yml
|
||||
# - https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/freebsd.yml
|
||||
# - https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/freebsd-qt6.yml
|
||||
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/flatpak.yml
|
||||
|
||||
52
.kde-ci.yml
52
.kde-ci.yml
@@ -1,8 +1,8 @@
|
||||
# SPDX-FileCopyrightText: 2021 Tobias Fella <fella@posteo.de>
|
||||
# SPDX-FileCopyrightText: 2021 Tobias Fella <tobias.fella@kde.org>
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
|
||||
Dependencies:
|
||||
- 'on': ['@all']
|
||||
- 'on': ['Linux/Qt5', 'Android/Qt5', 'FreeBSD/Qt5', 'Windows/Qt5']
|
||||
'require':
|
||||
'frameworks/extra-cmake-modules': '@stable'
|
||||
'frameworks/kcoreaddons': '@stable'
|
||||
@@ -11,18 +11,58 @@ Dependencies:
|
||||
'frameworks/kconfig': '@stable'
|
||||
'frameworks/syntax-highlighting': '@stable'
|
||||
'frameworks/kitemmodels': '@stable'
|
||||
'frameworks/kquickcharts': '@stable'
|
||||
'frameworks/knotifications': '@stable'
|
||||
'libraries/kquickimageeditor': '@stable'
|
||||
- 'on': ['Windows', 'Linux', 'FreeBSD']
|
||||
'frameworks/sonnet': '@stable'
|
||||
'libraries/kirigami-addons': '@latest'
|
||||
'third-party/libquotient': '@latest'
|
||||
'third-party/qtkeychain': '@latest'
|
||||
'third-party/cmark': '@latest'
|
||||
'third-party/qcoro': '@latest'
|
||||
- 'on': ['Windows/Qt5', 'Linux/Qt5', 'FreeBSD/Qt5']
|
||||
'require':
|
||||
'frameworks/qqc2-desktop-style': '@stable'
|
||||
'frameworks/kio': '@stable'
|
||||
'frameworks/kwindowsystem': '@stable'
|
||||
'frameworks/sonnet': '@stable'
|
||||
'frameworks/kconfigwidgets': '@stable'
|
||||
- 'on': ['Linux', 'FreeBSD']
|
||||
- 'on': ['Linux/Qt5', 'FreeBSD/Qt5']
|
||||
'require':
|
||||
'frameworks/kdbusaddons': '@stable'
|
||||
|
||||
- 'on': ['Linux/Qt6', 'Android/Qt6', 'FreeBSD/Qt6', 'Windows/Qt6']
|
||||
'require':
|
||||
'frameworks/extra-cmake-modules': '@latest-kf6'
|
||||
'frameworks/kcoreaddons': '@latest-kf6'
|
||||
'frameworks/kirigami': '@latest-kf6'
|
||||
'frameworks/ki18n': '@latest-kf6'
|
||||
'frameworks/kconfig': '@latest-kf6'
|
||||
'frameworks/syntax-highlighting': '@latest-kf6'
|
||||
'frameworks/kitemmodels': '@latest-kf6'
|
||||
'frameworks/kquickcharts': '@latest-kf6'
|
||||
'frameworks/knotifications': '@latest-kf6'
|
||||
'libraries/kquickimageeditor': '@latest-kf6'
|
||||
'frameworks/sonnet': '@latest-kf6'
|
||||
'libraries/kirigami-addons': '@latest-kf6'
|
||||
'third-party/libquotient': '@latest'
|
||||
'third-party/qtkeychain': '@latest'
|
||||
'third-party/cmark': '@latest'
|
||||
'third-party/qcoro': '@latest'
|
||||
- 'on': ['Windows/Qt6', 'Linux/Qt6', 'FreeBSD/Qt6']
|
||||
'require':
|
||||
'frameworks/qqc2-desktop-style': '@latest-kf6'
|
||||
'frameworks/kio': '@latest-kf6'
|
||||
'frameworks/kwindowsystem': '@latest-kf6'
|
||||
'frameworks/kconfigwidgets': '@latest-kf6'
|
||||
- 'on': ['Linux/Qt6', 'FreeBSD/Qt6']
|
||||
'require':
|
||||
'frameworks/kdbusaddons': '@latest-kf6'
|
||||
'frameworks/kstatusnotifieritem': '@latest-kf6'
|
||||
|
||||
- 'on': ['Linux/Qt6', 'Linux/Qt5']
|
||||
'require':
|
||||
'sdk/selenium-webdriver-at-spi': '@latest-kf6'
|
||||
|
||||
Options:
|
||||
require-passing-tests-on: [ 'Linux', 'Windows' ]
|
||||
per-test-timeout: 90
|
||||
require-passing-tests-on: [ 'Linux/Qt5', 'FreeBSD', 'Windows' ]
|
||||
|
||||
18
.reuse/dep5
18
.reuse/dep5
@@ -6,15 +6,11 @@ Files: 128-logo.png icons/* logo.png org.kde.neochat.svg org.kde.neochat.tray.sv
|
||||
Copyright: 2020 Carson Black <uhhadd@gmail.com>
|
||||
License: LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
|
||||
|
||||
Files: qtquickcontrols2.conf
|
||||
Copyright: 2020 Tobias Fella <fella@posteo.de>
|
||||
License: CC0-1.0
|
||||
|
||||
Files: android/res/drawable/splash.xml
|
||||
Copyright: 2020 Tobias Fella <fella@posteo.de>
|
||||
Copyright: 2020 Tobias Fella <tobias.fella@kde.org>
|
||||
License: BSD-2-Clause
|
||||
|
||||
Files: */qmldir .gitignore
|
||||
Files: .gitignore
|
||||
Copyright: None
|
||||
License: CC0-1.0
|
||||
|
||||
@@ -22,7 +18,7 @@ Files: .gitlab/issue_templates/bug.md
|
||||
Copyright: 2021 Carl Schwan <carlschwan@kde.org>
|
||||
License: CC0-1.0
|
||||
|
||||
Files: res.qrc res_android.qrc res_desktop.qrc
|
||||
Files: src/res.qrc src/res_android.qrc src/res_desktop.qrc
|
||||
Copyright: None
|
||||
License: CC0-1.0
|
||||
|
||||
@@ -31,17 +27,17 @@ Copyright: 2021 Carl Schwan <carlschwan@kde.org>
|
||||
License: BSD-2-Clause
|
||||
|
||||
Files: src/neochatconfig.kcfg
|
||||
Copyright: 2020-2021 Carl Schwan <carlschwan@kde.org>, Tobias Fella <fella@posteo.de>
|
||||
Copyright: 2020-2021 Carl Schwan <carlschwan@kde.org>, Tobias Fella <tobias.fella@kde.org>
|
||||
License: BSD-2-Clause
|
||||
|
||||
Files: src/neochat.notifyrc
|
||||
Copyright: 2020 Tobias Fella <fella@posteo.de>
|
||||
Copyright: 2020 Tobias Fella <tobias.fella@kde.org>
|
||||
License: BSD-2-Clause
|
||||
|
||||
Files: imports/NeoChat/Component/confetti.png imports/NeoChat/Component/glowdot.png
|
||||
Files: src/qml/Component/confetti.png src/qml/Component/glowdot.png
|
||||
Copyright: 2021 Alexey Andreyev <aa13q@ya.ru>
|
||||
License: CC0-1.0
|
||||
|
||||
Files: .flatpak-manifest.json
|
||||
Copyright: 2020-2022 Tobias Fella <fella@posteo.de>
|
||||
Copyright: 2020-2022 Tobias Fella <tobias.fella@kde.org>
|
||||
License: BSD-2-Clause
|
||||
|
||||
122
CMakeLists.txt
122
CMakeLists.txt
@@ -1,35 +1,45 @@
|
||||
# SPDX-FileCopyrightText: 2020-2021 Carl Schwan <carl@carlschwan.eu>
|
||||
# SPDX-FileCopyrightText: 2020-2021 Nicolas Fella <nicolas.fella@gmx.de>
|
||||
# SPDX-FileCopyrightText: 2020-2021 Tobias Fella <fella@posteo.de>
|
||||
# SPDX-FileCopyrightText: 2020-2021 Tobias Fella <tobias.fella@kde.org>
|
||||
# SPDX-FileCopyrightText: 2021 Adriaan de Groot <groot@kde.org>
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
project(NeoChat)
|
||||
set(PROJECT_VERSION "22.09")
|
||||
# KDE Applications version, managed by release script.
|
||||
set(RELEASE_SERVICE_VERSION_MAJOR "23")
|
||||
set(RELEASE_SERVICE_VERSION_MINOR "11")
|
||||
set(RELEASE_SERVICE_VERSION_MICRO "70")
|
||||
set(RELEASE_SERVICE_VERSION "${RELEASE_SERVICE_VERSION_MAJOR}.${RELEASE_SERVICE_VERSION_MINOR}.${RELEASE_SERVICE_VERSION_MICRO}")
|
||||
|
||||
set(KF5_MIN_VERSION "5.91.0")
|
||||
project(NeoChat VERSION ${RELEASE_SERVICE_VERSION})
|
||||
|
||||
set(KF_MIN_VERSION "5.105.0")
|
||||
set(QT_MIN_VERSION "5.15.2")
|
||||
if (ANDROID)
|
||||
set(QT_MIN_VERSION "5.15.10")
|
||||
endif()
|
||||
|
||||
find_package(ECM ${KF5_MIN_VERSION} REQUIRED NO_MODULE)
|
||||
find_package(ECM ${KF_MIN_VERSION} REQUIRED NO_MODULE)
|
||||
|
||||
set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
set(KDE_COMPILERSETTINGS_LEVEL 5.84)
|
||||
set(KDE_COMPILERSETTINGS_LEVEL 5.105)
|
||||
|
||||
include(FeatureSummary)
|
||||
include(ECMSetupVersion)
|
||||
include(KDEInstallDirs)
|
||||
include(ECMFindQmlModule)
|
||||
include(KDECMakeSettings)
|
||||
include(ECMAddTests)
|
||||
include(KDECompilerSettings NO_POLICY_SCOPE)
|
||||
include(ECMAddAppIcon)
|
||||
include(KDEGitCommitHooks)
|
||||
include(ECMCheckOutboundLicense)
|
||||
include(ECMQtDeclareLoggingCategory)
|
||||
if (NOT ANDROID)
|
||||
include(KDEClangFormat)
|
||||
endif()
|
||||
@@ -38,31 +48,54 @@ if(NEOCHAT_FLATPAK)
|
||||
include(cmake/Flatpak.cmake)
|
||||
endif()
|
||||
|
||||
if(QT_MAJOR_VERSION STREQUAL "6")
|
||||
set(BASICLISTITEM_BOLD "font.bold")
|
||||
set(OVERLAYSHEET_OPEN "onOpened")
|
||||
set(QTQUICK_MODULE_QML_VERSION "")
|
||||
set(QTLOCATION_MODULE_QML_VERSION "")
|
||||
set(QTMULTIMEDIA_MODULE_QML_VERSION "")
|
||||
set(QTMULTIMEDIA_AUDIO "MediaPlayer")
|
||||
# in Audio qt6 we don't have it but we disable it in qt5 => it seems ok
|
||||
set(QTMULTIMEDIA_AUDIO_AUTOLOAD "")
|
||||
# In Video qml qt6 we don't have it.
|
||||
set(QTMULTIMEDIA_VIDEO_FLUSHMODE "")
|
||||
else()
|
||||
set(BASICLISTITEM_BOLD "bold")
|
||||
set(OVERLAYSHEET_OPEN "onSheetOpenChanged")
|
||||
set(QTQUICK_MODULE_QML_VERSION "2.15")
|
||||
set(QTLOCATION_MODULE_QML_VERSION "5.15")
|
||||
set(QTMULTIMEDIA_MODULE_QML_VERSION "5.15")
|
||||
set(QTMULTIMEDIA_AUDIO "Audio")
|
||||
set(QTMULTIMEDIA_AUDIO_AUTOLOAD "autoLoad: false")
|
||||
set(QTMULTIMEDIA_VIDEO_FLUSHMODE "flushMode: VideoOutput.FirstFrame")
|
||||
endif()
|
||||
|
||||
set(QUOTIENT_FORCE_NAMESPACED_INCLUDES TRUE)
|
||||
|
||||
ecm_setup_version(${PROJECT_VERSION}
|
||||
VARIABLE_PREFIX NEOCHAT
|
||||
VERSION_HEADER ${CMAKE_CURRENT_BINARY_DIR}/neochat-version.h
|
||||
)
|
||||
|
||||
find_package(Qt${QT_MAJOR_VERSION} ${QT_MIN_VERSION} NO_MODULE COMPONENTS Core Quick Gui QuickControls2 Multimedia Svg)
|
||||
find_package(Qt${QT_MAJOR_VERSION} ${QT_MIN_VERSION} NO_MODULE COMPONENTS Core Quick Gui QuickControls2 Multimedia Svg WebView)
|
||||
set_package_properties(Qt${QT_MAJOR_VERSION} PROPERTIES
|
||||
TYPE REQUIRED
|
||||
PURPOSE "Basic application components"
|
||||
)
|
||||
find_package(KF5 ${KF5_MIN_VERSION} COMPONENTS Kirigami2 I18n Notifications Config CoreAddons)
|
||||
set_package_properties(KF5 PROPERTIES
|
||||
find_package(KF${QT_MAJOR_VERSION} ${KF_MIN_VERSION} COMPONENTS Kirigami2 I18n Notifications Config CoreAddons Sonnet ItemModels)
|
||||
set_package_properties(KF${QT_MAJOR_VERSION} PROPERTIES
|
||||
TYPE REQUIRED
|
||||
PURPOSE "Basic application components"
|
||||
)
|
||||
set_package_properties(KF5Kirigami2 PROPERTIES
|
||||
TYPE REQUIRED
|
||||
PURPOSE "Kirigami application UI framework"
|
||||
)
|
||||
set_package_properties(KF${QT_MAJOR_VERSION}Kirigami2 PROPERTIES
|
||||
TYPE REQUIRED
|
||||
PURPOSE "Kirigami application UI framework"
|
||||
)
|
||||
find_package(KF${QT_MAJOR_VERSION}KirigamiAddons 0.7.2 REQUIRED)
|
||||
|
||||
find_package(Qt${QT_MAJOR_VERSION}Keychain)
|
||||
set_package_properties(Qt${QT_MAJOR_VERSION}Keychain PROPERTIES
|
||||
TYPE REQUIRED
|
||||
PURPOSE "Secure storage of account secrets"
|
||||
)
|
||||
if(QT_MAJOR_VERSION STREQUAL "6")
|
||||
find_package(KF6StatusNotifierItem ${KF_MIN_VERSION} REQUIRED)
|
||||
endif()
|
||||
|
||||
if(ANDROID)
|
||||
find_package(OpenSSL)
|
||||
@@ -72,26 +105,34 @@ if(ANDROID)
|
||||
)
|
||||
else()
|
||||
find_package(Qt${QT_MAJOR_VERSION} ${QT_MIN_VERSION} COMPONENTS Widgets)
|
||||
find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS QQC2DesktopStyle ConfigWidgets KIO WindowSystem)
|
||||
set_package_properties(KF5QQC2DesktopStyle PROPERTIES
|
||||
find_package(KF${QT_MAJOR_VERSION} ${KF_MIN_VERSION} REQUIRED COMPONENTS QQC2DesktopStyle ConfigWidgets KIO WindowSystem)
|
||||
set_package_properties(KF${QT_MAJOR_VERSION}QQC2DesktopStyle PROPERTIES
|
||||
TYPE RUNTIME
|
||||
)
|
||||
ecm_find_qmlmodule(org.kde.sonnet 1.0)
|
||||
ecm_find_qmlmodule(org.kde.syntaxhighlighting 1.0)
|
||||
endif()
|
||||
|
||||
if (NOT ANDROID AND NOT WIN32 AND NOT APPLE)
|
||||
find_package(KF5DBusAddons ${KF5_MIN_VERSION} REQUIRED)
|
||||
find_package(KF${QT_MAJOR_VERSION}DBusAddons ${KF_MIN_VERSION} REQUIRED)
|
||||
endif()
|
||||
|
||||
find_package(Quotient 0.6)
|
||||
set_package_properties(Quotient PROPERTIES
|
||||
if(QT_MAJOR_VERSION STREQUAL "6" AND NOT ANDROID AND NOT WIN32)
|
||||
set(QUOTIENT_SUFFIX "Qt6")
|
||||
endif()
|
||||
|
||||
find_package(Quotient${QUOTIENT_SUFFIX} 0.7)
|
||||
set_package_properties(Quotient${QUOTIENT_SUFFIX} PROPERTIES
|
||||
TYPE REQUIRED
|
||||
DESCRIPTION "Qt wrapper around Matrix API"
|
||||
URL "https://github.com/quotient-im/libQuotient/"
|
||||
PURPOSE "Talk with matrix server"
|
||||
)
|
||||
|
||||
if (NOT TARGET Olm::Olm)
|
||||
message(FATAL_ERROR "NeoChat requires Quotient with the E2EE feature enabled")
|
||||
endif()
|
||||
|
||||
|
||||
find_package(cmark)
|
||||
set_package_properties(cmark PROPERTIES
|
||||
TYPE REQUIRED
|
||||
@@ -102,6 +143,8 @@ set_package_properties(cmark PROPERTIES
|
||||
|
||||
ecm_find_qmlmodule(org.kde.kquickimageeditor 1.0)
|
||||
ecm_find_qmlmodule(org.kde.kitemmodels 1.0)
|
||||
ecm_find_qmlmodule(org.kde.quickcharts 1.0)
|
||||
ecm_find_qmlmodule(QtLocation ${QTLOCATION_MODULE_QML_VERSION})
|
||||
|
||||
find_package(KQuickImageEditor COMPONENTS)
|
||||
set_package_properties(KQuickImageEditor PROPERTIES
|
||||
@@ -111,18 +154,18 @@ set_package_properties(KQuickImageEditor PROPERTIES
|
||||
PURPOSE "Add image editing capability to image attachments"
|
||||
)
|
||||
|
||||
find_package(QCoro${QT_MAJOR_VERSION} COMPONENTS Core QUIET)
|
||||
if(NOT QCoro${QT_MAJOR_VERSION}_FOUND)
|
||||
find_package(QCoro REQUIRED)
|
||||
endif()
|
||||
find_package(QCoro${QT_MAJOR_VERSION} 0.4 COMPONENTS Core REQUIRED)
|
||||
|
||||
qcoro_enable_coroutines()
|
||||
|
||||
if(NOT Quotient_VERSION_MINOR GREATER 6)
|
||||
cmake_policy(SET CMP0063 OLD)
|
||||
endif()
|
||||
find_package(KF${QT_MAJOR_VERSION}DocTools ${KF_MIN_VERSION})
|
||||
set_package_properties(KF${QT_MAJOR_VERSION}DocTools PROPERTIES DESCRIPTION
|
||||
"Tools to generate documentation"
|
||||
TYPE OPTIONAL
|
||||
)
|
||||
|
||||
if(ANDROID)
|
||||
find_package(Sqlite3)
|
||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/android/version.gradle.in ${CMAKE_BINARY_DIR}/version.gradle)
|
||||
endif()
|
||||
|
||||
@@ -137,6 +180,17 @@ add_definitions(-DQT_NO_FOREACH)
|
||||
|
||||
add_subdirectory(src)
|
||||
|
||||
if (BUILD_TESTING)
|
||||
find_package(Qt${QT_MAJOR_VERSION} ${QT_MIN_VERSION} NO_MODULE COMPONENTS Test)
|
||||
add_subdirectory(autotests)
|
||||
add_subdirectory(appiumtests)
|
||||
endif()
|
||||
|
||||
if(KF${QT_MAJOR_VERSION}DocTools_FOUND)
|
||||
kdoctools_install(po)
|
||||
add_subdirectory(doc)
|
||||
endif()
|
||||
|
||||
feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES)
|
||||
|
||||
if (NOT ANDROID)
|
||||
@@ -150,3 +204,9 @@ file(GLOB_RECURSE ALL_SOURCE_FILES *.cpp *.h *.qml)
|
||||
# Fixes the test by excluding this directory
|
||||
list(FILTER ALL_SOURCE_FILES EXCLUDE REGEX [[_(install|build)/.*]])
|
||||
ecm_check_outbound_license(LICENSES GPL-3.0-only FILES ${ALL_SOURCE_FILES})
|
||||
|
||||
ecm_qt_install_logging_categories(
|
||||
EXPORT NEOCHAT
|
||||
FILE neochat.categories
|
||||
DESTINATION ${KDE_INSTALL_LOGGINGCATEGORIESDIR}
|
||||
)
|
||||
|
||||
170
LICENSES/CC-BY-SA-4.0.txt
Normal file
170
LICENSES/CC-BY-SA-4.0.txt
Normal file
@@ -0,0 +1,170 @@
|
||||
Creative Commons Attribution-ShareAlike 4.0 International
|
||||
|
||||
Creative Commons Corporation (“Creative Commons”) is not a law firm and does not provide legal services or legal advice. Distribution of Creative Commons public licenses does not create a lawyer-client or other relationship. Creative Commons makes its licenses and related information available on an “as-is” basis. Creative Commons gives no warranties regarding its licenses, any material licensed under their terms and conditions, or any related information. Creative Commons disclaims all liability for damages resulting from their use to the fullest extent possible.
|
||||
|
||||
Using Creative Commons Public Licenses
|
||||
|
||||
Creative Commons public licenses provide a standard set of terms and conditions that creators and other rights holders may use to share original works of authorship and other material subject to copyright and certain other rights specified in the public license below. The following considerations are for informational purposes only, are not exhaustive, and do not form part of our licenses.
|
||||
|
||||
Considerations for licensors: Our public licenses are intended for use by those authorized to give the public permission to use material in ways otherwise restricted by copyright and certain other rights. Our licenses are irrevocable. Licensors should read and understand the terms and conditions of the license they choose before applying it. Licensors should also secure all rights necessary before applying our licenses so that the public can reuse the material as expected. Licensors should clearly mark any material not subject to the license. This includes other CC-licensed material, or material used under an exception or limitation to copyright. More considerations for licensors.
|
||||
|
||||
Considerations for the public: By using one of our public licenses, a licensor grants the public permission to use the licensed material under specified terms and conditions. If the licensor’s permission is not necessary for any reason–for example, because of any applicable exception or limitation to copyright–then that use is not regulated by the license. Our licenses grant only permissions under copyright and certain other rights that a licensor has authority to grant. Use of the licensed material may still be restricted for other reasons, including because others have copyright or other rights in the material. A licensor may make special requests, such as asking that all changes be marked or described.
|
||||
|
||||
Although not required by our licenses, you are encouraged to respect those requests where reasonable. More considerations for the public.
|
||||
|
||||
Creative Commons Attribution-ShareAlike 4.0 International Public License
|
||||
|
||||
By exercising the Licensed Rights (defined below), You accept and agree to be bound by the terms and conditions of this Creative Commons Attribution-ShareAlike 4.0 International Public License ("Public License"). To the extent this Public License may be interpreted as a contract, You are granted the Licensed Rights in consideration of Your acceptance of these terms and conditions, and the Licensor grants You such rights in consideration of benefits the Licensor receives from making the Licensed Material available under these terms and conditions.
|
||||
|
||||
Section 1 – Definitions.
|
||||
|
||||
a. Adapted Material means material subject to Copyright and Similar Rights that is derived from or based upon the Licensed Material and in which the Licensed Material is translated, altered, arranged, transformed, or otherwise modified in a manner requiring permission under the Copyright and Similar Rights held by the Licensor. For purposes of this Public License, where the Licensed Material is a musical work, performance, or sound recording, Adapted Material is always produced where the Licensed Material is synched in timed relation with a moving image.
|
||||
|
||||
b. Adapter's License means the license You apply to Your Copyright and Similar Rights in Your contributions to Adapted Material in accordance with the terms and conditions of this Public License.
|
||||
|
||||
c. BY-SA Compatible License means a license listed at creativecommons.org/compatiblelicenses, approved by Creative Commons as essentially the equivalent of this Public License.
|
||||
|
||||
d. Copyright and Similar Rights means copyright and/or similar rights closely related to copyright including, without limitation, performance, broadcast, sound recording, and Sui Generis Database Rights, without regard to how the rights are labeled or categorized. For purposes of this Public License, the rights specified in Section 2(b)(1)-(2) are not Copyright and Similar Rights.
|
||||
|
||||
e. Effective Technological Measures means those measures that, in the absence of proper authority, may not be circumvented under laws fulfilling obligations under Article 11 of the WIPO Copyright Treaty adopted on December 20, 1996, and/or similar international agreements.
|
||||
|
||||
f. Exceptions and Limitations means fair use, fair dealing, and/or any other exception or limitation to Copyright and Similar Rights that applies to Your use of the Licensed Material.
|
||||
|
||||
g. License Elements means the license attributes listed in the name of a Creative Commons Public License. The License Elements of this Public License are Attribution and ShareAlike.
|
||||
|
||||
h. Licensed Material means the artistic or literary work, database, or other material to which the Licensor applied this Public License.
|
||||
|
||||
i. Licensed Rights means the rights granted to You subject to the terms and conditions of this Public License, which are limited to all Copyright and Similar Rights that apply to Your use of the Licensed Material and that the Licensor has authority to license.
|
||||
|
||||
j. Licensor means the individual(s) or entity(ies) granting rights under this Public License.
|
||||
|
||||
k. Share means to provide material to the public by any means or process that requires permission under the Licensed Rights, such as reproduction, public display, public performance, distribution, dissemination, communication, or importation, and to make material available to the public including in ways that members of the public may access the material from a place and at a time individually chosen by them.
|
||||
|
||||
l. Sui Generis Database Rights means rights other than copyright resulting from Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, as amended and/or succeeded, as well as other essentially equivalent rights anywhere in the world.
|
||||
|
||||
m. You means the individual or entity exercising the Licensed Rights under this Public License. Your has a corresponding meaning.
|
||||
|
||||
Section 2 – Scope.
|
||||
|
||||
a. License grant.
|
||||
|
||||
1. Subject to the terms and conditions of this Public License, the Licensor hereby grants You a worldwide, royalty-free, non-sublicensable, non-exclusive, irrevocable license to exercise the Licensed Rights in the Licensed Material to:
|
||||
|
||||
A. reproduce and Share the Licensed Material, in whole or in part; and
|
||||
|
||||
B. produce, reproduce, and Share Adapted Material.
|
||||
|
||||
2. Exceptions and Limitations. For the avoidance of doubt, where Exceptions and Limitations apply to Your use, this Public License does not apply, and You do not need to comply with its terms and conditions.
|
||||
|
||||
3. Term. The term of this Public License is specified in Section 6(a).
|
||||
|
||||
4. Media and formats; technical modifications allowed. The Licensor authorizes You to exercise the Licensed Rights in all media and formats whether now known or hereafter created, and to make technical modifications necessary to do so. The Licensor waives and/or agrees not to assert any right or authority to forbid You from making technical modifications necessary to exercise the Licensed Rights, including technical modifications necessary to circumvent Effective Technological Measures. For purposes of this Public License, simply making modifications authorized by this Section 2(a)(4) never produces Adapted Material.
|
||||
|
||||
5. Downstream recipients.
|
||||
|
||||
A. Offer from the Licensor – Licensed Material. Every recipient of the Licensed Material automatically receives an offer from the Licensor to exercise the Licensed Rights under the terms and conditions of this Public License.
|
||||
|
||||
B. Additional offer from the Licensor – Adapted Material. Every recipient of Adapted Material from You automatically receives an offer from the Licensor to exercise the Licensed Rights in the Adapted Material under the conditions of the Adapter’s License You apply.
|
||||
|
||||
C. No downstream restrictions. You may not offer or impose any additional or different terms or conditions on, or apply any Effective Technological Measures to, the Licensed Material if doing so restricts exercise of the Licensed Rights by any recipient of the Licensed Material.
|
||||
|
||||
6. No endorsement. Nothing in this Public License constitutes or may be construed as permission to assert or imply that You are, or that Your use of the Licensed Material is, connected with, or sponsored, endorsed, or granted official status by, the Licensor or others designated to receive attribution as provided in Section 3(a)(1)(A)(i).
|
||||
|
||||
b. Other rights.
|
||||
|
||||
1. Moral rights, such as the right of integrity, are not licensed under this Public License, nor are publicity, privacy, and/or other similar personality rights; however, to the extent possible, the Licensor waives and/or agrees not to assert any such rights held by the Licensor to the limited extent necessary to allow You to exercise the Licensed Rights, but not otherwise.
|
||||
|
||||
2. Patent and trademark rights are not licensed under this Public License.
|
||||
|
||||
3. To the extent possible, the Licensor waives any right to collect royalties from You for the exercise of the Licensed Rights, whether directly or through a collecting society under any voluntary or waivable statutory or compulsory licensing scheme. In all other cases the Licensor expressly reserves any right to collect such royalties.
|
||||
|
||||
Section 3 – License Conditions.
|
||||
|
||||
Your exercise of the Licensed Rights is expressly made subject to the following conditions.
|
||||
|
||||
a. Attribution.
|
||||
|
||||
1. If You Share the Licensed Material (including in modified form), You must:
|
||||
|
||||
A. retain the following if it is supplied by the Licensor with the Licensed Material:
|
||||
|
||||
i. identification of the creator(s) of the Licensed Material and any others designated to receive attribution, in any reasonable manner requested by the Licensor (including by pseudonym if designated);
|
||||
|
||||
ii. a copyright notice;
|
||||
|
||||
iii. a notice that refers to this Public License;
|
||||
|
||||
iv. a notice that refers to the disclaimer of warranties;
|
||||
|
||||
v. a URI or hyperlink to the Licensed Material to the extent reasonably practicable;
|
||||
|
||||
B. indicate if You modified the Licensed Material and retain an indication of any previous modifications; and
|
||||
|
||||
C. indicate the Licensed Material is licensed under this Public License, and include the text of, or the URI or hyperlink to, this Public License.
|
||||
|
||||
2. You may satisfy the conditions in Section 3(a)(1) in any reasonable manner based on the medium, means, and context in which You Share the Licensed Material. For example, it may be reasonable to satisfy the conditions by providing a URI or hyperlink to a resource that includes the required information.
|
||||
|
||||
3. If requested by the Licensor, You must remove any of the information required by Section 3(a)(1)(A) to the extent reasonably practicable.
|
||||
|
||||
b. ShareAlike.In addition to the conditions in Section 3(a), if You Share Adapted Material You produce, the following conditions also apply.
|
||||
|
||||
1. The Adapter’s License You apply must be a Creative Commons license with the same License Elements, this version or later, or a BY-SA Compatible License.
|
||||
|
||||
2. You must include the text of, or the URI or hyperlink to, the Adapter's License You apply. You may satisfy this condition in any reasonable manner based on the medium, means, and context in which You Share Adapted Material.
|
||||
|
||||
3. You may not offer or impose any additional or different terms or conditions on, or apply any Effective Technological Measures to, Adapted Material that restrict exercise of the rights granted under the Adapter's License You apply.
|
||||
|
||||
Section 4 – Sui Generis Database Rights.
|
||||
|
||||
Where the Licensed Rights include Sui Generis Database Rights that apply to Your use of the Licensed Material:
|
||||
|
||||
a. for the avoidance of doubt, Section 2(a)(1) grants You the right to extract, reuse, reproduce, and Share all or a substantial portion of the contents of the database;
|
||||
|
||||
b. if You include all or a substantial portion of the database contents in a database in which You have Sui Generis Database Rights, then the database in which You have Sui Generis Database Rights (but not its individual contents) is Adapted Material, including for purposes of Section 3(b); and
|
||||
|
||||
c. You must comply with the conditions in Section 3(a) if You Share all or a substantial portion of the contents of the database.
|
||||
For the avoidance of doubt, this Section 4 supplements and does not replace Your obligations under this Public License where the Licensed Rights include other Copyright and Similar Rights.
|
||||
|
||||
Section 5 – Disclaimer of Warranties and Limitation of Liability.
|
||||
|
||||
a. Unless otherwise separately undertaken by the Licensor, to the extent possible, the Licensor offers the Licensed Material as-is and as-available, and makes no representations or warranties of any kind concerning the Licensed Material, whether express, implied, statutory, or other. This includes, without limitation, warranties of title, merchantability, fitness for a particular purpose, non-infringement, absence of latent or other defects, accuracy, or the presence or absence of errors, whether or not known or discoverable. Where disclaimers of warranties are not allowed in full or in part, this disclaimer may not apply to You.
|
||||
|
||||
b. To the extent possible, in no event will the Licensor be liable to You on any legal theory (including, without limitation, negligence) or otherwise for any direct, special, indirect, incidental, consequential, punitive, exemplary, or other losses, costs, expenses, or damages arising out of this Public License or use of the Licensed Material, even if the Licensor has been advised of the possibility of such losses, costs, expenses, or damages. Where a limitation of liability is not allowed in full or in part, this limitation may not apply to You.
|
||||
|
||||
c. The disclaimer of warranties and limitation of liability provided above shall be interpreted in a manner that, to the extent possible, most closely approximates an absolute disclaimer and waiver of all liability.
|
||||
|
||||
Section 6 – Term and Termination.
|
||||
|
||||
a. This Public License applies for the term of the Copyright and Similar Rights licensed here. However, if You fail to comply with this Public License, then Your rights under this Public License terminate automatically.
|
||||
|
||||
b. Where Your right to use the Licensed Material has terminated under Section 6(a), it reinstates:
|
||||
|
||||
1. automatically as of the date the violation is cured, provided it is cured within 30 days of Your discovery of the violation; or
|
||||
|
||||
2. upon express reinstatement by the Licensor.
|
||||
|
||||
c. For the avoidance of doubt, this Section 6(b) does not affect any right the Licensor may have to seek remedies for Your violations of this Public License.
|
||||
|
||||
d. For the avoidance of doubt, the Licensor may also offer the Licensed Material under separate terms or conditions or stop distributing the Licensed Material at any time; however, doing so will not terminate this Public License.
|
||||
|
||||
e. Sections 1, 5, 6, 7, and 8 survive termination of this Public License.
|
||||
|
||||
Section 7 – Other Terms and Conditions.
|
||||
|
||||
a. The Licensor shall not be bound by any additional or different terms or conditions communicated by You unless expressly agreed.
|
||||
|
||||
b. Any arrangements, understandings, or agreements regarding the Licensed Material not stated herein are separate from and independent of the terms and conditions of this Public License.
|
||||
|
||||
Section 8 – Interpretation.
|
||||
|
||||
a. For the avoidance of doubt, this Public License does not, and shall not be interpreted to, reduce, limit, restrict, or impose conditions on any use of the Licensed Material that could lawfully be made without permission under this Public License.
|
||||
|
||||
b. To the extent possible, if any provision of this Public License is deemed unenforceable, it shall be automatically reformed to the minimum extent necessary to make it enforceable. If the provision cannot be reformed, it shall be severed from this Public License without affecting the enforceability of the remaining terms and conditions.
|
||||
|
||||
c. No term or condition of this Public License will be waived and no failure to comply consented to unless expressly agreed to by the Licensor.
|
||||
|
||||
d. Nothing in this Public License constitutes or may be interpreted as a limitation upon, or waiver of, any privileges and immunities that apply to the Licensor or You, including from the legal processes of any jurisdiction or authority.
|
||||
|
||||
Creative Commons is not a party to its public licenses. Notwithstanding, Creative Commons may elect to apply one of its public licenses to material it publishes and in those instances will be considered the “Licensor.” Except for the limited purpose of indicating that material is shared under a Creative Commons public license or as otherwise permitted by the Creative Commons policies published at creativecommons.org/policies, Creative Commons does not authorize the use of the trademark “Creative Commons” or any other trademark or logo of Creative Commons without its prior written consent including, without limitation, in connection with any unauthorized modifications to any of its public licenses or any other arrangements, understandings, or agreements concerning use of licensed material. For the avoidance of doubt, this paragraph does not form part of the public licenses.
|
||||
|
||||
Creative Commons may be contacted at creativecommons.org.
|
||||
0
LICENSES/MIT.txt
Executable file → Normal file
0
LICENSES/MIT.txt
Executable file → Normal file
102
README.md
102
README.md
@@ -1,24 +1,51 @@
|
||||
<!--
|
||||
SPDX-FileCopyrightText: 2020-2021 Carl Schwan <carlschwan@kde.org>
|
||||
SPDX-FileCopyrightText: 2020-2021 Tobias Fella <fella@posteo.de>
|
||||
SPDX-FileCopyrightText: 2020-2021 Tobias Fella <tobias.fella@kde.org>
|
||||
SPDX-FileCopyrightText: 2023 James Graham <james.h.graham@protonmail.com>
|
||||
SPDX-License-Identifier: CC0-1.0
|
||||
-->
|
||||
|
||||
# NeoChat
|
||||
|
||||
NeoChat is a client for Matrix, the decentralized communication protocol for instant
|
||||
messaging. It is a fork of Spectral, using KDE frameworks, most notably Kirigami,
|
||||
KConfig and KI18n.
|
||||
A Qt/QML based Matrix client.
|
||||
|
||||
<a href='https://matrix.org'><img src='https://matrix.org/docs/legacy/made-for-matrix.png' alt='Made for Matrix' height=64 target=_blank /></a>
|
||||
<a href='https://flathub.org/apps/details/org.kde.neochat'><img width='190px' alt='Download on Flathub' src='https://flathub.org/assets/badges/flathub-badge-i-en.png'/></a>
|
||||
|
||||
## Introduction
|
||||
|
||||
NeoChat is a client for [Matrix](https://matrix.org), the decentralized communication protocol for instant
|
||||
messaging. It is a fork of Spectral, using KDE frameworks, most notably [Kirigami](https://invent.kde.org/frameworks/kirigami)
|
||||
to provide a convergent experience across multiple platforms.
|
||||
|
||||
NeoChat also make use of other KDE Frameworks as well as [libQuotient](https://github.com/quotient-im/libQuotient), a
|
||||
Qt-based SDK for the [Matrix Protocol](https://spec.matrix.org/).
|
||||
|
||||

|
||||
|
||||
## Features
|
||||
|
||||
NeoChat aims to be a fully featured application for the Matrix specification. As such everything in the current stable specification with the notable exceptions
|
||||
of VoIP, threads and some aspects of End-to-End Encryption are supported. There are a few other smaller omissions due to the fact that the Matrix spec is constantly
|
||||
evolving but the aim remains to provide eventual support for the entire spec.
|
||||
|
||||
Due to the nature of the Matrix specification development NeoChat also supports numerous unstable features. Currently these are:
|
||||
- Polls - MSC3381
|
||||
- Sticker Packs - MSC2545
|
||||
- Location Events - MSC3488
|
||||
|
||||
## Get it
|
||||
|
||||
A stable release [is available](https://apps.kde.org/neochat) for download for Linux distributions.
|
||||
Details where to find stable releases for NeoChat can be found on its [homepage](https://apps.kde.org/neochat).
|
||||
|
||||
In addition to the stable builds, unstable nightly builds are available for all platforms. These can be downloaded
|
||||
from the [binary factory](https://binary-factory.kde.org/). There are unstable versions for the following platforms
|
||||
in addition to stable ones:
|
||||
- Android
|
||||
- MacOS
|
||||
- Windows
|
||||
|
||||
Along with the stable release, a Flatpak version is available for the nightly
|
||||
version:
|
||||
Additionally the nightly Flatpak version can be obtained from the nightly Flatpak repo using the following commands in your terminal:
|
||||
|
||||
```
|
||||
flatpak remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo
|
||||
@@ -26,42 +53,53 @@ flatpak remote-add --if-not-exists kdeapps --from https://distribute.kde.org/kde
|
||||
flatpak install kdeapps org.kde.neochat
|
||||
```
|
||||
|
||||
A nightly build is also available for Android in the [KDE nightly F-Droid repo](https://community.kde.org/Android/FDroid)
|
||||
and can also directly be downloaded from the [binary factory](https://binary-factory.kde.org/view/Android/job/NeoChat_Nightly_android-arm64/).
|
||||
The unstable Android version can also be obtained from the [KDE nightly F-Droid repo](https://community.kde.org/Android/FDroid).
|
||||
|
||||
Nightly builds for [Windows](https://binary-factory.kde.org/job/NeoChat_Nightly_win64/), [MacOS](https://binary-factory.kde.org/job/NeoChat_Nightly_macos/) and [AppImages](https://binary-factory.kde.org/job/NeoChat_Nightly_appimage/) can also be downloaded from the [binary factory](https://binary-factory.kde.org/search/?q=neochat).
|
||||
## Running
|
||||
|
||||

|
||||
Just start the executable in your preferred way - either from the build directory or from the installed location.
|
||||
|
||||
## Features
|
||||
## Building NeoChat
|
||||
|
||||
* Sending messages
|
||||
* Sending files from clipboard and filesystem
|
||||
* Reply to message (right-click on a message to access menu)
|
||||
* Start a private chat (but not encrypted)
|
||||
* Show notifications, for the moment there is only a global switch
|
||||
to disable it. We plan to implement the configuration part of the
|
||||
specification soon.
|
||||
* Autocompletion of usernames in chat
|
||||
* Emoji picker
|
||||
* Basic room setting page
|
||||
* Send and accept invitations
|
||||
* /rainbow <message> (very important)
|
||||
* /me <message>
|
||||
The best way to build KDE apps during development is to use `kdesrc-build`. The full instructions for this can be found on
|
||||
the KDE community website's get involved section under [development](https://community.kde.org/Get_Involved/development). This
|
||||
is primarily aimed at Linux development.
|
||||
|
||||
NeoChat is still missing a few features to become a full-featured
|
||||
Matrix client (most notably encryption support and video chat support).
|
||||
We welcome contributions in this direction.
|
||||
For Windows and Android [Craft](https://invent.kde.org/packaging/craft) is the primary choice. There are guides for setting up
|
||||
development environments for [Windows](https://community.kde.org/Get_Involved/development/Windows) and [Android](https://develop.kde.org/docs/packaging/android/building_applications/).
|
||||
|
||||
## Tests
|
||||
|
||||
Tests are in the repository under [autotests](autotests) and should all pass for any contribution.
|
||||
|
||||
The project has CI setup to test new commits to the repository. All tests are expected to pass for a merge request to
|
||||
be complete.
|
||||
|
||||
Current build status
|
||||
|
||||

|
||||
|
||||
Currently the number of tests is limited, but growing. If anyone wants to help improve this, those
|
||||
contributions would be especially welcome.
|
||||
|
||||
## Contributing
|
||||
|
||||
As is the case throughout the KDE ecosystem contributions are welcome from all. The code base is managed in the
|
||||
[NeoChat repository](https://invent.kde.org/network/neochat) of the KDE Gitlab instance.
|
||||
|
||||
- [Code of Conduct](https://kde.org/code-of-conduct)
|
||||
- [Report a Bug](https://bugs.kde.org/enter_bug.cgi?format=guided&product=neochat)
|
||||
- [Feature Request](https://community.kde.org/Infrastructure/GitLab#Submitting_a_merge_request)
|
||||
- [Create a Merge Request](https://community.kde.org/Infrastructure/GitLab#Submitting_a_merge_request)
|
||||
- [Translation](https://community.kde.org/Get_Involved/translation)
|
||||
|
||||
## Contact
|
||||
|
||||
You can reach the maintainers at [#neochat:kde.org](https://matrix.to/#/#neochat:kde.org), if you are already on Matrix.
|
||||
Development happens in http://invent.kde.org/network/neochat (not in GitHub).
|
||||
The best place to reach the maintainers is on the KDE Matrix instance in the NeoChat channel, [#neochat:kde.org](https://matrix.to/#/#neochat:kde.org).
|
||||
|
||||
## Acknowledgement
|
||||
|
||||
This program utilizes [libQuotient](https://github.com/quotient-im/libQuotient/)
|
||||
library and some C++ models from [Quaternion](https://github.com/quotient-im/Quaternion/).
|
||||
This program utilizes [libQuotient](https://github.com/quotient-im/libQuotient/) as its Matrix SDK.
|
||||
|
||||
This program is a fork of [Spectral](https://gitlab.com/spectral-im/spectral/).
|
||||
|
||||
|
||||
@@ -9,12 +9,13 @@
|
||||
android:versionName="${versionName}"
|
||||
android:versionCode="${versionCode}"
|
||||
android:installLocation="auto">
|
||||
<application android:name="org.qtproject.qt5.android.bindings.QtApplication" android:label="NeoChat" android:icon="@drawable/neochat">
|
||||
<application android:name="org.qtproject.qt5.android.bindings.QtApplication" android:label="NeoChat" android:icon="@drawable/neochat" android:usesCleartextTraffic="true">
|
||||
<activity android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|layoutDirection|locale|fontScale|keyboard|keyboardHidden|navigation"
|
||||
android:name="org.qtproject.qt5.android.bindings.QtActivity"
|
||||
android:label="NeoChat"
|
||||
android:windowSoftInputMode="adjustResize"
|
||||
android:launchMode="singleTop">
|
||||
android:launchMode="singleTop"
|
||||
android:exported="true">
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
@@ -22,7 +23,6 @@
|
||||
</intent-filter>
|
||||
|
||||
<meta-data android:name="android.app.lib_name" android:value="neochat-app"/>
|
||||
<meta-data android:name="android.app.qt_sources_resource_id" android:resource="@array/qt_sources"/>
|
||||
<meta-data android:name="android.app.repository" android:value="default"/>
|
||||
<meta-data android:name="android.app.qt_libs_resource_id" android:resource="@array/qt_libs"/>
|
||||
<meta-data android:name="android.app.bundled_libs_resource_id" android:resource="@array/bundled_libs"/>
|
||||
@@ -38,8 +38,6 @@
|
||||
<meta-data android:name="android.app.load_local_jars" android:value="-- %%INSERT_LOCAL_JARS%% --"/>
|
||||
<meta-data android:name="android.app.static_init_classes" android:value="-- %%INSERT_INIT_CLASSES%% --"/>
|
||||
<!-- Messages maps -->
|
||||
<meta-data android:value="@string/ministro_not_found_msg" android:name="android.app.ministro_not_found_msg"/>
|
||||
<meta-data android:value="@string/ministro_needed_msg" android:name="android.app.ministro_needed_msg"/>
|
||||
<meta-data android:value="@string/fatal_error_msg" android:name="android.app.fatal_error_msg"/>
|
||||
|
||||
<!-- Splash screen -->
|
||||
|
||||
@@ -12,7 +12,7 @@ buildscript {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:3.6.4'
|
||||
classpath 'com.android.tools.build:gradle:7.0.2'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,6 +47,7 @@ android {
|
||||
compileSdkVersion androidCompileSdkVersion.toInteger()
|
||||
|
||||
buildToolsVersion androidBuildToolsVersion
|
||||
ndkVersion androidNdkVersion
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
@@ -73,6 +74,10 @@ android {
|
||||
defaultConfig {
|
||||
minSdkVersion qtMinSdkVersion
|
||||
targetSdkVersion qtTargetSdkVersion
|
||||
applicationId "org.kde.neochat"
|
||||
namespace "org.kde.neochat"
|
||||
versionCode timestamp
|
||||
versionName projectVersionFull
|
||||
manifestPlaceholders = [versionName: projectVersionFull, versionCode: timestamp]
|
||||
}
|
||||
|
||||
|
||||
23
appiumtests/CMakeLists.txt
Normal file
23
appiumtests/CMakeLists.txt
Normal file
@@ -0,0 +1,23 @@
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
# SPDX-FileCopyrightText: 2022 Harald Sitter <sitter@kde.org>
|
||||
|
||||
|
||||
if(NOT BUILD_TESTING OR NOT CMAKE_SYSTEM_NAME MATCHES "Linux")
|
||||
return()
|
||||
endif()
|
||||
|
||||
find_package(SeleniumWebDriverATSPI)
|
||||
set_package_properties(SeleniumWebDriverATSPI PROPERTIES
|
||||
DESCRIPTION "Server component for selenium tests using Linux accessibility infrastructure"
|
||||
PURPOSE "Needed for GUI tests"
|
||||
URL "https://invent.kde.org/sdk/selenium-webdriver-at-spi"
|
||||
TYPE OPTIONAL
|
||||
)
|
||||
if(NOT SeleniumWebDriverATSPI_FOUND)
|
||||
return()
|
||||
endif()
|
||||
|
||||
add_test(
|
||||
NAME logintest
|
||||
COMMAND selenium-webdriver-at-spi-run ${CMAKE_CURRENT_SOURCE_DIR}/logintest.py
|
||||
)
|
||||
42
appiumtests/login-server.py
Normal file
42
appiumtests/login-server.py
Normal file
@@ -0,0 +1,42 @@
|
||||
# SPDX-License-Identifier: MIT
|
||||
# SPDX-FileCopyrightText: 2023 Tobias Fella <tobias.fella@kde.org>
|
||||
|
||||
|
||||
from flask import Flask, request, abort
|
||||
app = Flask(__name__)
|
||||
|
||||
@app.route("/_matrix/client/v3/login", methods=["GET"])
|
||||
def login_get():
|
||||
result = dict()
|
||||
result["flows"] = [dict()]
|
||||
result["flows"][0]["type"] = "m.login.password"
|
||||
return result
|
||||
|
||||
@app.route("/_matrix/client/v3/login", methods=["POST"])
|
||||
def login_post():
|
||||
data = request.get_json()
|
||||
if data["identifier"]["user"] != "user" or data["password"] != "1234":
|
||||
abort(403)
|
||||
print(data)
|
||||
result = dict()
|
||||
result["access_token"] = "token_1234"
|
||||
result["device_id"] = "device_1234"
|
||||
result["user_id"] = "@user:localhost:1234"
|
||||
return result
|
||||
|
||||
@app.route("/_matrix/client/r0/sync")
|
||||
def sync():
|
||||
result = dict()
|
||||
result["next_batch"] = "batch1234"
|
||||
return result
|
||||
|
||||
@app.route("/.well-known/matrix/client")
|
||||
def well_known():
|
||||
reply = dict()
|
||||
reply["m.homeserver"] = dict()
|
||||
reply["m.homeserver"]["base_url"] = "https://localhost:1234"
|
||||
return reply
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run(ssl_context='adhoc', port=1234)
|
||||
49
appiumtests/logintest.py
Executable file
49
appiumtests/logintest.py
Executable file
@@ -0,0 +1,49 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# SPDX-License-Identifier: MIT
|
||||
# SPDX-FileCopyrightText: 2021-2022 Harald Sitter <sitter@kde.org>
|
||||
# SPDX-FileCopyrightText: 2023 Tobias Fella <tobias.fella@kde.org>
|
||||
|
||||
import unittest
|
||||
from appium import webdriver
|
||||
from appium.webdriver.common.appiumby import AppiumBy
|
||||
from selenium.webdriver.support.ui import WebDriverWait
|
||||
import os
|
||||
import time
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
class LoginTest(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(self):
|
||||
desired_caps = {}
|
||||
desired_caps["app"] = "neochat --ignore-ssl-errors"
|
||||
desired_caps["timeouts"] = {'implicit': 10000}
|
||||
self.driver = webdriver.Remote(
|
||||
command_executor='http://127.0.0.1:4723',
|
||||
desired_capabilities=desired_caps)
|
||||
self.mockServerProcess = subprocess.Popen([sys.executable, os.path.join(os.path.dirname(__file__), "login-server.py")])
|
||||
|
||||
def setUp(self):
|
||||
pass
|
||||
|
||||
def tearDown(self):
|
||||
if not self._outcome.result.wasSuccessful():
|
||||
self.driver.get_screenshot_as_file("failed_test_shot_{}.png".format(self.id()))
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(self):
|
||||
self.mockServerProcess.terminate()
|
||||
self.driver.quit()
|
||||
|
||||
def test_login(self):
|
||||
self.driver.find_element(by=AppiumBy.NAME, value="Login").click()
|
||||
|
||||
self.driver.find_element(by=AppiumBy.NAME, value="Matrix ID").send_keys("@user:localhost:1234")
|
||||
self.driver.find_element(by=AppiumBy.NAME, value="Continue").click()
|
||||
self.driver.find_element(by=AppiumBy.NAME, value="Password").send_keys("1234")
|
||||
self.driver.find_element(by=AppiumBy.NAME, value="Login").click()
|
||||
self.driver.find_element(by=AppiumBy.NAME, value="Join some rooms to get started").click()
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
34
autotests/CMakeLists.txt
Normal file
34
autotests/CMakeLists.txt
Normal file
@@ -0,0 +1,34 @@
|
||||
# SPDX-FileCopyrightText: 2022 Carl Schwan <carl@carlschwan.eu>
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
|
||||
enable_testing()
|
||||
|
||||
ecm_add_test(
|
||||
neochatroomtest.cpp
|
||||
LINK_LIBRARIES neochat Qt::Test
|
||||
TEST_NAME neochatroomtest
|
||||
)
|
||||
|
||||
ecm_add_test(
|
||||
texthandlertest.cpp
|
||||
LINK_LIBRARIES neochat Qt::Test
|
||||
TEST_NAME texthandlertest
|
||||
)
|
||||
|
||||
ecm_add_test(
|
||||
delegatesizehelpertest.cpp
|
||||
LINK_LIBRARIES neochat Qt::Test
|
||||
TEST_NAME delegatesizehelpertest
|
||||
)
|
||||
|
||||
ecm_add_test(
|
||||
mediasizehelpertest.cpp
|
||||
LINK_LIBRARIES neochat Qt::Test
|
||||
TEST_NAME mediasizehelpertest
|
||||
)
|
||||
|
||||
ecm_add_test(
|
||||
eventhandlertest.cpp
|
||||
LINK_LIBRARIES neochat Qt::Test
|
||||
TEST_NAME eventhandlertest
|
||||
)
|
||||
156
autotests/delegatesizehelpertest.cpp
Normal file
156
autotests/delegatesizehelpertest.cpp
Normal file
@@ -0,0 +1,156 @@
|
||||
// SPDX-FileCopyrightText: 2023 James Graham <james.h.graham@protonmail.com>
|
||||
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
|
||||
#include <QObject>
|
||||
#include <QTest>
|
||||
|
||||
#include "delegatesizehelper.h"
|
||||
|
||||
class DelegateSizeHelperTest : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private Q_SLOTS:
|
||||
void risingPercentage_data();
|
||||
void risingPercentage();
|
||||
|
||||
void fallingPercentage_data();
|
||||
void fallingPercentage();
|
||||
|
||||
void equalPercentage_data();
|
||||
void equalPercentage();
|
||||
|
||||
void equalBreakpoint_data();
|
||||
void equalBreakpoint();
|
||||
};
|
||||
|
||||
void DelegateSizeHelperTest::risingPercentage_data()
|
||||
{
|
||||
QTest::addColumn<qreal>("parentWidth");
|
||||
QTest::addColumn<int>("currentPercentageWidth");
|
||||
QTest::addColumn<qreal>("currentWidth");
|
||||
|
||||
QTest::newRow("zero") << qreal(0) << int(0) << qreal(0);
|
||||
QTest::newRow("one hundred") << qreal(100) << int(0) << qreal(0);
|
||||
QTest::newRow("one fifty") << qreal(150) << int(50) << qreal(75);
|
||||
QTest::newRow("two hundred") << qreal(200) << int(100) << qreal(200);
|
||||
QTest::newRow("three hundred") << qreal(300) << int(100) << qreal(300);
|
||||
}
|
||||
|
||||
void DelegateSizeHelperTest::risingPercentage()
|
||||
{
|
||||
QFETCH(qreal, parentWidth);
|
||||
QFETCH(int, currentPercentageWidth);
|
||||
QFETCH(qreal, currentWidth);
|
||||
|
||||
DelegateSizeHelper delegateSizeHelper;
|
||||
delegateSizeHelper.setStartBreakpoint(100);
|
||||
delegateSizeHelper.setEndBreakpoint(200);
|
||||
delegateSizeHelper.setStartPercentWidth(0);
|
||||
delegateSizeHelper.setEndPercentWidth(100);
|
||||
|
||||
delegateSizeHelper.setParentWidth(parentWidth);
|
||||
|
||||
QCOMPARE(delegateSizeHelper.currentPercentageWidth(), currentPercentageWidth);
|
||||
QCOMPARE(delegateSizeHelper.currentWidth(), currentWidth);
|
||||
}
|
||||
|
||||
void DelegateSizeHelperTest::fallingPercentage_data()
|
||||
{
|
||||
QTest::addColumn<qreal>("parentWidth");
|
||||
QTest::addColumn<int>("currentPercentageWidth");
|
||||
QTest::addColumn<qreal>("currentWidth");
|
||||
|
||||
QTest::newRow("zero") << qreal(0) << int(100) << qreal(0);
|
||||
QTest::newRow("one hundred") << qreal(100) << int(100) << qreal(100);
|
||||
QTest::newRow("one fifty") << qreal(150) << int(50) << qreal(75);
|
||||
QTest::newRow("two hundred") << qreal(200) << int(0) << qreal(0);
|
||||
QTest::newRow("three hundred") << qreal(300) << int(0) << qreal(0);
|
||||
}
|
||||
|
||||
void DelegateSizeHelperTest::fallingPercentage()
|
||||
{
|
||||
QFETCH(qreal, parentWidth);
|
||||
QFETCH(int, currentPercentageWidth);
|
||||
QFETCH(qreal, currentWidth);
|
||||
|
||||
DelegateSizeHelper delegateSizeHelper;
|
||||
delegateSizeHelper.setStartBreakpoint(100);
|
||||
delegateSizeHelper.setEndBreakpoint(200);
|
||||
delegateSizeHelper.setStartPercentWidth(100);
|
||||
delegateSizeHelper.setEndPercentWidth(0);
|
||||
|
||||
delegateSizeHelper.setParentWidth(parentWidth);
|
||||
|
||||
QCOMPARE(delegateSizeHelper.currentPercentageWidth(), currentPercentageWidth);
|
||||
QCOMPARE(delegateSizeHelper.currentWidth(), currentWidth);
|
||||
}
|
||||
|
||||
void DelegateSizeHelperTest::equalPercentage_data()
|
||||
{
|
||||
QTest::addColumn<qreal>("parentWidth");
|
||||
QTest::addColumn<int>("currentPercentageWidth");
|
||||
QTest::addColumn<qreal>("currentWidth");
|
||||
|
||||
QTest::newRow("zero") << qreal(0) << int(50) << qreal(0);
|
||||
QTest::newRow("one hundred") << qreal(100) << int(50) << qreal(50);
|
||||
QTest::newRow("one fifty") << qreal(150) << int(50) << qreal(75);
|
||||
QTest::newRow("two hundred") << qreal(200) << int(50) << qreal(100);
|
||||
QTest::newRow("three hundred") << qreal(300) << int(50) << qreal(150);
|
||||
}
|
||||
|
||||
void DelegateSizeHelperTest::equalPercentage()
|
||||
{
|
||||
QFETCH(qreal, parentWidth);
|
||||
QFETCH(int, currentPercentageWidth);
|
||||
QFETCH(qreal, currentWidth);
|
||||
|
||||
DelegateSizeHelper delegateSizeHelper;
|
||||
delegateSizeHelper.setStartBreakpoint(100);
|
||||
delegateSizeHelper.setEndBreakpoint(200);
|
||||
delegateSizeHelper.setStartPercentWidth(50);
|
||||
delegateSizeHelper.setEndPercentWidth(50);
|
||||
|
||||
delegateSizeHelper.setParentWidth(parentWidth);
|
||||
|
||||
QCOMPARE(delegateSizeHelper.currentPercentageWidth(), currentPercentageWidth);
|
||||
QCOMPARE(delegateSizeHelper.currentWidth(), currentWidth);
|
||||
}
|
||||
|
||||
void DelegateSizeHelperTest::equalBreakpoint_data()
|
||||
{
|
||||
QTest::addColumn<int>("startPercentageWidth");
|
||||
QTest::addColumn<int>("endPercentageWidth");
|
||||
QTest::addColumn<int>("currentPercentageWidth");
|
||||
QTest::addColumn<qreal>("currentWidth");
|
||||
|
||||
QTest::newRow("start higher") << int(100) << int(0) << int(-1) << qreal(0);
|
||||
QTest::newRow("equal") << int(50) << int(50) << int(50) << qreal(500);
|
||||
QTest::newRow("end higher") << int(0) << int(100) << int(-1) << qreal(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* We expect a default return except in the case where the the two percentages are
|
||||
* equal as that case can be calculated without dividing by zero.
|
||||
*/
|
||||
void DelegateSizeHelperTest::equalBreakpoint()
|
||||
{
|
||||
QFETCH(int, startPercentageWidth);
|
||||
QFETCH(int, endPercentageWidth);
|
||||
QFETCH(int, currentPercentageWidth);
|
||||
QFETCH(qreal, currentWidth);
|
||||
|
||||
DelegateSizeHelper delegateSizeHelper;
|
||||
delegateSizeHelper.setStartBreakpoint(100);
|
||||
delegateSizeHelper.setEndBreakpoint(100);
|
||||
delegateSizeHelper.setStartPercentWidth(startPercentageWidth);
|
||||
delegateSizeHelper.setEndPercentWidth(endPercentageWidth);
|
||||
|
||||
delegateSizeHelper.setParentWidth(1000);
|
||||
|
||||
QCOMPARE(delegateSizeHelper.currentPercentageWidth(), currentPercentageWidth);
|
||||
QCOMPARE(delegateSizeHelper.currentWidth(), currentWidth);
|
||||
}
|
||||
|
||||
QTEST_GUILESS_MAIN(DelegateSizeHelperTest)
|
||||
#include "delegatesizehelpertest.moc"
|
||||
729
autotests/eventhandlertest.cpp
Normal file
729
autotests/eventhandlertest.cpp
Normal file
@@ -0,0 +1,729 @@
|
||||
// SPDX-FileCopyrightText: 2023 James Graham <james.h.graham@protonmail.com>
|
||||
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
|
||||
#include <QObject>
|
||||
#include <QTest>
|
||||
|
||||
#include "eventhandler.h"
|
||||
|
||||
#include <KFormat>
|
||||
|
||||
#include <Quotient/connection.h>
|
||||
#include <Quotient/quotient_common.h>
|
||||
#include <Quotient/syncdata.h>
|
||||
|
||||
#include "enums/delegatetype.h"
|
||||
#include "linkpreviewer.h"
|
||||
#include "models/reactionmodel.h"
|
||||
#include "neochatroom.h"
|
||||
#include "utils.h"
|
||||
|
||||
using namespace Quotient;
|
||||
|
||||
class TestRoom : public NeoChatRoom
|
||||
{
|
||||
public:
|
||||
using NeoChatRoom::NeoChatRoom;
|
||||
|
||||
void update(SyncRoomData &&data, bool fromCache = false)
|
||||
{
|
||||
Room::updateData(std::move(data), fromCache);
|
||||
}
|
||||
};
|
||||
|
||||
class EventHandlerTest : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private:
|
||||
Connection *connection = nullptr;
|
||||
TestRoom *room = nullptr;
|
||||
EventHandler eventHandler;
|
||||
|
||||
private Q_SLOTS:
|
||||
void initTestCase();
|
||||
|
||||
void eventId();
|
||||
void delegateType_data();
|
||||
void delegateType();
|
||||
void author();
|
||||
void authorDisplayName();
|
||||
void time();
|
||||
void timeString();
|
||||
void highlighted();
|
||||
void hidden();
|
||||
void body();
|
||||
void genericBody_data();
|
||||
void genericBody();
|
||||
void mediaInfo();
|
||||
void linkPreviewer();
|
||||
void reactions();
|
||||
void hasReply();
|
||||
void replyId();
|
||||
void replyDelegateType();
|
||||
void replyAuthor();
|
||||
void replyBody();
|
||||
void replyMediaInfo();
|
||||
void location();
|
||||
void readMarkers();
|
||||
};
|
||||
|
||||
void EventHandlerTest::initTestCase()
|
||||
{
|
||||
connection = Connection::makeMockConnection(QStringLiteral("@bob:kde.org"));
|
||||
room = new TestRoom(connection, QStringLiteral("#myroom:kde.org"), JoinState::Join);
|
||||
|
||||
const auto json = QJsonDocument::fromJson(R"EVENT({
|
||||
"account_data": {
|
||||
"events": [
|
||||
{
|
||||
"content": {
|
||||
"tags": {
|
||||
"u.work": {
|
||||
"order": 0.9
|
||||
}
|
||||
}
|
||||
},
|
||||
"type": "m.tag"
|
||||
},
|
||||
{
|
||||
"content": {
|
||||
"custom_config_key": "custom_config_value"
|
||||
},
|
||||
"type": "org.example.custom.room.config"
|
||||
}
|
||||
]
|
||||
},
|
||||
"ephemeral": {
|
||||
"events": [
|
||||
{
|
||||
"content": {
|
||||
"user_ids": [
|
||||
"@alice:matrix.org",
|
||||
"@bob:example.com"
|
||||
]
|
||||
},
|
||||
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
|
||||
"type": "m.typing"
|
||||
},
|
||||
{
|
||||
"content": {
|
||||
"$153456789:example.org": {
|
||||
"m.read": {
|
||||
"@alice:matrix.org": {
|
||||
"ts": 1436451550453
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"type": "m.receipt"
|
||||
},
|
||||
{
|
||||
"content": {
|
||||
"$1532735824654:example.org": {
|
||||
"m.read": {
|
||||
"@bob:example.com": {
|
||||
"ts": 1436451550453
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"type": "m.receipt"
|
||||
},
|
||||
{
|
||||
"content": {
|
||||
"$1532735824654:example.org": {
|
||||
"m.read": {
|
||||
"@tim:example.com": {
|
||||
"ts": 1436451550454
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"type": "m.receipt"
|
||||
},
|
||||
{
|
||||
"content": {
|
||||
"$1532735824654:example.org": {
|
||||
"m.read": {
|
||||
"@jeff:example.com": {
|
||||
"ts": 1436451550455
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"type": "m.receipt"
|
||||
},
|
||||
{
|
||||
"content": {
|
||||
"$1532735824654:example.org": {
|
||||
"m.read": {
|
||||
"@tina:example.com": {
|
||||
"ts": 1436451550456
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"type": "m.receipt"
|
||||
},
|
||||
{
|
||||
"content": {
|
||||
"$1532735824654:example.org": {
|
||||
"m.read": {
|
||||
"@sally:example.com": {
|
||||
"ts": 1436451550457
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"type": "m.receipt"
|
||||
},
|
||||
{
|
||||
"content": {
|
||||
"$1532735824654:example.org": {
|
||||
"m.read": {
|
||||
"@fred:example.com": {
|
||||
"ts": 1436451550458
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"type": "m.receipt"
|
||||
}
|
||||
]
|
||||
},
|
||||
"state": {
|
||||
"events": [
|
||||
{
|
||||
"content": {
|
||||
"avatar_url": "mxc://example.org/SEsfnsuifSDFSSEF",
|
||||
"displayname": "Alice Margatroid",
|
||||
"membership": "join",
|
||||
"reason": "Looking for support"
|
||||
},
|
||||
"event_id": "$143273582443PhrSn:example.org",
|
||||
"origin_server_ts": 1432735824653,
|
||||
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
|
||||
"sender": "@example:example.org",
|
||||
"state_key": "@alice:example.org",
|
||||
"type": "m.room.member",
|
||||
"unsigned": {
|
||||
"age": 1234
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"summary": {
|
||||
"m.heroes": [
|
||||
"@alice:example.com",
|
||||
"@bob:example.com"
|
||||
],
|
||||
"m.invited_member_count": 0,
|
||||
"m.joined_member_count": 2
|
||||
},
|
||||
"timeline": {
|
||||
"events": [
|
||||
{
|
||||
"content": {
|
||||
"body": "This is an example\ntext message",
|
||||
"format": "org.matrix.custom.html",
|
||||
"formatted_body": "<b>This is an example<br>text message</b>",
|
||||
"msgtype": "m.text"
|
||||
},
|
||||
"event_id": "$153456789:example.org",
|
||||
"origin_server_ts": 1432735824654,
|
||||
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
|
||||
"sender": "@example:example.org",
|
||||
"type": "m.room.message",
|
||||
"unsigned": {
|
||||
"age": 1232
|
||||
}
|
||||
},
|
||||
{
|
||||
"content": {
|
||||
"avatar_url": "mxc://kde.org/123456",
|
||||
"displayname": "after",
|
||||
"membership": "join"
|
||||
},
|
||||
"origin_server_ts": 1690651134736,
|
||||
"sender": "@example:example.org",
|
||||
"state_key": "@example:example.org",
|
||||
"type": "m.room.member",
|
||||
"unsigned": {
|
||||
"replaces_state": "$1234567890:example.org",
|
||||
"prev_content": {
|
||||
"avatar_url": "mxc://kde.org/12345",
|
||||
"displayname": "before",
|
||||
"membership": "join"
|
||||
},
|
||||
"prev_sender": "@example:example.orgg",
|
||||
"age": 1234
|
||||
},
|
||||
"event_id": "$143273583553PhrSn:example.org",
|
||||
"room_id": "!jEsUZKDJdhlrceRyVU:example.org"
|
||||
},
|
||||
{
|
||||
"content": {
|
||||
"body": "This is a highlight @bob:kde.org and this is a link https://kde.org",
|
||||
"format": "org.matrix.custom.html",
|
||||
"msgtype": "m.text"
|
||||
},
|
||||
"event_id": "$1532735824654:example.org",
|
||||
"origin_server_ts": 1532735824654,
|
||||
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
|
||||
"sender": "@example:example.org",
|
||||
"type": "m.room.message",
|
||||
"unsigned": {
|
||||
"age": 1233
|
||||
}
|
||||
},
|
||||
{
|
||||
"content": {
|
||||
"m.relates_to": {
|
||||
"event_id": "$153456789:example.org",
|
||||
"key": "👍",
|
||||
"rel_type": "m.annotation"
|
||||
}
|
||||
},
|
||||
"origin_server_ts": 1690322545182,
|
||||
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
|
||||
"sender": "@alice:matrix.org",
|
||||
"type": "m.reaction",
|
||||
"unsigned": {
|
||||
"age": 390159120
|
||||
},
|
||||
"event_id": "$163456789:example.org",
|
||||
"age": 390159120
|
||||
},
|
||||
{
|
||||
"age": 4926305285,
|
||||
"content": {
|
||||
"body": "video caption",
|
||||
"filename": "video.mp4",
|
||||
"info": {
|
||||
"duration": 10,
|
||||
"h": 1080,
|
||||
"mimetype": "video/mp4",
|
||||
"size": 62650636,
|
||||
"w": 1920,
|
||||
"thumbnail_info": {
|
||||
"h": 450,
|
||||
"mimetype": "image/jpeg",
|
||||
"size": 382249,
|
||||
"w": 800
|
||||
},
|
||||
"thumbnail_url": "mxc://kde.org/2234567"
|
||||
},
|
||||
"msgtype": "m.video",
|
||||
"url": "mxc://kde.org/1234567"
|
||||
},
|
||||
"event_id": "$263456789:example.org",
|
||||
"origin_server_ts": 1685793783330,
|
||||
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
|
||||
"sender": "@example:example.org",
|
||||
"type": "m.room.message",
|
||||
"unsigned": {
|
||||
"age": 4926305285
|
||||
},
|
||||
"user_id": "@example:example.org"
|
||||
},
|
||||
{
|
||||
"content": {
|
||||
"body": "> <@example:example.org> This is an example\ntext message\n\nreply",
|
||||
"format": "org.matrix.custom.html",
|
||||
"formatted_body": "<mx-reply><blockquote><a href=\"https://matrix.to/#/!jEsUZKDJdhlrceRyVU:example.org/$153456789:example.org?via=kde.org&via=matrix.org\">In reply to</a> <a href=\"https://matrix.to/#/@example:example.org\">@example:example.org</a><br><b>This is an example<br>text message</b></blockquote></mx-reply>reply",
|
||||
"m.relates_to": {
|
||||
"m.in_reply_to": {
|
||||
"event_id": "$153456789:example.org"
|
||||
}
|
||||
},
|
||||
"msgtype": "m.text"
|
||||
},
|
||||
"origin_server_ts": 1690725965572,
|
||||
"sender": "@alice:matrix.org",
|
||||
"type": "m.room.message",
|
||||
"unsigned": {
|
||||
"age": 98
|
||||
},
|
||||
"event_id": "$154456789:example.org",
|
||||
"room_id": "!jEsUZKDJdhlrceRyVU:example.org"
|
||||
},
|
||||
{
|
||||
"content": {
|
||||
"body": "> <@example:example.org> video caption\n\nreply",
|
||||
"m.relates_to": {
|
||||
"m.in_reply_to": {
|
||||
"event_id": "$263456789:example.org"
|
||||
}
|
||||
},
|
||||
"msgtype": "m.text"
|
||||
},
|
||||
"origin_server_ts": 1690725965573,
|
||||
"sender": "@alice:matrix.org",
|
||||
"type": "m.room.message",
|
||||
"unsigned": {
|
||||
"age": 98
|
||||
},
|
||||
"event_id": "$154456799:example.org",
|
||||
"room_id": "!jEsUZKDJdhlrceRyVU:example.org"
|
||||
},
|
||||
{
|
||||
"age": 96845207,
|
||||
"content": {
|
||||
"body": "Lat: 51.7035, Lon: -1.14394",
|
||||
"geo_uri": "geo:51.7035,-1.14394",
|
||||
"msgtype": "m.location",
|
||||
"org.matrix.msc1767.text": "Lat: 51.7035, Lon: -1.14394",
|
||||
"org.matrix.msc3488.asset": {
|
||||
"type": "m.pin"
|
||||
},
|
||||
"org.matrix.msc3488.location": {
|
||||
"uri": "geo:51.7035,-1.14394"
|
||||
}
|
||||
},
|
||||
"event_id": "$1544567999:example.org",
|
||||
"origin_server_ts": 1690821582876,
|
||||
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
|
||||
"sender": "@example:example.org",
|
||||
"type": "m.room.message",
|
||||
"unsigned": {
|
||||
"age": 96845207
|
||||
}
|
||||
}
|
||||
],
|
||||
"limited": true,
|
||||
"prev_batch": "t34-23535_0_0"
|
||||
}
|
||||
})EVENT");
|
||||
SyncRoomData roomData(QStringLiteral("@bob:kde.org"), JoinState::Join, json.object());
|
||||
room->update(std::move(roomData));
|
||||
|
||||
eventHandler.setRoom(room);
|
||||
}
|
||||
|
||||
void EventHandlerTest::eventId()
|
||||
{
|
||||
eventHandler.setEvent(room->messageEvents().at(0).get());
|
||||
|
||||
QCOMPARE(eventHandler.getId(), QStringLiteral("$153456789:example.org"));
|
||||
}
|
||||
|
||||
void EventHandlerTest::delegateType_data()
|
||||
{
|
||||
QTest::addColumn<int>("eventNum");
|
||||
QTest::addColumn<DelegateType::Type>("delegateType");
|
||||
|
||||
QTest::newRow("message") << 0 << DelegateType::Message;
|
||||
QTest::newRow("state") << 1 << DelegateType::State;
|
||||
QTest::newRow("message 2") << 2 << DelegateType::Message;
|
||||
QTest::newRow("reaction") << 3 << DelegateType::Other;
|
||||
QTest::newRow("video") << 4 << DelegateType::Video;
|
||||
QTest::newRow("location") << 7 << DelegateType::Location;
|
||||
}
|
||||
|
||||
void EventHandlerTest::delegateType()
|
||||
{
|
||||
QFETCH(int, eventNum);
|
||||
QFETCH(DelegateType::Type, delegateType);
|
||||
|
||||
eventHandler.setEvent(room->messageEvents().at(eventNum).get());
|
||||
|
||||
QCOMPARE(eventHandler.getDelegateType(), delegateType);
|
||||
}
|
||||
|
||||
void EventHandlerTest::author()
|
||||
{
|
||||
auto event = room->messageEvents().at(0).get();
|
||||
auto author = room->user(event->senderId());
|
||||
eventHandler.setEvent(event);
|
||||
|
||||
auto eventHandlerAuthor = eventHandler.getAuthor();
|
||||
|
||||
QCOMPARE(eventHandlerAuthor["isLocalUser"_ls], author->id() == room->localUser()->id());
|
||||
QCOMPARE(eventHandlerAuthor["id"_ls], author->id());
|
||||
QCOMPARE(eventHandlerAuthor["displayName"_ls], author->displayname(room));
|
||||
QCOMPARE(eventHandlerAuthor["avatarSource"_ls], room->avatarForMember(author));
|
||||
QCOMPARE(eventHandlerAuthor["avatarMediaId"_ls], author->avatarMediaId(room));
|
||||
QCOMPARE(eventHandlerAuthor["color"_ls], Utils::getUserColor(author->hueF()));
|
||||
QCOMPARE(eventHandlerAuthor["object"_ls], QVariant::fromValue(author));
|
||||
}
|
||||
|
||||
void EventHandlerTest::authorDisplayName()
|
||||
{
|
||||
auto event = room->messageEvents().at(1).get();
|
||||
eventHandler.setEvent(event);
|
||||
|
||||
QCOMPARE(eventHandler.getAuthorDisplayName(), QStringLiteral("before"));
|
||||
}
|
||||
|
||||
void EventHandlerTest::time()
|
||||
{
|
||||
auto event = room->messageEvents().at(0).get();
|
||||
eventHandler.setEvent(event);
|
||||
|
||||
QCOMPARE(eventHandler.getTime(), QDateTime::fromMSecsSinceEpoch(1432735824654, Qt::UTC));
|
||||
QCOMPARE(eventHandler.getTime(true, QDateTime::fromMSecsSinceEpoch(1234, Qt::UTC)), QDateTime::fromMSecsSinceEpoch(1234, Qt::UTC));
|
||||
}
|
||||
|
||||
void EventHandlerTest::timeString()
|
||||
{
|
||||
auto event = room->messageEvents().at(0).get();
|
||||
eventHandler.setEvent(event);
|
||||
|
||||
KFormat format;
|
||||
|
||||
QCOMPARE(eventHandler.getTimeString(false),
|
||||
QLocale().toString(QDateTime::fromMSecsSinceEpoch(1432735824654, Qt::UTC).toLocalTime().time(), QLocale::ShortFormat));
|
||||
QCOMPARE(eventHandler.getTimeString(true),
|
||||
format.formatRelativeDate(QDateTime::fromMSecsSinceEpoch(1432735824654, Qt::UTC).toLocalTime().date(), QLocale::ShortFormat));
|
||||
QCOMPARE(eventHandler.getTimeString(false, QLocale::ShortFormat, true, QDateTime::fromMSecsSinceEpoch(1690699214545, Qt::UTC)),
|
||||
QLocale().toString(QDateTime::fromMSecsSinceEpoch(1690699214545, Qt::UTC).toLocalTime().time(), QLocale::ShortFormat));
|
||||
QCOMPARE(eventHandler.getTimeString(true, QLocale::ShortFormat, true, QDateTime::fromMSecsSinceEpoch(1690699214545, Qt::UTC)),
|
||||
format.formatRelativeDate(QDateTime::fromMSecsSinceEpoch(1690699214545, Qt::UTC).toLocalTime().date(), QLocale::ShortFormat));
|
||||
QCOMPARE(eventHandler.getTimeString(false, QLocale::LongFormat, true, QDateTime::fromMSecsSinceEpoch(1690699214545, Qt::UTC)),
|
||||
QLocale().toString(QDateTime::fromMSecsSinceEpoch(1690699214545, Qt::UTC).toLocalTime().time(), QLocale::LongFormat));
|
||||
QCOMPARE(eventHandler.getTimeString(true, QLocale::LongFormat, true, QDateTime::fromMSecsSinceEpoch(1690699214545, Qt::UTC)),
|
||||
format.formatRelativeDate(QDateTime::fromMSecsSinceEpoch(1690699214545, Qt::UTC).toLocalTime().date(), QLocale::LongFormat));
|
||||
}
|
||||
|
||||
void EventHandlerTest::highlighted()
|
||||
{
|
||||
auto event = room->messageEvents().at(2).get();
|
||||
eventHandler.setEvent(event);
|
||||
|
||||
QCOMPARE(eventHandler.isHighlighted(), true);
|
||||
|
||||
event = room->messageEvents().at(0).get();
|
||||
eventHandler.setEvent(event);
|
||||
|
||||
QCOMPARE(eventHandler.isHighlighted(), false);
|
||||
}
|
||||
|
||||
void EventHandlerTest::hidden()
|
||||
{
|
||||
auto event = room->messageEvents().at(3).get();
|
||||
eventHandler.setEvent(event);
|
||||
|
||||
QCOMPARE(eventHandler.isHidden(), true);
|
||||
|
||||
event = room->messageEvents().at(0).get();
|
||||
eventHandler.setEvent(event);
|
||||
|
||||
QCOMPARE(eventHandler.isHidden(), false);
|
||||
}
|
||||
|
||||
void EventHandlerTest::body()
|
||||
{
|
||||
auto event = room->messageEvents().at(0).get();
|
||||
eventHandler.setEvent(event);
|
||||
|
||||
QCOMPARE(eventHandler.getRichBody(), QStringLiteral("<b>This is an example<br>text message</b>"));
|
||||
QCOMPARE(eventHandler.getRichBody(true), QStringLiteral("<b>This is an example text message</b>"));
|
||||
QCOMPARE(eventHandler.getPlainBody(), QStringLiteral("This is an example\ntext message"));
|
||||
QCOMPARE(eventHandler.getPlainBody(true), QStringLiteral("This is an example text message"));
|
||||
}
|
||||
|
||||
void EventHandlerTest::genericBody_data()
|
||||
{
|
||||
QTest::addColumn<int>("eventNum");
|
||||
QTest::addColumn<QString>("output");
|
||||
|
||||
QTest::newRow("message") << 0 << QStringLiteral("sent a message");
|
||||
QTest::newRow("member") << 1 << QStringLiteral("changed their display name and updated their avatar");
|
||||
QTest::newRow("message 2") << 2 << QStringLiteral("sent a message");
|
||||
QTest::newRow("reaction") << 3 << QStringLiteral("Unknown event");
|
||||
QTest::newRow("video") << 4 << QStringLiteral("sent a message");
|
||||
}
|
||||
|
||||
void EventHandlerTest::genericBody()
|
||||
{
|
||||
QFETCH(int, eventNum);
|
||||
QFETCH(QString, output);
|
||||
|
||||
eventHandler.setEvent(room->messageEvents().at(eventNum).get());
|
||||
|
||||
QCOMPARE(eventHandler.getGenericBody(), output);
|
||||
}
|
||||
|
||||
void EventHandlerTest::mediaInfo()
|
||||
{
|
||||
auto event = room->messageEvents().at(4).get();
|
||||
eventHandler.setEvent(event);
|
||||
|
||||
auto mediaInfo = eventHandler.getMediaInfo();
|
||||
auto thumbnailInfo = mediaInfo["tempInfo"_ls].toMap();
|
||||
|
||||
QCOMPARE(mediaInfo["source"_ls], room->makeMediaUrl(event->id(), QUrl("mxc://kde.org/1234567"_ls)));
|
||||
QCOMPARE(mediaInfo["mimeType"_ls], QStringLiteral("video/mp4"));
|
||||
QCOMPARE(mediaInfo["mimeIcon"_ls], QStringLiteral("video-mp4"));
|
||||
QCOMPARE(mediaInfo["size"_ls], 62650636);
|
||||
QCOMPARE(mediaInfo["duration"_ls], 10);
|
||||
QCOMPARE(mediaInfo["width"_ls], 1920);
|
||||
QCOMPARE(mediaInfo["height"_ls], 1080);
|
||||
QCOMPARE(thumbnailInfo["source"_ls], room->makeMediaUrl(event->id(), QUrl("mxc://kde.org/2234567"_ls)));
|
||||
QCOMPARE(thumbnailInfo["mimeType"_ls], QStringLiteral("image/jpeg"));
|
||||
QCOMPARE(thumbnailInfo["mimeIcon"_ls], QStringLiteral("image-jpeg"));
|
||||
QCOMPARE(thumbnailInfo["size"_ls], 382249);
|
||||
QCOMPARE(thumbnailInfo["width"_ls], 800);
|
||||
QCOMPARE(thumbnailInfo["height"_ls], 450);
|
||||
}
|
||||
|
||||
void EventHandlerTest::linkPreviewer()
|
||||
{
|
||||
auto event = room->messageEvents().at(2).get();
|
||||
eventHandler.setEvent(event);
|
||||
|
||||
QCOMPARE(eventHandler.getLinkPreviewer()->url(), QUrl("https://kde.org"_ls));
|
||||
|
||||
event = room->messageEvents().at(0).get();
|
||||
eventHandler.setEvent(event);
|
||||
|
||||
QCOMPARE(eventHandler.getLinkPreviewer(), nullptr);
|
||||
}
|
||||
|
||||
void EventHandlerTest::reactions()
|
||||
{
|
||||
auto event = room->messageEvents().at(0).get();
|
||||
eventHandler.setEvent(event);
|
||||
|
||||
QCOMPARE(eventHandler.getReactions()->rowCount(), 1);
|
||||
}
|
||||
|
||||
void EventHandlerTest::hasReply()
|
||||
{
|
||||
auto event = room->messageEvents().at(5).get();
|
||||
eventHandler.setEvent(event);
|
||||
|
||||
QCOMPARE(eventHandler.hasReply(), true);
|
||||
|
||||
event = room->messageEvents().at(0).get();
|
||||
eventHandler.setEvent(event);
|
||||
|
||||
QCOMPARE(eventHandler.hasReply(), false);
|
||||
}
|
||||
|
||||
void EventHandlerTest::replyId()
|
||||
{
|
||||
auto event = room->messageEvents().at(5).get();
|
||||
eventHandler.setEvent(event);
|
||||
|
||||
QCOMPARE(eventHandler.getReplyId(), QStringLiteral("$153456789:example.org"));
|
||||
|
||||
event = room->messageEvents().at(0).get();
|
||||
eventHandler.setEvent(event);
|
||||
|
||||
QCOMPARE(eventHandler.getReplyId(), QStringLiteral(""));
|
||||
}
|
||||
|
||||
void EventHandlerTest::replyDelegateType()
|
||||
{
|
||||
auto event = room->messageEvents().at(5).get();
|
||||
eventHandler.setEvent(event);
|
||||
|
||||
QCOMPARE(eventHandler.getReplyDelegateType(), DelegateType::Message);
|
||||
|
||||
event = room->messageEvents().at(0).get();
|
||||
eventHandler.setEvent(event);
|
||||
|
||||
QCOMPARE(eventHandler.getReplyDelegateType(), DelegateType::Other);
|
||||
}
|
||||
|
||||
void EventHandlerTest::replyAuthor()
|
||||
{
|
||||
auto event = room->messageEvents().at(5).get();
|
||||
auto replyEvent = room->messageEvents().at(0).get();
|
||||
auto replyAuthor = room->user(replyEvent->senderId());
|
||||
eventHandler.setEvent(event);
|
||||
|
||||
auto eventHandlerReplyAuthor = eventHandler.getReplyAuthor();
|
||||
|
||||
QCOMPARE(eventHandlerReplyAuthor["isLocalUser"_ls], replyAuthor->id() == room->localUser()->id());
|
||||
QCOMPARE(eventHandlerReplyAuthor["id"_ls], replyAuthor->id());
|
||||
QCOMPARE(eventHandlerReplyAuthor["displayName"_ls], replyAuthor->displayname(room));
|
||||
QCOMPARE(eventHandlerReplyAuthor["avatarSource"_ls], room->avatarForMember(replyAuthor));
|
||||
QCOMPARE(eventHandlerReplyAuthor["avatarMediaId"_ls], replyAuthor->avatarMediaId(room));
|
||||
QCOMPARE(eventHandlerReplyAuthor["color"_ls], Utils::getUserColor(replyAuthor->hueF()));
|
||||
QCOMPARE(eventHandlerReplyAuthor["object"_ls], QVariant::fromValue(replyAuthor));
|
||||
|
||||
event = room->messageEvents().at(0).get();
|
||||
eventHandler.setEvent(event);
|
||||
|
||||
QCOMPARE(eventHandler.getReplyAuthor(), room->getUser(nullptr));
|
||||
}
|
||||
|
||||
void EventHandlerTest::replyBody()
|
||||
{
|
||||
auto event = room->messageEvents().at(5).get();
|
||||
eventHandler.setEvent(event);
|
||||
|
||||
QCOMPARE(eventHandler.getReplyRichBody(), QStringLiteral("<b>This is an example<br>text message</b>"));
|
||||
QCOMPARE(eventHandler.getReplyRichBody(true), QStringLiteral("<b>This is an example text message</b>"));
|
||||
QCOMPARE(eventHandler.getReplyPlainBody(), QStringLiteral("This is an example\ntext message"));
|
||||
QCOMPARE(eventHandler.getReplyPlainBody(true), QStringLiteral("This is an example text message"));
|
||||
}
|
||||
|
||||
void EventHandlerTest::replyMediaInfo()
|
||||
{
|
||||
auto event = room->messageEvents().at(6).get();
|
||||
auto replyEvent = room->messageEvents().at(4).get();
|
||||
eventHandler.setEvent(event);
|
||||
|
||||
auto mediaInfo = eventHandler.getReplyMediaInfo();
|
||||
auto thumbnailInfo = mediaInfo["tempInfo"_ls].toMap();
|
||||
|
||||
QCOMPARE(mediaInfo["source"_ls], room->makeMediaUrl(replyEvent->id(), QUrl("mxc://kde.org/1234567"_ls)));
|
||||
QCOMPARE(mediaInfo["mimeType"_ls], QStringLiteral("video/mp4"));
|
||||
QCOMPARE(mediaInfo["mimeIcon"_ls], QStringLiteral("video-mp4"));
|
||||
QCOMPARE(mediaInfo["size"_ls], 62650636);
|
||||
QCOMPARE(mediaInfo["duration"_ls], 10);
|
||||
QCOMPARE(mediaInfo["width"_ls], 1920);
|
||||
QCOMPARE(mediaInfo["height"_ls], 1080);
|
||||
QCOMPARE(thumbnailInfo["source"_ls], room->makeMediaUrl(replyEvent->id(), QUrl("mxc://kde.org/2234567"_ls)));
|
||||
QCOMPARE(thumbnailInfo["mimeType"_ls], QStringLiteral("image/jpeg"));
|
||||
QCOMPARE(thumbnailInfo["mimeIcon"_ls], QStringLiteral("image-jpeg"));
|
||||
QCOMPARE(thumbnailInfo["size"_ls], 382249);
|
||||
QCOMPARE(thumbnailInfo["width"_ls], 800);
|
||||
QCOMPARE(thumbnailInfo["height"_ls], 450);
|
||||
}
|
||||
|
||||
void EventHandlerTest::location()
|
||||
{
|
||||
auto event = room->messageEvents().at(7).get();
|
||||
eventHandler.setEvent(event);
|
||||
|
||||
QCOMPARE(eventHandler.getLatitude(), QStringLiteral("51.7035").toFloat());
|
||||
QCOMPARE(eventHandler.getLongitude(), QStringLiteral("-1.14394").toFloat());
|
||||
QCOMPARE(eventHandler.getLocationAssetType(), QStringLiteral("m.pin"));
|
||||
}
|
||||
|
||||
void EventHandlerTest::readMarkers()
|
||||
{
|
||||
auto event = room->messageEvents().at(0).get();
|
||||
eventHandler.setEvent(event);
|
||||
|
||||
QCOMPARE(eventHandler.hasReadMarkers(), true);
|
||||
|
||||
auto readMarkers = eventHandler.getReadMarkers();
|
||||
|
||||
QCOMPARE(readMarkers.size(), 1);
|
||||
QCOMPARE(readMarkers[0].toMap()["id"_ls], QStringLiteral("@alice:matrix.org"));
|
||||
|
||||
QCOMPARE(eventHandler.getNumberExcessReadMarkers(), QString());
|
||||
QCOMPARE(eventHandler.getReadMarkersString(), QStringLiteral("1 user: @alice:matrix.org"));
|
||||
|
||||
event = room->messageEvents().at(2).get();
|
||||
eventHandler.setEvent(event);
|
||||
|
||||
QCOMPARE(eventHandler.hasReadMarkers(), true);
|
||||
|
||||
readMarkers = eventHandler.getReadMarkers();
|
||||
|
||||
QCOMPARE(readMarkers.size(), 5);
|
||||
|
||||
QCOMPARE(eventHandler.getNumberExcessReadMarkers(), QStringLiteral("+ 1"));
|
||||
// There are no guarantees on the order of the users it will be different every time so don't match the whole string.
|
||||
QCOMPARE(eventHandler.getReadMarkersString().startsWith(QStringLiteral("6 users:")), true);
|
||||
}
|
||||
|
||||
QTEST_MAIN(EventHandlerTest)
|
||||
#include "eventhandlertest.moc"
|
||||
72
autotests/mediasizehelpertest.cpp
Normal file
72
autotests/mediasizehelpertest.cpp
Normal file
@@ -0,0 +1,72 @@
|
||||
// SPDX-FileCopyrightText: 2023 James Graham <james.h.graham@protonmail.com>
|
||||
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
|
||||
#include <QObject>
|
||||
#include <QTest>
|
||||
#include <qglobal.h>
|
||||
#include <qtestcase.h>
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#include "mediasizehelper.h"
|
||||
#include "neochatconfig.h"
|
||||
|
||||
class MediaSizeHelperTest : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private Q_SLOTS:
|
||||
void uninitialized();
|
||||
|
||||
void limits_data();
|
||||
void limits();
|
||||
};
|
||||
|
||||
void MediaSizeHelperTest::uninitialized()
|
||||
{
|
||||
MediaSizeHelper mediasizehelper;
|
||||
QCOMPARE(mediasizehelper.currentSize(), QSize(540, qRound(qreal(NeoChatConfig::self()->mediaMaxWidth()) / qreal(16.0) * qreal(9.0))));
|
||||
}
|
||||
|
||||
void MediaSizeHelperTest::limits_data()
|
||||
{
|
||||
QTest::addColumn<qreal>("mediaWidth");
|
||||
QTest::addColumn<qreal>("mediaHeight");
|
||||
QTest::addColumn<qreal>("contentMaxWidth");
|
||||
QTest::addColumn<qreal>("contentMaxHeight");
|
||||
QTest::addColumn<QSize>("currentSize");
|
||||
|
||||
QTest::newRow("media smaller than content limits") << qreal(200) << qreal(150) << qreal(400) << qreal(900) << QSize(200, 150);
|
||||
QTest::newRow("media smaller than max limits") << qreal(200) << qreal(150) << qreal(-1) << qreal(-1) << QSize(200, 150);
|
||||
|
||||
QTest::newRow("limit by max width") << qreal(600) << qreal(50) << qreal(-1) << qreal(-1) << QSize(540, qRound(qreal(540) / (qreal(600) / qreal(50))));
|
||||
QTest::newRow("limit by max height") << qreal(50) << qreal(600) << qreal(-1) << qreal(-1) << QSize(qRound(qreal(540) * (qreal(50) / qreal(600))), 540);
|
||||
|
||||
QTest::newRow("limit by content width") << qreal(600) << qreal(50) << qreal(300) << qreal(-1) << QSize(300, qRound(qreal(300) / (qreal(600) / qreal(50))));
|
||||
QTest::newRow("limit by content height") << qreal(50) << qreal(600) << qreal(-1) << qreal(300) << QSize(qRound(qreal(300) * (qreal(50) / qreal(600))), 300);
|
||||
|
||||
QTest::newRow("limit by content width tall media")
|
||||
<< qreal(400) << qreal(600) << qreal(100) << qreal(400) << QSize(100, qRound(qreal(100) / (qreal(400) / qreal(600))));
|
||||
QTest::newRow("limit by content height wide media")
|
||||
<< qreal(1000) << qreal(600) << qreal(400) << qreal(100) << QSize(qRound(qreal(100) * (qreal(1000) / qreal(600))), 100);
|
||||
}
|
||||
|
||||
void MediaSizeHelperTest::limits()
|
||||
{
|
||||
QFETCH(qreal, mediaWidth);
|
||||
QFETCH(qreal, mediaHeight);
|
||||
QFETCH(qreal, contentMaxWidth);
|
||||
QFETCH(qreal, contentMaxHeight);
|
||||
QFETCH(QSize, currentSize);
|
||||
|
||||
MediaSizeHelper mediasizehelper;
|
||||
mediasizehelper.setMediaWidth(mediaWidth);
|
||||
mediasizehelper.setMediaHeight(mediaHeight);
|
||||
mediasizehelper.setContentMaxWidth(contentMaxWidth);
|
||||
mediasizehelper.setContentMaxHeight(contentMaxHeight);
|
||||
|
||||
QCOMPARE(mediasizehelper.currentSize(), currentSize);
|
||||
}
|
||||
|
||||
QTEST_GUILESS_MAIN(MediaSizeHelperTest)
|
||||
#include "mediasizehelpertest.moc"
|
||||
148
autotests/neochatroomtest.cpp
Normal file
148
autotests/neochatroomtest.cpp
Normal file
@@ -0,0 +1,148 @@
|
||||
// SPDX-FileCopyrightText: 2022 Carl Schwan <carl@carlschwan.eu>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <QObject>
|
||||
#include <QSignalSpy>
|
||||
#include <QTest>
|
||||
|
||||
#include "neochatroom.h"
|
||||
|
||||
#include <Quotient/connection.h>
|
||||
#include <Quotient/quotient_common.h>
|
||||
#include <Quotient/syncdata.h>
|
||||
|
||||
using namespace Quotient;
|
||||
|
||||
class TestRoom : public NeoChatRoom
|
||||
{
|
||||
public:
|
||||
using NeoChatRoom::NeoChatRoom;
|
||||
|
||||
void update(SyncRoomData &&data, bool fromCache = false)
|
||||
{
|
||||
Room::updateData(std::move(data), fromCache);
|
||||
}
|
||||
};
|
||||
|
||||
class NeoChatRoomTest : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
private:
|
||||
Connection *connection = nullptr;
|
||||
TestRoom *room = nullptr;
|
||||
|
||||
private Q_SLOTS:
|
||||
void initTestCase();
|
||||
void subtitleTextTest();
|
||||
void eventTest();
|
||||
};
|
||||
|
||||
void NeoChatRoomTest::initTestCase()
|
||||
{
|
||||
connection = Connection::makeMockConnection(QStringLiteral("@bob:kde.org"));
|
||||
room = new TestRoom(connection, QStringLiteral("#myroom:kde.org"), JoinState::Join);
|
||||
|
||||
auto json = QJsonDocument::fromJson(R"EVENT({
|
||||
"account_data": {
|
||||
"events": [
|
||||
{
|
||||
"content": {
|
||||
"tags": {
|
||||
"u.work": {
|
||||
"order": 0.9
|
||||
}
|
||||
}
|
||||
},
|
||||
"type": "m.tag"
|
||||
},
|
||||
{
|
||||
"content": {
|
||||
"custom_config_key": "custom_config_value"
|
||||
},
|
||||
"type": "org.example.custom.room.config"
|
||||
}
|
||||
]
|
||||
},
|
||||
"ephemeral": {
|
||||
"events": [
|
||||
{
|
||||
"content": {
|
||||
"user_ids": [
|
||||
"@alice:matrix.org",
|
||||
"@bob:example.com"
|
||||
]
|
||||
},
|
||||
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
|
||||
"type": "m.typing"
|
||||
}
|
||||
]
|
||||
},
|
||||
"state": {
|
||||
"events": [
|
||||
{
|
||||
"content": {
|
||||
"avatar_url": "mxc://example.org/SEsfnsuifSDFSSEF",
|
||||
"displayname": "Alice Margatroid",
|
||||
"membership": "join",
|
||||
"reason": "Looking for support"
|
||||
},
|
||||
"event_id": "$143273582443PhrSn:example.org",
|
||||
"origin_server_ts": 1432735824653,
|
||||
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
|
||||
"sender": "@example:example.org",
|
||||
"state_key": "@alice:example.org",
|
||||
"type": "m.room.member",
|
||||
"unsigned": {
|
||||
"age": 1234
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"summary": {
|
||||
"m.heroes": [
|
||||
"@alice:example.com",
|
||||
"@bob:example.com"
|
||||
],
|
||||
"m.invited_member_count": 0,
|
||||
"m.joined_member_count": 2
|
||||
},
|
||||
"timeline": {
|
||||
"events": [
|
||||
{
|
||||
"content": {
|
||||
"body": "This is an **example** text message",
|
||||
"format": "org.matrix.custom.html",
|
||||
"formatted_body": "<b>This is an example text message</b>",
|
||||
"msgtype": "m.text"
|
||||
},
|
||||
"event_id": "$143273582443PhrSn:example.org",
|
||||
"origin_server_ts": 1432735824654,
|
||||
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
|
||||
"sender": "@example:example.org",
|
||||
"type": "m.room.message",
|
||||
"unsigned": {
|
||||
"age": 1235
|
||||
}
|
||||
}
|
||||
],
|
||||
"limited": true,
|
||||
"prev_batch": "t34-23535_0_0"
|
||||
}
|
||||
})EVENT");
|
||||
SyncRoomData roomData(QStringLiteral("@bob:kde.org"), JoinState::Join, json.object());
|
||||
room->update(std::move(roomData));
|
||||
}
|
||||
|
||||
void NeoChatRoomTest::subtitleTextTest()
|
||||
{
|
||||
QCOMPARE(room->timelineSize(), 1);
|
||||
QCOMPARE(room->lastEventToString(), QStringLiteral("@example:example.org: This is an example text message"));
|
||||
}
|
||||
|
||||
void NeoChatRoomTest::eventTest()
|
||||
{
|
||||
QCOMPARE(room->timelineSize(), 1);
|
||||
}
|
||||
|
||||
QTEST_GUILESS_MAIN(NeoChatRoomTest)
|
||||
#include "neochatroomtest.moc"
|
||||
651
autotests/texthandlertest.cpp
Normal file
651
autotests/texthandlertest.cpp
Normal file
@@ -0,0 +1,651 @@
|
||||
// SPDX-FileCopyrightText: 2023 James Graham <james.h.graham@protonmail.com>
|
||||
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
|
||||
#include <QObject>
|
||||
#include <QTest>
|
||||
|
||||
#include "texthandler.h"
|
||||
|
||||
#include <Quotient/quotient_common.h>
|
||||
#include <Quotient/syncdata.h>
|
||||
#include <qnamespace.h>
|
||||
|
||||
#include "utils.h"
|
||||
|
||||
using namespace Quotient;
|
||||
|
||||
class TestRoom : public NeoChatRoom
|
||||
{
|
||||
public:
|
||||
using NeoChatRoom::NeoChatRoom;
|
||||
|
||||
void update(SyncRoomData &&data, bool fromCache = false)
|
||||
{
|
||||
Room::updateData(std::move(data), fromCache);
|
||||
}
|
||||
};
|
||||
|
||||
class TextHandlerTest : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private:
|
||||
Connection *connection = nullptr;
|
||||
TestRoom *room = nullptr;
|
||||
|
||||
private Q_SLOTS:
|
||||
void initTestCase();
|
||||
|
||||
void allowedAttributes();
|
||||
void stripDisallowedTags();
|
||||
void stripDisallowedAttributes();
|
||||
void emptyCodeTags();
|
||||
|
||||
void sendSimpleStringCase();
|
||||
void sendSingleParaMarkup();
|
||||
void sendMultipleSectionMarkup();
|
||||
void sendBadLinks();
|
||||
void sendEscapeCode();
|
||||
void sendCodeClass();
|
||||
|
||||
void receiveStripReply();
|
||||
void receivePlainTextIn();
|
||||
|
||||
void receiveRichInPlainOut_data();
|
||||
void receiveRichInPlainOut();
|
||||
void receivePlainStripHtml();
|
||||
void receivePlainStripMarkup();
|
||||
void receiveStripNewlines();
|
||||
|
||||
void receiveRichUserPill();
|
||||
void receiveRichStrikethrough();
|
||||
void receiveRichtextIn();
|
||||
void receiveRichMxcUrl();
|
||||
void receiveRichPlainUrl();
|
||||
void receiveRichEmote();
|
||||
void receiveRichEdited_data();
|
||||
void receiveRichEdited();
|
||||
void receiveLineSeparator();
|
||||
|
||||
void linkPreviewsMatch_data();
|
||||
void linkPreviewsMatch();
|
||||
void linkPreviewsReject_data();
|
||||
void linkPreviewsReject();
|
||||
};
|
||||
|
||||
void TextHandlerTest::initTestCase()
|
||||
{
|
||||
connection = Connection::makeMockConnection(QStringLiteral("@bob:kde.org"));
|
||||
room = new TestRoom(connection, QStringLiteral("#myroom:kde.org"), JoinState::Join);
|
||||
|
||||
const auto json = QJsonDocument::fromJson(R"EVENT({
|
||||
"account_data": {
|
||||
"events": [
|
||||
{
|
||||
"content": {
|
||||
"tags": {
|
||||
"u.work": {
|
||||
"order": 0.9
|
||||
}
|
||||
}
|
||||
},
|
||||
"type": "m.tag"
|
||||
},
|
||||
{
|
||||
"content": {
|
||||
"custom_config_key": "custom_config_value"
|
||||
},
|
||||
"type": "org.example.custom.room.config"
|
||||
}
|
||||
]
|
||||
},
|
||||
"ephemeral": {
|
||||
"events": [
|
||||
{
|
||||
"content": {
|
||||
"user_ids": [
|
||||
"@alice:matrix.org",
|
||||
"@bob:example.com"
|
||||
]
|
||||
},
|
||||
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
|
||||
"type": "m.typing"
|
||||
}
|
||||
]
|
||||
},
|
||||
"state": {
|
||||
"events": [
|
||||
{
|
||||
"content": {
|
||||
"avatar_url": "mxc://example.org/SEsfnsuifSDFSSEF",
|
||||
"displayname": "Alice Margatroid",
|
||||
"membership": "join",
|
||||
"reason": "Looking for support"
|
||||
},
|
||||
"event_id": "$143273582443PhrSn:example.org",
|
||||
"origin_server_ts": 1432735824653,
|
||||
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
|
||||
"sender": "@example:example.org",
|
||||
"state_key": "@alice:example.org",
|
||||
"type": "m.room.member",
|
||||
"unsigned": {
|
||||
"age": 1234
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"summary": {
|
||||
"m.heroes": [
|
||||
"@alice:example.com",
|
||||
"@bob:example.com"
|
||||
],
|
||||
"m.invited_member_count": 0,
|
||||
"m.joined_member_count": 2
|
||||
},
|
||||
"timeline": {
|
||||
"events": [
|
||||
{
|
||||
"content": {
|
||||
"body": "This is an **example** text message",
|
||||
"format": "org.matrix.custom.html",
|
||||
"formatted_body": "<b>This is an example text message</b>",
|
||||
"msgtype": "m.text"
|
||||
},
|
||||
"event_id": "$143273582443PhrSn:example.org",
|
||||
"origin_server_ts": 1432735824654,
|
||||
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
|
||||
"sender": "@example:example.org",
|
||||
"type": "m.room.message",
|
||||
"unsigned": {
|
||||
"age": 1232
|
||||
}
|
||||
},
|
||||
{
|
||||
"content": {
|
||||
"body": "/me This is an emote.",
|
||||
"format": "org.matrix.custom.html",
|
||||
"formatted_body": "This is an emote.",
|
||||
"msgtype": "m.emote"
|
||||
},
|
||||
"event_id": "$153273582443PhrSn:example.org",
|
||||
"origin_server_ts": 1532735824654,
|
||||
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
|
||||
"sender": "@example:example.org",
|
||||
"type": "m.room.message",
|
||||
"unsigned": {
|
||||
"age": 1231
|
||||
}
|
||||
},
|
||||
{
|
||||
"content": {
|
||||
"body": "tested",
|
||||
"msgtype": "m.text"
|
||||
},
|
||||
"event_id": "$zrCiBxBnqqTn0Z5FY78qSZAszno_w8nJJXzfBULG-3E",
|
||||
"origin_server_ts": 1680948575928,
|
||||
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
|
||||
"sender": "@example:example.org",
|
||||
"type": "m.room.message",
|
||||
"unsigned": {
|
||||
"age": 1747776,
|
||||
"m.relations": {
|
||||
"m.replace": {
|
||||
"event_id": "$UX0PlpyI7vYO32iHMuuYEP7ECMh4sX3XLGiB2SwM4mQ",
|
||||
"origin_server_ts": 1680948580992,
|
||||
"sender": "@example:example.org"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"limited": true,
|
||||
"prev_batch": "t34-23535_0_0"
|
||||
}
|
||||
})EVENT");
|
||||
SyncRoomData roomData(QStringLiteral("@bob:kde.org"), JoinState::Join, json.object());
|
||||
room->update(std::move(roomData));
|
||||
}
|
||||
|
||||
void TextHandlerTest::allowedAttributes()
|
||||
{
|
||||
const QString testInputString1 = QStringLiteral("<p><span data-mx-spoiler><font color=#FFFFFF>Test</font><span></p>");
|
||||
const QString testOutputString1 = QStringLiteral("<p><span data-mx-spoiler><font color=#FFFFFF>Test</font><span></p>");
|
||||
// Handle urls where the href has either single (') or double (") quotes.
|
||||
const QString testInputString2 = QStringLiteral("<p><a href=\"https://kde.org\">link</a><a href='https://kde.org'>link</a></p>");
|
||||
const QString testOutputString2 = QStringLiteral("<p><a href=\"https://kde.org\">link</a><a href='https://kde.org'>link</a></p>");
|
||||
|
||||
TextHandler testTextHandler;
|
||||
testTextHandler.setData(testInputString1);
|
||||
|
||||
QCOMPARE(testTextHandler.handleSendText(), testOutputString1);
|
||||
QCOMPARE(testTextHandler.handleRecieveRichText(), testOutputString1);
|
||||
|
||||
testTextHandler.setData(testInputString2);
|
||||
QCOMPARE(testTextHandler.handleSendText(), testOutputString2);
|
||||
QCOMPARE(testTextHandler.handleRecieveRichText(), testOutputString2);
|
||||
}
|
||||
|
||||
void TextHandlerTest::stripDisallowedTags()
|
||||
{
|
||||
const QString testInputString = QStringLiteral("<p>Allowed</p> <span>Allowed</span> <body>Disallowed</body>");
|
||||
const QString testOutputString = QStringLiteral("<p>Allowed</p> <span>Allowed</span> Disallowed");
|
||||
|
||||
TextHandler testTextHandler;
|
||||
testTextHandler.setData(testInputString);
|
||||
|
||||
QCOMPARE(testTextHandler.handleSendText(), testOutputString);
|
||||
QCOMPARE(testTextHandler.handleRecieveRichText(), testOutputString);
|
||||
}
|
||||
|
||||
void TextHandlerTest::stripDisallowedAttributes()
|
||||
{
|
||||
const QString testInputString = QStringLiteral("<p style=\"font-size:50px;\" color=#FFFFFF>Test</p>");
|
||||
const QString testOutputString = QStringLiteral("<p>Test</p>");
|
||||
|
||||
TextHandler testTextHandler;
|
||||
testTextHandler.setData(testInputString);
|
||||
|
||||
QCOMPARE(testTextHandler.handleSendText(), testOutputString);
|
||||
QCOMPARE(testTextHandler.handleRecieveRichText(), testOutputString);
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure that empty code tags are handled.
|
||||
* (this was a bug during development hence the test)
|
||||
*/
|
||||
void TextHandlerTest::emptyCodeTags()
|
||||
{
|
||||
const QString testInputString = QStringLiteral("<pre><code></code></pre>");
|
||||
const QString testOutputString = QStringLiteral("<pre><code></code></pre>");
|
||||
|
||||
TextHandler testTextHandler;
|
||||
testTextHandler.setData(testInputString);
|
||||
|
||||
QCOMPARE(testTextHandler.handleSendText(), testOutputString);
|
||||
QCOMPARE(testTextHandler.handleRecieveRichText(), testOutputString);
|
||||
}
|
||||
|
||||
void TextHandlerTest::sendSimpleStringCase()
|
||||
{
|
||||
const QString testInputString = QStringLiteral("This data should just be put in a paragraph.");
|
||||
const QString testOutputString = QStringLiteral("<p>This data should just be put in a paragraph.</p>");
|
||||
|
||||
TextHandler testTextHandler;
|
||||
testTextHandler.setData(testInputString);
|
||||
|
||||
QCOMPARE(testTextHandler.handleSendText(), testOutputString);
|
||||
}
|
||||
|
||||
void TextHandlerTest::sendSingleParaMarkup()
|
||||
{
|
||||
const QString testInputString = QStringLiteral(
|
||||
"Text para with **bold**, *italic*, [link](https://kde.org), , `inline code`.");
|
||||
const QString testOutputString = QStringLiteral(
|
||||
"<p>Text para with <strong>bold</strong>, <em>italic</em>, <a href=\"https://kde.org\">link</a>, <img "
|
||||
"src=\"mxc://kde.org/aebd3ffd40503e1ef0525bf8f0d60282fec6183e\" alt=\"image\">, <code>inline code</code>.</p>");
|
||||
|
||||
TextHandler testTextHandler;
|
||||
testTextHandler.setData(testInputString);
|
||||
|
||||
QCOMPARE(testTextHandler.handleSendText(), testOutputString);
|
||||
}
|
||||
|
||||
void TextHandlerTest::sendMultipleSectionMarkup()
|
||||
{
|
||||
const QString testInputString =
|
||||
QStringLiteral("Text para\n> blockquote\n* List 1\n* List 2\n1. one\n2. two\n# Heading 1\n## Heading 2\nhorizontal rule\n\n---\n```\ncodeblock\n```");
|
||||
const QString testOutputString = QStringLiteral(
|
||||
"<p>Text para</p>\n<blockquote>\n<p>blockquote</p>\n</blockquote>\n<ul>\n<li>List 1</li>\n<li>List "
|
||||
"2</li>\n</ul>\n<ol>\n<li>one</li>\n<li>two</li>\n</ol>\n<h1>Heading 1</h1>\n<h2>Heading 2</h2>\n<p>horizontal "
|
||||
"rule</p>\n<hr>\n<pre><code>codeblock\n</code></pre>");
|
||||
|
||||
TextHandler testTextHandler;
|
||||
testTextHandler.setData(testInputString);
|
||||
|
||||
QCOMPARE(testTextHandler.handleSendText(), testOutputString);
|
||||
}
|
||||
|
||||
void TextHandlerTest::sendBadLinks()
|
||||
{
|
||||
const QString testInputString = QStringLiteral("[link](kde.org), ");
|
||||
const QString testOutputString = QStringLiteral("<p><a>link</a>, <img alt=\"image\"></p>");
|
||||
|
||||
TextHandler testTextHandler;
|
||||
testTextHandler.setData(testInputString);
|
||||
|
||||
QCOMPARE(testTextHandler.handleSendText(), testOutputString);
|
||||
}
|
||||
|
||||
/**
|
||||
* All text between code tags is treated as plain so it should get escaped.
|
||||
*/
|
||||
void TextHandlerTest::sendEscapeCode()
|
||||
{
|
||||
const QString testInputString = QStringLiteral("```\n<p>Test <span style=\"font-size:50px;\">some</span> code</p>\n```");
|
||||
const QString testOutputString =
|
||||
QStringLiteral("<pre><code><p>Test <span style="font-size:50px;">some</span> code</p>\n</code></pre>");
|
||||
|
||||
TextHandler testTextHandler;
|
||||
testTextHandler.setData(testInputString);
|
||||
|
||||
QCOMPARE(testTextHandler.handleSendText(), testOutputString);
|
||||
}
|
||||
|
||||
void TextHandlerTest::sendCodeClass()
|
||||
{
|
||||
const QString testInputString = QStringLiteral("```html\nsome code\n```\n<pre><code class=\"code-underline\">some more code</code></pre>");
|
||||
const QString testOutputString = QStringLiteral("<pre><code class=\"language-html\">some code\n</code></pre>\n<pre><code>some more code</code></pre>");
|
||||
|
||||
TextHandler testTextHandler;
|
||||
testTextHandler.setData(testInputString);
|
||||
|
||||
QCOMPARE(testTextHandler.handleSendText(), testOutputString);
|
||||
}
|
||||
|
||||
void TextHandlerTest::receiveStripReply()
|
||||
{
|
||||
const QString testInputString = QStringLiteral(
|
||||
"<mx-reply><blockquote><a href=\"https://matrix.to/#/!somewhere:example.org/$event:example.org\">In reply to</a><a "
|
||||
"href=\"https://matrix.to/#/@alice:example.org\">@alice:example.org</a><br />Message replied to.</blockquote></mx-reply>Reply message.");
|
||||
const QString testOutputString = QStringLiteral("Reply message.");
|
||||
|
||||
TextHandler testTextHandler;
|
||||
testTextHandler.setData(testInputString);
|
||||
|
||||
QCOMPARE(testTextHandler.handleRecieveRichText(), testOutputString);
|
||||
QCOMPARE(testTextHandler.handleRecievePlainText(), testOutputString);
|
||||
}
|
||||
|
||||
void TextHandlerTest::receiveRichInPlainOut_data()
|
||||
{
|
||||
QTest::addColumn<QString>("testInputString");
|
||||
QTest::addColumn<QString>("testOutputString");
|
||||
|
||||
QTest::newRow("ampersand") << QStringLiteral("a & b") << QStringLiteral("a & b");
|
||||
QTest::newRow("quote") << QStringLiteral(""a and b"") << QStringLiteral("\"a and b\"");
|
||||
QTest::newRow("new line") << QStringLiteral("new<br>line") << QStringLiteral("new\nline");
|
||||
}
|
||||
|
||||
void TextHandlerTest::receiveRichInPlainOut()
|
||||
{
|
||||
QFETCH(QString, testInputString);
|
||||
QFETCH(QString, testOutputString);
|
||||
|
||||
TextHandler testTextHandler;
|
||||
testTextHandler.setData(testInputString);
|
||||
|
||||
QCOMPARE(testTextHandler.handleRecievePlainText(Qt::RichText), testOutputString);
|
||||
}
|
||||
|
||||
void TextHandlerTest::receivePlainTextIn()
|
||||
{
|
||||
const QString testInputString = QStringLiteral("<plain text in tag bracket>\nTest link https://kde.org.");
|
||||
const QString testOutputStringRich = QStringLiteral("<plain text in tag bracket><br>Test link <a href=\"https://kde.org\">https://kde.org</a>.");
|
||||
QString testOutputStringPlain = QStringLiteral("<plain text in tag bracket>\nTest link https://kde.org.");
|
||||
|
||||
// Make sure quotes are maintained in a plain string.
|
||||
const QString testInputString2 = QStringLiteral("last line is \"Time to switch to a new topic.\"");
|
||||
const QString testOutputString2 = QStringLiteral("last line is \"Time to switch to a new topic.\"");
|
||||
|
||||
TextHandler testTextHandler;
|
||||
testTextHandler.setData(testInputString);
|
||||
|
||||
QCOMPARE(testTextHandler.handleRecieveRichText(Qt::PlainText), testOutputStringRich);
|
||||
QCOMPARE(testTextHandler.handleRecievePlainText(), testOutputStringPlain);
|
||||
|
||||
testTextHandler.setData(testInputString2);
|
||||
QCOMPARE(testTextHandler.handleRecieveRichText(Qt::PlainText), testOutputString2);
|
||||
QCOMPARE(testTextHandler.handleRecievePlainText(), testOutputString2);
|
||||
}
|
||||
|
||||
void TextHandlerTest::receiveStripNewlines()
|
||||
{
|
||||
const QString testInputStringPlain = QStringLiteral("Test\nmany\nnew\nlines.");
|
||||
const QString testInputStringRich = QStringLiteral("Test<br>many<br />new<br>lines.");
|
||||
const QString testOutputString = QStringLiteral("Test many new lines.");
|
||||
|
||||
const QString testInputStringPlain2 = QStringLiteral("* List\n* Items");
|
||||
const QString testOutputString2 = QStringLiteral("List Items");
|
||||
|
||||
TextHandler testTextHandler;
|
||||
testTextHandler.setData(testInputStringPlain);
|
||||
|
||||
QCOMPARE(testTextHandler.handleRecievePlainText(Qt::PlainText, true), testOutputString);
|
||||
QCOMPARE(testTextHandler.handleRecieveRichText(Qt::PlainText, nullptr, nullptr, true), testOutputString);
|
||||
|
||||
testTextHandler.setData(testInputStringRich);
|
||||
QCOMPARE(testTextHandler.handleRecievePlainText(Qt::RichText, true), testOutputString);
|
||||
QCOMPARE(testTextHandler.handleRecieveRichText(Qt::RichText, nullptr, nullptr, true), testOutputString);
|
||||
|
||||
testTextHandler.setData(testInputStringPlain2);
|
||||
QCOMPARE(testTextHandler.handleRecievePlainText(Qt::RichText, true), testOutputString2);
|
||||
}
|
||||
|
||||
/**
|
||||
* For a plain text output of a received string all html is stripped except for
|
||||
* code which is unescaped if it's html.
|
||||
*/
|
||||
void TextHandlerTest::receivePlainStripHtml()
|
||||
{
|
||||
const QString testInputString = QStringLiteral("<p>Test</p> <pre><code>Some code <strong>with tags</strong></code></pre>");
|
||||
const QString testOutputString = QStringLiteral("Test Some code <strong>with tags</strong>");
|
||||
|
||||
TextHandler testTextHandler;
|
||||
testTextHandler.setData(testInputString);
|
||||
|
||||
QCOMPARE(testTextHandler.handleRecievePlainText(Qt::RichText), testOutputString);
|
||||
}
|
||||
|
||||
void TextHandlerTest::receivePlainStripMarkup()
|
||||
{
|
||||
const QString testInputString = QStringLiteral("**bold** `<p>inline code</p>` *italic*");
|
||||
const QString testOutputString = QStringLiteral("bold <p>inline code</p> italic");
|
||||
|
||||
TextHandler testTextHandler;
|
||||
testTextHandler.setData(testInputString);
|
||||
|
||||
QCOMPARE(testTextHandler.handleRecievePlainText(), testOutputString);
|
||||
}
|
||||
|
||||
void TextHandlerTest::receiveRichUserPill()
|
||||
{
|
||||
const QString testInputString = QStringLiteral("<p><a href=\"https://matrix.to/#/@alice:example.org\">@alice:example.org</a></p>");
|
||||
const QString testOutputString = QStringLiteral("<p><b><a href=\"https://matrix.to/#/@alice:example.org\">@alice:example.org</a></b></p>");
|
||||
|
||||
TextHandler testTextHandler;
|
||||
testTextHandler.setData(testInputString);
|
||||
|
||||
QCOMPARE(testTextHandler.handleRecieveRichText(), testOutputString);
|
||||
}
|
||||
|
||||
void TextHandlerTest::receiveRichStrikethrough()
|
||||
{
|
||||
const QString testInputString = QStringLiteral("<p><del>Test</del></p>");
|
||||
const QString testOutputString = QStringLiteral("<p><s>Test</s></p>");
|
||||
|
||||
TextHandler testTextHandler;
|
||||
testTextHandler.setData(testInputString);
|
||||
|
||||
QCOMPARE(testTextHandler.handleRecieveRichText(), testOutputString);
|
||||
}
|
||||
|
||||
void TextHandlerTest::receiveRichtextIn()
|
||||
{
|
||||
const QString testInputString = QStringLiteral("<p>Test</p> <pre><code>Some code <strong>with tags</strong></code></pre>");
|
||||
const QString testOutputString = QStringLiteral("<p>Test</p> <pre><code>Some code <strong>with tags</strong></code></pre>");
|
||||
|
||||
TextHandler testTextHandler;
|
||||
testTextHandler.setData(testInputString);
|
||||
|
||||
QCOMPARE(testTextHandler.handleRecieveRichText(), testOutputString);
|
||||
}
|
||||
|
||||
void TextHandlerTest::receiveRichMxcUrl()
|
||||
{
|
||||
const QString testInputString = QStringLiteral(
|
||||
"<img src=\"mxc://kde.org/aebd3ffd40503e1ef0525bf8f0d60282fec6183e\" alt=\"image\"><img src=\"mxc://kde.org/34c3464b3a1bd7f55af2d559e07d2c773c430e73\" "
|
||||
"alt=\"image\">");
|
||||
const QString testOutputString = QStringLiteral(
|
||||
"<img "
|
||||
"src=\"mxc://kde.org/aebd3ffd40503e1ef0525bf8f0d60282fec6183e?user_id=@bob:kde.org&room_id=%23myroom:kde.org&event_id=$143273582443PhrSn:example.org\" "
|
||||
"alt=\"image\"><img "
|
||||
"src=\"mxc://kde.org/34c3464b3a1bd7f55af2d559e07d2c773c430e73?user_id=@bob:kde.org&room_id=%23myroom:kde.org&event_id=$143273582443PhrSn:example.org\" "
|
||||
"alt=\"image\">");
|
||||
|
||||
TextHandler testTextHandler;
|
||||
testTextHandler.setData(testInputString);
|
||||
|
||||
QCOMPARE(testTextHandler.handleRecieveRichText(Qt::RichText, room, room->messageEvents().at(0).get()), testOutputString);
|
||||
}
|
||||
|
||||
/**
|
||||
* For when your rich input string has a plain text url left in.
|
||||
*
|
||||
* This test is to show that a url that is already rich will be left alone but a
|
||||
* plain one will be linkified.
|
||||
*/
|
||||
void TextHandlerTest::receiveRichPlainUrl()
|
||||
{
|
||||
// This is an actual link that caused trouble which is why it's so long. Keeping
|
||||
// so we can confirm consistent behaviour for complex urls.
|
||||
const QString testInputStringLink1 = QStringLiteral(
|
||||
"https://matrix.to/#/!RvzunyTWZGfNxJVQqv:matrix.org/$-9TJVTh5PvW6MvIhFDwteiyLBVGriinueO5eeIazQS8?via=libera.chat&via=matrix.org&via=fedora.im "
|
||||
"<a "
|
||||
"href=\"https://matrix.to/#/!RvzunyTWZGfNxJVQqv:matrix.org/"
|
||||
"$-9TJVTh5PvW6MvIhFDwteiyLBVGriinueO5eeIazQS8?via=libera.chat&via=matrix.org&via=fedora.im\">Link already rich</a>");
|
||||
const QString testOutputStringLink1 = QStringLiteral(
|
||||
"<a "
|
||||
"href=\"https://matrix.to/#/!RvzunyTWZGfNxJVQqv:matrix.org/"
|
||||
"$-9TJVTh5PvW6MvIhFDwteiyLBVGriinueO5eeIazQS8?via=libera.chat&via=matrix.org&via=fedora.im\">https://matrix.to/#/"
|
||||
"!RvzunyTWZGfNxJVQqv:matrix.org/$-9TJVTh5PvW6MvIhFDwteiyLBVGriinueO5eeIazQS8?via=libera.chat&via=matrix.org&via=fedora.im</a> <a "
|
||||
"href=\"https://matrix.to/#/!RvzunyTWZGfNxJVQqv:matrix.org/"
|
||||
"$-9TJVTh5PvW6MvIhFDwteiyLBVGriinueO5eeIazQS8?via=libera.chat&via=matrix.org&via=fedora.im\">Link already rich</a>");
|
||||
|
||||
// Another real case. The linkification wasn't handling it when a single link
|
||||
// contains what looks like and email. It was been broken into 3 but needs to
|
||||
// be just single link.
|
||||
const QString testInputStringLink2 = QStringLiteral("https://lore.kernel.org/lkml/CAHk-=wio46vC4t6xXD-sFqjoPwFm_u515jm3suzmkGxQTeA1_A@mail.gmail.com/");
|
||||
const QString testOutputStringLink2 = QStringLiteral(
|
||||
"<a "
|
||||
"href=\"https://lore.kernel.org/lkml/CAHk-=wio46vC4t6xXD-sFqjoPwFm_u515jm3suzmkGxQTeA1_A@mail.gmail.com/\">https://lore.kernel.org/lkml/"
|
||||
"CAHk-=wio46vC4t6xXD-sFqjoPwFm_u515jm3suzmkGxQTeA1_A@mail.gmail.com/</a>");
|
||||
|
||||
QString testInputStringEmail = QStringLiteral(R"(email@example.com <a href="mailto:email@example.com">Link already rich</a>)");
|
||||
QString testOutputStringEmail =
|
||||
QStringLiteral(R"(<a href="mailto:email@example.com">email@example.com</a> <a href="mailto:email@example.com">Link already rich</a>)");
|
||||
|
||||
QString testInputStringMxId = QStringLiteral("@user:kde.org <a href=\"https://matrix.to/#/@user:kde.org\">Link already rich</a>");
|
||||
QString testOutputStringMxId = QStringLiteral(
|
||||
"<b><a href=\"https://matrix.to/#/@user:kde.org\">@user:kde.org</a></b> <b><a href=\"https://matrix.to/#/@user:kde.org\">Link already rich</a></b>");
|
||||
|
||||
TextHandler testTextHandler;
|
||||
testTextHandler.setData(testInputStringLink1);
|
||||
|
||||
QCOMPARE(testTextHandler.handleRecieveRichText(Qt::RichText), testOutputStringLink1);
|
||||
|
||||
testTextHandler.setData(testInputStringLink2);
|
||||
QCOMPARE(testTextHandler.handleRecieveRichText(Qt::RichText), testOutputStringLink2);
|
||||
|
||||
testTextHandler.setData(testInputStringEmail);
|
||||
QCOMPARE(testTextHandler.handleRecieveRichText(Qt::RichText), testOutputStringEmail);
|
||||
|
||||
testTextHandler.setData(testInputStringMxId);
|
||||
QCOMPARE(testTextHandler.handleRecieveRichText(Qt::RichText), testOutputStringMxId);
|
||||
}
|
||||
|
||||
// Test that user pill is add to an emote message.
|
||||
// N.B. The second message in the test timeline is marked as an emote.
|
||||
void TextHandlerTest::receiveRichEmote()
|
||||
{
|
||||
auto event = room->messageEvents().at(1).get();
|
||||
auto author = room->user(event->senderId());
|
||||
const QString testInputString = QStringLiteral("This is an emote.");
|
||||
const QString testOutputString = QStringLiteral("* <a href=\"https://matrix.to/#/@example:example.org\" style=\"color:")
|
||||
+ Utils::getUserColor(author->hueF()).name() + QStringLiteral("\">@example:example.org</a> This is an emote.");
|
||||
|
||||
TextHandler testTextHandler;
|
||||
testTextHandler.setData(testInputString);
|
||||
|
||||
QCOMPARE(testTextHandler.handleRecieveRichText(Qt::RichText, room, event), testOutputString);
|
||||
}
|
||||
|
||||
void TextHandlerTest::receiveRichEdited_data()
|
||||
{
|
||||
QTest::addColumn<QString>("testInputString");
|
||||
QTest::addColumn<QString>("testOutputString");
|
||||
|
||||
QTest::newRow("basic") << QStringLiteral("Edited") << QStringLiteral("Edited <span style=\"color:#000000\">(edited)</span>");
|
||||
QTest::newRow("multiple paragraphs") << QStringLiteral("<p>Edited</p>\n<p>Edited</p>")
|
||||
<< QStringLiteral("<p>Edited</p>\n<p>Edited <span style=\"color:#000000\">(edited)</span></p>");
|
||||
QTest::newRow("blockquote") << QStringLiteral("<blockquote>Edited</blockquote>")
|
||||
<< QStringLiteral("<blockquote>Edited</blockquote><p> <span style=\"color:#000000\">(edited)</span></p>");
|
||||
}
|
||||
|
||||
void TextHandlerTest::receiveRichEdited()
|
||||
{
|
||||
QFETCH(QString, testInputString);
|
||||
QFETCH(QString, testOutputString);
|
||||
|
||||
TextHandler testTextHandler;
|
||||
testTextHandler.setData(testInputString);
|
||||
|
||||
QCOMPARE(testTextHandler.handleRecieveRichText(Qt::RichText, room, room->messageEvents().at(2).get()), testOutputString);
|
||||
}
|
||||
|
||||
void TextHandlerTest::receiveLineSeparator()
|
||||
{
|
||||
auto text = QStringLiteral("foo\u2028bar");
|
||||
TextHandler textHandler;
|
||||
textHandler.setData(text);
|
||||
QCOMPARE(textHandler.handleRecievePlainText(Qt::PlainText, true), QStringLiteral("foo bar"));
|
||||
}
|
||||
|
||||
void TextHandlerTest::linkPreviewsMatch_data()
|
||||
{
|
||||
QTest::addColumn<QString>("testInputString");
|
||||
QTest::addColumn<QList<QUrl>>("testOutputLinks");
|
||||
|
||||
QTest::newRow("plainHttps") << QStringLiteral("https://kde.org") << QList<QUrl>({QUrl("https://kde.org"_ls)});
|
||||
QTest::newRow("richHttps") << QStringLiteral("<a href=\"https://kde.org\">Rich Link</a>") << QList<QUrl>({QUrl("https://kde.org"_ls)});
|
||||
QTest::newRow("plainWww") << QStringLiteral("www.example.org") << QList<QUrl>({QUrl("www.example.org"_ls)});
|
||||
QTest::newRow("multipleHttps") << QStringLiteral("https://kde.org www.example.org")
|
||||
<< QList<QUrl>({
|
||||
QUrl("https://kde.org"_ls),
|
||||
QUrl("www.example.org"_ls),
|
||||
});
|
||||
}
|
||||
|
||||
void TextHandlerTest::linkPreviewsMatch()
|
||||
{
|
||||
QFETCH(QString, testInputString);
|
||||
QFETCH(QList<QUrl>, testOutputLinks);
|
||||
|
||||
TextHandler testTextHandler;
|
||||
testTextHandler.setData(testInputString);
|
||||
|
||||
QCOMPARE(testTextHandler.getLinkPreviews(), testOutputLinks);
|
||||
}
|
||||
|
||||
void TextHandlerTest::linkPreviewsReject_data()
|
||||
{
|
||||
QTest::addColumn<QString>("testInputString");
|
||||
QTest::addColumn<QList<QUrl>>("testOutputLinks");
|
||||
|
||||
QTest::newRow("mxc") << QStringLiteral("mxc://example.org/SEsfnsuifSDFSSEF") << QList<QUrl>();
|
||||
QTest::newRow("matrixTo") << QStringLiteral("https://matrix.to/#/@alice:example.org") << QList<QUrl>();
|
||||
QTest::newRow("noSpace") << QStringLiteral("testhttps://kde.org") << QList<QUrl>();
|
||||
}
|
||||
|
||||
void TextHandlerTest::linkPreviewsReject()
|
||||
{
|
||||
QFETCH(QString, testInputString);
|
||||
QFETCH(QList<QUrl>, testOutputLinks);
|
||||
|
||||
TextHandler testTextHandler;
|
||||
testTextHandler.setData(testInputString);
|
||||
|
||||
QCOMPARE(testTextHandler.getLinkPreviews(), testOutputLinks);
|
||||
}
|
||||
|
||||
QTEST_MAIN(TextHandlerTest)
|
||||
#include "texthandlertest.moc"
|
||||
5
doc/CMakeLists.txt
Normal file
5
doc/CMakeLists.txt
Normal file
@@ -0,0 +1,5 @@
|
||||
# SPDX-FileCopyrightText: 2022 Carl Schwan <carl@carlschwan.eu>
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
kdoctools_create_manpage(man-neochat.1.docbook 1 INSTALL_DESTINATION ${MAN_INSTALL_DIR})
|
||||
85
doc/man-neochat.1.docbook
Normal file
85
doc/man-neochat.1.docbook
Normal file
@@ -0,0 +1,85 @@
|
||||
<?xml version="1.0" ?>
|
||||
<!DOCTYPE refentry PUBLIC "-//KDE//DTD DocBook XML V4.5-Based Variant V1.1//EN" "dtd/kdedbx45.dtd" [
|
||||
<!ENTITY % English "INCLUDE">
|
||||
]>
|
||||
<!--
|
||||
SPDX-FileCopyrightText: 2022 Carl Schwan <carl@carlschwan.eu>
|
||||
SPDX-License-Identifier: CC-BY-SA-4.0
|
||||
-->
|
||||
|
||||
<refentry lang="&language;">
|
||||
<refentryinfo>
|
||||
<title>NeoChat User's Manual</title>
|
||||
<author><firstname>Carl</firstname><surname>Schwan</surname>
|
||||
<contrib>NeoChat man page.</contrib>
|
||||
<email>carl@carlschwan.eu</email></author>
|
||||
<date>2022-11-01</date>
|
||||
<releaseinfo>22.09</releaseinfo>
|
||||
<productname>NeoChat</productname>
|
||||
</refentryinfo>
|
||||
|
||||
<refmeta>
|
||||
<refentrytitle>
|
||||
<command>neochat</command>
|
||||
</refentrytitle>
|
||||
<manvolnum>1</manvolnum>
|
||||
</refmeta>
|
||||
|
||||
<refnamediv>
|
||||
<refname>neochat</refname>
|
||||
<refpurpose>Client for interacting with the matrix messaging protocol</refpurpose>
|
||||
</refnamediv>
|
||||
<!-- body begins here -->
|
||||
<refsynopsisdiv id='synopsis'>
|
||||
<cmdsynopsis>
|
||||
<command>neochat</command>
|
||||
<arg choice="opt"><replaceable>URI</replaceable></arg>
|
||||
</cmdsynopsis>
|
||||
</refsynopsisdiv>
|
||||
|
||||
|
||||
<refsect1 id="description">
|
||||
<title>Description</title>
|
||||
<para>
|
||||
<command>neochat</command> is a chat application for the matrix protocol
|
||||
that work on both desktop and mobile.
|
||||
</para>
|
||||
</refsect1>
|
||||
|
||||
<refsect1 id="options"><title>Options</title>
|
||||
<variablelist>
|
||||
<varlistentry>
|
||||
<term><option>URI</option></term>
|
||||
<listitem>
|
||||
<para>
|
||||
The matrix uri for an user or a room. e.g matrix:u/user:example.org and
|
||||
matrix:r/root:example.org. This will makes NeoChat try to open the given
|
||||
room or conversation.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
</variablelist>
|
||||
</refsect1>
|
||||
|
||||
<refsect1 id="bug">
|
||||
<title>Reporting bugs</title>
|
||||
<para>You can report bugs and feature requests at <ulink url="https://bugs.kde.org/enter_bug.cgi?product=NeoChat&component=General">https://bugs.kde.org/enter_bug.cgi?product=NeoChat&component=General</ulink></para>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
<title>See Also</title>
|
||||
<simplelist>
|
||||
<member>
|
||||
A list of frequently asked questions about Matrix <ulink url="https://matrix.org/faq/">https://matrix.org/faq/</ulink>
|
||||
</member>
|
||||
<member>kf5options(7)</member>
|
||||
<member>qt5options(7)</member>
|
||||
</simplelist>
|
||||
</refsect1>
|
||||
|
||||
<refsect1 id="copyright"><title>Copyright</title>
|
||||
<para>Copyright © 2020-2022 Tobias Fella </para>
|
||||
<para>Copyright © 2020-2022 Carl Schwan </para>
|
||||
<para>License: GNU General Public Version 3 or later <<ulink url="https://www.gnu.org/licenses/gpl-3.0.html">https://www.gnu.org/licenses/gpl-3.0.html</ulink>></para>
|
||||
</refsect1>
|
||||
</refentry>
|
||||
BIN
icons/150-apps-neochat.png
Normal file
BIN
icons/150-apps-neochat.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.5 KiB |
BIN
icons/300-apps-neochat.png
Normal file
BIN
icons/300-apps-neochat.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
BIN
icons/44-apps-neochat.png
Normal file
BIN
icons/44-apps-neochat.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.5 KiB |
BIN
icons/windows/promoimage-1920x1080.png
Normal file
BIN
icons/windows/promoimage-1920x1080.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 68 KiB |
BIN
icons/windows/storelogo-1080x1080.png
Normal file
BIN
icons/windows/storelogo-1080x1080.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 67 KiB |
BIN
icons/windows/storelogo-720x1080.png
Normal file
BIN
icons/windows/storelogo-720x1080.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 36 KiB |
@@ -1,197 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.de>
|
||||
// SPDX-FileCopyrightText: 2020 Noah Davis <noahadvs@gmail.com>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
import QtQuick.Controls 2.15
|
||||
|
||||
import org.kde.kirigami 2.15 as Kirigami
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
import NeoChat.Page 1.0
|
||||
|
||||
Loader {
|
||||
id: root
|
||||
|
||||
property var attachmentMimetype: FileType.mimeTypeForUrl(chatBoxHelper.attachmentPath)
|
||||
readonly property bool hasImage: attachmentMimetype.valid && FileType.supportedImageFormats.includes(attachmentMimetype.preferredSuffix)
|
||||
|
||||
active: visible
|
||||
sourceComponent: Component {
|
||||
Pane {
|
||||
id: attachmentPane
|
||||
property string baseFileName: chatBoxHelper.attachmentPath.toString().substring(chatBoxHelper.attachmentPath.toString().lastIndexOf('/') + 1, chatBoxHelper.attachmentPath.length)
|
||||
Kirigami.Theme.colorSet: Kirigami.Theme.View
|
||||
|
||||
contentItem: Item {
|
||||
property real spacing: attachmentPane.spacing > 0 ? attachmentPane.spacing : toolBar.spacing
|
||||
implicitWidth: Math.max(image.implicitWidth, imageBusyIndicator.implicitWidth, fileInfoLayout.implicitWidth, toolBar.implicitWidth)
|
||||
implicitHeight: Math.max(
|
||||
(hasImage ? Math.max(image.preferredHeight, imageBusyIndicator.implicitHeight) + spacing : 0)
|
||||
+ fileInfoLayout.implicitHeight,
|
||||
toolBar.implicitHeight
|
||||
)
|
||||
|
||||
Image {
|
||||
id: image
|
||||
property real preferredHeight: Math.min(implicitHeight, Kirigami.Units.gridUnit * 8)
|
||||
height: preferredHeight
|
||||
anchors {
|
||||
horizontalCenter: parent.horizontalCenter
|
||||
bottom: fileInfoLayout.top
|
||||
bottomMargin: parent.spacing
|
||||
}
|
||||
width: Math.min(implicitWidth, attachmentPane.availableWidth)
|
||||
asynchronous: true
|
||||
cache: false // Cache is not needed. Images will rarely be shown repeatedly.
|
||||
smooth: height == preferredHeight && parent.height == parent.implicitHeight // Don't smooth until height animation stops
|
||||
source: hasImage ? chatBoxHelper.attachmentPath : ""
|
||||
visible: hasImage
|
||||
fillMode: Image.PreserveAspectFit
|
||||
|
||||
onSourceChanged: {
|
||||
// Reset source size height, which affect implicitHeight
|
||||
sourceSize.height = -1
|
||||
}
|
||||
|
||||
onSourceSizeChanged: {
|
||||
if (implicitHeight > Kirigami.Units.gridUnit * 8) {
|
||||
// This can save a lot of RAM when loading large images.
|
||||
// It also improves visual quality for large images.
|
||||
sourceSize.height = Kirigami.Units.gridUnit * 8
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on height {
|
||||
NumberAnimation {
|
||||
property: "height"
|
||||
duration: Kirigami.Units.shortDuration
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BusyIndicator {
|
||||
id: imageBusyIndicator
|
||||
anchors {
|
||||
horizontalCenter: parent.horizontalCenter
|
||||
top: parent.top
|
||||
bottom: fileInfoLayout.top
|
||||
bottomMargin: parent.spacing
|
||||
}
|
||||
visible: running
|
||||
running: image.visible && image.progress < 1
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: fileInfoLayout
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.verticalCenter: undefined
|
||||
anchors.bottom: parent.bottom
|
||||
spacing: parent.spacing
|
||||
|
||||
Kirigami.Icon {
|
||||
id: mimetypeIcon
|
||||
implicitHeight: Kirigami.Units.fontMetrics.roundedIconSize(fileLabel.implicitHeight)
|
||||
implicitWidth: implicitHeight
|
||||
source: attachmentMimetype.iconName
|
||||
}
|
||||
|
||||
Label {
|
||||
id: fileLabel
|
||||
text: baseFileName
|
||||
}
|
||||
|
||||
states: State {
|
||||
when: !hasImage
|
||||
AnchorChanges {
|
||||
target: fileInfoLayout
|
||||
anchors.bottom: undefined
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Using a toolbar to get a button spacing consistent with what the QQC2 style normally has
|
||||
// Also has some accessibility info
|
||||
ToolBar {
|
||||
id: toolBar
|
||||
width: parent.width
|
||||
anchors.top: parent.top
|
||||
|
||||
leftPadding: 0
|
||||
rightPadding: 0
|
||||
topPadding: 0
|
||||
bottomPadding: 0
|
||||
|
||||
Kirigami.Theme.inherit: true
|
||||
Kirigami.Theme.colorSet: Kirigami.Theme.View
|
||||
|
||||
contentItem: RowLayout {
|
||||
spacing: parent.spacing
|
||||
Label {
|
||||
Layout.leftMargin: -attachmentPane.leftPadding
|
||||
Layout.topMargin: -attachmentPane.topPadding
|
||||
leftPadding: cancelAttachmentButton.leftPadding + 1 + attachmentPane.leftPadding
|
||||
rightPadding: cancelAttachmentButton.rightPadding + 1
|
||||
topPadding: cancelAttachmentButton.topPadding + attachmentPane.topPadding
|
||||
bottomPadding: cancelAttachmentButton.bottomPadding
|
||||
text: i18n("Attachment:")
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
background: Kirigami.ShadowedRectangle {
|
||||
property real cornerRadius: cancelAttachmentButton.background.hasOwnProperty("radius") ?
|
||||
Math.min(cancelAttachmentButton.background.radius, height/2) : 0
|
||||
corners.bottomLeftRadius: toolBar.mirrored ? cornerRadius : 0
|
||||
corners.bottomRightRadius: toolBar.mirrored ? 0 : cornerRadius
|
||||
color: Kirigami.Theme.backgroundColor
|
||||
opacity: 0.75
|
||||
}
|
||||
}
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
Button {
|
||||
id: editImageButton
|
||||
visible: hasImage
|
||||
icon.name: "document-edit"
|
||||
text: i18n("Edit")
|
||||
display: AbstractButton.IconOnly
|
||||
|
||||
Component {
|
||||
id: imageEditorPage
|
||||
ImageEditorPage {
|
||||
imagePath: chatBoxHelper.attachmentPath
|
||||
}
|
||||
}
|
||||
onClicked: {
|
||||
let imageEditor = applicationWindow().pageStack.layers.push(imageEditorPage);
|
||||
imageEditor.newPathChanged.connect(function(newPath) {
|
||||
applicationWindow().pageStack.layers.pop();
|
||||
chatBoxHelper.attachmentPath = newPath;
|
||||
});
|
||||
}
|
||||
ToolTip.text: text
|
||||
ToolTip.visible: hovered
|
||||
}
|
||||
Button {
|
||||
id: cancelAttachmentButton
|
||||
icon.name: "dialog-cancel"
|
||||
text: i18n("Cancel")
|
||||
display: AbstractButton.IconOnly
|
||||
onClicked: chatBoxHelper.clearAttachment();
|
||||
ToolTip.text: text
|
||||
ToolTip.visible: hovered
|
||||
}
|
||||
}
|
||||
background: null
|
||||
}
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
color: Kirigami.Theme.backgroundColor
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,422 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.de>
|
||||
// SPDX-FileCopyrightText: 2020 Noah Davis <noahadvs@gmail.com>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Templates 2.15 as T
|
||||
import Qt.labs.platform 1.1 as Platform
|
||||
import QtQuick.Window 2.15
|
||||
|
||||
import org.kde.kirigami 2.18 as Kirigami
|
||||
import org.kde.neochat 1.0
|
||||
|
||||
ToolBar {
|
||||
id: chatBar
|
||||
property string replyEventId: ""
|
||||
property string editEventId: ""
|
||||
property alias inputFieldText: inputField.text
|
||||
property alias textField: inputField
|
||||
property alias emojiPaneOpened: emojiButton.checked
|
||||
|
||||
// store each user we autoComplete here, this will be helpful later to generate
|
||||
// the matrix.to links.
|
||||
// This use an hack to define: https://doc.qt.io/qt-5/qml-var.html#property-value-initialization-semantics
|
||||
property var userAutocompleted: ({})
|
||||
|
||||
signal closeAllTriggered()
|
||||
signal inputFieldForceActiveFocusTriggered()
|
||||
signal messageSent()
|
||||
signal pasteImageTriggered()
|
||||
signal editLastUserMessage()
|
||||
signal replyPreviousUserMessage()
|
||||
|
||||
property alias isCompleting: completionMenu.visible
|
||||
|
||||
onInputFieldForceActiveFocusTriggered: {
|
||||
inputField.forceActiveFocus();
|
||||
// set the cursor to the end of the text
|
||||
inputField.cursorPosition = inputField.length;
|
||||
}
|
||||
|
||||
position: ToolBar.Footer
|
||||
|
||||
Kirigami.Theme.colorSet: Kirigami.Theme.View
|
||||
|
||||
// Using a custom background because some styles like Material
|
||||
// or Fusion might have ugly colors for a TextArea placed inside
|
||||
// of a toolbar. ToolBar is otherwise the closest QQC2 component
|
||||
// to what we want because of the padding and spacing values.
|
||||
background: Rectangle {
|
||||
color: Kirigami.Theme.backgroundColor
|
||||
}
|
||||
|
||||
contentItem: RowLayout {
|
||||
spacing: chatBar.spacing
|
||||
|
||||
ScrollView {
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
Layout.minimumHeight: inputField.implicitHeight
|
||||
// lineSpacing is height+leading, so subtract leading once since leading only exists between lines.
|
||||
Layout.maximumHeight: fontMetrics.lineSpacing * 8 - fontMetrics.leading
|
||||
+ inputField.topPadding + inputField.bottomPadding
|
||||
|
||||
// HACK: Hide unnecessary horizontal scrollbar (https://bugreports.qt.io/browse/QTBUG-83890)
|
||||
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
|
||||
|
||||
FontMetrics {
|
||||
id: fontMetrics
|
||||
font: inputField.font
|
||||
}
|
||||
|
||||
TextArea {
|
||||
id: inputField
|
||||
focus: true
|
||||
/* Some QQC2 styles will have their own predefined backgrounds for TextAreas.
|
||||
* Make sure there is no background since we are using the ToolBar background.
|
||||
*
|
||||
* This could cause a problem if the QQC2 style was designed around TextArea
|
||||
* background colors being very different from the QPalette::Base color.
|
||||
* Luckily, none of the Qt QQC2 styles do that and neither do KDE's QQC2 styles.
|
||||
*/
|
||||
background: MouseArea {
|
||||
acceptedButtons: Qt.NoButton
|
||||
cursorShape: Qt.IBeamCursor
|
||||
z: 1
|
||||
}
|
||||
|
||||
leftPadding: mirrored ? 0 : Kirigami.Units.largeSpacing
|
||||
rightPadding: !mirrored ? 0 : Kirigami.Units.largeSpacing
|
||||
topPadding: 0
|
||||
bottomPadding: 0
|
||||
|
||||
property real progress: 0
|
||||
property bool autoAppeared: false
|
||||
//property int lineHeight: contentHeight / lineCount
|
||||
|
||||
text: inputFieldText
|
||||
placeholderText: readOnly ? i18n("This room is encrypted. Sending encrypted messages is not yet supported.") : editEventId.length > 0 ? i18n("Edit Message") : currentRoom.usesEncryption ? i18n("Send an encrypted message…") : i18n("Send a message…")
|
||||
verticalAlignment: TextEdit.AlignVCenter
|
||||
horizontalAlignment: TextEdit.AlignLeft
|
||||
wrapMode: Text.Wrap
|
||||
readOnly: currentRoom.usesEncryption && !Controller.encryptionSupported
|
||||
|
||||
Kirigami.Theme.colorSet: Kirigami.Theme.View
|
||||
Kirigami.Theme.inherit: false
|
||||
Kirigami.SpellChecking.enabled: true
|
||||
|
||||
color: Kirigami.Theme.textColor
|
||||
selectionColor: Kirigami.Theme.highlightColor
|
||||
selectedTextColor: Kirigami.Theme.highlightedTextColor
|
||||
hoverEnabled: !Kirigami.Settings.tabletMode
|
||||
|
||||
selectByMouse: !Kirigami.Settings.tabletMode
|
||||
|
||||
ChatDocumentHandler {
|
||||
id: documentHandler
|
||||
document: inputField.textDocument
|
||||
cursorPosition: inputField.cursorPosition
|
||||
selectionStart: inputField.selectionStart
|
||||
selectionEnd: inputField.selectionEnd
|
||||
room: currentRoom ?? null
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: repeatTimer
|
||||
interval: 5000
|
||||
}
|
||||
|
||||
function sendMessage(event) {
|
||||
if (isCompleting && completionMenu.count > 0) {
|
||||
chatBar.complete();
|
||||
} else if (event.modifiers & Qt.ShiftModifier) {
|
||||
inputField.insert(cursorPosition, "\n")
|
||||
} else {
|
||||
currentRoom.sendTypingNotification(false)
|
||||
chatBar.postMessage()
|
||||
}
|
||||
isCompleting = false;
|
||||
}
|
||||
|
||||
Keys.onReturnPressed: { sendMessage(event) }
|
||||
Keys.onEnterPressed: { sendMessage(event) }
|
||||
|
||||
Keys.onEscapePressed: {
|
||||
closeAllTriggered()
|
||||
}
|
||||
|
||||
Keys.onPressed: {
|
||||
if (event.key === Qt.Key_V && event.modifiers & Qt.ControlModifier) {
|
||||
chatBar.pasteImage();
|
||||
} else if (event.key === Qt.Key_Up && event.modifiers & Qt.ControlModifier) {
|
||||
replyPreviousUserMessage();
|
||||
} else if (event.key === Qt.Key_Up && inputField.text.length === 0) {
|
||||
editLastUserMessage();
|
||||
}
|
||||
}
|
||||
|
||||
Keys.onBacktabPressed: {
|
||||
if (event.modifiers & Qt.ControlModifier) {
|
||||
switchRoomUp();
|
||||
return;
|
||||
}
|
||||
if (!isCompleting) {
|
||||
nextItemInFocusChain(false).forceActiveFocus(Qt.TabFocusReason)
|
||||
return
|
||||
}
|
||||
if (!autoAppeared) {
|
||||
let decrementedIndex = completionMenu.currentIndex - 1
|
||||
// Wrap around to the last item
|
||||
if (decrementedIndex < 0) {
|
||||
decrementedIndex = Math.max(completionMenu.count - 1, 0) // 0 if count == 0
|
||||
}
|
||||
completionMenu.currentIndex = decrementedIndex
|
||||
} else {
|
||||
autoAppeared = false;
|
||||
}
|
||||
|
||||
chatBar.complete();
|
||||
}
|
||||
|
||||
// yes, decrement goes up and increment goes down visually.
|
||||
Keys.onUpPressed: (event) => {
|
||||
if (chatBar.isCompleting) {
|
||||
event.accepted = true
|
||||
completionMenu.listView.decrementCurrentIndex()
|
||||
autoAppeared = true;
|
||||
}
|
||||
event.accepted = false
|
||||
}
|
||||
|
||||
Keys.onDownPressed: (event) => {
|
||||
if (chatBar.isCompleting) {
|
||||
event.accepted = true
|
||||
completionMenu.listView.incrementCurrentIndex()
|
||||
autoAppeared = true;
|
||||
}
|
||||
event.accepted = false
|
||||
}
|
||||
|
||||
Keys.onTabPressed: {
|
||||
if (event.modifiers & Qt.ControlModifier) {
|
||||
switchRoomDown();
|
||||
return;
|
||||
}
|
||||
if (!isCompleting) {
|
||||
nextItemInFocusChain().forceActiveFocus(Qt.TabFocusReason);
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO detect moved cursor
|
||||
|
||||
// ignore first time tab was clicked so that user can select
|
||||
// first emoji/user
|
||||
if (!autoAppeared) {
|
||||
let incrementedIndex = completionMenu.currentIndex + 1;
|
||||
// Wrap around to the first item
|
||||
if (incrementedIndex > completionMenu.count - 1) {
|
||||
incrementedIndex = 0
|
||||
}
|
||||
completionMenu.currentIndex = incrementedIndex;
|
||||
} else {
|
||||
autoAppeared = false;
|
||||
}
|
||||
|
||||
chatBar.complete();
|
||||
}
|
||||
|
||||
onTextChanged: {
|
||||
if (!repeatTimer.running && Config.typingNotifications) {
|
||||
currentRoom.sendTypingNotification(true)
|
||||
}
|
||||
repeatTimer.start()
|
||||
|
||||
currentRoom.cachedInput = text
|
||||
autoAppeared = false;
|
||||
|
||||
const completionInfo = documentHandler.getAutocompletionInfo(isCompleting);
|
||||
|
||||
if (completionInfo.type === ChatDocumentHandler.Ignore) {
|
||||
if (completionInfo.keyword) {
|
||||
// custom emojis
|
||||
const idx = completionMenu.currentIndex;
|
||||
completionMenu.model = Array.from(chatBar.customEmojiModel.filterModel(completionInfo.keyword)).concat(EmojiModel.filterModel(completionInfo.keyword))
|
||||
completionMenu.currentIndex = idx;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (completionInfo.type === ChatDocumentHandler.None) {
|
||||
isCompleting = false;
|
||||
return;
|
||||
}
|
||||
|
||||
completionMenu.completionType = completionInfo.type
|
||||
if (completionInfo.type === ChatDocumentHandler.User) {
|
||||
completionMenu.model = currentRoom.getUsers(completionInfo.keyword, 10);
|
||||
} else if (completionInfo.type === ChatDocumentHandler.Command) {
|
||||
completionMenu.model = CommandModel.filterModel(completionInfo.keyword);
|
||||
} else {
|
||||
completionMenu.model = Array.from(chatBar.customEmojiModel.filterModel(completionInfo.keyword)).concat(EmojiModel.filterModel(completionInfo.keyword))
|
||||
}
|
||||
|
||||
if (completionMenu.model.length === 0) {
|
||||
isCompleting = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isCompleting) {
|
||||
isCompleting = true
|
||||
autoAppeared = true;
|
||||
completionMenu.endPosition = cursorPosition
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
visible: !chatBoxHelper.isReplying && (!chatBoxHelper.hasAttachment || uploadingBusySpinner.running)
|
||||
implicitWidth: uploadButton.implicitWidth
|
||||
implicitHeight: uploadButton.implicitHeight
|
||||
ToolButton {
|
||||
id: uploadButton
|
||||
anchors.fill: parent
|
||||
// Matrix does not allow sending attachments in replies
|
||||
visible: !chatBoxHelper.isReplying && !chatBoxHelper.hasAttachment && !uploadingBusySpinner.running
|
||||
icon.name: "mail-attachment"
|
||||
text: i18n("Attach an image or file")
|
||||
display: AbstractButton.IconOnly
|
||||
|
||||
onClicked: {
|
||||
if (Clipboard.hasImage) {
|
||||
attachDialog.open()
|
||||
} else {
|
||||
var fileDialog = openFileDialog.createObject(ApplicationWindow.overlay)
|
||||
fileDialog.chosen.connect((path) => {
|
||||
if (!path) { return }
|
||||
chatBoxHelper.attachmentPath = path;
|
||||
})
|
||||
fileDialog.open()
|
||||
}
|
||||
}
|
||||
|
||||
ToolTip.text: text
|
||||
ToolTip.visible: hovered
|
||||
}
|
||||
BusyIndicator {
|
||||
id: uploadingBusySpinner
|
||||
anchors.fill: parent
|
||||
visible: running
|
||||
running: currentRoom && currentRoom.hasFileUploading
|
||||
}
|
||||
}
|
||||
|
||||
ToolButton {
|
||||
id: emojiButton
|
||||
icon.name: "preferences-desktop-emoticons"
|
||||
text: i18n("Add an Emoji")
|
||||
display: AbstractButton.IconOnly
|
||||
checkable: true
|
||||
|
||||
ToolTip.text: text
|
||||
ToolTip.visible: hovered
|
||||
}
|
||||
|
||||
ToolButton {
|
||||
id: sendButton
|
||||
icon.name: "document-send"
|
||||
text: i18n("Send message")
|
||||
display: AbstractButton.IconOnly
|
||||
|
||||
onClicked: {
|
||||
chatBar.postMessage()
|
||||
}
|
||||
|
||||
ToolTip.text: text
|
||||
ToolTip.visible: hovered
|
||||
}
|
||||
}
|
||||
|
||||
Action {
|
||||
id: pasteAction
|
||||
shortcut: StandardKey.Paste
|
||||
onTriggered: {
|
||||
if (Clipboard.hasImage) {
|
||||
pasteImageTriggered();
|
||||
}
|
||||
activeFocusItem.paste();
|
||||
}
|
||||
}
|
||||
|
||||
CompletionMenu {
|
||||
id: completionMenu
|
||||
width: parent.width
|
||||
//height: 80 //Math.min(implicitHeight, delegate.implicitHeight * 6)
|
||||
height: implicitHeight
|
||||
y: -height - 1
|
||||
z: 1
|
||||
Behavior on height {
|
||||
NumberAnimation {
|
||||
property: "height"
|
||||
duration: Kirigami.Units.shortDuration
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
onCompleteTriggered: {
|
||||
complete()
|
||||
isCompleting = false;
|
||||
}
|
||||
}
|
||||
|
||||
property CustomEmojiModel customEmojiModel: CustomEmojiModel {
|
||||
connection: Controller.activeConnection
|
||||
}
|
||||
|
||||
function pasteImage() {
|
||||
let localPath = Platform.StandardPaths.writableLocation(Platform.StandardPaths.CacheLocation) + "/screenshots/" + (new Date()).getTime() + ".png";
|
||||
if (!Clipboard.saveImage(localPath)) {
|
||||
return;
|
||||
}
|
||||
chatBoxHelper.attachmentPath = localPath;
|
||||
}
|
||||
|
||||
function postMessage() {
|
||||
checkForFancyEffectsReason();
|
||||
|
||||
if (chatBoxHelper.hasAttachment) {
|
||||
// send attachment but don't reset the text
|
||||
actionsHandler.postMessage("", chatBoxHelper.attachmentPath,
|
||||
chatBoxHelper.replyEventId, chatBoxHelper.editEventId, {}, this.customEmojiModel);
|
||||
currentRoom.markAllMessagesAsRead();
|
||||
messageSent();
|
||||
return;
|
||||
}
|
||||
|
||||
const re = /^s\/([^\/]*)\/([^\/]*)/;
|
||||
if (Config.allowQuickEdit && re.test(inputField.text)) {
|
||||
// send edited messages
|
||||
actionsHandler.postEdit(inputField.text);
|
||||
} else {
|
||||
// send normal message
|
||||
actionsHandler.postMessage(inputField.text.trim(), chatBoxHelper.attachmentPath,
|
||||
chatBoxHelper.replyEventId, chatBoxHelper.editEventId, userAutocompleted, this.customEmojiModel);
|
||||
}
|
||||
currentRoom.markAllMessagesAsRead();
|
||||
inputField.clear();
|
||||
inputField.text = Qt.binding(function() {
|
||||
return currentRoom ? currentRoom.cachedInput : "";
|
||||
});
|
||||
messageSent()
|
||||
}
|
||||
|
||||
function complete() {
|
||||
documentHandler.replaceAutoComplete(completionMenu.currentDisplayText);
|
||||
if (completionMenu.completionType === ChatDocumentHandler.User
|
||||
&& completionMenu.currentDisplayText.length > 0
|
||||
&& completionMenu.currentItem.userId.length > 0) {
|
||||
userAutocompleted[completionMenu.currentDisplayText] = completionMenu.currentItem.userId;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,283 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.de>
|
||||
// SPDX-FileCopyrightText: 2020 Noah Davis <noahadvs@gmail.com>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15 as QQC2
|
||||
import Qt.labs.platform 1.1 as Platform
|
||||
import org.kde.kirigami 2.15 as Kirigami
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
import NeoChat.Component.ChatBox 1.0
|
||||
import NeoChat.Component.Emoji 1.0
|
||||
|
||||
Item {
|
||||
id: root
|
||||
property alias inputFieldText: chatBar.inputFieldText
|
||||
|
||||
signal fancyEffectsReasonFound(string fancyEffect)
|
||||
signal messageSent()
|
||||
signal editLastUserMessage()
|
||||
signal replyPreviousUserMessage()
|
||||
|
||||
Kirigami.Theme.colorSet: Kirigami.Theme.View
|
||||
|
||||
implicitWidth: {
|
||||
let w = 0
|
||||
for(let i = 0; i < visibleChildren.length; ++i) {
|
||||
w = Math.max(w, Math.ceil(visibleChildren[i].implicitWidth))
|
||||
}
|
||||
return w
|
||||
}
|
||||
implicitHeight: {
|
||||
let h = 0
|
||||
for(let i = 0; i < visibleChildren.length; ++i) {
|
||||
h += Math.ceil(visibleChildren[i].implicitHeight)
|
||||
}
|
||||
return h
|
||||
}
|
||||
|
||||
// For some reason, this is needed to make the height animation work even though
|
||||
// it used to work and height should be directly affected by implicitHeight
|
||||
height: implicitHeight
|
||||
|
||||
Behavior on height {
|
||||
NumberAnimation {
|
||||
property: "height"
|
||||
duration: Kirigami.Units.shortDuration
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
|
||||
Kirigami.Separator {
|
||||
id: connectionPaneSeparator
|
||||
visible: connectionPane.visible
|
||||
width: parent.width
|
||||
height: visible ? implicitHeight : 0
|
||||
anchors.bottom: connectionPane.top
|
||||
z: 1
|
||||
}
|
||||
|
||||
QQC2.Pane {
|
||||
id: connectionPane
|
||||
padding: fontMetrics.lineSpacing * 0.25
|
||||
FontMetrics {
|
||||
id: fontMetrics
|
||||
font: networkLabel.font
|
||||
}
|
||||
spacing: 0
|
||||
Kirigami.Theme.colorSet: Kirigami.Theme.View
|
||||
background: Rectangle {
|
||||
color: Kirigami.Theme.backgroundColor
|
||||
}
|
||||
visible: !Controller.isOnline
|
||||
width: parent.width
|
||||
QQC2.Label {
|
||||
id: networkLabel
|
||||
text: i18n("NeoChat is offline. Please check your network connection.")
|
||||
}
|
||||
anchors.bottom: emojiPickerLoaderSeparator.top
|
||||
}
|
||||
|
||||
Kirigami.Separator {
|
||||
id: emojiPickerLoaderSeparator
|
||||
visible: emojiPickerLoader.visible
|
||||
width: parent.width
|
||||
height: visible ? implicitHeight : 0
|
||||
anchors.bottom: emojiPickerLoader.top
|
||||
z: 1
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: emojiPickerLoader
|
||||
active: visible
|
||||
visible: chatBar.emojiPaneOpened
|
||||
width: parent.width
|
||||
height: visible ? implicitHeight : 0
|
||||
anchors.bottom: replySeparator.top
|
||||
sourceComponent: QQC2.Pane {
|
||||
topPadding: 0
|
||||
bottomPadding: 0
|
||||
rightPadding: 0
|
||||
leftPadding: 0
|
||||
Kirigami.Theme.colorSet: Kirigami.Theme.View
|
||||
contentItem: EmojiPicker {
|
||||
textArea: chatBar.textField
|
||||
onChosen: addText(emoji)
|
||||
}
|
||||
}
|
||||
Behavior on height {
|
||||
NumberAnimation {
|
||||
property: "height"
|
||||
duration: Kirigami.Units.shortDuration
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Kirigami.Separator {
|
||||
id: replySeparator
|
||||
visible: replyPane.visible
|
||||
width: parent.width
|
||||
height: visible ? implicitHeight : 0
|
||||
anchors.bottom: replyPane.top
|
||||
}
|
||||
|
||||
|
||||
ReplyPane {
|
||||
id: replyPane
|
||||
visible: chatBoxHelper.isReplying || chatBoxHelper.isEditing
|
||||
user: chatBoxHelper.replyUser
|
||||
width: parent.width
|
||||
height: visible ? implicitHeight : 0
|
||||
anchors.bottom: attachmentSeparator.top
|
||||
Behavior on height {
|
||||
NumberAnimation {
|
||||
property: "height"
|
||||
duration: Kirigami.Units.shortDuration
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
onReplyCancelled: {
|
||||
root.focusInputField()
|
||||
}
|
||||
}
|
||||
|
||||
Kirigami.Separator {
|
||||
id: attachmentSeparator
|
||||
visible: attachmentPane.visible
|
||||
width: parent.width
|
||||
height: visible ? implicitHeight : 0
|
||||
anchors.bottom: attachmentPane.top
|
||||
}
|
||||
|
||||
AttachmentPane {
|
||||
id: attachmentPane
|
||||
visible: chatBoxHelper.hasAttachment
|
||||
width: parent.width
|
||||
height: visible ? implicitHeight : 0
|
||||
anchors.bottom: chatBarSeparator.top
|
||||
Behavior on height {
|
||||
NumberAnimation {
|
||||
property: "height"
|
||||
duration: Kirigami.Units.shortDuration
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Kirigami.Separator {
|
||||
id: chatBarSeparator
|
||||
visible: chatBar.visible
|
||||
width: parent.width
|
||||
height: visible ? implicitHeight : 0
|
||||
anchors.bottom: chatBar.top
|
||||
}
|
||||
|
||||
ChatBar {
|
||||
id: chatBar
|
||||
visible: currentRoom.canSendEvent("m.room.message")
|
||||
width: parent.width
|
||||
height: visible ? implicitHeight : 0
|
||||
anchors.bottom: parent.bottom
|
||||
|
||||
Behavior on height {
|
||||
NumberAnimation {
|
||||
property: "height"
|
||||
duration: Kirigami.Units.shortDuration
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
|
||||
onCloseAllTriggered: closeAll()
|
||||
onMessageSent: {
|
||||
closeAll()
|
||||
checkForFancyEffectsReason()
|
||||
root.messageSent();
|
||||
}
|
||||
onEditLastUserMessage: {
|
||||
root.editLastUserMessage();
|
||||
}
|
||||
onReplyPreviousUserMessage: {
|
||||
root.replyPreviousUserMessage();
|
||||
}
|
||||
}
|
||||
|
||||
function checkForFancyEffectsReason() {
|
||||
if (!Config.showFancyEffects) {
|
||||
return
|
||||
}
|
||||
|
||||
let text = root.inputFieldText.trim()
|
||||
if (text.includes('\u{2744}')) {
|
||||
root.fancyEffectsReasonFound("snowflake")
|
||||
}
|
||||
if (text.includes('\u{1F386}')) {
|
||||
root.fancyEffectsReasonFound("fireworks")
|
||||
}
|
||||
if (text.includes('\u{1F387}')) {
|
||||
root.fancyEffectsReasonFound("fireworks")
|
||||
}
|
||||
if (text.includes('\u{1F389}')) {
|
||||
root.fancyEffectsReasonFound("confetti")
|
||||
}
|
||||
if (text.includes('\u{1F38A}')) {
|
||||
root.fancyEffectsReasonFound("confetti")
|
||||
}
|
||||
}
|
||||
|
||||
function addText(text) {
|
||||
root.inputFieldText = inputFieldText + text
|
||||
}
|
||||
|
||||
function insertText(str) {
|
||||
root.inputFieldText = inputFieldText.substr(0, inputField.cursorPosition) + str + inputFieldText.substr(inputField.cursorPosition)
|
||||
}
|
||||
|
||||
function focusInputField() {
|
||||
chatBar.inputFieldForceActiveFocusTriggered()
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: RoomManager
|
||||
|
||||
function onCurrentRoomChanged() {
|
||||
chatBar.userAutocompleted = {};
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: chatBoxHelper
|
||||
|
||||
function onShouldClearText() {
|
||||
root.inputFieldText = "";
|
||||
}
|
||||
|
||||
function onEditing(editContent, editFormatedContent) {
|
||||
// Set the input field in edit mode
|
||||
root.inputFieldText = editContent;
|
||||
|
||||
// clean autocompletion list
|
||||
chatBar.userAutocompleted = {};
|
||||
|
||||
// Fill autocompletion list with values extracted from message.
|
||||
// We can't just iterate on every user in the list and try to
|
||||
// find matching display name since some users have display name
|
||||
// matching frequent words and this will marks too many words as
|
||||
// mentions.
|
||||
const regex = /<a href=\"https:\/\/matrix.to\/#\/(@[a-zA-Z09]*:[a-zA-Z09.]*)\">([^<]*)<\/a>/g;
|
||||
|
||||
let match;
|
||||
while ((match = regex.exec(editFormatedContent.toString())) !== null) {
|
||||
chatBar.userAutocompleted[match[2]] = match[1];
|
||||
}
|
||||
chatBox.forceActiveFocus();
|
||||
}
|
||||
}
|
||||
|
||||
function closeAll() {
|
||||
chatBoxHelper.clear();
|
||||
chatBar.emojiPaneOpened = false;
|
||||
}
|
||||
}
|
||||
@@ -1,163 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.de>
|
||||
// SPDX-FileCopyrightText: 2020 Noah Davis <noahadvs@gmail.com>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
import QtQuick.Controls 2.15
|
||||
import Qt.labs.qmlmodels 1.0
|
||||
|
||||
import org.kde.kirigami 2.15 as Kirigami
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
import NeoChat.Component 1.0
|
||||
|
||||
Popup {
|
||||
id: control
|
||||
|
||||
// Expose internal ListView properties.
|
||||
property alias model: completionListView.model
|
||||
property alias listView: completionListView
|
||||
property alias currentIndex: completionListView.currentIndex
|
||||
property alias currentItem: completionListView.currentItem
|
||||
property alias count: completionListView.count
|
||||
property alias delegate: completionListView.delegate
|
||||
|
||||
// Autocomplee text
|
||||
property string currentDisplayText: currentItem && (currentItem.displayName ?? "")
|
||||
|
||||
property int completionType: ChatDocumentHandler.Emoji
|
||||
property int beginPosition: 0
|
||||
property int endPosition: 0
|
||||
|
||||
signal completeTriggered()
|
||||
|
||||
Kirigami.Theme.colorSet: Kirigami.Theme.View
|
||||
|
||||
bottomPadding: 0
|
||||
leftPadding: 0
|
||||
rightPadding: 0
|
||||
topPadding: 0
|
||||
clip: true
|
||||
|
||||
onVisibleChanged: if (!visible) {
|
||||
completionListView.currentIndex = 0;
|
||||
}
|
||||
|
||||
implicitHeight: Math.min(completionListView.contentHeight, Kirigami.Units.gridUnit * 10)
|
||||
|
||||
contentItem: ScrollView {
|
||||
// HACK: Hide unnecessary horizontal scrollbar (https://bugreports.qt.io/browse/QTBUG-83890)
|
||||
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
|
||||
ListView {
|
||||
id: completionListView
|
||||
implicitWidth: contentWidth
|
||||
delegate: {
|
||||
if (completionType === ChatDocumentHandler.Emoji) {
|
||||
emojiDelegate
|
||||
} else if (completionType === ChatDocumentHandler.Command) {
|
||||
commandDelegate
|
||||
} else if (completionType === ChatDocumentHandler.User) {
|
||||
usernameDelegate
|
||||
}
|
||||
}
|
||||
|
||||
keyNavigationWraps: true
|
||||
|
||||
//interactive: Window.window ? contentHeight + control.topPadding + control.bottomPadding > Window.window.height : false
|
||||
clip: true
|
||||
currentIndex: control.currentIndex || 0
|
||||
}
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
color: Kirigami.Theme.backgroundColor
|
||||
}
|
||||
|
||||
Component {
|
||||
id: usernameDelegate
|
||||
Kirigami.BasicListItem {
|
||||
id: usernameItem
|
||||
width: ListView.view.width ?? implicitWidth
|
||||
property string displayName: modelData.displayName
|
||||
property string userId: modelData.id
|
||||
leading: Kirigami.Avatar {
|
||||
implicitHeight: Kirigami.Units.gridUnit
|
||||
implicitWidth: implicitHeight
|
||||
source: modelData.avatarMediaId ? ("image://mxc/" + modelData.avatarMediaId) : ""
|
||||
color: modelData.color ? Qt.darker(modelData.color, 1.1) : null
|
||||
}
|
||||
labelItem.textFormat: Text.PlainText
|
||||
text: modelData.displayName
|
||||
onClicked: completeTriggered();
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: emojiDelegate
|
||||
Kirigami.BasicListItem {
|
||||
id: emojiItem
|
||||
width: ListView.view.width ?? implicitWidth
|
||||
property string displayName: modelData.isCustom ? modelData.shortname : modelData.unicode
|
||||
text: modelData.shortname
|
||||
height: Kirigami.Units.gridUnit * 2
|
||||
|
||||
leading: Image {
|
||||
source: modelData.isCustom ? modelData.unicode : ""
|
||||
|
||||
width: height
|
||||
sourceSize.width: width
|
||||
sourceSize.height: height
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
visible: parent.status === Image.Loading
|
||||
radius: height/2
|
||||
gradient: ShimmerGradient { }
|
||||
}
|
||||
|
||||
Label {
|
||||
id: unicodeLabel
|
||||
|
||||
visible: !modelData.isCustom
|
||||
|
||||
font.family: 'emoji'
|
||||
font.pixelSize: height - 2
|
||||
|
||||
text: modelData.unicode
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
|
||||
anchors.fill: parent
|
||||
}
|
||||
}
|
||||
|
||||
onClicked: completeTriggered();
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: commandDelegate
|
||||
Kirigami.BasicListItem {
|
||||
id: commandItem
|
||||
width: ListView.view.width ?? implicitWidth
|
||||
text: "<i>" + modelData.parameter.replace("<", "<").replace(">", ">") + "</i> " + modelData.help
|
||||
property string displayName: modelData.command
|
||||
|
||||
leading: Label {
|
||||
id: commandLabel
|
||||
Layout.preferredHeight: Kirigami.Units.gridUnit
|
||||
Layout.preferredWidth: textMetrics.tightBoundingRect.width
|
||||
text: modelData.command
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
TextMetrics {
|
||||
id: textMetrics
|
||||
text: modelData.command
|
||||
font: commandLabel.font
|
||||
}
|
||||
onClicked: completeTriggered();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,121 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.de>
|
||||
// SPDX-FileCopyrightText: 2020 Noah Davis <noahadvs@gmail.com>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
import QtQuick.Controls 2.15
|
||||
|
||||
import org.kde.kirigami 2.14 as Kirigami
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
|
||||
Loader {
|
||||
id: root
|
||||
readonly property bool isEdit: chatBoxHelper.isEditing
|
||||
property var user: null
|
||||
property string avatarMediaUrl: user ? "image://mxc/" + user.avatarMediaId : ""
|
||||
|
||||
signal replyCancelled()
|
||||
|
||||
active: visible
|
||||
sourceComponent: Pane {
|
||||
id: replyPane
|
||||
|
||||
Kirigami.Theme.colorSet: Kirigami.Theme.View
|
||||
|
||||
spacing: leftPadding
|
||||
|
||||
contentItem: RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: replyPane.spacing
|
||||
|
||||
FontMetrics {
|
||||
id: fontMetrics
|
||||
font: textArea.font
|
||||
}
|
||||
|
||||
Kirigami.Avatar {
|
||||
id: avatar
|
||||
Layout.alignment: textContentLayout.height > avatar.height ? Qt.AlignHCenter | Qt.AlignTop : Qt.AlignCenter
|
||||
Layout.preferredWidth: Layout.preferredHeight
|
||||
Layout.preferredHeight: fontMetrics.lineSpacing * 2 - fontMetrics.leading
|
||||
source: root.avatarMediaUrl
|
||||
name: user ? user.displayName : ""
|
||||
color: user ? user.color : "transparent"
|
||||
visible: Boolean(user)
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: textContentLayout
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
Layout.fillWidth: true
|
||||
spacing: fontMetrics.leading
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
textFormat: Text.StyledText
|
||||
elide: Text.ElideRight
|
||||
text: {
|
||||
let heading = "<b>%1</b>"
|
||||
let userName = user ? "<font color=\""+ user.color +"\">" + currentRoom.htmlSafeMemberName(user.id) + "</font>" : ""
|
||||
if (isEdit) {
|
||||
heading = heading.arg(i18n("Editing message:")) + "<br/>"
|
||||
} else {
|
||||
heading = heading.arg(i18n("Replying to %1:", userName))
|
||||
}
|
||||
|
||||
return heading
|
||||
}
|
||||
}
|
||||
ScrollView {
|
||||
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
|
||||
Layout.fillWidth: true
|
||||
Layout.maximumHeight: fontMetrics.lineSpacing * 8 - fontMetrics.leading
|
||||
|
||||
// HACK: Hide unnecessary horizontal scrollbar (https://bugreports.qt.io/browse/QTBUG-83890)
|
||||
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
|
||||
|
||||
TextArea {
|
||||
id: textArea
|
||||
leftPadding: 0
|
||||
rightPadding: 0
|
||||
topPadding: 0
|
||||
bottomPadding: 0
|
||||
text: {
|
||||
const stylesheet = "<style> a{color:"+Kirigami.Theme.linkColor+";}.user-pill{}</style>";
|
||||
const content = chatBoxHelper.isReplying ? chatBoxHelper.replyEventContent : chatBoxHelper.editContent;
|
||||
return stylesheet + content;
|
||||
}
|
||||
selectByMouse: true
|
||||
selectByKeyboard: true
|
||||
readOnly: true
|
||||
wrapMode: Label.Wrap
|
||||
textFormat: TextEdit.RichText
|
||||
background: Item {}
|
||||
HoverHandler {
|
||||
cursorShape: textArea.hoveredLink ? Qt.PointingHandCursor : Qt.IBeamCursor
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
id: cancelReplyButton
|
||||
Layout.alignment: avatar.Layout.alignment
|
||||
icon.name: "dialog-cancel"
|
||||
text: i18n("Cancel")
|
||||
display: AbstractButton.IconOnly
|
||||
onClicked: {
|
||||
chatBoxHelper.clear();
|
||||
root.replyCancelled();
|
||||
}
|
||||
ToolTip.text: text
|
||||
ToolTip.visible: hovered
|
||||
}
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
color: Kirigami.Theme.backgroundColor
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
module NeoChat.Component.ChatBox
|
||||
ChatBox 1.0 ChatBox.qml
|
||||
ChatBar 1.0 ChatBar.qml
|
||||
ReplyPane 1.0 ReplyPane.qml
|
||||
AttachmentPane 1.0 AttachmentPane.qml
|
||||
CompletionMenu 1.0 CompletionMenu.qml
|
||||
EmojiPickerPane 1.0 EmojiPickerPane.qml
|
||||
@@ -1,169 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2018-2019 Black Hat <bhat@encom.eu.org>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
import org.kde.kirigami 2.15 as Kirigami
|
||||
|
||||
import org.kde.neochat 1.0 as NeoChat
|
||||
import NeoChat.Component 1.0
|
||||
|
||||
ColumnLayout {
|
||||
id: _picker
|
||||
|
||||
property string emojiCategory: "history"
|
||||
property var textArea
|
||||
readonly property var emojiModel: NeoChat.EmojiModel
|
||||
|
||||
property NeoChat.CustomEmojiModel customModel: NeoChat.CustomEmojiModel {
|
||||
connection: NeoChat.Controller.activeConnection
|
||||
}
|
||||
|
||||
signal chosen(string emoji)
|
||||
|
||||
spacing: 0
|
||||
|
||||
ListView {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: Kirigami.Units.gridUnit * 2 + 2 // for the focus line
|
||||
|
||||
boundsBehavior: Flickable.DragOverBounds
|
||||
|
||||
clip: true
|
||||
|
||||
orientation: ListView.Horizontal
|
||||
|
||||
model: ListModel {
|
||||
ListElement { label: "custom"; category: "custom" }
|
||||
ListElement { label: "⌛️"; category: "history" }
|
||||
ListElement { label: "😏"; category: "people" }
|
||||
ListElement { label: "🌲"; category: "nature" }
|
||||
ListElement { label: "🍛"; category: "food"}
|
||||
ListElement { label: "🚁"; category: "activity" }
|
||||
ListElement { label: "🚅"; category: "travel" }
|
||||
ListElement { label: "💡"; category: "objects" }
|
||||
ListElement { label: "🔣"; category: "symbols" }
|
||||
ListElement { label: "🏁"; category: "flags" }
|
||||
}
|
||||
|
||||
delegate: ItemDelegate {
|
||||
id: del
|
||||
|
||||
required property string label
|
||||
required property string category
|
||||
|
||||
width: contentItem.Layout.preferredWidth
|
||||
height: Kirigami.Units.gridUnit * 2
|
||||
|
||||
contentItem: Kirigami.Heading {
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
level: del.label === "custom" ? 4 : 1
|
||||
|
||||
Layout.preferredWidth: del.label === "custom" ? implicitWidth + Kirigami.Units.largeSpacing : Kirigami.Units.gridUnit * 2
|
||||
|
||||
font.family: del.label === "custom" ? Kirigami.Theme.defaultFont.family : 'emoji'
|
||||
text: del.label === "custom" ? i18n("Custom") : del.label
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.bottom: parent.bottom
|
||||
|
||||
width: parent.width
|
||||
height: 2
|
||||
|
||||
visible: emojiCategory === category
|
||||
|
||||
color: Kirigami.Theme.focusColor
|
||||
}
|
||||
|
||||
onClicked: emojiCategory = category
|
||||
}
|
||||
}
|
||||
|
||||
Kirigami.Separator {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 1
|
||||
}
|
||||
|
||||
GridView {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: Kirigami.Units.gridUnit * 8
|
||||
Layout.fillHeight: true
|
||||
|
||||
cellWidth: Kirigami.Units.gridUnit * 2
|
||||
cellHeight: Kirigami.Units.gridUnit * 2
|
||||
|
||||
boundsBehavior: Flickable.DragOverBounds
|
||||
|
||||
clip: true
|
||||
|
||||
model: {
|
||||
switch (emojiCategory) {
|
||||
case "custom":
|
||||
return _picker.customModel
|
||||
case "history":
|
||||
return emojiModel.history
|
||||
case "people":
|
||||
return emojiModel.people
|
||||
case "nature":
|
||||
return emojiModel.nature
|
||||
case "food":
|
||||
return emojiModel.food
|
||||
case "activity":
|
||||
return emojiModel.activity
|
||||
case "travel":
|
||||
return emojiModel.travel
|
||||
case "objects":
|
||||
return emojiModel.objects
|
||||
case "symbols":
|
||||
return emojiModel.symbols
|
||||
case "flags":
|
||||
return emojiModel.flags
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
delegate: ItemDelegate {
|
||||
width: Kirigami.Units.gridUnit * 2
|
||||
height: Kirigami.Units.gridUnit * 2
|
||||
|
||||
contentItem: Kirigami.Heading {
|
||||
level: 1
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
font.family: 'emoji'
|
||||
text: modelData.isCustom ? "" : modelData.unicode
|
||||
}
|
||||
|
||||
Image {
|
||||
visible: modelData.isCustom
|
||||
source: visible ? modelData.unicode : ""
|
||||
anchors.fill: parent
|
||||
anchors.margins: 2
|
||||
|
||||
sourceSize.width: width
|
||||
sourceSize.height: height
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
visible: parent.status === Image.Loading
|
||||
radius: height/2
|
||||
gradient: ShimmerGradient { }
|
||||
}
|
||||
}
|
||||
|
||||
onClicked: {
|
||||
if (modelData.isCustom) {
|
||||
chosen(modelData.shortname)
|
||||
} else {
|
||||
chosen(modelData.unicode)
|
||||
}
|
||||
emojiModel.emojiUsed(modelData)
|
||||
}
|
||||
}
|
||||
|
||||
ScrollBar.vertical: ScrollBar {}
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
module NeoChat.Component.Emoji
|
||||
EmojiPicker 1.0 EmojiPicker.qml
|
||||
@@ -1,308 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2019 Black Hat <bhat@encom.eu.org>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
import Qt.labs.platform 1.1
|
||||
|
||||
import org.kde.kirigami 2.15 as Kirigami
|
||||
|
||||
Popup {
|
||||
id: root
|
||||
|
||||
property alias source: image.source
|
||||
property string filename
|
||||
property string blurhash: ""
|
||||
property int imageWidth: -1
|
||||
property int imageHeight: -1
|
||||
property var modelData
|
||||
|
||||
parent: Overlay.overlay
|
||||
closePolicy: Popup.CloseOnEscape
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
modal: true
|
||||
padding: 0
|
||||
background: null
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
spacing: Kirigami.Units.largeSpacing
|
||||
|
||||
Control {
|
||||
Layout.fillWidth: true
|
||||
|
||||
contentItem: RowLayout {
|
||||
spacing: Kirigami.Units.largeSpacing
|
||||
|
||||
Kirigami.Avatar {
|
||||
id: avatar
|
||||
|
||||
Layout.preferredWidth: Kirigami.Units.iconSizes.medium
|
||||
Layout.preferredHeight: Kirigami.Units.iconSizes.medium
|
||||
|
||||
name: modelData.author.name ?? modelData.author.displayName
|
||||
source: modelData.author.avatarMediaId ? ("image://mxc/" + modelData.author.avatarMediaId) : ""
|
||||
color: modelData.author.color
|
||||
}
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 0
|
||||
|
||||
Label {
|
||||
id: nameLabel
|
||||
|
||||
text: modelData.author.displayName
|
||||
textFormat: Text.PlainText
|
||||
font.weight: Font.Bold
|
||||
color: author.color
|
||||
}
|
||||
Label {
|
||||
id: timeLabel
|
||||
|
||||
text: time.toLocaleString(Qt.locale(), Locale.ShortFormat)
|
||||
}
|
||||
}
|
||||
Label {
|
||||
id: imageLabel
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: Kirigami.Units.largeSpacing
|
||||
|
||||
text: modelData.display
|
||||
font.weight: Font.Bold
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
ToolButton {
|
||||
Layout.preferredWidth: Kirigami.Units.gridUnit * 2
|
||||
Layout.preferredHeight: Kirigami.Units.gridUnit * 2
|
||||
|
||||
text: i18n("Zoom in")
|
||||
Accessible.name: text
|
||||
icon.name: "zoom-in"
|
||||
display: AbstractButton.IconOnly
|
||||
onClicked: {
|
||||
image.scaleFactor = image.scaleFactor + 0.25
|
||||
if (image.scaleFactor > 3) {
|
||||
image.scaleFactor = 3
|
||||
}
|
||||
}
|
||||
|
||||
ToolTip.text: text
|
||||
ToolTip.delay: Kirigami.Units.toolTipDelay
|
||||
ToolTip.visible: hovered
|
||||
}
|
||||
ToolButton {
|
||||
Layout.preferredWidth: Kirigami.Units.gridUnit * 2
|
||||
Layout.preferredHeight: Kirigami.Units.gridUnit * 2
|
||||
|
||||
text: i18n("Zoom out")
|
||||
Accessible.name: text
|
||||
icon.name: "zoom-out"
|
||||
display: AbstractButton.IconOnly
|
||||
onClicked: {
|
||||
image.scaleFactor = image.scaleFactor - 0.25
|
||||
if (image.scaleFactor < 0.25) {
|
||||
image.scaleFactor = 0.25
|
||||
}
|
||||
}
|
||||
|
||||
ToolTip.text: text
|
||||
ToolTip.delay: Kirigami.Units.toolTipDelay
|
||||
ToolTip.visible: hovered
|
||||
}
|
||||
ToolButton {
|
||||
Layout.preferredWidth: Kirigami.Units.gridUnit * 2
|
||||
Layout.preferredHeight: Kirigami.Units.gridUnit * 2
|
||||
|
||||
text: i18n("Rotate left")
|
||||
Accessible.name: text
|
||||
icon.name: "image-rotate-left-symbolic"
|
||||
display: AbstractButton.IconOnly
|
||||
onClicked: image.rotationAngle = image.rotationAngle - 90
|
||||
|
||||
ToolTip.text: text
|
||||
ToolTip.delay: Kirigami.Units.toolTipDelay
|
||||
ToolTip.visible: hovered
|
||||
}
|
||||
ToolButton {
|
||||
Layout.preferredWidth: Kirigami.Units.gridUnit * 2
|
||||
Layout.preferredHeight: Kirigami.Units.gridUnit * 2
|
||||
|
||||
text: i18n("Rotate right")
|
||||
Accessible.name: text
|
||||
icon.name: "image-rotate-right-symbolic"
|
||||
display: AbstractButton.IconOnly
|
||||
onClicked: image.rotationAngle = image.rotationAngle + 90
|
||||
|
||||
ToolTip.text: text
|
||||
ToolTip.delay: Kirigami.Units.toolTipDelay
|
||||
ToolTip.visible: hovered
|
||||
}
|
||||
ToolButton {
|
||||
Layout.preferredWidth: Kirigami.Units.gridUnit * 2
|
||||
Layout.preferredHeight: Kirigami.Units.gridUnit * 2
|
||||
|
||||
text: i18n("Save as")
|
||||
Accessible.name: text
|
||||
icon.name: "document-save"
|
||||
display: AbstractButton.IconOnly
|
||||
onClicked: {
|
||||
var dialog = saveAsDialog.createObject(ApplicationWindow.overlay)
|
||||
dialog.open()
|
||||
dialog.currentFile = dialog.folder + "/" + currentRoom.fileNameToDownload(eventId)
|
||||
}
|
||||
|
||||
ToolTip.text: text
|
||||
ToolTip.delay: Kirigami.Units.toolTipDelay
|
||||
ToolTip.visible: hovered
|
||||
}
|
||||
ToolButton {
|
||||
Layout.preferredWidth: Kirigami.Units.gridUnit * 2
|
||||
Layout.preferredHeight: Kirigami.Units.gridUnit * 2
|
||||
|
||||
text: i18n("Close")
|
||||
Accessible.name: text
|
||||
icon.name: "dialog-close"
|
||||
display: AbstractButton.IconOnly
|
||||
onClicked: {
|
||||
root.close()
|
||||
}
|
||||
|
||||
ToolTip.text: text
|
||||
ToolTip.delay: Kirigami.Units.toolTipDelay
|
||||
ToolTip.visible: hovered
|
||||
}
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
color: Kirigami.Theme.alternateBackgroundColor
|
||||
}
|
||||
|
||||
Kirigami.Separator {
|
||||
anchors {
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
bottom: parent.bottom
|
||||
}
|
||||
height: 1
|
||||
}
|
||||
}
|
||||
|
||||
BusyIndicator {
|
||||
Layout.fillWidth: true
|
||||
visible: image.status !== Image.Ready && root.blurhash === ""
|
||||
running: visible
|
||||
}
|
||||
// Provides container to fill the space that isn't taken up by the top controls and clips the image when zooming makes it larger than the available area.
|
||||
Item {
|
||||
id: imageContainer
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
Layout.leftMargin: Kirigami.Units.largeSpacing
|
||||
Layout.rightMargin: Kirigami.Units.largeSpacing
|
||||
Layout.bottomMargin: Kirigami.Units.largeSpacing
|
||||
clip: true
|
||||
|
||||
Image {
|
||||
id: image
|
||||
|
||||
property var scaleFactor: 1
|
||||
property int rotationAngle: 0
|
||||
property var rotationInsensitiveWidth: Math.min(root.imageWidth > 0 ? root.imageWidth : sourceSize.width, imageContainer.width - Kirigami.Units.largeSpacing * 2)
|
||||
property var rotationInsensitiveHeight: Math.min(root.imageHeight > 0 ? root.imageHeight : sourceSize.height, imageContainer.height - Kirigami.Units.largeSpacing * 2)
|
||||
|
||||
anchors.centerIn: parent
|
||||
width: rotationAngle % 180 === 0 ? rotationInsensitiveWidth : rotationInsensitiveHeight
|
||||
height: rotationAngle % 180 === 0 ? rotationInsensitiveHeight : rotationInsensitiveWidth
|
||||
fillMode: Image.PreserveAspectFit
|
||||
clip: true
|
||||
|
||||
Behavior on width {
|
||||
NumberAnimation {duration: Kirigami.Units.longDuration; easing.type: Easing.InOutCubic}
|
||||
}
|
||||
Behavior on height {
|
||||
NumberAnimation {duration: Kirigami.Units.longDuration; easing.type: Easing.InOutCubic}
|
||||
}
|
||||
|
||||
Image {
|
||||
anchors.centerIn: parent
|
||||
width: image.width
|
||||
height: image.height
|
||||
source: root.blurhash !== "" ? ("image://blurhash/" + root.blurhash) : ""
|
||||
visible: root.blurhash !== "" && parent.status !== Image.Ready
|
||||
}
|
||||
|
||||
transform: [
|
||||
Rotation {
|
||||
origin.x: image.width / 2
|
||||
origin.y: image.height / 2
|
||||
angle: image.rotationAngle
|
||||
|
||||
Behavior on angle {
|
||||
RotationAnimation {duration: Kirigami.Units.longDuration; easing.type: Easing.InOutCubic}
|
||||
}
|
||||
},
|
||||
Scale {
|
||||
origin.x: image.width / 2
|
||||
origin.y: image.height / 2
|
||||
xScale: image.scaleFactor
|
||||
yScale: image.scaleFactor
|
||||
|
||||
Behavior on xScale {
|
||||
NumberAnimation {duration: Kirigami.Units.longDuration; easing.type: Easing.InOutCubic}
|
||||
}
|
||||
Behavior on yScale {
|
||||
NumberAnimation {duration: Kirigami.Units.longDuration; easing.type: Easing.InOutCubic}
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.RightButton
|
||||
onClicked: {
|
||||
const contextMenu = fileDelegateContextMenu.createObject(parent, {
|
||||
author: modelData.author,
|
||||
message: modelData.message,
|
||||
eventId: modelData.eventId,
|
||||
source: modelData.source,
|
||||
file: root.parent,
|
||||
mimeType: modelData.mimeType,
|
||||
progressInfo: modelData.progressInfo,
|
||||
plainMessage: modelData.message,
|
||||
});
|
||||
contextMenu.closeFullscreen.connect(root.close)
|
||||
contextMenu.open();
|
||||
}
|
||||
}
|
||||
}
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.LeftButton
|
||||
onClicked: {
|
||||
root.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: saveAsDialog
|
||||
FileDialog {
|
||||
fileMode: FileDialog.SaveFile
|
||||
folder: StandardPaths.writableLocation(StandardPaths.DownloadLocation)
|
||||
onAccepted: {
|
||||
if (!currentFile) {
|
||||
return;
|
||||
}
|
||||
currentRoom.downloadFile(eventId, currentFile)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onClosed: {
|
||||
image.scaleFactor = 1
|
||||
image.rotationAngle = 0
|
||||
}
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2020 Tobias Fella <fella@posteo.de>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15 as QQC2
|
||||
import QtQuick.Layouts 1.15
|
||||
|
||||
import org.kde.kirigami 2.15 as Kirigami
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
|
||||
LoginStep {
|
||||
id: root
|
||||
|
||||
readonly property var homeserver: customHomeserver.visible ? customHomeserver.text : serverCombo.currentText
|
||||
property bool loading: false
|
||||
|
||||
title: i18nc("@title", "Select a Homeserver")
|
||||
|
||||
action: Kirigami.Action {
|
||||
enabled: LoginHelper.homeserverReachable && !customHomeserver.visible || customHomeserver.acceptableInput
|
||||
onTriggered: {
|
||||
// TODO
|
||||
console.log("register todo")
|
||||
}
|
||||
}
|
||||
|
||||
onHomeserverChanged: {
|
||||
LoginHelper.testHomeserver("@user:" + homeserver)
|
||||
}
|
||||
|
||||
Kirigami.FormLayout {
|
||||
Component.onCompleted: Controller.testHomeserver(homeserver)
|
||||
|
||||
QQC2.ComboBox {
|
||||
id: serverCombo
|
||||
|
||||
Kirigami.FormData.label: i18n("Homeserver:")
|
||||
model: ["matrix.org", "kde.org", "tchncs.de", i18n("Other...")]
|
||||
}
|
||||
|
||||
QQC2.TextField {
|
||||
id: customHomeserver
|
||||
|
||||
Kirigami.FormData.label: i18n("Url:")
|
||||
visible: serverCombo.currentIndex === 3
|
||||
onTextChanged: {
|
||||
Controller.testHomeserver(text)
|
||||
}
|
||||
validator: RegularExpressionValidator {
|
||||
regularExpression: /([a-zA-Z0-9\-]+\.)*[a-zA-Z0-9]+(:[0-9]+)?/
|
||||
}
|
||||
}
|
||||
|
||||
QQC2.Button {
|
||||
id: continueButton
|
||||
text: i18nc("@action:button", "Continue")
|
||||
action: root.action
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2020 Tobias Fella <fella@posteo.de>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15 as QQC2
|
||||
import QtQuick.Layouts 1.15
|
||||
|
||||
import org.kde.kirigami 2.15 as Kirigami
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
import NeoChat.Component 1.0
|
||||
|
||||
Kirigami.PlaceholderMessage {
|
||||
property var showContinueButton: false
|
||||
property var showBackButton: false
|
||||
property string title: i18n("Loading…")
|
||||
|
||||
anchors.centerIn: parent
|
||||
|
||||
QQC2.Label {
|
||||
text: i18n("Please wait. This might take a little while.")
|
||||
}
|
||||
|
||||
QQC2.BusyIndicator {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
running: false
|
||||
}
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu>
|
||||
// SPDX-FileCopyrightText: 2020 Tobias Fella <fella@posteo.de>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15 as QQC2
|
||||
import QtQuick.Layouts 1.15
|
||||
|
||||
import org.kde.kirigami 2.15 as Kirigami
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
import NeoChat.Component 1.0
|
||||
|
||||
LoginStep {
|
||||
id: login
|
||||
|
||||
showContinueButton: true
|
||||
showBackButton: false
|
||||
|
||||
title: i18nc("@title", "Login")
|
||||
message: i18n("Enter your Matrix ID")
|
||||
|
||||
Component.onCompleted: {
|
||||
LoginHelper.matrixId = ""
|
||||
}
|
||||
|
||||
Kirigami.FormLayout {
|
||||
QQC2.TextField {
|
||||
id: matrixIdField
|
||||
Kirigami.FormData.label: i18n("Matrix ID:")
|
||||
placeholderText: "@user:matrix.org"
|
||||
onTextChanged: {
|
||||
if(acceptableInput) {
|
||||
LoginHelper.matrixId = text
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
matrixIdField.forceActiveFocus()
|
||||
}
|
||||
|
||||
Keys.onReturnPressed: {
|
||||
login.action.trigger()
|
||||
}
|
||||
|
||||
validator: RegularExpressionValidator {
|
||||
regularExpression: /^\@?[a-zA-Z0-9\._=\-/]+\:[a-zA-Z0-9\-]+(\.[a-zA-Z0-9\-]+)*(\:[0-9]+)?$/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
action: Kirigami.Action {
|
||||
text: LoginHelper.testing && matrixIdField.acceptableInput ? i18n("Loading…") : i18nc("@action:button", "Continue")
|
||||
onTriggered: {
|
||||
if (LoginHelper.supportsSso && LoginHelper.supportsPassword) {
|
||||
processed("qrc:/imports/NeoChat/Component/Login/LoginMethod.qml");
|
||||
} else if (LoginHelper.supportsPassword) {
|
||||
processed("qrc:/imports/NeoChat/Component/Login/Password.qml");
|
||||
} else {
|
||||
processed("qrc:/imports/NeoChat/Component/Login/Sso.qml");
|
||||
}
|
||||
}
|
||||
enabled: LoginHelper.homeserverReachable
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2020 Tobias Fella <fella@posteo.de>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15 as Controls
|
||||
import QtQuick.Layouts 1.15
|
||||
import org.kde.kirigami 2.15 as Kirigami
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
import NeoChat.Component.Login 1.0
|
||||
|
||||
LoginStep {
|
||||
id: loginMethod
|
||||
|
||||
title: i18n("Login Methods")
|
||||
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
|
||||
Controls.Button {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
text: i18n("Login with password")
|
||||
Layout.preferredWidth: Kirigami.Units.gridUnit * 12
|
||||
onClicked: processed("qrc:/imports/NeoChat/Component/Login/Password.qml")
|
||||
}
|
||||
|
||||
Controls.Button {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
text: i18n("Login with single sign-on")
|
||||
Layout.preferredWidth: Kirigami.Units.gridUnit * 12
|
||||
onClicked: processed("qrc:/imports/NeoChat/Component/Login/Sso.qml")
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2020 Tobias Fella <fella@posteo.de>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15 as Controls
|
||||
import QtQuick.Layouts 1.15
|
||||
|
||||
import org.kde.kirigami 2.15 as Kirigami
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
import NeoChat.Component.Login 1.0
|
||||
|
||||
LoginStep {
|
||||
id: loginRegister
|
||||
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
|
||||
Controls.Button {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
text: i18n("Login")
|
||||
Layout.preferredWidth: Kirigami.Units.gridUnit * 12
|
||||
onClicked: processed("qrc:/imports/NeoChat/Component/Login/Login.qml")
|
||||
}
|
||||
|
||||
Controls.Button {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
text: i18n("Register")
|
||||
Layout.preferredWidth: Kirigami.Units.gridUnit * 12
|
||||
onClicked: processed("qrc:/imports/NeoChat/Component/Login/Homeserver.qml")
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.de>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import QtQuick 2.14
|
||||
import QtQuick.Controls 2.14
|
||||
import QtQuick.Layouts 1.14
|
||||
|
||||
/// Step for the login/registration flow
|
||||
ColumnLayout {
|
||||
|
||||
property string title: i18n("Welcome")
|
||||
property string message: i18n("Welcome")
|
||||
property bool showContinueButton: false
|
||||
property bool showBackButton: false
|
||||
property bool acceptable: false
|
||||
property string previousUrl: ""
|
||||
|
||||
/// Process this module, this is called by the continue button.
|
||||
/// Should call \sa processed when it finish successfully.
|
||||
property Action action: null
|
||||
|
||||
/// Called when switching to the next step.
|
||||
signal processed(url nextUrl)
|
||||
|
||||
signal showMessage(string message)
|
||||
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2020 Tobias Fella <fella@posteo.de>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15 as QQC2
|
||||
import QtQuick.Layouts 1.15
|
||||
|
||||
import org.kde.kirigami 2.15 as Kirigami
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
import NeoChat.Component 1.0
|
||||
|
||||
LoginStep {
|
||||
id: password
|
||||
|
||||
title: i18nc("@title", "Password")
|
||||
message: i18n("Enter your password")
|
||||
showContinueButton: true
|
||||
showBackButton: true
|
||||
previousUrl: LoginHelper.isLoggingIn ? "" : LoginHelper.supportsSso ? "qrc:/imports/NeoChat/Component/Login/LoginMethod.qml" : "qrc:/imports/NeoChat/Component/Login/Login.qml"
|
||||
|
||||
action: Kirigami.Action {
|
||||
text: i18nc("@action:button", "Login")
|
||||
enabled: passwordField.text.length > 0 && !LoginHelper.isLoggingIn
|
||||
onTriggered: {
|
||||
LoginHelper.login();
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: LoginHelper
|
||||
function onConnected() {
|
||||
processed("qrc:/imports/NeoChat/Component/Login/Loading.qml")
|
||||
}
|
||||
}
|
||||
|
||||
Kirigami.FormLayout {
|
||||
Kirigami.PasswordField {
|
||||
id: passwordField
|
||||
onTextChanged: LoginHelper.password = text
|
||||
enabled: !LoginHelper.isLoggingIn
|
||||
|
||||
Component.onCompleted: {
|
||||
passwordField.forceActiveFocus()
|
||||
}
|
||||
|
||||
Keys.onReturnPressed: {
|
||||
password.action.trigger()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2020 Tobias Fella <fella@posteo.de>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15 as QQC2
|
||||
import QtQuick.Layouts 1.15
|
||||
|
||||
import org.kde.kirigami 2.12 as Kirigami
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
import NeoChat.Component 1.0
|
||||
|
||||
LoginStep {
|
||||
id: root
|
||||
|
||||
title: i18nc("@title", "Login")
|
||||
message: i18n("Login with single sign-on")
|
||||
|
||||
Kirigami.FormLayout {
|
||||
Connections {
|
||||
target: LoginHelper
|
||||
function onSsoUrlChanged() {
|
||||
UrlHelper.openUrl(LoginHelper.ssoUrl)
|
||||
}
|
||||
function onConnected() {
|
||||
processed("qrc:/imports/NeoChat/Component/Login/Loading.qml")
|
||||
}
|
||||
}
|
||||
RowLayout {
|
||||
QQC2.Button {
|
||||
text: i18nc("@action:button", "Back")
|
||||
|
||||
onClicked: {
|
||||
module.source = "qrc:/imports/NeoChat/Component/Login/Login.qml"
|
||||
}
|
||||
}
|
||||
QQC2.Button {
|
||||
text: i18n("Login")
|
||||
onClicked: {
|
||||
LoginHelper.loginWithSso()
|
||||
root.showMessage(i18n("Complete the authentication steps in your browser"))
|
||||
}
|
||||
Component.onCompleted: forceActiveFocus()
|
||||
Keys.onReturnPressed: clicked()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
module NeoChat.Component.Login
|
||||
Login 1.0 Login.qml
|
||||
Password 1.0 Password.qml
|
||||
LoginRegister 1.0 LoginRegister.qml
|
||||
Loading 1.0 Loading.qml
|
||||
LoginMethod 1.0 LoginMethod.qml
|
||||
LoginStep 1.0 LoginStep.qml
|
||||
@@ -1,108 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2021 Carson Black <uhhadd@gmail.com>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Layouts 1.10
|
||||
import QtQuick.Controls 2.12 as QQC2
|
||||
import org.kde.kirigami 2.14 as Kirigami
|
||||
import org.kde.kitemmodels 1.0
|
||||
import org.kde.neochat 1.0
|
||||
|
||||
QQC2.Popup {
|
||||
id: _popup
|
||||
|
||||
Shortcut {
|
||||
sequence: "Ctrl+K"
|
||||
enabled: !Kirigami.Settings.hasPlatformMenuBar
|
||||
onActivated: _popup.open()
|
||||
}
|
||||
|
||||
onVisibleChanged: {
|
||||
if (!visible) {
|
||||
return
|
||||
}
|
||||
quickSearch.forceActiveFocus()
|
||||
quickSearch.text = ""
|
||||
}
|
||||
|
||||
anchors.centerIn: QQC2.Overlay.overlay
|
||||
background: Kirigami.Card {}
|
||||
height: 2 * Math.round(implicitHeight / 2)
|
||||
padding: Kirigami.Units.largeSpacing * 2
|
||||
|
||||
contentItem: ColumnLayout {
|
||||
spacing: Kirigami.Units.largeSpacing * 2
|
||||
|
||||
Kirigami.SearchField {
|
||||
id: quickSearch
|
||||
|
||||
// TODO: get this broken property removed/disabled by default in Kirigami,
|
||||
// we used to be able to expect that the text field wouldn't attempt to
|
||||
// perform a mini-DDOS attack using signals.
|
||||
autoAccept: false
|
||||
|
||||
Layout.preferredWidth: Kirigami.Units.gridUnit * 21 // 3 * 7 = 21, roughly 7 avatars on screen
|
||||
Keys.onLeftPressed: cView.decrementCurrentIndex()
|
||||
Keys.onRightPressed: cView.incrementCurrentIndex()
|
||||
onAccepted: {
|
||||
const item = cView.itemAtIndex(cView.currentIndex)
|
||||
|
||||
RoomManager.enterRoom(item.currentRoom)
|
||||
|
||||
_popup.close()
|
||||
}
|
||||
}
|
||||
ListView {
|
||||
id: cView
|
||||
|
||||
orientation: Qt.Horizontal
|
||||
spacing: Kirigami.Units.largeSpacing
|
||||
|
||||
model: SortFilterRoomListModel {
|
||||
id: sortFilterRoomListModel
|
||||
sourceModel: RoomListModel {
|
||||
id: roomListModel
|
||||
connection: Controller.activeConnection
|
||||
}
|
||||
filterText: quickSearch.text
|
||||
roomSortOrder: SortFilterRoomListModel.LastActivity
|
||||
}
|
||||
|
||||
Layout.preferredHeight: Kirigami.Units.gridUnit * 3
|
||||
Layout.fillWidth: true
|
||||
|
||||
delegate: Kirigami.Avatar {
|
||||
id: del
|
||||
|
||||
implicitHeight: Kirigami.Units.gridUnit * 3
|
||||
implicitWidth: Kirigami.Units.gridUnit * 3
|
||||
|
||||
required property string avatar
|
||||
required property var currentRoom
|
||||
required property int index
|
||||
|
||||
// When an item is hovered set the currentIndex of listview to it so that it is highlighted
|
||||
onHoveredChanged: {
|
||||
if (!hovered) {
|
||||
return
|
||||
}
|
||||
cView.currentIndex = index
|
||||
}
|
||||
|
||||
actions.main: Kirigami.Action {
|
||||
id: enterRoomAction
|
||||
onTriggered: {
|
||||
RoomManager.enterRoom(currentRoom);
|
||||
|
||||
_popup.close()
|
||||
}
|
||||
}
|
||||
|
||||
source: avatar != "" ? "image://mxc/" + avatar : ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
modal: true
|
||||
focus: true
|
||||
}
|
||||
@@ -1,122 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2022 Tobias Fella <fella@posteo.de>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
import QtMultimedia 5.15
|
||||
|
||||
import org.kde.kirigami 2.15 as Kirigami
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
|
||||
TimelineContainer {
|
||||
id: audioDelegate
|
||||
|
||||
onReplyClicked: ListView.view.goToEvent(eventID)
|
||||
|
||||
onOpenContextMenu: openFileContext(model, audioDelegate)
|
||||
|
||||
readonly property bool downloaded: model.progressInfo && model.progressInfo.completed
|
||||
onDownloadedChanged: audio.play()
|
||||
|
||||
hoverComponent: hoverActions
|
||||
innerObject: Control {
|
||||
Layout.fillWidth: true
|
||||
Layout.maximumWidth: audioDelegate.contentMaxWidth
|
||||
|
||||
Audio {
|
||||
id: audio
|
||||
source: model.progressInfo.localPath
|
||||
autoLoad: false
|
||||
}
|
||||
|
||||
states: [
|
||||
State {
|
||||
name: "notDownloaded"
|
||||
when: !model.progressInfo.completed && !model.progressInfo.active
|
||||
|
||||
PropertyChanges {
|
||||
target: playButton
|
||||
icon.name: "media-playback-start"
|
||||
onClicked: currentRoom.downloadFile(model.eventId)
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "downloading"
|
||||
when: model.progressInfo.active && !model.progressInfo.completed
|
||||
PropertyChanges {
|
||||
target: downloadBar
|
||||
visible: true
|
||||
}
|
||||
PropertyChanges {
|
||||
target: playButton
|
||||
icon.name: "media-playback-stop"
|
||||
onClicked: {
|
||||
currentRoom.cancelFileTransfer(model.eventId)
|
||||
}
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "paused"
|
||||
when: model.progressInfo.completed && (audio.playbackState === Audio.StoppedState || audio.playbackState === Audio.PausedState)
|
||||
PropertyChanges {
|
||||
target: playButton
|
||||
icon.name: "media-playback-start"
|
||||
onClicked: {
|
||||
audio.play()
|
||||
}
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "playing"
|
||||
when: model.progressInfo.completed && audio.playbackState === Audio.PlayingState
|
||||
|
||||
PropertyChanges {
|
||||
target: playButton
|
||||
|
||||
icon.name: "media-playback-pause"
|
||||
|
||||
onClicked: audio.pause()
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
contentItem: ColumnLayout {
|
||||
RowLayout {
|
||||
ToolButton {
|
||||
id: playButton
|
||||
}
|
||||
Label {
|
||||
text: model.display
|
||||
wrapMode: Text.Wrap
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
ProgressBar {
|
||||
id: downloadBar
|
||||
visible: false
|
||||
Layout.fillWidth: true
|
||||
from: 0
|
||||
to: model.content.info.size
|
||||
value: model.progressInfo.progress
|
||||
}
|
||||
RowLayout {
|
||||
visible: audio.hasAudio
|
||||
Layout.leftMargin: Kirigami.Units.largeSpacing
|
||||
Layout.rightMargin: Kirigami.Units.largeSpacing
|
||||
|
||||
Slider {
|
||||
from: 0
|
||||
to: audio.duration
|
||||
value: audio.position
|
||||
onMoved: audio.seek(value)
|
||||
}
|
||||
|
||||
Label {
|
||||
text: Controller.formatDuration(audio.position) + "/" + Controller.formatDuration(audio.duration)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2021 Tobias Fella <fella@posteo.de>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15 as QQC2
|
||||
import QtQuick.Layouts 1.15
|
||||
|
||||
import Qt.labs.qmlmodels 1.0
|
||||
import org.kde.kirigami 2.15 as Kirigami
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
|
||||
DelegateChooser {
|
||||
role: "eventType"
|
||||
|
||||
DelegateChoice {
|
||||
roleValue: "state"
|
||||
delegate: StateDelegate {}
|
||||
}
|
||||
|
||||
DelegateChoice {
|
||||
roleValue: "emote"
|
||||
delegate: MessageDelegate {
|
||||
isEmote: true
|
||||
}
|
||||
}
|
||||
|
||||
DelegateChoice {
|
||||
roleValue: "message"
|
||||
delegate: MessageDelegate {}
|
||||
}
|
||||
|
||||
DelegateChoice {
|
||||
roleValue: "notice"
|
||||
delegate: MessageDelegate {}
|
||||
}
|
||||
|
||||
DelegateChoice {
|
||||
roleValue: "image"
|
||||
delegate: ImageDelegate {}
|
||||
}
|
||||
|
||||
DelegateChoice {
|
||||
roleValue: "sticker"
|
||||
delegate: ImageDelegate {}
|
||||
}
|
||||
|
||||
DelegateChoice {
|
||||
roleValue: "audio"
|
||||
delegate: AudioDelegate {}
|
||||
}
|
||||
|
||||
DelegateChoice {
|
||||
roleValue: "video"
|
||||
delegate: VideoDelegate {}
|
||||
}
|
||||
|
||||
DelegateChoice {
|
||||
roleValue: "file"
|
||||
delegate: FileDelegate {}
|
||||
}
|
||||
|
||||
DelegateChoice {
|
||||
roleValue: "encrypted"
|
||||
delegate: EncryptedDelegate {}
|
||||
}
|
||||
|
||||
DelegateChoice {
|
||||
roleValue: "readMarker"
|
||||
delegate: ReadMarkerDelegate {}
|
||||
}
|
||||
|
||||
DelegateChoice {
|
||||
roleValue: "other"
|
||||
delegate: Item {}
|
||||
}
|
||||
}
|
||||
@@ -1,137 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2018-2019 Black Hat <bhat@encom.eu.org>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15 as QQC2
|
||||
import QtQuick.Layouts 1.15
|
||||
import Qt.labs.platform 1.1
|
||||
|
||||
import org.kde.kirigami 2.15 as Kirigami
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
import NeoChat.Component 1.0
|
||||
import NeoChat.Dialog 1.0
|
||||
import NeoChat.Menu.Timeline 1.0
|
||||
|
||||
TimelineContainer {
|
||||
id: fileDelegate
|
||||
|
||||
onReplyClicked: ListView.view.goToEvent(eventID)
|
||||
hoverComponent: hoverActions
|
||||
|
||||
onOpenContextMenu: openFileContext(model, fileDelegate)
|
||||
|
||||
readonly property bool downloaded: progressInfo && progressInfo.completed
|
||||
|
||||
function saveFileAs() {
|
||||
const dialog = fileDialog.createObject(QQC2.ApplicationWindow.overlay)
|
||||
dialog.open()
|
||||
dialog.currentFile = dialog.folder + "/" + currentRoom.fileNameToDownload(eventId)
|
||||
}
|
||||
|
||||
function openSavedFile() {
|
||||
if (UrlHelper.openUrl(progressInfo.localPath)) return;
|
||||
if (UrlHelper.openUrl(progressInfo.localDir)) return;
|
||||
}
|
||||
|
||||
innerObject: RowLayout {
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.maximumWidth: fileDelegate.contentMaxWidth
|
||||
Layout.margins: Kirigami.Units.largeSpacing
|
||||
|
||||
spacing: Kirigami.Units.largeSpacing
|
||||
|
||||
states: [
|
||||
State {
|
||||
name: "downloaded"
|
||||
when: progressInfo.completed
|
||||
|
||||
PropertyChanges {
|
||||
target: downloadButton
|
||||
|
||||
icon.name: "document-open"
|
||||
|
||||
QQC2.ToolTip.text: i18nc("tooltip for a button on a message; offers ability to open its downloaded file with an appropriate application", "Open File")
|
||||
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
||||
|
||||
onClicked: openSavedFile()
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "downloading"
|
||||
when: progressInfo.active
|
||||
|
||||
PropertyChanges {
|
||||
target: sizeLabel
|
||||
text: i18nc("file download progress", "%1 / %2", Controller.formatByteSize(progressInfo.progress), Controller.formatByteSize(progressInfo.total))
|
||||
}
|
||||
PropertyChanges {
|
||||
target: downloadButton
|
||||
icon.name: "media-playback-stop"
|
||||
|
||||
QQC2.ToolTip.text: i18nc("tooltip for a button on a message; stops downloading the message's file", "Stop Download")
|
||||
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
||||
onClicked: currentRoom.cancelFileTransfer(eventId)
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "raw"
|
||||
when: true
|
||||
|
||||
PropertyChanges {
|
||||
target: downloadButton
|
||||
|
||||
onClicked: fileDelegate.saveFileAs()
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
Kirigami.Icon {
|
||||
id: ikon
|
||||
source: model.fileMimetypeIcon
|
||||
fallback: "unknown"
|
||||
}
|
||||
ColumnLayout {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.fillWidth: true
|
||||
|
||||
spacing: 0
|
||||
|
||||
QQC2.Label {
|
||||
text: model.display
|
||||
wrapMode: Text.Wrap
|
||||
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
QQC2.Label {
|
||||
id: sizeLabel
|
||||
|
||||
text: Controller.formatByteSize(content.info ? content.info.size : 0)
|
||||
opacity: 0.7
|
||||
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
|
||||
QQC2.Button {
|
||||
id: downloadButton
|
||||
icon.name: "download"
|
||||
|
||||
QQC2.ToolTip.text: i18nc("tooltip for a button on a message; offers ability to download its file", "Download")
|
||||
QQC2.ToolTip.visible: hovered
|
||||
}
|
||||
|
||||
Component {
|
||||
id: fileDialog
|
||||
|
||||
FileDialog {
|
||||
fileMode: FileDialog.SaveFile
|
||||
folder: StandardPaths.writableLocation(StandardPaths.DownloadLocation)
|
||||
onAccepted: {
|
||||
currentRoom.downloadFile(eventId, file)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,124 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2018-2020 Black Hat <bhat@encom.eu.org>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
import Qt.labs.platform 1.1
|
||||
|
||||
import org.kde.kirigami 2.15 as Kirigami
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
import NeoChat.Component 1.0
|
||||
import NeoChat.Dialog 1.0
|
||||
import NeoChat.Menu.Timeline 1.0
|
||||
|
||||
|
||||
TimelineContainer {
|
||||
id: imageDelegate
|
||||
|
||||
onReplyClicked: ListView.view.goToEvent(eventID)
|
||||
hoverComponent: hoverActions
|
||||
|
||||
onOpenContextMenu: openFileContext(model, imageDelegate)
|
||||
|
||||
property var content: model.content
|
||||
readonly property bool isAnimated: contentType === "image/gif"
|
||||
|
||||
property bool openOnFinished: false
|
||||
readonly property bool downloaded: progressInfo && progressInfo.completed
|
||||
|
||||
readonly property bool isThumbnail: !(content.info.thumbnail_info == null || content.thumbnailMediaId == null)
|
||||
// readonly property var info: isThumbnail ? content.info.thumbnail_info : content.info
|
||||
readonly property var info: content.info
|
||||
readonly property string mediaId: isThumbnail ? content.thumbnailMediaId : content.mediaId
|
||||
|
||||
innerObject: Image {
|
||||
id: img
|
||||
|
||||
Layout.maximumWidth: imageDelegate.contentMaxWidth
|
||||
Layout.maximumHeight: imageDelegate.contentMaxWidth / sourceSize.width * sourceSize.height
|
||||
Layout.preferredWidth: imageDelegate.info.w > 0 ? imageDelegate.info.w : sourceSize.width
|
||||
Layout.preferredHeight: imageDelegate.info.h > 0 ? imageDelegate.info.h : sourceSize.height
|
||||
source: model.mediaUrl
|
||||
|
||||
Image {
|
||||
anchors.fill: parent
|
||||
source: content.info["xyz.amorgan.blurhash"] ? ("image://blurhash/" + content.info["xyz.amorgan.blurhash"]) : ""
|
||||
visible: parent.status !== Image.Ready
|
||||
}
|
||||
|
||||
fillMode: Image.PreserveAspectFit
|
||||
|
||||
ToolTip.text: model.display
|
||||
ToolTip.visible: hoverHandler.hovered
|
||||
ToolTip.delay: Kirigami.Units.toolTipDelay
|
||||
|
||||
HoverHandler {
|
||||
id: hoverHandler
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
|
||||
visible: progressInfo.active && !downloaded
|
||||
|
||||
color: "#BB000000"
|
||||
|
||||
ProgressBar {
|
||||
anchors.centerIn: parent
|
||||
|
||||
width: parent.width * 0.8
|
||||
|
||||
from: 0
|
||||
to: progressInfo.total
|
||||
value: progressInfo.progress
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: fileDialog
|
||||
|
||||
FileDialog {
|
||||
fileMode: FileDialog.SaveFile
|
||||
folder: StandardPaths.writableLocation(StandardPaths.DownloadLocation)
|
||||
onAccepted: {
|
||||
currentRoom.downloadFile(eventId, file)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TapHandler {
|
||||
acceptedButtons: Qt.LeftButton
|
||||
onLongPressed: openFileContext(model, parent)
|
||||
onTapped: {
|
||||
img.ToolTip.hide()
|
||||
fullScreenImage.open()
|
||||
}
|
||||
}
|
||||
|
||||
FullScreenImage {
|
||||
id: fullScreenImage
|
||||
filename: eventId
|
||||
source: mediaUrl
|
||||
blurhash: model.content.info["xyz.amorgan.blurhash"]
|
||||
imageWidth: content.info.w
|
||||
imageHeight: content.info.h
|
||||
modelData: model
|
||||
}
|
||||
|
||||
function downloadAndOpen() {
|
||||
if (downloaded) {
|
||||
openSavedFile()
|
||||
} else {
|
||||
openOnFinished = true
|
||||
currentRoom.downloadFile(eventId, StandardPaths.writableLocation(StandardPaths.CacheLocation) + "/" + eventId.replace(":", "_").replace("/", "_").replace("+", "_") + currentRoom.fileNameToDownload(eventId))
|
||||
}
|
||||
}
|
||||
|
||||
function openSavedFile() {
|
||||
if (UrlHelper.openUrl(progressInfo.localPath)) return;
|
||||
if (UrlHelper.openUrl(progressInfo.localDir)) return;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2022 Bharadwaj Raju <bharadwaj.raju777@protonmail.com>
|
||||
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-or-later OR LicenseRef-KDE-Accepted-GPL
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
|
||||
import org.kde.kirigami 2.15 as Kirigami
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
|
||||
RowLayout {
|
||||
id: row
|
||||
property var links: model.display.match(/(\bhttps?:\/\/[^\s\<\>\"\']*[^\s\<\>\"\'])/g)
|
||||
// don't show previews for room links or user mentions
|
||||
.filter(link => !link.includes("https://matrix.to"))
|
||||
// remove ending fullstops and commas
|
||||
.map(link => (link.length && [".", ","].includes(link[link.length-1])) ? link.substring(0, link.length-1) : link)
|
||||
LinkPreviewer {
|
||||
id: lp
|
||||
url: links[0]
|
||||
}
|
||||
visible: lp.loaded && lp.title
|
||||
Rectangle {
|
||||
Layout.fillHeight: true
|
||||
width: Kirigami.Units.smallSpacing
|
||||
visible: lp.loaded && lp.title
|
||||
color: Kirigami.Theme.highlightColor
|
||||
}
|
||||
Image {
|
||||
visible: lp.imageSource
|
||||
Layout.maximumHeight: Kirigami.Units.gridUnit * 5
|
||||
Layout.maximumWidth: Kirigami.Units.gridUnit * 5
|
||||
source: lp.imageSource.replace("mxc://", "image://mxc/")
|
||||
fillMode: Image.PreserveAspectFit
|
||||
}
|
||||
ColumnLayout {
|
||||
id: column
|
||||
spacing: Kirigami.Units.smallSpacing
|
||||
Kirigami.Heading {
|
||||
Layout.maximumWidth: messageDelegate.bubbleMaxWidth
|
||||
Layout.fillWidth: true
|
||||
level: 4
|
||||
wrapMode: Text.Wrap
|
||||
textFormat: Text.RichText
|
||||
text: "<style>
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
</style>
|
||||
<a href=\"" + links[0] + "\">" + lp.title.replace("–", "—") + "</a>"
|
||||
visible: lp.loaded
|
||||
onLinkActivated: RoomManager.openResource(link)
|
||||
}
|
||||
Label {
|
||||
text: lp.description
|
||||
Layout.maximumWidth: messageDelegate.bubbleMaxWidth
|
||||
Layout.fillWidth: true
|
||||
wrapMode: Text.Wrap
|
||||
visible: lp.loaded && lp.description
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2021 Tobias Fella <fella@posteo.de>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15 as QQC2
|
||||
import QtQuick.Layouts 1.15
|
||||
|
||||
import Qt.labs.qmlmodels 1.0
|
||||
import org.kde.kirigami 2.15 as Kirigami
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
|
||||
TimelineContainer {
|
||||
id: messageDelegate
|
||||
|
||||
property bool isEmote: false
|
||||
onOpenContextMenu: openMessageContext(model, label.selectedText, Controller.plainText(label.textDocument))
|
||||
|
||||
onReplyClicked: ListView.view.goToEvent(eventID)
|
||||
hoverComponent: hoverActions
|
||||
|
||||
innerObject: ColumnLayout {
|
||||
Layout.maximumWidth: messageDelegate.contentMaxWidth
|
||||
RichLabel {
|
||||
id: label
|
||||
isEmote: messageDelegate.isEmote
|
||||
}
|
||||
Loader {
|
||||
id: linkPreviewLoader
|
||||
Layout.fillWidth: true
|
||||
height: active ? item.implicitHeight : 0
|
||||
active: !currentRoom.usesEncryption && model.display && model.display.includes("http")
|
||||
visible: active
|
||||
sourceComponent: LinkPreviewDelegate {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2019 Black Hat <bhat@encom.eu.org>
|
||||
// SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
|
||||
import org.kde.kirigami 2.15 as Kirigami
|
||||
|
||||
Flow {
|
||||
spacing: Kirigami.Units.largeSpacing
|
||||
Repeater {
|
||||
model: reaction ?? null
|
||||
|
||||
delegate: AbstractButton {
|
||||
width: Math.max(implicitWidth, height)
|
||||
|
||||
contentItem: Label {
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
text: modelData.reaction + " " + modelData.count
|
||||
}
|
||||
|
||||
padding: Kirigami.Units.smallSpacing
|
||||
|
||||
background: Kirigami.ShadowedRectangle {
|
||||
color: checked ? Kirigami.Theme.positiveBackgroundColor : Kirigami.Theme.backgroundColor
|
||||
radius: height / 2
|
||||
shadow.size: Kirigami.Units.smallSpacing
|
||||
shadow.color: !model.isHighlighted ? Qt.rgba(0.0, 0.0, 0.0, 0.10) : Qt.rgba(Kirigami.Theme.textColor.r, Kirigami.Theme.textColor.g, Kirigami.Theme.textColor.b, 0.10)
|
||||
border.color: Kirigami.ColorUtils.tintWithAlpha(color, Kirigami.Theme.textColor, 0.15)
|
||||
border.width: 1
|
||||
}
|
||||
|
||||
|
||||
checkable: true
|
||||
|
||||
checked: modelData.hasLocalUser
|
||||
|
||||
onToggled: currentRoom.toggleReaction(eventId, modelData.reaction)
|
||||
|
||||
hoverEnabled: true
|
||||
|
||||
ToolTip.visible: hovered
|
||||
ToolTip.text: {
|
||||
var text = "";
|
||||
|
||||
for (var i = 0; i < modelData.authors.length && i < 3; i++) {
|
||||
if (i !== 0) {
|
||||
if (i < modelData.authors.length - 1) {
|
||||
text += ", "
|
||||
} else {
|
||||
text += i18nc("Separate the usernames of users", " and ")
|
||||
}
|
||||
}
|
||||
text += modelData.authors[i].displayName
|
||||
}
|
||||
if (modelData.authors.length > 3) {
|
||||
text += i18ncp("%1 is the number of other users", " and %1 other", " and %1 others", modelData.authors.length - 3)
|
||||
}
|
||||
|
||||
text = i18ncp("%2 is the users who reacted and %3 the emoji that was given", "%2 reacted with %3", "%2 reacted with %3", modelData.authors.length, text, modelData.reaction)
|
||||
|
||||
return text
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,103 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2019 Black Hat <bhat@encom.eu.org>
|
||||
// SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15 as QQC2
|
||||
import QtQuick.Layouts 1.15
|
||||
|
||||
import org.kde.kirigami 2.15 as Kirigami
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
import NeoChat.Component.Timeline 1.0
|
||||
|
||||
MouseArea {
|
||||
id: replyButton
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: replyName.implicitHeight + (loader.item ? loader.item.height : 0) + Kirigami.Units.largeSpacing
|
||||
implicitWidth: Math.min(contentMaxWidth, Math.max((loader.item ? loader.item.width : 0), replyName.implicitWidth)) + Kirigami.Units.gridUnit + Kirigami.Units.smallSpacing
|
||||
Component.onCompleted: {
|
||||
parent.Layout.fillWidth = true;
|
||||
parent.Layout.preferredWidth = Qt.binding(function() { return implicitWidth; })
|
||||
parent.Layout.maximumWidth = Qt.binding(function() { return contentMaxWidth + Kirigami.Units.largeSpacing * 2; })
|
||||
}
|
||||
Rectangle {
|
||||
id: replyLeftBorder
|
||||
width: Kirigami.Units.smallSpacing
|
||||
height: parent.height
|
||||
x: Config.compactLayout ? Kirigami.Units.largeSpacing : 0
|
||||
color: Kirigami.Theme.highlightColor
|
||||
}
|
||||
|
||||
Kirigami.Avatar {
|
||||
id: replyAvatar
|
||||
anchors.left: replyLeftBorder.right
|
||||
anchors.leftMargin: Kirigami.Units.smallSpacing
|
||||
width: visible ? Kirigami.Units.gridUnit : 0
|
||||
height: Kirigami.Units.gridUnit
|
||||
sourceSize.width: width
|
||||
sourceSize.height: height
|
||||
Layout.alignment: Qt.AlignTop
|
||||
visible: Config.showAvatarInTimeline
|
||||
source: reply.author.avatarMediaId ? ("image://mxc/" + reply.author.avatarMediaId) : ""
|
||||
name: reply.author.name || ""
|
||||
color: reply.author.color
|
||||
}
|
||||
|
||||
QQC2.Label {
|
||||
id: replyName
|
||||
anchors {
|
||||
left: replyAvatar.right
|
||||
leftMargin: Kirigami.Units.smallSpacing
|
||||
right: parent.right
|
||||
rightMargin: Kirigami.Units.smallSpacing
|
||||
}
|
||||
text: currentRoom.htmlSafeMemberName(reply.author.id)
|
||||
color: reply.author.color
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: loader
|
||||
anchors.top: replyName.bottom
|
||||
sourceComponent: {
|
||||
switch (reply.type) {
|
||||
case "image":
|
||||
case "sticker":
|
||||
return imageComponent;
|
||||
case "message":
|
||||
return textComponent;
|
||||
// TODO support more types
|
||||
default:
|
||||
return textComponent;
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: textComponent
|
||||
RichLabel {
|
||||
id: replyText
|
||||
textMessage: reply.display
|
||||
textFormat: Text.RichText
|
||||
width: Math.min(implicitWidth, contentMaxWidth - Kirigami.Units.largeSpacing * 3)
|
||||
x: Kirigami.Units.smallSpacing * 3 + replyAvatar.width
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: imageComponent
|
||||
Image {
|
||||
readonly property var content: reply.content
|
||||
readonly property bool isThumbnail: !(content.info.thumbnail_info == null || content.thumbnailMediaId == null)
|
||||
// readonly property var info: isThumbnail ? content.info.thumbnail_info : content.info
|
||||
readonly property var info: content.info
|
||||
readonly property string mediaId: isThumbnail ? content.thumbnailMediaId : content.mediaId
|
||||
source: "image://mxc/" + mediaId
|
||||
|
||||
width: contentMaxWidth * 0.75 - Kirigami.Units.smallSpacing * 5 - replyAvatar.width
|
||||
height: reply.content.info.h / reply.content.info.w * width
|
||||
x: Kirigami.Units.smallSpacing * 3 + replyAvatar.width
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2019 Black Hat <bhat@encom.eu.org>
|
||||
// SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
import QtQuick 2.15
|
||||
|
||||
import org.kde.kirigami 2.15 as Kirigami
|
||||
|
||||
Kirigami.Heading {
|
||||
level: 4
|
||||
text: model.showSection ? section : ""
|
||||
color: Kirigami.Theme.disabledTextColor
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
topPadding: Kirigami.Units.largeSpacing * 2
|
||||
bottomPadding: Kirigami.Units.smallSpacing
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2018-2020 Black Hat <bhat@encom.eu.org>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
|
||||
import org.kde.kirigami 2.15 as Kirigami
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
import NeoChat.Component 1.0
|
||||
import NeoChat.Dialog 1.0
|
||||
|
||||
Control {
|
||||
id: stateDelegate
|
||||
// extraWidth defines how the delegate can grow after the listView gets very wide
|
||||
readonly property int extraWidth: messageListView.width >= Kirigami.Units.gridUnit * 46 ? Math.min((messageListView.width - Kirigami.Units.gridUnit * 46), Kirigami.Units.gridUnit * 20) : 0
|
||||
readonly property int delegateMaxWidth: Config.compactLayout ? messageListView.width: Math.min(messageListView.width, Kirigami.Units.gridUnit * 40 + extraWidth)
|
||||
|
||||
width: delegateMaxWidth
|
||||
// anchors.leftMargin: Kirigami.Units.largeSpacing
|
||||
// anchors.rightMargin: Kirigami.Units.largeSpacing
|
||||
|
||||
state: Config.compactLayout ? "alignLeft" : "alignCenter"
|
||||
// Align left when in compact mode and center when using bubbles
|
||||
states: [
|
||||
State {
|
||||
name: "alignLeft"
|
||||
AnchorChanges {
|
||||
target: stateDelegate
|
||||
anchors.horizontalCenter: undefined
|
||||
anchors.left: parent ? parent.left : undefined
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "alignCenter"
|
||||
AnchorChanges {
|
||||
target: stateDelegate
|
||||
anchors.horizontalCenter: parent ? parent.horizontalCenter : undefined
|
||||
anchors.left: undefined
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
transitions: [
|
||||
Transition {
|
||||
AnchorAnimation{duration: Kirigami.Units.longDuration; easing.type: Easing.OutCubic}
|
||||
}
|
||||
]
|
||||
|
||||
height: sectionDelegate.height + rowLayout.height
|
||||
SectionDelegate {
|
||||
id: sectionDelegate
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
visible: model.showSection
|
||||
height: visible ? implicitHeight : 0
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: rowLayout
|
||||
height: label.contentHeight
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.leftMargin: Kirigami.Units.gridUnit * 1.5 + Kirigami.Units.smallSpacing + (Config.compactLayout ? Kirigami.Units.largeSpacing * 1.25 : 0)
|
||||
anchors.rightMargin: Kirigami.Units.largeSpacing
|
||||
|
||||
Kirigami.Avatar {
|
||||
id: icon
|
||||
Layout.preferredWidth: Kirigami.Units.iconSizes.small
|
||||
Layout.preferredHeight: Kirigami.Units.iconSizes.small
|
||||
Layout.alignment: Qt.AlignTop
|
||||
|
||||
name: author.displayName
|
||||
source: author.avatarMediaId ? ("image://mxc/" + author.avatarMediaId) : ""
|
||||
color: author.color
|
||||
|
||||
Component {
|
||||
id: userDetailDialog
|
||||
|
||||
UserDetailDialog {}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: userDetailDialog.createObject(ApplicationWindow.overlay, {room: currentRoom, user: author.object, displayName: author.displayName, avatarMediaId: author.avatarMediaId, avatarUrl: author.avatarUrl}).open()
|
||||
}
|
||||
}
|
||||
|
||||
Label {
|
||||
id: label
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: icon.height
|
||||
wrapMode: Text.WordWrap
|
||||
textFormat: Text.RichText
|
||||
text: `<style>a {text-decoration: none;}</style><a href="https://matrix.to/#/${author.id}" style="color: ${author.color}">${currentRoom.htmlSafeMemberName(author.id)}</a> ${aggregateDisplay}`
|
||||
onLinkActivated: userDetailDialog.createObject(ApplicationWindow.overlay, {room: currentRoom, user: author.object, displayName: author.displayName, avatarMediaId: author.avatarMediaId, avatarUrl: author.avatarUrl}).open()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,319 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2020 Black Hat <bhat@encom.eu.org>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15 as QQC2
|
||||
import QtQuick.Layouts 1.15
|
||||
|
||||
import org.kde.kirigami 2.15 as Kirigami
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
import NeoChat.Component 1.0
|
||||
import NeoChat.Dialog 1.0
|
||||
|
||||
QQC2.ItemDelegate {
|
||||
id: timelineContainer
|
||||
default property alias innerObject : column.children
|
||||
// readonly property bool failed: marks == EventStatus.SendingFailed
|
||||
|
||||
property bool isEmote: false
|
||||
property bool cardBackground: true
|
||||
property bool isHighlighted: model.isHighlighted || isTemporaryHighlighted
|
||||
property bool isTemporaryHighlighted: false
|
||||
|
||||
onIsTemporaryHighlightedChanged: if (isTemporaryHighlighted) temporaryHighlightTimer.start()
|
||||
|
||||
Timer {
|
||||
id: temporaryHighlightTimer
|
||||
|
||||
interval: 1500
|
||||
onTriggered: isTemporaryHighlighted = false
|
||||
}
|
||||
|
||||
signal openContextMenu
|
||||
|
||||
// The bubble and delegate widths are allowed to grow once the ListView gets beyond a certain size
|
||||
// extraWidth defines this as the excess after a certain ListView width, capped to a max value
|
||||
readonly property int extraWidth: messageListView.width >= Kirigami.Units.gridUnit * 46 ? Math.min((messageListView.width - Kirigami.Units.gridUnit * 46), Kirigami.Units.gridUnit * 20) : 0
|
||||
readonly property int bubbleMaxWidth: Kirigami.Units.gridUnit * 20 + extraWidth * 0.5
|
||||
readonly property int delegateMaxWidth: Config.compactLayout ? messageListView.width : Math.min(messageListView.width, Kirigami.Units.gridUnit * 40 + extraWidth)
|
||||
readonly property int contentMaxWidth: Config.compactLayout ? width - (Config.showAvatarInTimeline ? Kirigami.Units.gridUnit * 2 : 0) - Kirigami.Units.largeSpacing * 4 : Math.min(width - Kirigami.Units.gridUnit * 2 - Kirigami.Units.largeSpacing * 6, bubbleMaxWidth)
|
||||
|
||||
property bool showUserMessageOnRight: Config.showLocalMessagesOnRight &&
|
||||
model.author.isLocalUser && !Config.compactLayout
|
||||
|
||||
signal openExternally()
|
||||
signal replyClicked(string eventID)
|
||||
|
||||
Component.onCompleted: {
|
||||
if (model.isReply && model.reply === undefined) {
|
||||
messageEventModel.loadReply(sortedMessageEventModel.mapToSource(sortedMessageEventModel.index(model.index, 0)))
|
||||
}
|
||||
}
|
||||
|
||||
topPadding: 0
|
||||
bottomPadding: 0
|
||||
topInset: showAuthor ? Kirigami.Units.largeSpacing : (Config.compactLayout ? 1 : Kirigami.Units.smallSpacing)
|
||||
leftInset: Kirigami.Units.smallSpacing
|
||||
rightInset: Kirigami.Units.smallSpacing
|
||||
width: delegateMaxWidth
|
||||
height: sectionDelegate.height + Math.max(model.showAuthor ? avatar.height : 0, bubble.implicitHeight) + loader.height + (showAuthor ? Kirigami.Units.largeSpacing : (Config.compactLayout ? 1 : Kirigami.Units.smallSpacing))
|
||||
background: Rectangle {
|
||||
visible: timelineContainer.hovered
|
||||
color: Kirigami.ColorUtils.tintWithAlpha(Kirigami.Theme.backgroundColor, Kirigami.Theme.highlightColor, 0.15)
|
||||
radius: Kirigami.Units.smallSpacing
|
||||
}
|
||||
|
||||
property Item hoverComponent
|
||||
|
||||
// show hover actions
|
||||
onHoveredChanged: {
|
||||
if (hovered && !Kirigami.Settings.isMobile) {
|
||||
updateHoverComponent();
|
||||
}
|
||||
}
|
||||
|
||||
// updates the global hover component to point to this delegate, and update its position
|
||||
function updateHoverComponent() {
|
||||
if (hoverComponent) {
|
||||
hoverComponent.delegate = timelineContainer
|
||||
hoverComponent.bubble = bubble
|
||||
hoverComponent.updateFunction = updateHoverComponent;
|
||||
hoverComponent.event = model
|
||||
}
|
||||
}
|
||||
|
||||
state: Config.compactLayout ? "alignLeft" : "alignCenter"
|
||||
// Align left when in compact mode and center when using bubbles
|
||||
states: [
|
||||
State {
|
||||
name: "alignLeft"
|
||||
AnchorChanges {
|
||||
target: timelineContainer
|
||||
anchors.horizontalCenter: undefined
|
||||
anchors.left: parent ? parent.left : undefined
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "alignCenter"
|
||||
AnchorChanges {
|
||||
target: timelineContainer
|
||||
anchors.horizontalCenter: parent ? parent.horizontalCenter : undefined
|
||||
anchors.left: undefined
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
transitions: [
|
||||
Transition {
|
||||
AnchorAnimation{duration: Kirigami.Units.longDuration; easing.type: Easing.OutCubic}
|
||||
}
|
||||
]
|
||||
|
||||
SectionDelegate {
|
||||
id: sectionDelegate
|
||||
width: parent.width
|
||||
anchors.left: avatar.left
|
||||
anchors.leftMargin: Kirigami.Units.smallSpacing
|
||||
visible: model.showSection
|
||||
height: visible ? implicitHeight : 0
|
||||
}
|
||||
|
||||
Kirigami.Avatar {
|
||||
id: avatar
|
||||
width: visible || Config.showAvatarInTimeline ? Kirigami.Units.gridUnit * 2 + Kirigami.Units.smallSpacing * 2 : 0
|
||||
height: width
|
||||
padding: Kirigami.Units.smallSpacing
|
||||
topInset: Kirigami.Units.smallSpacing
|
||||
bottomInset: Kirigami.Units.smallSpacing
|
||||
leftInset: Kirigami.Units.smallSpacing
|
||||
rightInset: Kirigami.Units.smallSpacing
|
||||
sourceSize.width: width
|
||||
sourceSize.height: width
|
||||
anchors {
|
||||
top: sectionDelegate.bottom
|
||||
topMargin: model.showAuthor ? Kirigami.Units.largeSpacing : (Config.compactLayout ? 1 : Kirigami.Units.smallSpacing)
|
||||
left: parent.left
|
||||
leftMargin: Kirigami.Units.smallSpacing
|
||||
}
|
||||
|
||||
visible: model.showAuthor &&
|
||||
Config.showAvatarInTimeline &&
|
||||
(Config.compactLayout || !showUserMessageOnRight)
|
||||
name: model.author.name ?? model.author.displayName
|
||||
source: visible && model.author.avatarMediaId ? ("image://mxc/" + model.author.avatarMediaId) : ""
|
||||
color: model.author.color
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
userDetailDialog.createObject(QQC2.ApplicationWindow.overlay, {
|
||||
room: currentRoom,
|
||||
user: author.object,
|
||||
displayName: author.displayName,
|
||||
avatarMediaId: author.avatarMediaId,
|
||||
avatarUrl: author.avatarUrl
|
||||
}).open();
|
||||
}
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
}
|
||||
}
|
||||
|
||||
QQC2.Control {
|
||||
id: bubble
|
||||
topPadding: Config.compactLayout ? Kirigami.Units.smallSpacing / 2 : Kirigami.Units.largeSpacing
|
||||
bottomPadding: Config.compactLayout ? Kirigami.Units.mediumSpacing / 2 : Kirigami.Units.largeSpacing
|
||||
leftPadding: Kirigami.Units.largeSpacing + Kirigami.Units.smallSpacing
|
||||
rightPadding: Kirigami.Units.largeSpacing + Kirigami.Units.smallSpacing
|
||||
hoverEnabled: true
|
||||
|
||||
anchors {
|
||||
top: avatar.top
|
||||
leftMargin: Kirigami.Units.smallSpacing
|
||||
rightMargin: showUserMessageOnRight ? Kirigami.Units.smallSpacing : Kirigami.Units.largeSpacing
|
||||
}
|
||||
// HACK: anchoring didn't reset anchors.right when switching from parent.right to undefined reliably
|
||||
width: Config.compactLayout ? timelineContainer.width - (Config.showAvatarInTimeline ? Kirigami.Units.gridUnit * 2 : 0) + Kirigami.Units.largeSpacing * 2 : implicitWidth
|
||||
|
||||
state: showUserMessageOnRight ? "userMessageOnRight" : "userMessageOnLeft"
|
||||
// states for anchor animations on window resize
|
||||
// as setting anchors to undefined did not work reliably
|
||||
states: [
|
||||
State {
|
||||
name: "userMessageOnRight"
|
||||
AnchorChanges {
|
||||
target: bubble
|
||||
anchors.left: undefined
|
||||
anchors.right: parent.right
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "userMessageOnLeft"
|
||||
AnchorChanges {
|
||||
target: bubble
|
||||
anchors.left: avatar.right
|
||||
anchors.right: undefined
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
transitions: [
|
||||
Transition {
|
||||
AnchorAnimation{duration: Kirigami.Units.longDuration; easing.type: Easing.OutCubic}
|
||||
}
|
||||
]
|
||||
|
||||
contentItem: ColumnLayout {
|
||||
id: column
|
||||
spacing: Kirigami.Units.smallSpacing
|
||||
RowLayout {
|
||||
id: rowLayout
|
||||
|
||||
spacing: Kirigami.Units.smallSpacing
|
||||
visible: model.showAuthor && !isEmote
|
||||
|
||||
QQC2.Label {
|
||||
id: nameLabel
|
||||
|
||||
Layout.maximumWidth: contentMaxWidth - timeLabel.implicitWidth - rowLayout.spacing
|
||||
|
||||
text: visible ? author.displayName : ""
|
||||
textFormat: Text.PlainText
|
||||
font.weight: Font.Bold
|
||||
color: author.color
|
||||
elide: Text.ElideRight
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
userDetailDialog.createObject(QQC2.ApplicationWindow.overlay, {
|
||||
room: currentRoom,
|
||||
user: author.object,
|
||||
displayName: author.displayName,
|
||||
avatarMediaId: author.avatarMediaId,
|
||||
avatarUrl: author.avatarUrl
|
||||
}).open();
|
||||
}
|
||||
}
|
||||
}
|
||||
QQC2.Label {
|
||||
id: timeLabel
|
||||
|
||||
text: visible ? time.toLocaleTimeString(Qt.locale(), Locale.ShortFormat) : ""
|
||||
color: Kirigami.Theme.disabledTextColor
|
||||
QQC2.ToolTip.visible: hoverHandler.hovered
|
||||
QQC2.ToolTip.text: time.toLocaleString(Qt.locale(), Locale.LongFormat)
|
||||
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
||||
|
||||
HoverHandler {
|
||||
id: hoverHandler
|
||||
}
|
||||
}
|
||||
}
|
||||
Loader {
|
||||
id: replyLoader
|
||||
active: model.reply !== undefined
|
||||
source: 'qrc:imports/NeoChat/Component/Timeline/ReplyComponent.qml'
|
||||
visible: active
|
||||
|
||||
Connections {
|
||||
target: replyLoader.item
|
||||
function onClicked() {
|
||||
replyClicked(reply.eventId)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
background: Item {
|
||||
Kirigami.ShadowedRectangle {
|
||||
id: bubbleBackground
|
||||
visible: cardBackground && !Config.compactLayout
|
||||
anchors.fill: parent
|
||||
color: {
|
||||
if (model.author.isLocalUser) {
|
||||
return Kirigami.ColorUtils.tintWithAlpha(Kirigami.Theme.backgroundColor, Kirigami.Theme.highlightColor, 0.15)
|
||||
} else if (timelineContainer.isHighlighted) {
|
||||
return Kirigami.Theme.positiveBackgroundColor
|
||||
} else {
|
||||
return Kirigami.Theme.backgroundColor
|
||||
}
|
||||
}
|
||||
radius: Kirigami.Units.smallSpacing
|
||||
shadow.size: Kirigami.Units.smallSpacing
|
||||
shadow.color: timelineContainer.isHighlighted ? Qt.rgba(0.0, 0.0, 0.0, 0.10) : Qt.rgba(Kirigami.Theme.textColor.r, Kirigami.Theme.textColor.g, Kirigami.Theme.textColor.b, 0.10)
|
||||
border.color: Kirigami.ColorUtils.tintWithAlpha(color, Kirigami.Theme.textColor, 0.15)
|
||||
border.width: 1
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {target: bubbleBackground; duration: Kirigami.Units.veryLongDuration; easing.type: Easing.InOutCubic}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: loader
|
||||
anchors {
|
||||
left: bubble.left
|
||||
right: parent.right
|
||||
top: bubble.bottom
|
||||
topMargin: active && Config.compactLayout ? 0 : Kirigami.Units.smallSpacing
|
||||
}
|
||||
height: active ? item.implicitHeight : 0
|
||||
//Layout.bottomMargin: readMarker ? Kirigami.Units.smallSpacing : 0
|
||||
active: eventType !== "state" && eventType !== "notice" && reaction != undefined && reaction.length > 0
|
||||
visible: active
|
||||
sourceComponent: ReactionDelegate { }
|
||||
}
|
||||
|
||||
TapHandler {
|
||||
acceptedButtons: Qt.RightButton
|
||||
onTapped: timelineContainer.openContextMenu()
|
||||
}
|
||||
|
||||
TapHandler {
|
||||
acceptedButtons: Qt.LeftButton
|
||||
onLongPressed: timelineContainer.openContextMenu()
|
||||
}
|
||||
}
|
||||
@@ -1,141 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2019 Black Hat <bhat@encom.eu.org>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
import QtMultimedia 5.15
|
||||
import Qt.labs.platform 1.1 as Platform
|
||||
|
||||
import org.kde.kirigami 2.13 as Kirigami
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
import NeoChat.Component 1.0
|
||||
import NeoChat.Dialog 1.0
|
||||
import NeoChat.Menu.Timeline 1.0
|
||||
|
||||
TimelineContainer {
|
||||
id: videoDelegate
|
||||
|
||||
onReplyClicked: ListView.view.goToEvent(eventID)
|
||||
hoverComponent: hoverActions
|
||||
|
||||
property bool playOnFinished: false
|
||||
readonly property bool downloaded: progressInfo && progressInfo.completed
|
||||
|
||||
property bool supportStreaming: true
|
||||
readonly property int maxWidth: 1000 // TODO messageListView.width
|
||||
|
||||
onOpenContextMenu: openFileContext(model, vid)
|
||||
|
||||
onDownloadedChanged: {
|
||||
if (downloaded) {
|
||||
vid.source = progressInfo.localPath
|
||||
}
|
||||
|
||||
if (downloaded && playOnFinished) {
|
||||
playSavedFile()
|
||||
playOnFinished = false
|
||||
}
|
||||
}
|
||||
|
||||
innerObject: Video {
|
||||
id: vid
|
||||
|
||||
Layout.maximumWidth: videoDelegate.contentMaxWidth
|
||||
Layout.fillWidth: true
|
||||
Layout.maximumHeight: Kirigami.Units.gridUnit * 15
|
||||
Layout.minimumHeight: Kirigami.Units.gridUnit * 5
|
||||
|
||||
Layout.preferredWidth: (model.content.info.w === undefined || model.content.info.w > videoDelegate.maxWidth) ? videoDelegate.maxWidth : content.info.w
|
||||
Layout.preferredHeight: model.content.info.w === undefined ? (videoDelegate.maxWidth * 3 / 4) : (model.content.info.w > videoDelegate.maxWidth ? (model.content.info.h / model.content.info.w * videoDelegate.maxWidth) : model.content.info.h)
|
||||
|
||||
loops: MediaPlayer.Infinite
|
||||
|
||||
fillMode: VideoOutput.PreserveAspectFit
|
||||
|
||||
onDurationChanged: {
|
||||
if (!duration) {
|
||||
vid.supportStreaming = false;
|
||||
}
|
||||
}
|
||||
|
||||
onErrorChanged: {
|
||||
if (error != MediaPlayer.NoError) {
|
||||
vid.supportStreaming = false;
|
||||
}
|
||||
}
|
||||
|
||||
Image {
|
||||
anchors.fill: parent
|
||||
|
||||
visible: vid.playbackState == MediaPlayer.StoppedState || vid.error != MediaPlayer.NoError
|
||||
|
||||
source: model.content.thumbnailMediaId ? "image://mxc/" + model.content.thumbnailMediaId : ""
|
||||
|
||||
fillMode: Image.PreserveAspectFit
|
||||
}
|
||||
|
||||
Label {
|
||||
anchors.centerIn: parent
|
||||
|
||||
visible: vid.playbackState == MediaPlayer.StoppedState || vid.error != MediaPlayer.NoError
|
||||
color: "white"
|
||||
text: i18n("Video")
|
||||
font.pixelSize: 16
|
||||
|
||||
padding: 8
|
||||
|
||||
background: Rectangle {
|
||||
radius: Kirigami.Units.smallSpacing
|
||||
color: "black"
|
||||
opacity: 0.3
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
|
||||
visible: progressInfo.active && !videoDelegate.downloaded
|
||||
|
||||
color: "#BB000000"
|
||||
|
||||
ProgressBar {
|
||||
anchors.centerIn: parent
|
||||
|
||||
width: parent.width * 0.8
|
||||
|
||||
from: 0
|
||||
to: progressInfo.total
|
||||
value: progressInfo.progress
|
||||
}
|
||||
}
|
||||
|
||||
TapHandler {
|
||||
acceptedButtons: Qt.LeftButton
|
||||
onTapped: if (vid.supportStreaming || progressInfo.completed) {
|
||||
if (vid.playbackState == MediaPlayer.PlayingState) {
|
||||
vid.pause()
|
||||
} else {
|
||||
vid.play()
|
||||
}
|
||||
} else {
|
||||
videoDelegate.downloadAndPlay()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function downloadAndPlay() {
|
||||
if (vid.downloaded) {
|
||||
playSavedFile()
|
||||
} else {
|
||||
playOnFinished = true
|
||||
currentRoom.downloadFile(eventId, Platform.StandardPaths.writableLocation(Platform.StandardPaths.CacheLocation) + "/" + eventId.replace(":", "_").replace("/", "_").replace("+", "_") + currentRoom.fileNameToDownload(eventId))
|
||||
}
|
||||
}
|
||||
|
||||
function playSavedFile() {
|
||||
vid.stop()
|
||||
vid.play()
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
module NeoChat.Component.Timeline
|
||||
RichLabel 1.0 RichLabel.qml
|
||||
TimelineContainer 1.0 TimelineContainer.qml
|
||||
StateDelegate 1.0 StateDelegate.qml
|
||||
SectionDelegate 1.0 SectionDelegate.qml
|
||||
ImageDelegate 1.0 ImageDelegate.qml
|
||||
FileDelegate 1.0 FileDelegate.qml
|
||||
VideoDelegate 1.0 VideoDelegate.qml
|
||||
ReactionDelegate 1.0 ReactionDelegate.qml
|
||||
AudioDelegate 1.0 AudioDelegate.qml
|
||||
EncryptedDelegate 1.0 EncryptedDelegate.qml
|
||||
EventDelegate 1.0 EventDelegate.qml
|
||||
MessageDelegate 1.0 MessageDelegate.qml
|
||||
ReadMarkerDelegate 1.0 ReadMarkerDelegate.qml
|
||||
LinkPreviewDelegate 1.0 LinkPreviewDelegate.qml
|
||||
@@ -1,7 +0,0 @@
|
||||
module NeoChat.Component
|
||||
FullScreenImage 1.0 FullScreenImage.qml
|
||||
ChatTextInput 1.0 ChatTextInput.qml
|
||||
FancyEffectsContainer 1.0 FancyEffectsContainer.qml
|
||||
TypingPane 1.0 TypingPane.qml
|
||||
QuickSwitcher 1.0 QuickSwitcher.qml
|
||||
ShimmerGradient 1.0 ShimmerGradient.qml
|
||||
@@ -1,43 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2019 Black Hat <bhat@encom.eu.org>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
import org.kde.kirigami 2.15 as Kirigami
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
import NeoChat.Component 1.0
|
||||
|
||||
Kirigami.OverlaySheet {
|
||||
id: root
|
||||
|
||||
parent: applicationWindow().overlay
|
||||
|
||||
title: i18n("Create a Room")
|
||||
|
||||
contentItem: Kirigami.FormLayout {
|
||||
TextField {
|
||||
id: roomNameField
|
||||
Kirigami.FormData.label: i18n("Room Name")
|
||||
onAccepted: roomTopicField.forceActiveFocus();
|
||||
}
|
||||
|
||||
TextField {
|
||||
id: roomTopicField
|
||||
Kirigami.FormData.label: i18n("Room Topic")
|
||||
onAccepted: okButton.forceActiveFocus();
|
||||
}
|
||||
|
||||
Button {
|
||||
id: okButton
|
||||
|
||||
text: i18nc("@action:button", "Ok")
|
||||
onClicked: {
|
||||
Controller.createRoom(roomNameField.text, roomTopicField.text);
|
||||
root.close();
|
||||
root.destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu>
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15 as QQC2
|
||||
|
||||
import org.kde.kirigami 2.15 as Kirigami
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
import NeoChat.Component.Emoji 1.0
|
||||
|
||||
QQC2.Popup {
|
||||
id: root
|
||||
|
||||
signal react(string emoji)
|
||||
|
||||
modal: true
|
||||
focus: true
|
||||
closePolicy: QQC2.Popup.CloseOnEscape | QQC2.Popup.CloseOnPressOutsideParent
|
||||
margins: 0
|
||||
padding: Kirigami.Units.smallSpacing
|
||||
implicitWidth: Kirigami.Units.gridUnit * 15
|
||||
implicitHeight: Kirigami.Units.gridUnit * 20
|
||||
|
||||
contentItem: EmojiPicker {
|
||||
onChosen: react(emoji)
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
module NeoChat.Dialog.KeyVerification
|
||||
KeyVerificationDialog 1.0 KeyVerificationDialog.qml
|
||||
Message 1.0 Message.qml
|
||||
VerificationCanceled 1.0 VerificationCanceled.qml
|
||||
EmojiItem 1.0 EmojiItem.qml
|
||||
EmojiRow 1.0 EmojiRow.qml
|
||||
EmojiSas 1.0 EmojiSas.qml
|
||||
@@ -1,176 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2019 Black Hat <bhat@encom.eu.org>
|
||||
// SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15 as QQC2
|
||||
import QtQuick.Layouts 1.15
|
||||
|
||||
import org.kde.kirigami 2.15 as Kirigami
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
import NeoChat.Component 1.0
|
||||
|
||||
Kirigami.OverlaySheet {
|
||||
id: root
|
||||
|
||||
signal closed()
|
||||
|
||||
property var room
|
||||
property var user
|
||||
|
||||
property string displayName: user.displayName
|
||||
property string avatarMediaId: user.avatarMediaId
|
||||
property string avatarUrl: user.avatarUrl
|
||||
|
||||
parent: applicationWindow().overlay
|
||||
|
||||
leftPadding: 0
|
||||
rightPadding: 0
|
||||
topPadding: 0
|
||||
|
||||
title: i18nc("@title:menu Account detail dialog", "Account detail")
|
||||
|
||||
contentItem: ColumnLayout {
|
||||
spacing: 0
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: Kirigami.Units.largeSpacing
|
||||
Layout.rightMargin: Kirigami.Units.largeSpacing
|
||||
Layout.topMargin: Kirigami.Units.smallSpacing
|
||||
Layout.bottomMargin: Kirigami.Units.smallSpacing
|
||||
spacing: Kirigami.Units.largeSpacing
|
||||
|
||||
Kirigami.Avatar {
|
||||
Layout.preferredWidth: Kirigami.Units.iconSizes.huge
|
||||
Layout.preferredHeight: Kirigami.Units.iconSizes.huge
|
||||
|
||||
name: displayName
|
||||
source: avatarMediaId ? ("image://mxc/" + avatarMediaId) : ""
|
||||
color: user.color
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
|
||||
onClicked: {
|
||||
if (avatarMediaId) {
|
||||
fullScreenImage.createObject(parent, {filename: displayName, source: room.urlToMxcUrl(avatarUrl)}).showFullScreen()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
|
||||
Kirigami.Heading {
|
||||
level: 1
|
||||
Layout.fillWidth: true
|
||||
font.bold: true
|
||||
|
||||
elide: Text.ElideRight
|
||||
wrapMode: Text.NoWrap
|
||||
text: room.htmlSafeMemberName(user.id)
|
||||
}
|
||||
|
||||
QQC2.Label {
|
||||
Layout.fillWidth: true
|
||||
|
||||
text: i18n("Online")
|
||||
color: Kirigami.Theme.disabledTextColor
|
||||
}
|
||||
Kirigami.Heading {
|
||||
level: 5
|
||||
text: user.id
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QQC2.MenuSeparator {}
|
||||
|
||||
Kirigami.BasicListItem {
|
||||
visible: user !== room.localUser
|
||||
action: Kirigami.Action {
|
||||
text: room.connection.isIgnored(user) ? i18n("Unignore this user") : i18n("Ignore this user")
|
||||
icon.name: "im-invisible-user"
|
||||
onTriggered: {
|
||||
root.close()
|
||||
room.connection.isIgnored(user) ? room.connection.removeFromIgnoredUsers(user) : room.connection.addToIgnoredUsers(user)
|
||||
}
|
||||
}
|
||||
}
|
||||
Kirigami.BasicListItem {
|
||||
visible: user !== room.localUser && room.canSendState("kick") && room.containsUser(user.id)
|
||||
|
||||
action: Kirigami.Action {
|
||||
text: i18n("Kick this user")
|
||||
icon.name: "im-kick-user"
|
||||
onTriggered: {
|
||||
room.kickMember(user.id)
|
||||
root.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
Kirigami.BasicListItem {
|
||||
visible: user !== room.localUser && room.canSendState("ban") && !room.isUserBanned(user.id)
|
||||
|
||||
action: Kirigami.Action {
|
||||
text: i18n("Ban this user")
|
||||
icon.name: "im-ban-user"
|
||||
icon.color: Kirigami.Theme.negativeTextColor
|
||||
onTriggered: {
|
||||
room.ban(user.id)
|
||||
root.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
Kirigami.BasicListItem {
|
||||
visible: user !== room.localUser && room.canSendState("ban") && room.isUserBanned(user.id)
|
||||
|
||||
action: Kirigami.Action {
|
||||
text: i18n("Unban this user")
|
||||
icon.name: "im-irc"
|
||||
icon.color: Kirigami.Theme.negativeTextColor
|
||||
onTriggered: {
|
||||
room.unban(user.id)
|
||||
root.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
Kirigami.BasicListItem {
|
||||
visible: user === room.localUser || room.canSendState("redact")
|
||||
|
||||
action: Kirigami.Action {
|
||||
text: i18n("Delete recent messages by this user")
|
||||
icon.name: "delete"
|
||||
icon.color: Kirigami.Theme.negativeTextColor
|
||||
onTriggered: {
|
||||
room.deleteMessagesByUser(user.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
Kirigami.BasicListItem {
|
||||
visible: user !== room.localUser
|
||||
action: Kirigami.Action {
|
||||
text: i18n("Open a private chat")
|
||||
icon.name: "document-send"
|
||||
onTriggered: {
|
||||
Controller.openOrCreateDirectChat(user);
|
||||
root.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
Component {
|
||||
id: fullScreenImage
|
||||
|
||||
FullScreenImage {}
|
||||
}
|
||||
}
|
||||
|
||||
onSheetOpenChanged: {
|
||||
if (!sheetOpen) {
|
||||
closed()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
module NeoChat.Dialog
|
||||
UserDetailDialog 1.0 UserDetailDialog.qml
|
||||
LoginDialog 1.0 LoginDialog.qml
|
||||
CreateRoomDialog 1.0 CreateRoomDialog.qml
|
||||
AcceptInvitationDialog 1.0 AcceptInvitationDialog.qml
|
||||
OpenFileDialog 1.0 OpenFileDialog.qml
|
||||
ImageClipboardDialog 1.0 ImageClipboardDialog.qml
|
||||
StartChatDialog 1.0 StartChatDialog.qml
|
||||
EmojiDialog 1.0 EmojiDialog.qml
|
||||
KeyVerificationDialog 1.0 KeyVerificationDialog.qml
|
||||
@@ -1,5 +0,0 @@
|
||||
module NeoChat.Menu.Timeline
|
||||
MessageDelegateContextMenu 1.0 MessageDelegateContextMenu.qml
|
||||
FileDelegateContextMenu 1.0 FileDelegateContextMenu.qml
|
||||
MessageSourceSheet 1.0 MessageSourceSheet.qml
|
||||
ReportSheet 1.0 ReportSheet.qml
|
||||
@@ -1,6 +0,0 @@
|
||||
module NeoChat.Menu
|
||||
RoomListContextMenu 1.0 RoomListContextMenu.qml
|
||||
GlobalMenu 1.0 GlobalMenu.qml
|
||||
EditMenu 1.0 EditMenu.qml
|
||||
ShareAction 1.0 ShareAction.qml
|
||||
ShareDialog 1.0 ShareDialog.qml
|
||||
@@ -1,162 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2019 Black Hat <bhat@encom.eu.org>
|
||||
// SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
import org.kde.kirigami 2.15 as Kirigami
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
import NeoChat.Component 1.0
|
||||
|
||||
Kirigami.ScrollablePage {
|
||||
id: root
|
||||
property var connection
|
||||
|
||||
property alias keyword: identifierField.text
|
||||
property string server
|
||||
|
||||
title: i18n("Explore Rooms")
|
||||
|
||||
Component.onCompleted: identifierField.forceActiveFocus()
|
||||
|
||||
header: Control {
|
||||
padding: Kirigami.Units.largeSpacing
|
||||
contentItem: RowLayout {
|
||||
Kirigami.SearchField {
|
||||
id: identifierField
|
||||
property bool isRoomAlias: text.match(/#(.+):(.+)/g)
|
||||
property var room: isRoomAlias ? connection.roomByAlias(text) : null
|
||||
property bool isJoined: room != null
|
||||
|
||||
Layout.fillWidth: true
|
||||
|
||||
placeholderText: i18n("Find a room...")
|
||||
}
|
||||
|
||||
Button {
|
||||
id: joinButton
|
||||
|
||||
visible: identifierField.isRoomAlias
|
||||
|
||||
text: identifierField.isJoined ? i18n("View") : i18n("Join")
|
||||
highlighted: true
|
||||
|
||||
onClicked: {
|
||||
if (!identifierField.isJoined) {
|
||||
Controller.joinRoom(identifierField.text);
|
||||
// When joining the room, the room will be opened
|
||||
}
|
||||
applicationWindow().pageStack.layers.pop();
|
||||
}
|
||||
}
|
||||
|
||||
ComboBox {
|
||||
Layout.maximumWidth: 120
|
||||
|
||||
id: serverField
|
||||
|
||||
editable: currentIndex == 1
|
||||
|
||||
model: [i18n("Local"), i18n("Global"), "matrix.org"]
|
||||
|
||||
onCurrentIndexChanged: {
|
||||
if (currentIndex == 0) {
|
||||
server = ""
|
||||
} else if (currentIndex == 2) {
|
||||
server = "matrix.org"
|
||||
}
|
||||
}
|
||||
|
||||
Keys.onReturnPressed: {
|
||||
if (currentIndex == 1) {
|
||||
server = editText
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ListView {
|
||||
id: publicRoomsListView
|
||||
clip: true
|
||||
model: PublicRoomListModel {
|
||||
id: publicRoomListModel
|
||||
|
||||
connection: root.connection
|
||||
server: root.server
|
||||
keyword: root.keyword
|
||||
}
|
||||
|
||||
onContentYChanged: {
|
||||
if(publicRoomListModel.hasMore && contentHeight - contentY < publicRoomsListView.height + 200)
|
||||
publicRoomListModel.next();
|
||||
}
|
||||
delegate: Kirigami.AbstractListItem {
|
||||
property bool justJoined: false
|
||||
width: publicRoomsListView.width
|
||||
onClicked: {
|
||||
if (!isJoined) {
|
||||
Controller.joinRoom(roomID)
|
||||
justJoined = true;
|
||||
} else {
|
||||
RoomManager.enterRoom(connection.room(roomID))
|
||||
}
|
||||
applicationWindow().pageStack.layers.pop();
|
||||
}
|
||||
contentItem: RowLayout {
|
||||
Kirigami.Avatar {
|
||||
Layout.preferredWidth: Kirigami.Units.gridUnit * 2
|
||||
Layout.preferredHeight: Kirigami.Units.gridUnit * 2
|
||||
|
||||
source: model.avatar ? ("image://mxc/" + model.avatar) : ""
|
||||
name: name
|
||||
}
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
Kirigami.Heading {
|
||||
Layout.fillWidth: true
|
||||
level: 4
|
||||
text: name
|
||||
font.bold: true
|
||||
textFormat: Text.PlainText
|
||||
elide: Text.ElideRight
|
||||
wrapMode: Text.NoWrap
|
||||
}
|
||||
Label {
|
||||
visible: isJoined || justJoined
|
||||
text: i18n("Joined")
|
||||
color: Kirigami.Theme.linkColor
|
||||
}
|
||||
}
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
visible: text
|
||||
text: topic ? topic.replace(/(\r\n\t|\n|\r\t)/gm," ") : ""
|
||||
textFormat: Text.PlainText
|
||||
elide: Text.ElideRight
|
||||
wrapMode: Text.NoWrap
|
||||
}
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
Kirigami.Icon {
|
||||
source: "user"
|
||||
color: Kirigami.Theme.disabledTextColor
|
||||
implicitHeight: Kirigami.Units.iconSizes.small
|
||||
implicitWidth: Kirigami.Units.iconSizes.small
|
||||
}
|
||||
Label {
|
||||
text: memberCount + " " + (alias ?? roomID)
|
||||
color: Kirigami.Theme.disabledTextColor
|
||||
elide: Text.ElideRight
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,395 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2019 Black Hat <bhat@encom.eu.org>
|
||||
// SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15 as QQC2
|
||||
import QtQuick.Layouts 1.15
|
||||
import QtQml.Models 2.15
|
||||
|
||||
import org.kde.kirigami 2.15 as Kirigami
|
||||
import org.kde.kitemmodels 1.0
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
import NeoChat.Component 1.0
|
||||
import NeoChat.Menu 1.0
|
||||
|
||||
Kirigami.ScrollablePage {
|
||||
|
||||
header: ColumnLayout {
|
||||
visible: !page.collapsedMode
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
spacing: 0
|
||||
|
||||
ListView {
|
||||
id: spaceList
|
||||
property string activeSpaceId: ''
|
||||
|
||||
orientation: Qt.Horizontal
|
||||
spacing: Kirigami.Units.largeSpacing
|
||||
clip:true
|
||||
visible: spaceList.count > 0
|
||||
|
||||
Layout.preferredHeight: Kirigami.Units.gridUnit * 3
|
||||
Layout.fillWidth: true
|
||||
|
||||
model: SortFilterSpaceListModel {
|
||||
id: sortFilterSpaceListModel
|
||||
sourceModel: RoomListModel {
|
||||
id: spaceListModel
|
||||
connection: Controller.activeConnection
|
||||
}
|
||||
}
|
||||
|
||||
header: QQC2.Control {
|
||||
contentItem: QQC2.RoundButton {
|
||||
id: homeButton
|
||||
flat: true
|
||||
padding: Kirigami.Units.gridUnit / 2
|
||||
icon.name: "home"
|
||||
text: i18nc("@action:button", "Show All Rooms")
|
||||
display: QQC2.AbstractButton.IconOnly
|
||||
|
||||
onClicked: {
|
||||
sortFilterRoomListModel.activeSpaceId = "";
|
||||
spaceList.activeSpaceId = '';
|
||||
listView.positionViewAtIndex(0, ListView.Beginning);
|
||||
}
|
||||
|
||||
QQC2.ToolTip {
|
||||
text: homeButton.text
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
delegate: QQC2.Control {
|
||||
required property string avatar
|
||||
required property var currentRoom
|
||||
required property int index
|
||||
required property string id
|
||||
implicitWidth: ListView.view.headerItem.implicitWidth
|
||||
implicitHeight: ListView.view.headerItem.implicitHeight
|
||||
|
||||
contentItem: Kirigami.Avatar {
|
||||
actions.main: Kirigami.Action {
|
||||
onTriggered: {
|
||||
spaceList.activeSpaceId = id;
|
||||
sortFilterRoomListModel.activeSpaceId = id;
|
||||
}
|
||||
}
|
||||
|
||||
QQC2.ToolTip {
|
||||
text: currentRoom.displayName
|
||||
}
|
||||
|
||||
source: avatar !== "" ? "image://mxc/" + avatar : ""
|
||||
}
|
||||
}
|
||||
}
|
||||
Kirigami.Separator {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
id: page
|
||||
|
||||
title: i18n("Rooms")
|
||||
|
||||
property var enteredRoom
|
||||
property bool collapsedMode: Config.roomListPageWidth === applicationWindow().collapsedPageWidth && applicationWindow().shouldUseSidebars
|
||||
|
||||
verticalScrollBarPolicy: collapsedMode ? QQC2.ScrollBar.AlwaysOff : QQC2.ScrollBar.AsNeeded
|
||||
|
||||
onCollapsedModeChanged: if (collapsedMode) {
|
||||
sortFilterRoomListModel.filterText = "";
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: RoomManager
|
||||
function onCurrentRoomChanged() {
|
||||
itemSelection.setCurrentIndex(roomListModel.index(roomListModel.indexForRoom(RoomManager.currentRoom), 0), ItemSelectionModel.SelectCurrent)
|
||||
}
|
||||
}
|
||||
|
||||
function goToNextRoom() {
|
||||
do {
|
||||
listView.incrementCurrentIndex();
|
||||
} while (!listView.currentItem.visible && listView.currentIndex === listView.count)
|
||||
listView.currentItem.action.trigger();
|
||||
}
|
||||
|
||||
function goToPreviousRoom() {
|
||||
do {
|
||||
listView.decrementCurrentIndex();
|
||||
} while (!listView.currentItem.visible && listView.currentIndex !== 0)
|
||||
listView.currentItem.action.trigger();
|
||||
}
|
||||
|
||||
titleDelegate: collapsedMode ? empty : searchField
|
||||
|
||||
Component {
|
||||
id: empty
|
||||
Item {}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: searchField
|
||||
Kirigami.SearchField {
|
||||
Layout.topMargin: Kirigami.Units.smallSpacing
|
||||
Layout.bottomMargin: Kirigami.Units.smallSpacing
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
onTextChanged: sortFilterRoomListModel.filterText = text
|
||||
KeyNavigation.tab: listView
|
||||
}
|
||||
}
|
||||
|
||||
ListView {
|
||||
id: listView
|
||||
|
||||
activeFocusOnTab: true
|
||||
clip: accountList.count > 1
|
||||
|
||||
header: QQC2.ItemDelegate {
|
||||
visible: page.collapsedMode
|
||||
action: Kirigami.Action {
|
||||
id: enterRoomAction
|
||||
onTriggered: quickView.item.open();
|
||||
}
|
||||
topPadding: Kirigami.Units.largeSpacing
|
||||
leftPadding: Kirigami.Units.largeSpacing
|
||||
rightPadding: Kirigami.Units.largeSpacing
|
||||
bottomPadding: Kirigami.Units.largeSpacing
|
||||
width: visible ? page.width : 0
|
||||
height: visible ? Kirigami.Units.gridUnit * 2 : 0
|
||||
|
||||
Kirigami.Icon {
|
||||
anchors.centerIn: parent
|
||||
width: 22
|
||||
height: 22
|
||||
source: "search"
|
||||
}
|
||||
Kirigami.Separator {
|
||||
width: parent.width
|
||||
anchors.bottom: parent.bottom
|
||||
}
|
||||
}
|
||||
|
||||
Layout.fillWidth: true
|
||||
|
||||
Kirigami.PlaceholderMessage {
|
||||
anchors.centerIn: parent
|
||||
width: parent.width - (Kirigami.Units.largeSpacing * 4)
|
||||
visible: listView.count == 0
|
||||
text: sortFilterRoomListModel.filterText.length > 0 ? i18n("No rooms found") : i18n("Join some rooms to get started")
|
||||
helpfulAction: Kirigami.Action {
|
||||
icon.name: sortFilterRoomListModel.filterText.length > 0 ? "search" : "list-add"
|
||||
text: sortFilterRoomListModel.filterText.length > 0 ? i18n("Search in room directory") : i18n("Explore rooms")
|
||||
onTriggered: pageStack.layers.push("qrc:/imports/NeoChat/Page/JoinRoomPage.qml", {
|
||||
connection: Controller.activeConnection,
|
||||
keyword: sortFilterRoomListModel.filterText
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ItemSelectionModel {
|
||||
id: itemSelection
|
||||
model: roomListModel
|
||||
onCurrentChanged: {
|
||||
listView.currentIndex = sortFilterRoomListModel.mapFromSource(current).row
|
||||
}
|
||||
}
|
||||
|
||||
model: SortFilterRoomListModel {
|
||||
id: sortFilterRoomListModel
|
||||
sourceModel: RoomListModel {
|
||||
id: roomListModel
|
||||
connection: Controller.activeConnection
|
||||
}
|
||||
roomSortOrder: Config.mergeRoomList ? SortFilterRoomListModel.LastActivity : SortFilterRoomListModel.Categories
|
||||
onLayoutChanged: {
|
||||
listView.currentIndex = sortFilterRoomListModel.mapFromSource(itemSelection.currentIndex).row
|
||||
}
|
||||
}
|
||||
|
||||
section.property: sortFilterRoomListModel.filterText.length === 0 && !Config.mergeRoomList ? "category" : null
|
||||
section.delegate: Kirigami.ListSectionHeader {
|
||||
id: sectionHeader
|
||||
height: implicitHeight
|
||||
action: Kirigami.Action {
|
||||
onTriggered: roomListModel.setCategoryVisible(section, !roomListModel.categoryVisible(section))
|
||||
}
|
||||
contentItem: RowLayout {
|
||||
implicitHeight: categoryName.implicitHeight
|
||||
Kirigami.Heading {
|
||||
id: categoryName
|
||||
level: 3
|
||||
text: roomListModel.categoryName(section)
|
||||
Layout.fillWidth: true
|
||||
elide: Text.ElideRight
|
||||
visible: !page.collapsedMode
|
||||
}
|
||||
Kirigami.Icon {
|
||||
source: page.collapsedMode ? roomListModel.categoryIconName(section) : (roomListModel.categoryVisible(section) ? "go-up" : "go-down")
|
||||
implicitHeight: Kirigami.Units.iconSizes.small
|
||||
implicitWidth: Kirigami.Units.iconSizes.small
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
reuseItems: true
|
||||
currentIndex: -1 // we don't want any room highlighted by default
|
||||
|
||||
delegate: page.collapsedMode ? collapsedModeListComponent : normalModeListComponent
|
||||
|
||||
Component {
|
||||
id: collapsedModeListComponent
|
||||
|
||||
QQC2.ItemDelegate {
|
||||
action: Kirigami.Action {
|
||||
id: enterRoomAction
|
||||
onTriggered: {
|
||||
RoomManager.enterRoom(currentRoom);
|
||||
}
|
||||
}
|
||||
Keys.onEnterPressed: enterRoomAction.trigger()
|
||||
Keys.onReturnPressed: enterRoomAction.trigger()
|
||||
topPadding: Kirigami.Units.largeSpacing
|
||||
leftPadding: Kirigami.Units.largeSpacing
|
||||
rightPadding: Kirigami.Units.largeSpacing
|
||||
bottomPadding: Kirigami.Units.largeSpacing
|
||||
width: ListView.view.width
|
||||
height: ListView.view.width
|
||||
|
||||
contentItem: Kirigami.Avatar {
|
||||
source: avatar ? "image://mxc/" + avatar : ""
|
||||
name: model.name || i18n("No Name")
|
||||
sourceSize.width: Kirigami.Units.gridUnit + Kirigami.Units.largeSpacing * 2
|
||||
sourceSize.height: Kirigami.Units.gridUnit + Kirigami.Units.largeSpacing * 2
|
||||
}
|
||||
|
||||
QQC2.ToolTip {
|
||||
enabled: text.length !== 0
|
||||
text: name ?? ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: roomListContextMenu
|
||||
RoomListContextMenu {}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: normalModeListComponent
|
||||
Kirigami.BasicListItem {
|
||||
id: roomListItem
|
||||
visible: model.categoryVisible || sortFilterRoomListModel.filterText.length > 0 || Config.mergeRoomList
|
||||
topPadding: Kirigami.Units.largeSpacing
|
||||
bottomPadding: Kirigami.Units.largeSpacing
|
||||
highlighted: listView.currentIndex === index
|
||||
focus: true
|
||||
icon: undefined
|
||||
action: Kirigami.Action {
|
||||
id: enterRoomAction
|
||||
onTriggered: {
|
||||
RoomManager.enterRoom(currentRoom);
|
||||
}
|
||||
}
|
||||
Keys.onEnterPressed: enterRoomAction.trigger()
|
||||
Keys.onReturnPressed: enterRoomAction.trigger()
|
||||
bold: unreadCount > 0
|
||||
label: name ?? ""
|
||||
labelItem.textFormat: Text.PlainText
|
||||
subtitle: subtitleText
|
||||
subtitleItem.textFormat: Text.PlainText
|
||||
onPressAndHold: {
|
||||
createRoomListContextMenu()
|
||||
}
|
||||
TapHandler {
|
||||
acceptedButtons: Qt.RightButton
|
||||
acceptedDevices: PointerDevice.Mouse
|
||||
onTapped: createRoomListContextMenu()
|
||||
}
|
||||
|
||||
leading: Kirigami.Avatar {
|
||||
source: avatar ? "image://mxc/" + avatar : ""
|
||||
name: model.name || i18n("No Name")
|
||||
implicitWidth: visible ? height : 0
|
||||
visible: Config.showAvatarInTimeline
|
||||
sourceSize.width: Kirigami.Units.gridUnit + Kirigami.Units.largeSpacing * 2
|
||||
sourceSize.height: Kirigami.Units.gridUnit + Kirigami.Units.largeSpacing * 2
|
||||
}
|
||||
|
||||
trailing: RowLayout {
|
||||
QQC2.Label {
|
||||
text: notificationCount > 0 ? notificationCount : "●"
|
||||
visible: unreadCount > 0
|
||||
padding: Kirigami.Units.smallSpacing
|
||||
color: Kirigami.Theme.textColor
|
||||
Layout.minimumWidth: height
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
background: Rectangle {
|
||||
visible: notificationCount > 0
|
||||
Kirigami.Theme.colorSet: Kirigami.Theme.Button
|
||||
color: highlightCount > 0 ? Kirigami.Theme.positiveTextColor : Kirigami.Theme.disabledTextColor
|
||||
opacity: highlightCount > 0 ? 1 : 0.3
|
||||
radius: height / 2
|
||||
}
|
||||
}
|
||||
QQC2.Button {
|
||||
id: configButton
|
||||
visible: roomListItem.hovered
|
||||
Accessible.name: i18n("Configure room")
|
||||
|
||||
action: Kirigami.Action {
|
||||
id: optionAction
|
||||
icon.name: "configure"
|
||||
onTriggered: {
|
||||
createRoomListContextMenu()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function createRoomListContextMenu() {
|
||||
const menu = roomListContextMenu.createObject(page, {room: currentRoom})
|
||||
configButton.visible = true
|
||||
configButton.down = true
|
||||
menu.closed.connect(function() {
|
||||
configButton.down = undefined
|
||||
configButton.visible = Qt.binding(function() { return roomListItem.hovered || Kirigami.Settings.isMobile })
|
||||
})
|
||||
menu.open()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
footer: QQC2.ToolBar {
|
||||
visible: accountList.count > 1 && !collapsedMode
|
||||
height: visible ? implicitHeight : 0
|
||||
leftPadding: 0
|
||||
rightPadding: 0
|
||||
topPadding: 0
|
||||
bottomPadding: 0
|
||||
contentItem: RowLayout {
|
||||
spacing: 0
|
||||
Repeater {
|
||||
id: accountList
|
||||
model: AccountRegistry
|
||||
delegate: Kirigami.BasicListItem {
|
||||
checkable: true
|
||||
checked: Controller.activeConnection && Controller.activeConnection.localUserId === model.connection.localUserId
|
||||
onClicked: Controller.activeConnection = model.connection
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
text: model.connection.localUserId
|
||||
subtitle: model.connection.localUser.accountLabel
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,678 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2018-2020 Black Hat <bhat@encom.eu.org>
|
||||
// SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15 as QQC2
|
||||
import QtQuick.Layouts 1.15
|
||||
import Qt.labs.platform 1.1 as Platform
|
||||
import Qt.labs.qmlmodels 1.0
|
||||
|
||||
import org.kde.kirigami 2.19 as Kirigami
|
||||
import org.kde.kitemmodels 1.0
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
import NeoChat.Component 1.0
|
||||
import NeoChat.Component.ChatBox 1.0
|
||||
import NeoChat.Component.Timeline 1.0
|
||||
import NeoChat.Dialog 1.0
|
||||
import NeoChat.Menu.Timeline 1.0
|
||||
|
||||
Kirigami.ScrollablePage {
|
||||
id: page
|
||||
|
||||
/// It's not readonly because of the seperate window view.
|
||||
property var currentRoom: RoomManager.currentRoom
|
||||
property bool loading: page.currentRoom === null || (messageListView.count === 0 && !page.currentRoom.allHistoryLoaded && !page.currentRoom.isInvite)
|
||||
/// Used to determine if scrolling to the bottom should mark the message as unread
|
||||
property bool hasScrolledUpBefore: false;
|
||||
|
||||
/// Disable cancel shortcut. Used by the seperate window since it provide its own
|
||||
/// cancel implementation.
|
||||
property bool disableCancelShortcut: false
|
||||
|
||||
title: currentRoom.htmlSafeDisplayName
|
||||
|
||||
KeyNavigation.left: pageStack.get(0)
|
||||
|
||||
Connections {
|
||||
target: RoomManager
|
||||
function onCurrentRoomChanged() {
|
||||
if(!RoomManager.currentRoom) {
|
||||
if(pageStack.lastItem == page) {
|
||||
pageStack.pop()
|
||||
}
|
||||
} else if (page.currentRoom.isInvite) {
|
||||
page.currentRoom.clearInvitationNotification();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
signal switchRoomUp()
|
||||
signal switchRoomDown()
|
||||
|
||||
onCurrentRoomChanged: {
|
||||
hasScrolledUpBefore = false;
|
||||
chatBoxHelper.clearEditReply()
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: messageEventModel
|
||||
function onRowsInserted() {
|
||||
markReadIfVisibleTimer.restart()
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: markReadIfVisibleTimer
|
||||
interval: 1000
|
||||
onTriggered: {
|
||||
if (loading || !currentRoom.readMarkerLoaded || !applicationWindow().active) {
|
||||
restart()
|
||||
} else {
|
||||
markReadIfVisible()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ActionsHandler {
|
||||
id: actionsHandler
|
||||
room: page.currentRoom
|
||||
connection: Controller.activeConnection
|
||||
}
|
||||
|
||||
ChatBoxHelper {
|
||||
id: chatBoxHelper
|
||||
}
|
||||
|
||||
Shortcut {
|
||||
sequence: StandardKey.Cancel
|
||||
onActivated: applicationWindow().pageStack.get(0).forceActiveFocus()
|
||||
enabled: !page.disableCancelShortcut
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: Controller.activeConnection
|
||||
function onJoinedRoom(room, invited) {
|
||||
if(page.currentRoom.id === invited.id) {
|
||||
RoomManager.enterRoom(room);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: actionsHandler
|
||||
function onShowMessage(messageType, message) {
|
||||
page.header.contentItem.text = message;
|
||||
page.header.contentItem.type = messageType === ActionsHandler.Error ? Kirigami.MessageType.Error : Kirigami.MessageType.Information;
|
||||
page.header.visible = true;
|
||||
}
|
||||
}
|
||||
|
||||
header: QQC2.Control {
|
||||
height: visible ? implicitHeight : 0
|
||||
visible: false
|
||||
padding: Kirigami.Units.smallSpacing
|
||||
contentItem: Kirigami.InlineMessage {
|
||||
showCloseButton: true
|
||||
visible: true
|
||||
}
|
||||
}
|
||||
|
||||
Kirigami.PlaceholderMessage {
|
||||
id: invitation
|
||||
|
||||
visible: currentRoom && currentRoom.isInvite
|
||||
anchors.centerIn: parent
|
||||
text: i18n("Accept this invitation?")
|
||||
RowLayout {
|
||||
QQC2.Button {
|
||||
Layout.alignment : Qt.AlignHCenter
|
||||
text: i18n("Reject")
|
||||
|
||||
onClicked: RoomManager.leaveRoom(page.currentRoom);
|
||||
}
|
||||
|
||||
QQC2.Button {
|
||||
Layout.alignment : Qt.AlignHCenter
|
||||
text: i18n("Accept")
|
||||
|
||||
onClicked: {
|
||||
currentRoom.acceptInvitation();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Kirigami.LoadingPlaceholder {
|
||||
id: loadingIndicator
|
||||
anchors.centerIn: parent
|
||||
visible: loading
|
||||
}
|
||||
|
||||
focus: true
|
||||
|
||||
Keys.onTabPressed: {
|
||||
if (event.modifiers & Qt.ControlModifier) {
|
||||
switchRoomDown();
|
||||
}
|
||||
}
|
||||
|
||||
Keys.onBacktabPressed: {
|
||||
if (event.modifiers & Qt.ControlModifier) {
|
||||
switchRoomUp();
|
||||
}
|
||||
}
|
||||
|
||||
Keys.onPressed: {
|
||||
if (event.key === Qt.Key_PageDown && (event.modifiers & Qt.ControlModifier)) {
|
||||
switchRoomDown();
|
||||
} else if (event.key === Qt.Key_PageUp && (event.modifiers & Qt.ControlModifier)) {
|
||||
switchRoomUp();
|
||||
} else if (!(event.modifiers & Qt.ControlModifier) && event.key < Qt.Key_Escape) {
|
||||
event.accepted = true;
|
||||
chatBox.addText(event.text);
|
||||
chatBox.focusInputField();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: currentRoom
|
||||
function onPositiveMessage(message) {
|
||||
page.header.contentItem.text = message;
|
||||
page.header.contentItem.type = Kirigami.MessageType.Positive;
|
||||
page.header.visible = true;
|
||||
}
|
||||
}
|
||||
|
||||
// hover actions on a delegate, activated in TimelineContainer.qml
|
||||
Connections {
|
||||
target: page.flickable
|
||||
enabled: hoverActions.visible
|
||||
function onContentYChanged() {
|
||||
hoverActions.updateFunction();
|
||||
}
|
||||
}
|
||||
|
||||
CollapseStateProxyModel {
|
||||
id: collapseStateProxyModel
|
||||
sourceModel: sortedMessageEventModel
|
||||
}
|
||||
|
||||
ListView {
|
||||
id: messageListView
|
||||
visible: !invitation.visible
|
||||
|
||||
readonly property int largestVisibleIndex: count > 0 ? indexAt(contentX + (width / 2), contentY + height - 1) : -1
|
||||
readonly property bool isLoaded: page.width * page.height > 10
|
||||
|
||||
spacing: 0
|
||||
|
||||
verticalLayoutDirection: ListView.BottomToTop
|
||||
highlightMoveDuration: 500
|
||||
|
||||
model: !isLoaded ? undefined : collapseStateProxyModel
|
||||
|
||||
MessageEventModel {
|
||||
id: messageEventModel
|
||||
|
||||
room: currentRoom
|
||||
}
|
||||
|
||||
Timer {
|
||||
interval: 1000
|
||||
running: messageListView.atYBeginning
|
||||
triggeredOnStart: true
|
||||
onTriggered: {
|
||||
if (messageListView.atYBeginning && messageEventModel.canFetchMore(messageEventModel.index(0, 0))) {
|
||||
messageEventModel.fetchMore(messageEventModel.index(0, 0));
|
||||
}
|
||||
}
|
||||
repeat: true
|
||||
}
|
||||
|
||||
// HACK: The view should do this automatically but doesn't.
|
||||
onAtYBeginningChanged: if (atYBeginning && messageEventModel.canFetchMore(messageEventModel.index(0, 0))) {
|
||||
messageEventModel.fetchMore(messageEventModel.index(0, 0));
|
||||
}
|
||||
|
||||
onAtYEndChanged: if (atYEnd && hasScrolledUpBefore) {
|
||||
if (QQC2.ApplicationWindow.window && (QQC2.ApplicationWindow.window.visibility !== QQC2.ApplicationWindow.Hidden)) {
|
||||
currentRoom.markAllMessagesAsRead();
|
||||
}
|
||||
hasScrolledUpBefore = false;
|
||||
} else if (!atYEnd) {
|
||||
hasScrolledUpBefore = true;
|
||||
}
|
||||
|
||||
QQC2.Popup {
|
||||
anchors.centerIn: parent
|
||||
|
||||
id: attachDialog
|
||||
|
||||
padding: 16
|
||||
|
||||
contentItem: RowLayout {
|
||||
QQC2.ToolButton {
|
||||
Layout.preferredWidth: 160
|
||||
Layout.fillHeight: true
|
||||
|
||||
icon.name: 'mail-attachment'
|
||||
|
||||
text: i18n("Choose local file")
|
||||
|
||||
onClicked: {
|
||||
attachDialog.close()
|
||||
|
||||
var fileDialog = openFileDialog.createObject(QQC2.ApplicationWindow.overlay)
|
||||
|
||||
fileDialog.chosen.connect(function(path) {
|
||||
if (!path) return
|
||||
|
||||
chatBoxHelper.attachmentPath = path;
|
||||
})
|
||||
|
||||
fileDialog.open()
|
||||
}
|
||||
}
|
||||
|
||||
Kirigami.Separator {}
|
||||
|
||||
QQC2.ToolButton {
|
||||
Layout.preferredWidth: 160
|
||||
Layout.fillHeight: true
|
||||
|
||||
padding: 16
|
||||
|
||||
icon.name: 'insert-image'
|
||||
text: i18n("Clipboard image")
|
||||
onClicked: {
|
||||
const localPath = Platform.StandardPaths.writableLocation(Platform.StandardPaths.CacheLocation) + "/screenshots/" + (new Date()).getTime() + ".png"
|
||||
if (!Clipboard.saveImage(localPath)) {
|
||||
return;
|
||||
}
|
||||
chatBoxHelper.attachmentPath = localPath;
|
||||
attachDialog.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: openFileDialog
|
||||
|
||||
OpenFileDialog {}
|
||||
}
|
||||
|
||||
|
||||
MessageFilterModel {
|
||||
id: sortedMessageEventModel
|
||||
|
||||
sourceModel: messageEventModel
|
||||
}
|
||||
|
||||
delegate: EventDelegate {}
|
||||
|
||||
QQC2.RoundButton {
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: Kirigami.Units.largeSpacing
|
||||
anchors.rightMargin: Kirigami.Units.largeSpacing
|
||||
implicitWidth: Kirigami.Units.gridUnit * 2
|
||||
implicitHeight: Kirigami.Units.gridUnit * 2
|
||||
|
||||
id: goReadMarkerFab
|
||||
|
||||
visible: currentRoom && currentRoom.hasUnreadMessages && currentRoom.readMarkerLoaded
|
||||
action: Kirigami.Action {
|
||||
onTriggered: {
|
||||
messageListView.goToEvent(currentRoom.readMarkerEventId)
|
||||
}
|
||||
icon.name: "go-up"
|
||||
}
|
||||
|
||||
QQC2.ToolTip {
|
||||
text: i18n("Jump to first unread message")
|
||||
}
|
||||
}
|
||||
QQC2.RoundButton {
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: Kirigami.Units.largeSpacing + messageListView.headerItem.height
|
||||
anchors.rightMargin: Kirigami.Units.largeSpacing
|
||||
implicitWidth: Kirigami.Units.gridUnit * 2
|
||||
implicitHeight: Kirigami.Units.gridUnit * 2
|
||||
|
||||
id: goMarkAsReadFab
|
||||
|
||||
visible: !messageListView.atYEnd
|
||||
action: Kirigami.Action {
|
||||
onTriggered: {
|
||||
goToLastMessage();
|
||||
currentRoom.markAllMessagesAsRead();
|
||||
}
|
||||
icon.name: "go-down"
|
||||
}
|
||||
|
||||
QQC2.ToolTip {
|
||||
text: i18n("Jump to latest message")
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
positionViewAtBeginning();
|
||||
}
|
||||
|
||||
DropArea {
|
||||
id: dropAreaFile
|
||||
anchors.fill: parent
|
||||
onDropped: chatBoxHelper.attachmentPath = drop.urls[0]
|
||||
}
|
||||
|
||||
QQC2.Pane {
|
||||
visible: dropAreaFile.containsDrag
|
||||
anchors {
|
||||
fill: parent
|
||||
margins: Kirigami.Units.gridUnit
|
||||
}
|
||||
|
||||
Kirigami.PlaceholderMessage {
|
||||
anchors.centerIn: parent
|
||||
width: parent.width - (Kirigami.Units.largeSpacing * 4)
|
||||
text: i18n("Drag items here to share them")
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: messageDelegateContextMenu
|
||||
|
||||
MessageDelegateContextMenu {}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: fileDelegateContextMenu
|
||||
|
||||
FileDelegateContextMenu {}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: fullScreenImage
|
||||
|
||||
FullScreenImage {}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: userDetailDialog
|
||||
|
||||
UserDetailDialog {}
|
||||
}
|
||||
|
||||
header: TypingPane {
|
||||
id: typingPane
|
||||
visible: !loadingIndicator.visible && currentRoom && currentRoom.usersTyping.length > 0
|
||||
labelText: visible ? i18ncp(
|
||||
"Message displayed when some users are typing", "%2 is typing", "%2 are typing",
|
||||
currentRoom.usersTyping.length,
|
||||
currentRoom.usersTyping.map(user => user.displayName).join(", ")
|
||||
) : ""
|
||||
anchors.left: parent.left
|
||||
height: visible ? implicitHeight : 0
|
||||
Behavior on height {
|
||||
NumberAnimation {
|
||||
property: "height"
|
||||
duration: Kirigami.Units.shortDuration
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
z: 2
|
||||
}
|
||||
headerPositioning: ListView.OverlayHeader
|
||||
|
||||
function goToEvent(eventID) {
|
||||
const index = eventToIndex(eventID)
|
||||
messageListView.positionViewAtIndex(index, ListView.Center)
|
||||
itemAtIndex(index).isTemporaryHighlighted = true
|
||||
}
|
||||
|
||||
Item {
|
||||
id: hoverActions
|
||||
property var event: null
|
||||
property bool userMsg: event && event.author.id === Controller.activeConnection.localUserId
|
||||
property bool showEdit: event && (userMsg && (event.eventType === "emote" || event.eventType === "message"))
|
||||
property var delegate: null
|
||||
property var bubble: null
|
||||
property var hovered: bubble && bubble.hovered
|
||||
property var visibleDelayed: (hovered || hoverHandler.hovered) && !Kirigami.Settings.isMobile
|
||||
onVisibleDelayedChanged: if (visibleDelayed) {
|
||||
visible = true;
|
||||
} else {
|
||||
// HACK: delay disapearing by 200ms, otherwise this can create some glitches
|
||||
// See https://invent.kde.org/network/neochat/-/issues/333
|
||||
hoverActionsTimer.restart();
|
||||
}
|
||||
Timer {
|
||||
id: hoverActionsTimer
|
||||
interval: 200
|
||||
onTriggered: hoverActions.visible = hoverActions.visibleDelayed;
|
||||
}
|
||||
|
||||
property int childOffset: userMsg && Config.showLocalMessagesOnRight && !Config.compactLayout ? (bubble ? bubble.width : 0) - childWidth : Math.max((bubble ? bubble.width : 0) - childWidth, 0)
|
||||
x: delegate && bubble ? (delegate.x + bubble.x + Kirigami.Units.largeSpacing + childOffset - (Config.compactLayout ? Kirigami.Units.gridUnit * 3 + (delegate.width >= Kirigami.Units.gridUnit * 20 ? Kirigami.Units.gridUnit * 2 : 0 ): 0) - (userMsg && !Config.compactLayout ? Kirigami.Units.gridUnit : 0)) : 0
|
||||
y: bubble ? bubble.mapToItem(parent, 0, 0).y - hoverActions.childHeight + Kirigami.Units.smallSpacing: 0;
|
||||
|
||||
visible: false
|
||||
|
||||
property var updateFunction
|
||||
|
||||
property alias childWidth: hoverActionsRow.width
|
||||
property alias childHeight: hoverActionsRow.height
|
||||
|
||||
RowLayout {
|
||||
id: hoverActionsRow
|
||||
z: 4
|
||||
spacing: 0
|
||||
HoverHandler {
|
||||
id: hoverHandler
|
||||
margin: Kirigami.Units.smallSpacing
|
||||
}
|
||||
Kirigami.Icon {
|
||||
source: "security-high"
|
||||
width: height
|
||||
height: parent.height
|
||||
visible: hoverActions.event.verified
|
||||
HoverHandler {
|
||||
id: hover
|
||||
}
|
||||
QQC2.ToolTip.text: i18n("This message was sent from a verified device")
|
||||
QQC2.ToolTip.visible: hover.hovered
|
||||
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
||||
}
|
||||
|
||||
QQC2.Button {
|
||||
QQC2.ToolTip.text: i18n("React")
|
||||
QQC2.ToolTip.visible: hovered
|
||||
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
||||
icon.name: "preferences-desktop-emoticons"
|
||||
onClicked: emojiDialog.open();
|
||||
EmojiDialog {
|
||||
id: emojiDialog
|
||||
onReact: {
|
||||
page.currentRoom.toggleReaction(hoverActions.event.eventId, emoji);
|
||||
chatBox.focusInputField();
|
||||
}
|
||||
}
|
||||
}
|
||||
QQC2.Button {
|
||||
QQC2.ToolTip.text: i18n("Edit")
|
||||
QQC2.ToolTip.visible: hovered
|
||||
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
||||
visible: hoverActions.showEdit
|
||||
icon.name: "document-edit"
|
||||
onClicked: {
|
||||
if (hoverActions.showEdit) {
|
||||
chatBoxHelper.edit(hoverActions.event.message, hoverActions.event.formattedBody, hoverActions.event.eventId)
|
||||
}
|
||||
chatBox.focusInputField();
|
||||
}
|
||||
}
|
||||
QQC2.Button {
|
||||
QQC2.ToolTip.text: i18n("Reply")
|
||||
QQC2.ToolTip.visible: hovered
|
||||
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
||||
icon.name: "mail-replied-symbolic"
|
||||
onClicked: {
|
||||
chatBoxHelper.replyToMessage(hoverActions.event.eventId, hoverActions.event.message, hoverActions.event.author);
|
||||
chatBox.focusInputField();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
footer: ChatBox {
|
||||
id: chatBox
|
||||
visible: !invitation.visible && !(messageListView.count === 0 && !currentRoom.allHistoryLoaded)
|
||||
onMessageSent: {
|
||||
if (!messageListView.atYEnd) {
|
||||
goToLastMessage();
|
||||
}
|
||||
}
|
||||
onEditLastUserMessage: {
|
||||
const targetMessage = messageEventModel.getLastLocalUserMessageEventId();
|
||||
if (targetMessage) {
|
||||
chatBoxHelper.edit(targetMessage["message"], targetMessage["formattedBody"], targetMessage["event_id"]);
|
||||
chatBox.focusInputField();
|
||||
}
|
||||
}
|
||||
onReplyPreviousUserMessage: {
|
||||
const replyResponse = messageEventModel.getLatestMessageFromIndex(0);
|
||||
if (replyResponse && replyResponse["event_id"]) {
|
||||
chatBoxHelper.replyToMessage(replyResponse["event_id"], replyResponse["message"], replyResponse["sender_id"]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
background: FancyEffectsContainer {
|
||||
id: fancyEffectsContainer
|
||||
z: 100
|
||||
|
||||
enabled: Config.showFancyEffects
|
||||
|
||||
function processFancyEffectsReason(fancyEffect) {
|
||||
if (fancyEffect === "snowflake") {
|
||||
fancyEffectsContainer.showSnowEffect()
|
||||
}
|
||||
if (fancyEffect === "fireworks") {
|
||||
fancyEffectsContainer.showFireworksEffect()
|
||||
}
|
||||
if (fancyEffect === "confetti") {
|
||||
fancyEffectsContainer.showConfettiEffect()
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
enabled: Config.showFancyEffects
|
||||
target: messageEventModel
|
||||
function onFancyEffectsReasonFound(fancyEffect) {
|
||||
fancyEffectsContainer.processFancyEffectsReason(fancyEffect)
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
enabled: Config.showFancyEffects
|
||||
target: chatBox
|
||||
function onFancyEffectsReasonFound(fancyEffect) {
|
||||
fancyEffectsContainer.processFancyEffectsReason(fancyEffect)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function warning(title, message) {
|
||||
page.header.contentItem.text = `${title}<br />${message}`;
|
||||
page.header.contentItem.type = Kirigami.MessageType.Warning;
|
||||
page.header.visible = true;
|
||||
}
|
||||
|
||||
function showUserDetail(user) {
|
||||
userDetailDialog.createObject(QQC2.ApplicationWindow.overlay, {
|
||||
room: currentRoom,
|
||||
user: user,
|
||||
}).open();
|
||||
}
|
||||
|
||||
function goToLastMessage() {
|
||||
currentRoom.markAllMessagesAsRead()
|
||||
// scroll to the very end, i.e to messageListView.YEnd
|
||||
messageListView.positionViewAtIndex(0, ListView.End)
|
||||
}
|
||||
|
||||
function eventToIndex(eventID) {
|
||||
const index = messageEventModel.eventIDToIndex(eventID)
|
||||
if (index === -1)
|
||||
return -1
|
||||
return sortedMessageEventModel.mapFromSource(messageEventModel.index(index, 0)).row
|
||||
}
|
||||
|
||||
function firstVisibleIndex() {
|
||||
let center = messageListView.x + messageListView.width / 2;
|
||||
let index = -1
|
||||
let i = 0
|
||||
while(index === -1 && i < 100) {
|
||||
index = messageListView.indexAt(center, messageListView.y + messageListView.contentY + i);
|
||||
i++;
|
||||
}
|
||||
return index
|
||||
}
|
||||
|
||||
function lastVisibleIndex() {
|
||||
let center = messageListView.x + messageListView.width / 2;
|
||||
let index = -1
|
||||
let i = 0
|
||||
while(index === -1 && i < 100) {
|
||||
index = messageListView.indexAt(center, messageListView.y + messageListView.contentY + messageListView.height - i);
|
||||
i++
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
// Mark all messages as read if all unread messages are visible to the user
|
||||
function markReadIfVisible() {
|
||||
let readMarkerRow = eventToIndex(currentRoom.readMarkerEventId)
|
||||
if (readMarkerRow > 0 && readMarkerRow < firstVisibleIndex()) {
|
||||
currentRoom.markAllMessagesAsRead()
|
||||
}
|
||||
}
|
||||
|
||||
/// Open message context dialog for file and videos
|
||||
function openFileContext(event, file) {
|
||||
const contextMenu = fileDelegateContextMenu.createObject(page, {
|
||||
author: event.author,
|
||||
message: event.message,
|
||||
eventId: event.eventId,
|
||||
source: event.source,
|
||||
file: file,
|
||||
mimeType: event.mimeType,
|
||||
progressInfo: event.progressInfo,
|
||||
plainMessage: event.message,
|
||||
});
|
||||
contextMenu.open();
|
||||
}
|
||||
|
||||
/// Open context menu for normal message
|
||||
function openMessageContext(event, selectedText, plainMessage) {
|
||||
const contextMenu = messageDelegateContextMenu.createObject(page, {
|
||||
selectedText: selectedText,
|
||||
author: event.author,
|
||||
message: event.display,
|
||||
eventId: event.eventId,
|
||||
formattedBody: event.formattedBody,
|
||||
source: event.source,
|
||||
eventType: event.eventType,
|
||||
plainMessage: plainMessage,
|
||||
});
|
||||
contextMenu.open();
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15 as QQC2
|
||||
import QtQuick.Layouts 1.15
|
||||
|
||||
import org.kde.kirigami 2.15 as Kirigami
|
||||
|
||||
Kirigami.ApplicationWindow {
|
||||
id: window
|
||||
required property var currentRoom
|
||||
minimumWidth: Kirigami.Units.gridUnit * 10
|
||||
minimumHeight: Kirigami.Units.gridUnit * 15
|
||||
|
||||
Shortcut {
|
||||
sequence: StandardKey.Cancel
|
||||
onActivated: window.close()
|
||||
}
|
||||
pageStack.initialPage: RoomPage {
|
||||
visible: true
|
||||
currentRoom: window.currentRoom
|
||||
disableCancelShortcut: true
|
||||
}
|
||||
}
|
||||
@@ -1,104 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2020 Tobias Fella <fella@posteo.de>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15 as Controls
|
||||
import QtQuick.Layouts 1.15
|
||||
|
||||
import org.kde.kirigami 2.15 as Kirigami
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
import NeoChat.Component.Login 1.0
|
||||
|
||||
Kirigami.ScrollablePage {
|
||||
id: welcomePage
|
||||
|
||||
property alias currentStep: module.item
|
||||
|
||||
title: module.item.title ?? i18n("Welcome")
|
||||
|
||||
header: Controls.Control {
|
||||
contentItem: Kirigami.InlineMessage {
|
||||
id: headerMessage
|
||||
type: Kirigami.MessageType.Error
|
||||
showCloseButton: true
|
||||
visible: false
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: LoginHelper.init()
|
||||
|
||||
Connections {
|
||||
target: LoginHelper
|
||||
function onErrorOccured(message) {
|
||||
headerMessage.text = message;
|
||||
headerMessage.visible = true;
|
||||
headerMessage.type = Kirigami.MessageType.Error;
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: Controller
|
||||
function onInitiated() {
|
||||
pageStack.layers.pop();
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Kirigami.Icon {
|
||||
source: "org.kde.neochat"
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: Kirigami.Units.gridUnit * 16
|
||||
}
|
||||
Controls.Label {
|
||||
Layout.fillWidth: true
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
font.pixelSize: 25
|
||||
text: module.item.message ?? module.item.title ?? i18n("Welcome to Matrix")
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: module
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
source: "qrc:/imports/NeoChat/Component/Login/Login.qml"
|
||||
onSourceChanged: {
|
||||
headerMessage.visible = false
|
||||
headerMessage.text = ""
|
||||
}
|
||||
}
|
||||
RowLayout {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
|
||||
Controls.Button {
|
||||
text: i18nc("@action:button", "Back")
|
||||
|
||||
enabled: welcomePage.currentStep.previousUrl !== ""
|
||||
visible: welcomePage.currentStep.showBackButton
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
onClicked: {
|
||||
module.source = welcomePage.currentStep.previousUrl
|
||||
}
|
||||
}
|
||||
|
||||
Controls.Button {
|
||||
id: continueButton
|
||||
enabled: welcomePage.currentStep.acceptable
|
||||
visible: welcomePage.currentStep.showContinueButton
|
||||
action: welcomePage.currentStep.action
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: currentStep
|
||||
|
||||
function onProcessed(nextUrl) {
|
||||
module.source = nextUrl;
|
||||
}
|
||||
function onShowMessage(message) {
|
||||
headerMessage.text = message;
|
||||
headerMessage.visible = true;
|
||||
headerMessage.type = Kirigami.MessageType.Information;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
module NeoChat.Page
|
||||
LoadingPage 1.0 LoadingPage.qml
|
||||
RoomListPage 1.0 RoomListPage.qml
|
||||
RoomPage 1.0 RoomPage.qml
|
||||
RoomWindow 1.0 RoomWindow.qml
|
||||
JoinRoomPage 1.0 JoinRoomPage.qml
|
||||
InviteUserPage 1.0 InviteUserPage.qml
|
||||
ImageEditorPage 1.0 ImageEditorPage.qml
|
||||
|
||||
@@ -1,339 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2018-2019 Black Hat <bhat@encom.eu.org>
|
||||
// SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
|
||||
import org.kde.kirigami 2.15 as Kirigami
|
||||
import org.kde.kitemmodels 1.0
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
import NeoChat.Component 1.0
|
||||
import NeoChat.Dialog 1.0
|
||||
|
||||
|
||||
Kirigami.OverlayDrawer {
|
||||
id: roomDrawer
|
||||
readonly property var room: RoomManager.currentRoom
|
||||
|
||||
width: actualWidth
|
||||
|
||||
readonly property int minWidth: Kirigami.Units.gridUnit * 15
|
||||
readonly property int maxWidth: Kirigami.Units.gridUnit * 25
|
||||
readonly property int defaultWidth: Kirigami.Units.gridUnit * 20
|
||||
property int actualWidth: {
|
||||
if (Config.roomDrawerWidth === -1) {
|
||||
return Kirigami.Units.gridUnit * 20;
|
||||
} else {
|
||||
return Config.roomDrawerWidth
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.left: parent.left
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.right: undefined
|
||||
width: 2
|
||||
z: 500
|
||||
cursorShape: !Kirigami.Settings.isMobile ? Qt.SplitHCursor : undefined
|
||||
enabled: true
|
||||
visible: true
|
||||
onPressed: _lastX = mapToGlobal(mouseX, mouseY).x
|
||||
onReleased: {
|
||||
Config.roomDrawerWidth = roomDrawer.actualWidth;
|
||||
Config.save();
|
||||
}
|
||||
property real _lastX: -1
|
||||
|
||||
onPositionChanged: {
|
||||
if (_lastX === -1) {
|
||||
return;
|
||||
}
|
||||
if (Qt.application.layoutDirection === Qt.RightToLeft) {
|
||||
roomDrawer.actualWidth = Math.min(roomDrawer.maxWidth, Math.max(roomDrawer.minWidth, Config.roomDrawerWidth - _lastX + mapToGlobal(mouseX, mouseY).x))
|
||||
} else {
|
||||
roomDrawer.actualWidth = Math.min(roomDrawer.maxWidth, Math.max(roomDrawer.minWidth, Config.roomDrawerWidth + _lastX - mapToGlobal(mouseX, mouseY).x))
|
||||
}
|
||||
}
|
||||
}
|
||||
enabled: true
|
||||
|
||||
edge: Qt.application.layoutDirection == Qt.RightToLeft ? Qt.LeftEdge : Qt.RightEdge
|
||||
|
||||
// If modal has been changed and the drawer is closed automatically then dim on popup open will have been switched off in main.qml so switch it back on after the animation completes.
|
||||
// This is to avoid dim being active for a split second when the drawer is switched to modal which looks terrible.
|
||||
onAnimatingChanged: if (dim === false) dim = undefined
|
||||
|
||||
topPadding: 0
|
||||
leftPadding: 0
|
||||
rightPadding: 0
|
||||
Kirigami.Theme.colorSet: Kirigami.Theme.View
|
||||
contentItem: Loader {
|
||||
id: loader
|
||||
active: roomDrawer.drawerOpen
|
||||
sourceComponent: ColumnLayout {
|
||||
id: columnLayout
|
||||
property alias userSearchText: userListSearchField.text
|
||||
property alias highlightedUser: userListView.currentIndex
|
||||
spacing: 0
|
||||
|
||||
Kirigami.AbstractApplicationHeader {
|
||||
Layout.fillWidth: true
|
||||
topPadding: Kirigami.Units.smallSpacing / 2;
|
||||
bottomPadding: Kirigami.Units.smallSpacing / 2;
|
||||
rightPadding: Kirigami.Units.largeSpacing
|
||||
leftPadding: Kirigami.Units.largeSpacing
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
spacing: Kirigami.Units.smallSpacing
|
||||
|
||||
Kirigami.Heading {
|
||||
Layout.fillWidth: true
|
||||
text: i18n("Room information")
|
||||
level: 1
|
||||
}
|
||||
ToolButton {
|
||||
id: inviteButton
|
||||
|
||||
Layout.alignment: Qt.AlignRight
|
||||
icon.name: "list-add-user"
|
||||
text: i18n("Invite user to room")
|
||||
display: AbstractButton.IconOnly
|
||||
|
||||
onClicked: {
|
||||
applicationWindow().pageStack.layers.push("qrc:/imports/NeoChat/Page/InviteUserPage.qml", {room: room})
|
||||
roomDrawer.close();
|
||||
}
|
||||
|
||||
ToolTip {
|
||||
text: inviteButton.text
|
||||
}
|
||||
}
|
||||
ToolButton {
|
||||
id: favouriteButton
|
||||
|
||||
Layout.alignment: Qt.AlignRight
|
||||
icon.name: room && room.isFavourite ? "rating" : "rating-unrated"
|
||||
checkable: true
|
||||
checked: room && room.isFavourite
|
||||
text: room && room.isFavourite ? i18n("Remove room from favorites") : i18n("Make room favorite")
|
||||
display: AbstractButton.IconOnly
|
||||
|
||||
onClicked: room.isFavourite ? room.removeTag("m.favourite") : room.addTag("m.favourite", 1.0)
|
||||
|
||||
ToolTip {
|
||||
text: favouriteButton.text
|
||||
}
|
||||
}
|
||||
ToolButton {
|
||||
id: settingsButton
|
||||
|
||||
Layout.alignment: Qt.AlignRight
|
||||
icon.name: 'settings-configure'
|
||||
text: i18n("Room settings")
|
||||
display: AbstractButton.IconOnly
|
||||
|
||||
onClicked: ApplicationWindow.window.pageStack.pushDialogLayer('qrc:/imports/NeoChat/RoomSettings/Categories.qml', {room: room})
|
||||
|
||||
ToolTip {
|
||||
text: settingsButton.text
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.margins: Kirigami.Units.largeSpacing
|
||||
spacing: Kirigami.Units.largeSpacing
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: Kirigami.Units.smallSpacing
|
||||
spacing: Kirigami.Units.largeSpacing
|
||||
|
||||
Kirigami.Avatar {
|
||||
Layout.preferredWidth: Kirigami.Units.gridUnit * 3.5
|
||||
Layout.preferredHeight: Kirigami.Units.gridUnit * 3.5
|
||||
|
||||
name: room ? room.name : i18n("No name")
|
||||
source: room ? ("image://mxc/" + room.avatarMediaId) : ""
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
spacing: 0
|
||||
|
||||
Kirigami.Heading {
|
||||
Layout.fillWidth: true
|
||||
level: 1
|
||||
type: Kirigami.Heading.Type.Primary
|
||||
wrapMode: Label.Wrap
|
||||
text: room ? room.displayName : i18n("No name")
|
||||
textFormat: Text.PlainText
|
||||
}
|
||||
TextEdit {
|
||||
Layout.fillWidth: true
|
||||
textFormat: TextEdit.PlainText
|
||||
wrapMode: Text.WordWrap
|
||||
selectByMouse: true
|
||||
color: Kirigami.Theme.textColor
|
||||
selectedTextColor: Kirigami.Theme.highlightedTextColor
|
||||
selectionColor: Kirigami.Theme.highlightColor
|
||||
readOnly: true
|
||||
text: room && room.canonicalAlias ? room.canonicalAlias : i18n("No Canonical Alias")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TextEdit {
|
||||
Layout.fillWidth: true
|
||||
text: room && room.topic ? room.topic.replace(replaceLinks, "<a href=\"$1\">$1</a>") : i18n("No Topic")
|
||||
readonly property var replaceLinks: /(https:\/\/[^ ]*)/
|
||||
textFormat: TextEdit.MarkdownText
|
||||
wrapMode: Text.WordWrap
|
||||
selectByMouse: true
|
||||
color: Kirigami.Theme.textColor
|
||||
selectedTextColor: Kirigami.Theme.highlightedTextColor
|
||||
selectionColor: Kirigami.Theme.highlightColor
|
||||
onLinkActivated: UrlHelper.openUrl(link)
|
||||
readOnly: true
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.NoButton
|
||||
cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.IBeamCursor
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Kirigami.ListSectionHeader {
|
||||
label: i18n("Members")
|
||||
activeFocusOnTab: false
|
||||
|
||||
Label {
|
||||
Layout.alignment: Qt.AlignRight
|
||||
text: room ? i18np("%1 Member", "%1 Members", room.joinedCount) : i18n("No Member Count")
|
||||
}
|
||||
}
|
||||
|
||||
Control {
|
||||
Layout.fillWidth: true
|
||||
|
||||
// Note need to set padding individually to guarantee it will always work
|
||||
// see note - https://doc.qt.io/qt-6/qml-qtquick-controls2-control.html#padding-prop
|
||||
topPadding: Kirigami.Units.smallSpacing
|
||||
bottomPadding: Kirigami.Units.smallSpacing
|
||||
rightPadding: Kirigami.Units.largeSpacing
|
||||
leftPadding: Kirigami.Units.largeSpacing
|
||||
|
||||
background: Rectangle {
|
||||
color: Kirigami.Theme.backgroundColor
|
||||
Kirigami.Theme.inherit: false
|
||||
Kirigami.Theme.colorSet: Kirigami.Theme.Window
|
||||
}
|
||||
contentItem: Kirigami.SearchField {
|
||||
id: userListSearchField
|
||||
|
||||
onAccepted: sortedMessageEventModel.filterString = text;
|
||||
}
|
||||
}
|
||||
|
||||
ScrollView {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
|
||||
// HACK: Hide unnecessary horizontal scrollbar (https://bugreports.qt.io/browse/QTBUG-83890)
|
||||
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
|
||||
|
||||
ListView {
|
||||
id: userListView
|
||||
clip: true
|
||||
activeFocusOnTab: true
|
||||
|
||||
model: KSortFilterProxyModel {
|
||||
id: sortedMessageEventModel
|
||||
|
||||
sourceModel: UserListModel {
|
||||
room: roomDrawer.room
|
||||
}
|
||||
|
||||
sortRole: "perm"
|
||||
filterRole: "name"
|
||||
filterCaseSensitivity: Qt.CaseInsensitive
|
||||
}
|
||||
|
||||
delegate: Kirigami.BasicListItem {
|
||||
id: userListItem
|
||||
|
||||
implicitHeight: Kirigami.Units.gridUnit * 2
|
||||
leftPadding: Kirigami.Units.largeSpacing + Kirigami.Units.smallSpacing
|
||||
|
||||
label: name
|
||||
|
||||
onClicked: {
|
||||
const popup = userDetailDialog.createObject(ApplicationWindow.overlay, {room: room, user: user, displayName: name, avatarMediaId: avatar})
|
||||
popup.closed.connect(function() {
|
||||
userListItem.highlighted = false
|
||||
})
|
||||
if (roomDrawer.modal) {
|
||||
roomDrawer.close()
|
||||
}
|
||||
popup.open()
|
||||
}
|
||||
|
||||
leading: Kirigami.Avatar {
|
||||
implicitWidth: height
|
||||
sourceSize.height: Kirigami.Units.gridUnit + Kirigami.Units.smallSpacing * 2.5
|
||||
sourceSize.width: Kirigami.Units.gridUnit + Kirigami.Units.smallSpacing * 2.5
|
||||
source: avatar ? ("image://mxc/" + avatar) : ""
|
||||
name: name
|
||||
}
|
||||
|
||||
trailing: Label {
|
||||
visible: perm != UserType.Member
|
||||
|
||||
text: {
|
||||
switch (perm) {
|
||||
case UserType.Owner:
|
||||
return i18n("Owner");
|
||||
case UserType.Admin:
|
||||
return i18n("Admin");
|
||||
case UserType.Moderator:
|
||||
return i18n("Mod");
|
||||
case UserType.Muted:
|
||||
return i18n("Muted");
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
color: Kirigami.Theme.disabledTextColor
|
||||
textFormat: Text.PlainText
|
||||
wrapMode: Text.NoWrap
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onRoomChanged: {
|
||||
if (loader.active) {
|
||||
loader.item.userSearchText = ""
|
||||
loader.item.highlightedUser = -1
|
||||
}
|
||||
if (room == null) {
|
||||
close()
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: userDetailDialog
|
||||
|
||||
UserDetailDialog {}
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
module NeoChat.Panel
|
||||
RoomDrawer 1.0 RoomDrawer.qml
|
||||
@@ -1,202 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2019-2020 Black Hat <bhat@encom.eu.org>
|
||||
// SPDX-FileCopyrightText: 2021 Carl Schwan <carl@carlschwan.eu>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
import org.kde.kirigami 2.15 as Kirigami
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
import NeoChat.Component 1.0
|
||||
import NeoChat.Dialog 1.0
|
||||
|
||||
Kirigami.ScrollablePage {
|
||||
id: root
|
||||
|
||||
property var room
|
||||
|
||||
readonly property bool canChangeAvatar: room.canSendState("m.room.avatar")
|
||||
readonly property bool canChangeName: room.canSendState("m.room.name")
|
||||
readonly property bool canChangeTopic: room.canSendState("m.room.topic")
|
||||
readonly property bool canChangeCanonicalAlias: room.canSendState("m.room.canonical_alias")
|
||||
|
||||
title: i18n("General")
|
||||
|
||||
ColumnLayout {
|
||||
Kirigami.FormLayout {
|
||||
Layout.fillWidth: true
|
||||
|
||||
Kirigami.Avatar {
|
||||
Layout.bottomMargin: Kirigami.Units.largeSpacing
|
||||
|
||||
name: room.name
|
||||
source: room.avatarMediaId ? ("image://mxc/" + room.avatarMediaId) : ""
|
||||
|
||||
RoundButton {
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
height: Kirigami.Units.gridUnits
|
||||
width: Kirigami.Units.gridUnits
|
||||
icon.name: 'cloud-upload'
|
||||
Accessible.name: i18n("Update avatar")
|
||||
enabled: canChangeAvatar
|
||||
onClicked: {
|
||||
const fileDialog = openFileDialog.createObject(ApplicationWindow.overlay)
|
||||
|
||||
fileDialog.chosen.connect(function(path) {
|
||||
if (!path) return
|
||||
|
||||
room.changeAvatar(path)
|
||||
})
|
||||
|
||||
fileDialog.open()
|
||||
}
|
||||
}
|
||||
}
|
||||
TextField {
|
||||
id: roomNameField
|
||||
text: room.name
|
||||
Kirigami.FormData.label: i18n("Room Name:")
|
||||
enabled: canChangeName
|
||||
}
|
||||
|
||||
TextArea {
|
||||
id: roomTopicField
|
||||
Layout.fillWidth: true
|
||||
text: room.topic
|
||||
Kirigami.FormData.label: i18n("Room topic:")
|
||||
enabled: canChangeTopic
|
||||
}
|
||||
|
||||
|
||||
Kirigami.Separator {
|
||||
Layout.fillWidth: true
|
||||
visible: canonicalAliasComboBox.visible || altAlias.visible
|
||||
}
|
||||
|
||||
ComboBox {
|
||||
id: canonicalAliasComboBox
|
||||
visible: room.aliases && room.aliases.length
|
||||
Kirigami.FormData.label: i18n("Canonical Alias:")
|
||||
popup.z: 999; // HACK This is an absolute hack, but combos inside OverlaySheets have their popups show up underneath, because of fun z ordering stuff
|
||||
|
||||
enabled: canChangeCanonicalAlias
|
||||
|
||||
model: room.aliases
|
||||
|
||||
currentIndex: room.aliases.indexOf(room.canonicalAlias)
|
||||
onCurrentIndexChanged: {
|
||||
if (room.canonicalAlias != room.aliases[currentIndex]) {
|
||||
room.setCanonicalAlias(room.aliases[currentIndex])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: altAlias
|
||||
Kirigami.FormData.label: i18n("Other Aliases:")
|
||||
Layout.fillWidth: true
|
||||
|
||||
visible: room.altAliases && room.altAliases.length
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
|
||||
spacing: 0
|
||||
|
||||
Repeater {
|
||||
model: room.altAliases
|
||||
|
||||
delegate: RowLayout {
|
||||
Layout.maximumWidth: parent.width
|
||||
|
||||
Label {
|
||||
text: modelData
|
||||
}
|
||||
|
||||
ToolButton {
|
||||
icon.name: ""
|
||||
onClicked: room.removeLocalAlias(modelData)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Kirigami.Separator {
|
||||
Layout.fillWidth: true
|
||||
visible: next.visible || prev.visible
|
||||
}
|
||||
|
||||
Control {
|
||||
id: next
|
||||
Layout.fillWidth: true
|
||||
|
||||
visible: room.predecessorId && room.connection.room(room.predecessorId)
|
||||
|
||||
padding: Kirigami.Units.largeSpacing
|
||||
|
||||
contentItem: Kirigami.InlineMessage {
|
||||
text: i18n("This room continues another conversation.")
|
||||
actions: Kirigami.Action {
|
||||
text: i18n("See older messages...")
|
||||
onTriggered: {
|
||||
roomListForm.enteredRoom = Controller.activeConnection.room(room.predecessorId)
|
||||
root.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Control {
|
||||
id: prev
|
||||
Layout.fillWidth: true
|
||||
|
||||
visible: room.successorId && room.connection.room(room.successorId)
|
||||
|
||||
padding: Kirigami.Units.largeSpacing
|
||||
|
||||
contentItem: Kirigami.InlineMessage {
|
||||
text: i18n("This room has been replaced.")
|
||||
actions: Kirigami.Action {
|
||||
text: i18n("See new room...")
|
||||
onTriggered: {
|
||||
roomListForm.enteredRoom = Controller.activeConnection.room(room.successorId)
|
||||
root.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: openFileDialog
|
||||
|
||||
OpenFileDialog {}
|
||||
}
|
||||
}
|
||||
|
||||
footer: ToolBar {
|
||||
contentItem: RowLayout {
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
Button {
|
||||
Layout.alignment: Qt.AlignRight
|
||||
enabled: room.name !== roomNameField.text || room.topic !== roomTopicField.text
|
||||
text: i18n("Apply")
|
||||
onClicked: {
|
||||
if (room.name != roomNameField.text) {
|
||||
room.setName(roomNameField.text)
|
||||
}
|
||||
|
||||
if (room.topic != roomTopicField.text) {
|
||||
room.setTopic(roomTopicField.text)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2022 James Graham <james.h.graham@protonmail.com>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15 as QQC2
|
||||
import QtQuick.Layouts 1.15
|
||||
|
||||
import org.kde.kirigami 2.15 as Kirigami
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
|
||||
Kirigami.ScrollablePage {
|
||||
|
||||
property var room
|
||||
|
||||
title: i18nc('@title:window', 'Notifications')
|
||||
|
||||
ColumnLayout {
|
||||
Kirigami.FormLayout {
|
||||
Layout.fillWidth: true
|
||||
|
||||
QQC2.RadioButton {
|
||||
text: i18n("Follow global setting")
|
||||
Kirigami.FormData.label: i18n("Room notifications setting:")
|
||||
checked: room.pushNotificationState === PushNotificationState.Default
|
||||
enabled: room.pushNotificationState != PushNotificationState.Unknown
|
||||
onToggled: {
|
||||
room.pushNotificationState = PushNotificationState.Default
|
||||
}
|
||||
}
|
||||
QQC2.RadioButton {
|
||||
text: i18nc("As in 'notify for all messages'","All")
|
||||
checked: room.pushNotificationState === PushNotificationState.All
|
||||
enabled: room.pushNotificationState != PushNotificationState.Unknown
|
||||
onToggled: {
|
||||
room.pushNotificationState = PushNotificationState.All
|
||||
}
|
||||
}
|
||||
QQC2.RadioButton {
|
||||
text: i18nc("As in 'notify when the user is mentioned or the message contains a set keyword'","@Mentions and Keywords")
|
||||
checked: room.pushNotificationState === PushNotificationState.MentionKeyword
|
||||
enabled: room.pushNotificationState != PushNotificationState.Unknown
|
||||
onToggled: {
|
||||
room.pushNotificationState = PushNotificationState.MentionKeyword
|
||||
}
|
||||
}
|
||||
QQC2.RadioButton {
|
||||
text: i18nc("As in 'do not notify for any messages'","Off")
|
||||
checked: room.pushNotificationState === PushNotificationState.Mute
|
||||
enabled: room.pushNotificationState != PushNotificationState.Unknown
|
||||
onToggled: {
|
||||
room.pushNotificationState = PushNotificationState.Mute
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2019-2020 Black Hat <bhat@encom.eu.org>
|
||||
// SPDX-FileCopyrightText: 2021 Carl Schwan <carl@carlschwan.eu>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
import org.kde.kirigami 2.15 as Kirigami
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
import NeoChat.Component 1.0
|
||||
import NeoChat.Dialog 1.0
|
||||
|
||||
Kirigami.ScrollablePage {
|
||||
id: root
|
||||
|
||||
property var room
|
||||
|
||||
title: i18n("Security")
|
||||
|
||||
ColumnLayout {
|
||||
Kirigami.FormLayout {
|
||||
Layout.fillWidth: true
|
||||
|
||||
RadioButton {
|
||||
text: i18nc("@option:check", "Private (invite only)")
|
||||
Kirigami.FormData.label: i18nc("@option:check", "Access:")
|
||||
checked: room.joinRule === "invite"
|
||||
enabled: false
|
||||
}
|
||||
Label {
|
||||
text: i18n("Only invited people can join.")
|
||||
font: Kirigami.Theme.smallFont
|
||||
}
|
||||
RadioButton {
|
||||
text: i18nc("@option:check", "Space members")
|
||||
checked: room.joinRule === "restricted"
|
||||
enabled: false
|
||||
}
|
||||
Label {
|
||||
text: i18n("Anyone in a space can find and join.")
|
||||
font: Kirigami.Theme.smallFont
|
||||
}
|
||||
RadioButton {
|
||||
text: i18nc("@option:check", "Public")
|
||||
checked: room.joinRule === "public"
|
||||
enabled: false
|
||||
}
|
||||
Label {
|
||||
text: i18nc("@option:check", "Anyone can find and join.") + room.joinRule
|
||||
font: Kirigami.Theme.smallFont
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
footer: ToolBar {
|
||||
contentItem: RowLayout {
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
Button {
|
||||
Layout.alignment: Qt.AlignRight
|
||||
enabled: false
|
||||
text: i18n("Apply")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,130 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2020 Tobias Fella <fella@posteo.de>
|
||||
// SPDX-FileCopyrightText: 2022 Carl Schwan <carl@carlschwan.eu>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15 as Controls
|
||||
import QtQuick.Layouts 1.15
|
||||
import Qt.labs.platform 1.1
|
||||
|
||||
import org.kde.kirigami 2.15 as Kirigami
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
import NeoChat.Dialog 1.0
|
||||
|
||||
Kirigami.ScrollablePage {
|
||||
id: root
|
||||
title: i18n("Edit Account")
|
||||
property var connection
|
||||
|
||||
ColumnLayout {
|
||||
Kirigami.FormLayout {
|
||||
RowLayout {
|
||||
Kirigami.Avatar {
|
||||
id: avatar
|
||||
source: root.connection && root.connection.localUser.avatarMediaId ? ("image://mxc/" + root.connection.localUser.avatarMediaId) : ""
|
||||
|
||||
MouseArea {
|
||||
id: mouseArea
|
||||
anchors.fill: parent
|
||||
property var fileDialog: null;
|
||||
onClicked: {
|
||||
if (fileDialog != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
fileDialog = openFileDialog.createObject(Controls.ApplicationWindow.Overlay)
|
||||
|
||||
fileDialog.chosen.connect(function(receivedSource) {
|
||||
mouseArea.fileDialog = null;
|
||||
if (!receivedSource) {
|
||||
return;
|
||||
}
|
||||
parent.source = receivedSource;
|
||||
});
|
||||
fileDialog.onRejected.connect(function() {
|
||||
mouseArea.fileDialog = null;
|
||||
});
|
||||
fileDialog.open();
|
||||
}
|
||||
}
|
||||
}
|
||||
Controls.Button {
|
||||
visible: avatar.source.toString().length !== 0
|
||||
icon.name: "edit-clear"
|
||||
|
||||
onClicked: avatar.source = ""
|
||||
}
|
||||
Kirigami.FormData.label: i18n("Avatar:")
|
||||
}
|
||||
Controls.TextField {
|
||||
id: name
|
||||
text: root.connection ? root.connection.localUser.displayName : ""
|
||||
Kirigami.FormData.label: i18n("Name:")
|
||||
}
|
||||
Controls.TextField {
|
||||
id: accountLabel
|
||||
text: root.connection ? root.connection.localUser.accountLabel : ""
|
||||
Kirigami.FormData.label: i18n("Label:")
|
||||
}
|
||||
Controls.TextField {
|
||||
id: currentPassword
|
||||
Kirigami.FormData.label: i18n("Current Password:")
|
||||
enabled: roto.connection !== undefined && root.connection.canChangePassword !== false
|
||||
echoMode: TextInput.Password
|
||||
}
|
||||
Controls.TextField {
|
||||
id: newPassword
|
||||
Kirigami.FormData.label: i18n("New Password:")
|
||||
enabled: root.connection !== undefined && root.connection.canChangePassword !== false
|
||||
echoMode: TextInput.Password
|
||||
|
||||
}
|
||||
Controls.TextField {
|
||||
id: confirmPassword
|
||||
Kirigami.FormData.label: i18n("Confirm new Password:")
|
||||
enabled: root.connection !== undefined && root.connection.canChangePassword !== false
|
||||
echoMode: TextInput.Password
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
footer: RowLayout {
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
Controls.Button {
|
||||
text: i18n("Save")
|
||||
Layout.bottomMargin: Kirigami.Units.smallSpacing
|
||||
Layout.topMargin: Kirigami.Units.smallSpacing
|
||||
onClicked: {
|
||||
if (!Controller.setAvatar(root.connection, avatar.source)) {
|
||||
showPassiveNotification("The Avatar could not be set");
|
||||
}
|
||||
if (root.connection.localUser.displayName !== name.text) {
|
||||
root.connection.localUser.rename(name.text);
|
||||
}
|
||||
if (root.connection.localUser.accountLabel !== accountLabel.text) {
|
||||
root.connection.localUser.setAccountLabel(accountLabel.text);
|
||||
}
|
||||
if(currentPassword.text !== "" && newPassword.text !== "" && confirmPassword.text !== "") {
|
||||
if(newPassword.text === confirmPassword.text) {
|
||||
Controller.changePassword(root.connection, currentPassword.text, newPassword.text);
|
||||
} else {
|
||||
showPassiveNotification(i18n("Passwords do not match"));
|
||||
return;
|
||||
}
|
||||
}
|
||||
root.closeDialog();
|
||||
}
|
||||
}
|
||||
Controls.Button {
|
||||
text: i18n("Cancel")
|
||||
Layout.rightMargin: Kirigami.Units.smallSpacing
|
||||
Layout.bottomMargin: Kirigami.Units.smallSpacing
|
||||
Layout.topMargin: Kirigami.Units.smallSpacing
|
||||
onClicked: root.closeDialog();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,113 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2020 Tobias Fella <fella@posteo.de>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15 as Controls
|
||||
import QtQuick.Layouts 1.15
|
||||
import Qt.labs.platform 1.1
|
||||
|
||||
import org.kde.kirigami 2.15 as Kirigami
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
import NeoChat.Dialog 1.0
|
||||
|
||||
Kirigami.ScrollablePage {
|
||||
title: i18n("Accounts")
|
||||
|
||||
actions.main: Kirigami.Action {
|
||||
text: i18n("Add an account")
|
||||
icon.name: "list-add-user"
|
||||
onTriggered: pageStack.layers.push("qrc:/imports/NeoChat/Page/WelcomePage.qml")
|
||||
visible: !pageSettingStack.wideMode
|
||||
}
|
||||
|
||||
ListView {
|
||||
model: AccountRegistry
|
||||
anchors.fill: parent
|
||||
delegate: Kirigami.BasicListItem {
|
||||
text: model.connection.localUser.displayName
|
||||
labelItem.textFormat: Text.PlainText
|
||||
subtitle: model.connection.localUserId
|
||||
icon: model.connection.localUser.avatarMediaId ? ("image://mxc/" + model.connection.localUser.avatarMediaId) : "im-user"
|
||||
|
||||
onClicked: {
|
||||
Controller.activeConnection = model.connection;
|
||||
pageStack.layers.pop();
|
||||
}
|
||||
|
||||
trailing: RowLayout {
|
||||
Controls.ToolButton {
|
||||
display: Controls.AbstractButton.IconOnly
|
||||
Controls.ToolTip {
|
||||
text: parent.action.text
|
||||
}
|
||||
action: Kirigami.Action {
|
||||
text: i18n("Edit this account")
|
||||
iconName: "document-edit"
|
||||
onTriggered: pageSettingStack.pushDialogLayer(Qt.resolvedUrl('./AccountEditorPage.qml'), {
|
||||
connection: model.connection
|
||||
}, {
|
||||
title: i18n("Account editor")
|
||||
});
|
||||
}
|
||||
}
|
||||
Controls.ToolButton {
|
||||
display: Controls.AbstractButton.IconOnly
|
||||
Controls.ToolTip {
|
||||
text: parent.action.text
|
||||
}
|
||||
action: Kirigami.Action {
|
||||
text: i18n("Logout")
|
||||
iconName: "im-kick-user"
|
||||
onTriggered: {
|
||||
Controller.logout(model.connection, true);
|
||||
if (Controller.accountCount === 1) {
|
||||
pageStack.layers.pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
footer: Controls.ToolBar {
|
||||
Kirigami.Theme.colorSet: Kirigami.Theme.Window
|
||||
Kirigami.ActionToolBar {
|
||||
alignment: Qt.AlignRight
|
||||
rightPadding: Kirigami.Units.smallSpacing
|
||||
width: parent.width
|
||||
flat: false
|
||||
actions: Kirigami.Action {
|
||||
text: i18n("Add an account")
|
||||
icon.name: "list-add-user"
|
||||
onTriggered: pageStack.layers.push("qrc:/imports/NeoChat/Page/WelcomePage.qml")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: Controller
|
||||
function onConnectionAdded() {
|
||||
if (pageStack.layers.depth > 2)
|
||||
pageStack.layers.pop()
|
||||
}
|
||||
function onPasswordStatus(status) {
|
||||
if (status === Controller.Success) {
|
||||
showPassiveNotification(i18n("Password changed successfully"));
|
||||
} else if (status === Controller.Wrong) {
|
||||
showPassiveNotification(i18n("Wrong password entered"));
|
||||
} else {
|
||||
showPassiveNotification(i18n("Unknown problem while trying to change password"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
property Component openFileDialog: Component {
|
||||
id: openFileDialog
|
||||
|
||||
OpenFileDialog {
|
||||
folder: StandardPaths.writableLocation(StandardPaths.PicturesLocation)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,257 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2020 Tobias Fella <fella@posteo.de>
|
||||
// SPDX-FileCopyrightText: 2021 Carl Schwan <carl@carlschwan.eu>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15 as QQC2
|
||||
import QtQuick.Layouts 1.15
|
||||
|
||||
import org.kde.kirigami 2.15 as Kirigami
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
import NeoChat.Settings 1.0
|
||||
|
||||
Kirigami.ScrollablePage {
|
||||
title: i18nc("@title:window", "Appearance")
|
||||
ColumnLayout {
|
||||
RowLayout {
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
spacing: Kirigami.Units.gridUnit * 2
|
||||
QQC2.ButtonGroup { id: themeGroup }
|
||||
ThemeRadioButton {
|
||||
innerObject: [
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
Kirigami.Avatar {
|
||||
color: "#4a5bcc"
|
||||
Layout.alignment: Qt.AlignTop
|
||||
visible: Config.showAvatarInTimeline
|
||||
Layout.preferredWidth: Config.showAvatarInTimeline ? Kirigami.Units.largeSpacing * 2 : 0
|
||||
Layout.preferredHeight: Kirigami.Units.largeSpacing * 2
|
||||
}
|
||||
QQC2.Control {
|
||||
Layout.fillWidth: true
|
||||
contentItem: ColumnLayout {
|
||||
QQC2.Label {
|
||||
Layout.fillWidth: true
|
||||
font.weight: Font.Bold
|
||||
font.pixelSize: 7
|
||||
text: "Paul Müller"
|
||||
color: "#4a5bcc"
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
QQC2.Label {
|
||||
Layout.fillWidth: true
|
||||
text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus facilisis porta mauris, quis finibus sem suscipit tincidunt."
|
||||
wrapMode: Text.Wrap
|
||||
font.pixelSize: 7
|
||||
}
|
||||
}
|
||||
background: Kirigami.ShadowedRectangle {
|
||||
color: Kirigami.Theme.backgroundColor
|
||||
radius: Kirigami.Units.smallSpacing
|
||||
shadow.size: Kirigami.Units.smallSpacing
|
||||
shadow.color: Qt.rgba(0.0, 0.0, 0.0, 0.10)
|
||||
border.color: Kirigami.ColorUtils.tintWithAlpha(color, Kirigami.Theme.textColor, 0.15)
|
||||
border.width: 1
|
||||
}
|
||||
}
|
||||
},
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
Kirigami.Avatar {
|
||||
color: "#9f244b"
|
||||
Layout.alignment: Qt.AlignTop
|
||||
visible: Config.showAvatarInTimeline
|
||||
Layout.preferredWidth: Config.showAvatarInTimeline ? Kirigami.Units.largeSpacing * 2 : 0
|
||||
Layout.preferredHeight: Kirigami.Units.largeSpacing * 2
|
||||
}
|
||||
QQC2.Control {
|
||||
Layout.fillWidth: true
|
||||
contentItem: ColumnLayout {
|
||||
QQC2.Label {
|
||||
Layout.fillWidth: true
|
||||
font.weight: Font.Bold
|
||||
font.pixelSize: 7
|
||||
text: "Jean Paul"
|
||||
color: "#9f244b"
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
QQC2.Label {
|
||||
Layout.fillWidth: true
|
||||
text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus facilisis porta , quis sem suscipit tincidunt."
|
||||
wrapMode: Text.Wrap
|
||||
font.pixelSize: 7
|
||||
}
|
||||
}
|
||||
background: Kirigami.ShadowedRectangle {
|
||||
color: Kirigami.Theme.backgroundColor
|
||||
radius: Kirigami.Units.smallSpacing
|
||||
shadow.size: Kirigami.Units.smallSpacing
|
||||
shadow.color: Qt.rgba(0.0, 0.0, 0.0, 0.10)
|
||||
border.color: Kirigami.ColorUtils.tintWithAlpha(color, Kirigami.Theme.textColor, 0.15)
|
||||
border.width: 1
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
text: i18n("Bubbles")
|
||||
checked: !Config.compactLayout
|
||||
QQC2.ButtonGroup.group: themeGroup
|
||||
enabled: !Config.isCompactLayoutImmutable
|
||||
|
||||
onToggled: {
|
||||
Config.compactLayout = !checked;
|
||||
Config.save();
|
||||
}
|
||||
}
|
||||
ThemeRadioButton {
|
||||
innerObject: [
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
Kirigami.Avatar {
|
||||
color: "#4a5bcc"
|
||||
Layout.alignment: Qt.AlignTop
|
||||
visible: Config.showAvatarInTimeline
|
||||
Layout.preferredWidth: Config.showAvatarInTimeline ? Kirigami.Units.largeSpacing * 2 : 0
|
||||
Layout.preferredHeight: Kirigami.Units.largeSpacing * 2
|
||||
}
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
QQC2.Label {
|
||||
Layout.fillWidth: true
|
||||
font.weight: Font.Bold
|
||||
font.pixelSize: 7
|
||||
text: "Paul Müller"
|
||||
color: "#4a5bcc"
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
QQC2.Label {
|
||||
Layout.fillWidth: true
|
||||
text: "Lorem ipsum dolor sit amet, consectetur elit. Vivamus facilisis porta mauris, finibus sem suscipit tincidunt."
|
||||
wrapMode: Text.Wrap
|
||||
font.pixelSize: 7
|
||||
}
|
||||
}
|
||||
},
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
Kirigami.Avatar {
|
||||
color: "#9f244b"
|
||||
Layout.alignment: Qt.AlignTop
|
||||
visible: Config.showAvatarInTimeline
|
||||
Layout.preferredWidth: Config.showAvatarInTimeline ? Kirigami.Units.largeSpacing * 2 : 0
|
||||
Layout.preferredHeight: Kirigami.Units.largeSpacing * 2
|
||||
}
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
QQC2.Label {
|
||||
Layout.fillWidth: true
|
||||
font.weight: Font.Bold
|
||||
font.pixelSize: 7
|
||||
text: "Jean Paul"
|
||||
color: "#9f244b"
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
QQC2.Label {
|
||||
Layout.fillWidth: true
|
||||
text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus facilisis porta mauris, quis finibus sem suscipit tincidunt."
|
||||
wrapMode: Text.Wrap
|
||||
font.pixelSize: 7
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
text: i18n("Compact")
|
||||
checked: Config.compactLayout
|
||||
QQC2.ButtonGroup.group: themeGroup
|
||||
enabled: !Config.isCompactLayoutImmutable
|
||||
|
||||
onToggled: {
|
||||
Config.compactLayout = checked;
|
||||
Config.save();
|
||||
}
|
||||
}
|
||||
}
|
||||
Kirigami.FormLayout {
|
||||
Layout.maximumWidth: parent.width
|
||||
QQC2.CheckBox {
|
||||
Kirigami.FormData.label: i18n("Show Avatar:")
|
||||
text: i18n("In Chat")
|
||||
checked: Config.showAvatarInTimeline
|
||||
onToggled: {
|
||||
Config.showAvatarInTimeline = checked
|
||||
Config.save()
|
||||
}
|
||||
enabled: !Config.isShowAvatarInTimelineImmutable
|
||||
}
|
||||
QQC2.CheckBox {
|
||||
text: i18n("In Sidebar")
|
||||
checked: Config.showAvatarInRoomDrawer
|
||||
enabled: !Config.isShowAvatarInRoomDrawerImmutable
|
||||
onToggled: {
|
||||
Config.showAvatarInRoomDrawer = checked
|
||||
Config.save()
|
||||
}
|
||||
}
|
||||
QQC2.CheckBox {
|
||||
text: i18n("Show Fancy Effects")
|
||||
checked: Config.showFancyEffects
|
||||
enabled: !Config.isShowFancyEffectsImmutable
|
||||
onToggled: {
|
||||
Config.showFancyEffects = checked;
|
||||
Config.save();
|
||||
}
|
||||
}
|
||||
Loader {
|
||||
visible: item !== null
|
||||
Kirigami.FormData.label: item ? i18n("Theme:") : ""
|
||||
source: "qrc:/imports/NeoChat/Settings/ColorScheme.qml"
|
||||
}
|
||||
QQC2.CheckBox {
|
||||
visible: Controller.hasWindowSystem
|
||||
text: i18n("Use transparent chat page")
|
||||
enabled: !Config.compactLayout && !Config.isBlurImmutable
|
||||
checked: Config.blur
|
||||
onToggled: {
|
||||
Config.blur = checked;
|
||||
Config.save();
|
||||
}
|
||||
}
|
||||
RowLayout {
|
||||
visible: Controller.hasWindowSystem && Config.blur
|
||||
enabled: !Config.isTransparancyImmutable
|
||||
Kirigami.FormData.label: i18n("Transparency:")
|
||||
QQC2.Slider {
|
||||
enabled: !Config.compactLayout && Config.blur
|
||||
from: 0
|
||||
to: 1
|
||||
stepSize: 0.05
|
||||
value: Config.transparency
|
||||
onMoved: {
|
||||
Config.transparency = value;
|
||||
Config.save();
|
||||
}
|
||||
|
||||
HoverHandler { id: sliderHover }
|
||||
QQC2.ToolTip.visible: sliderHover.hovered && !enabled
|
||||
QQC2.ToolTip.text: i18n("Only enabled if the transparent chat page is enabled.")
|
||||
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
||||
}
|
||||
QQC2.Label {
|
||||
text: Math.round(Config.transparency * 100) + "%"
|
||||
}
|
||||
}
|
||||
QQC2.CheckBox {
|
||||
text: i18n("Show your messages on the right")
|
||||
checked: Config.showLocalMessagesOnRight
|
||||
enabled: !Config.isShowLocalMessagesOnRightImmutable && !Config.compactLayout
|
||||
onToggled: {
|
||||
Config.showLocalMessagesOnRight = checked
|
||||
Config.save()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,114 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Tobias Fella <fella@posteo.de>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15 as Controls
|
||||
import QtQuick.Layouts 1.15
|
||||
|
||||
import org.kde.kirigami 2.19 as Kirigami
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
|
||||
Kirigami.ScrollablePage {
|
||||
title: i18n("Devices")
|
||||
|
||||
ListView {
|
||||
model: DevicesModel {
|
||||
id: devices
|
||||
}
|
||||
|
||||
anchors.fill: parent
|
||||
|
||||
Kirigami.LoadingPlaceholder {
|
||||
visible: parent.count === 0 // We can assume 0 means loading since there is at least one device
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
|
||||
delegate: Kirigami.BasicListItem {
|
||||
text: model.displayName
|
||||
subtitle: model.id
|
||||
icon: "network-connect"
|
||||
trailing: RowLayout {
|
||||
Controls.ToolButton {
|
||||
display: Controls.AbstractButton.IconOnly
|
||||
action: Kirigami.Action {
|
||||
text: i18n("Edit device name")
|
||||
iconName: "document-edit"
|
||||
onTriggered: {
|
||||
renameSheet.index = model.index
|
||||
renameSheet.name = model.displayName
|
||||
renameSheet.open()
|
||||
}
|
||||
}
|
||||
}
|
||||
Controls.ToolButton {
|
||||
display: Controls.AbstractButton.IconOnly
|
||||
visible: Controller.encryptionSupported
|
||||
action: Kirigami.Action {
|
||||
text: i18n("Verify device")
|
||||
iconName: "security-low-symbolic"
|
||||
onTriggered: {
|
||||
devices.connection.startKeyVerificationSession(model.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
Controls.ToolButton {
|
||||
display: Controls.AbstractButton.IconOnly
|
||||
action: Kirigami.Action {
|
||||
text: i18n("Logout device")
|
||||
iconName: "edit-delete-remove"
|
||||
onTriggered: {
|
||||
passwordSheet.index = index
|
||||
passwordSheet.open()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Kirigami.OverlaySheet {
|
||||
id: passwordSheet
|
||||
|
||||
property var index
|
||||
|
||||
title: i18n("Remove device")
|
||||
Kirigami.FormLayout {
|
||||
Controls.TextField {
|
||||
id: passwordField
|
||||
Kirigami.FormData.label: i18n("Password:")
|
||||
echoMode: TextInput.Password
|
||||
}
|
||||
Controls.Button {
|
||||
text: i18n("Confirm")
|
||||
onClicked: {
|
||||
devices.logout(passwordSheet.index, passwordField.text)
|
||||
passwordField.text = ""
|
||||
passwordSheet.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Kirigami.OverlaySheet {
|
||||
id: renameSheet
|
||||
property int index
|
||||
property string name
|
||||
|
||||
title: i18n("Edit device")
|
||||
Kirigami.FormLayout {
|
||||
Controls.TextField {
|
||||
id: nameField
|
||||
Kirigami.FormData.label: i18n("Name:")
|
||||
text: renameSheet.name
|
||||
}
|
||||
Controls.Button {
|
||||
text: i18n("Save")
|
||||
onClicked: {
|
||||
devices.setName(renameSheet.index, nameField.text)
|
||||
renameSheet.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,122 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2021 Carson Black <uhhadd@gmail.com>
|
||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15 as QQC2
|
||||
import QtQuick.Layouts 1.15
|
||||
|
||||
import Qt.labs.platform 1.1
|
||||
|
||||
import org.kde.kirigami 2.15 as Kirigami
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
import NeoChat.Settings 1.0
|
||||
|
||||
import NeoChat.Component 1.0 as Components
|
||||
import NeoChat.Dialog 1.0
|
||||
|
||||
Kirigami.ScrollablePage {
|
||||
title: i18nc("@title:window", "Custom Emojis")
|
||||
|
||||
ListView {
|
||||
anchors.fill: parent
|
||||
|
||||
model: CustomEmojiModel {
|
||||
id: emojiModel
|
||||
connection: Controller.activeConnection
|
||||
}
|
||||
|
||||
Kirigami.PlaceholderMessage {
|
||||
anchors.centerIn: parent
|
||||
text: i18n("No custom inline stickers found")
|
||||
visible: parent.model.count === 0
|
||||
}
|
||||
|
||||
delegate: Kirigami.BasicListItem {
|
||||
id: del
|
||||
|
||||
required property string name
|
||||
required property url imageURL
|
||||
|
||||
text: name
|
||||
reserveSpaceForSubtitle: true
|
||||
|
||||
leading: Image {
|
||||
width: height
|
||||
sourceSize.width: width
|
||||
sourceSize.height: height
|
||||
source: imageURL
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
visible: parent.status === Image.Loading
|
||||
radius: height/2
|
||||
gradient: Components.ShimmerGradient { }
|
||||
}
|
||||
}
|
||||
|
||||
trailing: QQC2.ToolButton {
|
||||
width: height
|
||||
icon.name: "delete"
|
||||
onClicked: emojiModel.removeEmoji(del.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
footer: QQC2.ToolBar {
|
||||
Kirigami.Theme.colorSet: Kirigami.Theme.Window
|
||||
Kirigami.ActionToolBar {
|
||||
id: emojiCreator
|
||||
alignment: Qt.AlignRight
|
||||
rightPadding: Kirigami.Units.smallSpacing
|
||||
width: parent.width
|
||||
flat: false
|
||||
property string name
|
||||
actions: [
|
||||
Kirigami.Action {
|
||||
displayComponent: QQC2.TextField {
|
||||
id: emojiField
|
||||
placeholderText: i18n("new_emoji_name_here")
|
||||
|
||||
validator: RegularExpressionValidator {
|
||||
regularExpression: /[a-zA-Z_0-9]*/
|
||||
}
|
||||
onTextChanged: emojiCreator.name = text
|
||||
}
|
||||
},
|
||||
Kirigami.Action {
|
||||
text: i18n("Add Emoji...")
|
||||
|
||||
enabled: emojiCreator.name.length > 0
|
||||
property var fileDialog: null
|
||||
icon.name: 'list-add'
|
||||
|
||||
onTriggered: {
|
||||
if (this.fileDialog !== null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.fileDialog = openFileDialog.createObject(QQC2.Overlay.overlay)
|
||||
|
||||
this.fileDialog.chosen.connect((url) => {
|
||||
emojiModel.addEmoji(emojiCreator.name, url)
|
||||
this.fileDialog = null
|
||||
})
|
||||
this.fileDialog.onRejected.connect(() => {
|
||||
this.fileDialog = null
|
||||
})
|
||||
this.fileDialog.open()
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: openFileDialog
|
||||
|
||||
OpenFileDialog {
|
||||
folder: StandardPaths.writableLocation(StandardPaths.PicturesLocation)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,136 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2020 Tobias Fella <fella@posteo.de>
|
||||
// SPDX-FileCopyrightText: 2021 Carl Schwan <carl@carlschwan.eu>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15 as QQC2
|
||||
import QtQuick.Layouts 1.15
|
||||
|
||||
import org.kde.kirigami 2.15 as Kirigami
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
|
||||
Kirigami.ScrollablePage {
|
||||
title: i18nc("@title:window", "General")
|
||||
ColumnLayout {
|
||||
Kirigami.FormLayout {
|
||||
Layout.fillWidth: true
|
||||
QQC2.CheckBox {
|
||||
Kirigami.FormData.label: i18n("General settings:")
|
||||
text: i18n("Close to system tray")
|
||||
checked: Config.systemTray
|
||||
visible: Controller.supportSystemTray
|
||||
enabled: !Config.isSystemTrayImmutable
|
||||
onToggled: {
|
||||
Config.systemTray = checked
|
||||
Config.save()
|
||||
}
|
||||
}
|
||||
QQC2.CheckBox {
|
||||
text: i18n("Minimize to system tray on startup")
|
||||
checked: Config.minimizeToSystemTrayOnStartup
|
||||
visible: Controller.supportSystemTray && !Kirigami.Settings.isMobile
|
||||
enabled: Config.systemTray && !Config.isMinimizeToSystemTrayOnStartupImmutable
|
||||
onToggled: {
|
||||
Config.minimizeToSystemTrayOnStartup = checked
|
||||
Config.save()
|
||||
}
|
||||
}
|
||||
QQC2.CheckBox {
|
||||
// TODO: When there are enough notification and timeline event
|
||||
// settings, make 2 separate groups with FormData labels.
|
||||
Kirigami.FormData.label: i18n("Notifications and events:")
|
||||
text: i18n("Show notifications")
|
||||
checked: Config.showNotifications
|
||||
enabled: !Config.isShowNotificationsImmutable
|
||||
onToggled: {
|
||||
Config.showNotifications = checked
|
||||
Config.save()
|
||||
NotificationsManager.globalNotificationsEnabled = checked
|
||||
}
|
||||
}
|
||||
QQC2.CheckBox {
|
||||
text: i18n("Show leave and join events")
|
||||
checked: Config.showLeaveJoinEvent
|
||||
enabled: !Config.isShowLeaveJoinEventImmutable
|
||||
onToggled: {
|
||||
Config.showLeaveJoinEvent = checked
|
||||
Config.save()
|
||||
}
|
||||
}
|
||||
QQC2.CheckBox {
|
||||
text: i18n("Show name change events")
|
||||
checked: Config.showRename
|
||||
enabled: !Config.isShowRenameImmutable
|
||||
onToggled: {
|
||||
Config.showRename = checked
|
||||
Config.save()
|
||||
}
|
||||
}
|
||||
QQC2.CheckBox {
|
||||
text: i18n("Show avatar update events")
|
||||
checked: Config.showAvatarUpdate
|
||||
enabled: !Config.isShowAvatarUpdateImmutable
|
||||
onToggled: {
|
||||
Config.showAvatarUpdate = checked
|
||||
Config.save()
|
||||
}
|
||||
}
|
||||
QQC2.RadioButton {
|
||||
Kirigami.FormData.label: i18n("Rooms and private chats:")
|
||||
text: i18n("Separated")
|
||||
checked: !Config.mergeRoomList
|
||||
enabled: !Config.isMergeRoomListImmutable
|
||||
onToggled: {
|
||||
Config.mergeRoomList = false
|
||||
Config.save()
|
||||
}
|
||||
}
|
||||
QQC2.RadioButton {
|
||||
text: i18n("Intermixed")
|
||||
checked: Config.mergeRoomList
|
||||
enabled: !Config.isMergeRoomListImmutable
|
||||
onToggled: {
|
||||
Config.mergeRoomList = true
|
||||
Config.save()
|
||||
}
|
||||
}
|
||||
QQC2.CheckBox {
|
||||
id: quickEditCheckbox
|
||||
Layout.maximumWidth: parent.width
|
||||
text: i18n("Use s/text/replacement syntax to edit your last message")
|
||||
checked: Config.allowQuickEdit
|
||||
enabled: !Config.isAllowQuickEditImmutable
|
||||
onToggled: {
|
||||
Config.allowQuickEdit = checked
|
||||
Config.save()
|
||||
}
|
||||
|
||||
// TODO KF5.97 remove this line
|
||||
Component.onCompleted: this.contentItem.wrap = QQC2.Label.Wrap
|
||||
}
|
||||
QQC2.CheckBox {
|
||||
text: i18n("Send Typing Notifications")
|
||||
checked: Config.typingNotifications
|
||||
enabled: !Config.isTypingNotificationsImmutable
|
||||
onToggled: {
|
||||
Config.typingNotifications = checked
|
||||
Config.save()
|
||||
}
|
||||
}
|
||||
QQC2.CheckBox {
|
||||
text: i18n("Automatically hide/unhide the room information when resizing the window")
|
||||
Layout.maximumWidth: parent.width
|
||||
checked: Config.autoRoomInfoDrawer
|
||||
enabled: !Config.isAutoRoomInfoDrawerImmutable
|
||||
onToggled: {
|
||||
Config.autoRoomInfoDrawer = checked
|
||||
Config.save()
|
||||
}
|
||||
|
||||
// TODO KF5.97 remove this line
|
||||
Component.onCompleted: this.contentItem.wrap = QQC2.Label.Wrap
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2021 Carl Schwan <carlschwan@kde.org>
|
||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
|
||||
import QtQuick 2.15
|
||||
import org.kde.kirigami 2.18 as Kirigami
|
||||
import QtQuick.Layouts 1.15
|
||||
|
||||
Kirigami.CategorizedSettings {
|
||||
objectName: "settingsPage"
|
||||
actions: [
|
||||
Kirigami.SettingAction {
|
||||
text: i18n("General")
|
||||
icon.name: "org.kde.neochat"
|
||||
page: Qt.resolvedUrl("GeneralSettingsPage.qml")
|
||||
},
|
||||
Kirigami.SettingAction {
|
||||
text: i18n("Appearance")
|
||||
icon.name: "preferences-desktop-theme-global"
|
||||
page: Qt.resolvedUrl("AppearanceSettingsPage.qml")
|
||||
},
|
||||
Kirigami.SettingAction {
|
||||
text: i18n("Accounts")
|
||||
icon.name: "preferences-system-users"
|
||||
page: Qt.resolvedUrl("AccountsPage.qml")
|
||||
},
|
||||
Kirigami.SettingAction {
|
||||
text: i18n("Custom Emojis")
|
||||
icon.name: "preferences-desktop-emoticons"
|
||||
page: Qt.resolvedUrl("Emoticons.qml")
|
||||
},
|
||||
Kirigami.SettingAction {
|
||||
text: i18n("Spell Checking")
|
||||
iconName: "tools-check-spelling"
|
||||
page: Qt.resolvedUrl("SonnetConfigPage.qml")
|
||||
},
|
||||
Kirigami.SettingAction {
|
||||
text: i18n("Devices")
|
||||
iconName: "network-connect"
|
||||
page: Qt.resolvedUrl("DevicesPage.qml")
|
||||
},
|
||||
Kirigami.SettingAction {
|
||||
text: i18n("About NeoChat")
|
||||
icon.name: "help-about"
|
||||
page: Qt.resolvedUrl("About.qml")
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,344 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2021 Carl Schwan <carlschwan@kde.org>
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
|
||||
import QtQml 2.15
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15 as QQC2
|
||||
import QtQuick.Layouts 1.15
|
||||
import org.kde.kirigami 2.15 as Kirigami
|
||||
import org.kde.sonnet 1.0 as Sonnet
|
||||
|
||||
Kirigami.Page {
|
||||
id: page
|
||||
|
||||
/**
|
||||
* This property holds whether the setting on that page are automatically
|
||||
* applied or whether the user can apply then manually. By default, false.
|
||||
*/
|
||||
property bool instantApply: false
|
||||
|
||||
/**
|
||||
* This property holds whether the ListViews inside the page should get
|
||||
* extra padding and a background. By default, use the Kirigami.ApplicationWindow
|
||||
* wideMode value.
|
||||
*/
|
||||
property bool wideMode: QQC2.ApplicationWindow.window.wideMode ?? QQC2.ApplicationWindow.window.width > Kirigami.Units.gridUnit * 40
|
||||
|
||||
/**
|
||||
* Signal emmited when the user decide to discard it's change and close the
|
||||
* setting page.
|
||||
*
|
||||
* For example when using the ConfigPage inside Kirigami PageRow:
|
||||
*
|
||||
* \code
|
||||
* Sonnet.ConfigPage {
|
||||
* onClose: applicationWindow().pageStack.pop();
|
||||
* }
|
||||
* \endcode
|
||||
*/
|
||||
signal close()
|
||||
|
||||
function onBackRequested(event) {
|
||||
if (settings.modified) {
|
||||
applyDialog.open();
|
||||
event.accepted = true;
|
||||
}
|
||||
if (dialog) {
|
||||
dialog.close();
|
||||
}
|
||||
}
|
||||
title: i18nc("@window:title", "Spellchecking")
|
||||
|
||||
QQC2.Dialog {
|
||||
id: applyDialog
|
||||
title: qsTr("Apply Settings")
|
||||
contentItem: QQC2.Label {
|
||||
text: qsTr("The settings of the current module have changed.<br /> Do you want to apply the changes or discard them?")
|
||||
}
|
||||
standardButtons: QQC2.Dialog.Ok | QQC2.Dialog.Cancel | QQC2.Dialog.Discard
|
||||
|
||||
onAccepted: {
|
||||
settings.save();
|
||||
applyDialog.close();
|
||||
page.close();
|
||||
}
|
||||
onDiscarded: {
|
||||
applyDialog.close();
|
||||
page.close();
|
||||
}
|
||||
onRejected: applyDialog.close();
|
||||
}
|
||||
|
||||
onWideModeChanged: scroll.background.visible = wideMode;
|
||||
|
||||
leftPadding: wideMode ? Kirigami.Units.gridUnit : 0
|
||||
topPadding: wideMode ? Kirigami.Units.gridUnit : 0
|
||||
bottomPadding: wideMode ? Kirigami.Units.gridUnit : 0
|
||||
rightPadding: wideMode ? Kirigami.Units.gridUnit : 0
|
||||
|
||||
property var dialog: null
|
||||
|
||||
Sonnet.Settings {
|
||||
id: settings
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
|
||||
Kirigami.FormLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: wideMode ? 0 : Kirigami.Units.largeSpacing
|
||||
Layout.rightMargin: wideMode ? 0 : Kirigami.Units.largeSpacing
|
||||
|
||||
QQC2.ComboBox {
|
||||
Kirigami.FormData.label: i18n("Selected default language:")
|
||||
model: settings.dictionaryModel
|
||||
textRole: "display"
|
||||
valueRole: "languageCode"
|
||||
Component.onCompleted: currentIndex = indexOfValue(settings.defaultLanguage);
|
||||
onActivated: {
|
||||
settings.defaultLanguage = currentValue;
|
||||
}
|
||||
}
|
||||
|
||||
QQC2.Button {
|
||||
text: i18n("Open Personal Dictionary")
|
||||
onClicked: if (!dialog) {
|
||||
if (Kirigami.Settings.isMobile) {
|
||||
dialog = mobileSheet.createObject(page, {settings: settings});
|
||||
dialog.open();
|
||||
} else {
|
||||
dialog = desktopSheet.createObject(page, {settings: settings})
|
||||
dialog.show();
|
||||
}
|
||||
} else {
|
||||
if (Kirigami.Settings.isMobile) {
|
||||
dialog.open();
|
||||
} else {
|
||||
dialog.show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QQC2.CheckBox {
|
||||
Kirigami.FormData.label: i18n("Options:")
|
||||
checked: settings.checkerEnabledByDefault
|
||||
text: i18n("Enable automatic spell checking")
|
||||
onCheckedChanged: {
|
||||
settings.checkerEnabledByDefault = checked;
|
||||
if (instantApply) {
|
||||
settings.save();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QQC2.CheckBox {
|
||||
checked: settings.skipUppercase
|
||||
text: i18n("Ignore uppercase words")
|
||||
onCheckedChanged: {
|
||||
settings.skipUppercase = checked;
|
||||
if (instantApply) {
|
||||
settings.save();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QQC2.CheckBox {
|
||||
checked: settings.skipRunTogether
|
||||
text: i18n("Ignore hyphenated words")
|
||||
onCheckedChanged: {
|
||||
settings.skipRunTogether = checked;
|
||||
if (instantApply) {
|
||||
settings.save();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QQC2.CheckBox {
|
||||
id: autodetectLanguageCheckbox
|
||||
checked: settings.autodetectLanguage
|
||||
text: i18n("Detect language automatically")
|
||||
onCheckedChanged: {
|
||||
settings.autodetectLanguage = checked;
|
||||
if (instantApply) {
|
||||
settings.save();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Kirigami.Heading {
|
||||
level: 2
|
||||
text: i18n("Spell checking languages")
|
||||
wrapMode: Text.WordWrap
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Kirigami.Units.largeSpacing
|
||||
Layout.leftMargin: wideMode ? 0 : Kirigami.Units.largeSpacing
|
||||
Layout.rightMargin: wideMode ? 0 : Kirigami.Units.largeSpacing
|
||||
}
|
||||
QQC2.Label {
|
||||
text: i18n("%1 will provide spell checking and suggestions for the languages listed here when autodetection is enabled.", Qt.application.displayName)
|
||||
wrapMode: Text.WordWrap
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: wideMode ? 0 : Kirigami.Units.largeSpacing
|
||||
Layout.rightMargin: wideMode ? 0 : Kirigami.Units.largeSpacing
|
||||
}
|
||||
|
||||
QQC2.ScrollView {
|
||||
id: scroll
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
enabled: autodetectLanguageCheckbox.checked
|
||||
Component.onCompleted: background.visible = wideMode
|
||||
|
||||
// HACK: Hide unnecessary horizontal scrollbar (https://bugreports.qt.io/browse/QTBUG-83890)
|
||||
QQC2.ScrollBar.horizontal.policy: QQC2.ScrollBar.AlwaysOff
|
||||
|
||||
ListView {
|
||||
clip: true
|
||||
model: settings.dictionaryModel
|
||||
delegate: Kirigami.CheckableListItem {
|
||||
label: model.display
|
||||
action: Kirigami.Action {
|
||||
onTriggered: model.checked = checked
|
||||
}
|
||||
checked: model.checked
|
||||
trailing: Kirigami.Icon {
|
||||
source: "favorite"
|
||||
visible: model.isDefault
|
||||
HoverHandler {
|
||||
id: hover
|
||||
}
|
||||
QQC2.ToolTip {
|
||||
visible: hover.hovered
|
||||
text: qsTr("Default Language")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
component SheetHeader : RowLayout {
|
||||
QQC2.TextField {
|
||||
id: dictionaryField
|
||||
Layout.fillWidth: true
|
||||
placeholderText: i18n("Add a new word to your personal dictionary…")
|
||||
}
|
||||
QQC2.Button {
|
||||
text: i18nc("@action:button", "Add word")
|
||||
icon.name: "list-add"
|
||||
enabled: dictionaryField.text.length > 0
|
||||
onClicked: {
|
||||
add(dictionaryField.text);
|
||||
dictionaryField.clear();
|
||||
if (instantApply) {
|
||||
settings.save();
|
||||
}
|
||||
}
|
||||
Layout.rightMargin: Kirigami.Units.largeSpacing
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: desktopSheet
|
||||
QQC2.ApplicationWindow {
|
||||
id: window
|
||||
required property Sonnet.Settings settings
|
||||
title: i18n("Spell checking dictionary")
|
||||
width: Kirigami.Units.gridUnit * 20
|
||||
height: Kirigami.Units.gridUnit * 20
|
||||
flags: Qt.Dialog | Qt.WindowCloseButtonHint
|
||||
header: Kirigami.AbstractApplicationHeader {
|
||||
leftPadding: Kirigami.Units.smallSpacing
|
||||
rightPadding: Kirigami.Units.smallSpacing
|
||||
contentItem: SheetHeader {
|
||||
anchors.fill: parent
|
||||
}
|
||||
}
|
||||
QQC2.ScrollView {
|
||||
anchors.fill: parent
|
||||
|
||||
// HACK: Hide unnecessary horizontal scrollbar (https://bugreports.qt.io/browse/QTBUG-83890)
|
||||
QQC2.ScrollBar.horizontal.policy: QQC2.ScrollBar.AlwaysOff
|
||||
|
||||
ListView {
|
||||
model: settings.currentIgnoreList
|
||||
delegate: Kirigami.BasicListItem {
|
||||
label: model.modelData
|
||||
trailing: QQC2.ToolButton {
|
||||
icon.name: "delete"
|
||||
onClicked: {
|
||||
remove(modelData)
|
||||
if (instantApply) {
|
||||
settings.save();
|
||||
}
|
||||
}
|
||||
QQC2.ToolTip {
|
||||
text: i18n("Delete word")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: mobileSheet
|
||||
Kirigami.OverlaySheet {
|
||||
required property Sonnet.Settings settings
|
||||
id: dictionarySheet
|
||||
|
||||
header: SheetHeader {}
|
||||
|
||||
ListView {
|
||||
implicitWidth: Kirigami.Units.gridUnit * 15
|
||||
model: settings.currentIgnoreList
|
||||
delegate: Kirigami.BasicListItem {
|
||||
label: model.modelData
|
||||
trailing: QQC2.ToolButton {
|
||||
icon.name: "delete"
|
||||
onClicked: {
|
||||
remove(modelData)
|
||||
if (instantApply) {
|
||||
settings.save();
|
||||
}
|
||||
}
|
||||
QQC2.ToolTip {
|
||||
text: i18n("Delete word")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
footer: QQC2.ToolBar {
|
||||
visible: !instantApply
|
||||
height: visible ? implicitHeight : 0
|
||||
contentItem: RowLayout {
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
QQC2.Button {
|
||||
text: i18n("Apply")
|
||||
enabled: settings.modified
|
||||
onClicked: settings.save();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function add(word) {
|
||||
const dictionary = settings.currentIgnoreList;
|
||||
dictionary.push(word);
|
||||
settings.currentIgnoreList = dictionary;
|
||||
}
|
||||
|
||||
function remove(word) {
|
||||
settings.currentIgnoreList = settings.currentIgnoreList.filter(function (value, _, _) {
|
||||
return value !== word;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
module NeoChat.Settings
|
||||
ThemeRadioButton 1.0 ThemeRadioButton.qml
|
||||
SettingsPage 1.0 SettingsPage.qml
|
||||
SonnetConfigPage 1.0 SonnetConfigPage.qml
|
||||
@@ -2,7 +2,7 @@
|
||||
<!--
|
||||
- SPDX-License-Identifier: CC0-1.0
|
||||
- SPDX-FileCopyrightText: 2020-2021 Carl Schwan <carlschwan@kde.org>
|
||||
- SPDX-FileCopyrightText: 2020-2021 Tobias Fella <fella@posteo.de>
|
||||
- SPDX-FileCopyrightText: 2020-2021 Tobias Fella <tobias.fella@kde.org>
|
||||
-->
|
||||
<component type="desktop">
|
||||
<id>org.kde.neochat</id>
|
||||
@@ -16,14 +16,18 @@
|
||||
<name xml:lang="ca-valencia">NeoChat</name>
|
||||
<name xml:lang="cs">NeoChat</name>
|
||||
<name xml:lang="de">NeoChat</name>
|
||||
<name xml:lang="el">NeoChat</name>
|
||||
<name xml:lang="en-GB">NeoChat</name>
|
||||
<name xml:lang="eo">NeoChat</name>
|
||||
<name xml:lang="es">NeoChat</name>
|
||||
<name xml:lang="eu">NeoChat</name>
|
||||
<name xml:lang="fi">NeoChat</name>
|
||||
<name xml:lang="fr">NeoChat</name>
|
||||
<name xml:lang="gl">NeoChat</name>
|
||||
<name xml:lang="hu">NeoChat</name>
|
||||
<name xml:lang="ia">Neochat</name>
|
||||
<name xml:lang="id">NeoChat</name>
|
||||
<name xml:lang="ie">NeoChat</name>
|
||||
<name xml:lang="it">NeoChat</name>
|
||||
<name xml:lang="ka">NeoChat</name>
|
||||
<name xml:lang="ko">NeoChat</name>
|
||||
@@ -33,6 +37,7 @@
|
||||
<name xml:lang="pl">NeoChat</name>
|
||||
<name xml:lang="pt">NeoChat</name>
|
||||
<name xml:lang="pt-BR">NeoChat</name>
|
||||
<name xml:lang="ru">NeoChat</name>
|
||||
<name xml:lang="sk">NeoChat</name>
|
||||
<name xml:lang="sl">NeoChat</name>
|
||||
<name xml:lang="sv">NeoChat</name>
|
||||
@@ -41,119 +46,178 @@
|
||||
<name xml:lang="uk">NeoChat</name>
|
||||
<name xml:lang="x-test">xxNeoChatxx</name>
|
||||
<name xml:lang="zh-CN">NeoChat</name>
|
||||
<summary>A client for matrix, the decentralized communication protocol</summary>
|
||||
<summary xml:lang="ar">عميل لماتركس، ميفاق الاتصال اللامركزي</summary>
|
||||
<summary xml:lang="az">Matrix üçün müştəri, mərkəzləşməmiş kommunikasiya protokolu</summary>
|
||||
<summary xml:lang="ca">Un client per al Matrix, el protocol de comunicacions descentralitzat</summary>
|
||||
<summary xml:lang="ca-valencia">Un client per a Matrix, el protocol de comunicacions descentralitzat</summary>
|
||||
<summary xml:lang="cs">Klient pro decentralizovaný komunikační protokol matrix</summary>
|
||||
<summary xml:lang="de">Ein Programm für Matrix, das dezentrale Kommunikationsprotokoll</summary>
|
||||
<summary xml:lang="en-GB">A client for matrix, the decentralised communication protocol</summary>
|
||||
<summary xml:lang="es">Un cliente para Matrix, el protocolo de comunicaciones descentralizado</summary>
|
||||
<summary xml:lang="eu">Matrix, deszentralizatutako komunikazio protokolorako bezero bat</summary>
|
||||
<summary xml:lang="fi">Asiakas Matrixille, hajautetulle viestintäyhteyskäytännölle</summary>
|
||||
<summary xml:lang="fr">Un client pour « Matrix », le protocole décentralisé de communications.</summary>
|
||||
<summary xml:lang="hu">Kliens a matrixhoz, a decentralizált kommunikációs protokollhoz</summary>
|
||||
<summary xml:lang="ia">Un cliente per Matrix, le protocollo de communication decentralisate</summary>
|
||||
<summary xml:lang="id">Klien untuk matrix, protokol komunikasi terdesentralisasi</summary>
|
||||
<summary xml:lang="it">Un client per matrix, il protocollo di comunicazione decentralizzato</summary>
|
||||
<summary xml:lang="ka">კლიენტი Matrix-სთვის, დეცენტრალიზებული კომუნიკაციის პროტოკოლისთვის</summary>
|
||||
<summary xml:lang="ko">Matrix, 분산 대화 프로토콜 클라이언트</summary>
|
||||
<summary xml:lang="nl">Een client voor matrix, het gedecentraliseerde communicatieprotocol</summary>
|
||||
<summary xml:lang="nn">Ein klient for Matrix, den desentraliserte lynmeldingsprotokollen</summary>
|
||||
<summary xml:lang="pa">ਮੈਟਰਿਕਸ, ਸਰਬ-ਸਾਂਝੇ ਸੰਚਾਰ ਪਰੋਟੋਕਾਲ, ਲਈ ਕਲਾਈਂਟ ਹੈ</summary>
|
||||
<summary xml:lang="pl">Program do obsługi matriksa, rozproszonego protokołu porozumiewania się</summary>
|
||||
<summary xml:lang="pt">Um cliente para o Matrix, o protocolo de comunicação descentralizado</summary>
|
||||
<summary xml:lang="pt-BR">Um cliente do Matrix, o protocolo de comunicação descentralizado</summary>
|
||||
<summary xml:lang="sk">Klient pre matrix, decentralizovaný komunikačný protokol</summary>
|
||||
<summary xml:lang="sl">Odjemalec za matrix, decentralizirani komunikacijski protokol</summary>
|
||||
<summary xml:lang="sv">En klient för Matrix, det decentraliserade kommunikationsprotokollet</summary>
|
||||
<summary xml:lang="tr">Merkezi olmayan iletişim protokolü Matrix için bir istemci</summary>
|
||||
<summary xml:lang="uk">Клієнт matrix, децентралізованого протоколу обміну даними</summary>
|
||||
<summary xml:lang="x-test">xxA client for matrix, the decentralized communication protocolxx</summary>
|
||||
<summary xml:lang="zh-CN">分布式通讯协议 Matrix 的客户端</summary>
|
||||
<summary>Chat with your friends on matrix</summary>
|
||||
<summary xml:lang="ar">دردش مع أصدقائك على ماتركس</summary>
|
||||
<summary xml:lang="ca">Xategeu amb els vostres amics a Matrix</summary>
|
||||
<summary xml:lang="ca-valencia">Xategeu amb els vostres amics a Matrix</summary>
|
||||
<summary xml:lang="eo">Babilu kun viaj amikoj sur matrix</summary>
|
||||
<summary xml:lang="es">Charle con sus amigos en matrix</summary>
|
||||
<summary xml:lang="eu">Berriketan jardun zure lagunekin «Matrix»en</summary>
|
||||
<summary xml:lang="fr">Discuter avec vos ami(e)s sur le réseau Matrix</summary>
|
||||
<summary xml:lang="gl">Charle coas súas amizades en Matrix.</summary>
|
||||
<summary xml:lang="it">Conversa con i tuoi contatti su matrix</summary>
|
||||
<summary xml:lang="ka">ესაუბრეთ მეგობრებს Matrix-ზე</summary>
|
||||
<summary xml:lang="ko">Matrix를 사용하여 친구들과 대화하기</summary>
|
||||
<summary xml:lang="nl">Met uw vrienden chatten op matrix</summary>
|
||||
<summary xml:lang="nn">Prat med vennar på Matrix</summary>
|
||||
<summary xml:lang="sl">Klepet z vašimi prijatelji na matrixu</summary>
|
||||
<summary xml:lang="sv">Chatta med dina vänner på Matrix</summary>
|
||||
<summary xml:lang="ta">மேட்ரிக்ஸு மூலம் உங்கள் நண்பர்களிடம் பேசலாம்</summary>
|
||||
<summary xml:lang="tr">Matrix'te arkadaşlarınızla sohbet edin</summary>
|
||||
<summary xml:lang="uk">Спілкуйтеся з вашими друзями у matrix</summary>
|
||||
<summary xml:lang="x-test">xxChat with your friends on matrixxx</summary>
|
||||
<description>
|
||||
<p>NeoChat is a Matrix client. It allows you to send text messages, videos and audio files to your family, colleagues and friends using the Matrix protocol.</p>
|
||||
<p xml:lang="ar">نيوتشات هو عميل ماتركس Matrix. يتيح لك إرسال رسائل نصية ومقاطع فيديو وملفات صوتية إلى عائلتك وزملائك وأصدقائك باستخدام بروتوكول ماتركس</p>
|
||||
<p xml:lang="az">NeoChat Mtrix müştərisidir. O, Matrix protokolundan istifadə edərək, ailənizə, dostlarınıza, iş yoldaşlarınıza mətn, səsli və görüntülü ismarıclar göndərməyə imkan verir.</p>
|
||||
<p xml:lang="ca">El NeoChat és un client de Matrix. Permet enviar missatges de text, fitxers de vídeo i d'àudio a la família, col·legues i amics usant el protocol Matrix.</p>
|
||||
<p xml:lang="ca-valencia">NeoChat és un client de Matrix. Permet enviar missatges de text, fitxers de vídeo i d'àudio a la família, col·legues i amics utilitzant el protocol Matrix.</p>
|
||||
<p xml:lang="de">NeoChat ist ein Matrix-Client. Er ermöglicht Ihnen das Senden von Textnachrichten, Videos und Audiodateien an Ihre Familie, Kollegen und Freunde unter Verwendung des Matrix-Protokolls.</p>
|
||||
<p xml:lang="en-GB">NeoChat is a Matrix client. It allows you to send text messages, videos and audio files to your family, colleagues and friends using the Matrix protocol.</p>
|
||||
<p xml:lang="es">NeoChat es un cliente para Matrix. Le permite enviar mensajes de texto, vídeos y archivos de sonido a su familia, compañeros de trabajo y amigos usando el protocolo Matrix.</p>
|
||||
<p xml:lang="eu">NeoChat Matrix bezero bat da. Familiari, lankideei eta lagunei testu-mezuak, bideoak eta audio-fitxategiak bidaltzeko aukera ematen du, Matrix protokoloa erabiliz.</p>
|
||||
<p xml:lang="fi">NeoChat on Matrix-asiakas. Sillä voi lähettää perheelle, tuttaville ja kavereille tekstiviestejä sekä video- ja äänitiedostoja Matrix-yhteyskäytännöllä.</p>
|
||||
<p xml:lang="fr">NeoChat est un client Matrix. Il vous permet d'envoyer des messages de texte, des vidéos et des fichiers audio à votre famille, vos collègues et vos amis en utilisant le protocole Matrix.</p>
|
||||
<p xml:lang="hu">A NeoChat egy Matrix kliens. Szöveges üzeneteket, videókat ésaudio fájlokat küldhet családjának, kollégáinak és barátainak a Matrix protokoll használatával.</p>
|
||||
<p xml:lang="ia">NeoChat es un cliente de Matrix. Illo te permitte inviar messager de texto, files de video e audio a tu familia, collegas e amicos usante le protocollo de Matrix.</p>
|
||||
<p xml:lang="id">NeoChat adalah sebuah klien Matrix. Memungkinkan Anda untuk mengirim pesan teks, file video dan audio ke keluarga, kolega dan teman Anda menggunakan protokol Matrix.</p>
|
||||
<p xml:lang="it">NeoChat è un client Matrix. Ti consente di inviare messaggi di testo, file video e audio a familiari, colleghi e amici utilizzando il protocollo Matrix.</p>
|
||||
<p xml:lang="ko">NeoChat은 Matrix 클라이언트입니다. Matrix 프로토콜을 사용하여 가족, 동료, 친구에게 텍스트 메시지, 동영상, 오디오 파일을 전송할 수 있습니다.</p>
|
||||
<p xml:lang="nl">NeoChat is een Matrix-client. Het biedt u het verzenden van tekstberichten, video's en geluidsbestanden naar uw familie, collega's en vrienden met het Matrix-protocol.</p>
|
||||
<p xml:lang="pl">NeoChat jest programem do Matrisa. Umożliwia wysyłanie wiadomości tekstowych, filmów oraz dźwięku do twojej rodziny, znajomych oraz przyjaciół poprzez protokół Matriksa.</p>
|
||||
<p xml:lang="pt">O NeoChat é um cliente do Matrix. O mesmo permite-lhe enviar mensagens de texto, ficheiros de vídeo e áudio para a sua família, colegas e amigos com o protocolo Matrix.</p>
|
||||
<p xml:lang="pt-BR">O NeoChat é um cliente Matrix. Ele permite a você enviar mensagens de texto, arquivos de vídeo e áudio para seus familiares, colegas e amigos usando o protocolo Matrix.</p>
|
||||
<p xml:lang="sk">NeoChat je Matrix klient. Umožňuje vám posielať textové správy, videá a zvukové súbory rodine, kolegom a priateľom pomocou protokolu Matrix.</p>
|
||||
<p xml:lang="sl">NeoChat je odjemalec Matrixa. Dovoljuje vam pošiljanje besedilnih sporočil, videoposnetkov in zvočnih datotek vaši družini, kolegom in prijateljem z uporabo protokola Matrix.</p>
|
||||
<p xml:lang="sv">NeoChat är en Matrix-klient. Den låter dig skicka textmeddelanden, videor och ljudfiler till din familj, kollegor och vänner med användning av Matrix-protokollet.</p>
|
||||
<p xml:lang="tr">NeoChat, bir Matrix istemcisidir. Matrix protokolünü kullanarak ailenize, iş arkadaşlarınıza, arkadaşlarınıza ve başkalarına metin iletileri, video ve ses dosyaların göndermenize olanak verir.</p>
|
||||
<p xml:lang="uk">NeoChat — клієнт мережі обміну повідомленнями Matrix. За допомогою цієї програми ви зможете надсилати текстові повідомлення, відео та звукові файли вашій родині, колегам та друзям за допомогою протоколу Matrix.</p>
|
||||
<p xml:lang="x-test">xxNeoChat is a Matrix client. It allows you to send text messages, videos and audio files to your family, colleagues and friends using the Matrix protocol.xx</p>
|
||||
<p xml:lang="zh-CN">NeoChat 是一个 Matrix 客户端。 它允许您使用 Matrix 协议向您的家人、同事和朋友发送文本消息、视频和音频文件。</p>
|
||||
<p>Matrix is a decentralized communication protocol, putting the user back in control. Currently NeoChat implements large part of the protocol with the exception of encrypted chats and video chat.</p>
|
||||
<p xml:lang="ar">ماتريكس هو بروتوكول اتصال لامركزي ، يعيد المستخدم إلى السيطرة. يطبق نيوتشات حاليًا جزءًا كبيرًا من الميفاق باستثناء الدردشات المشفرة ودردشة الفيديو.</p>
|
||||
<p xml:lang="az">Matrix, istifadəçini nəzarətdə saxlayan, mərkəzləşməmişi rabitə protokoludur. NeoChat, söhbətin və video əlaqəsinin şifrələnməsindən başqa bir çox protokolları həyata keçirə bilir.</p>
|
||||
<p xml:lang="ca">Matrix és un protocol de comunicacions descentralitzat, que retorna el control a l'usuari. Actualment el NeoChat implementa una gran part del protocol amb l'excepció dels xats encriptats i els xats de vídeo.</p>
|
||||
<p xml:lang="ca-valencia">Matrix és un protocol de comunicacions descentralitzat, que retorna el control a l'usuari. Actualment NeoChat implementa una gran part del protocol amb l'excepció dels xats encriptats i els xats de vídeo.</p>
|
||||
<p xml:lang="de">Matrix ist ein dezentralisiertes Kommunikationsprotokoll, das dem Benutzer wieder die Kontrolle zurückgibt. Derzeit implementiert NeoChat einen großen Teil des Protokolls mit der Ausnahme von verschlüsselten Chats und Video-Chat.</p>
|
||||
<p xml:lang="en-GB">Matrix is a decentralised communication protocol, putting the user back in control. Currently NeoChat implements large part of the protocol with the exception of encrypted chats and video chat.</p>
|
||||
<p xml:lang="es">Matrix es un protocolo de comunicaciones descentralizado, que devuelve el control al usuario. En la actualidad, NeoChat implementa gran parte del protocolo con la excepción de chats cifrados y chats de vídeo.</p>
|
||||
<p xml:lang="eu">Matrix komunikazio-protokolo deszentralizatu bat da, erabiltzaileari kontrola itzultzen diona. Gaur egun, NeoChat-ek protokoloaren zati handi bat inplementatzen du, berriketa zifratuak eta bideo berriketak izan ezik.</p>
|
||||
<p xml:lang="fi">Matrix on hajautettu viestintäyhteyskäytäntö, joka antaa hallinnan takaisin käyttäjille. NeoChat tarjoaa nykyisellään valtaosan yhteyskäytännöstä salattuja keskustelu- ja videokeskusteluja lukuun ottamatta.</p>
|
||||
<p xml:lang="fr">Matrix est un protocole de communication décentralisé, donnant le contrôle à l'utilisateur. Actuellement, NeoChat met en œuvre une grande partie du protocole, à l'exception des discussions chiffrées et du chat vidéo.</p>
|
||||
<p xml:lang="hu">A Matrix egy decentralizált kommunikációs protokoll, amely a felhasználók kezébe adja az irányítást.</p>
|
||||
<p xml:lang="ia">Matrix es un protocollo de communication decentrate, ponente le usator in le controlo. Currentemente NeoChat implementa un grande parte del protocollo con le exception de conversationes cryptate e conversationes video.</p>
|
||||
<p xml:lang="id">Matrix adalah protokol komunikasi terdesentralisasi, menempatkan pengguna kembali dalam kendali. Saat ini NeoChat mengimplementasikan sebagian besar protokol dengan pengecualian obrolan terenkripsi dan obrolan video.</p>
|
||||
<p xml:lang="it">Matrix è un protocollo di comunicazione decentralizzato, che restituisce all'utente il controllo. Attualmente NeoChat implementa gran parte del protocollo ad eccezione delle chat cifrate e delle chat video.</p>
|
||||
<p xml:lang="ko">Matrix는 사용자에게 제어권을 돌려 주는 분산 통신 프로토콜입니다. NeoChat은 암호화된 대화 및 영상 통화를 제외한 프로토콜의 대부분 기능을 구현합니다.</p>
|
||||
<p xml:lang="nl">Matrix is een gedecentraliseerd communicatieprotocol, dat de gebruiker de controle teruggeeft. Op dit moment implementeert NeoChat grote delen van het protocol met de uitzondering van versleutelde chats en video-chat.</p>
|
||||
<p xml:lang="pl">Matrix jest protokołem rozproszonego porozumiewania się oddający użytkownikowi jego władzę. Obecnie NeoChat obsługuje dużą część protokołu poza szyfrowanymi rozmowami tekstowymi i z obrazem.</p>
|
||||
<p xml:lang="pt">O Matrix é um protocolo de comunicações descentralizado, colocando de novo o utilizador no poder. De momento, o NeoChat implementa uma boa parte do protocolo, com a excepção das conversas encriptadas e as conversas de vídeo.</p>
|
||||
<p xml:lang="pt-BR">O Matrix é um protocolo de comunicação descentralizado, colocando o usuário de volta no controle. Atualmente o NeoChat implementa grande parte do protocolo com a exceção de bate-papos criptografados e bate-papo por vídeo.</p>
|
||||
<p xml:lang="sk">Matrix je decentralizovaný komunikačný protokol, ktorý používateľovi vracia kontrolu. V súčasnosti NeoChat implementuje veľkú časť protokolu s výnimkou šifrovaných chatov a videohovorov.</p>
|
||||
<p xml:lang="sl">Matrix je decentraliziran komunikacijski protokol, kjer ima uporabnik uporabnik kontrolo rabe. Trenutno ima NeoChat izveden velik del protokola z izjemo šifriranih klepetov in video klepetov.</p>
|
||||
<p xml:lang="sv">Matrix är ett decentraliserat kommunikationsprotokoll, som ger tillbaka kontrollen till användaren. För närvarande implementerar NeoChat en stor del av protokollet, med undantag för krypterad chatt och videochatt.</p>
|
||||
<p xml:lang="tr">Matrix; tam denetimi kullanıcıya bırakan, merkezi olmayan bir iletişim protokolüdür. Şu anda NeoChat, uçtan uca şifrelenmiş metin ve video sohbetleri dışında protokolün büyük bir bölümünü bünyesinde bulundurur.</p>
|
||||
<p xml:lang="uk">Matrix — протокол децентралізованого спілкування, який передає контроль над даними користувачеві. У поточній версії NeoChat реалізовано більшу частину протоколу, окрім зашифрованого спілкування та відеоспілкування.</p>
|
||||
<p xml:lang="x-test">xxMatrix is a decentralized communication protocol, putting the user back in control. Currently NeoChat implements large part of the protocol with the exception of encrypted chats and video chat.xx</p>
|
||||
<p xml:lang="zh-CN">Matrix 是一个分布式通讯协议,使用户重新得到控制权。 目前,NeoChat 实现了协议的大部分,除了加密聊天和视频聊天。</p>
|
||||
<p>NeoChat works both on mobile and desktop while providing a consistent user experience.</p>
|
||||
<p xml:lang="ar">يعمل نيوتشات على كل من الأجهزة المحمولة وسطح المكتب مع توفير تجربة مستخدم متسقة.</p>
|
||||
<p xml:lang="az">Vahid istifadəçi interfeysi ilə təmin olunan NeoChat, həm mobil telefonda həm də kompyuterlərdə işləyir.</p>
|
||||
<p xml:lang="ca">El NeoChat funciona en els mòbils i a l'escriptori, proporcionant una experiència d'usuari coherent.</p>
|
||||
<p xml:lang="ca-valencia">NeoChat funciona en els mòbils i en l'escriptori, proporcionant una experiència d'usuari coherent.</p>
|
||||
<p xml:lang="de">NeoChat funktioniert sowohl auf dem Mobiltelefon als auch auf dem Arbeitsfläche und bietet ein einheitliches Benutzererlebnis. </p>
|
||||
<p xml:lang="en-GB">NeoChat works both on mobile and desktop while providing a consistent user experience.</p>
|
||||
<p xml:lang="es">NeoChat funciona en móviles y en el escritorio a la vez que proporciona una experiencia de usuario consistente.</p>
|
||||
<p xml:lang="eu">NeoChat mugikorretan eta mahaigainean dabil, erabiltzaile esperientzia koherentea eskainiz.</p>
|
||||
<p xml:lang="fi">NeoChat toimii sekä mobiili- että työpöytäalustoilla tarjoten yhdenmukaisen käyttökokemuksen.</p>
|
||||
<p xml:lang="fr">NeoChat fonctionne aussi bien sur les mobiles que sur les ordinateurs de bureau, tout en offrant une expérience utilisateur cohérente.</p>
|
||||
<p xml:lang="hu">A NeoChat mobilon és asztali számítógépen is működik, egységes felhasználói élményt nyújtva.</p>
|
||||
<p xml:lang="ia">NeoChat functiona sia sur mobile que ur scriptorio durante que forni un experientia de usator consistente.</p>
|
||||
<p xml:lang="id">NeoChat berfungsi baik di ponsel dan desktop sambil memberikan pengalaman pengguna yang konsisten.</p>
|
||||
<p xml:lang="it">NeoChat funziona sia su dispositivi mobili che desktop, fornendo un'esperienza utente coerente.</p>
|
||||
<p xml:lang="ko">NeoChat은 모바일과 데스크톱 모두에서 일관된 사용자 경험을 제공합니다.</p>
|
||||
<p xml:lang="nl">NeoChat werkt zowel op de mobiel en het bureaublad met het leveren van een consistente gebruikerservaring.</p>
|
||||
<p xml:lang="pl">NeoChat działa zarówno na urządzeniach przenośnych jak i biurkowych, zapewniając spójne wrażenia użytkownika</p>
|
||||
<p xml:lang="pt">O NeoChat funciona tanto em dispositivos móveis como no computador, fornecendo uma experiência de utilizador consistente.</p>
|
||||
<p xml:lang="pt-BR">O NeoChat funciona tanto no celular como no computador enquanto fornece uma experiência consistente ao usuário.</p>
|
||||
<p xml:lang="sk">NeoChat funguje na mobilných aj stolových počítačoch a poskytuje konzistentný používateľský zážitok.</p>
|
||||
<p xml:lang="sl">NeoChat deluje tako na mobilnih kot na namiznih platformah z zagotavljanjem konsistentne uporabniške izkušnje.</p>
|
||||
<p xml:lang="sv">NeoChat fungerar både på mobil och skrivbord och tillhandahåller en konsekvent användarupplevelse.</p>
|
||||
<p xml:lang="tr">NeoChat, hem masaüstü hem de taşınabilir ortamlarda çalışarak tutarlı bir kullanıcı deneyimi sunar.</p>
|
||||
<p xml:lang="uk">NeoChat працює на мобільних пристроях та звичайних комп'ютерах, маючи однорідний інтерфейс на усіх підтримуваних пристроях.</p>
|
||||
<p xml:lang="x-test">xxNeoChat works both on mobile and desktop while providing a consistent user experience.xx</p>
|
||||
<p xml:lang="zh-CN">NeoChat 在移动设备和桌面上均可用,并提供一致的用户体验。</p>
|
||||
<p>NeoChat is a client for Matrix, the decentralized communication protocol for instant messaging. It allows you to send text messages, videos and audio files to your family, colleagues and friends. It uses KDE frameworks and most notably Kirigami
|
||||
to provide a convergent experience across multiple platforms.</p>
|
||||
<p xml:lang="ar">نيوتشات هو عميل ماتركس Matrix، (ميفاق الاتصال اللامركزي للمراسلة الفورية). يتيح لك نيوتشات إرسال رسائل نصية ومقاطع فيديو وملفات صوتية إلى عائلتك وزملائك وأصدقائك. يستخدم أطر عمل كيدي وأبرزها Kirigami لتوفير تجربة متقاربة عبر منصات متعددة.</p>
|
||||
<p xml:lang="ca">El NeoChat és un client de Matrix, el protocol descentralitzat de comunicacions de missatgeria instantània. Permet enviar missatges de text, fitxers de vídeo i d'àudio a la família, col·legues i amics. Fa servir els Frameworks de KDE i, sobretot, el Kirigami per a proporcionar una experiència convergent a través de diverses plataformes.</p>
|
||||
<p xml:lang="ca-valencia">NeoChat és un client de Matrix, el protocol descentralitzat de comunicacions de missatgeria instantània. Permet enviar missatges de text, fitxers de vídeo i d'àudio a la família, col·legues i amics. Utilitza els Frameworks de KDE i, sobretot, Kirigami per a proporcionar una experiència convergent a través de diverses plataformes.</p>
|
||||
<p xml:lang="en-GB">NeoChat is a client for Matrix, the decentralised communication protocol for instant messaging. It allows you to send text messages, videos and audio files to your family, colleagues and friends. It uses KDE frameworks and most notably Kirigami to provide a convergent experience across multiple platforms.</p>
|
||||
<p xml:lang="eo">NeoChat estas kliento por Matrix, la malcentra komunikoprotokolo por tuja mesaĝado. Ĝi ebligas al vi sendi tekstmesaĝojn, filmetojn kaj sondosierojn al via familio, kolegoj kaj amikoj. Ĝi uzas KDE-framojn kaj precipe Kirigami por disponigi konverĝan sperton tra pluraj platformoj.</p>
|
||||
<p xml:lang="es">NeoChat es un cliente para Matrix, el protocolo de comunicaciones descentralizado para mensajería instantánea. Le permite enviar mensajes de texto, vídeos y archivos de sonido a su familia, compañeros de trabajo y amigos. Usa la infraestructura de KDE y, en particular, Kirigami para proporcionar una experiencia convergente en muchas plataformas.</p>
|
||||
<p xml:lang="eu">NeoChat «Matrix»erako, bat-bateko mezularitzarako komunikazio deszentralizatuko protokolorako, bezero bat da. Zure sendiari, kide eta lagunei testu mezuak, bideo eta audio fitxategiak bidaltzeko aukera ematen dizu. «KDE Frameworks» eta bereziki «Kirigami» erabiltzen ditu plataforma anitzen artean esperientzia konbergente bat eskaintzeko.</p>
|
||||
<p xml:lang="fi">NeoChat on asiakassovellus Matrixille, hajautetulle pikaviestinyhteyskäytännölle. Sillä voi lähettää teksti-, video- ja ääniviestejä perheelle, tutuille ja ystäville. Se käyttää KDE-kehystä ja erityisesti Kirigamia tuottaakseen mukautuvan monialustaisen käyttökokemuksen.</p>
|
||||
<p xml:lang="fr">NeoChat est un client pour le protocole Matrix, un protocole décentralisé de communications pour messagerie instantané. Il vous permet d'envoyer des messages de texte, des vidéos et des fichiers audio à votre famille, vos collègues et vos amis. Il utilise les environnements de développement et plus précisément Kirigami pour fournir une expérience convergente sur plusieurs plate-formes. </p>
|
||||
<p xml:lang="gl">NeoChat é un cliente para Matrix, o protocolo de comunicación descentralizada para mensaxaría instantánea. Podes enviar mensaxes de texto, vídeos e ficheiros de son á túa familia, colegas e amizades. Usas infraestruturas de KDE e principalmente Kirigami para proporcionar unha experiencia de uso converxente para varias plataformas.</p>
|
||||
<p xml:lang="ia">NeoChat es un cliente per Matrix, le protocollo de communication decentralisate per messager instantanee. Illo te permitte inviar messager de texto, files de video e audio a tu familia, collegas e amicos usante. Illo usa KDE frameworks e super toto Kirigamii forni un experientia convergente trans platteforme multiple.</p>
|
||||
<p xml:lang="it">NeoChat è un client per Matrix, il protocollo di comunicazione decentralizzato per la messaggistica istantanea. Ti consente di inviare messaggi di testo, video e file audio a familiari, colleghi e amici. Utilizza i framework KDE e in particolare Kirigami per fornire un'esperienza convergente su più piattaforme.</p>
|
||||
<p xml:lang="ka">NeoChat არის Matrix კლიენტი. ის საშუალებას გაძლევთ გაგზავნოთ ტექსტური შეტყობინებები, ვიდეოები და აუდიო ფაილები თქვენს ოჯახს, კოლეგებსა და მეგობრებს მატრიქსის პროტოკოლის გამოყენებით.</p>
|
||||
<p xml:lang="ko">NeoChat은 분산형 인스턴트 메시징 통신 프로토콜인 Matrix 클라이언트입니다. 가족, 동료, 친구에게 텍스트 메시지, 동영상, 오디오 파일을 전송할 수 있습니다. KDE 프레임워크와 Kirigami를 사용하여 다양한 플랫폼에서 일관적인 사용자 경험을 제공합니다.</p>
|
||||
<p xml:lang="nl">NeoChat is een client voor Matrix, het gedecentraliseerde communicatieprotocol voor instant messages. Het biedt u het verzenden van tekstberichten, video's en geluidsbestanden naar uw familie, collega's en vrienden. Het gebruik KDE frameworks en het meest opmerkelijk Kirigami om een convergente ervaring te leveren op meerdere platforms.</p>
|
||||
<p xml:lang="nn">NeoChat er ein klient for Matrix, ein protokoll for desentralisert kommunikasjon. Du kan utveksla tekst, lyd og videoar med kollegaar, vennar og familie. Programmet brukar KDE Frameworks og Kirigami for å gje ei brukarflate tilpassa ulike plattformer.</p>
|
||||
<p xml:lang="pl">NeoChat jest programem do Matriksa, protokołu rozproszonego porozumiewania się w czasie rzeczywistym. Umożliwia wysyłanie wiadomości tekstowych, filmów oraz dźwięku do twojej rodziny, znajomych oraz przyjaciół. Używa szkieletów KDE i głównie Kirigami, aby zapewnić spójne wrażenia na wielu platformach</p>
|
||||
<p xml:lang="pt">O NeoChat é um cliente do Matrix. O mesmo permite-lhe enviar mensagens de texto, ficheiros de vídeo e áudio para a sua família, colegas e amigos com o protocolo Matrix. Usa as plataformas do KDE, e principalmente o Kirigami, para oferecer uma experiência convergente entre várias plataformas.</p>
|
||||
<p xml:lang="sl">Neochat je odjemalec za Matrix, decentralizirani komunikacijski protokol za takojšnje sporočanje. Omogoča vam pošiljanje besedilnih sporočil, videoposnetkov in zvočnih datotek svoji družini, sodelavcem in prijateljem. Uporablja okvire ogrodje KDE frameworks in predvsem Kirigami za zagotavljanje konvergentne izkušnje na več platformah.</p>
|
||||
<p xml:lang="sv">NeoChat är en klient för Matrix, det decentraliserade kommunikationsprotokollet för direktmeddelanden. Den låter dig skicka textmeddelanden, videor och ljudfiler till din familj, kollegor och vänner. Den använder KDE Ramverk, i synnerhet Kirigami, för att tillhandahålla en konvergent upplevelse på flera plattformar.</p>
|
||||
<p xml:lang="tr">NeoChat, anlık iletileşme için merkezi olmayan iletişim protokolü olan Matrix için bir istemcidir. Ailenize, iş arkadaşlarınıza ve arkadaşlarınıza metin iletiler, videolar ve ses dosyaları göndermenize olanak tanır. Birden çok platformda yakınsak bir deneyim sağlamak için KDE Frameworks ve en önemlilerinden Kirigami'yi kullanır.</p>
|
||||
<p xml:lang="uk">NeoChat — клієнт Matrix, децентралізованого протоколу спілкування для миттєвого обміну повідомленнями. За його допомогою ви можете надсилати текстові повідомлення, відео та звукові файли вашій родин, колегами та друзям. У програмі використано бібліотеки KDE, зокрема Kirigami, для надання однорідного середовища на декількох програмних та апаратних платформах.</p>
|
||||
<p xml:lang="x-test">xxNeoChat is a client for Matrix, the decentralized communication protocol for instant messaging. It allows you to send text messages, videos and audio files to your family, colleagues and friends. It uses KDE frameworks and most notably Kirigami to provide a convergent experience across multiple platforms.xx</p>
|
||||
<p>NeoChat aims to be a fully featured application for the Matrix specification. As such everything in the current stable specification with the notable exceptions of VoIP, threads and some aspects of End-to-End Encryption are supported. There are a few other smaller omissions due to the fact that the Matrix spec is constantly evolving but the aim remains to provide eventual support for the entire spec.</p>
|
||||
<p xml:lang="ar">يهدف نيوتشات إلى أن يكون تطبيقًا كامل الميزات لمواصفات ماتركس. على هذا النحو يتم دعم كل شيء في المواصفات المستقرة الحالية مع الاستثناءات الملحوظة لـ VoIP والخيوط وبعض جوانب التشفير من طرف إلى طرف. هناك عدد قليل من الإغفالات الصغيرة الأخرى بسبب حقيقة أن مواصفات ماتركس تتطور باستمرار ، ولكن يبقى الهدف توفير الدعم النهائي للمواصفات بأكملها.</p>
|
||||
<p xml:lang="ca">NeoChat pretén ser una aplicació amb totes les característiques per a l'especificació de Matrix. Com a tal, s'ha implementat tota l'especificació actual estable amb les notables excepcions de la VoIP, fils i alguns aspectes de l'encriptatge d'extrem a extrem. Hi ha algunes altres omissions més petites a causa del fet que l'especificació de Matrix està evolucionant constantment, però l'objectiu segueix sent proporcionar suport eventual per a tota l'especificació.</p>
|
||||
<p xml:lang="ca-valencia">NeoChat pretén ser una aplicació amb totes les característiques per a l'especificació de Matrix. Com a tal, s'ha implementat tota l'especificació actual estable amb les notables excepcions de VoIP, fils i alguns aspectes de l'encriptació d'extrem a extrem. Hi ha algunes altres omissions més xicotetes a causa del fet que l'especificació de Matrix està evolucionant constantment, però l'objectiu seguix sent proporcionar suport eventual per a tota l'especificació.</p>
|
||||
<p xml:lang="en-GB">NeoChat aims to be a fully featured application for the Matrix specification. As such everything in the current stable specification with the notable exceptions of VoIP, threads and some aspects of End-to-End Encryption are supported. There are a few other smaller omissions due to the fact that the Matrix spec is constantly evolving but the aim remains to provide eventual support for the entire spec.</p>
|
||||
<p xml:lang="eo">NeoChat celas esti plene kapabla aplikaĵo por la Matrix-specifo. Kiel tia, ĉio en la nuna stabila specifo kun la rimarkindaj esceptoj de VoIP, fadenoj kaj kelkaj aspektoj de Fin-al-Fina Ĉifrado estas subtenataj. Estas kelkaj aliaj pli malgrandaj preterlasoj pro la fakto, ke la Matrix-speco konstante evoluas, sed la celo restas provizi finfine subtenon por la tuta specifaĵo.</p>
|
||||
<p xml:lang="es">NeoChat pretende ser una aplicación con todas las funciones para la especificación de Matrix. Como tal, admite todo en la especificación estable actual, con las notables excepciones de VoIP, subprocesos y algunas funciones de cifrado de extremo a extremo. Existen algunas omisiones menos importantes debido al hecho de que la especificación de Matrix está en constante evolución, pero el objetivo sigue siendo brindar compatibilidad final con toda la especificación.</p>
|
||||
<p xml:lang="eu">«NeoChat»ek «Matrix» zehaztapenaren ezaugarri guztiak eskaintzen dituen aplikazio bat izan nahi du. Beraz, egungo zehaztapen egonkorrean dagoen guztiaren euskarria du, VoIP, hariak eta muturren artean zifratzeko salbuespen nabarmenekin. Badira beste ez-betetze txikiago batzuk, «Matrix»en zehaztapena etengabe eboluzioan dagoelako, baina azken helburua zehaztapen osoaren euskarria ematea izaten jarraitzen du.</p>
|
||||
<p xml:lang="fi">NeoChat pyrkii olemaan Matrix-määritelmän täysominaisuuksinen sovellus, joten se tukee kaikkea nykyisessä vakaassa määritelmässä muutamaa huomattavaa poikkeusta lukuun ottamatta (VoIP, säikeet ja jotkin piirteet päästä päähän -salauksessa). Joitakin pienempiäkin puutteita on Matrix-määritelmän jatkuvan kehityksen vuoksi, mutta lopputavoitteena on tarjota määritelmän täysi tuki.</p>
|
||||
<p xml:lang="fr">L'objectif de NeoChat est d'être une application complète pour le protocole Matrix. En tant que tel, tout dans la spécification stable actuelle avec les exceptions notables de VoIP, les processus et certains aspects du chiffrement de bout en bout sont pris en charge. Il y a quelques autres petites omissions en raison du fait que la spécification du protocole Matrix est en constante évolution. Cependant, l'objectif reste de fournir un soutien éventuel pour l'ensemble de la spécification.</p>
|
||||
<p xml:lang="gl">NeoChat pretende ser unha aplicación completa para a especificación de Matrix. Coas excepcións de VoIP, conversas fiadas e algúns aspectos da cifraxe de extremo a extremo, a versión estábel segue as especificacións. Existen algunhas outras pequenas omisións debido ao feito de que Matrix está en continua evolución pero a intención é implementar a especificación completa.</p>
|
||||
<p xml:lang="ia">NeoChat aspira a esser un application plenemente eminente per le specification de Matrix. Tal como omne cosas in le specification currentemente stabile con le exceptiones notabile de VOIP, threads e alcun aspectos del cryptation End-to-End es supportate. Il ha ltere pauc omissiones, debite al facto que le specification de Matrix es in evolution constante ma le aspiration remane a fornir supporto eventual per le integre specification.</p>
|
||||
<p xml:lang="it">NeoChat mira ad essere un'applicazione completa per le specifiche Matrix. Pertanto, sono supportati tutti gli elementi dell'attuale specifica stabile con le notevoli eccezioni di VoIP, conversazioni e alcuni aspetti della cifratura end-to-end. Ci sono alcune altre piccole omissioni dovute al fatto che le specifiche Matrix sono in continua evoluzione, ma l'obiettivo rimane quello di fornire un eventuale supporto per l'intera specifica.</p>
|
||||
<p xml:lang="ka">NeoChat-ი მიზნად ისახავს Matrix სპეციფიკაციის სრული განხორციელება ჰქონდეს. როგორც ასეთი, ყველაფერი მიმდინარე სპეციფიკაციიდან, VoIP-ის, ძაფებისა და გამჭოლი დაშიფვრის ზოგიერთი ასპექტის გარდა, მხარდაჭერილია. შეძლება ასევე იყოს მცირე ლაფსუსებიც იმის გამო, რომ Matrix-ის სპეციფიკაცია მუდმივად ვითარგდება, მაგრამ ჩვენი მიზანი მისი სრული მხარდაჭერაა.</p>
|
||||
<p xml:lang="ko">NeoChat은 Matrix 표준을 따르는 프로그램을 목표로 합니다. 현재 안정 버전의 표준에서 제공하는 기능의 대부분을 지원하며, VoIP, 스레드, 일부 종단간 암호화와 같은 기능은 아직 지원하지 않습니다. Matrix 표준은 계속하여 진화 중이기 때문에 일부 기능이 빠져 있을 수도 있지만 장기적으로는 전체 표준을 지원하는 것이 목표입니다.</p>
|
||||
<p xml:lang="nl">NeoChat richt zich op het volledig bieden van alle mogelijkheden van de Matrix-specificatie. Alles in de huidige stabiele specificatie met merkbare uitzondering van VoIP, gekoppelde discussies en sommige aspecten van eind-tot-eind versleuteling worden ondersteund. Er zijn een paar andere kleinere omissies vanwege het feit dat de Matrix specificatie constant evolueert maar het doel blijft het eventueel bieden van ondersteuning van de gehele specificatie.</p>
|
||||
<p xml:lang="nn">NeoChat har som mål å støtta all funksjonalitet i Matrix-spesifikasjonen. Førebels er alt i den gjeldande stabile spesifikasjonen støtta, med unntak av VoIP, trådar og nokre delar av ende-til-kryptering. Det finst òg andre småting som ikkje er støtta, sidan Matrix-spesifikasjon er i stadig endring, men målet er altså støtte for alt.</p>
|
||||
<p xml:lang="pl">NeoChat w zamyśle ma być pełnowartościową aplikacją wg wytycznych Matriksa. Z tego powodu, wszystko, co jest obecnie w stabilnych wytycznych z pominięciem VoIP, wątków i niektórych części szyfrowania Użytkownik-do-Użytkownika są obecnie obsługiwane. Pominięto też kilka mniejszych rzeczy ze względu na ciągły rozwój wytycznych Matriksa, lecz celem nadal jest zapewnienie obsługi wszystkich wytycznych.</p>
|
||||
<p xml:lang="pt">O NeoChat pretende ser uma aplicação completa para a especificação do Matrix. Como tal, tudo o que existe na especificação estável actual, com as notáveis excepções do VoIP, tópicos e alguns aspectos da Encriptação Ponto-a-Ponto, são suportados. Existem mais algumas omissões, devido ao facto que a norma do Matrix está em constante evolução, mas o objectivo continua a ser oferecer o suporte eventual para a norma por inteiro.</p>
|
||||
<p xml:lang="sl">Neochat cilja, da bi bila popolna aplikacija po specifikaciji Matrixa. Kot takšna vsebuje vse v trenutni stabilni specifikaciji z pomembnimi izjemami pri VoIP, nitih in nekaterih vidikov šifriranja od konca do konca. Obstaja nekaj drugih manjših opustitev zaradi dejstva, da se specifikacija Matrix nenehno razvija, vendar cilj ostaja zagotoviti morebitno podporo celotni specifikaciji.</p>
|
||||
<p xml:lang="sv">NeoChat har som mål att vara ett fullständigt program enligt Matrix-specifikationen. Som sådant stöds allt i den nuvarande stabila specifikationen, med de nämnvärda undantagen VoIP, trådar och några aspekter av kryptering hela vägen. Det finns några ytterligare utelämnanden på grund av att Matrix-specifikationen hela tiden utvecklas, men målet förblir att till slut erbjuda stöd för hela specifikationen.</p>
|
||||
<p xml:lang="tr">NeoChat, Matrix belirtimi için tam özellikli bir uygulama olmayı hedefler. Bu nedenle; VoIP, ileti zincirleri ve Uçtan Uca Şifreleme'nin bazı yönleri gibi dikkate değer istisnalar dışında var olan kararlı belirtimdeki her şey desteklenir. Matrix belirtiminin sürekli gelişmesi nedeniyle birkaç küçük eksiklik daha var; ancak amaç tüm belirtim için nihai destek sağlamak olmayı sürdürüyor.</p>
|
||||
<p xml:lang="uk">Метою створення NeoChat є повноцінна реалізація програми для специфікації Matrix. Як наслідок, реалізовано усе у поточній стабільній специфікації, окрім голосового інтернет-зв'язку, потоків та деяких аспектів міжвузлового шифрування. Є також декілька інших незначних прогалин через те, що специфікація Matrix постійно змінюється, але метою лишається повна підтримка специфікації.</p>
|
||||
<p xml:lang="x-test">xxNeoChat aims to be a fully featured application for the Matrix specification. As such everything in the current stable specification with the notable exceptions of VoIP, threads and some aspects of End-to-End Encryption are supported. There are a few other smaller omissions due to the fact that the Matrix spec is constantly evolving but the aim remains to provide eventual support for the entire spec.xx</p>
|
||||
<p>Due to the nature of the Matrix specification development NeoChat also supports numerous unstable features. Currently these are:</p>
|
||||
<p xml:lang="ar">نظرًا لطبيعة تطوير مواصفات ماتركس، يدعم نيوتشات أيضًا العديد من الميزات غير المستقرة وهي:</p>
|
||||
<p xml:lang="ca">A causa de la naturalesa del desenvolupament de l'especificació de Matrix, el NeoChat també implementa nombroses característiques inestables. Actualment són:</p>
|
||||
<p xml:lang="ca-valencia">A causa de la naturalea del desenvolupament de l'especificació de Matrix, NeoChat també implementa nombroses característiques inestables. Actualment són:</p>
|
||||
<p xml:lang="en-GB">Due to the nature of the Matrix specification development NeoChat also supports numerous unstable features. Currently these are:</p>
|
||||
<p xml:lang="eo">Pro la naturo de la Matrix-specifevoluo NeoChat ankaŭ subtenas multajn malstabilajn funkciojn. Nuntempe ĉi tiuj estas:</p>
|
||||
<p xml:lang="es">Debido a la naturaleza del desarrollo de la especificación de Matrix, NeoChat también permite numerosas funciones no estables, como:</p>
|
||||
<p xml:lang="eu">«Matrix» zehaztapenaren garapenaren izaeragatik, «Neo¢hat»ek ezaugarri ezegonkor ugari ere onartzen ditu. Gaur egun hauek:</p>
|
||||
<p xml:lang="fi">Matrix-määritelmän kehittyessä NeoChat tukee myös monia epävakaita ominaisuuksia. Tällä hetkellä näitä ovat:</p>
|
||||
<p xml:lang="fr">En raison de la nature du développement des spécifications du protocole Matrix, NeoChat prend également en charge de nombreuses fonctionnalités instables. Actuellement, ce sont :</p>
|
||||
<p xml:lang="gl">Debido á natureza do desenvolvemento da especificación de Matrix, NeoChat tamén inclúe varias funcionalidades non estábeis:</p>
|
||||
<p xml:lang="ia">Debite al natura del disveloppamento de specification de Matrix NeoChat tamben supporta numerose characteristicas instabile. Currentemente istes es:</p>
|
||||
<p xml:lang="it">A causa della natura dello sviluppo delle specifiche Matrix, NeoChat supporta anche numerose funzionalità instabili. Attualmente queste sono:</p>
|
||||
<p xml:lang="ka">Matrix-ის სპეციფიკაციის განვითარების ბუნების გამო NeoChat-ს ასევე აქვს უამრავი არასტაბილური ფუნქციაც. ახლა ისინია:</p>
|
||||
<p xml:lang="ko">Matrix 표준 개발의 특징으로 인하여 NeoChat은 일부 실험적인 기능을 지원합니다. 현재 지원하는 기능은 다음과 같습니다.</p>
|
||||
<p xml:lang="nl">Vanwege de aard van de ontwikkeling van de Matrix specificatie ondersteunt NeoChat ook talloze onstabiele mogelijkheden. Dit zijn nu:</p>
|
||||
<p xml:lang="nn">På grunn av måten Matrix-spesifikasjonen vert utvikla på, støttar NeoChat òg nokre uferdige funksjonar:</p>
|
||||
<p xml:lang="pl">Ze względu na sposób rozwoju Matriksa, NeoChat obsługuje także kilka niestabilnych możliwości. Obecnie są to:</p>
|
||||
<p xml:lang="pt">Devido à natureza do desenvolvimento da especificação do Matrix, o NeoChat também suporta diversas funcionalidades instáveis. De momento são:</p>
|
||||
<p xml:lang="sl">Zaradi narave razvoja specifikacije Matrixa NeoChat podpira tudi številne nestabilne zmožnosti. Trenutno so to:</p>
|
||||
<p xml:lang="sv">På grund av sättet Matrix-specifikationens utvecklas, stöder NeoChat också ett stor antal instabila funktioner. För närvarande är de:</p>
|
||||
<p xml:lang="ta">மேட்ரிக்ஸு நெறிமுறை வரையறுக்கப்படும் விதத்தின் காரணமாக, பல நிலையற்ற அம்சங்களையும் நியோச்சாட் ஆதரிக்கிறது. தற்போது ஆதரிக்கப்படுபவை:</p>
|
||||
<p xml:lang="tr">NeoChat, Matrix belirtimi geliştirmesinin doğası gereği çok sayıda kararsız özelliği de destekler. Şu anda bunlar:</p>
|
||||
<p xml:lang="uk">Через природу розробки специфікації Matrix, у NeoChat також передбачено підтримку численних нестабільних можливостей. У поточній версії цими можливостями є:</p>
|
||||
<p xml:lang="x-test">xxDue to the nature of the Matrix specification development NeoChat also supports numerous unstable features. Currently these are:xx</p>
|
||||
<ul>
|
||||
<li>Polls - MSC3381</li>
|
||||
<li xml:lang="ar">التصويت - MSC3381</li>
|
||||
<li xml:lang="ca">Enquestes - MSC3381</li>
|
||||
<li xml:lang="ca-valencia">Enquestes - MSC3381</li>
|
||||
<li xml:lang="en-GB">Polls - MSC3381</li>
|
||||
<li xml:lang="eo">Enketoj - MSC3381</li>
|
||||
<li xml:lang="es">Encuestas - MSC3381</li>
|
||||
<li xml:lang="eu">Polls - MSC3381</li>
|
||||
<li xml:lang="fi">Kyselyt – MSC3381</li>
|
||||
<li xml:lang="fr">Sondages - MSC3381</li>
|
||||
<li xml:lang="gl">Enquisas — MSC3381</li>
|
||||
<li xml:lang="ia">Inquestas - MSC3381</li>
|
||||
<li xml:lang="it">Sondaggi - MSC3381</li>
|
||||
<li xml:lang="ka">Polls - MSC3381</li>
|
||||
<li xml:lang="ko">투표 - MSC3381</li>
|
||||
<li xml:lang="nl">Polls - MSC3381</li>
|
||||
<li xml:lang="nn">Avstemmingar – MSC3381</li>
|
||||
<li xml:lang="pl">Ankiety - MSC3381</li>
|
||||
<li xml:lang="pt">Inquéritos - MSC3381</li>
|
||||
<li xml:lang="sl">Polls - MSC3381</li>
|
||||
<li xml:lang="sv">Polls - MSC3381</li>
|
||||
<li xml:lang="ta">வாக்கெடுப்புகள் - MSC3381</li>
|
||||
<li xml:lang="tr">Anketler - MSC3381</li>
|
||||
<li xml:lang="uk">Опитування - MSC3381</li>
|
||||
<li xml:lang="x-test">xxPolls - MSC3381xx</li>
|
||||
<li>Sticker Packs - MSC2545</li>
|
||||
<li xml:lang="ar">حزم الملصقات - MSC2545</li>
|
||||
<li xml:lang="ca">Paquets d'adhesius - MSC2545</li>
|
||||
<li xml:lang="ca-valencia">Paquets d'adhesius - MSC2545</li>
|
||||
<li xml:lang="en-GB">Sticker Packs - MSC2545</li>
|
||||
<li xml:lang="eo">Glumark-Pakoj - MSC2545</li>
|
||||
<li xml:lang="es">Paquetes de pegatinas - MSC2545</li>
|
||||
<li xml:lang="eu">Eranskailu paketeak - MSC2545</li>
|
||||
<li xml:lang="fi">Tarrapakkaukset – MSC2545</li>
|
||||
<li xml:lang="fr">Paquets d'auto-collants - MSC2545</li>
|
||||
<li xml:lang="gl">Paquetes de adhesivos — MSC2545</li>
|
||||
<li xml:lang="ia">Etiquetta gummate (sticker) -MSC2545</li>
|
||||
<li xml:lang="it">Pacchetti di adesivi - MSC2545</li>
|
||||
<li xml:lang="ka">სტიკერების პაკეტები - MSC2545</li>
|
||||
<li xml:lang="ko">스티커 팩 - MSC2545</li>
|
||||
<li xml:lang="nl">Sticker Packs - MSC2545</li>
|
||||
<li xml:lang="nn">Klistremerke-pakkar – MSC2545</li>
|
||||
<li xml:lang="pl">Paczki naklejek - MSC2545</li>
|
||||
<li xml:lang="pt">Pacotes de Autocolantes - MSC2545</li>
|
||||
<li xml:lang="sl">Sticker Packs - MSC2545</li>
|
||||
<li xml:lang="sv">Sticker Packs - MSC2545</li>
|
||||
<li xml:lang="ta">ஒட்டி தொகுப்புகள் - MSC2545</li>
|
||||
<li xml:lang="tr">Yapışkan Paketleri - MSC2545</li>
|
||||
<li xml:lang="uk">Пакунки наліпок - MSC2545</li>
|
||||
<li xml:lang="x-test">xxSticker Packs - MSC2545xx</li>
|
||||
<li>Location Events - MSC3488</li>
|
||||
<li xml:lang="ar">موقع الأحداث - MSC3488</li>
|
||||
<li xml:lang="ca">Esdeveniments d'ubicació - MSC3488</li>
|
||||
<li xml:lang="ca-valencia">Esdeveniments d'ubicació - MSC3488</li>
|
||||
<li xml:lang="en-GB">Location Events - MSC3488</li>
|
||||
<li xml:lang="eo">Lokaj Eventoj - MSC3488</li>
|
||||
<li xml:lang="es">Eventos de ubicación - MSC3488</li>
|
||||
<li xml:lang="eu">Kokaleku-gertaerak - MSC3488</li>
|
||||
<li xml:lang="fi">Sijaintitapahtumat – MSC3488</li>
|
||||
<li xml:lang="fr">Événements de lieu - MSC3488</li>
|
||||
<li xml:lang="gl">Localización de eventos — MSC3488</li>
|
||||
<li xml:lang="ia">Eventos de Location - MSC3488</li>
|
||||
<li xml:lang="it">Località eventi - MSC3488</li>
|
||||
<li xml:lang="ka">მდებარეობის მოვლენები - MSC3488</li>
|
||||
<li xml:lang="ko">위치 이벤트 - MSC3488</li>
|
||||
<li xml:lang="nl">Locatie gebeurtenissen - MSC3488</li>
|
||||
<li xml:lang="nn">Posisjonshendingar – MSC3488</li>
|
||||
<li xml:lang="pl">Wydarzenia w miejscach - MSC3488</li>
|
||||
<li xml:lang="pt">Eventos com Localizações - MSC3488</li>
|
||||
<li xml:lang="sl">Location Events - MSC3488</li>
|
||||
<li xml:lang="sv">Location Events - MSC3488</li>
|
||||
<li xml:lang="ta">இட நிகழ்வுகள் - MSC3488</li>
|
||||
<li xml:lang="tr">Konum Etkinlikleri - MSC3488</li>
|
||||
<li xml:lang="uk">Місцеві зустрічі - MSC3488</li>
|
||||
<li xml:lang="x-test">xxLocation Events - MSC3488xx</li>
|
||||
</ul>
|
||||
</description>
|
||||
<url type="homepage">https://apps.kde.org/neochat/</url>
|
||||
<url type="bugtracker">https://bugs.kde.org/buglist.cgi?component=General&product=NeoChat</url>
|
||||
@@ -161,20 +225,24 @@
|
||||
<category>Network</category>
|
||||
</categories>
|
||||
<developer_name>The KDE Community</developer_name>
|
||||
<developer_name xml:lang="ar">مجتمع كدي</developer_name>
|
||||
<developer_name xml:lang="ar">مجتمع كِيدِي</developer_name>
|
||||
<developer_name xml:lang="az">KDE Cəmiyyəti</developer_name>
|
||||
<developer_name xml:lang="ca">La comunitat KDE</developer_name>
|
||||
<developer_name xml:lang="ca-valencia">La comunitat KDE</developer_name>
|
||||
<developer_name xml:lang="cs">Komunita KDE</developer_name>
|
||||
<developer_name xml:lang="de">Die KDE-Gemeinschaft</developer_name>
|
||||
<developer_name xml:lang="el">Η Κοινότητα του KDE</developer_name>
|
||||
<developer_name xml:lang="en-GB">The KDE Community</developer_name>
|
||||
<developer_name xml:lang="eo">La KDE-Komunumo</developer_name>
|
||||
<developer_name xml:lang="es">La comunidad KDE</developer_name>
|
||||
<developer_name xml:lang="eu">KDE komunitatea</developer_name>
|
||||
<developer_name xml:lang="fi">KDE-yhteisö</developer_name>
|
||||
<developer_name xml:lang="fr">La communauté de KDE</developer_name>
|
||||
<developer_name xml:lang="gl">A comunidade KDE</developer_name>
|
||||
<developer_name xml:lang="hu">A KDE Közösség</developer_name>
|
||||
<developer_name xml:lang="ia">Le communitate de KDE</developer_name>
|
||||
<developer_name xml:lang="id">Komunitas KDE</developer_name>
|
||||
<developer_name xml:lang="ie">Li comunité de KDE</developer_name>
|
||||
<developer_name xml:lang="it">La comunità KDE</developer_name>
|
||||
<developer_name xml:lang="ka">KDE-ის საზოგადოება</developer_name>
|
||||
<developer_name xml:lang="ko">KDE 커뮤니티</developer_name>
|
||||
@@ -184,9 +252,11 @@
|
||||
<developer_name xml:lang="pl">Społeczność KDE</developer_name>
|
||||
<developer_name xml:lang="pt">A Comunidade do KDE</developer_name>
|
||||
<developer_name xml:lang="pt-BR">A comunidade KDE</developer_name>
|
||||
<developer_name xml:lang="ru">Сообщество KDE</developer_name>
|
||||
<developer_name xml:lang="sk">KDE Komunita</developer_name>
|
||||
<developer_name xml:lang="sl">Skupnost KDE</developer_name>
|
||||
<developer_name xml:lang="sv">KDE-gemenskapen</developer_name>
|
||||
<developer_name xml:lang="ta">கே.டீ.யீ. சமூகம்</developer_name>
|
||||
<developer_name xml:lang="tr">KDE Topluluğu</developer_name>
|
||||
<developer_name xml:lang="uk">Спільнота KDE</developer_name>
|
||||
<developer_name xml:lang="x-test">xxThe KDE Communityxx</developer_name>
|
||||
@@ -195,6 +265,12 @@
|
||||
<project_license>GPL-3.0</project_license>
|
||||
<custom>
|
||||
<value key="KDE::matrix">#neochat:kde.org</value>
|
||||
<value key="KDE::windows_store">https://www.microsoft.com/store/apps/9PNXWVNRC29H</value>
|
||||
<value key="KDE::mastodon">https://kde.social/@neochat</value>
|
||||
<value key="KDE::windows_store::StoreLogo9x16">https://invent.kde.org/network/neochat/-/raw/master/icons/windows/storelogo-720x1080.png</value>
|
||||
<value key="KDE::windows_store::StoreLogoSquare">https://invent.kde.org/network/neochat/-/raw/master/icons/windows/storelogo-1080x1080.png</value>
|
||||
<value key="KDE::windows_store::Icon">https://invent.kde.org/network/neochat/-/raw/master/icons/300-apps-neochat.png</value>
|
||||
<value key="KDE::windows_store::PromotionalArt16x9">https://invent.kde.org/network/neochat/-/raw/master/icons/windows/promoimage-1920x1080.png</value>
|
||||
</custom>
|
||||
<launchable type="desktop-id">org.kde.neochat.desktop</launchable>
|
||||
<screenshots>
|
||||
@@ -204,11 +280,104 @@
|
||||
<screenshot type="default">
|
||||
<image>https://cdn.kde.org/screenshots/neochat/application.png</image>
|
||||
</screenshot>
|
||||
<screenshot x-kde-os="windows">
|
||||
<image>https://cdn.kde.org/screenshots/neochat/NeoChat-Windows-Timeline.png</image>
|
||||
<caption>Main view with room list, chat, and room information</caption>
|
||||
<caption xml:lang="ar">العرض الرئيسة مع قائمة الغرف والدردشات و معلومات الغرفة</caption>
|
||||
<caption xml:lang="ca">Vista principal amb la llista de sales, xats i informació de les sales</caption>
|
||||
<caption xml:lang="ca-valencia">Vista principal amb la llista de sales, xats i informació de les sales</caption>
|
||||
<caption xml:lang="eo">Ĉefa vido kun ĉambra listo, babilejo kaj ĉambra informo</caption>
|
||||
<caption xml:lang="es">Vista principal con la lista de salas, chat e información de la sala</caption>
|
||||
<caption xml:lang="eu">Ikuspegi nagusia gela-zerrenda, berriketa, eta gelako informazioarekin</caption>
|
||||
<caption xml:lang="fi">Päänäkymä, jossa huoneluettelo, keskustelu ja huoneen tiedot</caption>
|
||||
<caption xml:lang="fr">Vue principale avec la liste des salons ainsi que des informations sur les salons et forums de discussions</caption>
|
||||
<caption xml:lang="gl">Vista principal coa lista de salas, a charla, e información da sala.</caption>
|
||||
<caption xml:lang="it">Vista principale con elenco delle stanze, chat e informazioni sulla stanza</caption>
|
||||
<caption xml:lang="ka">მთავარი ხედი სურათების სიით, ჩატით და ოთახის ინფორმაციით</caption>
|
||||
<caption xml:lang="ko">대화방 목록, 채팅, 대화방 정보가 표시된 주 보기</caption>
|
||||
<caption xml:lang="nl">Hoofdweergave met lijst met rooms, chat en roominformatie</caption>
|
||||
<caption xml:lang="nn">Hovudvising med romliste, pratevindauge og rominformasjon</caption>
|
||||
<caption xml:lang="pl">Główny widok z wykazem pokojów, rozmowami i szczegółami pokojów</caption>
|
||||
<caption xml:lang="pt">A área principal com a lista de salas e com informações sobre a conversa e a sala</caption>
|
||||
<caption xml:lang="sl">Glavni pogled s seznamom sob, klepetom in informacijami o sobah</caption>
|
||||
<caption xml:lang="sv">Huvudvy med rumslista, chatt, och rumsinformation</caption>
|
||||
<caption xml:lang="ta">அரங்குப்பட்டியல், உரையாடல், மற்றும் அரங்குவிவரங்களைக் கொண்டுள்ள பிரதான காட்சி</caption>
|
||||
<caption xml:lang="tr">Oda listesini, sohbet penceresini ve oda bilgisini gösteren ana görünüm</caption>
|
||||
<caption xml:lang="uk">Головна панель із списком кімнат, спілкуванням та даними щодо кімнати</caption>
|
||||
<caption xml:lang="x-test">xxMain view with room list, chat, and room informationxx</caption>
|
||||
</screenshot>
|
||||
<screenshot x-kde-os="windows">
|
||||
<image>https://cdn.kde.org/screenshots/neochat/NeoChat-Windows-Login.png</image>
|
||||
<caption>Login screen</caption>
|
||||
<caption xml:lang="ar">شاشة الدخول</caption>
|
||||
<caption xml:lang="ca">Pantalla d'inici de sessió</caption>
|
||||
<caption xml:lang="ca-valencia">Pantalla d'inici de sessió</caption>
|
||||
<caption xml:lang="eo">Ensaluta ekrano</caption>
|
||||
<caption xml:lang="es">Pantalla de inicio de sesión</caption>
|
||||
<caption xml:lang="eu">Saio-hasteko pantaila</caption>
|
||||
<caption xml:lang="fi">Kirjautumisnäkymä</caption>
|
||||
<caption xml:lang="fr">Écran de connexion</caption>
|
||||
<caption xml:lang="gl">Pantalla de identificación.</caption>
|
||||
<caption xml:lang="it">Schermata di accesso</caption>
|
||||
<caption xml:lang="ka">შესვლის ეკრანი</caption>
|
||||
<caption xml:lang="ko">로그인 화면</caption>
|
||||
<caption xml:lang="nl">Aanmeldscherm</caption>
|
||||
<caption xml:lang="nn">Innloggingsbilete</caption>
|
||||
<caption xml:lang="pl">Ekran logowania</caption>
|
||||
<caption xml:lang="pt">Ecrã de autenticação</caption>
|
||||
<caption xml:lang="sl">Prijavni zaslon</caption>
|
||||
<caption xml:lang="sv">Inloggningsfönster</caption>
|
||||
<caption xml:lang="ta">நுழைவுத் திரை</caption>
|
||||
<caption xml:lang="tr">Oturum açma ekranı</caption>
|
||||
<caption xml:lang="uk">Вікно входу</caption>
|
||||
<caption xml:lang="x-test">xxLogin screenxx</caption>
|
||||
</screenshot>
|
||||
</screenshots>
|
||||
<content_rating type="oars-1.1">
|
||||
<content_attribute id="social-chat">intense</content_attribute>
|
||||
</content_rating>
|
||||
<releases>
|
||||
<release version="23.08.0" date="2023-08-24">
|
||||
<url>https://kde.org/announcements/gear/23.08.0/#neochathttpsappskdeorgneochat</url>
|
||||
<description>
|
||||
<p>Apart from a visual overhaul, NeoChat can now display location events and also a map with the location of all the users currently broadcasting their location using Itineray's Matrix integration. Great for locating where your friends are.</p>
|
||||
</description>
|
||||
</release>
|
||||
<release version="23.04.3" date="2023-07-06"/>
|
||||
<release version="23.04.2" date="2023-06-08"/>
|
||||
<release version="23.04.1" date="2023-05-11"/>
|
||||
<release version="23.04.0" date="2023-04-20">
|
||||
<url>https://kde.org/announcements/gear/23.04.0/#neochathttpsappskdeorgneochat</url>
|
||||
<description>
|
||||
<p>NeoChat improves its design with tweaks that provide a more compact layout and a simpler menu which works better for the collapsed room list.</p>
|
||||
<p>We have also improved the video controls, added a new command /knock <room-id> to send a knock event to a room, and you can now edit a prior message inline, within the chat pane.</p>
|
||||
<p>Other usability improvements include an overhaul of the keyboard navigation and shortcuts like Ctrl+PgUp/PgDn that allow you to skip from room to room.</p>
|
||||
</description>
|
||||
<artifacts>
|
||||
<artifact type="binary" platform="x86_64-windows-msvc">
|
||||
<location>https://download.kde.org/stable/release-service/23.04.0/windows/neochat-23.04.0-512-windows-cl-msvc2019-x86_64.exe</location>
|
||||
<checksum type="sha256">85e17fdd9b128bfdd171a93b11f7a95febeff0a00df9261df11f6ff67637686d</checksum>
|
||||
<size type="download">58032464</size>
|
||||
</artifact>
|
||||
</artifacts>
|
||||
</release>
|
||||
<release version="23.01" date="2023-01-30">
|
||||
<url>https://plasma-mobile.org/2023/01/30/january-blog-post/</url>
|
||||
<description>
|
||||
<p>New features and bugfixes:</p>
|
||||
<ul>
|
||||
<li>Notifications will now be shown for all accounts, not just the active one</li>
|
||||
<li>There is a new "compact" mode for the room list</li>
|
||||
<li>You can now search in the room history</li>
|
||||
<li>Emojis and Reactions have been significantly improved</li>
|
||||
<li>Fixed several crashes around user invitations</li>
|
||||
<li>Room permission settings can now be configured</li>
|
||||
</ul>
|
||||
</description>
|
||||
</release>
|
||||
<release version="22.11" date="2022-11-30">
|
||||
<url>https://plasma-mobile.org/2022/11/30/plasma-mobile-gear-22-11/</url>
|
||||
</release>
|
||||
<release version="22.09" date="2022-09-27">
|
||||
<url>https://www.plasma-mobile.org/2022/09/27/plasma-mobile-gear-22-09/</url>
|
||||
</release>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# SPDX-License-Identifier: CC0-1.0
|
||||
# SPDX-FileCopyrightText: 2020 Tobias Fella <fella@posteo.de>
|
||||
# SPDX-FileCopyrightText: 2020 Tobias Fella <tobias.fella@kde.org>
|
||||
[Desktop Entry]
|
||||
Version=1.5
|
||||
Name=NeoChat
|
||||
@@ -9,14 +9,18 @@ Name[ca]=NeoChat
|
||||
Name[ca@valencia]=NeoChat
|
||||
Name[cs]=NeoChat
|
||||
Name[de]=NeoChat
|
||||
Name[el]=NeoChat
|
||||
Name[en_GB]=NeoChat
|
||||
Name[eo]=NeoChat
|
||||
Name[es]=NeoChat
|
||||
Name[eu]=NeoChat
|
||||
Name[fi]=NeoChat
|
||||
Name[fr]=NeoChat
|
||||
Name[gl]=NeoChat
|
||||
Name[hu]=NeoChat
|
||||
Name[ia]=Neochat
|
||||
Name[id]=NeoChat
|
||||
Name[ie]=NeoChat
|
||||
Name[it]=NeoChat
|
||||
Name[ka]=NeoChat
|
||||
Name[ko]=NeoChat
|
||||
@@ -28,6 +32,7 @@ Name[pl]=NeoChat
|
||||
Name[pt]=NeoChat
|
||||
Name[pt_BR]=NeoChat
|
||||
Name[ro]=NeoChat
|
||||
Name[ru]=NeoChat
|
||||
Name[sk]=NeoChat
|
||||
Name[sl]=NeoChat
|
||||
Name[sv]=NeoChat
|
||||
@@ -43,14 +48,18 @@ GenericName[ca]=Client de Matrix
|
||||
GenericName[ca@valencia]=Client de Matrix
|
||||
GenericName[cs]=Klient protokolu Matrix
|
||||
GenericName[de]=Matrix-Programm
|
||||
GenericName[el]=Εφαρμογή του Matrix
|
||||
GenericName[en_GB]=Matrix Client
|
||||
GenericName[eo]=Matrix-Kliento
|
||||
GenericName[es]=Cliente para Matrix
|
||||
GenericName[eu]=Matrix bezeroa
|
||||
GenericName[fi]=Matrix-asiakas
|
||||
GenericName[fr]=Client « Matrix »
|
||||
GenericName[gl]=Cliente de Matrix
|
||||
GenericName[hu]=Matrix kliens
|
||||
GenericName[ia]=Cliente de Matrice
|
||||
GenericName[id]=Klien Matrix
|
||||
GenericName[ie]=Cliente de Matrix
|
||||
GenericName[it]=Client Matrix
|
||||
GenericName[ka]=Matrix -ის კლიენტი
|
||||
GenericName[ko]=Matrix 클라이언트
|
||||
@@ -62,6 +71,7 @@ GenericName[pl]=Program Matriksa
|
||||
GenericName[pt]=Cliente de Matrix
|
||||
GenericName[pt_BR]=Cliente Matrix
|
||||
GenericName[ro]=Client Matrix
|
||||
GenericName[ru]=Клиент Matrix
|
||||
GenericName[sk]=Matrix Client
|
||||
GenericName[sl]=Odjemalec Matrix
|
||||
GenericName[sv]=Matrix-klient
|
||||
@@ -76,25 +86,30 @@ Comment[az]=Matrix protokolu üçün müştəri
|
||||
Comment[ca]=Client per al protocol Matrix
|
||||
Comment[ca@valencia]=Client per al protocol Matrix
|
||||
Comment[de]=Programm für das Matrix-Protokoll
|
||||
Comment[el]=Πελάτης για το πρωτόκολλο Matrix
|
||||
Comment[en_GB]=Client for the Matrix protocol
|
||||
Comment[eo]=Kliento por la Matrix-protokolo
|
||||
Comment[es]=Cliente para el protocolo Matrix
|
||||
Comment[eu]=Matrix protokolorako bezeroa
|
||||
Comment[fi]=Asiakas Matrix-yhteyskäytännölle
|
||||
Comment[fr]=Client pour le protocole « Matrix »
|
||||
Comment[gl]=Cliente para o protocolo Matrix.
|
||||
Comment[hu]=Kliens a Matrix protokollhoz
|
||||
Comment[ia]=Cliente per le protocollo de Matrix
|
||||
Comment[id]=Klien untuk protokol Matrix
|
||||
Comment[ie]=Un cliente del protocol Matrix
|
||||
Comment[it]=Client per il protocollo Matrix
|
||||
Comment[ka]=კლიენტი Matrix-ის პროტოკოლისთვის
|
||||
Comment[ko]=Matrix 프로토콜용 클라이언트
|
||||
Comment[lt]=Matrix protokolo kliento programa
|
||||
Comment[nl]=Client voor het Matrix-protocol
|
||||
Comment[nn]=Lynmeldingsklient for Matrix-protokollen
|
||||
Comment[nn]=Klient for Matrix-protokollen
|
||||
Comment[pa]=ਮੈਟਰਿਕਸ ਪਰੋਟੋਕਾਲ ਲਈ ਕਲਾਈਂਟ ਹੈ
|
||||
Comment[pl]=Program obsługi protokołu Matriksa
|
||||
Comment[pt]=Cliente para o protocolo Matrix
|
||||
Comment[pt_BR]=Cliente para o protocolo Matrix
|
||||
Comment[ro]=Client pentru protocolul Matrix
|
||||
Comment[ru]=Клиент для протокола Matrix
|
||||
Comment[sk]=Klient protokolu Matrix
|
||||
Comment[sl]=Odjemalec za protokol Matrix
|
||||
Comment[sv]=Klient för protokollet Matrix
|
||||
|
||||
5987
po/ar/neochat.po
5987
po/ar/neochat.po
File diff suppressed because it is too large
Load Diff
6511
po/az/neochat.po
6511
po/az/neochat.po
File diff suppressed because it is too large
Load Diff
122
po/ca/docs/neochat/man-neochat.1.docbook
Normal file
122
po/ca/docs/neochat/man-neochat.1.docbook
Normal file
@@ -0,0 +1,122 @@
|
||||
<?xml version="1.0" ?>
|
||||
<!DOCTYPE refentry PUBLIC "-//KDE//DTD DocBook XML V4.5-Based Variant V1.1//EN" "dtd/kdedbx45.dtd" [
|
||||
<!ENTITY % Catalan "INCLUDE">
|
||||
]>
|
||||
<!--
|
||||
SPDX-FileCopyrightText: 2022 Carl Schwan <carl@carlschwan.eu>
|
||||
SPDX-License-Identifier: CC-BY-SA-4.0
|
||||
-->
|
||||
|
||||
<refentry lang="&language;">
|
||||
<refentryinfo>
|
||||
<title
|
||||
>Manual d'usuari del NeoChat</title>
|
||||
<author
|
||||
><firstname
|
||||
>Carl</firstname
|
||||
><surname
|
||||
>Schwan</surname
|
||||
> <contrib
|
||||
>Pàgina «man» del NeoChat.</contrib
|
||||
> <email
|
||||
>carl@carlschwan.eu</email
|
||||
></author>
|
||||
<date
|
||||
>1 de novembre de 2022</date>
|
||||
<releaseinfo
|
||||
>22.09</releaseinfo>
|
||||
<productname
|
||||
>NeoChat</productname>
|
||||
</refentryinfo>
|
||||
|
||||
<refmeta>
|
||||
<refentrytitle>
|
||||
<command
|
||||
>neochat</command>
|
||||
</refentrytitle>
|
||||
<manvolnum
|
||||
>1</manvolnum>
|
||||
</refmeta>
|
||||
|
||||
<refnamediv>
|
||||
<refname
|
||||
>neochat</refname>
|
||||
<refpurpose
|
||||
>Client per a interactuar amb el protocol de missatgeria Matrix</refpurpose>
|
||||
</refnamediv>
|
||||
<!-- body begins here -->
|
||||
<refsynopsisdiv id='synopsis'>
|
||||
<cmdsynopsis
|
||||
><command
|
||||
>neochat</command
|
||||
> <arg choice="opt"
|
||||
><replaceable
|
||||
>URI</replaceable
|
||||
></arg
|
||||
> </cmdsynopsis>
|
||||
</refsynopsisdiv>
|
||||
|
||||
|
||||
<refsect1 id="description">
|
||||
<title
|
||||
>Descripció</title>
|
||||
<para
|
||||
>El <command
|
||||
>neochat</command
|
||||
> és una aplicació de xat per al protocol Matrix que funciona tant a l'escriptori com al mòbil. </para>
|
||||
</refsect1>
|
||||
|
||||
<refsect1 id="options"
|
||||
><title
|
||||
>Opcions</title>
|
||||
<variablelist>
|
||||
<varlistentry>
|
||||
<term
|
||||
><option
|
||||
>URI</option
|
||||
></term>
|
||||
<listitem>
|
||||
<para
|
||||
>L'URI de Matrix per a un usuari o una sala. P. ex. matrix:u/usuari:exemple.org o matrix:r/root:exemple.org. Això farà que el NeoChat intenti obrir la sala o conversa indicada. </para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
</variablelist>
|
||||
</refsect1>
|
||||
|
||||
<refsect1 id="bug">
|
||||
<title
|
||||
>Informar dels errors</title>
|
||||
<para
|
||||
>Podeu informar dels errors o de peticions de característiques noves a <ulink url="https://bugs.kde.org/enter_bug.cgi?product=NeoChat&component=General"
|
||||
>https://bugs.kde.org/enter_bug.cgi?product=NeoChat&component=General</ulink
|
||||
></para>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
<title
|
||||
>Vegeu també</title>
|
||||
<simplelist>
|
||||
<member
|
||||
>Una llista de les preguntes més freqüents quan a Matrix <ulink url="https://matrix.org/faq/"
|
||||
>https://matrix.org/faq/</ulink
|
||||
> </member>
|
||||
<member
|
||||
>kf5options(7)</member>
|
||||
<member
|
||||
>qt5options(7)</member>
|
||||
</simplelist>
|
||||
</refsect1>
|
||||
|
||||
<refsect1 id="copyright"
|
||||
><title
|
||||
>Copyright</title>
|
||||
<para
|
||||
>Copyright © 2020-2022 Tobias Fella </para>
|
||||
<para
|
||||
>Copyright © 2020-2022 Carl Schwan </para>
|
||||
<para
|
||||
>Llicència: Publica general de GNU versió 3 o posterior <<ulink url="https://www.gnu.org/licenses/gpl-3.0.html"
|
||||
>https://www.gnu.org/licenses/gpl-3.0.html</ulink
|
||||
>></para>
|
||||
</refsect1>
|
||||
</refentry>
|
||||
5532
po/ca/neochat.po
5532
po/ca/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
5538
po/cs/neochat.po
5538
po/cs/neochat.po
File diff suppressed because it is too large
Load Diff
6216
po/da/neochat.po
6216
po/da/neochat.po
File diff suppressed because it is too large
Load Diff
5222
po/de/neochat.po
5222
po/de/neochat.po
File diff suppressed because it is too large
Load Diff
4569
po/el/neochat.po
Normal file
4569
po/el/neochat.po
Normal file
File diff suppressed because it is too large
Load Diff
6104
po/en_GB/neochat.po
6104
po/en_GB/neochat.po
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user