Compare commits
874 Commits
f1dda43e66
...
v0.2025.10
| Author | SHA1 | Date | |
|---|---|---|---|
| 4052e3235f | |||
| 13302ad1c7 | |||
| 0f8687e473 | |||
| 9399ccd684 | |||
| b3604039fa | |||
| 732089da2c | |||
| b3e7e4b196 | |||
| 09a0cfd349 | |||
| 5bf7346321 | |||
| dd558c57e0 | |||
| 5647196924 | |||
| 49b1834bc6 | |||
| d111647ea8 | |||
| d7580dab9b | |||
| 28db8a8d5f | |||
| e64d5617e7 | |||
| acd114650a | |||
| 7a47ffaa61 | |||
| 42f7f66f35 | |||
| b2b4ffeeae | |||
| 26de1f7daa | |||
| 07605933dc | |||
| 4bdc7ec616 | |||
| 8ca64550e5 | |||
| 25dbac804c | |||
| 6ab3fd168b | |||
| 94858e2371 | |||
| 6d13502e94 | |||
| 77001e595c | |||
| 6fad20ffa3 | |||
| 00fb6c9839 | |||
| 97fcf72d63 | |||
| 5d8d02515d | |||
| 859fe1feb0 | |||
| 8f61d83f41 | |||
| 6423b3e479 | |||
| 2bc8cec8a2 | |||
| b49a6cd685 | |||
| 2885380f40 | |||
| 2ec3b6a249 | |||
| 3ef795452d | |||
| 479d87c8b8 | |||
| a56077dcc7 | |||
| d3f4587c3b | |||
| 623705b7a1 | |||
| 8f87f4751d | |||
| 2ac6dfde9d | |||
| 81ade7a400 | |||
| 63f7ff9f27 | |||
| 8a0fa17a79 | |||
| 0ead5ed967 | |||
| 53261a6fbc | |||
| c60ff86a4d | |||
| 83a0b017c5 | |||
| 3746622a11 | |||
| ccd50cf59f | |||
| 93680eb43d | |||
| 3ae4b7086a | |||
| 446b1f8600 | |||
| 00fd208a2c | |||
| e574d03716 | |||
| c1f3116c9d | |||
| 3aec7e6c14 | |||
| 9f0020dec8 | |||
| 6e78ad9729 | |||
| 44d84a9b2a | |||
| ac7809415c | |||
| 675cecaa20 | |||
| 5d179cc088 | |||
| d905618590 | |||
| 3fd9bc0b18 | |||
| 39abee7f73 | |||
| b770619111 | |||
| 1c44857da4 | |||
| bca4440867 | |||
| 4855543961 | |||
| cb3d6a98b9 | |||
| ada67a13d3 | |||
| f4c928f26e | |||
| 91fd515d39 | |||
| be6e841d3d | |||
| af6afa6903 | |||
| 6ab5d2a28d | |||
| 4be033f288 | |||
| c550f92003 | |||
| ed836b3ee0 | |||
| ac7a43abf4 | |||
| 49f19fce91 | |||
| b2197eb8e9 | |||
| 5fbc2cae1c | |||
| 730abb49ce | |||
| edccab054a | |||
| e8210c6fdd | |||
| 55d69d7c13 | |||
| 50fb18d4ff | |||
| 77b1ea1fc8 | |||
| 61501a9b64 | |||
| c1507adac5 | |||
| 45fb9eda1c | |||
| 982a61f4bf | |||
| 18e5b41663 | |||
| 910c39cbd0 | |||
| 9952dfd49d | |||
| 00f75d5382 | |||
| d78828554b | |||
| b84b561109 | |||
| a618815500 | |||
| e1f3dc6ae4 | |||
| f378db6c6f | |||
| 7cec0f7d61 | |||
| f902d0374c | |||
| b5f0a0c4f7 | |||
| 00623cea09 | |||
| ed4f1d6f2c | |||
| 73f4a3407f | |||
| 6f11318e84 | |||
| e88ee91f0e | |||
| 3f8daf257c | |||
| dc387acadc | |||
| 68aa41ab96 | |||
| 85b23437b3 | |||
| c59fba817d | |||
| c3415ab75c | |||
| f1d0151d71 | |||
| 3c5c1756d1 | |||
| 6a6b65d1b3 | |||
| 81bd54dbe6 | |||
| 6a1bb0d3bc | |||
| 705e8b553f | |||
| e4729b22f2 | |||
| 662112551a | |||
| 38fe88aab8 | |||
| 578c51faa0 | |||
| a3ccc73b81 | |||
| 7312f4d43a | |||
| 8b546c7e02 | |||
| c0b6ff2e64 | |||
| 638b7cc1e5 | |||
| 05e54e1be0 | |||
| 4c3299ead0 | |||
| 1ef56b35ad | |||
| 061e79c295 | |||
| 5edfe732b1 | |||
| a8f9b67f71 | |||
| de7fbf1eb7 | |||
| a51a3d7e43 | |||
| 433b3b1003 | |||
| 6703c5b584 | |||
| 5f729efabe | |||
| b2085b3f28 | |||
| 2f893494b0 | |||
| e26af21f63 | |||
| 7e1d738f8d | |||
| 199448e11e | |||
| fdaabab807 | |||
| ca4560c5c9 | |||
| 2478f3064d | |||
| e9b8b43e7c | |||
| 951155f1b6 | |||
| 1b678175ef | |||
| 8eb1f40eec | |||
| 235887b3bf | |||
| 0b3d66dd48 | |||
| beb9ef3754 | |||
| 9f6a480736 | |||
| b3bac2927d | |||
| ef389f2ba2 | |||
| ef21dc6ae8 | |||
| 6e55b6b49e | |||
| db115ef1bd | |||
| 678838dbd5 | |||
| 586f87625d | |||
| 1542370f9b | |||
| 1f7d5968c7 | |||
| 39e51f7790 | |||
| 052663efbe | |||
| 8f84ff2611 | |||
| 37e1c5d97b | |||
| cef526bcf3 | |||
| 6af36cafa9 | |||
| fca859d93d | |||
| 2178300d8d | |||
| 636bdcce6b | |||
| 94b7703ca9 | |||
| a391dd1316 | |||
| b6ba5211b7 | |||
| 8e8e130045 | |||
| 1f40bc1a0f | |||
| 5437212222 | |||
| a8ab845cd2 | |||
| 8cee6dc98b | |||
| 70c2b73414 | |||
| 98013c4422 | |||
| e9e22b762d | |||
| 620db19936 | |||
| 94a79dd62c | |||
| b56c3efde0 | |||
| 066827f8f1 | |||
| c3b65d9cd8 | |||
| a15b916b06 | |||
| 31d0a5c233 | |||
| 140179e80a | |||
| 53cba2d7e4 | |||
| e54312d3b8 | |||
| cadc27b7b5 | |||
| 388b829ec1 | |||
| 67861f0f33 | |||
| a1f1eb34d5 | |||
| 2a6789063e | |||
| cbf1273a55 | |||
| 8143a23ced | |||
| 3c17810747 | |||
| bea7a2e9ed | |||
| 2f0a2ac6b0 | |||
| 18908b6b56 | |||
| b135a210cc | |||
| 3a2a829940 | |||
| e56dd2dd2d | |||
| 3f41a48bc7 | |||
| 65ed53281a | |||
| 1121557a2e | |||
| d4a7b86ee7 | |||
| 626c18b04e | |||
| bfa97ed7c7 | |||
| deae4d5367 | |||
| 899605c860 | |||
| dc9a279991 | |||
| 2a53892581 | |||
| 6bef0eb764 | |||
| 462b40640c | |||
| 72e1b2025c | |||
| fc7c4b1257 | |||
| 6c22c59056 | |||
| 94c2b1184f | |||
| 45231d703d | |||
| 7882fcbe8f | |||
| 3bbc8c4d35 | |||
| 8ae10dc80b | |||
| 9b11c2c629 | |||
| e2a231fb4a | |||
| 8a9502d1f2 | |||
| 534438df63 | |||
| 45a4feec96 | |||
| aa7a32395e | |||
| ab9f57f044 | |||
| 4040d6aa08 | |||
| 1c96f5c35e | |||
| 4d3e42812d | |||
| f7b3711d4f | |||
| 2408e076ff | |||
| 6f71ffb477 | |||
| 214433f36a | |||
| 309b22732e | |||
| 6fe7687b2a | |||
| a8cbf757ff | |||
| 4a4bedfe2b | |||
| 051291f725 | |||
| d2b338095f | |||
| 899827a8f2 | |||
| 5fcbe3d6a9 | |||
| a0a40e6cb2 | |||
| bb1190e3f8 | |||
| 0a3baed1da | |||
| 4931c489ed | |||
| 996f9abaa2 | |||
| 08c097e176 | |||
| daa861a98b | |||
| a25d08fd76 | |||
| 392d31cc53 | |||
| 92926fa8df | |||
| 61ae9ae465 | |||
| 89622697d5 | |||
| 17694f5646 | |||
| 5a1303149f | |||
| 8a0e190a86 | |||
| 0d7dfd8c9e | |||
| f979ff7050 | |||
| e3fcdea362 | |||
| 476fec2757 | |||
| 53c215399b | |||
| 2c330802da | |||
| 851d7046ea | |||
| c0019d7246 | |||
| 7688e4d3a8 | |||
| ef58749ce3 | |||
| 35941a7ddc | |||
| 1f2664e5a8 | |||
| 35656a5c34 | |||
| 799f22e989 | |||
| e226a37251 | |||
| 8e3bc9d700 | |||
| 58c3e6c2ab | |||
| 0dc148bfea | |||
| 3eff1b08a9 | |||
| 02d789471f | |||
| d367d47c4d | |||
| c93b8fc045 | |||
| eb9377e21d | |||
| a1764eee42 | |||
| 86ef74e20d | |||
| 4de53b9926 | |||
| 99a195a3fd | |||
| f1ced31f69 | |||
| b3cedf2baa | |||
| 3bf19fabda | |||
| cf81ebe8ad | |||
| 278b5566a1 | |||
| e8c1390f09 | |||
| 3c04abda45 | |||
| 2597f99ccf | |||
| 9d3a07c1cf | |||
| bdfd8925b5 | |||
| 1a4d1985f4 | |||
| 6273f3ea53 | |||
| 5bdc6fa471 | |||
| 3ba41291db | |||
| 0867811952 | |||
| 8d961cd805 | |||
| 97cea7b40b | |||
| 4106834db8 | |||
| a4a8f7cab2 | |||
| 9e209ee800 | |||
| ddfa84f040 | |||
| 6b3a6ec7c1 | |||
| 4d037c02bf | |||
| deaeab10d8 | |||
| 2a5375b1e7 | |||
| e7a03e3283 | |||
| efb3a12dcc | |||
| 3830d695d7 | |||
| f36620927b | |||
| 5423cbd628 | |||
| abde709e54 | |||
| 27f2d319ab | |||
| 66234b14bc | |||
| 6a9167e565 | |||
| 3c60f8ca06 | |||
| c26bf5c112 | |||
| 41cbde934a | |||
| 946941d95e | |||
| 50f0104239 | |||
| 40fa7edadf | |||
| d6926569c6 | |||
| a8bba324ca | |||
| 5bba5776b3 | |||
| 8104f6f228 | |||
| 3f4738e593 | |||
| 1516e17f5d | |||
| 676d2702b7 | |||
| 5d39548964 | |||
| 67d458bd38 | |||
| d9684c7d62 | |||
| 5a818d2119 | |||
| 6f96d4ce65 | |||
| f72395756a | |||
| 38d746b310 | |||
| f7270987ea | |||
| 6f565c0f0a | |||
| 7f252e79b6 | |||
| ba2bb17638 | |||
| bc7c658293 | |||
| 4d84e69bb5 | |||
| 03fac74908 | |||
| 5252ff1ecf | |||
| 20100d3fd4 | |||
| dd3b2656ad | |||
| 657f25e22b | |||
| 8be354fc49 | |||
| e574758340 | |||
| 40cf519492 | |||
| 0e4fda54e9 | |||
| 868f91e1ef | |||
| b9000c154f | |||
| 894c72a82f | |||
| c128cfc25c | |||
| 36c88b463c | |||
| 8a66e74074 | |||
| ea60b165da | |||
| 1011e0026b | |||
| 9462521287 | |||
| 576022c41a | |||
| 70c38b7ea8 | |||
| 36370f2dea | |||
| db9bf7f7bd | |||
| fa7aef0c37 | |||
| b135ea17f6 | |||
| 4b1643bc47 | |||
| 240a8ce9c7 | |||
| 8928e8722b | |||
| d692734e55 | |||
| 50197198b4 | |||
| 1ee1107c93 | |||
| cf90533b6c | |||
| f0211f621e | |||
| d9693af89b | |||
| 13722232fb | |||
| 0bcb033349 | |||
| e92c439724 | |||
| 7f34b585d3 | |||
| d7e9fd918a | |||
| 9899c0c5e2 | |||
| c50de0b0f0 | |||
| bb7d2d7ae0 | |||
| 862d172ca8 | |||
| 3671051d0e | |||
| 223e20cbbc | |||
| 9af4312561 | |||
| 934e40240e | |||
| edb1980387 | |||
| bb7b04013f | |||
| 26a3007268 | |||
| 5de2b09596 | |||
| 3660577a23 | |||
| 98b4c7cf04 | |||
| 427a7b8d25 | |||
| 67b84830cd | |||
| 973cd53266 | |||
| 1afdbe6932 | |||
| 942f582329 | |||
| 951a80389a | |||
| b7ecfc9925 | |||
| 59e389d793 | |||
| 2ec047cc00 | |||
| ee33f54745 | |||
| 7a79534ca8 | |||
| a74a9fc821 | |||
| e2c388b9db | |||
| 0f573ce09e | |||
| bc70e41b7c | |||
| f500e14aa3 | |||
| 8b47938238 | |||
| 8912212d8e | |||
| 6a346bf940 | |||
| b5bdae4611 | |||
| 6590da5793 | |||
| eb2b426ec7 | |||
| 4864a0411f | |||
| 68590cae33 | |||
| abf2bbaec2 | |||
| 0da7e2722f | |||
| 60d4b06057 | |||
| 4c3df34950 | |||
| 7737e60b52 | |||
| 71e816bc13 | |||
| c74f90ef04 | |||
| 26cb7e5a17 | |||
| 2bad6672d8 | |||
| 71c4011526 | |||
| 5e81078f59 | |||
| 31b78e74df | |||
| 2ff689aab0 | |||
| 0a6f0ed3f7 | |||
| bfec46673d | |||
| 3a16614c72 | |||
| 9c9efb845c | |||
| 6192f1b94d | |||
| ce451b2449 | |||
| 8534e16469 | |||
| f6cc6f2eae | |||
| 0904425221 | |||
| a729886522 | |||
| e5dfedc7d1 | |||
| f02423d084 | |||
| 8f4b6e83eb | |||
| 3029919553 | |||
| ac67db0591 | |||
| 1e08838f5b | |||
|
d814f7ee77
|
|||
| 7edfb9d386 | |||
| 6928d6caba | |||
| 1a626875cf | |||
| 6eb3b64334 | |||
| 09f3595e93 | |||
| 11e89622d4 | |||
| 0fa8acc264 | |||
| afc7c64ed8 | |||
| c794c1b885 | |||
| 6247529799 | |||
| ad3eedc1fb | |||
| 1cfac3cae6 | |||
| 478bcd5d13 | |||
| 6932da69b3 | |||
| 857f47bf55 | |||
| 373d742751 | |||
| 575622c522 | |||
| a3e86ccb1d | |||
| e491798ff1 | |||
| 15df4ac236 | |||
| 017a74c4e4 | |||
| 6e5b1127f3 | |||
| 95f4f88949 | |||
| 92858882d7 | |||
| 4cba95ec8c | |||
| bbb2f89d85 | |||
| 88213038b2 | |||
| cc18b41e76 | |||
| f16127a238 | |||
| 2a6ecfaede | |||
| cf4a09bf03 | |||
| 20e60262fd | |||
| 9e3928c976 | |||
| 95d8768545 | |||
| 8679d09040 | |||
| 4cc5c6acb3 | |||
| 6f9b548b1a | |||
| fbff3386a9 | |||
| e5899fca58 | |||
| dddec489b9 | |||
| b4049eaeaa | |||
| b3fd724b5a | |||
| aca25be86a | |||
| 0f8cbdac57 | |||
| 630219d667 | |||
| fef268e434 | |||
| e18dcf3a48 | |||
| e019320146 | |||
| 65b31e14f9 | |||
| 9cb5cbcde9 | |||
| 25914ff5a7 | |||
| c4af799279 | |||
| 3f8f0e14f4 | |||
| 5414b30e7f | |||
| 7aee897c1b | |||
| 4060f9cc11 | |||
| 5b526cbf5b | |||
| f5065ff42b | |||
| 86b5546f5f | |||
| 43ae2a293b | |||
| b8ddbd4255 | |||
| 87cdba1db8 | |||
| df83187e33 | |||
| eccdbf29ab | |||
| 28d181f8bc | |||
| d386daf2ff | |||
| 0c1e116c1e | |||
| bb0ed67827 | |||
| b111d06851 | |||
| 79388845ea | |||
| 99faef2e77 | |||
| 21fffbfe10 | |||
| 64fb9f0c2a | |||
| a42e0bef2c | |||
| 45a09006e1 | |||
| 240484be4c | |||
| 22f4d115e3 | |||
| 32920e0e5d | |||
| f03a5918d1 | |||
| dd1870b52a | |||
| f0c1a8f98f | |||
| 0c181d7e7c | |||
| e3dc0e833a | |||
| 6de875edea | |||
| 986a55173f | |||
| 59c8cabf02 | |||
| a33b9fab07 | |||
| f8a725e1e7 | |||
| b3ab3af01b | |||
| 79f9463e56 | |||
| 4257b2ed51 | |||
| 6488ab60ec | |||
| 18bd3dfcf9 | |||
| ec114e160d | |||
| de033af18f | |||
| e971c6fcb7 | |||
| d3c465391c | |||
| f1fa19593d | |||
| 60b6f9c73e | |||
| 55b95ddecb | |||
| 0800a251b2 | |||
| 82cf7a80eb | |||
| 379f3d12eb | |||
| e52972d4d4 | |||
| 1a0ca4dec2 | |||
| 0bac9d8d5a | |||
| a9608363c5 | |||
| f1a2c5ae8e | |||
| 192a81ede7 | |||
| 916aa5abbd | |||
| d4a5cc6eee | |||
| d19605cc8d | |||
| 8d529327a4 | |||
| 697e2f2071 | |||
| f7fb112f21 | |||
| b2c0211190 | |||
| c59e0ea6e5 | |||
| 6c2fc1444d | |||
| 94d969fe46 | |||
| a7bc3d301c | |||
| 18bab849f7 | |||
| 04878fcc30 | |||
| ffe1299548 | |||
| 64bdbf5725 | |||
| b82deb557f | |||
| d529a48a11 | |||
| b711e4e6bd | |||
| 186eecfbff | |||
| d766c33f59 | |||
| 2c9257f1a8 | |||
| 71ff604f90 | |||
| 83e00763ea | |||
| 5b647e0937 | |||
| e0444510f4 | |||
| 82e876a892 | |||
| c728e05032 | |||
| c7a8ce7060 | |||
| 762b4339fe | |||
| f824b8988e | |||
| 6280d6d167 | |||
| 4f18e744b4 | |||
| 01d8f720e8 | |||
| 18cf058af3 | |||
| e2406df367 | |||
| 1fd669bdb3 | |||
| f6add12c80 | |||
| 0f643bfe39 | |||
| 15be498e4b | |||
| fba465dd62 | |||
| 19dbe354e7 | |||
| fca5d37b7e | |||
| aa04ad2dc2 | |||
| 7ef4d814ef | |||
| 3f3deb665c | |||
| 97fc22ce57 | |||
| 616f3ad76d | |||
| faca63946c | |||
| 57bae341a2 | |||
| fd09a766d2 | |||
| 11564a5292 | |||
| ac12e350bf | |||
| 2def15337d | |||
| 3e3d58a4a9 | |||
| 5ce4f55228 | |||
| 21788fc7b0 | |||
| a1c4382fde | |||
| 364e95698e | |||
| 6eec142499 | |||
| a8f6b3a39a | |||
| 250933bf41 | |||
| 56c77c781a | |||
| f7602b39a1 | |||
| 8c86092356 | |||
| db0a4bff77 | |||
| e198ff9cb1 | |||
| b8eaa5cf97 | |||
| 0d597721bf | |||
| 003e0caada | |||
| 053637cfb4 | |||
| 8178213f1a | |||
| b4222a41de | |||
| f28e409ea5 | |||
| 287c6c06e1 | |||
| 8216bdb4b3 | |||
| aa15da50ab | |||
| 02759c6f83 | |||
| 6b0c49752c | |||
| 2e4f792fc3 | |||
| 17eba059f0 | |||
| e59a00922b | |||
| 872201c886 | |||
| 3352098284 | |||
| d0bbd7f24f | |||
| 7f87714b58 | |||
| 5594bee618 | |||
| c469ef23e6 | |||
| f6e74f2526 | |||
| 10b6e9c537 | |||
| 3f27af30b7 | |||
| 23db09f9b7 | |||
| d1b7681efc | |||
| 61ad405ad8 | |||
| aff98110e0 | |||
| 2f36db9142 | |||
| aa86ee1066 | |||
| dbbcce8165 | |||
| 1ed066ef0f | |||
| 763f7d45d8 | |||
| 2328f3afb5 | |||
| 2223245861 | |||
| 36226b01cd | |||
| da31f9cadd | |||
| 9da4857066 | |||
| 75c71135ba | |||
| 0cb5025a16 | |||
| 44d9f69434 | |||
| 3f343b283b | |||
| 03a28fc3c5 | |||
| 3513619221 | |||
| 0c9f5769d3 | |||
| 587a666ab6 | |||
| f26deea508 | |||
| b8e19040b5 | |||
| 7d9e0f4080 | |||
| 16ce7fbc7b | |||
| 639fce376a | |||
| 3cdbac5c22 | |||
| 3dcafdf403 | |||
| cd2fe9f8d9 | |||
| fd40596ce7 | |||
| 7ecda69703 | |||
| a3b76cd5c2 | |||
| 54df862998 | |||
| 301b7a4911 | |||
| e0a048abe6 | |||
| 671e3e19ff | |||
| 0c394c2e61 | |||
| 4ecbb5234c | |||
| 98f1700049 | |||
| 2f0b4a0187 | |||
| f66c6ed0c3 | |||
| 5d9785ac2d | |||
| bb97a8cccc | |||
| 571cf5b5b8 | |||
| 1974ed1c03 | |||
| 98275f7c87 | |||
| eca8726909 | |||
| baf125c450 | |||
| efcc710d91 | |||
| 5980ee4c86 | |||
| db9b7a22c2 | |||
| 5e24d4f322 | |||
| 2dd32cdce2 | |||
| 9cddd93dad | |||
| 4127898655 | |||
| 45d48483d0 | |||
| 852c25296a | |||
| aea631138e | |||
| 683fdbb02a | |||
| c3bbab35e2 | |||
| ba8941046e | |||
| d202f4e00d | |||
| 42da5d8d32 | |||
| 5af3533598 | |||
| 7843168fad | |||
| 8f51eb63b0 | |||
| 855f5f7af4 | |||
| c85dd2655c | |||
| fb0e4060cd | |||
| 707b4990a6 | |||
| 9c8b922069 | |||
| d4b421421d | |||
| 58e9646fa6 | |||
| 500f172561 | |||
| 68f6c90ea4 | |||
| 41e91f2922 | |||
| 999117cfeb | |||
| 6185df512f | |||
| 0cbf66c007 | |||
| cd378b721d | |||
| 547d38d1ef | |||
| dca56af5b9 | |||
| 224442772e | |||
| 003951fdf7 | |||
| d51b3da1b4 | |||
| 69f4af84db | |||
| 771759b252 | |||
| 20c7a71db6 | |||
| 8475ee0985 | |||
| f42811d3d4 | |||
| c3b1832cfb | |||
| eb6753afe1 | |||
| 5051cecb84 | |||
| cd03ede358 | |||
| 6563f8c738 | |||
| e5279b4827 | |||
| 79ff505963 | |||
| 8a67eba5fc | |||
| 6609a5f340 | |||
| d9972cb349 | |||
| 28d2539432 | |||
| f28386b71f | |||
| 53717076f5 | |||
| a9aa928629 | |||
| 8df121148d | |||
| 5e23c32ae8 | |||
| 9c0f6481c0 | |||
| 68ae45dd58 | |||
| 3091747438 | |||
| 2f266b8dd4 | |||
| ee20b87ee2 | |||
| 83e025d0bb | |||
| 5115c6e217 | |||
| 76f6a94de5 | |||
| 954830be18 | |||
| ea70299a45 | |||
| 88da071ed6 | |||
| 1dbf162a71 | |||
| 1c0964753b | |||
| daa1c7f577 | |||
| 854416ceb2 | |||
| 2230351e3e | |||
| 7da3244da2 | |||
| bfeb0c2988 | |||
| d4e75c1dec | |||
| 405bddcde0 | |||
| 8a27c45ab1 | |||
| 10b15896b3 | |||
| 0e97bbe37c | |||
| e0d7e90894 | |||
| 5d13f6aab6 | |||
| 1ebfbbe89e | |||
| 91ad43fdfc | |||
| 6fe6fc180d | |||
| d84d0bec38 | |||
| 7e7b1c6ee1 | |||
| effb354d1b | |||
| ba7d1ad35f | |||
| 3ca2b19502 | |||
| 8e0d91dcf5 | |||
| cd2c2587ae | |||
| 53044696ba | |||
| 6d6927213f | |||
| be1b5bce4f | |||
| 4b4fd0735b | |||
| c565b2a31f | |||
| 55f2261905 | |||
| 51912f2b83 | |||
| 7f4e2617ee | |||
| 960a385202 | |||
| 21f48d3485 | |||
| 7f9605e55f | |||
| cc409dc3f7 | |||
| af6091760c | |||
| e1d93c003c | |||
| ff9dd2dd03 | |||
| 7a306bb3d2 | |||
| 7ffc148358 | |||
| 50fef2edfa | |||
| aa40084010 | |||
| 740d788c7c | |||
| 4c2fa2c1b3 | |||
| 4350c7b7a9 | |||
| 595f14d98d | |||
| 2e95d6ea63 | |||
| 0da6abeb98 | |||
| e4e050e8e7 | |||
| 5bc082b75e | |||
| beedbd7646 | |||
| 507b069ffe | |||
| 71444b0427 | |||
| a08bba438e | |||
| df1e6711af | |||
| f6d4e934e3 | |||
| d5bd4c6735 | |||
| eb12ba6ed2 | |||
| 6e83c08535 | |||
| b6bfdec48d | |||
| f9ec796291 | |||
| 3beb1d0683 | |||
| 8836c7f0ca | |||
| ef5ce1d6e1 | |||
| 0ea1213139 | |||
| 51fe372f60 | |||
| eb8f9f8936 | |||
| afc1524874 | |||
| fbb975625c | |||
| 53e75d8209 | |||
| 5bdf970c10 | |||
| 50089f72c6 | |||
| 62e15e0208 | |||
| 3d8b02a7f3 | |||
| 20701d9cf1 | |||
| fa94442eb2 | |||
| 68ff77e172 | |||
| 102e9be3a8 | |||
| 92bf01a183 | |||
| 559504ae29 | |||
| 9b00b41a1e | |||
| b1f6ad17e1 | |||
| e7979fe9db | |||
| 7a276adbbc | |||
| db4997fdc4 | |||
| 44ebb841f0 | |||
| 09ae4e2096 | |||
| 0b46efe4ea |
@@ -1,4 +1,3 @@
|
||||
.svn
|
||||
db.sqlite
|
||||
out/**/*.o
|
||||
out/**/*.d
|
||||
.git
|
||||
db.sqlite*
|
||||
out/
|
||||
|
||||
@@ -6,15 +6,51 @@ jobs:
|
||||
Build-All:
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
valid_volumes: ['/opt/keys']
|
||||
image: node:23-bookworm-slim
|
||||
valid_volumes:
|
||||
- '/opt/keys'
|
||||
- '/opt/deps'
|
||||
volumes:
|
||||
- /opt/keys:/opt/keys
|
||||
- /opt/deps:/opt/deps
|
||||
steps:
|
||||
- name: check out code
|
||||
- name: Install build dependencies
|
||||
run: >
|
||||
apt update && apt install -y \
|
||||
build-essential \
|
||||
clang-19 \
|
||||
cmake \
|
||||
curl \
|
||||
docker.io \
|
||||
doxygen \
|
||||
file \
|
||||
gcc-aarch64-linux-gnu \
|
||||
git \
|
||||
graphviz \
|
||||
libgpgme11 \
|
||||
libssl-dev \
|
||||
mingw-w64 \
|
||||
rsync \
|
||||
unzip \
|
||||
zip \
|
||||
zlib1g-dev
|
||||
- name: Get code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
- run: ln -s /opt/keys .keys
|
||||
- name: Setup environment
|
||||
run: |
|
||||
update-alternatives --install /usr/bin/clang clang /usr/bin/clang-19 100
|
||||
update-alternatives --install /usr/bin/clang++ clang++ /usr/bin/clang++-19 100
|
||||
ln -s /opt/keys .keys
|
||||
ln -sf /opt/deps/ios_toolchain deps/
|
||||
ln -sf /opt/deps/macos_toolchain deps/
|
||||
- name: Build documentation
|
||||
run: |
|
||||
mkdir -p out/html/ ~/.ssh/
|
||||
make -j`nproc` docs
|
||||
echo 'pildefriends ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKD3Kde5vDO0TrMBDK0IGGeNGe/XinWAZkSQ/rXxwUjt' >> ~/.ssh/known_hosts
|
||||
rsync -avP --delete -e "ssh -i /opt/keys/ssh.ed25519" out/html/ tfdocs@pildefriends:docs/html/
|
||||
- name: Setup JDK
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
@@ -23,16 +59,13 @@ jobs:
|
||||
- name: Setup Android SDK
|
||||
uses: android-actions/setup-android@v3
|
||||
with:
|
||||
packages: 'tools platform-tools build-tools;34.0.0 platforms;android-34 ndk;26.3.11579264'
|
||||
- run: sudo apt update && sudo apt install -y doxygen graphviz mingw-w64 libgpgme11
|
||||
- run: ANDROID_SDK=$HOME/.android/sdk make -j`nproc` all docs
|
||||
- run: docker build .
|
||||
- uses: actions/upload-artifact@v3
|
||||
packages: 'tools platform-tools build-tools;35.0.0 platforms;android-35 ndk;27.2.12479018'
|
||||
- name: Docker build
|
||||
run: DOCKER_BUILDKIT=1 docker build .
|
||||
- name: Build
|
||||
run: ANDROID_SDK=$HOME/.android/sdk make -j`nproc` all dist
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
path: out/TildeFriends-release.fdroid.apk
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
path: out/winrelease/tildefriends.exe
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
path: out/tildefriends-x86_64.AppImage
|
||||
name: dist
|
||||
path: dist/*
|
||||
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -1,11 +1,13 @@
|
||||
build/
|
||||
*.core
|
||||
db.*
|
||||
deps/ios_toolchain/
|
||||
deps/ios_toolchain
|
||||
deps/macos_toolchain
|
||||
deps/openssl/
|
||||
dist/
|
||||
.flatpak-builder
|
||||
.keys
|
||||
**/.DS_Store
|
||||
logs/
|
||||
**/node_modules
|
||||
out
|
||||
|
||||
7
.gitmodules
vendored
7
.gitmodules
vendored
@@ -19,10 +19,9 @@
|
||||
[submodule "deps/picohttpparser"]
|
||||
path = deps/picohttpparser
|
||||
url = https://github.com/h2o/picohttpparser.git
|
||||
[submodule "deps/openssl_src"]
|
||||
path = deps/openssl_src
|
||||
url = https://github.com/openssl/openssl.git
|
||||
shallow = true
|
||||
[submodule "deps/c-ares"]
|
||||
path = deps/c-ares
|
||||
url = https://github.com/c-ares/c-ares.git
|
||||
[submodule "deps/zsign"]
|
||||
path = deps/zsign
|
||||
url = https://github.com/zhlynn/zsign.git
|
||||
|
||||
@@ -1,19 +1,15 @@
|
||||
FROM bitnami/minideb:bullseye AS build
|
||||
FROM bitnami/minideb:bookworm AS build
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends \
|
||||
gcc \
|
||||
libc6-dev \
|
||||
libssl-dev \
|
||||
make
|
||||
|
||||
COPY . /app
|
||||
RUN make -C /app -j $(nproc) release
|
||||
|
||||
FROM bitnami/minideb:bullseye
|
||||
RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends \
|
||||
libssl1.1
|
||||
FROM bitnami/minideb:bookworm
|
||||
|
||||
COPY --from=build /app/out/release/tildefriends /app/out/release/tildefriends
|
||||
COPY --from=build /app/apps /app/apps
|
||||
|
||||
374
Doxyfile
374
Doxyfile
@@ -1,4 +1,4 @@
|
||||
# Doxyfile 1.9.8
|
||||
# Doxyfile 1.9.4
|
||||
|
||||
# This file describes the settings to be used by the documentation system
|
||||
# doxygen (www.doxygen.org) for a project.
|
||||
@@ -19,8 +19,7 @@
|
||||
# configuration file:
|
||||
# doxygen -x [configFile]
|
||||
# Use doxygen to compare the used configuration file with the template
|
||||
# configuration file without replacing the environment variables or CMake type
|
||||
# replacement variables:
|
||||
# configuration file without replacing the environment variables:
|
||||
# doxygen -x_noenv [configFile]
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
@@ -86,7 +85,7 @@ CREATE_SUBDIRS = NO
|
||||
# level increment doubles the number of directories, resulting in 4096
|
||||
# directories at level 8 which is the default and also the maximum value. The
|
||||
# sub-directories are organized in 2 levels, the first level always has a fixed
|
||||
# number of 16 directories.
|
||||
# numer of 16 directories.
|
||||
# Minimum value: 0, maximum value: 8, default value: 8.
|
||||
# This tag requires that the tag CREATE_SUBDIRS is set to YES.
|
||||
|
||||
@@ -342,7 +341,7 @@ OPTIMIZE_OUTPUT_SLICE = NO
|
||||
#
|
||||
# Note see also the list of default file extension mappings.
|
||||
|
||||
EXTENSION_MAPPING =
|
||||
EXTENSION_MAPPING = js=javascript
|
||||
|
||||
# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments
|
||||
# according to the Markdown format, which allows for more readable
|
||||
@@ -363,17 +362,6 @@ MARKDOWN_SUPPORT = YES
|
||||
|
||||
TOC_INCLUDE_HEADINGS = 5
|
||||
|
||||
# The MARKDOWN_ID_STYLE tag can be used to specify the algorithm used to
|
||||
# generate identifiers for the Markdown headings. Note: Every identifier is
|
||||
# unique.
|
||||
# Possible values are: DOXYGEN use a fixed 'autotoc_md' string followed by a
|
||||
# sequence number starting at 0 and GITHUB use the lower case version of title
|
||||
# with any whitespace replaced by '-' and punctuation characters removed.
|
||||
# The default value is: DOXYGEN.
|
||||
# This tag requires that the tag MARKDOWN_SUPPORT is set to YES.
|
||||
|
||||
MARKDOWN_ID_STYLE = DOXYGEN
|
||||
|
||||
# When enabled doxygen tries to link words that correspond to documented
|
||||
# classes, or namespaces to their corresponding documentation. Such a link can
|
||||
# be prevented in individual cases by putting a % sign in front of the word or
|
||||
@@ -498,14 +486,6 @@ LOOKUP_CACHE_SIZE = 0
|
||||
|
||||
NUM_PROC_THREADS = 1
|
||||
|
||||
# If the TIMESTAMP tag is set different from NO then each generated page will
|
||||
# contain the date or date and time when the page was generated. Setting this to
|
||||
# NO can help when comparing the output of multiple runs.
|
||||
# Possible values are: YES, NO, DATETIME and DATE.
|
||||
# The default value is: NO.
|
||||
|
||||
TIMESTAMP = NO
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
# Build related configuration options
|
||||
#---------------------------------------------------------------------------
|
||||
@@ -587,8 +567,7 @@ HIDE_UNDOC_MEMBERS = NO
|
||||
# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all
|
||||
# undocumented classes that are normally visible in the class hierarchy. If set
|
||||
# to NO, these classes will be included in the various overviews. This option
|
||||
# will also hide undocumented C++ concepts if enabled. This option has no effect
|
||||
# if EXTRACT_ALL is enabled.
|
||||
# has no effect if EXTRACT_ALL is enabled.
|
||||
# The default value is: NO.
|
||||
|
||||
HIDE_UNDOC_CLASSES = NO
|
||||
@@ -626,8 +605,7 @@ INTERNAL_DOCS = NO
|
||||
# Windows (including Cygwin) and MacOS, users should typically set this option
|
||||
# to NO, whereas on Linux or other Unix flavors it should typically be set to
|
||||
# YES.
|
||||
# Possible values are: SYSTEM, NO and YES.
|
||||
# The default value is: SYSTEM.
|
||||
# The default value is: system dependent.
|
||||
|
||||
CASE_SENSE_NAMES = YES
|
||||
|
||||
@@ -879,26 +857,11 @@ WARN_IF_INCOMPLETE_DOC = YES
|
||||
|
||||
WARN_NO_PARAMDOC = NO
|
||||
|
||||
# If WARN_IF_UNDOC_ENUM_VAL option is set to YES, doxygen will warn about
|
||||
# undocumented enumeration values. If set to NO, doxygen will accept
|
||||
# undocumented enumeration values. If EXTRACT_ALL is set to YES then this flag
|
||||
# will automatically be disabled.
|
||||
# The default value is: NO.
|
||||
|
||||
WARN_IF_UNDOC_ENUM_VAL = NO
|
||||
|
||||
# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when
|
||||
# a warning is encountered. If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS
|
||||
# then doxygen will continue running as if WARN_AS_ERROR tag is set to NO, but
|
||||
# at the end of the doxygen process doxygen will return with a non-zero status.
|
||||
# If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS_PRINT then doxygen behaves
|
||||
# like FAIL_ON_WARNINGS but in case no WARN_LOGFILE is defined doxygen will not
|
||||
# write the warning messages in between other messages but write them at the end
|
||||
# of a run, in case a WARN_LOGFILE is defined the warning messages will be
|
||||
# besides being in the defined file also be shown at the end of a run, unless
|
||||
# the WARN_LOGFILE is defined as - i.e. standard output (stdout) in that case
|
||||
# the behavior will remain as with the setting FAIL_ON_WARNINGS.
|
||||
# Possible values are: NO, YES, FAIL_ON_WARNINGS and FAIL_ON_WARNINGS_PRINT.
|
||||
# Possible values are: NO, YES and FAIL_ON_WARNINGS.
|
||||
# The default value is: NO.
|
||||
|
||||
WARN_AS_ERROR = NO
|
||||
@@ -943,28 +906,23 @@ WARN_LOGFILE =
|
||||
# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING
|
||||
# Note: If this tag is empty the current directory is searched.
|
||||
|
||||
INPUT = src/
|
||||
INPUT = README.md \
|
||||
core/app.js \
|
||||
core/client.js \
|
||||
core/core.js \
|
||||
core/tfrpc.js \
|
||||
docs/ \
|
||||
src/
|
||||
|
||||
# This tag can be used to specify the character encoding of the source files
|
||||
# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses
|
||||
# libiconv (or the iconv built into libc) for the transcoding. See the libiconv
|
||||
# documentation (see:
|
||||
# https://www.gnu.org/software/libiconv/) for the list of possible encodings.
|
||||
# See also: INPUT_FILE_ENCODING
|
||||
# The default value is: UTF-8.
|
||||
|
||||
INPUT_ENCODING = UTF-8
|
||||
|
||||
# This tag can be used to specify the character encoding of the source files
|
||||
# that doxygen parses The INPUT_FILE_ENCODING tag can be used to specify
|
||||
# character encoding on a per file pattern basis. Doxygen will compare the file
|
||||
# name with each pattern and apply the encoding instead of the default
|
||||
# INPUT_ENCODING) if there is a match. The character encodings are a list of the
|
||||
# form: pattern=encoding (like *.php=ISO-8859-1). See cfg_input_encoding
|
||||
# "INPUT_ENCODING" for further information on supported encodings.
|
||||
|
||||
INPUT_FILE_ENCODING =
|
||||
|
||||
# If the value of the INPUT tag contains directories, you can use the
|
||||
# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and
|
||||
# *.h) to filter out the source-files in the directories.
|
||||
@@ -976,14 +934,15 @@ INPUT_FILE_ENCODING =
|
||||
# Note the list of default checked file patterns might differ from the list of
|
||||
# default file extension mappings.
|
||||
#
|
||||
# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cxxm,
|
||||
# *.cpp, *.cppm, *.c++, *.c++m, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl,
|
||||
# *.ddl, *.odl, *.h, *.hh, *.hxx, *.hpp, *.h++, *.ixx, *.l, *.cs, *.d, *.php,
|
||||
# *.php4, *.php5, *.phtml, *.inc, *.m, *.markdown, *.md, *.mm, *.dox (to be
|
||||
# provided as doxygen C comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08,
|
||||
# *.f18, *.f, *.for, *.vhd, *.vhdl, *.ucf, *.qsf and *.ice.
|
||||
# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp,
|
||||
# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h,
|
||||
# *.hh, *.hxx, *.hpp, *.h++, *.l, *.cs, *.d, *.php, *.php4, *.php5, *.phtml,
|
||||
# *.inc, *.m, *.markdown, *.md, *.mm, *.dox (to be provided as doxygen C
|
||||
# comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f18, *.f, *.for, *.vhd,
|
||||
# *.vhdl, *.ucf, *.qsf and *.ice.
|
||||
|
||||
FILE_PATTERNS = *.h \
|
||||
*.js \
|
||||
*.md
|
||||
|
||||
# The RECURSIVE tag can be used to specify whether or not subdirectories should
|
||||
@@ -1022,6 +981,9 @@ EXCLUDE_PATTERNS =
|
||||
# output. The symbol name can be a fully qualified name, a word, or if the
|
||||
# wildcard * is used, a substring. Examples: ANamespace, AClass,
|
||||
# ANamespace::AClass, ANamespace::*Test
|
||||
#
|
||||
# Note that the wildcards are matched against the file with absolute path, so to
|
||||
# exclude all test directories use the pattern */test/*
|
||||
|
||||
EXCLUDE_SYMBOLS =
|
||||
|
||||
@@ -1049,7 +1011,7 @@ EXAMPLE_RECURSIVE = NO
|
||||
# that contain images that are to be included in the documentation (see the
|
||||
# \image command).
|
||||
|
||||
IMAGE_PATH =
|
||||
IMAGE_PATH = docs/images/
|
||||
|
||||
# The INPUT_FILTER tag can be used to specify a program that doxygen should
|
||||
# invoke to filter for each input file. Doxygen will invoke the filter program
|
||||
@@ -1066,11 +1028,6 @@ IMAGE_PATH =
|
||||
# code is scanned, but not when the output code is generated. If lines are added
|
||||
# or removed, the anchors will not be placed correctly.
|
||||
#
|
||||
# Note that doxygen will use the data processed and written to standard output
|
||||
# for further processing, therefore nothing else, like debug statements or used
|
||||
# commands (so in case of a Windows batch file always use @echo OFF), should be
|
||||
# written to standard output.
|
||||
#
|
||||
# Note that for custom extensions or not directly supported extensions you also
|
||||
# need to set EXTENSION_MAPPING for the extension otherwise the files are not
|
||||
# properly processed by doxygen.
|
||||
@@ -1110,16 +1067,7 @@ FILTER_SOURCE_PATTERNS =
|
||||
# (index.html). This can be useful if you have a project on for instance GitHub
|
||||
# and want to reuse the introduction page also for the doxygen output.
|
||||
|
||||
USE_MDFILE_AS_MAINPAGE =
|
||||
|
||||
# The Fortran standard specifies that for fixed formatted Fortran code all
|
||||
# characters from position 72 are to be considered as comment. A common
|
||||
# extension is to allow longer lines before the automatic comment starts. The
|
||||
# setting FORTRAN_COMMENT_AFTER will also make it possible that longer lines can
|
||||
# be processed before the automatic comment starts.
|
||||
# Minimum value: 7, maximum value: 10000, default value: 72.
|
||||
|
||||
FORTRAN_COMMENT_AFTER = 72
|
||||
USE_MDFILE_AS_MAINPAGE = README.md
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
# Configuration options related to source browsing
|
||||
@@ -1258,11 +1206,10 @@ CLANG_DATABASE_PATH =
|
||||
|
||||
ALPHABETICAL_INDEX = YES
|
||||
|
||||
# The IGNORE_PREFIX tag can be used to specify a prefix (or a list of prefixes)
|
||||
# that should be ignored while generating the index headers. The IGNORE_PREFIX
|
||||
# tag works for classes, function and member names. The entity will be placed in
|
||||
# the alphabetical list under the first letter of the entity name that remains
|
||||
# after removing the prefix.
|
||||
# In case all classes in a project start with a common prefix, all classes will
|
||||
# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag
|
||||
# can be used to specify a prefix (or a list of prefixes) that should be ignored
|
||||
# while generating the index headers.
|
||||
# This tag requires that the tag ALPHABETICAL_INDEX is set to YES.
|
||||
|
||||
IGNORE_PREFIX =
|
||||
@@ -1341,12 +1288,7 @@ HTML_STYLESHEET =
|
||||
# Doxygen will copy the style sheet files to the output directory.
|
||||
# Note: The order of the extra style sheet files is of importance (e.g. the last
|
||||
# style sheet in the list overrules the setting of the previous ones in the
|
||||
# list).
|
||||
# Note: Since the styling of scrollbars can currently not be overruled in
|
||||
# Webkit/Chromium, the styling will be left out of the default doxygen.css if
|
||||
# one or more extra stylesheets have been specified. So if scrollbar
|
||||
# customization is desired it has to be added explicitly. For an example see the
|
||||
# documentation.
|
||||
# list). For an example see the documentation.
|
||||
# This tag requires that the tag GENERATE_HTML is set to YES.
|
||||
|
||||
HTML_EXTRA_STYLESHEET =
|
||||
@@ -1361,19 +1303,6 @@ HTML_EXTRA_STYLESHEET =
|
||||
|
||||
HTML_EXTRA_FILES =
|
||||
|
||||
# The HTML_COLORSTYLE tag can be used to specify if the generated HTML output
|
||||
# should be rendered with a dark or light theme.
|
||||
# Possible values are: LIGHT always generate light mode output, DARK always
|
||||
# generate dark mode output, AUTO_LIGHT automatically set the mode according to
|
||||
# the user preference, use light mode if no preference is set (the default),
|
||||
# AUTO_DARK automatically set the mode according to the user preference, use
|
||||
# dark mode if no preference is set and TOGGLE allow to user to switch between
|
||||
# light and dark mode via a button.
|
||||
# The default value is: AUTO_LIGHT.
|
||||
# This tag requires that the tag GENERATE_HTML is set to YES.
|
||||
|
||||
HTML_COLORSTYLE = AUTO_LIGHT
|
||||
|
||||
# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen
|
||||
# will adjust the colors in the style sheet and background images according to
|
||||
# this color. Hue is specified as an angle on a color-wheel, see
|
||||
@@ -1404,6 +1333,15 @@ HTML_COLORSTYLE_SAT = 100
|
||||
|
||||
HTML_COLORSTYLE_GAMMA = 80
|
||||
|
||||
# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML
|
||||
# page will contain the date and time when the page was generated. Setting this
|
||||
# to YES can help to show when doxygen was last run and thus if the
|
||||
# documentation is up to date.
|
||||
# The default value is: NO.
|
||||
# This tag requires that the tag GENERATE_HTML is set to YES.
|
||||
|
||||
#HTML_TIMESTAMP = NO
|
||||
|
||||
# If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML
|
||||
# documentation will contain a main index with vertical navigation menus that
|
||||
# are dynamically created via JavaScript. If disabled, the navigation index will
|
||||
@@ -1423,13 +1361,6 @@ HTML_DYNAMIC_MENUS = YES
|
||||
|
||||
HTML_DYNAMIC_SECTIONS = NO
|
||||
|
||||
# If the HTML_CODE_FOLDING tag is set to YES then classes and functions can be
|
||||
# dynamically folded and expanded in the generated HTML source code.
|
||||
# The default value is: YES.
|
||||
# This tag requires that the tag GENERATE_HTML is set to YES.
|
||||
|
||||
HTML_CODE_FOLDING = YES
|
||||
|
||||
# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries
|
||||
# shown in the various tree structured indices initially; the user can expand
|
||||
# and collapse entries dynamically later on. Doxygen will expand the tree to
|
||||
@@ -1560,16 +1491,6 @@ BINARY_TOC = NO
|
||||
|
||||
TOC_EXPAND = NO
|
||||
|
||||
# The SITEMAP_URL tag is used to specify the full URL of the place where the
|
||||
# generated documentation will be placed on the server by the user during the
|
||||
# deployment of the documentation. The generated sitemap is called sitemap.xml
|
||||
# and placed on the directory specified by HTML_OUTPUT. In case no SITEMAP_URL
|
||||
# is specified no sitemap is generated. For information about the sitemap
|
||||
# protocol see https://www.sitemaps.org
|
||||
# This tag requires that the tag GENERATE_HTML is set to YES.
|
||||
|
||||
SITEMAP_URL =
|
||||
|
||||
# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and
|
||||
# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that
|
||||
# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help
|
||||
@@ -1680,7 +1601,7 @@ DISABLE_INDEX = NO
|
||||
# The default value is: NO.
|
||||
# This tag requires that the tag GENERATE_HTML is set to YES.
|
||||
|
||||
GENERATE_TREEVIEW = NO
|
||||
GENERATE_TREEVIEW = YES
|
||||
|
||||
# When both GENERATE_TREEVIEW and DISABLE_INDEX are set to YES, then the
|
||||
# FULL_SIDEBAR option determines if the side bar is limited to only the treeview
|
||||
@@ -1745,6 +1666,17 @@ HTML_FORMULA_FORMAT = png
|
||||
|
||||
FORMULA_FONTSIZE = 10
|
||||
|
||||
# Use the FORMULA_TRANSPARENT tag to determine whether or not the images
|
||||
# generated for formulas are transparent PNGs. Transparent PNGs are not
|
||||
# supported properly for IE 6.0, but are supported on all modern browsers.
|
||||
#
|
||||
# Note that when changing this option you need to delete any form_*.png files in
|
||||
# the HTML output directory before the changes have effect.
|
||||
# The default value is: YES.
|
||||
# This tag requires that the tag GENERATE_HTML is set to YES.
|
||||
|
||||
#FORMULA_TRANSPARENT = YES
|
||||
|
||||
# The FORMULA_MACROFILE can contain LaTeX \newcommand and \renewcommand commands
|
||||
# to create new LaTeX commands to be used in formulas as building blocks. See
|
||||
# the section "Including formulas" for details.
|
||||
@@ -1806,8 +1738,8 @@ MATHJAX_RELPATH = https://cdn.jsdelivr.net/npm/mathjax@2
|
||||
|
||||
# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax
|
||||
# extension names that should be enabled during MathJax rendering. For example
|
||||
# for MathJax version 2 (see
|
||||
# https://docs.mathjax.org/en/v2.7-latest/tex.html#tex-and-latex-extensions):
|
||||
# for MathJax version 2 (see https://docs.mathjax.org/en/v2.7-latest/tex.html
|
||||
# #tex-and-latex-extensions):
|
||||
# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols
|
||||
# For example for MathJax version 3 (see
|
||||
# http://docs.mathjax.org/en/latest/input/tex/extensions/index.html):
|
||||
@@ -2058,16 +1990,9 @@ PDF_HYPERLINKS = YES
|
||||
|
||||
USE_PDFLATEX = YES
|
||||
|
||||
# The LATEX_BATCHMODE tag signals the behavior of LaTeX in case of an error.
|
||||
# Possible values are: NO same as ERROR_STOP, YES same as BATCH, BATCH In batch
|
||||
# mode nothing is printed on the terminal, errors are scrolled as if <return> is
|
||||
# hit at every error; missing files that TeX tries to input or request from
|
||||
# keyboard input (\read on a not open input stream) cause the job to abort,
|
||||
# NON_STOP In nonstop mode the diagnostic message will appear on the terminal,
|
||||
# but there is no possibility of user interaction just like in batch mode,
|
||||
# SCROLL In scroll mode, TeX will stop only for missing files to input or if
|
||||
# keyboard input is necessary and ERROR_STOP In errorstop mode, TeX will stop at
|
||||
# each error, asking for user intervention.
|
||||
# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \batchmode
|
||||
# command to the generated LaTeX files. This will instruct LaTeX to keep running
|
||||
# if errors occur, instead of asking the user for help.
|
||||
# The default value is: NO.
|
||||
# This tag requires that the tag GENERATE_LATEX is set to YES.
|
||||
|
||||
@@ -2088,6 +2013,14 @@ LATEX_HIDE_INDICES = NO
|
||||
|
||||
LATEX_BIB_STYLE = plain
|
||||
|
||||
# If the LATEX_TIMESTAMP tag is set to YES then the footer of each generated
|
||||
# page will contain the date and time when the page was generated. Setting this
|
||||
# to NO can help when comparing the output of multiple runs.
|
||||
# The default value is: NO.
|
||||
# This tag requires that the tag GENERATE_LATEX is set to YES.
|
||||
|
||||
#LATEX_TIMESTAMP = NO
|
||||
|
||||
# The LATEX_EMOJI_DIRECTORY tag is used to specify the (relative or absolute)
|
||||
# path from which the emoji images will be read. If a relative path is entered,
|
||||
# it will be relative to the LATEX_OUTPUT directory. If left blank the
|
||||
@@ -2253,39 +2186,13 @@ DOCBOOK_OUTPUT = docbook
|
||||
#---------------------------------------------------------------------------
|
||||
|
||||
# If the GENERATE_AUTOGEN_DEF tag is set to YES, doxygen will generate an
|
||||
# AutoGen Definitions (see https://autogen.sourceforge.net/) file that captures
|
||||
# AutoGen Definitions (see http://autogen.sourceforge.net/) file that captures
|
||||
# the structure of the code including all documentation. Note that this feature
|
||||
# is still experimental and incomplete at the moment.
|
||||
# The default value is: NO.
|
||||
|
||||
GENERATE_AUTOGEN_DEF = NO
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
# Configuration options related to Sqlite3 output
|
||||
#---------------------------------------------------------------------------
|
||||
|
||||
# If the GENERATE_SQLITE3 tag is set to YES doxygen will generate a Sqlite3
|
||||
# database with symbols found by doxygen stored in tables.
|
||||
# The default value is: NO.
|
||||
|
||||
GENERATE_SQLITE3 = NO
|
||||
|
||||
# The SQLITE3_OUTPUT tag is used to specify where the Sqlite3 database will be
|
||||
# put. If a relative path is entered the value of OUTPUT_DIRECTORY will be put
|
||||
# in front of it.
|
||||
# The default directory is: sqlite3.
|
||||
# This tag requires that the tag GENERATE_SQLITE3 is set to YES.
|
||||
|
||||
SQLITE3_OUTPUT = sqlite3
|
||||
|
||||
# The SQLITE3_OVERWRITE_DB tag is set to YES, the existing doxygen_sqlite3.db
|
||||
# database file will be recreated with each doxygen run. If set to NO, doxygen
|
||||
# will warn if an a database file is already found and not modify it.
|
||||
# The default value is: YES.
|
||||
# This tag requires that the tag GENERATE_SQLITE3 is set to YES.
|
||||
|
||||
SQLITE3_RECREATE_DB = YES
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
# Configuration options related to the Perl module output
|
||||
#---------------------------------------------------------------------------
|
||||
@@ -2428,15 +2335,15 @@ TAGFILES =
|
||||
|
||||
GENERATE_TAGFILE =
|
||||
|
||||
# If the ALLEXTERNALS tag is set to YES, all external classes and namespaces
|
||||
# will be listed in the class and namespace index. If set to NO, only the
|
||||
# inherited external classes will be listed.
|
||||
# If the ALLEXTERNALS tag is set to YES, all external class will be listed in
|
||||
# the class index. If set to NO, only the inherited external classes will be
|
||||
# listed.
|
||||
# The default value is: NO.
|
||||
|
||||
ALLEXTERNALS = NO
|
||||
|
||||
# If the EXTERNAL_GROUPS tag is set to YES, all external groups will be listed
|
||||
# in the topic index. If set to NO, only the current project's groups will be
|
||||
# in the modules index. If set to NO, only the current project's groups will be
|
||||
# listed.
|
||||
# The default value is: YES.
|
||||
|
||||
@@ -2450,9 +2357,16 @@ EXTERNAL_GROUPS = YES
|
||||
EXTERNAL_PAGES = YES
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
# Configuration options related to diagram generator tools
|
||||
# Configuration options related to the dot tool
|
||||
#---------------------------------------------------------------------------
|
||||
|
||||
# You can include diagrams made with dia in doxygen documentation. Doxygen will
|
||||
# then run dia to produce the diagram and insert it in the documentation. The
|
||||
# DIA_PATH tag allows you to specify the directory where the dia binary resides.
|
||||
# If left empty dia is assumed to be found in the default search path.
|
||||
|
||||
DIA_PATH =
|
||||
|
||||
# If set to YES the inheritance and collaboration graphs will hide inheritance
|
||||
# and usage relations if the target is undocumented or is not a class.
|
||||
# The default value is: YES.
|
||||
@@ -2461,10 +2375,10 @@ HIDE_UNDOC_RELATIONS = YES
|
||||
|
||||
# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is
|
||||
# available from the path. This tool is part of Graphviz (see:
|
||||
# https://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent
|
||||
# http://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent
|
||||
# Bell Labs. The other options in this section have no effect if this option is
|
||||
# set to NO
|
||||
# The default value is: YES.
|
||||
# The default value is: NO.
|
||||
|
||||
HAVE_DOT = YES
|
||||
|
||||
@@ -2478,51 +2392,37 @@ HAVE_DOT = YES
|
||||
|
||||
DOT_NUM_THREADS = 0
|
||||
|
||||
# DOT_COMMON_ATTR is common attributes for nodes, edges and labels of
|
||||
# subgraphs. When you want a differently looking font in the dot files that
|
||||
# doxygen generates you can specify fontname, fontcolor and fontsize attributes.
|
||||
# For details please see <a href=https://graphviz.org/doc/info/attrs.html>Node,
|
||||
# Edge and Graph Attributes specification</a> You need to make sure dot is able
|
||||
# to find the font, which can be done by putting it in a standard location or by
|
||||
# setting the DOTFONTPATH environment variable or by setting DOT_FONTPATH to the
|
||||
# directory containing the font. Default graphviz fontsize is 14.
|
||||
# The default value is: fontname=Helvetica,fontsize=10.
|
||||
# When you want a differently looking font in the dot files that doxygen
|
||||
# generates you can specify the font name using DOT_FONTNAME. You need to make
|
||||
# sure dot is able to find the font, which can be done by putting it in a
|
||||
# standard location or by setting the DOTFONTPATH environment variable or by
|
||||
# setting DOT_FONTPATH to the directory containing the font.
|
||||
# The default value is: Helvetica.
|
||||
# This tag requires that the tag HAVE_DOT is set to YES.
|
||||
|
||||
DOT_COMMON_ATTR = "fontname=Helvetica,fontsize=10"
|
||||
#DOT_FONTNAME = Helvetica
|
||||
|
||||
# DOT_EDGE_ATTR is concatenated with DOT_COMMON_ATTR. For elegant style you can
|
||||
# add 'arrowhead=open, arrowtail=open, arrowsize=0.5'. <a
|
||||
# href=https://graphviz.org/doc/info/arrows.html>Complete documentation about
|
||||
# arrows shapes.</a>
|
||||
# The default value is: labelfontname=Helvetica,labelfontsize=10.
|
||||
# The DOT_FONTSIZE tag can be used to set the size (in points) of the font of
|
||||
# dot graphs.
|
||||
# Minimum value: 4, maximum value: 24, default value: 10.
|
||||
# This tag requires that the tag HAVE_DOT is set to YES.
|
||||
|
||||
DOT_EDGE_ATTR = "labelfontname=Helvetica,labelfontsize=10"
|
||||
#DOT_FONTSIZE = 10
|
||||
|
||||
# DOT_NODE_ATTR is concatenated with DOT_COMMON_ATTR. For view without boxes
|
||||
# around nodes set 'shape=plain' or 'shape=plaintext' <a
|
||||
# href=https://www.graphviz.org/doc/info/shapes.html>Shapes specification</a>
|
||||
# The default value is: shape=box,height=0.2,width=0.4.
|
||||
# By default doxygen will tell dot to use the default font as specified with
|
||||
# DOT_FONTNAME. If you specify a different font using DOT_FONTNAME you can set
|
||||
# the path where dot can find it using this tag.
|
||||
# This tag requires that the tag HAVE_DOT is set to YES.
|
||||
|
||||
DOT_NODE_ATTR = "shape=box,height=0.2,width=0.4"
|
||||
#DOT_FONTPATH =
|
||||
|
||||
# You can set the path where dot can find font specified with fontname in
|
||||
# DOT_COMMON_ATTR and others dot attributes.
|
||||
# This tag requires that the tag HAVE_DOT is set to YES.
|
||||
|
||||
DOT_FONTPATH =
|
||||
|
||||
# If the CLASS_GRAPH tag is set to YES or GRAPH or BUILTIN then doxygen will
|
||||
# generate a graph for each documented class showing the direct and indirect
|
||||
# inheritance relations. In case the CLASS_GRAPH tag is set to YES or GRAPH and
|
||||
# HAVE_DOT is enabled as well, then dot will be used to draw the graph. In case
|
||||
# the CLASS_GRAPH tag is set to YES and HAVE_DOT is disabled or if the
|
||||
# CLASS_GRAPH tag is set to BUILTIN, then the built-in generator will be used.
|
||||
# If the CLASS_GRAPH tag is set to TEXT the direct and indirect inheritance
|
||||
# relations will be shown as texts / links.
|
||||
# Possible values are: NO, YES, TEXT, GRAPH and BUILTIN.
|
||||
# If the CLASS_GRAPH tag is set to YES (or GRAPH) then doxygen will generate a
|
||||
# graph for each documented class showing the direct and indirect inheritance
|
||||
# relations. In case HAVE_DOT is set as well dot will be used to draw the graph,
|
||||
# otherwise the built-in generator will be used. If the CLASS_GRAPH tag is set
|
||||
# to TEXT the direct and indirect inheritance relations will be shown as texts /
|
||||
# links.
|
||||
# Possible values are: NO, YES, TEXT and GRAPH.
|
||||
# The default value is: YES.
|
||||
|
||||
CLASS_GRAPH = YES
|
||||
@@ -2530,21 +2430,15 @@ CLASS_GRAPH = YES
|
||||
# If the COLLABORATION_GRAPH tag is set to YES then doxygen will generate a
|
||||
# graph for each documented class showing the direct and indirect implementation
|
||||
# dependencies (inheritance, containment, and class references variables) of the
|
||||
# class with other documented classes. Explicit enabling a collaboration graph,
|
||||
# when COLLABORATION_GRAPH is set to NO, can be accomplished by means of the
|
||||
# command \collaborationgraph. Disabling a collaboration graph can be
|
||||
# accomplished by means of the command \hidecollaborationgraph.
|
||||
# class with other documented classes.
|
||||
# The default value is: YES.
|
||||
# This tag requires that the tag HAVE_DOT is set to YES.
|
||||
|
||||
COLLABORATION_GRAPH = YES
|
||||
|
||||
# If the GROUP_GRAPHS tag is set to YES then doxygen will generate a graph for
|
||||
# groups, showing the direct groups dependencies. Explicit enabling a group
|
||||
# dependency graph, when GROUP_GRAPHS is set to NO, can be accomplished by means
|
||||
# of the command \groupgraph. Disabling a directory graph can be accomplished by
|
||||
# means of the command \hidegroupgraph. See also the chapter Grouping in the
|
||||
# manual.
|
||||
# groups, showing the direct groups dependencies. See also the chapter Grouping
|
||||
# in the manual.
|
||||
# The default value is: YES.
|
||||
# This tag requires that the tag HAVE_DOT is set to YES.
|
||||
|
||||
@@ -2604,9 +2498,7 @@ TEMPLATE_RELATIONS = NO
|
||||
# If the INCLUDE_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are set to
|
||||
# YES then doxygen will generate a graph for each documented file showing the
|
||||
# direct and indirect include dependencies of the file with other documented
|
||||
# files. Explicit enabling an include graph, when INCLUDE_GRAPH is is set to NO,
|
||||
# can be accomplished by means of the command \includegraph. Disabling an
|
||||
# include graph can be accomplished by means of the command \hideincludegraph.
|
||||
# files.
|
||||
# The default value is: YES.
|
||||
# This tag requires that the tag HAVE_DOT is set to YES.
|
||||
|
||||
@@ -2615,10 +2507,7 @@ INCLUDE_GRAPH = YES
|
||||
# If the INCLUDED_BY_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are
|
||||
# set to YES then doxygen will generate a graph for each documented file showing
|
||||
# the direct and indirect include dependencies of the file with other documented
|
||||
# files. Explicit enabling an included by graph, when INCLUDED_BY_GRAPH is set
|
||||
# to NO, can be accomplished by means of the command \includedbygraph. Disabling
|
||||
# an included by graph can be accomplished by means of the command
|
||||
# \hideincludedbygraph.
|
||||
# files.
|
||||
# The default value is: YES.
|
||||
# This tag requires that the tag HAVE_DOT is set to YES.
|
||||
|
||||
@@ -2658,10 +2547,7 @@ GRAPHICAL_HIERARCHY = YES
|
||||
# If the DIRECTORY_GRAPH tag is set to YES then doxygen will show the
|
||||
# dependencies a directory has on other directories in a graphical way. The
|
||||
# dependency relations are determined by the #include relations between the
|
||||
# files in the directories. Explicit enabling a directory graph, when
|
||||
# DIRECTORY_GRAPH is set to NO, can be accomplished by means of the command
|
||||
# \directorygraph. Disabling a directory graph can be accomplished by means of
|
||||
# the command \hidedirectorygraph.
|
||||
# files in the directories.
|
||||
# The default value is: YES.
|
||||
# This tag requires that the tag HAVE_DOT is set to YES.
|
||||
|
||||
@@ -2677,13 +2563,12 @@ DIR_GRAPH_MAX_DEPTH = 1
|
||||
# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images
|
||||
# generated by dot. For an explanation of the image formats see the section
|
||||
# output formats in the documentation of the dot tool (Graphviz (see:
|
||||
# https://www.graphviz.org/)).
|
||||
# http://www.graphviz.org/)).
|
||||
# Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order
|
||||
# to make the SVG files visible in IE 9+ (other browsers do not have this
|
||||
# requirement).
|
||||
# Possible values are: png, jpg, jpg:cairo, jpg:cairo:gd, jpg:gd, jpg:gd:gd,
|
||||
# gif, gif:cairo, gif:cairo:gd, gif:gd, gif:gd:gd, svg, png:gd, png:gd:gd,
|
||||
# png:cairo, png:cairo:gd, png:cairo:cairo, png:cairo:gdiplus, png:gdiplus and
|
||||
# Possible values are: png, jpg, gif, svg, png:gd, png:gd:gd, png:cairo,
|
||||
# png:cairo:gd, png:cairo:cairo, png:cairo:gdiplus, png:gdiplus and
|
||||
# png:gdiplus:gdiplus.
|
||||
# The default value is: png.
|
||||
# This tag requires that the tag HAVE_DOT is set to YES.
|
||||
@@ -2715,12 +2600,11 @@ DOT_PATH =
|
||||
|
||||
DOTFILE_DIRS =
|
||||
|
||||
# You can include diagrams made with dia in doxygen documentation. Doxygen will
|
||||
# then run dia to produce the diagram and insert it in the documentation. The
|
||||
# DIA_PATH tag allows you to specify the directory where the dia binary resides.
|
||||
# If left empty dia is assumed to be found in the default search path.
|
||||
# The MSCFILE_DIRS tag can be used to specify one or more directories that
|
||||
# contain msc files that are included in the documentation (see the \mscfile
|
||||
# command).
|
||||
|
||||
DIA_PATH =
|
||||
MSCFILE_DIRS =
|
||||
|
||||
# The DIAFILE_DIRS tag can be used to specify one or more directories that
|
||||
# contain dia files that are included in the documentation (see the \diafile
|
||||
@@ -2770,6 +2654,18 @@ DOT_GRAPH_MAX_NODES = 50
|
||||
|
||||
MAX_DOT_GRAPH_DEPTH = 0
|
||||
|
||||
# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent
|
||||
# background. This is disabled by default, because dot on Windows does not seem
|
||||
# to support this out of the box.
|
||||
#
|
||||
# Warning: Depending on the platform used, enabling this option may lead to
|
||||
# badly anti-aliased labels on the edges of a graph (i.e. they become hard to
|
||||
# read).
|
||||
# The default value is: NO.
|
||||
# This tag requires that the tag HAVE_DOT is set to YES.
|
||||
|
||||
#DOT_TRANSPARENT = NO
|
||||
|
||||
# Set the DOT_MULTI_TARGETS tag to YES to allow dot to generate multiple output
|
||||
# files in one run (i.e. multiple -o and -T options on the command line). This
|
||||
# makes dot run faster, but since only newer versions of dot (>1.8.10) support
|
||||
@@ -2797,19 +2693,3 @@ GENERATE_LEGEND = YES
|
||||
# The default value is: YES.
|
||||
|
||||
DOT_CLEANUP = YES
|
||||
|
||||
# You can define message sequence charts within doxygen comments using the \msc
|
||||
# command. If the MSCGEN_TOOL tag is left empty (the default), then doxygen will
|
||||
# use a built-in version of mscgen tool to produce the charts. Alternatively,
|
||||
# the MSCGEN_TOOL tag can also specify the name an external tool. For instance,
|
||||
# specifying prog as the value, doxygen will call the tool as prog -T
|
||||
# <outfile_format> -o <outputfile> <inputfile>. The external tool should support
|
||||
# output file formats "png", "eps", "svg", and "ismap".
|
||||
|
||||
MSCGEN_TOOL =
|
||||
|
||||
# The MSCFILE_DIRS tag can be used to specify one or more directories that
|
||||
# contain msc files that are included in the documentation (see the \mscfile
|
||||
# command).
|
||||
|
||||
MSCFILE_DIRS =
|
||||
|
||||
527
GNUmakefile
527
GNUmakefile
@@ -3,11 +3,27 @@
|
||||
MAKEFLAGS += --warn-undefined-variables
|
||||
MAKEFLAGS += --no-builtin-rules
|
||||
|
||||
VERSION_CODE := 30
|
||||
VERSION_NUMBER := 0.0.25-wip
|
||||
## == Tilde Friends build. ==
|
||||
##
|
||||
## This is a list of all supported build targets.
|
||||
##
|
||||
## Consider passing -j$(nproc) or adding it to your $MAKEFLAGS to build in
|
||||
## parallel (faster).
|
||||
##
|
||||
## Useful variables to override:
|
||||
## CC := Compiler.
|
||||
## AS := Assembler.
|
||||
## LD := Linker.
|
||||
## ANDROID_SDK := Path to the Android SDK.
|
||||
|
||||
VERSION_CODE := 44
|
||||
VERSION_CODE_IOS := 18
|
||||
VERSION_NUMBER := 0.2025.10
|
||||
VERSION_NAME := This program kills fascists.
|
||||
|
||||
SQLITE_URL := https://www.sqlite.org/2024/sqlite-amalgamation-3470000.zip
|
||||
IPHONEOS_VERSION_MIN=14.0
|
||||
|
||||
SQLITE_URL := https://www.sqlite.org/2025/sqlite-amalgamation-3500400.zip
|
||||
BUNDLETOOL_URL := https://github.com/google/bundletool/releases/download/1.17.0/bundletool-all-1.17.0.jar
|
||||
APPIMAGETOOL_URL := https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage
|
||||
APPIMAGETOOL_MD5 := e989fadfc4d685fd3d6aeeb9b525d74d out/appimagetool
|
||||
@@ -20,18 +36,27 @@ UNAME_M := $(shell uname -m)
|
||||
ANDROID_SDK ?= ~/Android/Sdk
|
||||
BUNDLETOOL = out/bundletool.jar
|
||||
|
||||
HAVE_WIN := 0
|
||||
HAVE_WIN :=
|
||||
HAVE_CROSS_AARCH64 :=
|
||||
|
||||
export SOURCE_DATE_EPOCH=1
|
||||
export TZ=UTC
|
||||
|
||||
ifeq ($(UNAME_S),Darwin)
|
||||
BUILD_TYPES := macosdebug macosrelease iosdebug iosrelease iossimdebug iossimrelease
|
||||
BUILD_TYPES := debug release iosdebug iosrelease iossimdebug iossimrelease
|
||||
HAVE_ANDROID = 0
|
||||
HAVE_LINUX_IOS = 0
|
||||
HAVE_LINUX_MACOS = 0
|
||||
HAVE_WIN = 0
|
||||
else ifeq ($(UNAME_S),Linux)
|
||||
BUILD_TYPES := debug release
|
||||
HAVE_ANDROID = $(if $(shell which $(ANDROID_SDK)/platform-tools/adb),1,0)
|
||||
HAVE_LINUX_IOS = $(if $(shell which deps/ios_toolchain/target/bin deps/ios_toolchain/target/bin/arm-apple-darwin11-clang),1,0)
|
||||
HAVE_WIN = $(if $(shell which x86_64-w64-mingw32-gcc-win32),1,0)
|
||||
HAVE_ANDROID = $(if $(shell which $(ANDROID_SDK)/platform-tools/adb),1)
|
||||
HAVE_LINUX_IOS = $(if $(shell which deps/ios_toolchain/target/bin deps/ios_toolchain/target/bin/arm-apple-darwin11-clang),1)
|
||||
HAVE_LINUX_MACOS = $(if $(shell which deps/macos_toolchain/bin/oa64-clang),1)
|
||||
HAVE_WIN = $(if $(shell which x86_64-w64-mingw32-gcc-win32),1)
|
||||
ifneq ($(UNAME_M),aarch64)
|
||||
HAVE_CROSS_AARCH64 = $(if $(shell which aarch64-linux-gnu-gcc),1)
|
||||
endif
|
||||
else ifeq ($(UNAME_S),Haiku)
|
||||
BUILD_TYPES := debug release
|
||||
CFLAGS += -Dstatic_assert=_Static_assert
|
||||
@@ -39,6 +64,10 @@ LDFLAGS += \
|
||||
-lbsd \
|
||||
-lnetwork \
|
||||
-Wno-stringop-overflow
|
||||
HAVE_ANDROID = 0
|
||||
HAVE_LINUX_IOS = 0
|
||||
HAVE_LINUX_MACOS = 0
|
||||
HAVE_WIN = 0
|
||||
else ifeq ($(UNAME_S),OpenBSD)
|
||||
BUILD_TYPES := debug release
|
||||
CFLAGS += \
|
||||
@@ -46,18 +75,23 @@ CFLAGS += \
|
||||
LDFLAGS += \
|
||||
-lexecinfo \
|
||||
-lc++abi
|
||||
HAVE_ANDROID := 0
|
||||
HAVE_LINUX_IOS := 0
|
||||
HAVE_ANDROID :=
|
||||
HAVE_LINUX_IOS :=
|
||||
HAVE_LINUX_MACOS :=
|
||||
else
|
||||
$(error Unexpected host platform $(UNAME_S).)
|
||||
endif
|
||||
|
||||
# Everything is set above.
|
||||
$(info Building Tilde Friends $(VERSION_NUMBER) android=$(if $(HAVE_ANDROID),1,0) win=$(if $(HAVE_WIN),1,0) cross_aarch64=$(if $(HAVE_CROSS_AARCH64),1,0) cross_ios=$(if $(HAVE_LINUX_IOS),1,0) cross_macos=$(if $(HAVE_LINUX_MACOS),1,0))
|
||||
|
||||
CFLAGS += \
|
||||
-std=gnu11 \
|
||||
-Wall \
|
||||
-Wextra \
|
||||
-Wno-unused-parameter \
|
||||
-Wno-cast-function-type-mismatch \
|
||||
-Wno-unknown-warning-option \
|
||||
-Wno-unused-parameter \
|
||||
-MMD \
|
||||
-MP \
|
||||
-ffunction-sections \
|
||||
@@ -66,14 +100,13 @@ CFLAGS += \
|
||||
-g
|
||||
LDFLAGS += \
|
||||
-Wno-attributes \
|
||||
-Wno-aggressive-loop-optimizations \
|
||||
-flto=auto
|
||||
-Wno-aggressive-loop-optimizations
|
||||
|
||||
ANDROID_MIN_SDK_VERSION := 24
|
||||
ANDROID_TARGET_SDK_VERSION := 34
|
||||
ANDROID_BUILD_TOOLS := $(ANDROID_SDK)/build-tools/34.0.0
|
||||
ANDROID_TARGET_SDK_VERSION := 35
|
||||
ANDROID_BUILD_TOOLS := $(ANDROID_SDK)/build-tools/35.0.0
|
||||
ANDROID_PLATFORM := $(ANDROID_SDK)/platforms/android-$(ANDROID_TARGET_SDK_VERSION)
|
||||
ANDROID_NDK ?= $(ANDROID_SDK)/ndk/26.3.11579264
|
||||
ANDROID_NDK ?= $(ANDROID_SDK)/ndk/27.2.12479018
|
||||
|
||||
ANDROID_ARMV7A_TARGETS := \
|
||||
out/androiddebug-armv7a/tildefriends \
|
||||
@@ -110,14 +143,38 @@ WINDOWS_TARGETS := \
|
||||
out/winrelease/tildefriends.exe
|
||||
ifeq ($(HAVE_WIN),1)
|
||||
BUILD_TYPES += windebug winrelease
|
||||
all: out/windebug/tildefriends.standalone.exe out/winrelease/tildefriends.standalone.exe
|
||||
endif
|
||||
|
||||
LINUX_TARGETS := \
|
||||
AARCH64_TARGETS := \
|
||||
out/armdebug/tildefriends \
|
||||
out/armrelease/tildefriends
|
||||
ifeq ($(HAVE_CROSS_AARCH64),1)
|
||||
BUILD_TYPES += armdebug armrelease
|
||||
endif
|
||||
|
||||
HOST_TARGETS := \
|
||||
out/debug/tildefriends \
|
||||
out/release/tildefriends
|
||||
ifeq ($(UNAME_S),Darwin)
|
||||
MACOS_TARGETS := \
|
||||
out/macosdebug/tildefriends \
|
||||
out/macosrelease/tildefriends
|
||||
out/debug/tildefriends \
|
||||
out/release/tildefriends
|
||||
else ifeq ($(UNAME_S),Linux)
|
||||
ifeq ($(HAVE_LINUX_MACOS),1)
|
||||
MACOS_TARGETS := \
|
||||
out/macosdebug-arm/tildefriends \
|
||||
out/macosrelease-arm/tildefriends \
|
||||
out/macosdebug-x86_64/tildefriends \
|
||||
out/macosrelease-x86_64/tildefriends
|
||||
all: out/macosdebug/tildefriends.standalone
|
||||
all: out/macosrelease/tildefriends.standalone
|
||||
else
|
||||
MACOS_TARGETS :=
|
||||
endif
|
||||
else
|
||||
MACOS_TARGETS :=
|
||||
endif
|
||||
IOS_TARGETS := \
|
||||
out/iosdebug/tildefriends \
|
||||
out/iosrelease/tildefriends
|
||||
@@ -131,44 +188,60 @@ ifeq ($(HAVE_LINUX_IOS),1)
|
||||
BUILD_TYPES += iosdebug iosrelease
|
||||
all: $(IOS_APPS)
|
||||
endif
|
||||
ifeq ($(HAVE_LINUX_MACOS),1)
|
||||
BUILD_TYPES += \
|
||||
macosdebug-arm \
|
||||
macosrelease-arm \
|
||||
macosdebug-x86_64 \
|
||||
macosrelease-x86_64
|
||||
all: $(IOS_APPS)
|
||||
endif
|
||||
ifeq ($(UNAME_S),Darwin)
|
||||
all: $(IOS_APPS) \
|
||||
out/tildefriends-iossimdebug.app/tildefriends \
|
||||
out/tildefriends-iossimrelease.app/tildefriends
|
||||
endif
|
||||
ifeq ($(HAVE_CROSS_AARCH64),1)
|
||||
all: out/armrelease/tildefriends.standalone
|
||||
endif
|
||||
|
||||
DEBUG_TARGETS := \
|
||||
out/debug/tildefriends \
|
||||
out/windebug/tildefriends.exe \
|
||||
out/iosdebug/tildefriends \
|
||||
out/iossimdebug/tildefriends \
|
||||
out/macosdebug/tildefriends \
|
||||
out/androiddebug/tildefriends \
|
||||
out/androiddebug-armv7a/tildefriends \
|
||||
out/androiddebug-x86_64/tildefriends \
|
||||
out/androiddebug-x86/tildefriends
|
||||
out/androiddebug-x86/tildefriends \
|
||||
out/armdebug/tildefriends \
|
||||
out/macosdebug-arm/tildefriends \
|
||||
out/macosdebug-x86_64/tildefriends
|
||||
RELEASE_TARGETS := \
|
||||
out/release/tildefriends \
|
||||
out/winrelease/tildefriends.exe \
|
||||
out/iosrelease/tildefriends \
|
||||
out/iossimrelease/tildefriends \
|
||||
out/macosrelease/tildefriends \
|
||||
out/androidrelease/tildefriends \
|
||||
out/androidrelease-armv7a/tildefriends \
|
||||
out/androidrelease-x86_64/tildefriends \
|
||||
out/androidrelease-x86/tildefriends
|
||||
out/androidrelease-x86/tildefriends \
|
||||
out/armrelease/tildefriends \
|
||||
out/macosrelease-arm/tildefriends \
|
||||
out/macosrelease-x86_64/tildefriends
|
||||
ALL_TARGETS = $(DEBUG_TARGETS) $(RELEASE_TARGETS)
|
||||
ANDROID_RELEASE_TARGETS := $(filter-out $(DEBUG_TARGETS),$(ANDROID_TARGETS))
|
||||
NONANDROID_RELEASE_TARGETS := $(filter-out $(ANDROID_ARM64_TARGETS),$(RELEASE_TARGETS))
|
||||
NONANDROID_TARGETS := $(filter-out $(ANDROID_TARGETS),$(ALL_TARGETS))
|
||||
NONMACOS_TARGETS := $(filter-out $(MACOS_TARGETS) $(IOS_TARGETS) $(IOSSIM_TARGETS),$(ALL_TARGETS))
|
||||
DEADSTRIP_TARGETS := $(filter-out $(ANDROID_TARGETS),$(NONMACOS_TARGETS))
|
||||
ifneq ($(UNAME_S),OpenBSD)
|
||||
$(NONMACOS_TARGETS): LDFLAGS += -static-libgcc
|
||||
endif
|
||||
|
||||
$(NONANDROID_TARGETS): CFLAGS += -fno-omit-frame-pointer
|
||||
$(filter-out $(WINDOWS_TARGETS),$(ALL_TARGETS)): LDFLAGS += -rdynamic
|
||||
$(filter-out $(WINDOWS_TARGETS),$(ALL_TARGETS)): LDFLAGS += \
|
||||
-rdynamic \
|
||||
-gz=zlib
|
||||
$(ANDROID_TARGETS): CFLAGS += \
|
||||
--sysroot $(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/sysroot \
|
||||
-fPIC \
|
||||
@@ -177,33 +250,43 @@ $(ANDROID_TARGETS): CFLAGS += \
|
||||
-fno-asynchronous-unwind-tables \
|
||||
-funwind-tables \
|
||||
-Wno-unknown-warning-option
|
||||
$(ANDROID_TARGETS): LDFLAGS += --sysroot $(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/sysroot -fPIC
|
||||
$(ANDROID_TARGETS): LDFLAGS += \
|
||||
--sysroot $(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/sysroot \
|
||||
-Wl,-z,max-page-size=16384 \
|
||||
-fPIC
|
||||
$(DEBUG_TARGETS): CFLAGS += -DDEBUG -Og
|
||||
$(DEBUG_TARGETS): LDFLAGS += -Og
|
||||
$(RELEASE_TARGETS): CFLAGS += \
|
||||
-DNDEBUG \
|
||||
-flto
|
||||
$(NONANDROID_RELEASE_TARGETS): CFLAGS += -O3
|
||||
-DNDEBUG
|
||||
$(ANDROID_RELEASE_TARGETS): CFLAGS += -Oz
|
||||
$(ANDROID_RELEASE_TARGETS): LDFLAGS += -Oz
|
||||
$(NONANDROID_RELEASE_TARGETS): CFLAGS += -Os
|
||||
$(NONANDROID_RELEASE_TARGETS): LDFLAGS += -Os
|
||||
$(WINDOWS_TARGETS): CC = x86_64-w64-mingw32-gcc-win32
|
||||
$(WINDOWS_TARGETS): AS = $(CC)
|
||||
$(WINDOWS_TARGETS): CFLAGS += \
|
||||
-D_WIN32_WINNT=0x0A00 \
|
||||
-DWINVER=0x0A00 \
|
||||
-DNTDDI_VERSION=NTDDI_WIN10 \
|
||||
-Ideps/openssl/mingw64/usr/local/include
|
||||
-DNTDDI_VERSION=NTDDI_WIN10
|
||||
$(WINDOWS_TARGETS): LDFLAGS += \
|
||||
-static \
|
||||
-lm \
|
||||
-Ldeps/openssl/mingw64/usr/local/lib
|
||||
-lm
|
||||
$(AARCH64_TARGETS): CC = aarch64-linux-gnu-gcc
|
||||
$(AARCH64_TARGETS): AS = $(CC)
|
||||
ifeq ($(UNAME_S),Darwin)
|
||||
$(MACOS_TARGETS): CC = xcrun clang
|
||||
$(HOST_TARGETS): CC = xcrun clang
|
||||
$(IOS_TARGETS): IOS_SYSROOT := $(shell xcrun --sdk iphoneos --show-sdk-path)
|
||||
$(IOS_TARGETS): CC = xcrun --sdk iphoneos clang -isysroot $(IOS_SYSROOT) -arch arm64
|
||||
$(IOSSIM_TARGETS): IOSSIM_SYSROOT := $(shell xcrun --sdk iphonesimulator --show-sdk-path)
|
||||
$(IOSSIM_TARGETS): CC = xcrun --sdk iphonesimulator clang -isysroot $(IOSSIM_SYSROOT) -arch x86_64
|
||||
else ifeq ($(UNAME_S),Linux)
|
||||
$(IOS_TARGETS): IOS_SYSROOT := deps/iPhoneOS17.0.sdk
|
||||
$(IOS_TARGETS): CC = PATH=$$PATH:deps/ios_toolchain/target/bin deps/ios_toolchain/target/bin/arm-apple-darwin11-clang
|
||||
$(IOS_TARGETS): CFLAGS += -isysroot deps/ios_toolchain/target/SDKs/iPhoneOS18.2.sdk -arch arm64 -DTARGET_OS_IPHONE=1
|
||||
$(IOS_TARGETS): LDFLAGS += -isysroot deps/ios_toolchain/target/SDKs/iPhoneOS18.2.sdk
|
||||
$(IOS_TARGETS): CC = PATH=$$PATH:deps/ios_toolchain/target/bin LD_LIBRARY_PATH=$$LD_LIBRARY_PATH:deps/ios_toolchain/target/lib deps/ios_toolchain/target/bin/arm-apple-darwin11-clang
|
||||
$(filter $(BUILD_DIR)/macosdebug-x86_64/%,$(ALL_TARGETS)): CC = PATH=deps/macos_toolchain/bin:$$PATH LD_LIBRARY_PATH=$$LD_LIBRARY_PATH:deps/macos_toolchain/lib deps/macos_toolchain/bin/o64-clang
|
||||
$(filter $(BUILD_DIR)/macosdebug-arm/%,$(ALL_TARGETS)): CC = PATH=deps/macos_toolchain/bin:$$PATH LD_LIBRARY_PATH=$$LD_LIBRARY_PATH:deps/macos_toolchain/lib deps/macos_toolchain/bin/oa64-clang
|
||||
$(filter $(BUILD_DIR)/macosrelease-x86_64/%,$(ALL_TARGETS)): CC = PATH=deps/macos_toolchain/bin:$$PATH LD_LIBRARY_PATH=$$LD_LIBRARY_PATH:deps/macos_toolchain/lib deps/macos_toolchain/bin/o64-clang
|
||||
$(filter $(BUILD_DIR)/macosrelease-arm/%,$(ALL_TARGETS)): CC = PATH=deps/macos_toolchain/bin:$$PATH LD_LIBRARY_PATH=$$LD_LIBRARY_PATH:deps/macos_toolchain/lib deps/macos_toolchain/bin/oa64-clang
|
||||
endif
|
||||
$(ANDROID_X86_64_TARGETS): ANDROID_NDK_TARGET_TRIPLE := x86_64-linux-android
|
||||
$(ANDROID_X86_TARGETS): ANDROID_NDK_TARGET_TRIPLE := i686-linux-android
|
||||
@@ -214,25 +297,16 @@ $(ANDROID_TARGETS): AS = $(CC)
|
||||
$(ANDROID_TARGETS): CFLAGS += \
|
||||
-target $(ANDROID_NDK_TARGET_TRIPLE)$(ANDROID_MIN_SDK_VERSION) \
|
||||
-Wno-unknown-warning-option
|
||||
$(ANDROID_ARMV7A_TARGETS): CFLAGS += -Ideps/openssl/android/armeabi-v7a/usr/local/include
|
||||
$(ANDROID_ARMV7A_TARGETS): LDFLAGS += -Ldeps/openssl/android/armeabi-v7a/usr/local/lib
|
||||
$(ANDROID_ARM64_TARGETS): CFLAGS += -Ideps/openssl/android/arm64-v8a/usr/local/include
|
||||
$(ANDROID_ARM64_TARGETS): LDFLAGS += -Ldeps/openssl/android/arm64-v8a/usr/local/lib
|
||||
$(ANDROID_X86_TARGETS): CFLAGS += -Ideps/openssl/android/x86/usr/local/include
|
||||
$(ANDROID_X86_TARGETS): CFLAGS += -Wno-atomic-alignment
|
||||
$(ANDROID_X86_TARGETS): LDFLAGS += -Ldeps/openssl/android/x86/usr/local/lib
|
||||
$(ANDROID_X86_64_TARGETS): CFLAGS += -Ideps/openssl/android/x86_64/usr/local/include
|
||||
$(ANDROID_X86_64_TARGETS): LDFLAGS += -Ldeps/openssl/android/x86_64/usr/local/lib
|
||||
$(NONMACOS_TARGETS): CFLAGS += -Wno-cast-function-type
|
||||
$(DEADSTRIP_TARGETS): LDFLAGS += -Wl,--gc-sections
|
||||
$(IOS_TARGETS): CFLAGS += -miphoneos-version-min=9.0 -Ideps/openssl/ios/ios64-xcrun/usr/local/include
|
||||
$(IOS_TARGETS): LDFLAGS += -miphoneos-version-min=9.0 -Ldeps/openssl/ios/ios64-xcrun/usr/local/lib
|
||||
$(IOSSIM_TARGETS): CFLAGS += -Ideps/openssl/ios/iossimulator-xcrun/usr/local/include
|
||||
$(IOSSIM_TARGETS): LDFLAGS += -Ldeps/openssl/ios/iossimulator-xcrun/usr/local/lib
|
||||
$(MACOS_TARGETS): LDFLAGS += -Wl,-dead_strip
|
||||
$(NONMACOS_TARGETS): LDFLAGS += -Wl,--gc-sections -Wl,--as-needed
|
||||
$(IOS_TARGETS): CFLAGS += -miphoneos-version-min=$(IPHONEOS_VERSION_MIN)
|
||||
$(IOS_TARGETS): LDFLAGS += -miphoneos-version-min=$(IPHONEOS_VERSION_MIN)
|
||||
|
||||
ifeq ($(UNAME_M),x86_64)
|
||||
ifeq ($(UNAME_S),Linux)
|
||||
all: appimage
|
||||
all: appimage out/release/tildefriends.standalone
|
||||
endif
|
||||
ifneq ($(UNAME_S),Haiku)
|
||||
out/debug/tildefriends: CFLAGS += -fsanitize=address -fsanitize=undefined -fno-common
|
||||
@@ -247,13 +321,14 @@ endif
|
||||
|
||||
get_objs = \
|
||||
$(foreach build_type,$(BUILD_TYPES),$(addprefix $(BUILD_DIR)/$(build_type)/,$(addsuffix .o,$(basename $(value $(1)))))) \
|
||||
$(foreach build_type,debug release,$(addprefix $(BUILD_DIR)/$(build_type)/,$(addsuffix .o,$(basename $(value $(1)_unix))))) \
|
||||
$(foreach build_type,windebug winrelease,$(addprefix $(BUILD_DIR)/$(build_type)/,$(addsuffix .o,$(basename $(value $(1)_win))))) \
|
||||
$(foreach build_type,androiddebug androidrelease androiddebug-x86 androidrelease-x86 androiddebug-x86_64 androidrelease-x86_64 androiddebug-armv7a androiddebug-armv7a,$(addprefix $(BUILD_DIR)/$(build_type)/,$(addsuffix .o,$(basename $(value $(1)_android))))) \
|
||||
$(foreach build_type,androiddebug androidrelease androiddebug-x86 androidrelease-x86 androiddebug-x86_64 androidrelease-x86_64 androiddebug-armv7a androidrelease-armv7a,$(addprefix $(BUILD_DIR)/$(build_type)/,$(addsuffix .o,$(basename $(value $(1)_android))))) \
|
||||
$(foreach build_type,androiddebug androidrelease androiddebug-x86 androidrelease-x86 androiddebug-x86_64 androidrelease-x86_64 androiddebug-armv7a androidrelease-armv7a,$(addprefix $(BUILD_DIR)/$(build_type)/,$(addsuffix .o,$(basename $(value $(1)_unix))))) \
|
||||
$(foreach build_type,macosdebug macosrelease iosdebug iosrelease iossimdebug iossimrelease,$(addprefix $(BUILD_DIR)/$(build_type)/,$(addsuffix .o,$(basename $(value $(1)_macos))))) \
|
||||
$(foreach build_type,iosdebug iosrelease iossimdebug iossimrelease,$(addprefix $(BUILD_DIR)/$(build_type)/,$(addsuffix .o,$(basename $(value $(1)_ios))))) \
|
||||
$(foreach build_type,androiddebug-x86 androidrelease-x86,$(addprefix $(BUILD_DIR)/$(build_type)/,$(addsuffix .o,$(basename $(value $(1)_x86)))))
|
||||
$(foreach build_type,iosdebug iosrelease iossimdebug iossimrelease macosdebug-arm macosrelease-arm macosdebug-x86_64 macosrelease-x86_64,$(addprefix $(BUILD_DIR)/$(build_type)/,$(addsuffix .o,$(basename $(value $(1)_macos))))) \
|
||||
$(foreach build_type,androiddebug-x86 androidrelease-x86,$(addprefix $(BUILD_DIR)/$(build_type)/,$(addsuffix .o,$(basename $(value $(1)_x86))))) \
|
||||
$(if $(findstring Darwin,$(UNAME_S)),$(foreach build_type,debug release,$(addprefix $(BUILD_DIR)/$(build_type)/,$(addsuffix .o,$(basename $(value $(1)_macos)))))) \
|
||||
$(if $(findstring Darwin,$(UNAME_S)),,$(foreach build_type,debug release armdebug armrelease,$(addprefix $(BUILD_DIR)/$(build_type)/,$(addsuffix .o,$(basename $(value $(1)_unix))))))
|
||||
|
||||
APP_SOURCES := $(wildcard src/*.c)
|
||||
APP_SOURCES_ios := $(wildcard src/*.m)
|
||||
@@ -275,10 +350,12 @@ $(APP_OBJS): CFLAGS += \
|
||||
-Ideps/valgrind \
|
||||
-Wdouble-promotion \
|
||||
-Werror
|
||||
ifneq ($(UNAME_S),Darwin)
|
||||
ifeq ($(UNAME_M),x86_64)
|
||||
$(filter-out $(BUILD_DIR)/android% $(BUILD_DIR)/macos% $(BUILD_DIR)/ios%,$(APP_OBJS)): CFLAGS += \
|
||||
$(filter-out $(BUILD_DIR)/android% $(BUILD_DIR)/ios% $(BUILD_DIR)/macos%,$(APP_OBJS)): CFLAGS += \
|
||||
-fanalyzer
|
||||
endif
|
||||
endif
|
||||
|
||||
ARES_SOURCES := \
|
||||
deps/c-ares/src/lib/ares_addrinfo2hostent.c \
|
||||
@@ -506,16 +583,16 @@ $(UV_OBJS): CFLAGS += \
|
||||
-Ideps/libuv/include \
|
||||
-Ideps/libuv/src \
|
||||
-Wno-dangling-pointer \
|
||||
-Wno-format-truncation \
|
||||
-Wno-incompatible-pointer-types \
|
||||
-Wno-maybe-uninitialized \
|
||||
-Wno-nonnull \
|
||||
-Wno-sign-compare \
|
||||
-Wno-unknown-attributes \
|
||||
-Wno-unused-but-set-parameter \
|
||||
-Wno-unused-but-set-variable \
|
||||
-Wno-unused-result \
|
||||
-Wno-unused-variable \
|
||||
-Wno-nonnull
|
||||
$(UV_OBJS): CFLAGS += -fno-lto
|
||||
-Wno-unused-variable
|
||||
$(filter out/win%,$(UV_OBJS)): \
|
||||
CFLAGS += \
|
||||
-Wno-cast-function-type \
|
||||
@@ -542,6 +619,7 @@ SODIUM_SOURCES := \
|
||||
deps/libsodium/src/libsodium/crypto_core/hsalsa20/ref2/core_hsalsa20_ref2.c \
|
||||
deps/libsodium/src/libsodium/crypto_core/salsa/ref/core_salsa_ref.c \
|
||||
deps/libsodium/src/libsodium/crypto_core/softaes/softaes.c \
|
||||
deps/libsodium/src/libsodium/crypto_generichash/crypto_generichash.c \
|
||||
deps/libsodium/src/libsodium/crypto_generichash/blake2b/ref/blake2b-compress-ref.c \
|
||||
deps/libsodium/src/libsodium/crypto_generichash/blake2b/ref/blake2b-ref.c \
|
||||
deps/libsodium/src/libsodium/crypto_generichash/blake2b/ref/generichash_blake2b.c \
|
||||
@@ -611,12 +689,12 @@ $(SQLITE_OBJS): CFLAGS += \
|
||||
-DSQLITE_MAX_COMPOUND_SELECT=300 \
|
||||
-DSQLITE_MAX_EXPR_DEPTH=40 \
|
||||
-DSQLITE_MAX_FUNCTION_ARG=8 \
|
||||
-DSQLITE_MAX_LENGTH=5242880 \
|
||||
-DSQLITE_MAX_LENGTH=10485760 \
|
||||
-DSQLITE_MAX_LIKE_PATTERN_LENGTH=50 \
|
||||
-DSQLITE_MAX_SQL_LENGTH=100000 \
|
||||
-DSQLITE_MAX_TRIGGER_DEPTH=10 \
|
||||
-DSQLITE_MAX_VARIABLE_NUMBER=100 \
|
||||
-DSQLITE_MAX_VDBE_OP=25000 \
|
||||
-DSQLITE_MAX_VDBE_OP=50000 \
|
||||
-DSQLITE_OMIT_DEPRECATED \
|
||||
-DSQLITE_OMIT_DESERIALIZE \
|
||||
-DSQLITE_OMIT_LOAD_EXTENSION \
|
||||
@@ -635,7 +713,7 @@ $(SQLITE_OBJS): CFLAGS += \
|
||||
|
||||
QUICKJS_SOURCES := \
|
||||
deps/quickjs/cutils.c \
|
||||
deps/quickjs/libbf.c \
|
||||
deps/quickjs/dtoa.c \
|
||||
deps/quickjs/libregexp.c \
|
||||
deps/quickjs/libunicode.c \
|
||||
deps/quickjs/quickjs.c
|
||||
@@ -712,18 +790,13 @@ $(MINIUNZIP_OBJS): CFLAGS += \
|
||||
LDFLAGS += \
|
||||
-pthread \
|
||||
-lm
|
||||
$(LINUX_TARGETS) $(MACOS_TARGETS) $(IOS_TARGETS) $(IOSSIM_TARGETS): LDFLAGS += \
|
||||
-lssl \
|
||||
-lcrypto
|
||||
ifneq ($(UNAME_S),Haiku)
|
||||
ifneq ($(UNAME_S),OpenBSD)
|
||||
debug release $(MACOS_TARGETS) $(IOS_TARGETS) $(IOSSIM_TARGETS): LDFLAGS += \
|
||||
$(HOST_TARGETS) $(IOS_TARGETS) $(IOSSIM_TARGETS): LDFLAGS += \
|
||||
-ldl
|
||||
endif
|
||||
endif
|
||||
$(WINDOWS_TARGETS): LDFLAGS += \
|
||||
-lssl \
|
||||
-lcrypto \
|
||||
-lcrypt32 \
|
||||
-ldbghelp \
|
||||
-liphlpapi \
|
||||
@@ -736,9 +809,7 @@ $(WINDOWS_TARGETS): LDFLAGS += \
|
||||
$(ANDROID_TARGETS): LDFLAGS += \
|
||||
-target $(ANDROID_NDK_TARGET_TRIPLE)$(ANDROID_MIN_SDK_VERSION) \
|
||||
-ldl \
|
||||
-llog \
|
||||
-lssl \
|
||||
-lcrypto
|
||||
-llog
|
||||
$(MACOS_TARGETS) $(IOS_TARGETS) $(IOSSIM_TARGETS): CFLAGS += \
|
||||
-Wno-unknown-warning-option
|
||||
$(IOS_TARGETS) $(IOSSIM_TARGETS): LDFLAGS += \
|
||||
@@ -747,9 +818,16 @@ $(IOS_TARGETS) $(IOSSIM_TARGETS): LDFLAGS += \
|
||||
-framework UIKit \
|
||||
-framework WebKit
|
||||
|
||||
unix: debug release
|
||||
win: windebug winrelease
|
||||
all: $(BUILD_TYPES)
|
||||
##
|
||||
## Common targets:
|
||||
##
|
||||
all: $(BUILD_TYPES) ## Build all targets that appear possible to build on this machine.
|
||||
debug: ## Build a debug executable for the current host platform.
|
||||
release: ## Build a release executable for the current host platform.
|
||||
armdebug: ## Cross-compile aarch64 debug on Linux.
|
||||
armrelease: ## Cross-compile aarch64 release on Linux.
|
||||
windebug: ## Cross-compile a debug win32 executable on Linux.
|
||||
winrelease: ## Cross-compile a release win32 executable on Linux.
|
||||
.PHONY: all win unix
|
||||
|
||||
ALL_APP_OBJS := \
|
||||
@@ -807,35 +885,58 @@ src/android/AndroidManifest.xml : $(firstword $(MAKEFILE_LIST))
|
||||
-e 's/android:targetSdkVersion="[[:digit:]]*"/android:targetSdkVersion="$(ANDROID_TARGET_SDK_VERSION)"/' \
|
||||
$@
|
||||
|
||||
# Android support.
|
||||
src/ios/Info.plist : $(firstword $(MAKEFILE_LIST))
|
||||
@echo "[ios_version] $@"
|
||||
@cat $@ | \
|
||||
tr '\n' '^' | \
|
||||
sed -r \
|
||||
-e 's@(<key>CFBundleShortVersionString</key>\^[[:space:]]*<string>)[0-9.]*(</string>)@\1$(VERSION_NUMBER:%-wip=%)\2@' \
|
||||
-e 's@(<key>CFBundleVersion</key>\^[[:space:]]*<string>)[[:digit:]]+(</string>)@\1$(VERSION_CODE_IOS)\2@' \
|
||||
-e 's@(<key>MinimumOSVersion</key>\^[[:space:]]*<string>)[0-9.]*(</string>)@\1$(IPHONEOS_VERSION_MIN)\2@' | \
|
||||
tr '^' '\n' > \
|
||||
$@.tmp && mv $@.tmp $@ || rm -f $@.tmp
|
||||
|
||||
##
|
||||
## Android targets:
|
||||
##
|
||||
androiddebug: ## Build a debug 64-bit ARM Android APK.
|
||||
androidrelease: ## Build a release 64-bit ARM Android APK.
|
||||
androiddebug-armv7a: ## Build a debug 32-bit ARM Android APK.
|
||||
androidrelease-armv7a: ## Build a release 32-bit ARM Android APK.
|
||||
androiddebug-x86: ## Build a debug x86 Android APK.
|
||||
androidrelease-x86: ## Build a release x86 Android APK.
|
||||
androiddebug-x86_64: ## Build a debug x86_64 Android APK.
|
||||
androidrelease-x86_64: ## Build a release x86_64 Android APK.
|
||||
|
||||
out/res/layout_activity_main.xml.flat: src/android/res/layout/activity_main.xml
|
||||
@mkdir -p $(dir $@)
|
||||
@echo "[aapt2] $@"
|
||||
@$(ANDROID_BUILD_TOOLS)/aapt2 compile -o out/res/ src/android/res/layout/activity_main.xml
|
||||
|
||||
out/res/drawable_icon.xml.flat: src/android/res/drawable/icon.xml
|
||||
out/res/drawable_%.xml.flat: src/android/res/drawable/%.xml
|
||||
@mkdir -p $(dir $@)
|
||||
@echo "[aapt2] $@"
|
||||
@$(ANDROID_BUILD_TOOLS)/aapt2 compile -o out/res/ src/android/res/drawable/icon.xml
|
||||
@$(ANDROID_BUILD_TOOLS)/aapt2 compile -o out/res/ $(<)
|
||||
|
||||
out/apk/res.apk out/gen/com/unprompted/tildefriends/R.java: out/res/layout_activity_main.xml.flat out/res/drawable_icon.xml.flat src/android/AndroidManifest.xml
|
||||
out/apk/res.apk out/gen/com/unprompted/tildefriends/R.java: out/res/layout_activity_main.xml.flat out/res/drawable_icon.xml.flat out/res/drawable_logo.xml.flat src/android/AndroidManifest.xml
|
||||
@echo [aapt2 link] res.apk
|
||||
@mkdir -p out/apk/
|
||||
@$(ANDROID_BUILD_TOOLS)/aapt2 link -I $(ANDROID_PLATFORM)/android.jar out/res/layout_activity_main.xml.flat out/res/drawable_icon.xml.flat \
|
||||
@$(ANDROID_BUILD_TOOLS)/aapt2 link -I $(ANDROID_PLATFORM)/android.jar out/res/layout_activity_main.xml.flat out/res/drawable_icon.xml.flat out/res/drawable_logo.xml.flat \
|
||||
--min-sdk-version $(ANDROID_MIN_SDK_VERSION) \
|
||||
--target-sdk-version $(ANDROID_TARGET_SDK_VERSION) \
|
||||
--manifest src/android/AndroidManifest.xml \
|
||||
-o out/apk/res.apk \
|
||||
--java out/gen/
|
||||
|
||||
out/apk/res.fdroid.apk out/gen_fdroid/com/unprompted/tildefriends/R.java: out/res/layout_activity_main.xml.flat out/res/drawable_icon.xml.flat src/android/AndroidManifest.xml
|
||||
out/apk/res.fdroid.apk out/gen_fdroid/com/unprompted/tildefriends/R.java: out/res/layout_activity_main.xml.flat out/res/drawable_icon_fdroid.xml.flat out/res/drawable_logo.xml.flat src/android/AndroidManifest.xml
|
||||
@echo [aapt2 link] res.fdroid.apk
|
||||
@mkdir -p out/apk/
|
||||
@$(ANDROID_BUILD_TOOLS)/aapt2 link -I $(ANDROID_PLATFORM)/android.jar out/res/layout_activity_main.xml.flat out/res/drawable_icon.xml.flat \
|
||||
@sed -e 's@drawable/icon@drawable/icon_fdroid@' src/android/AndroidManifest.xml > out/apk/AndroidManifest.fdroid.xml
|
||||
@$(ANDROID_BUILD_TOOLS)/aapt2 link -I $(ANDROID_PLATFORM)/android.jar out/res/layout_activity_main.xml.flat out/res/drawable_icon_fdroid.xml.flat out/res/drawable_logo.xml.flat \
|
||||
--min-sdk-version $(ANDROID_MIN_SDK_VERSION) \
|
||||
--target-sdk-version $(ANDROID_TARGET_SDK_VERSION) \
|
||||
--rename-manifest-package com.unprompted.tildefriends.fdroid \
|
||||
--manifest src/android/AndroidManifest.xml \
|
||||
--manifest out/apk/AndroidManifest.fdroid.xml \
|
||||
-o out/apk/res.fdroid.apk \
|
||||
--java out/gen_fdroid/
|
||||
|
||||
@@ -852,11 +953,12 @@ out/apk/classes.dex: $(CLASS_FILES)
|
||||
@$(ANDROID_BUILD_TOOLS)/d8 --lib $(ANDROID_PLATFORM)/android.jar --output $(dir $@) out/classes/com/unprompted/tildefriends/*.class
|
||||
|
||||
PACKAGE_DIRS := \
|
||||
apps/ \
|
||||
core/ \
|
||||
deps/codemirror/ \
|
||||
deps/prettier/ \
|
||||
deps/lit/
|
||||
apps \
|
||||
core \
|
||||
deps/codemirror \
|
||||
deps/prettier \
|
||||
deps/lit \
|
||||
deps/speedscope
|
||||
|
||||
RAW_FILES := $(sort $(filter-out apps/blog% apps/issues% apps/welcome% apps/journal% %.map, $(shell find $(PACKAGE_DIRS) -type f -not -name '.*')))
|
||||
|
||||
@@ -877,7 +979,7 @@ $(BUNDLETOOL):
|
||||
@curl -q -L --create-dirs -o $@ $(BUNDLETOOL_URL)
|
||||
|
||||
out/TildeFriends.aab: out/apk/classes.dex $(filter-out %debug%, $(ANDROID_TARGETS)) $(RAW_FILES) out/apk/res.apk src/android/AndroidManifest.xml $(BUNDLETOOL)
|
||||
@rm -rf out/aab/staging/
|
||||
@rm -rf out/aab/staging/ out/aab/base.zip
|
||||
@mkdir -p out/aab/staging
|
||||
@$(ANDROID_BUILD_TOOLS)/aapt2 link --proto-format -o out/aab/temporary.apk \
|
||||
-I $(ANDROID_PLATFORM)/android.jar \
|
||||
@@ -886,6 +988,7 @@ out/TildeFriends.aab: out/apk/classes.dex $(filter-out %debug%, $(ANDROID_TARGET
|
||||
--manifest src/android/AndroidManifest.xml \
|
||||
-R out/res/layout_activity_main.xml.flat \
|
||||
-R out/res/drawable_icon.xml.flat \
|
||||
-R out/res/drawable_logo.xml.flat \
|
||||
--auto-add-overlay
|
||||
@unzip out/aab/temporary.apk -d out/aab/staging/
|
||||
@mkdir -p out/aab/staging/root/deps
|
||||
@@ -896,14 +999,11 @@ out/TildeFriends.aab: out/apk/classes.dex $(filter-out %debug%, $(ANDROID_TARGET
|
||||
@cp out/apk/classes.dex out/aab/staging/dex/
|
||||
@rm -fv out/base.zip
|
||||
@mkdir -p out/aab/staging/lib/arm64-v8a out/aab/staging/lib/armeabi-v7a out/aab/staging/lib/x86_64 out/aab/staging/lib/x86
|
||||
@cp out/androidrelease/tildefriends out/aab/staging/lib/arm64-v8a/libtildefriends.so
|
||||
@cp out/androidrelease-armv7a/tildefriends out/aab/staging/lib/armeabi-v7a/libtildefriends.so
|
||||
@cp out/androidrelease-x86_64/tildefriends out/aab/staging/lib/x86_64/libtildefriends.so
|
||||
@cp out/androidrelease-x86/tildefriends out/aab/staging/lib/x86/libtildefriends.so
|
||||
@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip out/aab/staging/lib/arm64-v8a/libtildefriends.so
|
||||
@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip out/aab/staging/lib/armeabi-v7a/libtildefriends.so
|
||||
@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip out/aab/staging/lib/x86_64/libtildefriends.so
|
||||
@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip out/aab/staging/lib/x86/libtildefriends.so
|
||||
@mkdir -p out/aab/staging/BUNDLE-METADATA/com.android.tools.build.debugsymbols/arm64-v8a out/aab/staging/BUNDLE-METADATA/com.android.tools.build.debugsymbols/armeabi-v7a out/aab/staging/BUNDLE-METADATA/com.android.tools.build.debugsymbols/x86_64 out/aab/staging/BUNDLE-METADATA/com.android.tools.build.debugsymbols/x86
|
||||
@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip out/androidrelease/tildefriends -o out/aab/staging/lib/arm64-v8a/libtildefriends.so
|
||||
@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip out/androidrelease-armv7a/tildefriends -o out/aab/staging/lib/armeabi-v7a/libtildefriends.so
|
||||
@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip out/androidrelease-x86_64/tildefriends -o out/aab/staging/lib/x86_64/libtildefriends.so
|
||||
@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip out/androidrelease-x86/tildefriends -o out/aab/staging/lib/x86/libtildefriends.so
|
||||
@cp -r apps/ out/aab/staging/root/
|
||||
@rm -rf out/aab/staging/root/apps/welcome*
|
||||
@cp -r core/ out/aab/staging/root/
|
||||
@@ -912,9 +1012,14 @@ out/TildeFriends.aab: out/apk/classes.dex $(filter-out %debug%, $(ANDROID_TARGET
|
||||
@cp -r deps/codemirror/ out/aab/staging/root/deps/
|
||||
@cd out/aab/staging/; zip -r ../base.zip *; cd ../../../
|
||||
@java -jar $(BUNDLETOOL) build-bundle --overwrite --config=src/android/BundleConfig.json --modules=out/aab/base.zip --output=$@
|
||||
@jarsigner -keystore .keys/android.jks $@ androidKey -storepass android
|
||||
@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip --only-keep-debug out/androidrelease/tildefriends -o out/aab/staging/BUNDLE-METADATA/com.android.tools.build.debugsymbols/arm64-v8a/libtildefriends.so.sym
|
||||
@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip --only-keep-debug out/androidrelease-armv7a/tildefriends -o out/aab/staging/BUNDLE-METADATA/com.android.tools.build.debugsymbols/armeabi-v7a/libtildefriends.so.sym
|
||||
@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip --only-keep-debug out/androidrelease-x86_64/tildefriends -o out/aab/staging/BUNDLE-METADATA/com.android.tools.build.debugsymbols/x86_64/libtildefriends.so.sym
|
||||
@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip --only-keep-debug out/androidrelease-x86/tildefriends -o out/aab/staging/BUNDLE-METADATA/com.android.tools.build.debugsymbols/x86/libtildefriends.so.sym
|
||||
@cd out/aab/staging; zip -u ../../../$@ BUNDLE-METADATA/com.android.tools.build.debugsymbols/arm64-v8a/libtildefriends.so.sym BUNDLE-METADATA/com.android.tools.build.debugsymbols/armeabi-v7a/libtildefriends.so.sym BUNDLE-METADATA/com.android.tools.build.debugsymbols/x86_64/libtildefriends.so.sym BUNDLE-METADATA/com.android.tools.build.debugsymbols/x86/libtildefriends.so.sym; cd ../../../
|
||||
@$(ANDROID_BUILD_TOOLS)/apksigner sign -ks .keys/android.jks --ks-key-alias androidKey -ks-pass pass:android --min-sdk-version=$(ANDROID_MIN_SDK_VERSION) --alignment-preserved $@
|
||||
|
||||
aab: out/TildeFriends.aab
|
||||
aab: out/TildeFriends.aab ## Build an Android App Bundle.
|
||||
.PHONY: aab
|
||||
|
||||
out/TildeFriends.apks: out/TildeFriends.aab $(BUNDLETOOL)
|
||||
@@ -974,27 +1079,44 @@ out/apk/TildeFriends-%.fdroid.unsigned.apk:
|
||||
|
||||
out/%.apk: out/apk/%.unsigned.apk
|
||||
@echo "[apksigner] $(notdir $@)"
|
||||
@$(ANDROID_BUILD_TOOLS)/apksigner sign --ks .keys/android.jks --ks-key-alias androidKey --ks-pass pass:android --key-pass pass:android --min-sdk-version $(ANDROID_MIN_SDK_VERSION) --out $@ $<
|
||||
@$(ANDROID_BUILD_TOOLS)/apksigner sign --ks .keys/android.jks --ks-key-alias androidKey --ks-pass pass:android --key-pass pass:android --min-sdk-version $(ANDROID_MIN_SDK_VERSION) --out $@ --alignment-preserved $<
|
||||
|
||||
out/%.zopfli.apk: out/%.apk
|
||||
@echo "[zopfli] $(notdir $@)"
|
||||
$(ANDROID_BUILD_TOOLS)/zipalign -f -z 4 $< $@.zopfli
|
||||
@$(ANDROID_BUILD_TOOLS)/apksigner sign --ks .keys/android.jks --ks-key-alias androidKey --ks-pass pass:android --key-pass pass:android --min-sdk-version $(ANDROID_MIN_SDK_VERSION) --out $@ $@.zopfli
|
||||
@$(ANDROID_BUILD_TOOLS)/apksigner sign --ks .keys/android.jks --ks-key-alias androidKey --ks-pass pass:android --key-pass pass:android --min-sdk-version $(ANDROID_MIN_SDK_VERSION) --out $@ --alignment-preserved $@.zopfli
|
||||
|
||||
release-apk: out/TildeFriends-arm-release.zopfli.apk out/TildeFriends-x86-release.zopfli.apk
|
||||
release-apk: out/TildeFriends-arm-release.zopfli.apk out/TildeFriends-x86-release.zopfli.apk ## Build an Android release APK.
|
||||
.PHONY: release-apk
|
||||
|
||||
apkgo: out/TildeFriends-arm-debug.apk
|
||||
fdroid: out/apk/TildeFriends-release.fdroid.unsigned.apk ## Build Android APK for distribution on F-Droid.
|
||||
.PHONY: fdroid
|
||||
|
||||
apkgo: out/TildeFriends-arm-debug.apk ## Build, install, and run a debug Android APK.
|
||||
@adb install -r $<
|
||||
@adb shell am start com.unprompted.tildefriends/.TildeFriendsActivity
|
||||
.PHONY: apkgo
|
||||
|
||||
releaseapkgo: out/TildeFriends-arm-release.apk
|
||||
releaseapkgo: out/TildeFriends-arm-release.apk ## Build, install, and run a release Android APK.
|
||||
@adb install -r $<
|
||||
@adb shell am start com.unprompted.tildefriends/.TildeFriendsActivity
|
||||
.PHONY: releaseapkgo
|
||||
|
||||
# iOS Support
|
||||
x86releaseapkgo: out/TildeFriends-x86-release.apk ## Build, install, and run an x86 release Android APK.
|
||||
@adb install -r $<
|
||||
@adb shell am start com.unprompted.tildefriends/.TildeFriendsActivity
|
||||
.PHONY: x86releaseapkgo
|
||||
|
||||
apklog: ## Display Android log output.
|
||||
@adb logcat *:S tildefriends
|
||||
.PHONY: apklog
|
||||
|
||||
##
|
||||
## iPhoneOS targets:
|
||||
##
|
||||
iosdebug: ## Build a debug iPhoneOS executable.
|
||||
iosrelease: ## Build a release iPhoneOS executable.
|
||||
|
||||
out/%.app/Info.plist: src/ios/Info.plist
|
||||
@mkdir -p $(dir $@)
|
||||
@cp -v $< $@
|
||||
@@ -1003,20 +1125,34 @@ out/%.app/tildefriends.png: src/ios/tildefriends.png
|
||||
@cp -v $< $@
|
||||
|
||||
out/data.zip: $(RAW_FILES)
|
||||
@echo [zip] $@
|
||||
@zip -u $@ -q -9 $(RAW_FILES)
|
||||
|
||||
out/tildefriends-%.app/tildefriends: out/%/tildefriends out/tildefriends-%.app/Info.plist out/tildefriends-%.app/tildefriends.png out/data.zip
|
||||
out/zsign_build/zsign: $(wildcard deps/zsign/*.cpp deps/zsign/*.h deps/zsign/*.txt deps/zsign/common/*)
|
||||
@+echo [cmake] $@
|
||||
@cmake -B out/zsign_build deps/zsign
|
||||
@cmake --build out/zsign_build -- COLOR=0 VERBOSE=0 MAKESILENT=-s
|
||||
|
||||
ifeq ($(HAVE_LINUX_IOS),1)
|
||||
ZSIGN_DEP = out/zsign_build/zsign
|
||||
else
|
||||
ZSIGN_DEP =
|
||||
endif
|
||||
|
||||
out/tildefriends-%.app/tildefriends: out/%/tildefriends out/tildefriends-%.app/Info.plist out/tildefriends-%.app/tildefriends.png out/data.zip $(ZSIGN_DEP)
|
||||
@mkdir -p $(dir $@)
|
||||
@cp -v $< $@
|
||||
@cp -v $(filter-out out/zsign%,$<) $@
|
||||
@cp -v out/data.zip $(@D)/
|
||||
ifeq ($(HAVE_LINUX_IOS),1)
|
||||
@zsign -q -k .keys/apple.p12 -f -m src/ios/embedded.mobileprovision $(realpath $(dir $@))
|
||||
@mkdir -p $(realpath $(dir $@))/_CodeSignature
|
||||
@out/zsign_build/zsign -q -k .keys/apple.p12 -f -m src/ios/embedded.mobileprovision $(realpath $(dir $@))
|
||||
endif
|
||||
.SECONDARY:
|
||||
out/tildefriends-%.ipa: out/tildefriends-ios%.app/tildefriends
|
||||
@echo "[ipa] $@"
|
||||
@rm -rf $@.tmp $@
|
||||
@mkdir -p $@.tmp/Payload/tildefriends.app/
|
||||
@cp src/ios/tildefriends512.png $@.tmp/iTunesArtwork
|
||||
@cp -R $(dir $<)/* $@.tmp/Payload/tildefriends.app/
|
||||
@cd $@.tmp/ && zip -u ../../$@ -q -9 -r ./
|
||||
@rm -rf $@.tmp/
|
||||
@@ -1031,57 +1167,31 @@ out/%/tildefriends.standalone.exe: out/%/tildefriends.exe out/data.zip
|
||||
@cat $< out/data.zip > $@
|
||||
@chmod +x $@
|
||||
|
||||
iossimdebug-app: out/tildefriends-iossimdebug.app/tildefriends
|
||||
iossimrelease-app: out/tildefriends-iossimrelease.app/tildefriends
|
||||
iosdebug-app: out/tildefriends-iosdebug.app/tildefriends
|
||||
iosrelease-app: out/tildefriends-iosrelease.app/tildefriends
|
||||
iossimdebug-app: out/tildefriends-iossimdebug.app/tildefriends ## Build a debug iOS Simulator .app directory.
|
||||
iossimrelease-app: out/tildefriends-iossimrelease.app/tildefriends ## Build a release iOS Simulator .app directory.
|
||||
iosdebug-app: out/tildefriends-iosdebug.app/tildefriends ## Build a debug iOS .app directory.
|
||||
iosrelease-app: out/tildefriends-iosrelease.app/tildefriends ## Build a release iOS .app directory.
|
||||
|
||||
iosdebug-ipa: out/tildefriends-debug.ipa
|
||||
iosrelease-ipa: out/tildefriends-release.ipa
|
||||
iosdebug-ipa: out/tildefriends-debug.ipa ## Build a debug iOS .ipa.
|
||||
iosrelease-ipa: out/tildefriends-release.ipa ## Build a release iOS .ipa.
|
||||
.PHONY: iossimdebug-app iossimrelease-app iosdebug-app iosrelease-app
|
||||
|
||||
ios%go: out/tildefriends-ios%.app/tildefriends
|
||||
ideviceinstaller -i $(realpath $(dir $<))
|
||||
|
||||
iossimdebuggo: out/tildefriends-iossimdebug.app/tildefriends
|
||||
iossimdebuggo: out/tildefriends-iossimdebug.app/tildefriends ## Build, install, and run an iOS debug build.
|
||||
xcrun simctl install booted out/tildefriends-iossimdebug.app/
|
||||
xcrun simctl launch booted com.unprompted.tildefriends
|
||||
.PHONY: iossimdebuggo
|
||||
|
||||
apklog:
|
||||
@adb logcat *:S tildefriends
|
||||
.PHONY: apklog
|
||||
out/macos%/tildefriends: out/macos%-arm/tildefriends out/macos%-x86_64/tildefriends
|
||||
@echo [lipo] $@
|
||||
@mkdir -p $(@D)
|
||||
@deps/macos_toolchain/bin/lipo -create -output $@ $^
|
||||
|
||||
fetchdeps:
|
||||
@echo "[fetch] sqlite"
|
||||
@test -f out/deps/sqlite.zip && test "$$(cat out/deps/sqlite.txt 2>/dev/null)" = $(SQLITE_URL) || (mkdir -p out/deps/ && curl -q $(SQLITE_URL) -o out/deps/sqlite.zip)
|
||||
@test -d deps/sqlite/ && test "$$(cat out/deps/sqlite.txt 2>/dev/null)" = $(SQLITE_URL) || (mkdir -p deps/sqlite/ && unzip -qDjo -d deps/sqlite/ out/deps/sqlite.zip)
|
||||
@echo -n $(SQLITE_URL) > out/deps/sqlite.txt
|
||||
@echo "[fetch] prettier"
|
||||
@test -f deps/prettier/standalone.mjs || curl -q --create-dirs -O --output-dir deps/prettier/ https://cdn.jsdelivr.net/npm/prettier@3.2.5/standalone.mjs
|
||||
@test -f deps/prettier/html.mjs || curl -q --create-dirs -O --output-dir deps/prettier/ https://cdn.jsdelivr.net/npm/prettier@3.2.5/plugins/html.mjs
|
||||
@test -f deps/prettier/babel.mjs || curl -q --create-dirs -O --output-dir deps/prettier/ https://cdn.jsdelivr.net/npm/prettier@3.2.5/plugins/babel.mjs
|
||||
@test -f deps/prettier/estree.mjs || curl -q --create-dirs -O --output-dir deps/prettier/ https://cdn.jsdelivr.net/npm/prettier@3.2.5/plugins/estree.mjs
|
||||
.PHONY: fetchdeps
|
||||
|
||||
ANDROID_DEPS := deps/openssl/android/arm64-v8a/usr/local/lib/libssl.a
|
||||
$(ANDROID_DEPS):
|
||||
+@ANDROID_NDK_ROOT=$(ANDROID_NDK) tools/ssl-android
|
||||
$(filter $(BUILD_DIR)/android%,$(APP_OBJS)): | $(ANDROID_DEPS)
|
||||
|
||||
ifeq ($(HAVE_WIN),1)
|
||||
WINDOWS_DEPS := deps/openssl/mingw64/usr/local/lib/libssl.a
|
||||
$(WINDOWS_DEPS):
|
||||
+@tools/ssl-mingw64
|
||||
$(filter $(BUILD_DIR)/win%,$(APP_OBJS)): | $(WINDOWS_DEPS)
|
||||
endif
|
||||
|
||||
ifeq ($(UNAME_S),Darwin)
|
||||
IOS_DEPS := deps/openssl/ios/ios64-xcrun/usr/local/lib/libssl.a
|
||||
$(IOS_DEPS):
|
||||
+@tools/ssl-ios
|
||||
$(filter $(BUILD_DIR)/ios%,$(APP_OBJS)): | $(IOS_DEPS)
|
||||
endif
|
||||
##
|
||||
## Linux package targets:
|
||||
##
|
||||
|
||||
out/tildefriends-x86_64.AppImage: out/release/tildefriends out/data.zip
|
||||
@echo "[appimage] $$@"
|
||||
@@ -1102,19 +1212,37 @@ out/tildefriends-x86_64.AppImage: out/release/tildefriends out/data.zip
|
||||
@cd out; ./appimagetool --appimage-extract; cd ..
|
||||
@cd out; unset SOURCE_DATE_EPOCH; PATH=$$PATH:squashfs-root/usr/bin ARCH=x86_64 squashfs-root/usr/bin/appimagetool -u 'zsync|https://dev.tildefriends.net/releases/tildefriends-x86_64.AppImage.zsync' tildefriends.AppDir tildefriends-x86_64.AppImage; cd ..
|
||||
|
||||
appimage: out/tildefriends-x86_64.AppImage
|
||||
appimage: out/tildefriends-x86_64.AppImage ## Build an AppImage.
|
||||
.PHONY: appimage
|
||||
|
||||
flatpak: out/
|
||||
flatpak-builder --force-clean --user --install-deps-from=flathub --install --repo=out/flatpak-repo out/flatpak src/com.unprompted.tildefriends.yml
|
||||
flatpak: out/ ## Build a flatpak.
|
||||
flatpak remote-add --user --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo
|
||||
flatpak-builder --user --disable-rofiles-fuse --install-deps-from=flathub --install --repo=out/flatpak-repo out/flatpak src/com.unprompted.tildefriends.yml
|
||||
flatpak build-bundle out/flatpak-repo out/tildefriends.flatpak com.unprompted.tildefriends
|
||||
.PHONY: flatpak
|
||||
|
||||
clean:
|
||||
rm -rf $(BUILD_DIR)
|
||||
.PHONY: clean
|
||||
##
|
||||
## Targets for release management:
|
||||
##
|
||||
|
||||
tarball:
|
||||
fetchdeps: ## Update various external sources that live in the tree that can't be pulled in as git submodules.
|
||||
@echo "[fetch] sqlite"
|
||||
@test -f out/deps/sqlite.zip && test "$$(cat out/deps/sqlite.txt 2>/dev/null)" = $(SQLITE_URL) || (mkdir -p out/deps/ && curl -q $(SQLITE_URL) -o out/deps/sqlite.zip)
|
||||
@test -d deps/sqlite/ && test "$$(cat out/deps/sqlite.txt 2>/dev/null)" = $(SQLITE_URL) || (mkdir -p deps/sqlite/ && unzip -qDjo -d deps/sqlite/ out/deps/sqlite.zip)
|
||||
@echo -n $(SQLITE_URL) > out/deps/sqlite.txt
|
||||
@echo "[fetch] prettier"
|
||||
@test -f deps/prettier/standalone.mjs || curl -q --create-dirs -O --output-dir deps/prettier/ https://cdn.jsdelivr.net/npm/prettier@3.2.5/standalone.mjs
|
||||
@test -f deps/prettier/html.mjs || curl -q --create-dirs -O --output-dir deps/prettier/ https://cdn.jsdelivr.net/npm/prettier@3.2.5/plugins/html.mjs
|
||||
@test -f deps/prettier/babel.mjs || curl -q --create-dirs -O --output-dir deps/prettier/ https://cdn.jsdelivr.net/npm/prettier@3.2.5/plugins/babel.mjs
|
||||
@test -f deps/prettier/estree.mjs || curl -q --create-dirs -O --output-dir deps/prettier/ https://cdn.jsdelivr.net/npm/prettier@3.2.5/plugins/estree.mjs
|
||||
.PHONY: fetchdeps
|
||||
|
||||
shots: ## Copy generated screenshots from `tildefriends test -t=auto` into place in the metadata/ directory.
|
||||
@echo [shots] $(wildcard out/screenshot*.png)
|
||||
@cp -f out/screenshot*.png metadata/en-US/images/phoneScreenshots/
|
||||
.PHONY: shots
|
||||
|
||||
tarball: ## Build an all-inclusive source tarball (.tar.xz).
|
||||
@echo [archive] out/tildefriends-$(VERSION_NUMBER).tar.xz
|
||||
@rm -rf out/tildefriends-$(VERSION_NUMBER)
|
||||
@mkdir -p out/tildefriends-$(VERSION_NUMBER)
|
||||
@@ -1129,7 +1257,6 @@ tarball:
|
||||
--exclude=deps/libsodium/test \
|
||||
--exclude=deps/libuv/docs \
|
||||
--exclude=deps/libuv/test \
|
||||
--exclude=deps/openssl \
|
||||
--exclude=deps/speedscope/*.map \
|
||||
--exclude=deps/sqlite/shell.c \
|
||||
--exclude=deps/zlib/contrib/vstudio \
|
||||
@@ -1139,7 +1266,21 @@ tarball:
|
||||
tildefriends-$(VERSION_NUMBER)
|
||||
.PHONY: tarball
|
||||
|
||||
dist: release-apk iosrelease-ipa aab $(if $(HAVE_WIN), out/winrelease/tildefriends.standalone.exe) out/TildeFriends-release.fdroid.apk appimage tarball
|
||||
dist: ## Build versions of all distributables for release.
|
||||
dist: release-apk aab out/TildeFriends-release.fdroid.apk appimage tarball out/release/tildefriends.standalone
|
||||
ifeq ($(HAVE_LINUX_IOS),1)
|
||||
dist: iosrelease-ipa
|
||||
endif
|
||||
ifeq ($(HAVE_LINUX_MACOS),1)
|
||||
dist: out/macosrelease/tildefriends.standalone
|
||||
endif
|
||||
ifeq ($(HAVE_WIN),1)
|
||||
dist: out/winrelease/tildefriends.standalone.exe
|
||||
endif
|
||||
ifeq ($(HAVE_CROSS_AARCH64),1)
|
||||
dist: out/armrelease/tildefriends.standalone
|
||||
endif
|
||||
dist:
|
||||
@mkdir -p dist/
|
||||
@echo "[cp] tildefriends-$(VERSION_NUMBER).tar.xz"
|
||||
@cp out/tildefriends-$(VERSION_NUMBER).tar.xz dist/tildefriends-$(VERSION_NUMBER).tar.xz
|
||||
@@ -1147,8 +1288,10 @@ dist: release-apk iosrelease-ipa aab $(if $(HAVE_WIN), out/winrelease/tildefrien
|
||||
@cp out/TildeFriends-x86-release.zopfli.apk dist/TildeFriends-x86-$(VERSION_NUMBER).apk
|
||||
@echo "[cp] TildeFriends-arm-$(VERSION_NUMBER).apk"
|
||||
@cp out/TildeFriends-arm-release.zopfli.apk dist/TildeFriends-arm-$(VERSION_NUMBER).apk
|
||||
@echo "[cp] TildeFriends-$(VERSION_NUMBER).ipa"
|
||||
@cp out/tildefriends-release.ipa dist/TildeFriends-$(VERSION_NUMBER).ipa
|
||||
@test $(HAVE_LINUX_IOS) && echo "[cp] TildeFriends-$(VERSION_NUMBER).ipa"
|
||||
@test $(HAVE_LINUX_IOS) && cp out/tildefriends-release.ipa dist/TildeFriends-$(VERSION_NUMBER).ipa
|
||||
@test $(HAVE_LINUX_MACOS) && echo "[cp] tildefriends-macos-$(VERSION_NUMBER)"
|
||||
@test $(HAVE_LINUX_MACOS) && cp out/macosrelease/tildefriends.standalone dist/tildefriends-macos-$(VERSION_NUMBER)
|
||||
@test $(HAVE_WIN) && echo "[cp] tildefriends-$(VERSION_NUMBER).exe"
|
||||
@test $(HAVE_WIN) && cp out/winrelease/tildefriends.standalone.exe dist/tildefriends-$(VERSION_NUMBER).exe
|
||||
@echo "[cp] TildeFriends-$(VERSION_NUMBER).aab"
|
||||
@@ -1157,26 +1300,82 @@ dist: release-apk iosrelease-ipa aab $(if $(HAVE_WIN), out/winrelease/tildefrien
|
||||
@cp out/TildeFriends-release.fdroid.apk dist/TildeFriends-$(VERSION_NUMBER).fdroid.apk
|
||||
@echo "[cp] TildeFriends-x86_64-$(VERSION_NUMBER).AppImage"
|
||||
@cp out/tildefriends-x86_64.AppImage dist/TildeFriends-x86_64-$(VERSION_NUMBER).AppImage
|
||||
@echo "[cp] tildefriends-linux-$(UNAME_M)-$(VERSION_NUMBER)"
|
||||
@cp out/release/tildefriends.standalone dist/tildefriends-linux-$(UNAME_M)-$(VERSION_NUMBER)
|
||||
@test $(HAVE_CROSS_AARCH64) && echo "[cp] tildefriends-linux-aarch64-$(VERSION_NUMBER)"
|
||||
@test $(HAVE_CROSS_AARCH64) && cp out/armrelease/tildefriends.standalone dist/tildefriends-linux-aarch64-$(VERSION_NUMBER)
|
||||
.PHONY: dist
|
||||
|
||||
dist-test: dist
|
||||
dist-test: dist ## Exercise some built distributable files, making sure they work as intended.
|
||||
@tar -xf tildefriends-$(VERSION_NUMBER).tar.xz
|
||||
@$(MAKE) -C tildefriends-$(VERSION_NUMBER)/ debug release
|
||||
@docker build tildefriends-$(VERSION_NUMBER)/
|
||||
@rm -rf tildefriends-$(VERSION_NUMBER)
|
||||
.PHONY: dist-test
|
||||
|
||||
format:
|
||||
dist-ios: iosrelease-app
|
||||
rm -rfv out/Payload out/tildefriends.ipa
|
||||
mkdir -p out/Payload/tildefriends.app
|
||||
cp -avR out/tildefriends-iosrelease.app/* out/Payload/tildefriends.app/
|
||||
cp src/ios/tildefriends.png out/Payload/tildefriends.app/
|
||||
xcrun -sdk iphoneos actool --compile out/Payload/tildefriends.app/ --platform iphoneos --minimum-deployment-target $(IPHONEOS_VERSION_MIN) --app-icon AppIcon src/ios/icons/Assets.xcassets src/ios/icons/*.png --output-partial-info-plist out/actool.plist
|
||||
cp src/ios/distribution.mobileprovision out/Payload/tildefriends.app/embedded.mobileprovision
|
||||
xcrun -sdk iphoneos codesign -f -s 'Apple Distribution' --entitlements src/ios/Entitlements.plist --generate-entitlement-der out/Payload/tildefriends.app
|
||||
cd out; zip -r tildefriends.ipa Payload; cd ..
|
||||
xcrun -sdk iphoneos altool --upload-app -f out/tildefriends.ipa -t ios -u $$(cat .keys/altool-user) -p $$(cat .keys/altool-password)
|
||||
|
||||
##
|
||||
## Targets for tidying up:
|
||||
##
|
||||
|
||||
format: ## Standardize formatting of C source.
|
||||
@clang-format -i $(wildcard src/*.c src/*.h src/*.m)
|
||||
.PHONY: format
|
||||
|
||||
prettier:
|
||||
prettier: ## Standardize formatting of JavaScript and Markdown source.
|
||||
@npm run prettier
|
||||
.PHONY: prettier
|
||||
|
||||
docs:
|
||||
clean: ## Clean all generated files from the out/ directory.
|
||||
rm -rf $(BUILD_DIR)
|
||||
.PHONY: clean
|
||||
|
||||
##
|
||||
## Documentation:
|
||||
##
|
||||
help: ## Display this help message.
|
||||
@awk \
|
||||
-F: \
|
||||
-vG=$$(tput setaf 2) \
|
||||
-vO=$$(tput setaf 3) \
|
||||
-vB=$$(tput setaf 4) \
|
||||
-vM=$$(tput setaf 5) \
|
||||
-vC=$$(tput setaf 6) \
|
||||
-vR=$$(tput sgr0) ' \
|
||||
/^## ==.*==$$/ { sub(/^## ?/, ""); printf "%s%s%s\n", C, $$0, R } \
|
||||
/^##.*:=.*/ { sub(/^## ?/, ""); sub(/:=/, ":"); printf " %s%-20s%s %s%s%s\n", M, $$1, R, O, $$2, R } \
|
||||
/^##/ { sub(/^## ?/, ""); print $$0 } \
|
||||
/^[[:alnum:]-]+:.*##/ { \
|
||||
sub(/:.*##\s?/, ":"); \
|
||||
printf " %s%-21s%s %s%s%s\n", G, $$1, R, O, $$2, R \
|
||||
} \
|
||||
' < $(filter-out %.d,$(MAKEFILE_LIST))
|
||||
@echo "" # Blank line.
|
||||
.PHONY: help
|
||||
.DEFAULT_GOAL := help
|
||||
|
||||
docs: debug
|
||||
docs: ## Build HTML docs.
|
||||
@echo '# CLI Usage\n' > docs/usage.md
|
||||
@echo "## tildefriends -h" >> docs/usage.md
|
||||
@echo '\n```' >> docs/usage.md
|
||||
@out/debug/tildefriends -h >> docs/usage.md
|
||||
@echo '```' >> docs/usage.md
|
||||
@for command in $$(out/debug/tildefriends -h | grep -Po '[A-Za-z_]*(?= - )'); do
|
||||
@ echo "\n## tildefriends $$command -h" >> docs/usage.md
|
||||
@ echo '\n```' >> docs/usage.md
|
||||
@ out/debug/tildefriends $$command -h >> docs/usage.md
|
||||
@ echo '```' >> docs/usage.md
|
||||
@done
|
||||
@doxygen
|
||||
.PHONY: docs
|
||||
|
||||
fdroid: out/apk/TildeFriends-release.fdroid.unsigned.apk
|
||||
.PHONY: fdroid
|
||||
|
||||
57
README.md
57
README.md
@@ -1,23 +1,18 @@
|
||||
# Tilde Friends
|
||||
|
||||
Tilde Friends is a tool for making and sharing.
|
||||
Tilde Friends participates in the Secure Scuttlebutt decentralized social
|
||||
network while also functioning as a platform for making, sharing, and running
|
||||
web applications.
|
||||
|
||||
A public instance lives at https://www.tildefriends.net/.
|
||||
|
||||
It is both a peer-to-peer social network client, participating in Secure
|
||||
Scuttlebutt, as well as a platform for writing and running web applications.
|
||||
|
||||
## Goals
|
||||
|
||||
1. Make it easy and fun to run all sorts of web applications.
|
||||
2. Provide security that is easy to understand and protects your data.
|
||||
3. Make creating and sharing web applications accessible to anyone with a
|
||||
browser.
|
||||
1. Be the fanciest, best-maintained Secure Scuttlebutt client in town.
|
||||
1. Make it easy to make, share, and run all sorts of applications while
|
||||
respecting the privacy and safety of your data.
|
||||
|
||||
## Building
|
||||
|
||||
Builds on Linux (x86_64 and aarch64), MacOS, OpenBSD, and Haiku. Builds for
|
||||
all of those host platforms plus mingw64, iOS, and android.
|
||||
## Getting the Source
|
||||
|
||||
Tilde Friends uses git submodules, so either:
|
||||
|
||||
@@ -35,29 +30,39 @@ git submodule update --init --recursive
|
||||
|
||||
The `.tar.xz` source releases are all-inclusive.
|
||||
|
||||
1. On Linux only, system OpenSSL libraries (`libssl-dev`, in debian-speak) are
|
||||
assumed to be available.
|
||||
2. To build, run `make debug` or `make release`. An executable will be
|
||||
generated in a subdirectory of `out/`.
|
||||
3. It's possible to build for Android, iOS, and Windows on Linux, if you have
|
||||
the right dependencies in the right places. `make windebug winrelease
|
||||
iosdebug-ipa iosrelease-ipa release-apk`.
|
||||
4. To build in docker, `docker build .`.
|
||||
5. `make format` will normalize formatting to the coding standard.
|
||||
## Building
|
||||
|
||||
Builds on Linux (x86_64 and aarch64), MacOS, OpenBSD, and Haiku. It's possible
|
||||
to build for Android, iOS, and Windows on Linux, if you have the right
|
||||
dependencies in the right places.
|
||||
|
||||
### Requirements
|
||||
|
||||
On MacOS, Xcode's command-line tools are expected to be available.
|
||||
|
||||
### Build Commands
|
||||
|
||||
Run `make` with no arguments to see available build targets and options. `make
|
||||
debug` is a good place to start.
|
||||
|
||||
To build in docker, `docker build .`.
|
||||
|
||||
`make format` and `make prettier` will normalize formatting to the coding
|
||||
standard.
|
||||
|
||||
## Running
|
||||
|
||||
By default, running the built `tildefriends` executable will start a web server
|
||||
at <http://localhost:12345/>. `tildefriends -h` lists further options.
|
||||
By default, running the built `out/debug/tildefriends` executable will start a
|
||||
web server at <http://localhost:12345/>. `tildefriends -h` lists further
|
||||
options.
|
||||
|
||||
The first user to create an account and log in will be granted administrative
|
||||
privileges. Further administration can be done at
|
||||
privileges. Further administration can be done in the `admin` app at
|
||||
<http://localhost:12345/~core/admin/>.
|
||||
|
||||
## Documentation
|
||||
|
||||
Docs are a work in progress:
|
||||
<https://www.tildefriends.net/~cory/wiki/#test-wiki/tf-app-quick-reference>.
|
||||
Docs live here: <https://docs.tildefriends.net/>.
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"type": "tildefriends-app",
|
||||
"emoji": "🎛",
|
||||
"previous": "&R49FywYF8CXPhoSEydLbSCgvCddeyTiBwGuDU/gqY+M=.sha256"
|
||||
"previous": "&kmKNyb/uaXNb24gCinJtfS8iWx4cLUWdtl0y2DwEUas=.sha256"
|
||||
}
|
||||
|
||||
@@ -72,7 +72,7 @@ ${description.value}</textarea
|
||||
</button>
|
||||
</li>
|
||||
`;
|
||||
} else {
|
||||
} else if (description.type != 'hidden') {
|
||||
return html`
|
||||
<li class="w3-row">
|
||||
<label class="w3-quarter" for=${'gs_' + key} style="font-weight: bold">${title_case(key)}</label>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* W3.CSS 4.15 December 2020 by Jan Egil and Borge Refsnes */
|
||||
/* W3.CSS 5.02 March 31 2025 by Jan Egil and Borge Refsnes */
|
||||
html{box-sizing:border-box}*,*:before,*:after{box-sizing:inherit}
|
||||
/* Extract from normalize.css by Nicolas Gallagher and Jonathan Neal git.io/normalize */
|
||||
html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}
|
||||
@@ -108,6 +108,8 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
|
||||
.w3-round-small{border-radius:2px}.w3-round,.w3-round-medium{border-radius:4px}.w3-round-large{border-radius:8px}.w3-round-xlarge{border-radius:16px}.w3-round-xxlarge{border-radius:32px}
|
||||
.w3-row-padding,.w3-row-padding>.w3-half,.w3-row-padding>.w3-third,.w3-row-padding>.w3-twothird,.w3-row-padding>.w3-threequarter,.w3-row-padding>.w3-quarter,.w3-row-padding>.w3-col{padding:0 8px}
|
||||
.w3-container,.w3-panel{padding:0.01em 16px}.w3-panel{margin-top:16px;margin-bottom:16px}
|
||||
.w3-grid{display:grid}.w3-grid-padding{display:grid;gap:16px}.w3-flex{display:flex}
|
||||
.w3-text-center{text-align:center}.w3-text-bold,.w3-bold{font-weight:bold}.w3-text-italic,.w3-italic{font-style:italic}
|
||||
.w3-code,.w3-codespan{font-family:Consolas,"courier new";font-size:16px}
|
||||
.w3-code{width:auto;background-color:#fff;padding:8px 12px;border-left:4px solid #4CAF50;word-wrap:break-word}
|
||||
.w3-codespan{color:crimson;background-color:#f1f1f1;padding-left:4px;padding-right:4px;font-size:110%}
|
||||
@@ -148,6 +150,7 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
|
||||
.w3-button:hover{color:#000!important;background-color:#ccc!important}
|
||||
.w3-transparent,.w3-hover-none:hover{background-color:transparent!important}
|
||||
.w3-hover-none:hover{box-shadow:none!important}
|
||||
.w3-rtl{direction:rtl}.w3-ltr{direction:ltr}
|
||||
/* Colors */
|
||||
.w3-amber,.w3-hover-amber:hover{color:#000!important;background-color:#ffc107!important}
|
||||
.w3-aqua,.w3-hover-aqua:hover{color:#000!important;background-color:#00ffff!important}
|
||||
@@ -175,6 +178,19 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
|
||||
.w3-grey,.w3-hover-grey:hover,.w3-gray,.w3-hover-gray:hover{color:#000!important;background-color:#9e9e9e!important}
|
||||
.w3-light-grey,.w3-hover-light-grey:hover,.w3-light-gray,.w3-hover-light-gray:hover{color:#000!important;background-color:#f1f1f1!important}
|
||||
.w3-dark-grey,.w3-hover-dark-grey:hover,.w3-dark-gray,.w3-hover-dark-gray:hover{color:#fff!important;background-color:#616161!important}
|
||||
.w3-asphalt,.w3-hover-asphalt:hover{color:#fff!important;background-color:#343a40!important}
|
||||
.w3-crimson,.w3-hover-crimson:hover{color:#fff!important;background-color:#a20025!important}
|
||||
.w3-cobalt,w3-hover-cobalt:hover{color:#fff!important;background-color:#0050ef!important}
|
||||
.w3-emerald,.w3-hover-emerald:hover{color:#fff!important;background-color:#008a00!important}
|
||||
.w3-olive,.w3-hover-olive:hover{color:#fff!important;background-color:#6d8764!important}
|
||||
.w3-paper,.w3-hover-paper:hover{color:#000!important;background-color:#f8f9fa!important}
|
||||
.w3-sienna,.w3-hover-sienna:hover{color:#fff!important;background-color:#a0522d!important}
|
||||
.w3-taupe,.w3-hover-taupe:hover{color:#fff!important;background-color:#87794e!important}
|
||||
.w3-danger{color:#fff!important;background-color:#dd0000!important}
|
||||
.w3-note{color:#000!important;background-color:#fff599!important}
|
||||
.w3-info{color:#fff!important;background-color:#0a6fc2!important}
|
||||
.w3-warning{color:#000!important;background-color:#ffb305!important}
|
||||
.w3-success{color:#fff!important;background-color:#008a00!important}
|
||||
.w3-pale-red,.w3-hover-pale-red:hover{color:#000!important;background-color:#ffdddd!important}
|
||||
.w3-pale-green,.w3-hover-pale-green:hover{color:#000!important;background-color:#ddffdd!important}
|
||||
.w3-pale-yellow,.w3-hover-pale-yellow:hover{color:#000!important;background-color:#ffffcc!important}
|
||||
@@ -232,4 +248,4 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
|
||||
.w3-border-light-grey,.w3-hover-border-light-grey:hover,.w3-border-light-gray,.w3-hover-border-light-gray:hover{border-color:#f1f1f1!important}
|
||||
.w3-border-dark-grey,.w3-hover-border-dark-grey:hover,.w3-border-dark-gray,.w3-hover-border-dark-gray:hover{border-color:#616161!important}
|
||||
.w3-border-pale-red,.w3-hover-border-pale-red:hover{border-color:#ffe7e7!important}.w3-border-pale-green,.w3-hover-border-pale-green:hover{border-color:#e7ffe7!important}
|
||||
.w3-border-pale-yellow,.w3-hover-border-pale-yellow:hover{border-color:#ffffcc!important}.w3-border-pale-blue,.w3-hover-border-pale-blue:hover{border-color:#e7ffff!important}
|
||||
.w3-border-pale-yellow,.w3-hover-border-pale-yellow:hover{border-color:#ffffcc!important}.w3-border-pale-blue,.w3-hover-border-pale-blue:hover{border-color:#e7ffff!important}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"type": "tildefriends-app",
|
||||
"emoji": "📜",
|
||||
"previous": "&miGORZ8BwjHg2YO0t4bms6SI28XWPYqnqOZ8u9zsbZc=.sha256"
|
||||
"previous": "&sJqeyYjHys6Z8IqqtZ2ij2ZC1E2xieu/FU/u2hE+O1U=.sha256"
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ function* treeify(prefix, o) {
|
||||
|
||||
function markdown(md) {
|
||||
let parsed = new commonmark.Parser().parse(md ?? '*undocumented*');
|
||||
return new commonmark.HtmlRenderer().render(parsed);
|
||||
return new commonmark.HtmlRenderer({safe: true}).render(parsed);
|
||||
}
|
||||
|
||||
function document(api) {
|
||||
@@ -55,6 +55,9 @@ app.setDocument(`<head>
|
||||
</head>
|
||||
<body style="color:#fff">
|
||||
${markdown(docs.docs.global)}
|
||||
<!--
|
||||
${Object.keys(docs.docs).filter(x => [...treeify('', globalThis)].indexOf(x) == -1).map(x => `<p>STALE: ${x}</p>`).join('')}
|
||||
-->
|
||||
${[...treeify('', globalThis)].map(x => document(x)).join('\n')}
|
||||
<a id="Database"></a>
|
||||
${markdown(docs.docs.database)}
|
||||
|
||||
@@ -195,51 +195,6 @@ Call a function after some delay.
|
||||
* *Number* **timeout** Number of milliseconds to wait before calling the callback function.
|
||||
`;
|
||||
|
||||
docs['parseHttpRequest()'] = `
|
||||
Parses an HTTP request.
|
||||
### Parameters
|
||||
* *Uint8Array* **request** The request data. Maybe be partial or contain extra data. The return value will
|
||||
indicate when and where it is complete.
|
||||
* *Number* **last_length** The length of the data passed on a previous attempt for the same request, or 0 initially.
|
||||
### Returns
|
||||
* *Integer* **-2** if the request is incomplete.
|
||||
* *Integer* **-1** if the request could not be parsed.
|
||||
* *Object* An object with **bytes_parsed**, **minor_version**, **path**, and **headers** fields on successful parse.
|
||||
`;
|
||||
|
||||
docs['parseHttpResponse()'] = `
|
||||
Parses an HTTP response.
|
||||
### Parameters
|
||||
* *Uint8Array* **response** The response data. Maybe be partial or contain extra data. The return value will
|
||||
indicate when and where it is complete.
|
||||
* *Number* **last_length** The length of the data passed on a previous attempt for the same response, or 0 initially.
|
||||
### Returns
|
||||
* *Integer* **-2** if the response is incomplete.
|
||||
* *Integer* **-1** if the response could not be parsed.
|
||||
* *Object* An object with **bytes_parsed**, **minor_version**, **status**, **message**, and **headers** fields on successful parse.
|
||||
`;
|
||||
|
||||
docs['sha1Digest()'] = `
|
||||
Calculates a SHA1 digest.
|
||||
|
||||
Completes synchronously.
|
||||
### Parameters
|
||||
* *String* **value** The value for which to calculate the digest.
|
||||
### Returns
|
||||
*String* The SHA1 digest of UTF-8 encoded \`value\`.
|
||||
`;
|
||||
|
||||
docs['maskBytes()'] = `
|
||||
Masks bytes for WebSocket communication.
|
||||
|
||||
Completes synchronously.
|
||||
### Parameters
|
||||
* *Uint8Array* **bytes** The byte array of data to mask.
|
||||
* *Uint32* **mask** The mask to apply.
|
||||
### Returns
|
||||
*Uint32Array* The masked bytes.
|
||||
`;
|
||||
|
||||
docs['exit()'] = `
|
||||
Exits the app. But why would you want to do that?
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"type": "tildefriends-app",
|
||||
"emoji": "💻",
|
||||
"previous": "&icsPplXHgmpkbNWyo/WTvRdT/A8BXxW4lJixOtP4ueQ=.sha256"
|
||||
"previous": "&sFRTDn/RpxP1NJeECXHrXKwCRUJsEOEDVaCMPl50zpM=.sha256"
|
||||
}
|
||||
|
||||
@@ -19,10 +19,6 @@ async function fetch_info(apps) {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
*/
|
||||
async function fetch_shared_apps() {
|
||||
let messages = {};
|
||||
|
||||
@@ -69,17 +65,17 @@ async function main() {
|
||||
const stylesheet = `
|
||||
body {
|
||||
color: whitesmoke;
|
||||
font-family: sans-serif;
|
||||
margin: 16px;
|
||||
margin: 8px;
|
||||
}
|
||||
.container {
|
||||
|
||||
.iconbox {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, 64px);
|
||||
gap: 1em;
|
||||
justify-content: space-around;
|
||||
background-color: #ffffff10;
|
||||
border: 2px solid #073642;
|
||||
border-radius: 8px;
|
||||
grid-template-columns: repeat(auto-fill, minmax(96px, 1fr));
|
||||
}
|
||||
|
||||
.iconbox::after {
|
||||
content: "";
|
||||
flex: auto;
|
||||
}
|
||||
|
||||
.app {
|
||||
@@ -101,16 +97,28 @@ async function main() {
|
||||
`;
|
||||
|
||||
const body = `
|
||||
<h1 style="text-shadow: #808080 0 0 10px;">Welcome to Tilde Friends.</h1>
|
||||
<h1>Welcome to Tilde Friends</h1>
|
||||
|
||||
<h2>your apps</h2>
|
||||
<div id="apps" class="container"></div>
|
||||
<div class="w3-card-4 w3-margin-top">
|
||||
<header class="w3-container w3-light-blue">
|
||||
<h2>Your Apps</h2>
|
||||
</header>
|
||||
<div id="apps" class="w3-indigo iconbox"></div>
|
||||
</div>
|
||||
|
||||
<h2>shared apps</h2>
|
||||
<div id="shared_apps" class="container"></div>
|
||||
<div class="w3-card-4 w3-margin-top">
|
||||
<header class="w3-container w3-light-blue">
|
||||
<h2>Shared Apps</h2>
|
||||
</header>
|
||||
<div id="shared_apps" class="w3-indigo iconbox"></div>
|
||||
</div>
|
||||
|
||||
<h2>core apps</h2>
|
||||
<div id="core_apps" class="container"></div>
|
||||
<div class="w3-card-4 w3-margin-top">
|
||||
<header class="w3-container w3-light-blue">
|
||||
<h2>Core Apps</h2>
|
||||
</header>
|
||||
<div id="core_apps" class="w3-indigo iconbox"></div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
const script = `
|
||||
@@ -126,9 +134,13 @@ async function main() {
|
||||
|
||||
// For each app in the provided list
|
||||
for (let app of Object.keys(apps).sort()) {
|
||||
|
||||
// Create the item
|
||||
let div = list.appendChild(document.createElement('div'));
|
||||
let inline = document.createElement('div');
|
||||
inline.style.display = 'inline-block';
|
||||
inline.classList.add('w3-button');
|
||||
list.appendChild(inline);
|
||||
let div = document.createElement('div');
|
||||
inline.appendChild(div);
|
||||
div.classList.add('app');
|
||||
|
||||
// The app's icon
|
||||
@@ -161,12 +173,13 @@ async function main() {
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<link type="text/css" rel="stylesheet" href="w3.css"></link>
|
||||
<style>
|
||||
${stylesheet}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<body class="w3-darkgray">
|
||||
${body}
|
||||
</body>
|
||||
|
||||
|
||||
251
apps/apps/w3.css
Normal file
251
apps/apps/w3.css
Normal file
@@ -0,0 +1,251 @@
|
||||
/* W3.CSS 5.02 March 31 2025 by Jan Egil and Borge Refsnes */
|
||||
html{box-sizing:border-box}*,*:before,*:after{box-sizing:inherit}
|
||||
/* Extract from normalize.css by Nicolas Gallagher and Jonathan Neal git.io/normalize */
|
||||
html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}
|
||||
article,aside,details,figcaption,figure,footer,header,main,menu,nav,section{display:block}summary{display:list-item}
|
||||
audio,canvas,progress,video{display:inline-block}progress{vertical-align:baseline}
|
||||
audio:not([controls]){display:none;height:0}[hidden],template{display:none}
|
||||
a{background-color:transparent}a:active,a:hover{outline-width:0}
|
||||
abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}
|
||||
b,strong{font-weight:bolder}dfn{font-style:italic}mark{background:#ff0;color:#000}
|
||||
small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}
|
||||
sub{bottom:-0.25em}sup{top:-0.5em}figure{margin:1em 40px}img{border-style:none}
|
||||
code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}hr{box-sizing:content-box;height:0;overflow:visible}
|
||||
button,input,select,textarea,optgroup{font:inherit;margin:0}optgroup{font-weight:bold}
|
||||
button,input{overflow:visible}button,select{text-transform:none}
|
||||
button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button}
|
||||
button::-moz-focus-inner,[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner{border-style:none;padding:0}
|
||||
button:-moz-focusring,[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring{outline:1px dotted ButtonText}
|
||||
fieldset{border:1px solid #c0c0c0;margin:0 2px;padding:.35em .625em .75em}
|
||||
legend{color:inherit;display:table;max-width:100%;padding:0;white-space:normal}textarea{overflow:auto}
|
||||
[type=checkbox],[type=radio]{padding:0}
|
||||
[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}
|
||||
[type=search]{-webkit-appearance:textfield;outline-offset:-2px}
|
||||
[type=search]::-webkit-search-decoration{-webkit-appearance:none}
|
||||
::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}
|
||||
/* End extract */
|
||||
html,body{font-family:Verdana,sans-serif;font-size:15px;line-height:1.5}html{overflow-x:hidden}
|
||||
h1{font-size:36px}h2{font-size:30px}h3{font-size:24px}h4{font-size:20px}h5{font-size:18px}h6{font-size:16px}
|
||||
.w3-serif{font-family:serif}.w3-sans-serif{font-family:sans-serif}.w3-cursive{font-family:cursive}.w3-monospace{font-family:monospace}
|
||||
h1,h2,h3,h4,h5,h6{font-family:"Segoe UI",Arial,sans-serif;font-weight:400;margin:10px 0}.w3-wide{letter-spacing:4px}
|
||||
hr{border:0;border-top:1px solid #eee;margin:20px 0}
|
||||
.w3-image{max-width:100%;height:auto}img{vertical-align:middle}a{color:inherit}
|
||||
.w3-table,.w3-table-all{border-collapse:collapse;border-spacing:0;width:100%;display:table}.w3-table-all{border:1px solid #ccc}
|
||||
.w3-bordered tr,.w3-table-all tr{border-bottom:1px solid #ddd}.w3-striped tbody tr:nth-child(even){background-color:#f1f1f1}
|
||||
.w3-table-all tr:nth-child(odd){background-color:#fff}.w3-table-all tr:nth-child(even){background-color:#f1f1f1}
|
||||
.w3-hoverable tbody tr:hover,.w3-ul.w3-hoverable li:hover{background-color:#ccc}.w3-centered tr th,.w3-centered tr td{text-align:center}
|
||||
.w3-table td,.w3-table th,.w3-table-all td,.w3-table-all th{padding:8px 8px;display:table-cell;text-align:left;vertical-align:top}
|
||||
.w3-table th:first-child,.w3-table td:first-child,.w3-table-all th:first-child,.w3-table-all td:first-child{padding-left:16px}
|
||||
.w3-btn,.w3-button{border:none;display:inline-block;padding:8px 16px;vertical-align:middle;overflow:hidden;text-decoration:none;color:inherit;background-color:inherit;text-align:center;cursor:pointer;white-space:nowrap}
|
||||
.w3-btn:hover{box-shadow:0 8px 16px 0 rgba(0,0,0,0.2),0 6px 20px 0 rgba(0,0,0,0.19)}
|
||||
.w3-btn,.w3-button{-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}
|
||||
.w3-disabled,.w3-btn:disabled,.w3-button:disabled{cursor:not-allowed;opacity:0.3}.w3-disabled *,:disabled *{pointer-events:none}
|
||||
.w3-btn.w3-disabled:hover,.w3-btn:disabled:hover{box-shadow:none}
|
||||
.w3-badge,.w3-tag{background-color:#000;color:#fff;display:inline-block;padding-left:8px;padding-right:8px;text-align:center}.w3-badge{border-radius:50%}
|
||||
.w3-ul{list-style-type:none;padding:0;margin:0}.w3-ul li{padding:8px 16px;border-bottom:1px solid #ddd}.w3-ul li:last-child{border-bottom:none}
|
||||
.w3-tooltip,.w3-display-container{position:relative}.w3-tooltip .w3-text{display:none}.w3-tooltip:hover .w3-text{display:inline-block}
|
||||
.w3-ripple:active{opacity:0.5}.w3-ripple{transition:opacity 0s}
|
||||
.w3-input{padding:8px;display:block;border:none;border-bottom:1px solid #ccc;width:100%}
|
||||
.w3-select{padding:9px 0;width:100%;border:none;border-bottom:1px solid #ccc}
|
||||
.w3-dropdown-click,.w3-dropdown-hover{position:relative;display:inline-block;cursor:pointer}
|
||||
.w3-dropdown-hover:hover .w3-dropdown-content{display:block}
|
||||
.w3-dropdown-hover:first-child,.w3-dropdown-click:hover{background-color:#ccc;color:#000}
|
||||
.w3-dropdown-hover:hover > .w3-button:first-child,.w3-dropdown-click:hover > .w3-button:first-child{background-color:#ccc;color:#000}
|
||||
.w3-dropdown-content{cursor:auto;color:#000;background-color:#fff;display:none;position:absolute;min-width:160px;margin:0;padding:0;z-index:1}
|
||||
.w3-check,.w3-radio{width:24px;height:24px;position:relative;top:6px}
|
||||
.w3-sidebar{height:100%;width:200px;background-color:#fff;position:fixed!important;z-index:1;overflow:auto}
|
||||
.w3-bar-block .w3-dropdown-hover,.w3-bar-block .w3-dropdown-click{width:100%}
|
||||
.w3-bar-block .w3-dropdown-hover .w3-dropdown-content,.w3-bar-block .w3-dropdown-click .w3-dropdown-content{min-width:100%}
|
||||
.w3-bar-block .w3-dropdown-hover .w3-button,.w3-bar-block .w3-dropdown-click .w3-button{width:100%;text-align:left;padding:8px 16px}
|
||||
.w3-main,#main{transition:margin-left .4s}
|
||||
.w3-modal{z-index:3;display:none;padding-top:100px;position:fixed;left:0;top:0;width:100%;height:100%;overflow:auto;background-color:rgb(0,0,0);background-color:rgba(0,0,0,0.4)}
|
||||
.w3-modal-content{margin:auto;background-color:#fff;position:relative;padding:0;outline:0;width:600px}
|
||||
.w3-bar{width:100%;overflow:hidden}.w3-center .w3-bar{display:inline-block;width:auto}
|
||||
.w3-bar .w3-bar-item{padding:8px 16px;float:left;width:auto;border:none;display:block;outline:0}
|
||||
.w3-bar .w3-dropdown-hover,.w3-bar .w3-dropdown-click{position:static;float:left}
|
||||
.w3-bar .w3-button{white-space:normal}
|
||||
.w3-bar-block .w3-bar-item{width:100%;display:block;padding:8px 16px;text-align:left;border:none;white-space:normal;float:none;outline:0}
|
||||
.w3-bar-block.w3-center .w3-bar-item{text-align:center}.w3-block{display:block;width:100%}
|
||||
.w3-responsive{display:block;overflow-x:auto}
|
||||
.w3-container:after,.w3-container:before,.w3-panel:after,.w3-panel:before,.w3-row:after,.w3-row:before,.w3-row-padding:after,.w3-row-padding:before,
|
||||
.w3-cell-row:before,.w3-cell-row:after,.w3-clear:after,.w3-clear:before,.w3-bar:before,.w3-bar:after{content:"";display:table;clear:both}
|
||||
.w3-col,.w3-half,.w3-third,.w3-twothird,.w3-threequarter,.w3-quarter{float:left;width:100%}
|
||||
.w3-col.s1{width:8.33333%}.w3-col.s2{width:16.66666%}.w3-col.s3{width:24.99999%}.w3-col.s4{width:33.33333%}
|
||||
.w3-col.s5{width:41.66666%}.w3-col.s6{width:49.99999%}.w3-col.s7{width:58.33333%}.w3-col.s8{width:66.66666%}
|
||||
.w3-col.s9{width:74.99999%}.w3-col.s10{width:83.33333%}.w3-col.s11{width:91.66666%}.w3-col.s12{width:99.99999%}
|
||||
@media (min-width:601px){.w3-col.m1{width:8.33333%}.w3-col.m2{width:16.66666%}.w3-col.m3,.w3-quarter{width:24.99999%}.w3-col.m4,.w3-third{width:33.33333%}
|
||||
.w3-col.m5{width:41.66666%}.w3-col.m6,.w3-half{width:49.99999%}.w3-col.m7{width:58.33333%}.w3-col.m8,.w3-twothird{width:66.66666%}
|
||||
.w3-col.m9,.w3-threequarter{width:74.99999%}.w3-col.m10{width:83.33333%}.w3-col.m11{width:91.66666%}.w3-col.m12{width:99.99999%}}
|
||||
@media (min-width:993px){.w3-col.l1{width:8.33333%}.w3-col.l2{width:16.66666%}.w3-col.l3{width:24.99999%}.w3-col.l4{width:33.33333%}
|
||||
.w3-col.l5{width:41.66666%}.w3-col.l6{width:49.99999%}.w3-col.l7{width:58.33333%}.w3-col.l8{width:66.66666%}
|
||||
.w3-col.l9{width:74.99999%}.w3-col.l10{width:83.33333%}.w3-col.l11{width:91.66666%}.w3-col.l12{width:99.99999%}}
|
||||
.w3-rest{overflow:hidden}.w3-stretch{margin-left:-16px;margin-right:-16px}
|
||||
.w3-content,.w3-auto{margin-left:auto;margin-right:auto}.w3-content{max-width:980px}.w3-auto{max-width:1140px}
|
||||
.w3-cell-row{display:table;width:100%}.w3-cell{display:table-cell}
|
||||
.w3-cell-top{vertical-align:top}.w3-cell-middle{vertical-align:middle}.w3-cell-bottom{vertical-align:bottom}
|
||||
.w3-hide{display:none!important}.w3-show-block,.w3-show{display:block!important}.w3-show-inline-block{display:inline-block!important}
|
||||
@media (max-width:1205px){.w3-auto{max-width:95%}}
|
||||
@media (max-width:600px){.w3-modal-content{margin:0 10px;width:auto!important}.w3-modal{padding-top:30px}
|
||||
.w3-dropdown-hover.w3-mobile .w3-dropdown-content,.w3-dropdown-click.w3-mobile .w3-dropdown-content{position:relative}
|
||||
.w3-hide-small{display:none!important}.w3-mobile{display:block;width:100%!important}.w3-bar-item.w3-mobile,.w3-dropdown-hover.w3-mobile,.w3-dropdown-click.w3-mobile{text-align:center}
|
||||
.w3-dropdown-hover.w3-mobile,.w3-dropdown-hover.w3-mobile .w3-btn,.w3-dropdown-hover.w3-mobile .w3-button,.w3-dropdown-click.w3-mobile,.w3-dropdown-click.w3-mobile .w3-btn,.w3-dropdown-click.w3-mobile .w3-button{width:100%}}
|
||||
@media (max-width:768px){.w3-modal-content{width:500px}.w3-modal{padding-top:50px}}
|
||||
@media (min-width:993px){.w3-modal-content{width:900px}.w3-hide-large{display:none!important}.w3-sidebar.w3-collapse{display:block!important}}
|
||||
@media (max-width:992px) and (min-width:601px){.w3-hide-medium{display:none!important}}
|
||||
@media (max-width:992px){.w3-sidebar.w3-collapse{display:none}.w3-main{margin-left:0!important;margin-right:0!important}.w3-auto{max-width:100%}}
|
||||
.w3-top,.w3-bottom{position:fixed;width:100%;z-index:1}.w3-top{top:0}.w3-bottom{bottom:0}
|
||||
.w3-overlay{position:fixed;display:none;width:100%;height:100%;top:0;left:0;right:0;bottom:0;background-color:rgba(0,0,0,0.5);z-index:2}
|
||||
.w3-display-topleft{position:absolute;left:0;top:0}.w3-display-topright{position:absolute;right:0;top:0}
|
||||
.w3-display-bottomleft{position:absolute;left:0;bottom:0}.w3-display-bottomright{position:absolute;right:0;bottom:0}
|
||||
.w3-display-middle{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);-ms-transform:translate(-50%,-50%)}
|
||||
.w3-display-left{position:absolute;top:50%;left:0%;transform:translate(0%,-50%);-ms-transform:translate(-0%,-50%)}
|
||||
.w3-display-right{position:absolute;top:50%;right:0%;transform:translate(0%,-50%);-ms-transform:translate(0%,-50%)}
|
||||
.w3-display-topmiddle{position:absolute;left:50%;top:0;transform:translate(-50%,0%);-ms-transform:translate(-50%,0%)}
|
||||
.w3-display-bottommiddle{position:absolute;left:50%;bottom:0;transform:translate(-50%,0%);-ms-transform:translate(-50%,0%)}
|
||||
.w3-display-container:hover .w3-display-hover{display:block}.w3-display-container:hover span.w3-display-hover{display:inline-block}.w3-display-hover{display:none}
|
||||
.w3-display-position{position:absolute}
|
||||
.w3-circle{border-radius:50%}
|
||||
.w3-round-small{border-radius:2px}.w3-round,.w3-round-medium{border-radius:4px}.w3-round-large{border-radius:8px}.w3-round-xlarge{border-radius:16px}.w3-round-xxlarge{border-radius:32px}
|
||||
.w3-row-padding,.w3-row-padding>.w3-half,.w3-row-padding>.w3-third,.w3-row-padding>.w3-twothird,.w3-row-padding>.w3-threequarter,.w3-row-padding>.w3-quarter,.w3-row-padding>.w3-col{padding:0 8px}
|
||||
.w3-container,.w3-panel{padding:0.01em 16px}.w3-panel{margin-top:16px;margin-bottom:16px}
|
||||
.w3-grid{display:grid}.w3-grid-padding{display:grid;gap:16px}.w3-flex{display:flex}
|
||||
.w3-text-center{text-align:center}.w3-text-bold,.w3-bold{font-weight:bold}.w3-text-italic,.w3-italic{font-style:italic}
|
||||
.w3-code,.w3-codespan{font-family:Consolas,"courier new";font-size:16px}
|
||||
.w3-code{width:auto;background-color:#fff;padding:8px 12px;border-left:4px solid #4CAF50;word-wrap:break-word}
|
||||
.w3-codespan{color:crimson;background-color:#f1f1f1;padding-left:4px;padding-right:4px;font-size:110%}
|
||||
.w3-card,.w3-card-2{box-shadow:0 2px 5px 0 rgba(0,0,0,0.16),0 2px 10px 0 rgba(0,0,0,0.12)}
|
||||
.w3-card-4,.w3-hover-shadow:hover{box-shadow:0 4px 10px 0 rgba(0,0,0,0.2),0 4px 20px 0 rgba(0,0,0,0.19)}
|
||||
.w3-spin{animation:w3-spin 2s infinite linear}@keyframes w3-spin{0%{transform:rotate(0deg)}100%{transform:rotate(359deg)}}
|
||||
.w3-animate-fading{animation:fading 10s infinite}@keyframes fading{0%{opacity:0}50%{opacity:1}100%{opacity:0}}
|
||||
.w3-animate-opacity{animation:opac 0.8s}@keyframes opac{from{opacity:0} to{opacity:1}}
|
||||
.w3-animate-top{position:relative;animation:animatetop 0.4s}@keyframes animatetop{from{top:-300px;opacity:0} to{top:0;opacity:1}}
|
||||
.w3-animate-left{position:relative;animation:animateleft 0.4s}@keyframes animateleft{from{left:-300px;opacity:0} to{left:0;opacity:1}}
|
||||
.w3-animate-right{position:relative;animation:animateright 0.4s}@keyframes animateright{from{right:-300px;opacity:0} to{right:0;opacity:1}}
|
||||
.w3-animate-bottom{position:relative;animation:animatebottom 0.4s}@keyframes animatebottom{from{bottom:-300px;opacity:0} to{bottom:0;opacity:1}}
|
||||
.w3-animate-zoom {animation:animatezoom 0.6s}@keyframes animatezoom{from{transform:scale(0)} to{transform:scale(1)}}
|
||||
.w3-animate-input{transition:width 0.4s ease-in-out}.w3-animate-input:focus{width:100%!important}
|
||||
.w3-opacity,.w3-hover-opacity:hover{opacity:0.60}.w3-opacity-off,.w3-hover-opacity-off:hover{opacity:1}
|
||||
.w3-opacity-max{opacity:0.25}.w3-opacity-min{opacity:0.75}
|
||||
.w3-greyscale-max,.w3-grayscale-max,.w3-hover-greyscale:hover,.w3-hover-grayscale:hover{filter:grayscale(100%)}
|
||||
.w3-greyscale,.w3-grayscale{filter:grayscale(75%)}.w3-greyscale-min,.w3-grayscale-min{filter:grayscale(50%)}
|
||||
.w3-sepia{filter:sepia(75%)}.w3-sepia-max,.w3-hover-sepia:hover{filter:sepia(100%)}.w3-sepia-min{filter:sepia(50%)}
|
||||
.w3-tiny{font-size:10px!important}.w3-small{font-size:12px!important}.w3-medium{font-size:15px!important}.w3-large{font-size:18px!important}
|
||||
.w3-xlarge{font-size:24px!important}.w3-xxlarge{font-size:36px!important}.w3-xxxlarge{font-size:48px!important}.w3-jumbo{font-size:64px!important}
|
||||
.w3-left-align{text-align:left!important}.w3-right-align{text-align:right!important}.w3-justify{text-align:justify!important}.w3-center{text-align:center!important}
|
||||
.w3-border-0{border:0!important}.w3-border{border:1px solid #ccc!important}
|
||||
.w3-border-top{border-top:1px solid #ccc!important}.w3-border-bottom{border-bottom:1px solid #ccc!important}
|
||||
.w3-border-left{border-left:1px solid #ccc!important}.w3-border-right{border-right:1px solid #ccc!important}
|
||||
.w3-topbar{border-top:6px solid #ccc!important}.w3-bottombar{border-bottom:6px solid #ccc!important}
|
||||
.w3-leftbar{border-left:6px solid #ccc!important}.w3-rightbar{border-right:6px solid #ccc!important}
|
||||
.w3-section,.w3-code{margin-top:16px!important;margin-bottom:16px!important}
|
||||
.w3-margin{margin:16px!important}.w3-margin-top{margin-top:16px!important}.w3-margin-bottom{margin-bottom:16px!important}
|
||||
.w3-margin-left{margin-left:16px!important}.w3-margin-right{margin-right:16px!important}
|
||||
.w3-padding-small{padding:4px 8px!important}.w3-padding{padding:8px 16px!important}.w3-padding-large{padding:12px 24px!important}
|
||||
.w3-padding-16{padding-top:16px!important;padding-bottom:16px!important}.w3-padding-24{padding-top:24px!important;padding-bottom:24px!important}
|
||||
.w3-padding-32{padding-top:32px!important;padding-bottom:32px!important}.w3-padding-48{padding-top:48px!important;padding-bottom:48px!important}
|
||||
.w3-padding-64{padding-top:64px!important;padding-bottom:64px!important}
|
||||
.w3-padding-top-64{padding-top:64px!important}.w3-padding-top-48{padding-top:48px!important}
|
||||
.w3-padding-top-32{padding-top:32px!important}.w3-padding-top-24{padding-top:24px!important}
|
||||
.w3-left{float:left!important}.w3-right{float:right!important}
|
||||
.w3-button:hover{color:#000!important;background-color:#ccc!important}
|
||||
.w3-transparent,.w3-hover-none:hover{background-color:transparent!important}
|
||||
.w3-hover-none:hover{box-shadow:none!important}
|
||||
.w3-rtl{direction:rtl}.w3-ltr{direction:ltr}
|
||||
/* Colors */
|
||||
.w3-amber,.w3-hover-amber:hover{color:#000!important;background-color:#ffc107!important}
|
||||
.w3-aqua,.w3-hover-aqua:hover{color:#000!important;background-color:#00ffff!important}
|
||||
.w3-blue,.w3-hover-blue:hover{color:#fff!important;background-color:#2196F3!important}
|
||||
.w3-light-blue,.w3-hover-light-blue:hover{color:#000!important;background-color:#87CEEB!important}
|
||||
.w3-brown,.w3-hover-brown:hover{color:#fff!important;background-color:#795548!important}
|
||||
.w3-cyan,.w3-hover-cyan:hover{color:#000!important;background-color:#00bcd4!important}
|
||||
.w3-blue-grey,.w3-hover-blue-grey:hover,.w3-blue-gray,.w3-hover-blue-gray:hover{color:#fff!important;background-color:#607d8b!important}
|
||||
.w3-green,.w3-hover-green:hover{color:#fff!important;background-color:#4CAF50!important}
|
||||
.w3-light-green,.w3-hover-light-green:hover{color:#000!important;background-color:#8bc34a!important}
|
||||
.w3-indigo,.w3-hover-indigo:hover{color:#fff!important;background-color:#3f51b5!important}
|
||||
.w3-khaki,.w3-hover-khaki:hover{color:#000!important;background-color:#f0e68c!important}
|
||||
.w3-lime,.w3-hover-lime:hover{color:#000!important;background-color:#cddc39!important}
|
||||
.w3-orange,.w3-hover-orange:hover{color:#000!important;background-color:#ff9800!important}
|
||||
.w3-deep-orange,.w3-hover-deep-orange:hover{color:#fff!important;background-color:#ff5722!important}
|
||||
.w3-pink,.w3-hover-pink:hover{color:#fff!important;background-color:#e91e63!important}
|
||||
.w3-purple,.w3-hover-purple:hover{color:#fff!important;background-color:#9c27b0!important}
|
||||
.w3-deep-purple,.w3-hover-deep-purple:hover{color:#fff!important;background-color:#673ab7!important}
|
||||
.w3-red,.w3-hover-red:hover{color:#fff!important;background-color:#f44336!important}
|
||||
.w3-sand,.w3-hover-sand:hover{color:#000!important;background-color:#fdf5e6!important}
|
||||
.w3-teal,.w3-hover-teal:hover{color:#fff!important;background-color:#009688!important}
|
||||
.w3-yellow,.w3-hover-yellow:hover{color:#000!important;background-color:#ffeb3b!important}
|
||||
.w3-white,.w3-hover-white:hover{color:#000!important;background-color:#fff!important}
|
||||
.w3-black,.w3-hover-black:hover{color:#fff!important;background-color:#000!important}
|
||||
.w3-grey,.w3-hover-grey:hover,.w3-gray,.w3-hover-gray:hover{color:#000!important;background-color:#9e9e9e!important}
|
||||
.w3-light-grey,.w3-hover-light-grey:hover,.w3-light-gray,.w3-hover-light-gray:hover{color:#000!important;background-color:#f1f1f1!important}
|
||||
.w3-dark-grey,.w3-hover-dark-grey:hover,.w3-dark-gray,.w3-hover-dark-gray:hover{color:#fff!important;background-color:#616161!important}
|
||||
.w3-asphalt,.w3-hover-asphalt:hover{color:#fff!important;background-color:#343a40!important}
|
||||
.w3-crimson,.w3-hover-crimson:hover{color:#fff!important;background-color:#a20025!important}
|
||||
.w3-cobalt,w3-hover-cobalt:hover{color:#fff!important;background-color:#0050ef!important}
|
||||
.w3-emerald,.w3-hover-emerald:hover{color:#fff!important;background-color:#008a00!important}
|
||||
.w3-olive,.w3-hover-olive:hover{color:#fff!important;background-color:#6d8764!important}
|
||||
.w3-paper,.w3-hover-paper:hover{color:#000!important;background-color:#f8f9fa!important}
|
||||
.w3-sienna,.w3-hover-sienna:hover{color:#fff!important;background-color:#a0522d!important}
|
||||
.w3-taupe,.w3-hover-taupe:hover{color:#fff!important;background-color:#87794e!important}
|
||||
.w3-danger{color:#fff!important;background-color:#dd0000!important}
|
||||
.w3-note{color:#000!important;background-color:#fff599!important}
|
||||
.w3-info{color:#fff!important;background-color:#0a6fc2!important}
|
||||
.w3-warning{color:#000!important;background-color:#ffb305!important}
|
||||
.w3-success{color:#fff!important;background-color:#008a00!important}
|
||||
.w3-pale-red,.w3-hover-pale-red:hover{color:#000!important;background-color:#ffdddd!important}
|
||||
.w3-pale-green,.w3-hover-pale-green:hover{color:#000!important;background-color:#ddffdd!important}
|
||||
.w3-pale-yellow,.w3-hover-pale-yellow:hover{color:#000!important;background-color:#ffffcc!important}
|
||||
.w3-pale-blue,.w3-hover-pale-blue:hover{color:#000!important;background-color:#ddffff!important}
|
||||
.w3-text-amber,.w3-hover-text-amber:hover{color:#ffc107!important}
|
||||
.w3-text-aqua,.w3-hover-text-aqua:hover{color:#00ffff!important}
|
||||
.w3-text-blue,.w3-hover-text-blue:hover{color:#2196F3!important}
|
||||
.w3-text-light-blue,.w3-hover-text-light-blue:hover{color:#87CEEB!important}
|
||||
.w3-text-brown,.w3-hover-text-brown:hover{color:#795548!important}
|
||||
.w3-text-cyan,.w3-hover-text-cyan:hover{color:#00bcd4!important}
|
||||
.w3-text-blue-grey,.w3-hover-text-blue-grey:hover,.w3-text-blue-gray,.w3-hover-text-blue-gray:hover{color:#607d8b!important}
|
||||
.w3-text-green,.w3-hover-text-green:hover{color:#4CAF50!important}
|
||||
.w3-text-light-green,.w3-hover-text-light-green:hover{color:#8bc34a!important}
|
||||
.w3-text-indigo,.w3-hover-text-indigo:hover{color:#3f51b5!important}
|
||||
.w3-text-khaki,.w3-hover-text-khaki:hover{color:#b4aa50!important}
|
||||
.w3-text-lime,.w3-hover-text-lime:hover{color:#cddc39!important}
|
||||
.w3-text-orange,.w3-hover-text-orange:hover{color:#ff9800!important}
|
||||
.w3-text-deep-orange,.w3-hover-text-deep-orange:hover{color:#ff5722!important}
|
||||
.w3-text-pink,.w3-hover-text-pink:hover{color:#e91e63!important}
|
||||
.w3-text-purple,.w3-hover-text-purple:hover{color:#9c27b0!important}
|
||||
.w3-text-deep-purple,.w3-hover-text-deep-purple:hover{color:#673ab7!important}
|
||||
.w3-text-red,.w3-hover-text-red:hover{color:#f44336!important}
|
||||
.w3-text-sand,.w3-hover-text-sand:hover{color:#fdf5e6!important}
|
||||
.w3-text-teal,.w3-hover-text-teal:hover{color:#009688!important}
|
||||
.w3-text-yellow,.w3-hover-text-yellow:hover{color:#d2be0e!important}
|
||||
.w3-text-white,.w3-hover-text-white:hover{color:#fff!important}
|
||||
.w3-text-black,.w3-hover-text-black:hover{color:#000!important}
|
||||
.w3-text-grey,.w3-hover-text-grey:hover,.w3-text-gray,.w3-hover-text-gray:hover{color:#757575!important}
|
||||
.w3-text-light-grey,.w3-hover-text-light-grey:hover,.w3-text-light-gray,.w3-hover-text-light-gray:hover{color:#f1f1f1!important}
|
||||
.w3-text-dark-grey,.w3-hover-text-dark-grey:hover,.w3-text-dark-gray,.w3-hover-text-dark-gray:hover{color:#3a3a3a!important}
|
||||
.w3-border-amber,.w3-hover-border-amber:hover{border-color:#ffc107!important}
|
||||
.w3-border-aqua,.w3-hover-border-aqua:hover{border-color:#00ffff!important}
|
||||
.w3-border-blue,.w3-hover-border-blue:hover{border-color:#2196F3!important}
|
||||
.w3-border-light-blue,.w3-hover-border-light-blue:hover{border-color:#87CEEB!important}
|
||||
.w3-border-brown,.w3-hover-border-brown:hover{border-color:#795548!important}
|
||||
.w3-border-cyan,.w3-hover-border-cyan:hover{border-color:#00bcd4!important}
|
||||
.w3-border-blue-grey,.w3-hover-border-blue-grey:hover,.w3-border-blue-gray,.w3-hover-border-blue-gray:hover{border-color:#607d8b!important}
|
||||
.w3-border-green,.w3-hover-border-green:hover{border-color:#4CAF50!important}
|
||||
.w3-border-light-green,.w3-hover-border-light-green:hover{border-color:#8bc34a!important}
|
||||
.w3-border-indigo,.w3-hover-border-indigo:hover{border-color:#3f51b5!important}
|
||||
.w3-border-khaki,.w3-hover-border-khaki:hover{border-color:#f0e68c!important}
|
||||
.w3-border-lime,.w3-hover-border-lime:hover{border-color:#cddc39!important}
|
||||
.w3-border-orange,.w3-hover-border-orange:hover{border-color:#ff9800!important}
|
||||
.w3-border-deep-orange,.w3-hover-border-deep-orange:hover{border-color:#ff5722!important}
|
||||
.w3-border-pink,.w3-hover-border-pink:hover{border-color:#e91e63!important}
|
||||
.w3-border-purple,.w3-hover-border-purple:hover{border-color:#9c27b0!important}
|
||||
.w3-border-deep-purple,.w3-hover-border-deep-purple:hover{border-color:#673ab7!important}
|
||||
.w3-border-red,.w3-hover-border-red:hover{border-color:#f44336!important}
|
||||
.w3-border-sand,.w3-hover-border-sand:hover{border-color:#fdf5e6!important}
|
||||
.w3-border-teal,.w3-hover-border-teal:hover{border-color:#009688!important}
|
||||
.w3-border-yellow,.w3-hover-border-yellow:hover{border-color:#ffeb3b!important}
|
||||
.w3-border-white,.w3-hover-border-white:hover{border-color:#fff!important}
|
||||
.w3-border-black,.w3-hover-border-black:hover{border-color:#000!important}
|
||||
.w3-border-grey,.w3-hover-border-grey:hover,.w3-border-gray,.w3-hover-border-gray:hover{border-color:#9e9e9e!important}
|
||||
.w3-border-light-grey,.w3-hover-border-light-grey:hover,.w3-border-light-gray,.w3-hover-border-light-gray:hover{border-color:#f1f1f1!important}
|
||||
.w3-border-dark-grey,.w3-hover-border-dark-grey:hover,.w3-border-dark-gray,.w3-hover-border-dark-gray:hover{border-color:#616161!important}
|
||||
.w3-border-pale-red,.w3-hover-border-pale-red:hover{border-color:#ffe7e7!important}.w3-border-pale-green,.w3-hover-border-pale-green:hover{border-color:#e7ffe7!important}
|
||||
.w3-border-pale-yellow,.w3-hover-border-pale-yellow:hover{border-color:#ffffcc!important}.w3-border-pale-blue,.w3-hover-border-pale-blue:hover{border-color:#e7ffff!important}
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"type": "tildefriends-app",
|
||||
"emoji": "🪵",
|
||||
"previous": "&TIrBnpN3iz3O9L9MCCteAcVJZjA83EKdcfu4SCM76VE=.sha256"
|
||||
"previous": "&3jabNEk6W2uolzTvfXX6fcWF50N3501vtgZ6ZxFVJ1s=.sha256"
|
||||
}
|
||||
|
||||
@@ -52,8 +52,8 @@ export async function get_blog_message(id) {
|
||||
}
|
||||
|
||||
export function markdown(md) {
|
||||
let reader = new commonmark.Parser({safe: true});
|
||||
let writer = new commonmark.HtmlRenderer();
|
||||
let reader = new commonmark.Parser();
|
||||
let writer = new commonmark.HtmlRenderer({safe: true});
|
||||
let parsed = reader.parse(md || '');
|
||||
let walker = parsed.walker();
|
||||
let event, node;
|
||||
|
||||
42
apps/blog/lit-all.min.js
vendored
42
apps/blog/lit-all.min.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"type": "tildefriends-app",
|
||||
"emoji": "➡️"
|
||||
"emoji": "➡️",
|
||||
"previous": "&YDDSzbRD8NFAykYlZnk4r4hAK5qXjT5LmKE6rhS1s+A=.sha256"
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ async function contacts_internal(id, last_row_id, following, max_row_id) {
|
||||
result.blocking = result.blocking || {};
|
||||
let contacts = await query(
|
||||
`
|
||||
SELECT content FROM messages
|
||||
SELECT json(content) AS content FROM messages
|
||||
WHERE author = ? AND
|
||||
rowid > ? AND
|
||||
rowid <= ? AND
|
||||
@@ -189,50 +189,6 @@ async function fetch_about(db, ids, users) {
|
||||
return Object.assign({}, users);
|
||||
}
|
||||
|
||||
async function getAbout(db, id) {
|
||||
if (g_about_cache[id]) {
|
||||
return g_about_cache[id];
|
||||
}
|
||||
let o = await db.get(id + ':about');
|
||||
const k_version = 4;
|
||||
let f = o ? JSON.parse(o) : o;
|
||||
if (!f || f.version != k_version) {
|
||||
f = {about: {}, sequence: 0, version: k_version};
|
||||
}
|
||||
await ssb.sqlAsync(
|
||||
'SELECT ' +
|
||||
' sequence, ' +
|
||||
' content ' +
|
||||
'FROM messages ' +
|
||||
'WHERE ' +
|
||||
' author = ?1 AND ' +
|
||||
' sequence > ?2 AND ' +
|
||||
" json_extract(content, '$.type') = 'about' AND " +
|
||||
" json_extract(content, '$.about') = ?1 " +
|
||||
'UNION SELECT MAX(sequence) as sequence, NULL FROM messages WHERE author = ?1 ' +
|
||||
'ORDER BY sequence',
|
||||
[id, f.sequence],
|
||||
function (row) {
|
||||
f.sequence = row.sequence;
|
||||
if (row.content) {
|
||||
let about = {};
|
||||
try {
|
||||
about = JSON.parse(row.content);
|
||||
} catch {}
|
||||
delete about.about;
|
||||
delete about.type;
|
||||
f.about = Object.assign(f.about, about);
|
||||
}
|
||||
}
|
||||
);
|
||||
let j = JSON.stringify(f);
|
||||
if (o != j) {
|
||||
await db.set(id + ':about', j);
|
||||
}
|
||||
g_about_cache[id] = f.about;
|
||||
return f.about;
|
||||
}
|
||||
|
||||
async function getSize(db, id) {
|
||||
let size = 0;
|
||||
await ssb.sqlAsync(
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* W3.CSS 4.15 December 2020 by Jan Egil and Borge Refsnes */
|
||||
/* W3.CSS 5.02 March 31 2025 by Jan Egil and Borge Refsnes */
|
||||
html{box-sizing:border-box}*,*:before,*:after{box-sizing:inherit}
|
||||
/* Extract from normalize.css by Nicolas Gallagher and Jonathan Neal git.io/normalize */
|
||||
html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}
|
||||
@@ -108,6 +108,8 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
|
||||
.w3-round-small{border-radius:2px}.w3-round,.w3-round-medium{border-radius:4px}.w3-round-large{border-radius:8px}.w3-round-xlarge{border-radius:16px}.w3-round-xxlarge{border-radius:32px}
|
||||
.w3-row-padding,.w3-row-padding>.w3-half,.w3-row-padding>.w3-third,.w3-row-padding>.w3-twothird,.w3-row-padding>.w3-threequarter,.w3-row-padding>.w3-quarter,.w3-row-padding>.w3-col{padding:0 8px}
|
||||
.w3-container,.w3-panel{padding:0.01em 16px}.w3-panel{margin-top:16px;margin-bottom:16px}
|
||||
.w3-grid{display:grid}.w3-grid-padding{display:grid;gap:16px}.w3-flex{display:flex}
|
||||
.w3-text-center{text-align:center}.w3-text-bold,.w3-bold{font-weight:bold}.w3-text-italic,.w3-italic{font-style:italic}
|
||||
.w3-code,.w3-codespan{font-family:Consolas,"courier new";font-size:16px}
|
||||
.w3-code{width:auto;background-color:#fff;padding:8px 12px;border-left:4px solid #4CAF50;word-wrap:break-word}
|
||||
.w3-codespan{color:crimson;background-color:#f1f1f1;padding-left:4px;padding-right:4px;font-size:110%}
|
||||
@@ -148,6 +150,7 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
|
||||
.w3-button:hover{color:#000!important;background-color:#ccc!important}
|
||||
.w3-transparent,.w3-hover-none:hover{background-color:transparent!important}
|
||||
.w3-hover-none:hover{box-shadow:none!important}
|
||||
.w3-rtl{direction:rtl}.w3-ltr{direction:ltr}
|
||||
/* Colors */
|
||||
.w3-amber,.w3-hover-amber:hover{color:#000!important;background-color:#ffc107!important}
|
||||
.w3-aqua,.w3-hover-aqua:hover{color:#000!important;background-color:#00ffff!important}
|
||||
@@ -175,6 +178,19 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
|
||||
.w3-grey,.w3-hover-grey:hover,.w3-gray,.w3-hover-gray:hover{color:#000!important;background-color:#9e9e9e!important}
|
||||
.w3-light-grey,.w3-hover-light-grey:hover,.w3-light-gray,.w3-hover-light-gray:hover{color:#000!important;background-color:#f1f1f1!important}
|
||||
.w3-dark-grey,.w3-hover-dark-grey:hover,.w3-dark-gray,.w3-hover-dark-gray:hover{color:#fff!important;background-color:#616161!important}
|
||||
.w3-asphalt,.w3-hover-asphalt:hover{color:#fff!important;background-color:#343a40!important}
|
||||
.w3-crimson,.w3-hover-crimson:hover{color:#fff!important;background-color:#a20025!important}
|
||||
.w3-cobalt,w3-hover-cobalt:hover{color:#fff!important;background-color:#0050ef!important}
|
||||
.w3-emerald,.w3-hover-emerald:hover{color:#fff!important;background-color:#008a00!important}
|
||||
.w3-olive,.w3-hover-olive:hover{color:#fff!important;background-color:#6d8764!important}
|
||||
.w3-paper,.w3-hover-paper:hover{color:#000!important;background-color:#f8f9fa!important}
|
||||
.w3-sienna,.w3-hover-sienna:hover{color:#fff!important;background-color:#a0522d!important}
|
||||
.w3-taupe,.w3-hover-taupe:hover{color:#fff!important;background-color:#87794e!important}
|
||||
.w3-danger{color:#fff!important;background-color:#dd0000!important}
|
||||
.w3-note{color:#000!important;background-color:#fff599!important}
|
||||
.w3-info{color:#fff!important;background-color:#0a6fc2!important}
|
||||
.w3-warning{color:#000!important;background-color:#ffb305!important}
|
||||
.w3-success{color:#fff!important;background-color:#008a00!important}
|
||||
.w3-pale-red,.w3-hover-pale-red:hover{color:#000!important;background-color:#ffdddd!important}
|
||||
.w3-pale-green,.w3-hover-pale-green:hover{color:#000!important;background-color:#ddffdd!important}
|
||||
.w3-pale-yellow,.w3-hover-pale-yellow:hover{color:#000!important;background-color:#ffffcc!important}
|
||||
@@ -232,4 +248,4 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
|
||||
.w3-border-light-grey,.w3-hover-border-light-grey:hover,.w3-border-light-gray,.w3-hover-border-light-gray:hover{border-color:#f1f1f1!important}
|
||||
.w3-border-dark-grey,.w3-hover-border-dark-grey:hover,.w3-border-dark-gray,.w3-hover-border-dark-gray:hover{border-color:#616161!important}
|
||||
.w3-border-pale-red,.w3-hover-border-pale-red:hover{border-color:#ffe7e7!important}.w3-border-pale-green,.w3-hover-border-pale-green:hover{border-color:#e7ffe7!important}
|
||||
.w3-border-pale-yellow,.w3-hover-border-pale-yellow:hover{border-color:#ffffcc!important}.w3-border-pale-blue,.w3-hover-border-pale-blue:hover{border-color:#e7ffff!important}
|
||||
.w3-border-pale-yellow,.w3-hover-border-pale-yellow:hover{border-color:#ffffcc!important}.w3-border-pale-blue,.w3-hover-border-pale-blue:hover{border-color:#e7ffff!important}
|
||||
|
||||
5
apps/intro.json
Normal file
5
apps/intro.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"type": "tildefriends-app",
|
||||
"emoji": "💡",
|
||||
"previous": "&eN6DNPpQUNhGvxneLuLPgsOXR6qyFZ7u+MAz0b4fa7k=.sha256"
|
||||
}
|
||||
16
apps/intro/app.js
Normal file
16
apps/intro/app.js
Normal file
@@ -0,0 +1,16 @@
|
||||
import * as tfrpc from '/tfrpc.js';
|
||||
|
||||
async function main() {
|
||||
await app.setDocument(utf8Decode(getFile('index.html')));
|
||||
}
|
||||
|
||||
tfrpc.register(async function complete() {
|
||||
if (
|
||||
core.user?.credentials?.permissions?.administration &&
|
||||
(await core.globalSettingsGet('index')) == '/~core/intro/'
|
||||
) {
|
||||
return await core.globalSettingsSet('index', '/~core/ssb/');
|
||||
}
|
||||
});
|
||||
|
||||
main();
|
||||
286
apps/intro/index.html
Normal file
286
apps/intro/index.html
Normal file
@@ -0,0 +1,286 @@
|
||||
<!doctype html>
|
||||
<html style="height: 100%; margin: 0; padding: 0; box-sizing: border-box">
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<link rel="stylesheet" type="text/css" href="w3.css" />
|
||||
<style>
|
||||
.slide {
|
||||
display: none;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
.dot {
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
cursor: pointer;
|
||||
}
|
||||
.w3-left,
|
||||
.w3-right {
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body
|
||||
style="
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
"
|
||||
class="w3-flex w3-dark-gray w3-center"
|
||||
>
|
||||
<div
|
||||
style="
|
||||
flex: 1 1 auto;
|
||||
overflow: auto;
|
||||
contain: content;
|
||||
padding-top: 16px;
|
||||
padding-bottom: 16px;
|
||||
"
|
||||
>
|
||||
<div class="slide">
|
||||
<div
|
||||
class="w3-content w3-xlarge w3-card-4 w3-blue w3-panel w3-padding-32 w3-round-xlarge"
|
||||
style="margin: 32px"
|
||||
>
|
||||
<div>
|
||||
<div>Welcome to</div>
|
||||
<div>~😎 Tilde Friends.</div>
|
||||
</div>
|
||||
<footer>
|
||||
<button class="w3-button w3-yellow proceed">Next</button>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
<div class="slide w3-card-4 w3-gray" style="width: 90%">
|
||||
<header class="w3-container w3-blue w3-xlarge">
|
||||
<h1>This brief tutorial will introduce:</h1>
|
||||
</header>
|
||||
<ul class="w3-large w3-left-align">
|
||||
<li><b>Secure Scuttlebutt</b>, a decentralized social network.</li>
|
||||
<li>
|
||||
<b>Tilde Friends</b>, the application platform that you are using
|
||||
right now.
|
||||
</li>
|
||||
<li>
|
||||
<b>How to get started</b> if you want to get gossiping right away.
|
||||
</li>
|
||||
</ul>
|
||||
<footer class="w3-center w3-xlarge w3-padding">
|
||||
<button class="w3-button w3-yellow proceed">Onward</button>
|
||||
</footer>
|
||||
</div>
|
||||
<div class="slide w3-gray" style="width: 90%">
|
||||
<div class="w3-card-4 w3-xlarge">
|
||||
<header class="w3-container w3-blue">
|
||||
<h1>💻Secure Scuttlebutt in a Nutshell🦀</h1>
|
||||
</header>
|
||||
<div class="w3-container w3-large w3-left-align">
|
||||
<p>
|
||||
Secure Scuttlebutt is a social network whose technical operation
|
||||
attempts to mirror human social interaction.
|
||||
</p>
|
||||
<ul>
|
||||
<li>
|
||||
You can create your own account and post to your own feed on
|
||||
your own device. This is all <b>local</b> with no external
|
||||
communication. This puts you fully in control of your own words
|
||||
and actions.
|
||||
</li>
|
||||
<li>
|
||||
Before you can interact with others, you need to
|
||||
<b>connect over the network</b>, either directly to your friends
|
||||
(i.e., peer-to-peer between your phones on coffee shop Wi-Fi) or
|
||||
to 🚪<i>rooms</i> and 🍻<i>pubs</i> (hint: search the web for
|
||||
<i>#ssbroom</i>).
|
||||
</li>
|
||||
<li>
|
||||
Who you choose to <b>follow</b> determines what you see, with
|
||||
most people choosing to see messages from friends and friends of
|
||||
those friends. If you encounter content you'd rather not see,
|
||||
<b>block</b> the offending account to improve the experience for
|
||||
you and your followers.
|
||||
</li>
|
||||
<li>
|
||||
Your feed is an <b>immutable</b> log of your activity. Post with
|
||||
care, because like your words in real life, posts can't be taken
|
||||
back.
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<footer class="w3-center w3-xlarge w3-padding">
|
||||
<a
|
||||
class="w3-button w3-light-gray"
|
||||
href="https://scuttlebutt.nz/"
|
||||
target="_blank"
|
||||
>See scuttlebutt.nz</a
|
||||
>
|
||||
<button class="w3-button w3-yellow proceed">Got It</button>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
<div class="slide w3-gray" style="width: 90%">
|
||||
<div class="w3-card-4 w3-xlarge">
|
||||
<header class="w3-container w3-blue w3-center">
|
||||
<h1>~😎 Let's Talk Tilde Friends ~😎</h1>
|
||||
</header>
|
||||
<div class="w3-container w3-large w3-left-align">
|
||||
<p>
|
||||
Tilde Friends is an application platform that is an application of
|
||||
its own.
|
||||
</p>
|
||||
<ul>
|
||||
<li>
|
||||
This intro is a Tilde Friends app. You can click <b>edit</b> at
|
||||
the top to look under the hood and make changes.
|
||||
</li>
|
||||
<li>
|
||||
It is already possible to make and share new applications using
|
||||
only Tilde Friends and Secure Scuttlebutt without having to set
|
||||
up development environments, configure web servers, register
|
||||
domain names, or pay for hosting services.
|
||||
</li>
|
||||
<li>
|
||||
But it's also set up so that you can't just break an app that
|
||||
everybody is using or do malicious things with personal content.
|
||||
There are <b>protections</b> in place like an operating system.
|
||||
The intent is also for it to be <b>safe</b> to run strange apps
|
||||
without worrying about adverse effects.
|
||||
</li>
|
||||
<li>
|
||||
But this is all a big 🚧work in progress🚧 and
|
||||
<b>experiment</b>. Let's see where it takes us.
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<footer class="w3-center w3-xlarge w3-padding">
|
||||
<button class="w3-button w3-yellow proceed">Okay</button>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
<div class="slide w3-gray" style="width: 90%">
|
||||
<div class="w3-card-4 w3-xlarge">
|
||||
<header class="w3-container w3-blue w3-center">
|
||||
<h1>🦀Let's Get this Tilde Friends Party Started🎉</h1>
|
||||
</header>
|
||||
<div class="w3-container w3-large w3-left-align">
|
||||
<p>The button below will take you to the Secure Scuttlebutt app.</p>
|
||||
<ul>
|
||||
<li>
|
||||
Remember:
|
||||
<ol>
|
||||
<li>You are in charge. This is all on your device.</li>
|
||||
<li>
|
||||
Make network connections to exchange messages with others.
|
||||
</li>
|
||||
<li>
|
||||
Follow more accounts to see more content, and block those
|
||||
posting content you'd rather not see.
|
||||
</li>
|
||||
<li>
|
||||
Be respectful, and consider the consequences of what you
|
||||
post.
|
||||
</li>
|
||||
<li>
|
||||
This is all under active development. Exercise patience, and
|
||||
report issues encountered.
|
||||
</li>
|
||||
</ol>
|
||||
</li>
|
||||
<li>
|
||||
To see this tutorial again later, select <b>apps</b> ->
|
||||
<b>Core Apps</b> -> <b>intro</b>.
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<footer class="w3-center w3-xlarge w3-padding">
|
||||
<button class="w3-button w3-yellow" id="complete">Let's Go!</button>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="w3-text-white w3-xlarge w3-center w3-flex"
|
||||
style="
|
||||
width: 100%;
|
||||
flex: 0 1;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
"
|
||||
>
|
||||
<div class="w3-jumbo" id="left" style="flex: 1 0; cursor: pointer">
|
||||
❮
|
||||
</div>
|
||||
<span class="w3-badge dot w3-border w3-hover-yellow"></span>
|
||||
<span class="w3-badge dot w3-border w3-hover-yellow"></span>
|
||||
<span class="w3-badge dot w3-border w3-hover-yellow"></span>
|
||||
<span class="w3-badge dot w3-border w3-hover-yellow"></span>
|
||||
<span class="w3-badge dot w3-border w3-hover-yellow"></span>
|
||||
<div class="w3-jumbo" style="flex: 1 0; cursor: pointer" id="right">
|
||||
❯
|
||||
</div>
|
||||
</div>
|
||||
<script type="module">
|
||||
import * as tfrpc from '/static/tfrpc.js';
|
||||
|
||||
let index = 0;
|
||||
function set(i) {
|
||||
show(i - index);
|
||||
}
|
||||
function show(delta) {
|
||||
let slides = [...document.getElementsByClassName('slide')];
|
||||
let dots = [...document.getElementsByClassName('dot')];
|
||||
index = (index + delta + slides.length) % slides.length;
|
||||
for (let slide of slides) {
|
||||
slide.style.display =
|
||||
slides.indexOf(slide) == index ? 'block' : 'none';
|
||||
}
|
||||
for (let dot of dots) {
|
||||
if (dots.indexOf(dot) == index) {
|
||||
dot.classList.add('w3-white');
|
||||
} else {
|
||||
dot.classList.remove('w3-white');
|
||||
}
|
||||
}
|
||||
document.getElementById('left').style.visibility =
|
||||
index == 0 ? 'hidden' : 'visible';
|
||||
document.getElementById('right').style.visibility =
|
||||
index == slides.length - 1 ? 'hidden' : 'visible';
|
||||
}
|
||||
|
||||
let dots = [...document.getElementsByClassName('dot')];
|
||||
for (let dot of dots) {
|
||||
dot.onclick = () => set(dots.indexOf(dot));
|
||||
}
|
||||
for (let button of document.getElementsByClassName('proceed')) {
|
||||
button.onclick = () => show(1);
|
||||
}
|
||||
document.getElementById('left').onclick = () => show(-1);
|
||||
document.getElementById('right').onclick = () => show(1);
|
||||
document.getElementById('complete').onclick = function () {
|
||||
console.log('completing');
|
||||
tfrpc.rpc.complete().finally(function () {
|
||||
console.log('completed');
|
||||
let a = document.createElement('a');
|
||||
a.href = '/~core/ssb/';
|
||||
a.target = '_top';
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
});
|
||||
};
|
||||
window.addEventListener('keyup', function (event) {
|
||||
if (event.key == 'ArrowLeft') {
|
||||
show(-1);
|
||||
} else if (event.key == 'ArrowRight') {
|
||||
show(1);
|
||||
}
|
||||
});
|
||||
show(0);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
251
apps/intro/w3.css
Normal file
251
apps/intro/w3.css
Normal file
@@ -0,0 +1,251 @@
|
||||
/* W3.CSS 5.02 March 31 2025 by Jan Egil and Borge Refsnes */
|
||||
html{box-sizing:border-box}*,*:before,*:after{box-sizing:inherit}
|
||||
/* Extract from normalize.css by Nicolas Gallagher and Jonathan Neal git.io/normalize */
|
||||
html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}
|
||||
article,aside,details,figcaption,figure,footer,header,main,menu,nav,section{display:block}summary{display:list-item}
|
||||
audio,canvas,progress,video{display:inline-block}progress{vertical-align:baseline}
|
||||
audio:not([controls]){display:none;height:0}[hidden],template{display:none}
|
||||
a{background-color:transparent}a:active,a:hover{outline-width:0}
|
||||
abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}
|
||||
b,strong{font-weight:bolder}dfn{font-style:italic}mark{background:#ff0;color:#000}
|
||||
small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}
|
||||
sub{bottom:-0.25em}sup{top:-0.5em}figure{margin:1em 40px}img{border-style:none}
|
||||
code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}hr{box-sizing:content-box;height:0;overflow:visible}
|
||||
button,input,select,textarea,optgroup{font:inherit;margin:0}optgroup{font-weight:bold}
|
||||
button,input{overflow:visible}button,select{text-transform:none}
|
||||
button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button}
|
||||
button::-moz-focus-inner,[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner{border-style:none;padding:0}
|
||||
button:-moz-focusring,[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring{outline:1px dotted ButtonText}
|
||||
fieldset{border:1px solid #c0c0c0;margin:0 2px;padding:.35em .625em .75em}
|
||||
legend{color:inherit;display:table;max-width:100%;padding:0;white-space:normal}textarea{overflow:auto}
|
||||
[type=checkbox],[type=radio]{padding:0}
|
||||
[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}
|
||||
[type=search]{-webkit-appearance:textfield;outline-offset:-2px}
|
||||
[type=search]::-webkit-search-decoration{-webkit-appearance:none}
|
||||
::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}
|
||||
/* End extract */
|
||||
html,body{font-family:Verdana,sans-serif;font-size:15px;line-height:1.5}html{overflow-x:hidden}
|
||||
h1{font-size:36px}h2{font-size:30px}h3{font-size:24px}h4{font-size:20px}h5{font-size:18px}h6{font-size:16px}
|
||||
.w3-serif{font-family:serif}.w3-sans-serif{font-family:sans-serif}.w3-cursive{font-family:cursive}.w3-monospace{font-family:monospace}
|
||||
h1,h2,h3,h4,h5,h6{font-family:"Segoe UI",Arial,sans-serif;font-weight:400;margin:10px 0}.w3-wide{letter-spacing:4px}
|
||||
hr{border:0;border-top:1px solid #eee;margin:20px 0}
|
||||
.w3-image{max-width:100%;height:auto}img{vertical-align:middle}a{color:inherit}
|
||||
.w3-table,.w3-table-all{border-collapse:collapse;border-spacing:0;width:100%;display:table}.w3-table-all{border:1px solid #ccc}
|
||||
.w3-bordered tr,.w3-table-all tr{border-bottom:1px solid #ddd}.w3-striped tbody tr:nth-child(even){background-color:#f1f1f1}
|
||||
.w3-table-all tr:nth-child(odd){background-color:#fff}.w3-table-all tr:nth-child(even){background-color:#f1f1f1}
|
||||
.w3-hoverable tbody tr:hover,.w3-ul.w3-hoverable li:hover{background-color:#ccc}.w3-centered tr th,.w3-centered tr td{text-align:center}
|
||||
.w3-table td,.w3-table th,.w3-table-all td,.w3-table-all th{padding:8px 8px;display:table-cell;text-align:left;vertical-align:top}
|
||||
.w3-table th:first-child,.w3-table td:first-child,.w3-table-all th:first-child,.w3-table-all td:first-child{padding-left:16px}
|
||||
.w3-btn,.w3-button{border:none;display:inline-block;padding:8px 16px;vertical-align:middle;overflow:hidden;text-decoration:none;color:inherit;background-color:inherit;text-align:center;cursor:pointer;white-space:nowrap}
|
||||
.w3-btn:hover{box-shadow:0 8px 16px 0 rgba(0,0,0,0.2),0 6px 20px 0 rgba(0,0,0,0.19)}
|
||||
.w3-btn,.w3-button{-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}
|
||||
.w3-disabled,.w3-btn:disabled,.w3-button:disabled{cursor:not-allowed;opacity:0.3}.w3-disabled *,:disabled *{pointer-events:none}
|
||||
.w3-btn.w3-disabled:hover,.w3-btn:disabled:hover{box-shadow:none}
|
||||
.w3-badge,.w3-tag{background-color:#000;color:#fff;display:inline-block;padding-left:8px;padding-right:8px;text-align:center}.w3-badge{border-radius:50%}
|
||||
.w3-ul{list-style-type:none;padding:0;margin:0}.w3-ul li{padding:8px 16px;border-bottom:1px solid #ddd}.w3-ul li:last-child{border-bottom:none}
|
||||
.w3-tooltip,.w3-display-container{position:relative}.w3-tooltip .w3-text{display:none}.w3-tooltip:hover .w3-text{display:inline-block}
|
||||
.w3-ripple:active{opacity:0.5}.w3-ripple{transition:opacity 0s}
|
||||
.w3-input{padding:8px;display:block;border:none;border-bottom:1px solid #ccc;width:100%}
|
||||
.w3-select{padding:9px 0;width:100%;border:none;border-bottom:1px solid #ccc}
|
||||
.w3-dropdown-click,.w3-dropdown-hover{position:relative;display:inline-block;cursor:pointer}
|
||||
.w3-dropdown-hover:hover .w3-dropdown-content{display:block}
|
||||
.w3-dropdown-hover:first-child,.w3-dropdown-click:hover{background-color:#ccc;color:#000}
|
||||
.w3-dropdown-hover:hover > .w3-button:first-child,.w3-dropdown-click:hover > .w3-button:first-child{background-color:#ccc;color:#000}
|
||||
.w3-dropdown-content{cursor:auto;color:#000;background-color:#fff;display:none;position:absolute;min-width:160px;margin:0;padding:0;z-index:1}
|
||||
.w3-check,.w3-radio{width:24px;height:24px;position:relative;top:6px}
|
||||
.w3-sidebar{height:100%;width:200px;background-color:#fff;position:fixed!important;z-index:1;overflow:auto}
|
||||
.w3-bar-block .w3-dropdown-hover,.w3-bar-block .w3-dropdown-click{width:100%}
|
||||
.w3-bar-block .w3-dropdown-hover .w3-dropdown-content,.w3-bar-block .w3-dropdown-click .w3-dropdown-content{min-width:100%}
|
||||
.w3-bar-block .w3-dropdown-hover .w3-button,.w3-bar-block .w3-dropdown-click .w3-button{width:100%;text-align:left;padding:8px 16px}
|
||||
.w3-main,#main{transition:margin-left .4s}
|
||||
.w3-modal{z-index:3;display:none;padding-top:100px;position:fixed;left:0;top:0;width:100%;height:100%;overflow:auto;background-color:rgb(0,0,0);background-color:rgba(0,0,0,0.4)}
|
||||
.w3-modal-content{margin:auto;background-color:#fff;position:relative;padding:0;outline:0;width:600px}
|
||||
.w3-bar{width:100%;overflow:hidden}.w3-center .w3-bar{display:inline-block;width:auto}
|
||||
.w3-bar .w3-bar-item{padding:8px 16px;float:left;width:auto;border:none;display:block;outline:0}
|
||||
.w3-bar .w3-dropdown-hover,.w3-bar .w3-dropdown-click{position:static;float:left}
|
||||
.w3-bar .w3-button{white-space:normal}
|
||||
.w3-bar-block .w3-bar-item{width:100%;display:block;padding:8px 16px;text-align:left;border:none;white-space:normal;float:none;outline:0}
|
||||
.w3-bar-block.w3-center .w3-bar-item{text-align:center}.w3-block{display:block;width:100%}
|
||||
.w3-responsive{display:block;overflow-x:auto}
|
||||
.w3-container:after,.w3-container:before,.w3-panel:after,.w3-panel:before,.w3-row:after,.w3-row:before,.w3-row-padding:after,.w3-row-padding:before,
|
||||
.w3-cell-row:before,.w3-cell-row:after,.w3-clear:after,.w3-clear:before,.w3-bar:before,.w3-bar:after{content:"";display:table;clear:both}
|
||||
.w3-col,.w3-half,.w3-third,.w3-twothird,.w3-threequarter,.w3-quarter{float:left;width:100%}
|
||||
.w3-col.s1{width:8.33333%}.w3-col.s2{width:16.66666%}.w3-col.s3{width:24.99999%}.w3-col.s4{width:33.33333%}
|
||||
.w3-col.s5{width:41.66666%}.w3-col.s6{width:49.99999%}.w3-col.s7{width:58.33333%}.w3-col.s8{width:66.66666%}
|
||||
.w3-col.s9{width:74.99999%}.w3-col.s10{width:83.33333%}.w3-col.s11{width:91.66666%}.w3-col.s12{width:99.99999%}
|
||||
@media (min-width:601px){.w3-col.m1{width:8.33333%}.w3-col.m2{width:16.66666%}.w3-col.m3,.w3-quarter{width:24.99999%}.w3-col.m4,.w3-third{width:33.33333%}
|
||||
.w3-col.m5{width:41.66666%}.w3-col.m6,.w3-half{width:49.99999%}.w3-col.m7{width:58.33333%}.w3-col.m8,.w3-twothird{width:66.66666%}
|
||||
.w3-col.m9,.w3-threequarter{width:74.99999%}.w3-col.m10{width:83.33333%}.w3-col.m11{width:91.66666%}.w3-col.m12{width:99.99999%}}
|
||||
@media (min-width:993px){.w3-col.l1{width:8.33333%}.w3-col.l2{width:16.66666%}.w3-col.l3{width:24.99999%}.w3-col.l4{width:33.33333%}
|
||||
.w3-col.l5{width:41.66666%}.w3-col.l6{width:49.99999%}.w3-col.l7{width:58.33333%}.w3-col.l8{width:66.66666%}
|
||||
.w3-col.l9{width:74.99999%}.w3-col.l10{width:83.33333%}.w3-col.l11{width:91.66666%}.w3-col.l12{width:99.99999%}}
|
||||
.w3-rest{overflow:hidden}.w3-stretch{margin-left:-16px;margin-right:-16px}
|
||||
.w3-content,.w3-auto{margin-left:auto;margin-right:auto}.w3-content{max-width:980px}.w3-auto{max-width:1140px}
|
||||
.w3-cell-row{display:table;width:100%}.w3-cell{display:table-cell}
|
||||
.w3-cell-top{vertical-align:top}.w3-cell-middle{vertical-align:middle}.w3-cell-bottom{vertical-align:bottom}
|
||||
.w3-hide{display:none!important}.w3-show-block,.w3-show{display:block!important}.w3-show-inline-block{display:inline-block!important}
|
||||
@media (max-width:1205px){.w3-auto{max-width:95%}}
|
||||
@media (max-width:600px){.w3-modal-content{margin:0 10px;width:auto!important}.w3-modal{padding-top:30px}
|
||||
.w3-dropdown-hover.w3-mobile .w3-dropdown-content,.w3-dropdown-click.w3-mobile .w3-dropdown-content{position:relative}
|
||||
.w3-hide-small{display:none!important}.w3-mobile{display:block;width:100%!important}.w3-bar-item.w3-mobile,.w3-dropdown-hover.w3-mobile,.w3-dropdown-click.w3-mobile{text-align:center}
|
||||
.w3-dropdown-hover.w3-mobile,.w3-dropdown-hover.w3-mobile .w3-btn,.w3-dropdown-hover.w3-mobile .w3-button,.w3-dropdown-click.w3-mobile,.w3-dropdown-click.w3-mobile .w3-btn,.w3-dropdown-click.w3-mobile .w3-button{width:100%}}
|
||||
@media (max-width:768px){.w3-modal-content{width:500px}.w3-modal{padding-top:50px}}
|
||||
@media (min-width:993px){.w3-modal-content{width:900px}.w3-hide-large{display:none!important}.w3-sidebar.w3-collapse{display:block!important}}
|
||||
@media (max-width:992px) and (min-width:601px){.w3-hide-medium{display:none!important}}
|
||||
@media (max-width:992px){.w3-sidebar.w3-collapse{display:none}.w3-main{margin-left:0!important;margin-right:0!important}.w3-auto{max-width:100%}}
|
||||
.w3-top,.w3-bottom{position:fixed;width:100%;z-index:1}.w3-top{top:0}.w3-bottom{bottom:0}
|
||||
.w3-overlay{position:fixed;display:none;width:100%;height:100%;top:0;left:0;right:0;bottom:0;background-color:rgba(0,0,0,0.5);z-index:2}
|
||||
.w3-display-topleft{position:absolute;left:0;top:0}.w3-display-topright{position:absolute;right:0;top:0}
|
||||
.w3-display-bottomleft{position:absolute;left:0;bottom:0}.w3-display-bottomright{position:absolute;right:0;bottom:0}
|
||||
.w3-display-middle{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);-ms-transform:translate(-50%,-50%)}
|
||||
.w3-display-left{position:absolute;top:50%;left:0%;transform:translate(0%,-50%);-ms-transform:translate(-0%,-50%)}
|
||||
.w3-display-right{position:absolute;top:50%;right:0%;transform:translate(0%,-50%);-ms-transform:translate(0%,-50%)}
|
||||
.w3-display-topmiddle{position:absolute;left:50%;top:0;transform:translate(-50%,0%);-ms-transform:translate(-50%,0%)}
|
||||
.w3-display-bottommiddle{position:absolute;left:50%;bottom:0;transform:translate(-50%,0%);-ms-transform:translate(-50%,0%)}
|
||||
.w3-display-container:hover .w3-display-hover{display:block}.w3-display-container:hover span.w3-display-hover{display:inline-block}.w3-display-hover{display:none}
|
||||
.w3-display-position{position:absolute}
|
||||
.w3-circle{border-radius:50%}
|
||||
.w3-round-small{border-radius:2px}.w3-round,.w3-round-medium{border-radius:4px}.w3-round-large{border-radius:8px}.w3-round-xlarge{border-radius:16px}.w3-round-xxlarge{border-radius:32px}
|
||||
.w3-row-padding,.w3-row-padding>.w3-half,.w3-row-padding>.w3-third,.w3-row-padding>.w3-twothird,.w3-row-padding>.w3-threequarter,.w3-row-padding>.w3-quarter,.w3-row-padding>.w3-col{padding:0 8px}
|
||||
.w3-container,.w3-panel{padding:0.01em 16px}.w3-panel{margin-top:16px;margin-bottom:16px}
|
||||
.w3-grid{display:grid}.w3-grid-padding{display:grid;gap:16px}.w3-flex{display:flex}
|
||||
.w3-text-center{text-align:center}.w3-text-bold,.w3-bold{font-weight:bold}.w3-text-italic,.w3-italic{font-style:italic}
|
||||
.w3-code,.w3-codespan{font-family:Consolas,"courier new";font-size:16px}
|
||||
.w3-code{width:auto;background-color:#fff;padding:8px 12px;border-left:4px solid #4CAF50;word-wrap:break-word}
|
||||
.w3-codespan{color:crimson;background-color:#f1f1f1;padding-left:4px;padding-right:4px;font-size:110%}
|
||||
.w3-card,.w3-card-2{box-shadow:0 2px 5px 0 rgba(0,0,0,0.16),0 2px 10px 0 rgba(0,0,0,0.12)}
|
||||
.w3-card-4,.w3-hover-shadow:hover{box-shadow:0 4px 10px 0 rgba(0,0,0,0.2),0 4px 20px 0 rgba(0,0,0,0.19)}
|
||||
.w3-spin{animation:w3-spin 2s infinite linear}@keyframes w3-spin{0%{transform:rotate(0deg)}100%{transform:rotate(359deg)}}
|
||||
.w3-animate-fading{animation:fading 10s infinite}@keyframes fading{0%{opacity:0}50%{opacity:1}100%{opacity:0}}
|
||||
.w3-animate-opacity{animation:opac 0.8s}@keyframes opac{from{opacity:0} to{opacity:1}}
|
||||
.w3-animate-top{position:relative;animation:animatetop 0.4s}@keyframes animatetop{from{top:-300px;opacity:0} to{top:0;opacity:1}}
|
||||
.w3-animate-left{position:relative;animation:animateleft 0.4s}@keyframes animateleft{from{left:-300px;opacity:0} to{left:0;opacity:1}}
|
||||
.w3-animate-right{position:relative;animation:animateright 0.4s}@keyframes animateright{from{right:-300px;opacity:0} to{right:0;opacity:1}}
|
||||
.w3-animate-bottom{position:relative;animation:animatebottom 0.4s}@keyframes animatebottom{from{bottom:-300px;opacity:0} to{bottom:0;opacity:1}}
|
||||
.w3-animate-zoom {animation:animatezoom 0.6s}@keyframes animatezoom{from{transform:scale(0)} to{transform:scale(1)}}
|
||||
.w3-animate-input{transition:width 0.4s ease-in-out}.w3-animate-input:focus{width:100%!important}
|
||||
.w3-opacity,.w3-hover-opacity:hover{opacity:0.60}.w3-opacity-off,.w3-hover-opacity-off:hover{opacity:1}
|
||||
.w3-opacity-max{opacity:0.25}.w3-opacity-min{opacity:0.75}
|
||||
.w3-greyscale-max,.w3-grayscale-max,.w3-hover-greyscale:hover,.w3-hover-grayscale:hover{filter:grayscale(100%)}
|
||||
.w3-greyscale,.w3-grayscale{filter:grayscale(75%)}.w3-greyscale-min,.w3-grayscale-min{filter:grayscale(50%)}
|
||||
.w3-sepia{filter:sepia(75%)}.w3-sepia-max,.w3-hover-sepia:hover{filter:sepia(100%)}.w3-sepia-min{filter:sepia(50%)}
|
||||
.w3-tiny{font-size:10px!important}.w3-small{font-size:12px!important}.w3-medium{font-size:15px!important}.w3-large{font-size:18px!important}
|
||||
.w3-xlarge{font-size:24px!important}.w3-xxlarge{font-size:36px!important}.w3-xxxlarge{font-size:48px!important}.w3-jumbo{font-size:64px!important}
|
||||
.w3-left-align{text-align:left!important}.w3-right-align{text-align:right!important}.w3-justify{text-align:justify!important}.w3-center{text-align:center!important}
|
||||
.w3-border-0{border:0!important}.w3-border{border:1px solid #ccc!important}
|
||||
.w3-border-top{border-top:1px solid #ccc!important}.w3-border-bottom{border-bottom:1px solid #ccc!important}
|
||||
.w3-border-left{border-left:1px solid #ccc!important}.w3-border-right{border-right:1px solid #ccc!important}
|
||||
.w3-topbar{border-top:6px solid #ccc!important}.w3-bottombar{border-bottom:6px solid #ccc!important}
|
||||
.w3-leftbar{border-left:6px solid #ccc!important}.w3-rightbar{border-right:6px solid #ccc!important}
|
||||
.w3-section,.w3-code{margin-top:16px!important;margin-bottom:16px!important}
|
||||
.w3-margin{margin:16px!important}.w3-margin-top{margin-top:16px!important}.w3-margin-bottom{margin-bottom:16px!important}
|
||||
.w3-margin-left{margin-left:16px!important}.w3-margin-right{margin-right:16px!important}
|
||||
.w3-padding-small{padding:4px 8px!important}.w3-padding{padding:8px 16px!important}.w3-padding-large{padding:12px 24px!important}
|
||||
.w3-padding-16{padding-top:16px!important;padding-bottom:16px!important}.w3-padding-24{padding-top:24px!important;padding-bottom:24px!important}
|
||||
.w3-padding-32{padding-top:32px!important;padding-bottom:32px!important}.w3-padding-48{padding-top:48px!important;padding-bottom:48px!important}
|
||||
.w3-padding-64{padding-top:64px!important;padding-bottom:64px!important}
|
||||
.w3-padding-top-64{padding-top:64px!important}.w3-padding-top-48{padding-top:48px!important}
|
||||
.w3-padding-top-32{padding-top:32px!important}.w3-padding-top-24{padding-top:24px!important}
|
||||
.w3-left{float:left!important}.w3-right{float:right!important}
|
||||
.w3-button:hover{color:#000!important;background-color:#ccc!important}
|
||||
.w3-transparent,.w3-hover-none:hover{background-color:transparent!important}
|
||||
.w3-hover-none:hover{box-shadow:none!important}
|
||||
.w3-rtl{direction:rtl}.w3-ltr{direction:ltr}
|
||||
/* Colors */
|
||||
.w3-amber,.w3-hover-amber:hover{color:#000!important;background-color:#ffc107!important}
|
||||
.w3-aqua,.w3-hover-aqua:hover{color:#000!important;background-color:#00ffff!important}
|
||||
.w3-blue,.w3-hover-blue:hover{color:#fff!important;background-color:#2196F3!important}
|
||||
.w3-light-blue,.w3-hover-light-blue:hover{color:#000!important;background-color:#87CEEB!important}
|
||||
.w3-brown,.w3-hover-brown:hover{color:#fff!important;background-color:#795548!important}
|
||||
.w3-cyan,.w3-hover-cyan:hover{color:#000!important;background-color:#00bcd4!important}
|
||||
.w3-blue-grey,.w3-hover-blue-grey:hover,.w3-blue-gray,.w3-hover-blue-gray:hover{color:#fff!important;background-color:#607d8b!important}
|
||||
.w3-green,.w3-hover-green:hover{color:#fff!important;background-color:#4CAF50!important}
|
||||
.w3-light-green,.w3-hover-light-green:hover{color:#000!important;background-color:#8bc34a!important}
|
||||
.w3-indigo,.w3-hover-indigo:hover{color:#fff!important;background-color:#3f51b5!important}
|
||||
.w3-khaki,.w3-hover-khaki:hover{color:#000!important;background-color:#f0e68c!important}
|
||||
.w3-lime,.w3-hover-lime:hover{color:#000!important;background-color:#cddc39!important}
|
||||
.w3-orange,.w3-hover-orange:hover{color:#000!important;background-color:#ff9800!important}
|
||||
.w3-deep-orange,.w3-hover-deep-orange:hover{color:#fff!important;background-color:#ff5722!important}
|
||||
.w3-pink,.w3-hover-pink:hover{color:#fff!important;background-color:#e91e63!important}
|
||||
.w3-purple,.w3-hover-purple:hover{color:#fff!important;background-color:#9c27b0!important}
|
||||
.w3-deep-purple,.w3-hover-deep-purple:hover{color:#fff!important;background-color:#673ab7!important}
|
||||
.w3-red,.w3-hover-red:hover{color:#fff!important;background-color:#f44336!important}
|
||||
.w3-sand,.w3-hover-sand:hover{color:#000!important;background-color:#fdf5e6!important}
|
||||
.w3-teal,.w3-hover-teal:hover{color:#fff!important;background-color:#009688!important}
|
||||
.w3-yellow,.w3-hover-yellow:hover{color:#000!important;background-color:#ffeb3b!important}
|
||||
.w3-white,.w3-hover-white:hover{color:#000!important;background-color:#fff!important}
|
||||
.w3-black,.w3-hover-black:hover{color:#fff!important;background-color:#000!important}
|
||||
.w3-grey,.w3-hover-grey:hover,.w3-gray,.w3-hover-gray:hover{color:#000!important;background-color:#9e9e9e!important}
|
||||
.w3-light-grey,.w3-hover-light-grey:hover,.w3-light-gray,.w3-hover-light-gray:hover{color:#000!important;background-color:#f1f1f1!important}
|
||||
.w3-dark-grey,.w3-hover-dark-grey:hover,.w3-dark-gray,.w3-hover-dark-gray:hover{color:#fff!important;background-color:#616161!important}
|
||||
.w3-asphalt,.w3-hover-asphalt:hover{color:#fff!important;background-color:#343a40!important}
|
||||
.w3-crimson,.w3-hover-crimson:hover{color:#fff!important;background-color:#a20025!important}
|
||||
.w3-cobalt,w3-hover-cobalt:hover{color:#fff!important;background-color:#0050ef!important}
|
||||
.w3-emerald,.w3-hover-emerald:hover{color:#fff!important;background-color:#008a00!important}
|
||||
.w3-olive,.w3-hover-olive:hover{color:#fff!important;background-color:#6d8764!important}
|
||||
.w3-paper,.w3-hover-paper:hover{color:#000!important;background-color:#f8f9fa!important}
|
||||
.w3-sienna,.w3-hover-sienna:hover{color:#fff!important;background-color:#a0522d!important}
|
||||
.w3-taupe,.w3-hover-taupe:hover{color:#fff!important;background-color:#87794e!important}
|
||||
.w3-danger{color:#fff!important;background-color:#dd0000!important}
|
||||
.w3-note{color:#000!important;background-color:#fff599!important}
|
||||
.w3-info{color:#fff!important;background-color:#0a6fc2!important}
|
||||
.w3-warning{color:#000!important;background-color:#ffb305!important}
|
||||
.w3-success{color:#fff!important;background-color:#008a00!important}
|
||||
.w3-pale-red,.w3-hover-pale-red:hover{color:#000!important;background-color:#ffdddd!important}
|
||||
.w3-pale-green,.w3-hover-pale-green:hover{color:#000!important;background-color:#ddffdd!important}
|
||||
.w3-pale-yellow,.w3-hover-pale-yellow:hover{color:#000!important;background-color:#ffffcc!important}
|
||||
.w3-pale-blue,.w3-hover-pale-blue:hover{color:#000!important;background-color:#ddffff!important}
|
||||
.w3-text-amber,.w3-hover-text-amber:hover{color:#ffc107!important}
|
||||
.w3-text-aqua,.w3-hover-text-aqua:hover{color:#00ffff!important}
|
||||
.w3-text-blue,.w3-hover-text-blue:hover{color:#2196F3!important}
|
||||
.w3-text-light-blue,.w3-hover-text-light-blue:hover{color:#87CEEB!important}
|
||||
.w3-text-brown,.w3-hover-text-brown:hover{color:#795548!important}
|
||||
.w3-text-cyan,.w3-hover-text-cyan:hover{color:#00bcd4!important}
|
||||
.w3-text-blue-grey,.w3-hover-text-blue-grey:hover,.w3-text-blue-gray,.w3-hover-text-blue-gray:hover{color:#607d8b!important}
|
||||
.w3-text-green,.w3-hover-text-green:hover{color:#4CAF50!important}
|
||||
.w3-text-light-green,.w3-hover-text-light-green:hover{color:#8bc34a!important}
|
||||
.w3-text-indigo,.w3-hover-text-indigo:hover{color:#3f51b5!important}
|
||||
.w3-text-khaki,.w3-hover-text-khaki:hover{color:#b4aa50!important}
|
||||
.w3-text-lime,.w3-hover-text-lime:hover{color:#cddc39!important}
|
||||
.w3-text-orange,.w3-hover-text-orange:hover{color:#ff9800!important}
|
||||
.w3-text-deep-orange,.w3-hover-text-deep-orange:hover{color:#ff5722!important}
|
||||
.w3-text-pink,.w3-hover-text-pink:hover{color:#e91e63!important}
|
||||
.w3-text-purple,.w3-hover-text-purple:hover{color:#9c27b0!important}
|
||||
.w3-text-deep-purple,.w3-hover-text-deep-purple:hover{color:#673ab7!important}
|
||||
.w3-text-red,.w3-hover-text-red:hover{color:#f44336!important}
|
||||
.w3-text-sand,.w3-hover-text-sand:hover{color:#fdf5e6!important}
|
||||
.w3-text-teal,.w3-hover-text-teal:hover{color:#009688!important}
|
||||
.w3-text-yellow,.w3-hover-text-yellow:hover{color:#d2be0e!important}
|
||||
.w3-text-white,.w3-hover-text-white:hover{color:#fff!important}
|
||||
.w3-text-black,.w3-hover-text-black:hover{color:#000!important}
|
||||
.w3-text-grey,.w3-hover-text-grey:hover,.w3-text-gray,.w3-hover-text-gray:hover{color:#757575!important}
|
||||
.w3-text-light-grey,.w3-hover-text-light-grey:hover,.w3-text-light-gray,.w3-hover-text-light-gray:hover{color:#f1f1f1!important}
|
||||
.w3-text-dark-grey,.w3-hover-text-dark-grey:hover,.w3-text-dark-gray,.w3-hover-text-dark-gray:hover{color:#3a3a3a!important}
|
||||
.w3-border-amber,.w3-hover-border-amber:hover{border-color:#ffc107!important}
|
||||
.w3-border-aqua,.w3-hover-border-aqua:hover{border-color:#00ffff!important}
|
||||
.w3-border-blue,.w3-hover-border-blue:hover{border-color:#2196F3!important}
|
||||
.w3-border-light-blue,.w3-hover-border-light-blue:hover{border-color:#87CEEB!important}
|
||||
.w3-border-brown,.w3-hover-border-brown:hover{border-color:#795548!important}
|
||||
.w3-border-cyan,.w3-hover-border-cyan:hover{border-color:#00bcd4!important}
|
||||
.w3-border-blue-grey,.w3-hover-border-blue-grey:hover,.w3-border-blue-gray,.w3-hover-border-blue-gray:hover{border-color:#607d8b!important}
|
||||
.w3-border-green,.w3-hover-border-green:hover{border-color:#4CAF50!important}
|
||||
.w3-border-light-green,.w3-hover-border-light-green:hover{border-color:#8bc34a!important}
|
||||
.w3-border-indigo,.w3-hover-border-indigo:hover{border-color:#3f51b5!important}
|
||||
.w3-border-khaki,.w3-hover-border-khaki:hover{border-color:#f0e68c!important}
|
||||
.w3-border-lime,.w3-hover-border-lime:hover{border-color:#cddc39!important}
|
||||
.w3-border-orange,.w3-hover-border-orange:hover{border-color:#ff9800!important}
|
||||
.w3-border-deep-orange,.w3-hover-border-deep-orange:hover{border-color:#ff5722!important}
|
||||
.w3-border-pink,.w3-hover-border-pink:hover{border-color:#e91e63!important}
|
||||
.w3-border-purple,.w3-hover-border-purple:hover{border-color:#9c27b0!important}
|
||||
.w3-border-deep-purple,.w3-hover-border-deep-purple:hover{border-color:#673ab7!important}
|
||||
.w3-border-red,.w3-hover-border-red:hover{border-color:#f44336!important}
|
||||
.w3-border-sand,.w3-hover-border-sand:hover{border-color:#fdf5e6!important}
|
||||
.w3-border-teal,.w3-hover-border-teal:hover{border-color:#009688!important}
|
||||
.w3-border-yellow,.w3-hover-border-yellow:hover{border-color:#ffeb3b!important}
|
||||
.w3-border-white,.w3-hover-border-white:hover{border-color:#fff!important}
|
||||
.w3-border-black,.w3-hover-border-black:hover{border-color:#000!important}
|
||||
.w3-border-grey,.w3-hover-border-grey:hover,.w3-border-gray,.w3-hover-border-gray:hover{border-color:#9e9e9e!important}
|
||||
.w3-border-light-grey,.w3-hover-border-light-grey:hover,.w3-border-light-gray,.w3-hover-border-light-gray:hover{border-color:#f1f1f1!important}
|
||||
.w3-border-dark-grey,.w3-hover-border-dark-grey:hover,.w3-border-dark-gray,.w3-hover-border-dark-gray:hover{border-color:#616161!important}
|
||||
.w3-border-pale-red,.w3-hover-border-pale-red:hover{border-color:#ffe7e7!important}.w3-border-pale-green,.w3-hover-border-pale-green:hover{border-color:#e7ffe7!important}
|
||||
.w3-border-pale-yellow,.w3-hover-border-pale-yellow:hover{border-color:#ffffcc!important}.w3-border-pale-blue,.w3-hover-border-pale-blue:hover{border-color:#e7ffff!important}
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"type": "tildefriends-app",
|
||||
"emoji": "🦟",
|
||||
"previous": "&cUqvSDUls3jn0haD85LPFAGdkc8wFuy347TtATNcJgg=.sha256"
|
||||
"previous": "&O0huuEgL/UQC9bkUfhPOyZFo9eRiz+koOkba6QwCGNA=.sha256"
|
||||
}
|
||||
|
||||
42
apps/issues/lit-all.min.js
vendored
42
apps/issues/lit-all.min.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,5 +1,11 @@
|
||||
import * as linkify from './commonmark-linkify.js';
|
||||
|
||||
var reUnsafeProtocol = /^javascript:|vbscript:|file:|data:/i;
|
||||
var reSafeDataProtocol = /^data:image\/(?:png|gif|jpeg|webp)/i;
|
||||
var potentiallyUnsafe = function (url) {
|
||||
return reUnsafeProtocol.test(url) && !reSafeDataProtocol.test(url);
|
||||
};
|
||||
|
||||
function image(node, entering) {
|
||||
if (
|
||||
node.firstChild?.type === 'text' &&
|
||||
@@ -61,8 +67,8 @@ function image(node, entering) {
|
||||
}
|
||||
|
||||
export function markdown(md) {
|
||||
var reader = new commonmark.Parser({safe: true});
|
||||
var writer = new commonmark.HtmlRenderer();
|
||||
var reader = new commonmark.Parser();
|
||||
var writer = new commonmark.HtmlRenderer({safe: true});
|
||||
writer.image = image;
|
||||
var parsed = reader.parse(md || '');
|
||||
parsed = linkify.transform(parsed);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"type": "tildefriends-app",
|
||||
"emoji": "📝",
|
||||
"previous": "&b//KqE4Vx6kOSBRODK1p/8wjOLKZJ+CBB5IkaBt5YsM=.sha256"
|
||||
"previous": "&5LpOTEnor/rYFk3axyfmmehAoq9aEwNQRH4jwNhRQ7o=.sha256"
|
||||
}
|
||||
|
||||
42
apps/journal/lit-all.min.js
vendored
42
apps/journal/lit-all.min.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -18,8 +18,8 @@ class TfJournalEntryElement extends LitElement {
|
||||
}
|
||||
|
||||
markdown(md) {
|
||||
var reader = new commonmark.Parser({safe: true});
|
||||
var writer = new commonmark.HtmlRenderer();
|
||||
var reader = new commonmark.Parser();
|
||||
var writer = new commonmark.HtmlRenderer({safe: true});
|
||||
var parsed = reader.parse(md || '');
|
||||
return writer.render(parsed);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"type": "tildefriends-app",
|
||||
"emoji": "🚪",
|
||||
"previous": "&HXCdDG8gGYXElTyEFbg85jqa6lDXNL2ENPIA9UoJNbI=.sha256"
|
||||
"previous": "&DJwkqNfYWtW9yBtJQMseEXm7l04Enpi+yAxZulLq9Vk=.sha256"
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
async function main() {
|
||||
let host = core.url.match(/.*\/\/(.*?)\//)[1];
|
||||
let id = (await ssb.getServerIdentity()).substring(1);
|
||||
let room = `net:${host}:${ssb.port}~shs:${id}:SSB+Room+SK3TLYC2T86EHQCUHBUHASCASE18JBV24=`;
|
||||
print(core.url);
|
||||
let host = core.url.match(/.*?\/\/([^:/]*)/)[1];
|
||||
let port = await ssb.port();
|
||||
let id = (await ssb.getServerIdentity()).substring(1).split('.')[0];
|
||||
let room = `net:${host}:${port}~shs:${id}:SSB+Room+PSK3TLYC2T86EHQCUHBUHASCASE18JBV24=`;
|
||||
await app.setDocument(`
|
||||
<body style="color: #fff">
|
||||
<h1>Server</h1>
|
||||
|
||||
42
apps/sneaker/lit-all.min.js
vendored
42
apps/sneaker/lit-all.min.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"type": "tildefriends-app",
|
||||
"emoji": "🐌",
|
||||
"previous": "&jEWLK3TSDaS67X4zN//XhAjtODbVyWwqu+rxiNuzJRM=.sha256"
|
||||
"emoji": "🦀",
|
||||
"previous": "&Gic1e3jOZ7z5131jSCclbFXRpjyu8JlWJrjE7Fvn5dc=.sha256"
|
||||
}
|
||||
|
||||
@@ -21,9 +21,6 @@ tfrpc.register(async function createIdentity() {
|
||||
tfrpc.register(async function getServerIdentity() {
|
||||
return ssb.getServerIdentity();
|
||||
});
|
||||
tfrpc.register(async function setServerFollowingMe(id, following) {
|
||||
return ssb.setServerFollowingMe(id, following);
|
||||
});
|
||||
tfrpc.register(async function getIdentities() {
|
||||
return ssb.getIdentities();
|
||||
});
|
||||
@@ -79,6 +76,9 @@ tfrpc.register(function setHash(hash) {
|
||||
core.register('onMessage', async function (id) {
|
||||
await tfrpc.rpc.notifyNewMessage(id);
|
||||
});
|
||||
core.register('onBlob', async function (id) {
|
||||
await tfrpc.rpc.notifyNewBlob(id);
|
||||
});
|
||||
tfrpc.register(async function store_blob(blob) {
|
||||
if (Array.isArray(blob)) {
|
||||
blob = Uint8Array.from(blob);
|
||||
@@ -106,6 +106,19 @@ tfrpc.register(async function getActiveIdentity() {
|
||||
tfrpc.register(async function sync() {
|
||||
return await ssb.sync();
|
||||
});
|
||||
tfrpc.register(async function url() {
|
||||
return core.url;
|
||||
});
|
||||
tfrpc.register(async function globalSettingsGet(key) {
|
||||
return core.globalSettingsGet(key);
|
||||
});
|
||||
tfrpc.register(async function globalSettingsSet(key, value) {
|
||||
return core.globalSettingsSet(key, value);
|
||||
});
|
||||
tfrpc.register(function isAdministrator() {
|
||||
return core.user?.credentials?.permissions?.administration;
|
||||
});
|
||||
|
||||
core.register('onBroadcastsChanged', async function () {
|
||||
await tfrpc.rpc.set('broadcasts', await ssb.getBroadcasts());
|
||||
});
|
||||
|
||||
@@ -7,7 +7,7 @@ function textNode(text) {
|
||||
function linkNode(text, link) {
|
||||
const linkNode = new commonmark.Node('link', undefined);
|
||||
if (link.startsWith('#')) {
|
||||
linkNode.destination = `#q=${encodeURIComponent(link)}`;
|
||||
linkNode.destination = `#${encodeURIComponent(link)}`;
|
||||
} else {
|
||||
linkNode.destination = link;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as tfrpc from '/static/tfrpc.js';
|
||||
import {html, render} from './lit-all.min.js';
|
||||
import {styles} from './tf-styles.js';
|
||||
import {styles, generate_theme} from './tf-styles.js';
|
||||
|
||||
let g_emojis;
|
||||
|
||||
@@ -14,33 +14,20 @@ function get_emojis() {
|
||||
});
|
||||
}
|
||||
|
||||
async function get_recent(author) {
|
||||
let recent = await tfrpc.rpc.query(
|
||||
`
|
||||
SELECT DISTINCT content ->> '$.vote.expression' AS value
|
||||
FROM messages
|
||||
WHERE author = ? AND
|
||||
content ->> '$.type' = 'vote'
|
||||
ORDER BY timestamp DESC LIMIT 10
|
||||
`,
|
||||
[author]
|
||||
);
|
||||
return recent.map((x) => x.value);
|
||||
}
|
||||
|
||||
export async function picker(callback, anchor, author) {
|
||||
export async function picker(callback, anchor, author, recent) {
|
||||
let json = await get_emojis();
|
||||
let recent = await get_recent(author);
|
||||
|
||||
let div = document.createElement('div');
|
||||
div.id = 'emoji_picker';
|
||||
div.style.color = '#000';
|
||||
div.style.background = '#fff';
|
||||
div.style.border = '1px solid #000';
|
||||
div.style.display = 'block';
|
||||
div.style.display = 'flex';
|
||||
div.style.overflow = 'scroll';
|
||||
div.style.fontWeight = 'bold';
|
||||
div.style.fontSize = 'xx-large';
|
||||
div.style.flex = '1 1';
|
||||
div.style.flexDirection = 'column';
|
||||
let input = document.createElement('input');
|
||||
input.type = 'text';
|
||||
input.style.display = 'block';
|
||||
@@ -50,6 +37,7 @@ export async function picker(callback, anchor, author) {
|
||||
input.style.position = 'relative';
|
||||
div.appendChild(input);
|
||||
let list = document.createElement('div');
|
||||
list.style.overflow = 'scroll';
|
||||
div.appendChild(list);
|
||||
div.addEventListener('mousedown', function (event) {
|
||||
event.stopPropagation();
|
||||
@@ -142,21 +130,43 @@ export async function picker(callback, anchor, author) {
|
||||
}
|
||||
refresh();
|
||||
input.oninput = refresh;
|
||||
let modal = html`
|
||||
<style>
|
||||
${styles}
|
||||
</style>
|
||||
<div class="w3-modal" style="display: block">
|
||||
<div class="w3-modal-content w3-card-4">${div}</div>
|
||||
</div>
|
||||
`;
|
||||
let parent = document.createElement('div');
|
||||
document.body.appendChild(parent);
|
||||
function cleanup() {
|
||||
parent.parentElement.removeChild(parent);
|
||||
window.removeEventListener('keydown', key_down);
|
||||
document.body.removeEventListener('mousedown', cleanup);
|
||||
}
|
||||
let modal = html`
|
||||
<style>
|
||||
${styles}
|
||||
</style>
|
||||
<style>
|
||||
${generate_theme()}
|
||||
</style>
|
||||
<div
|
||||
class="w3-modal"
|
||||
style="display: block; box-sizing: border-box; z-index: 10"
|
||||
>
|
||||
<div class="w3-modal-content w3-card-4">
|
||||
<div
|
||||
class="w3-content w3-theme-d1"
|
||||
style="display: flex; flex-direction: column; max-height: 80vh"
|
||||
>
|
||||
<header class="w3-container" style="flex: 0 0">
|
||||
<h1>Choose a Reaction</h1>
|
||||
<span class="w3-button w3-display-topright" @click=${cleanup}
|
||||
>×</span
|
||||
>
|
||||
</header>
|
||||
${div}
|
||||
<footer class="w3-container w3-padding" style="flex: 0 0">
|
||||
<button class="w3-button" @click=${cleanup}>Close</button>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
document.body.appendChild(parent);
|
||||
render(modal, parent);
|
||||
input.focus();
|
||||
document.body.addEventListener('mousedown', cleanup);
|
||||
|
||||
File diff suppressed because one or more lines are too long
42
apps/ssb/lit-all.min.js
vendored
42
apps/ssb/lit-all.min.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -8,10 +8,18 @@ import * as tf_compose from './tf-compose.js';
|
||||
import * as tf_news from './tf-news.js';
|
||||
import * as tf_profile from './tf-profile.js';
|
||||
import * as tf_reactions_modal from './tf-reactions-modal.js';
|
||||
import * as tf_tab_mentions from './tf-tab-mentions.js';
|
||||
import * as tf_tab_news from './tf-tab-news.js';
|
||||
import * as tf_tab_news_feed from './tf-tab-news-feed.js';
|
||||
import * as tf_tab_search from './tf-tab-search.js';
|
||||
import * as tf_tab_connections from './tf-tab-connections.js';
|
||||
import * as tf_tab_query from './tf-tab-query.js';
|
||||
import * as tf_tag from './tf-tag.js';
|
||||
import * as tf_styles from './tf-styles.js';
|
||||
|
||||
window.addEventListener('load', function () {
|
||||
let style = document.createElement('style');
|
||||
style.innerText = tf_styles.styles;
|
||||
Promise.resolve(tf_styles.generate_theme()).then(function (x) {
|
||||
style.innerText += x;
|
||||
});
|
||||
document.body.appendChild(style);
|
||||
});
|
||||
|
||||
@@ -1,22 +1,33 @@
|
||||
import {LitElement, html, css, guard, until} from './lit-all.min.js';
|
||||
import * as tfrpc from '/static/tfrpc.js';
|
||||
import {styles} from './tf-styles.js';
|
||||
import {styles, generate_theme} from './tf-styles.js';
|
||||
|
||||
class TfElement extends LitElement {
|
||||
static get properties() {
|
||||
return {
|
||||
whoami: {type: String},
|
||||
hash: {type: String},
|
||||
unread: {type: Array},
|
||||
tab: {type: String},
|
||||
broadcasts: {type: Array},
|
||||
connections: {type: Array},
|
||||
loading: {type: Boolean},
|
||||
loading_about: {type: Number},
|
||||
loaded: {type: Boolean},
|
||||
following: {type: Array},
|
||||
users: {type: Object},
|
||||
ids: {type: Array},
|
||||
tags: {type: Array},
|
||||
channels: {type: Array},
|
||||
channels_unread: {type: Object},
|
||||
channels_latest: {type: Object},
|
||||
guest: {type: Boolean},
|
||||
url: {type: String},
|
||||
private_closed: {type: Object},
|
||||
private_messages: {type: Array},
|
||||
grouped_private_messages: {type: Object},
|
||||
recent_reactions: {type: Array},
|
||||
is_administrator: {type: Boolean},
|
||||
stay_connected: {type: Boolean},
|
||||
progress: {type: Number},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -26,14 +37,20 @@ class TfElement extends LitElement {
|
||||
super();
|
||||
let self = this;
|
||||
this.hash = '#';
|
||||
this.unread = [];
|
||||
this.tab = 'news';
|
||||
this.broadcasts = [];
|
||||
this.connections = [];
|
||||
this.following = [];
|
||||
this.users = {};
|
||||
this.loaded = false;
|
||||
this.tags = [];
|
||||
this.loading_about = 0;
|
||||
this.channels = [];
|
||||
this.channels_unread = {};
|
||||
this.channels_latest = {};
|
||||
this.loading_latest = 0;
|
||||
this.loading_latest_scheduled = 0;
|
||||
this.recent_reactions = [];
|
||||
this.private_closed = {};
|
||||
tfrpc.rpc.getBroadcasts().then((b) => {
|
||||
self.broadcasts = b || [];
|
||||
});
|
||||
@@ -43,10 +60,22 @@ class TfElement extends LitElement {
|
||||
tfrpc.rpc.getHash().then((hash) => self.set_hash(hash));
|
||||
tfrpc.register(function hashChanged(hash) {
|
||||
self.set_hash(hash);
|
||||
self.reset_progress();
|
||||
});
|
||||
tfrpc.register(async function notifyNewMessage(id) {
|
||||
await self.fetch_new_message(id);
|
||||
});
|
||||
tfrpc.register(async function notifyNewBlob(id) {
|
||||
window.dispatchEvent(
|
||||
new CustomEvent('blob-stored', {
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
detail: {
|
||||
id: id,
|
||||
},
|
||||
})
|
||||
);
|
||||
});
|
||||
tfrpc.register(function set(name, value) {
|
||||
if (name === 'broadcasts') {
|
||||
self.broadcasts = value;
|
||||
@@ -62,98 +91,217 @@ class TfElement extends LitElement {
|
||||
async initial_load() {
|
||||
let whoami = await tfrpc.rpc.getActiveIdentity();
|
||||
let ids = (await tfrpc.rpc.getIdentities()) || [];
|
||||
this.is_administrator = await tfrpc.rpc.isAdministrator();
|
||||
this.stay_connected =
|
||||
this.is_administrator &&
|
||||
(await tfrpc.rpc.globalSettingsGet('stay_connected'));
|
||||
this.url = await tfrpc.rpc.url();
|
||||
this.whoami = whoami ?? (ids.length ? ids[0] : undefined);
|
||||
this.guest = !this.whoami?.length;
|
||||
this.ids = ids;
|
||||
let private_closed =
|
||||
(await tfrpc.rpc.databaseGet('private_closed')) ?? '{}';
|
||||
this.private_closed = JSON.parse(private_closed);
|
||||
await this.load_channels();
|
||||
}
|
||||
|
||||
async close_private_chat(event) {
|
||||
let update = {};
|
||||
update[event.detail.key] = true;
|
||||
this.private_closed = Object.assign(update, this.private_closed);
|
||||
await tfrpc.rpc.databaseSet(
|
||||
'private_closed',
|
||||
JSON.stringify(this.private_closed)
|
||||
);
|
||||
}
|
||||
|
||||
async load_channels() {
|
||||
let channels = await tfrpc.rpc.query(
|
||||
`
|
||||
SELECT
|
||||
content ->> 'channel' AS channel,
|
||||
content ->> 'subscribed' AS subscribed
|
||||
FROM
|
||||
messages
|
||||
WHERE
|
||||
author = ? AND
|
||||
content ->> 'type' = 'channel'
|
||||
ORDER BY sequence
|
||||
`,
|
||||
[this.whoami]
|
||||
);
|
||||
let channel_map = {};
|
||||
for (let row of channels) {
|
||||
if (row.subscribed) {
|
||||
channel_map[row.channel] = true;
|
||||
} else {
|
||||
delete channel_map[row.channel];
|
||||
}
|
||||
}
|
||||
this.channels = Object.keys(channel_map).sort();
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this._keydown = this.keydown.bind(this);
|
||||
window.addEventListener('keydown', this._keydown);
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
window.removeEventListener('keydown', this._keydown);
|
||||
}
|
||||
|
||||
keydown(event) {
|
||||
if (event.altKey && event.key == 'ArrowUp') {
|
||||
this.next_channel(-1);
|
||||
event.preventDefault();
|
||||
} else if (event.altKey && event.key == 'ArrowDown') {
|
||||
this.next_channel(1);
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
visible_private() {
|
||||
if (!this.grouped_private_messages || !this.private_closed) {
|
||||
return [];
|
||||
}
|
||||
let self = this;
|
||||
return Object.fromEntries(
|
||||
Object.entries(this.grouped_private_messages).filter(([key, value]) => {
|
||||
let channel = '🔐' + [...new Set(JSON.parse(key))].sort().join(',');
|
||||
let grouped_latest = Math.max(...value.map((x) => x.rowid));
|
||||
return (
|
||||
!self.private_closed[key] ||
|
||||
self.channels_unread[channel] === undefined ||
|
||||
grouped_latest > self.channels_unread[channel]
|
||||
);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
next_channel(delta) {
|
||||
let channel_names = [
|
||||
'',
|
||||
'@',
|
||||
'👍',
|
||||
...Object.keys(this.visible_private())
|
||||
.sort()
|
||||
.map((x) => '🔐' + JSON.parse(x).join(',')),
|
||||
...this.channels.map((x) => '#' + x),
|
||||
];
|
||||
let index = channel_names.indexOf(this.hash.substring(1));
|
||||
index = index != -1 ? index + delta : 0;
|
||||
tfrpc.rpc.setHash(
|
||||
'#' +
|
||||
encodeURIComponent(
|
||||
channel_names[(index + channel_names.length) % channel_names.length]
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
set_hash(hash) {
|
||||
this.hash = hash || '#';
|
||||
this.hash = decodeURIComponent(hash || '#');
|
||||
if (this.hash.startsWith('#q=')) {
|
||||
this.tab = 'search';
|
||||
} else if (this.hash === '#connections') {
|
||||
this.tab = 'connections';
|
||||
} else if (this.hash === '#mentions') {
|
||||
this.tab = 'mentions';
|
||||
} else if (this.hash.startsWith('#sql=')) {
|
||||
this.tab = 'query';
|
||||
} else {
|
||||
this.tab = 'news';
|
||||
}
|
||||
}
|
||||
|
||||
async fetch_about(ids, users) {
|
||||
const k_cache_version = 1;
|
||||
async fetch_about(following, users) {
|
||||
this.loading_about++;
|
||||
let ids = Object.keys(following).sort();
|
||||
const k_cache_version = 3;
|
||||
let cache = await tfrpc.rpc.databaseGet('about');
|
||||
let original_cache = cache;
|
||||
cache = cache ? JSON.parse(cache) : {};
|
||||
if (cache.version !== k_cache_version) {
|
||||
cache = {
|
||||
version: k_cache_version,
|
||||
about: {},
|
||||
last_row_id: 0,
|
||||
};
|
||||
}
|
||||
let max_row_id = (
|
||||
await tfrpc.rpc.query(
|
||||
`
|
||||
SELECT MAX(rowid) AS max_row_id FROM messages
|
||||
`,
|
||||
[]
|
||||
)
|
||||
)[0].max_row_id;
|
||||
|
||||
let ids_out_of_date = ids.filter(
|
||||
(x) =>
|
||||
(users[x]?.seq && !cache.about[x]?.seq) ||
|
||||
(users[x]?.seq && users[x]?.seq > cache.about[x].seq)
|
||||
);
|
||||
|
||||
for (let id of Object.keys(cache.about)) {
|
||||
if (ids.indexOf(id) == -1) {
|
||||
delete cache.about[id];
|
||||
} else {
|
||||
users[id] = Object.assign(cache.about[id], users[id] || {});
|
||||
}
|
||||
}
|
||||
|
||||
let abouts = await tfrpc.rpc.query(
|
||||
`
|
||||
SELECT
|
||||
messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
|
||||
FROM
|
||||
messages,
|
||||
json_each(?1) AS following
|
||||
WHERE
|
||||
messages.author = following.value AND
|
||||
messages.rowid > ?3 AND
|
||||
messages.rowid <= ?4 AND
|
||||
json_extract(messages.content, '$.type') = 'about'
|
||||
UNION
|
||||
SELECT
|
||||
messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
|
||||
FROM
|
||||
messages,
|
||||
json_each(?2) AS following
|
||||
WHERE
|
||||
messages.author = following.value AND
|
||||
messages.rowid <= ?4 AND
|
||||
json_extract(messages.content, '$.type') = 'about'
|
||||
ORDER BY messages.author, messages.sequence
|
||||
`,
|
||||
[
|
||||
JSON.stringify(ids.filter((id) => cache.about[id])),
|
||||
JSON.stringify(ids.filter((id) => !cache.about[id])),
|
||||
cache.last_row_id,
|
||||
max_row_id,
|
||||
]
|
||||
console.log(
|
||||
'loading about for',
|
||||
ids.length,
|
||||
'accounts',
|
||||
ids_out_of_date.length,
|
||||
'out of date'
|
||||
);
|
||||
for (let about of abouts) {
|
||||
let content = JSON.parse(about.content);
|
||||
if (content.about === about.author) {
|
||||
delete content.type;
|
||||
delete content.about;
|
||||
cache.about[about.author] = Object.assign(
|
||||
cache.about[about.author] || {},
|
||||
content
|
||||
if (ids_out_of_date.length) {
|
||||
try {
|
||||
let rows = await tfrpc.rpc.query(
|
||||
`
|
||||
SELECT all_abouts.author, json(json_group_object(all_abouts.key, all_abouts.value)) AS about
|
||||
FROM (
|
||||
SELECT
|
||||
messages.author,
|
||||
fields.key,
|
||||
RANK() OVER (PARTITION BY messages.author, fields.key ORDER BY messages.sequence DESC) AS rank,
|
||||
fields.value
|
||||
FROM messages JOIN json_each(messages.content) AS fields
|
||||
WHERE
|
||||
messages.content ->> '$.type' = 'about' AND
|
||||
messages.content ->> '$.about' = messages.author AND
|
||||
NOT fields.key IN ('about', 'type')) all_abouts
|
||||
JOIN json_each(?) AS following ON all_abouts.author = following.value
|
||||
WHERE rank = 1
|
||||
GROUP BY all_abouts.author
|
||||
`,
|
||||
[JSON.stringify(ids_out_of_date)]
|
||||
);
|
||||
users = users || {};
|
||||
for (let row of rows) {
|
||||
users[row.author] = Object.assign(
|
||||
users[row.author] || {},
|
||||
JSON.parse(row.about)
|
||||
);
|
||||
cache.about[row.author] = Object.assign(
|
||||
{seq: users[row.author].seq},
|
||||
JSON.parse(row.about)
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
}
|
||||
cache.last_row_id = max_row_id;
|
||||
await tfrpc.rpc.databaseSet('about', JSON.stringify(cache));
|
||||
users = users || {};
|
||||
for (let id of Object.keys(cache.about)) {
|
||||
users[id] = Object.assign(users[id] || {}, cache.about[id]);
|
||||
|
||||
for (let id of ids_out_of_date) {
|
||||
if (!cache.about[id]?.seq) {
|
||||
cache.about[id] = Object.assign(cache.about[id] ?? {}, {
|
||||
seq: users[id]?.seq ?? 0,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
this.loading_about--;
|
||||
|
||||
let new_cache = JSON.stringify(cache);
|
||||
if (new_cache != original_cache) {
|
||||
let start_time = new Date();
|
||||
tfrpc.rpc.databaseSet('about', new_cache).then(function () {
|
||||
console.log('saving about took', (new Date() - start_time) / 1000);
|
||||
});
|
||||
}
|
||||
|
||||
return Object.assign({}, users);
|
||||
}
|
||||
|
||||
@@ -167,10 +315,15 @@ class TfElement extends LitElement {
|
||||
`,
|
||||
[JSON.stringify(this.following), id]
|
||||
);
|
||||
if (messages && messages.length) {
|
||||
this.unread = [...this.unread, ...messages];
|
||||
this.unread = this.unread.slice(this.unread.length - 1024);
|
||||
for (let message of messages) {
|
||||
if (
|
||||
message.author == this.whoami &&
|
||||
JSON.parse(message.content)?.type == 'channel'
|
||||
) {
|
||||
this.load_channels();
|
||||
}
|
||||
}
|
||||
this.schedule_load_latest();
|
||||
}
|
||||
|
||||
async _handle_whoami_changed(event) {
|
||||
@@ -185,70 +338,353 @@ class TfElement extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
async create_identity() {
|
||||
if (confirm('Are you sure you want to create a new identity?')) {
|
||||
await tfrpc.rpc.createIdentity();
|
||||
this.ids = (await tfrpc.rpc.getIdentities()) || [];
|
||||
if (this.ids && !this.whoami) {
|
||||
this.whoami = this.ids[0];
|
||||
async get_latest_private(following) {
|
||||
const k_version = 1;
|
||||
// { "version": 1, "range": [1234, 5678], messages: [ "%1.sha256", "%2.sha256", ... ], latest: rowid }
|
||||
let cache = JSON.parse(
|
||||
(await tfrpc.rpc.databaseGet(`private:${this.whoami}`)) ?? '{}'
|
||||
);
|
||||
if (cache.version !== k_version) {
|
||||
cache = {
|
||||
version: k_version,
|
||||
messages: [],
|
||||
range: [],
|
||||
};
|
||||
}
|
||||
let latest = (
|
||||
await tfrpc.rpc.query('SELECT MAX(rowid) AS latest FROM messages')
|
||||
)[0].latest;
|
||||
let ranges = [];
|
||||
const k_chunk_size = 512;
|
||||
if (cache.range.length) {
|
||||
for (let i = cache.range[1]; i < latest; i += k_chunk_size) {
|
||||
ranges.push([i, Math.min(i + k_chunk_size, latest), true]);
|
||||
}
|
||||
for (let i = cache.range[0]; i >= 0; i -= k_chunk_size) {
|
||||
ranges.push([Math.max(i - k_chunk_size, 0), i, false]);
|
||||
}
|
||||
} else {
|
||||
for (let i = 0; i < latest; i += k_chunk_size) {
|
||||
ranges.push([i, Math.min(i + k_chunk_size, latest), true]);
|
||||
}
|
||||
}
|
||||
for (let range of ranges) {
|
||||
let messages = await tfrpc.rpc.query(
|
||||
`
|
||||
SELECT messages.rowid, messages.id, json(content) AS content
|
||||
FROM messages
|
||||
WHERE
|
||||
messages.rowid > ?1 AND
|
||||
messages.rowid <= ?2 AND
|
||||
json(messages.content) LIKE '"%'
|
||||
ORDER BY messages.rowid DESC
|
||||
`,
|
||||
[range[0], range[1]]
|
||||
);
|
||||
messages = (await this.decrypt(messages)).filter((x) => x.decrypted);
|
||||
if (messages.length) {
|
||||
cache.latest = Math.max(
|
||||
cache.latest ?? 0,
|
||||
...messages.map((x) => x.rowid)
|
||||
);
|
||||
if (range[2]) {
|
||||
cache.messages = [...cache.messages, ...messages.map((x) => x.id)];
|
||||
} else {
|
||||
cache.messages = [...messages.map((x) => x.id), ...cache.messages];
|
||||
}
|
||||
}
|
||||
cache.range[0] = Math.min(cache.range[0] ?? range[0], range[0]);
|
||||
cache.range[1] = Math.max(cache.range[1] ?? range[1], range[1]);
|
||||
await tfrpc.rpc.databaseSet(
|
||||
`private:${this.whoami}`,
|
||||
JSON.stringify(cache)
|
||||
);
|
||||
}
|
||||
return [cache.latest, cache.messages];
|
||||
}
|
||||
|
||||
async query_timed(sql, args) {
|
||||
let start = new Date();
|
||||
let result = await tfrpc.rpc.query(sql, args);
|
||||
let end = new Date();
|
||||
console.log((end - start) / 1000, sql.replaceAll(/\s+/g, ' ').trim());
|
||||
return result;
|
||||
}
|
||||
|
||||
async group_private_messages(messages) {
|
||||
let groups = {};
|
||||
let result = await this.decrypt(
|
||||
await tfrpc.rpc.query(
|
||||
`
|
||||
SELECT messages.rowid, messages.id, author, timestamp, json(content) AS content
|
||||
FROM messages
|
||||
JOIN json_each(?) AS ids
|
||||
WHERE messages.id = ids.value
|
||||
ORDER BY timestamp DESC
|
||||
`,
|
||||
[JSON.stringify(messages)]
|
||||
)
|
||||
);
|
||||
for (let message of result) {
|
||||
let key = JSON.stringify(
|
||||
[
|
||||
...new Set(
|
||||
message?.decrypted?.recps?.filter((x) => x != this.whoami)
|
||||
),
|
||||
].sort() ?? []
|
||||
);
|
||||
if (!groups[key]) {
|
||||
groups[key] = [];
|
||||
}
|
||||
groups[key].push(message);
|
||||
}
|
||||
return groups;
|
||||
}
|
||||
|
||||
async load_channels_latest(following) {
|
||||
let start_time = new Date();
|
||||
let latest_private = this.get_latest_private(following);
|
||||
const k_args = [
|
||||
JSON.stringify(this.channels),
|
||||
JSON.stringify(following),
|
||||
'"' + this.whoami.replace('"', '""') + '"',
|
||||
this.whoami,
|
||||
];
|
||||
let channels = (
|
||||
await Promise.all([
|
||||
this.query_timed(
|
||||
`
|
||||
SELECT channels.value AS channel, MAX(messages.rowid) AS rowid FROM messages
|
||||
JOIN json_each(?1) AS channels ON messages.content ->> 'channel' = channels.value
|
||||
JOIN json_each(?2) AS following ON messages.author = following.value
|
||||
WHERE
|
||||
messages.content ->> 'type' = 'post' AND
|
||||
messages.content ->> 'root' IS NULL AND
|
||||
messages.author != ?4
|
||||
GROUP by channel
|
||||
`,
|
||||
k_args
|
||||
),
|
||||
this.query_timed(
|
||||
`
|
||||
SELECT channels.value AS channel, MAX(messages.rowid) AS rowid FROM messages
|
||||
JOIN messages_refs ON messages.id = messages_refs.message
|
||||
JOIN json_each(?1) AS channels ON messages_refs.ref = '#' || channels.value
|
||||
JOIN json_each(?2) AS following ON messages.author = following.value
|
||||
WHERE
|
||||
messages.content ->> 'type' = 'post' AND
|
||||
messages.content ->> 'root' IS NULL AND
|
||||
messages.author != ?4
|
||||
GROUP by channel
|
||||
`,
|
||||
k_args
|
||||
),
|
||||
this.query_timed(
|
||||
`
|
||||
SELECT '' AS channel, MAX(messages.rowid) AS rowid FROM messages
|
||||
JOIN json_each(?2) AS following ON messages.author = following.value
|
||||
WHERE
|
||||
messages.content ->> 'type' = 'post' AND
|
||||
messages.content ->> 'root' IS NULL AND
|
||||
messages.author != ?4
|
||||
`,
|
||||
k_args
|
||||
),
|
||||
this.query_timed(
|
||||
`
|
||||
SELECT '@' AS channel, MAX(messages.rowid) AS rowid FROM messages_fts(?3)
|
||||
JOIN messages ON messages.rowid = messages_fts.rowid
|
||||
JOIN json_each(?2) AS following ON messages.author = following.value
|
||||
WHERE messages.author != ?4
|
||||
`,
|
||||
k_args
|
||||
),
|
||||
])
|
||||
).flat();
|
||||
let latest = {};
|
||||
for (let row of channels) {
|
||||
if (!latest[row.channel]) {
|
||||
latest[row.channel] = row.rowid;
|
||||
} else {
|
||||
latest[row.channel] = Math.max(row.rowid, latest[row.channel]);
|
||||
}
|
||||
}
|
||||
this.channels_latest = latest;
|
||||
console.log('channels took', (new Date() - start_time) / 1000.0);
|
||||
let self = this;
|
||||
start_time = new Date();
|
||||
latest_private.then(async function (latest) {
|
||||
self.channels_latest = Object.assign({}, self.channels_latest, {
|
||||
'🔐': latest[0],
|
||||
});
|
||||
console.log('private took', (new Date() - start_time) / 1000.0);
|
||||
self.private_messages = latest[1];
|
||||
self.grouped_private_messages = await self.group_private_messages(
|
||||
latest[1]
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
_schedule_load_latest_timer() {
|
||||
--this.loading_latest_scheduled;
|
||||
this.schedule_load_latest();
|
||||
}
|
||||
|
||||
reset_progress() {
|
||||
if (this.progress === undefined) {
|
||||
this._progress_start = new Date();
|
||||
requestAnimationFrame(this.update_progress.bind(this));
|
||||
}
|
||||
}
|
||||
|
||||
async load_recent_tags() {
|
||||
let start = new Date();
|
||||
this.tags = await tfrpc.rpc.query(
|
||||
update_progress() {
|
||||
if (
|
||||
!this.loading_latest &&
|
||||
!this.loading_latest_scheduled &&
|
||||
!this.shadowRoot.getElementById('tf-tab-news')?.is_loading()
|
||||
) {
|
||||
this.progress = undefined;
|
||||
return;
|
||||
}
|
||||
this.progress = (new Date() - this._progress_start).valueOf();
|
||||
requestAnimationFrame(this.update_progress.bind(this));
|
||||
}
|
||||
|
||||
schedule_load_latest() {
|
||||
this.reset_progress();
|
||||
if (!this.loading_latest) {
|
||||
this.shadowRoot.getElementById('tf-tab-news')?.load_latest();
|
||||
this.load();
|
||||
} else if (!this.loading_latest_scheduled) {
|
||||
this.loading_latest_scheduled++;
|
||||
setTimeout(this._schedule_load_latest_timer.bind(this), 5000);
|
||||
}
|
||||
}
|
||||
|
||||
async fetch_user_info(users) {
|
||||
let info = await tfrpc.rpc.query(
|
||||
`
|
||||
WITH
|
||||
recent AS (SELECT id, json(content) AS content FROM messages
|
||||
WHERE messages.timestamp > ? AND json_extract(content, '$.type') = 'post'
|
||||
ORDER BY timestamp DESC LIMIT 1024),
|
||||
recent_channels AS (SELECT recent.id, '#' || json_extract(content, '$.channel') AS tag
|
||||
FROM recent
|
||||
WHERE json_extract(content, '$.channel') IS NOT NULL),
|
||||
recent_mentions AS (SELECT recent.id, json_extract(mention.value, '$.link') AS tag
|
||||
FROM recent, json_each(recent.content, '$.mentions') AS mention
|
||||
WHERE json_valid(mention.value) AND tag LIKE '#%'),
|
||||
combined AS (SELECT id, tag FROM recent_channels UNION ALL SELECT id, tag FROM recent_mentions),
|
||||
by_message AS (SELECT DISTINCT id, tag FROM combined)
|
||||
SELECT tag, COUNT(*) AS count FROM by_message GROUP BY tag ORDER BY count DESC LIMIT 10
|
||||
`,
|
||||
[new Date() - 7 * 24 * 60 * 60 * 1000]
|
||||
SELECT messages_stats.author, messages_stats.max_sequence, messages_stats.max_timestamp AS max_ts FROM messages_stats
|
||||
JOIN json_each(?) AS following
|
||||
ON messages_stats.author = following.value
|
||||
`,
|
||||
[JSON.stringify(Object.keys(users))]
|
||||
);
|
||||
console.log('tags took', (new Date() - start) / 1000.0, 'seconds');
|
||||
for (let row of info) {
|
||||
users[row.author] = Object.assign(users[row.author], {
|
||||
seq: row.max_sequence,
|
||||
ts: row.max_ts,
|
||||
});
|
||||
}
|
||||
return users;
|
||||
}
|
||||
|
||||
async load_recent_reactions() {
|
||||
this.recent_reactions = (
|
||||
await tfrpc.rpc.query(
|
||||
`
|
||||
SELECT DISTINCT content ->> '$.vote.expression' AS value
|
||||
FROM messages
|
||||
WHERE author = ? AND
|
||||
content ->> '$.type' = 'vote'
|
||||
ORDER BY timestamp DESC LIMIT 10
|
||||
`,
|
||||
[this.whoami]
|
||||
)
|
||||
).map((x) => x.value);
|
||||
}
|
||||
|
||||
async load() {
|
||||
let whoami = this.whoami;
|
||||
let tags = this.load_recent_tags();
|
||||
let following = await tfrpc.rpc.following([whoami], 2);
|
||||
let users = {};
|
||||
let by_count = [];
|
||||
for (let [id, v] of Object.entries(following)) {
|
||||
users[id] = {
|
||||
following: v.of,
|
||||
blocking: v.ob,
|
||||
followed: v.if,
|
||||
blocked: v.ib,
|
||||
};
|
||||
by_count.push({count: v.of, id: id});
|
||||
this.loading_latest = true;
|
||||
this.reset_progress();
|
||||
try {
|
||||
let start_time = new Date();
|
||||
let whoami = this.whoami;
|
||||
let following = await tfrpc.rpc.following([whoami], 2);
|
||||
let old_users = this.users ?? {};
|
||||
let users = {};
|
||||
let by_count = [];
|
||||
for (let [id, v] of Object.entries(following)) {
|
||||
users[id] = Object.assign(
|
||||
{
|
||||
following: v.of,
|
||||
blocking: v.ob,
|
||||
followed: v.if,
|
||||
blocked: v.ib,
|
||||
follow_depth: following[id]?.d,
|
||||
},
|
||||
old_users[id]
|
||||
);
|
||||
by_count.push({count: v.of, id: id});
|
||||
}
|
||||
let reactions = this.load_recent_reactions();
|
||||
this.load_channels_latest(Object.keys(following));
|
||||
this.channels_unread = JSON.parse(
|
||||
(await tfrpc.rpc.databaseGet('unread')) ?? '{}'
|
||||
);
|
||||
this.following = Object.keys(following);
|
||||
let about_start_time = new Date();
|
||||
start_time = new Date();
|
||||
users = await this.fetch_user_info(users);
|
||||
console.log(
|
||||
'user info took',
|
||||
(new Date() - start_time) / 1000.0,
|
||||
'seconds'
|
||||
);
|
||||
this.users = users;
|
||||
|
||||
let self = this;
|
||||
this.fetch_about(following, users).then(function (result) {
|
||||
self.users = result;
|
||||
console.log(
|
||||
'about took',
|
||||
(new Date() - about_start_time) / 1000.0,
|
||||
'seconds for',
|
||||
Object.keys(users).length,
|
||||
'users'
|
||||
);
|
||||
});
|
||||
console.log(
|
||||
`load finished ${whoami} => ${this.whoami} in ${(new Date() - start_time) / 1000}`
|
||||
);
|
||||
await reactions;
|
||||
this.whoami = whoami;
|
||||
this.loaded = whoami;
|
||||
} finally {
|
||||
this.loading_latest = false;
|
||||
}
|
||||
console.log(by_count.sort((x, y) => y.count - x.count).slice(0, 20));
|
||||
let start_time = new Date();
|
||||
users = await this.fetch_about(Object.keys(following).sort(), users);
|
||||
console.log(
|
||||
'about took',
|
||||
(new Date() - start_time) / 1000.0,
|
||||
'seconds for',
|
||||
Object.keys(users).length,
|
||||
'users'
|
||||
}
|
||||
|
||||
channel_set_unread(event) {
|
||||
this.channels_unread[event.detail.channel ?? ''] = event.detail.unread;
|
||||
this.channels_unread = Object.assign({}, this.channels_unread);
|
||||
tfrpc.rpc.databaseSet('unread', JSON.stringify(this.channels_unread));
|
||||
}
|
||||
|
||||
async decrypt(messages) {
|
||||
let whoami = this.whoami;
|
||||
return Promise.all(
|
||||
messages.map(async function (message) {
|
||||
let content;
|
||||
try {
|
||||
content = JSON.parse(message?.content);
|
||||
} catch {}
|
||||
if (typeof content === 'string') {
|
||||
let decrypted;
|
||||
try {
|
||||
decrypted = await tfrpc.rpc.try_decrypt(whoami, content);
|
||||
} catch {}
|
||||
if (decrypted) {
|
||||
try {
|
||||
message.decrypted = JSON.parse(decrypted);
|
||||
} catch {
|
||||
message.decrypted = decrypted;
|
||||
}
|
||||
}
|
||||
}
|
||||
return message;
|
||||
})
|
||||
);
|
||||
this.following = Object.keys(following);
|
||||
this.users = users;
|
||||
await tags;
|
||||
console.log(`load finished ${whoami} => ${this.whoami}`);
|
||||
this.whoami = whoami;
|
||||
this.loaded = whoami;
|
||||
}
|
||||
|
||||
render_tab() {
|
||||
@@ -262,9 +698,22 @@ class TfElement extends LitElement {
|
||||
whoami=${this.whoami}
|
||||
.users=${this.users}
|
||||
hash=${this.hash}
|
||||
.unread=${this.unread}
|
||||
@refresh=${() => (this.unread = [])}
|
||||
?loading=${this.loading}
|
||||
?loading=${this.loading || this.loading_about != 0}
|
||||
.channels=${this.channels}
|
||||
.channels_latest=${this.channels_latest}
|
||||
.channels_unread=${this.channels_unread}
|
||||
@channelsetunread=${this.channel_set_unread}
|
||||
@refresh=${this.refresh}
|
||||
@toggle_stay_connected=${this.toggle_stay_connected}
|
||||
@loadmessages=${this.reset_progress}
|
||||
@closeprivatechat=${this.close_private_chat}
|
||||
.connections=${this.connections}
|
||||
.private_messages=${this.private_messages}
|
||||
.visible_private_messages=${this.visible_private()}
|
||||
.grouped_private_messages=${this.grouped_private_messages}
|
||||
.recent_reactions=${this.recent_reactions}
|
||||
?is_administrator=${this.is_administrator}
|
||||
?stay_connected=${this.stay_connected}
|
||||
></tf-tab-news>
|
||||
`;
|
||||
} else if (this.tab === 'connections') {
|
||||
@@ -275,14 +724,6 @@ class TfElement extends LitElement {
|
||||
.broadcasts=${this.broadcasts}
|
||||
></tf-tab-connections>
|
||||
`;
|
||||
} else if (this.tab === 'mentions') {
|
||||
return html`
|
||||
<tf-tab-mentions
|
||||
.following=${this.following}
|
||||
whoami=${this.whoami}
|
||||
.users="${this.users}}"
|
||||
></tf-tab-mentions>
|
||||
`;
|
||||
} else if (this.tab === 'search') {
|
||||
return html`
|
||||
<tf-tab-search
|
||||
@@ -294,33 +735,46 @@ class TfElement extends LitElement {
|
||||
: null}
|
||||
></tf-tab-search>
|
||||
`;
|
||||
} else if (this.tab === 'query') {
|
||||
return html`
|
||||
<tf-tab-query
|
||||
.following=${this.following}
|
||||
whoami=${this.whoami}
|
||||
.users=${this.users}
|
||||
query=${this.hash?.startsWith('#sql=')
|
||||
? decodeURIComponent(this.hash.substring(5))
|
||||
: null}
|
||||
></tf-tab-query>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
async set_tab(tab) {
|
||||
this.tab = tab;
|
||||
if (tab === 'news') {
|
||||
this.schedule_load_latest();
|
||||
await tfrpc.rpc.setHash('#');
|
||||
} else if (tab === 'connections') {
|
||||
await tfrpc.rpc.setHash('#connections');
|
||||
} else if (tab === 'mentions') {
|
||||
await tfrpc.rpc.setHash('#mentions');
|
||||
} else if (tab === 'query') {
|
||||
await tfrpc.rpc.setHash('#sql=');
|
||||
}
|
||||
}
|
||||
|
||||
refresh() {
|
||||
tfrpc.rpc.sync();
|
||||
}
|
||||
|
||||
async toggle_stay_connected() {
|
||||
let stay_connected = await tfrpc.rpc.globalSettingsGet('stay_connected');
|
||||
let new_stay_connected = !this.stay_connected;
|
||||
try {
|
||||
if (new_stay_connected != stay_connected) {
|
||||
await tfrpc.rpc.globalSettingsSet('stay_connected', new_stay_connected);
|
||||
}
|
||||
} finally {
|
||||
this.stay_connected = await tfrpc.rpc.globalSettingsGet('stay_connected');
|
||||
}
|
||||
}
|
||||
|
||||
async pick_color() {
|
||||
let input = document.createElement('input');
|
||||
input.type = 'color';
|
||||
input.value = (await tfrpc.rpc.localStorageGet('color')) ?? '#ff0000';
|
||||
input.addEventListener('change', async function () {
|
||||
await tfrpc.rpc.localStorageSet('color', input.value);
|
||||
window.location.reload();
|
||||
});
|
||||
input.click();
|
||||
}
|
||||
|
||||
render() {
|
||||
let self = this;
|
||||
|
||||
@@ -334,13 +788,33 @@ class TfElement extends LitElement {
|
||||
const k_tabs = {
|
||||
'📰': 'news',
|
||||
'📡': 'connections',
|
||||
'@': 'mentions',
|
||||
'🔍': 'search',
|
||||
'👩💻': 'query',
|
||||
};
|
||||
|
||||
let tabs = html`
|
||||
<div class="w3-bar w3-theme-l1">
|
||||
<div
|
||||
class="w3-bar w3-theme-l1"
|
||||
style="position: static; top: 0; z-index: 10"
|
||||
>
|
||||
${this.is_administrator
|
||||
? html`
|
||||
<button
|
||||
class=${'w3-bar-item w3-button w3-circle w3-ripple' +
|
||||
(this.connections?.some((x) => x.flags.one_shot)
|
||||
? ' w3-spin'
|
||||
: '')}
|
||||
@click=${this.refresh}
|
||||
>
|
||||
↻
|
||||
</button>
|
||||
<button
|
||||
class="w3-bar-item w3-button w3-ripple"
|
||||
@click=${this.toggle_stay_connected}
|
||||
>
|
||||
${this.stay_connected ? '🔗' : '⛓️💥'}
|
||||
</button>
|
||||
`
|
||||
: undefined}
|
||||
${Object.entries(k_tabs).map(
|
||||
([k, v]) => html`
|
||||
<button
|
||||
@@ -357,28 +831,57 @@ class TfElement extends LitElement {
|
||||
</button>
|
||||
`
|
||||
)}
|
||||
<button
|
||||
class="w3-bar-item w3-button w3-right"
|
||||
@click=${this.pick_color}
|
||||
>
|
||||
🎨<span class="w3-hide-small">Color</span>
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
let contents = !this.loaded
|
||||
? this.loading
|
||||
? html`<div
|
||||
class="w3-panel w3-theme-l5 w3-card-4 w3-padding-large w3-round-xlarge"
|
||||
let contents = this.guest
|
||||
? html`<div
|
||||
class="w3-display-middle w3-panel w3-theme-l5 w3-card-4 w3-padding-large w3-round-xlarge w3-xlarge w3-container"
|
||||
>
|
||||
<p>⚠️🦀 Must be logged in to Tilde Friends to scuttle here. 🦀⚠️</p>
|
||||
<footer class="w3-center">
|
||||
<a
|
||||
class="w3-button w3-theme-d1"
|
||||
href=${`/login?return=${encodeURIComponent(this.url)}`}
|
||||
>Login</a
|
||||
>
|
||||
Loading...
|
||||
</footer>
|
||||
</div>`
|
||||
: !this.loaded || this.loading
|
||||
? html`<div
|
||||
class="w3-display-middle w3-panel w3-theme-l5 w3-card-4 w3-padding-large w3-round-xlarge w3-xlarge"
|
||||
>
|
||||
<span class="w3-spin" style="display: inline-block">🦀</span>
|
||||
Loading...
|
||||
</div>`
|
||||
: this.render_tab();
|
||||
let progress =
|
||||
this.progress !== undefined
|
||||
? html`
|
||||
<div style="position: absolute; width: 100%" id="progress">
|
||||
<div
|
||||
class="w3-theme-l3"
|
||||
style=${`height: 4px; position: absolute; right: ${Math.cos(this.progress / 250) > 0 ? 'auto' : '0'}; width: ${50 * Math.sin(this.progress / 250) + 50}%`}
|
||||
></div>
|
||||
</div>
|
||||
${this.render_tab()}`
|
||||
: html`<div>Select or create an identity.</div>`
|
||||
: this.render_tab();
|
||||
`
|
||||
: undefined;
|
||||
return html`
|
||||
<style>
|
||||
${generate_theme()}
|
||||
</style>
|
||||
<div
|
||||
style="width: 100vw; min-height: 100vh; height: 100%"
|
||||
style="width: 100vw; min-height: 100vh; height: 100vh; display: flex; flex-direction: column"
|
||||
class="w3-theme-dark"
|
||||
>
|
||||
${tabs}
|
||||
<div style="padding: 8px">
|
||||
${this.tags.map(
|
||||
(x) => html`<tf-tag tag=${x.tag} count=${x.count}></tf-tag>`
|
||||
)}
|
||||
${progress}
|
||||
<div style="flex: 0 0">${tabs}</div>
|
||||
<div style="flex: 1 1; overflow: auto; contain: layout">
|
||||
${contents}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import {LitElement, html, unsafeHTML, live} from './lit-all.min.js';
|
||||
import * as tfutils from './tf-utils.js';
|
||||
import * as tfrpc from '/static/tfrpc.js';
|
||||
import {styles} from './tf-styles.js';
|
||||
import {styles, generate_theme} from './tf-styles.js';
|
||||
import Tribute from './tribute.esm.js';
|
||||
|
||||
class TfComposeElement extends LitElement {
|
||||
@@ -14,6 +14,9 @@ class TfComposeElement extends LitElement {
|
||||
apps: {type: Object},
|
||||
drafts: {type: Object},
|
||||
author: {type: String},
|
||||
channel: {type: String},
|
||||
new_thread: {type: Boolean},
|
||||
recipients: {type: Array},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -27,6 +30,7 @@ class TfComposeElement extends LitElement {
|
||||
this.apps = undefined;
|
||||
this.drafts = {};
|
||||
this.author = undefined;
|
||||
this.new_thread = false;
|
||||
}
|
||||
|
||||
process_text(text) {
|
||||
@@ -88,7 +92,9 @@ class TfComposeElement extends LitElement {
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
detail: {
|
||||
id: this.branch,
|
||||
id:
|
||||
this.branch ??
|
||||
(this.recipients ? this.recipients.join(',') : undefined),
|
||||
draft: draft,
|
||||
},
|
||||
})
|
||||
@@ -196,11 +202,26 @@ class TfComposeElement extends LitElement {
|
||||
let message = {
|
||||
type: 'post',
|
||||
text: edit.innerText,
|
||||
channel: this.channel,
|
||||
};
|
||||
if (this.root || this.branch) {
|
||||
message.root = this.root;
|
||||
message.root = this.new_thread ? (this.branch ?? this.root) : this.root;
|
||||
message.branch = this.branch;
|
||||
}
|
||||
let reply = Object.fromEntries(
|
||||
(
|
||||
await tfrpc.rpc.query(
|
||||
`
|
||||
SELECT messages.id, messages.author FROM messages
|
||||
JOIN json_each(?) AS refs ON messages.id = refs.value
|
||||
`,
|
||||
[JSON.stringify([this.root, this.branch])]
|
||||
)
|
||||
).map((row) => [row.id, row.author])
|
||||
);
|
||||
if (Object.keys(reply).length) {
|
||||
message.reply = reply;
|
||||
}
|
||||
if (Object.values(draft.mentions || {}).length) {
|
||||
message.mentions = Object.values(draft.mentions);
|
||||
}
|
||||
@@ -237,10 +258,12 @@ class TfComposeElement extends LitElement {
|
||||
let self = this;
|
||||
let input = document.createElement('input');
|
||||
input.type = 'file';
|
||||
input.onchange = function (event) {
|
||||
input.addEventListener('change', function (event) {
|
||||
input.parentNode.removeChild(input);
|
||||
let file = event.target.files[0];
|
||||
self.add_file(file);
|
||||
};
|
||||
});
|
||||
document.body.appendChild(input);
|
||||
input.click();
|
||||
}
|
||||
|
||||
@@ -271,7 +294,7 @@ class TfComposeElement extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
firstUpdated() {
|
||||
get_values() {
|
||||
let values = Object.entries(this.users).map((x) => ({
|
||||
key: x[1].name ?? x[0],
|
||||
value: x[0],
|
||||
@@ -287,11 +310,15 @@ class TfComposeElement extends LitElement {
|
||||
values
|
||||
);
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
firstUpdated() {
|
||||
let tribute = new Tribute({
|
||||
iframe: this.shadowRoot,
|
||||
collection: [
|
||||
{
|
||||
values: values,
|
||||
values: this.get_values(),
|
||||
selectTemplate: function (item) {
|
||||
return item
|
||||
? `[@${item.original.key}](${item.original.value})`
|
||||
@@ -310,6 +337,7 @@ class TfComposeElement extends LitElement {
|
||||
],
|
||||
});
|
||||
tribute.attach(this.renderRoot.getElementById('edit'));
|
||||
this._tribute = tribute;
|
||||
}
|
||||
|
||||
updated() {
|
||||
@@ -320,6 +348,7 @@ class TfComposeElement extends LitElement {
|
||||
preview.innerHTML = this.process_text(edit.innerText);
|
||||
this.last_updated_text = edit.innerText;
|
||||
}
|
||||
this._tribute.collection[0].values = this.get_values();
|
||||
let encrypt = this.renderRoot.getElementById('encrypt_to');
|
||||
if (encrypt) {
|
||||
let tribute = new Tribute({
|
||||
@@ -339,7 +368,7 @@ class TfComposeElement extends LitElement {
|
||||
remove_mention(id) {
|
||||
let draft = this.get_draft();
|
||||
delete draft.mentions[id];
|
||||
setTimeout(() => this.notify(), 0);
|
||||
setTimeout(() => this.notify(draft), 0);
|
||||
}
|
||||
|
||||
render_mention(mention) {
|
||||
@@ -426,12 +455,15 @@ class TfComposeElement extends LitElement {
|
||||
self.apps = await tfrpc.rpc.apps();
|
||||
}
|
||||
if (!this.apps) {
|
||||
return html`<button class="w3-button w3-theme-d1" @click=${attach_app}>
|
||||
return html`<button
|
||||
class="w3-button w3-bar-item w3-theme-d1"
|
||||
@click=${attach_app}
|
||||
>
|
||||
Attach App
|
||||
</button>`;
|
||||
} else {
|
||||
return html`<button
|
||||
class="w3-button w3-theme-d1"
|
||||
class="w3-button w3-bar-item w3-theme-d1"
|
||||
@click=${() => (this.apps = null)}
|
||||
>
|
||||
Discard App
|
||||
@@ -452,23 +484,38 @@ class TfComposeElement extends LitElement {
|
||||
if (draft.content_warning !== undefined) {
|
||||
return html`
|
||||
<div class="w3-container w3-padding">
|
||||
<p>
|
||||
<input type="checkbox" class="w3-check w3-theme-d1" id="cw" @change=${() => self.set_content_warning(undefined)} checked="checked"></input>
|
||||
<label for="cw">CW</label>
|
||||
</p>
|
||||
<input type="text" class="w3-input w3-border w3-theme-d1" id="content_warning" placeholder="Enter a content warning here." @input=${self.input} value=${draft.content_warning}></input>
|
||||
</div>
|
||||
`;
|
||||
} else {
|
||||
}
|
||||
}
|
||||
|
||||
render_new_thread() {
|
||||
let self = this;
|
||||
if (
|
||||
this.root !== undefined &&
|
||||
this.branch !== undefined &&
|
||||
this.root != this.branch
|
||||
) {
|
||||
return html`
|
||||
<input type="checkbox" class="w3-check w3-theme-d1" id="cw" @change=${() => self.set_content_warning('')}></input>
|
||||
<label for="cw">CW</label>
|
||||
<input type="checkbox" class="w3-check w3-theme-d1" id="new_thread" @change=${() => (self.new_thread = !self.new_thread)} ?checked=${self.new_thread}></input>
|
||||
<label for="new_thread">New Thread</label>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
get_draft() {
|
||||
return this.drafts[this.branch || ''] || {};
|
||||
let key =
|
||||
this.branch ||
|
||||
(this.recipients ? this.recipients.join(',') : undefined) ||
|
||||
'';
|
||||
let draft = this.drafts[key] || {};
|
||||
if (this.recipients && !draft.encrypt_to?.length) {
|
||||
draft.encrypt_to = [
|
||||
...new Set(this.recipients).union(new Set(draft.encrypt_to ?? [])),
|
||||
];
|
||||
}
|
||||
return draft;
|
||||
}
|
||||
|
||||
update_encrypt(event) {
|
||||
@@ -490,7 +537,7 @@ class TfComposeElement extends LitElement {
|
||||
return html`
|
||||
<div style="display: flex; flex-direction: row; width: 100%">
|
||||
<label for="encrypt_to">🔐 To:</label>
|
||||
<input type="text" id="encrypt_to" style="display: flex; flex: 1 1" @input=${this.update_encrypt}></input>
|
||||
<input type="text" id="encrypt_to" class="w3-input w3-theme-d1 w3-border" style="display: flex; flex: 1 1" @input=${this.update_encrypt}></input>
|
||||
<button class="w3-button w3-theme-d1" @click=${() => this.set_encrypt(undefined)}>🚮</button>
|
||||
</div>
|
||||
<ul>
|
||||
@@ -512,6 +559,31 @@ class TfComposeElement extends LitElement {
|
||||
this.requestUpdate();
|
||||
}
|
||||
|
||||
toggle_menu(event) {
|
||||
event.srcElement.parentNode
|
||||
.querySelector('.w3-dropdown-content')
|
||||
.classList.toggle('w3-show');
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this._click_callback = this.document_click.bind(this);
|
||||
document.body.addEventListener('mouseup', this._click_callback);
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
document.body.removeEventListener('mouseup', this._click_callback);
|
||||
}
|
||||
|
||||
document_click(event) {
|
||||
let content = this.renderRoot.querySelector('.w3-dropdown-content');
|
||||
let target = event.target;
|
||||
if (content && !content.contains(target)) {
|
||||
content.classList.remove('w3-show');
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
let self = this;
|
||||
let draft = self.get_draft();
|
||||
@@ -525,22 +597,38 @@ class TfComposeElement extends LitElement {
|
||||
draft.encrypt_to !== undefined
|
||||
? undefined
|
||||
: html`<button
|
||||
class="w3-button w3-theme-d1"
|
||||
class="w3-button w3-bar-item w3-theme-d1"
|
||||
@click=${() => this.set_encrypt([])}
|
||||
>
|
||||
🔐
|
||||
🔐 Encrypt
|
||||
</button>`;
|
||||
let result = html`
|
||||
<style>
|
||||
${generate_theme()}
|
||||
</style>
|
||||
<style>
|
||||
.w3-input:empty::before {
|
||||
content: attr(placeholder);
|
||||
}
|
||||
.w3-input:empty:focus::before {
|
||||
content: '';
|
||||
}
|
||||
</style>
|
||||
<div
|
||||
class="w3-card-4 w3-theme-d4 w3-padding-small"
|
||||
class="w3-card-4 w3-theme-d4 w3-padding w3-margin-top w3-margin-bottom"
|
||||
style="box-sizing: border-box"
|
||||
>
|
||||
${this.render_encrypt()}
|
||||
<div class="w3-container w3-padding-small">
|
||||
<header class="w3-container">
|
||||
${this.channel !== undefined
|
||||
? html`<p>To #${this.channel}:</p>`
|
||||
: undefined}
|
||||
${this.render_encrypt()}
|
||||
</header>
|
||||
<div class="w3-container" style="padding: 0 0 16px 0">
|
||||
<div class="w3-half">
|
||||
<span
|
||||
class="w3-input w3-theme-d1 w3-border"
|
||||
style="resize: vertical; width: 100%; overflow: hidden; white-space: pre-wrap"
|
||||
style="resize: vertical; width: 100%; white-space: pre-wrap"
|
||||
placeholder="Write a post here."
|
||||
id="edit"
|
||||
@input=${this.input}
|
||||
@@ -549,25 +637,62 @@ class TfComposeElement extends LitElement {
|
||||
.innerText=${live(draft.text ?? '')}
|
||||
></span>
|
||||
</div>
|
||||
<div class="w3-half w3-padding">
|
||||
<div class="w3-half w3-container">
|
||||
${content_warning}
|
||||
<div id="preview"></div>
|
||||
<p id="preview"></p>
|
||||
</div>
|
||||
</div>
|
||||
${Object.values(draft.mentions || {}).map((x) =>
|
||||
self.render_mention(x)
|
||||
)}
|
||||
${this.render_attach_app()} ${this.render_content_warning()}
|
||||
<button class="w3-button w3-theme-d1" id="submit" @click=${this.submit}>
|
||||
Submit
|
||||
</button>
|
||||
<button class="w3-button w3-theme-d1" @click=${this.attach}>
|
||||
Attach
|
||||
</button>
|
||||
${this.render_attach_app_button()} ${encrypt}
|
||||
<button class="w3-button w3-theme-d1" @click=${this.discard}>
|
||||
Discard
|
||||
</button>
|
||||
<footer>
|
||||
${this.render_attach_app()} ${this.render_content_warning()}
|
||||
${this.render_new_thread()}
|
||||
<button
|
||||
class="w3-button w3-theme-d1"
|
||||
id="submit"
|
||||
@click=${this.submit}
|
||||
>
|
||||
Submit
|
||||
</button>
|
||||
<div class="w3-dropdown-click">
|
||||
<button class="w3-button w3-theme-d1" @click=${this.toggle_menu}>
|
||||
⚙️
|
||||
</button>
|
||||
<div class="w3-dropdown-content w3-bar-block">
|
||||
${this.get_draft().content_warning === undefined
|
||||
? html`
|
||||
<button
|
||||
class="w3-button w3-bar-item w3-theme-d1"
|
||||
@click=${() => self.set_content_warning('')}
|
||||
>
|
||||
Add Content Warning
|
||||
</button>
|
||||
`
|
||||
: html`
|
||||
<button
|
||||
class="w3-button w3-bar-item w3-theme-d1"
|
||||
@click=${() => self.set_content_warning(undefined)}
|
||||
>
|
||||
Remove Content Warning
|
||||
</button>
|
||||
`}
|
||||
<button
|
||||
class="w3-button w3-bar-item w3-theme-d1"
|
||||
@click=${this.attach}
|
||||
>
|
||||
Attach
|
||||
</button>
|
||||
${this.render_attach_app_button()} ${encrypt}
|
||||
<button
|
||||
class="w3-button w3-bar-item w3-theme-d1"
|
||||
@click=${this.discard}
|
||||
>
|
||||
Discard
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
`;
|
||||
return result;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
import {LitElement, html, unsafeHTML, until} from './lit-all.min.js';
|
||||
import {LitElement, html, unsafeHTML, repeat, until} from './lit-all.min.js';
|
||||
import * as tfrpc from '/static/tfrpc.js';
|
||||
import {styles} from './tf-styles.js';
|
||||
import {styles, generate_theme} from './tf-styles.js';
|
||||
|
||||
class TfNewsElement extends LitElement {
|
||||
static get properties() {
|
||||
@@ -11,6 +11,10 @@ class TfNewsElement extends LitElement {
|
||||
following: {type: Array},
|
||||
drafts: {type: Object},
|
||||
expanded: {type: Object},
|
||||
channel: {type: String},
|
||||
channel_unread: {type: Number},
|
||||
recent_reactions: {type: Array},
|
||||
hash: {type: String},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -25,6 +29,8 @@ class TfNewsElement extends LitElement {
|
||||
this.following = [];
|
||||
this.drafts = {};
|
||||
this.expanded = {};
|
||||
this.channel_unread = -1;
|
||||
this.recent_reactions = [];
|
||||
}
|
||||
|
||||
process_messages(messages) {
|
||||
@@ -33,12 +39,13 @@ class TfNewsElement extends LitElement {
|
||||
|
||||
console.log('processing', messages.length, 'messages');
|
||||
|
||||
function ensure_message(id) {
|
||||
function ensure_message(id, rowid) {
|
||||
let found = messages_by_id[id];
|
||||
if (found) {
|
||||
return found;
|
||||
} else {
|
||||
let added = {
|
||||
rowid: rowid,
|
||||
id: id,
|
||||
placeholder: true,
|
||||
content: '"placeholder"',
|
||||
@@ -53,7 +60,7 @@ class TfNewsElement extends LitElement {
|
||||
|
||||
function link_message(message) {
|
||||
if (message.content.type === 'vote') {
|
||||
let parent = ensure_message(message.content.vote.link);
|
||||
let parent = ensure_message(message.content.vote.link, message.rowid);
|
||||
if (!parent.votes) {
|
||||
parent.votes = [];
|
||||
}
|
||||
@@ -62,14 +69,14 @@ class TfNewsElement extends LitElement {
|
||||
} else if (message.content.type == 'post') {
|
||||
if (message.content.root) {
|
||||
if (typeof message.content.root === 'string') {
|
||||
let m = ensure_message(message.content.root);
|
||||
let m = ensure_message(message.content.root, message.rowid);
|
||||
if (!m.child_messages) {
|
||||
m.child_messages = [];
|
||||
}
|
||||
m.child_messages.push(message);
|
||||
message.parent_message = message.content.root;
|
||||
} else {
|
||||
let m = ensure_message(message.content.root[0]);
|
||||
let m = ensure_message(message.content.root[0], message.rowid);
|
||||
if (!m.child_messages) {
|
||||
m.child_messages = [];
|
||||
}
|
||||
@@ -153,43 +160,120 @@ class TfNewsElement extends LitElement {
|
||||
return recursive_sort(roots, true);
|
||||
}
|
||||
|
||||
group_following(messages) {
|
||||
group_messages(messages) {
|
||||
let result = [];
|
||||
let group = [];
|
||||
let type = undefined;
|
||||
for (let message of messages) {
|
||||
if (message?.content?.type === 'contact') {
|
||||
if (
|
||||
message?.content?.type === 'contact' ||
|
||||
message?.content?.type === 'channel'
|
||||
) {
|
||||
if (type && message.content.type !== type) {
|
||||
if (group.length == 1) {
|
||||
result.push(group[0]);
|
||||
group = [];
|
||||
} else if (group.length > 1) {
|
||||
result.push({
|
||||
rowid: Math.max(...group.map((x) => x.rowid)),
|
||||
type: `${type}_group`,
|
||||
messages: group,
|
||||
});
|
||||
group = [];
|
||||
}
|
||||
}
|
||||
type = message.content.type;
|
||||
group.push(message);
|
||||
} else {
|
||||
if (group.length > 0) {
|
||||
if (group.length == 1) {
|
||||
result.push(group[0]);
|
||||
group = [];
|
||||
} else if (group.length > 1) {
|
||||
result.push({
|
||||
type: 'contact_group',
|
||||
rowid: Math.max(...group.map((x) => x.rowid)),
|
||||
type: `${type}_group`,
|
||||
messages: group,
|
||||
});
|
||||
group = [];
|
||||
}
|
||||
result.push(message);
|
||||
type = undefined;
|
||||
}
|
||||
}
|
||||
if (group.length == 1) {
|
||||
result.push(group[0]);
|
||||
group = [];
|
||||
} else if (group.length > 1) {
|
||||
result.push({
|
||||
rowid: Math.max(...group.map((x) => x.rowid)),
|
||||
type: `${type}_group`,
|
||||
messages: group,
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
unread_allowed() {
|
||||
return !this.hash?.startsWith('#%') && !this.hash?.startsWith('#@');
|
||||
}
|
||||
|
||||
load_and_render(messages) {
|
||||
let messages_by_id = this.process_messages(messages);
|
||||
let final_messages = this.group_following(
|
||||
let final_messages = this.group_messages(
|
||||
this.finalize_messages(messages_by_id)
|
||||
);
|
||||
let unread_rowid = -1;
|
||||
if (this.unread_allowed()) {
|
||||
for (let message of final_messages) {
|
||||
if (message.rowid >= this.channel_unread) {
|
||||
unread_rowid = message.rowid;
|
||||
}
|
||||
}
|
||||
}
|
||||
return html`
|
||||
<div style="display: flex; flex-direction: column">
|
||||
${final_messages.map(
|
||||
(x) =>
|
||||
html`<tf-message
|
||||
<style>
|
||||
${generate_theme()}
|
||||
</style>
|
||||
<div>
|
||||
${repeat(
|
||||
final_messages,
|
||||
(x) => x.id,
|
||||
(x) => html`
|
||||
<tf-message
|
||||
.message=${x}
|
||||
whoami=${this.whoami}
|
||||
.users=${this.users}
|
||||
.drafts=${this.drafts}
|
||||
.expanded=${this.expanded}
|
||||
collapsed="true"
|
||||
></tf-message>`
|
||||
channel=${this.channel}
|
||||
channel_unread=${this.channel_unread}
|
||||
.recent_reactions=${this.recent_reactions}
|
||||
></tf-message>
|
||||
${x.rowid == unread_rowid
|
||||
? html`<div style="display: flex; flex-direction: row">
|
||||
<div
|
||||
style="border-bottom: 1px solid #f00; flex: 1; align-self: center; height: 1px"
|
||||
></div>
|
||||
<button
|
||||
style="color: #f00; padding: 8px"
|
||||
class="w3-button"
|
||||
@click=${() =>
|
||||
this.dispatchEvent(
|
||||
new Event('mark_all_read', {
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
})
|
||||
)}
|
||||
>
|
||||
unread
|
||||
</button>
|
||||
<div
|
||||
style="border-bottom: 1px solid #f00; flex: 1; align-self: center; height: 1px"
|
||||
></div>
|
||||
</div>`
|
||||
: undefined}
|
||||
`
|
||||
)}
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import {LitElement, html, unsafeHTML} from './lit-all.min.js';
|
||||
import {LitElement, html, until, unsafeHTML} from './lit-all.min.js';
|
||||
import * as tfrpc from '/static/tfrpc.js';
|
||||
import * as tfutils from './tf-utils.js';
|
||||
import {styles} from './tf-styles.js';
|
||||
import {styles, generate_theme} from './tf-styles.js';
|
||||
|
||||
class TfProfileElement extends LitElement {
|
||||
static get properties() {
|
||||
@@ -11,9 +11,10 @@ class TfProfileElement extends LitElement {
|
||||
id: {type: String},
|
||||
users: {type: Object},
|
||||
size: {type: Number},
|
||||
server_follows_me: {type: Boolean},
|
||||
sequence: {type: Number},
|
||||
following: {type: Boolean},
|
||||
blocking: {type: Boolean},
|
||||
show_followed: {type: Boolean},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -27,7 +28,7 @@ class TfProfileElement extends LitElement {
|
||||
this.id = null;
|
||||
this.users = {};
|
||||
this.size = 0;
|
||||
this.server_follows_me = undefined;
|
||||
this.sequence = 0;
|
||||
}
|
||||
|
||||
async load() {
|
||||
@@ -63,27 +64,8 @@ class TfProfileElement extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
async initial_load() {
|
||||
this.server_follows_me = undefined;
|
||||
let server_id = await tfrpc.rpc.getServerIdentity();
|
||||
let followed = await tfrpc.rpc.query(
|
||||
`
|
||||
SELECT json_extract(content, '$.following') AS following
|
||||
FROM messages
|
||||
WHERE author = ? AND
|
||||
json_extract(content, '$.type') = 'contact' AND
|
||||
json_extract(content, '$.contact') = ? ORDER BY sequence DESC LIMIT 1
|
||||
`,
|
||||
[server_id, this.whoami]
|
||||
);
|
||||
let is_followed = false;
|
||||
for (let row of followed) {
|
||||
is_followed = row.following != 0;
|
||||
}
|
||||
this.server_follows_me = is_followed;
|
||||
}
|
||||
|
||||
modify(change) {
|
||||
let self = this;
|
||||
tfrpc.rpc
|
||||
.appendMessage(
|
||||
this.whoami,
|
||||
@@ -95,6 +77,10 @@ class TfProfileElement extends LitElement {
|
||||
change
|
||||
)
|
||||
)
|
||||
.then(function () {
|
||||
self._follow_whoami = undefined;
|
||||
self.load();
|
||||
})
|
||||
.catch(function (error) {
|
||||
alert(error?.message);
|
||||
});
|
||||
@@ -156,7 +142,8 @@ class TfProfileElement extends LitElement {
|
||||
let self = this;
|
||||
let input = document.createElement('input');
|
||||
input.type = 'file';
|
||||
input.onchange = function (event) {
|
||||
input.addEventListener('change', function (event) {
|
||||
input.parentNode.removeChild(input);
|
||||
let file = event.target.files[0];
|
||||
file
|
||||
.arrayBuffer()
|
||||
@@ -171,78 +158,116 @@ class TfProfileElement extends LitElement {
|
||||
.catch(function (e) {
|
||||
alert(e.message);
|
||||
});
|
||||
};
|
||||
});
|
||||
document.body.appendChild(input);
|
||||
input.click();
|
||||
}
|
||||
|
||||
async server_follow_me(follow) {
|
||||
try {
|
||||
await tfrpc.rpc.setServerFollowingMe(this.whoami, follow);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
try {
|
||||
await this.initial_load();
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
}
|
||||
|
||||
copy_id() {
|
||||
navigator.clipboard.writeText(this.id);
|
||||
}
|
||||
|
||||
render() {
|
||||
if (
|
||||
this.id == this.whoami &&
|
||||
this.editing &&
|
||||
this.server_follows_me === undefined
|
||||
) {
|
||||
this.initial_load();
|
||||
show_image(link) {
|
||||
let div = document.createElement('div');
|
||||
div.style.left = 0;
|
||||
div.style.top = 0;
|
||||
div.style.width = '100%';
|
||||
div.style.height = '100%';
|
||||
div.style.position = 'fixed';
|
||||
div.style.background = '#000';
|
||||
div.style.zIndex = 100;
|
||||
div.style.display = 'grid';
|
||||
let img = document.createElement('img');
|
||||
img.src = link;
|
||||
img.style.maxWidth = '100vw';
|
||||
img.style.maxHeight = '100vh';
|
||||
img.style.display = 'block';
|
||||
img.style.margin = 'auto';
|
||||
img.style.objectFit = 'contain';
|
||||
img.style.width = '100vw';
|
||||
div.appendChild(img);
|
||||
function image_close(event) {
|
||||
document.body.removeChild(div);
|
||||
window.removeEventListener('keydown', image_close);
|
||||
}
|
||||
div.onclick = image_close;
|
||||
window.addEventListener('keydown', image_close);
|
||||
document.body.appendChild(div);
|
||||
}
|
||||
|
||||
body_click(event) {
|
||||
if (event.srcElement.tagName == 'IMG') {
|
||||
this.show_image(event.srcElement.src);
|
||||
}
|
||||
}
|
||||
|
||||
toggle_account_list(event) {
|
||||
let content = event.srcElement.nextElementSibling;
|
||||
this.show_followed = !this.show_followed;
|
||||
}
|
||||
|
||||
async load_follows() {
|
||||
let accounts = await tfrpc.rpc.following([this.id], 1);
|
||||
return html`
|
||||
<div class="w3-container">
|
||||
<button
|
||||
class="w3-button w3-block w3-theme-d1 followed_accounts"
|
||||
@click=${this.toggle_account_list}
|
||||
>
|
||||
${this.show_followed ? 'Hide' : 'Show'} Followed Accounts
|
||||
(${Object.keys(accounts).length})
|
||||
</button>
|
||||
<div class=${'w3-card' + (this.show_followed ? '' : ' w3-hide')}>
|
||||
<ul class="w3-ul w3-theme-d4 w3-border-theme">
|
||||
${Object.keys(accounts).map(
|
||||
(x) => html`
|
||||
<li class="w3-border-theme">
|
||||
<tf-user id=${x} .users=${this.users}></tf-user>
|
||||
</li>
|
||||
`
|
||||
)}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
render() {
|
||||
this.load();
|
||||
let self = this;
|
||||
let profile = this.users[this.id] || {};
|
||||
tfrpc.rpc
|
||||
.query(
|
||||
`SELECT SUM(LENGTH(content)) AS size FROM messages WHERE author = ?`,
|
||||
`SELECT SUM(LENGTH(content)) AS size, MAX(sequence) AS sequence FROM messages WHERE author = ?`,
|
||||
[this.id]
|
||||
)
|
||||
.then(function (result) {
|
||||
self.size = result[0].size;
|
||||
self.sequence = result[0].sequence;
|
||||
});
|
||||
let edit;
|
||||
let follow;
|
||||
let block;
|
||||
if (this.id === this.whoami) {
|
||||
if (this.editing) {
|
||||
let server_follow;
|
||||
if (this.server_follows_me === true) {
|
||||
server_follow = html`<button
|
||||
class="w3-button w3-theme-d1"
|
||||
@click=${() => this.server_follow_me(false)}
|
||||
>
|
||||
Server, Stop Following Me
|
||||
</button>`;
|
||||
} else if (this.server_follows_me === false) {
|
||||
server_follow = html`<button
|
||||
class="w3-button w3-theme-d1"
|
||||
@click=${() => this.server_follow_me(true)}
|
||||
>
|
||||
Server, Follow Me
|
||||
</button>`;
|
||||
}
|
||||
edit = html`
|
||||
<button class="w3-button w3-theme-d1" @click=${this.save_edits}>
|
||||
<button
|
||||
id="save_profile"
|
||||
class="w3-button w3-theme-d1"
|
||||
@click=${this.save_edits}
|
||||
>
|
||||
Save Profile
|
||||
</button>
|
||||
<button class="w3-button w3-theme-d1" @click=${this.discard_edits}>
|
||||
Discard
|
||||
</button>
|
||||
${server_follow}
|
||||
`;
|
||||
} else {
|
||||
edit = html`<button class="w3-button w3-theme-d1" @click=${this.edit}>
|
||||
edit = html`<button
|
||||
id="edit_profile"
|
||||
class="w3-button w3-theme-d1"
|
||||
@click=${this.edit}
|
||||
>
|
||||
Edit Profile
|
||||
</button>`;
|
||||
}
|
||||
@@ -268,49 +293,72 @@ class TfProfileElement extends LitElement {
|
||||
let edit_profile = this.editing
|
||||
? html`
|
||||
<div style="flex: 1 0 50%; display: flex; flex-direction: column; gap: 8px">
|
||||
<div class="w3-container">
|
||||
<div>
|
||||
<label for="name">Name:</label>
|
||||
<input class="w3-input w3-theme-d1" type="text" id="name" value=${this.editing.name} @input=${(event) => (this.editing = Object.assign({}, this.editing, {name: event.srcElement.value}))}></input>
|
||||
</div>
|
||||
<div><label for="description">Description:</label></div>
|
||||
<textarea class="w3-input w3-theme-d1" style="resize: vertical" rows="8" id="description" @input=${(event) => (this.editing = Object.assign({}, this.editing, {description: event.srcElement.value}))}>${this.editing.description}</textarea>
|
||||
<div>
|
||||
<label for="public_web_hosting">Public Web Hosting:</label>
|
||||
<input class="w3-check w3-theme-d1" type="checkbox" id="public_web_hosting" ?checked=${this.editing.publicWebHosting} @input=${(event) => (self.editing = Object.assign({}, self.editing, {publicWebHosting: event.srcElement.checked}))}></input>
|
||||
</div>
|
||||
<div>
|
||||
<button class="w3-button w3-theme-d1" @click=${this.attach_image}>Attach Image</button>
|
||||
</div>
|
||||
<div>
|
||||
<label for="name">Name:</label>
|
||||
<input class="w3-input w3-theme-d1" type="text" id="name" value=${this.editing.name} @input=${(event) => (this.editing = Object.assign({}, this.editing, {name: event.srcElement.value}))} placeholder="Choose a name"></input>
|
||||
</div>
|
||||
<div><label for="description">Description:</label></div>
|
||||
<textarea class="w3-input w3-theme-d1" style="resize: vertical" rows="8" id="description" @input=${(event) => (this.editing = Object.assign({}, this.editing, {description: event.srcElement.value}))} placeholder="Tell people a little bit about yourself here, if you like.">${this.editing.description}</textarea>
|
||||
<div>
|
||||
<label for="public_web_hosting">Public Web Hosting:</label>
|
||||
<input class="w3-check w3-theme-d1" type="checkbox" id="public_web_hosting" ?checked=${this.editing.publicWebHosting} @input=${(event) => (self.editing = Object.assign({}, self.editing, {publicWebHosting: event.srcElement.checked}))}></input>
|
||||
</div>
|
||||
<div>
|
||||
<button class="w3-button w3-theme-d1" @click=${this.attach_image}>Attach Image</button>
|
||||
</div>
|
||||
</div>`
|
||||
: null;
|
||||
let image =
|
||||
typeof profile.image == 'string' ? profile.image : profile.image?.link;
|
||||
let image = profile.image;
|
||||
if (typeof image == 'string' && !image.startsWith('&')) {
|
||||
try {
|
||||
image = JSON.parse(image)?.link;
|
||||
} catch {}
|
||||
}
|
||||
image = this.editing?.image ?? image;
|
||||
let description = this.editing?.description ?? profile.description;
|
||||
return html`<div class="w3-container" style="box-sizing: border-box; border: 2px solid black; background-color: rgba(255, 255, 255, 0.2)">
|
||||
<p><tf-user id=${this.id} .users=${this.users}></tf-user> (${tfutils.human_readable_size(this.size)})
|
||||
<input type="text" class="w3-input w3-border w3-theme-d1" readonly value=${this.id}></input>
|
||||
<button class="w3-button w3-theme-d1 w3-ripple" @click=${this.copy_id}>Copy</button>
|
||||
<div style="display: flex; flex-direction: row; gap: 1em">
|
||||
${edit_profile}
|
||||
<div style="flex: 1 0 50%">
|
||||
<div><img src=${'/' + image + '/view'} style="width: 256px; height: auto"></img></div>
|
||||
<div>${unsafeHTML(tfutils.markdown(description))}</div>
|
||||
return html`
|
||||
<style>${generate_theme()}</style>
|
||||
<div class="w3-card-4 w3-container w3-theme-d3" style="box-sizing: border-box">
|
||||
<header class="w3-container">
|
||||
<p><tf-user id=${this.id} .users=${this.users}></tf-user> (${tfutils.human_readable_size(this.size)} in ${this.sequence} messages)</p>
|
||||
</header>
|
||||
<div class="w3-container" @click=${this.body_click}>
|
||||
<div class="w3-margin-bottom" style="display: flex; flex-direction: row">
|
||||
<input type="text" class="w3-input w3-border w3-theme-d1" style="display: flex 1 1" readonly value=${this.id}></input>
|
||||
<button class="w3-button w3-theme-d1 w3-ripple" style="flex: 0 0 auto" @click=${this.copy_id}>Copy</button>
|
||||
</div>
|
||||
<div style="display: flex; flex-direction: row; gap: 1em">
|
||||
${edit_profile}
|
||||
<div style="flex: 1 0 50%; contain: layout; overflow: auto; word-wrap: normal; word-break: normal">
|
||||
${
|
||||
image
|
||||
? html`<div><img src=${'/' + image + '/view'} style="width: 256px; height: auto"></img></div>`
|
||||
: html`<div>
|
||||
<div class="w3-jumbo">😎</div>
|
||||
<div><i>Profile image not set.</i></div>
|
||||
</div>`
|
||||
}
|
||||
<div>${unsafeHTML(tfutils.markdown(description))}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
Following ${profile.following} identities.
|
||||
Followed by ${profile.followed} identities.
|
||||
Blocking ${profile.blocking} identities.
|
||||
Blocked by ${profile.blocked} identities.
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
Following ${profile.following} identities.
|
||||
Followed by ${profile.followed} identities.
|
||||
Blocking ${profile.blocking} identities.
|
||||
Blocked by ${profile.blocked} identities.
|
||||
</div>
|
||||
<p>
|
||||
${edit}
|
||||
${follow}
|
||||
${block}
|
||||
</p>
|
||||
${until(this.load_follows(), html`<p>Loading accounts followed...</p>`)}
|
||||
<footer class="w3-container">
|
||||
<p>
|
||||
<a class="w3-button w3-theme-d1" href=${'#🔐' + (this.id != this.whoami ? this.id : '')}>
|
||||
Open Private Chat
|
||||
</a>
|
||||
${edit}
|
||||
${follow}
|
||||
${block}
|
||||
</p>
|
||||
</footer>
|
||||
</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {LitElement, html, unsafeHTML} from './lit-all.min.js';
|
||||
import {styles} from './tf-styles.js';
|
||||
import {styles, generate_theme} from './tf-styles.js';
|
||||
|
||||
class TfReactionsModalElement extends LitElement {
|
||||
static get properties() {
|
||||
@@ -24,47 +24,57 @@ class TfReactionsModalElement extends LitElement {
|
||||
render() {
|
||||
let self = this;
|
||||
return this.votes?.length
|
||||
? html` <div
|
||||
class="w3-modal w3-animate-opacity"
|
||||
style="display: block; box-sizing: border-box"
|
||||
@click=${this.clear}
|
||||
>
|
||||
? html` <style>
|
||||
${generate_theme()}
|
||||
</style>
|
||||
<div
|
||||
class="w3-modal-content w3-card-4 w3-theme-d1"
|
||||
onclick="event.stopPropagation()"
|
||||
class="w3-modal w3-animate-opacity"
|
||||
style="display: block; box-sizing: border-box; z-index: 10"
|
||||
@click=${this.clear}
|
||||
>
|
||||
<div class="w3-container w3-padding">
|
||||
<header class="w3-container">
|
||||
<h2>Reactions</h2>
|
||||
<span class="w3-button w3-display-topright" @click=${this.clear}
|
||||
>×</span
|
||||
>
|
||||
</header>
|
||||
<ul class="w3-theme-dark w3-container w3-ul">
|
||||
${this.votes.map(
|
||||
(x) => html`
|
||||
<li class="w3-bar">
|
||||
<span class="w3-bar-item"
|
||||
>${x?.content?.vote?.expression}</span
|
||||
>
|
||||
<tf-user
|
||||
class="w3-bar-item"
|
||||
id=${x.author}
|
||||
.users=${this.users}
|
||||
></tf-user>
|
||||
<span class="w3-bar-item w3-right"
|
||||
>${new Date(x?.timestamp).toLocaleString()}</span
|
||||
>
|
||||
</li>
|
||||
`
|
||||
)}
|
||||
</ul>
|
||||
<footer class="w3-container w3-padding">
|
||||
<button class="w3-button" @click=${this.clear}>Close</button>
|
||||
</footer>
|
||||
<div
|
||||
class="w3-modal-content w3-card-4 w3-theme-d1"
|
||||
onclick="event.stopPropagation()"
|
||||
>
|
||||
<div class="w3-container w3-padding">
|
||||
<header class="w3-container">
|
||||
<h2>Reactions</h2>
|
||||
<span
|
||||
class="w3-button w3-display-topright"
|
||||
@click=${this.clear}
|
||||
>×</span
|
||||
>
|
||||
</header>
|
||||
<ul class="w3-theme-dark w3-container w3-ul">
|
||||
${this.votes
|
||||
.sort((x, y) => y.timestamp - x.timestamp)
|
||||
.map(
|
||||
(x) => html`
|
||||
<li
|
||||
style="display: flex; flex-direction: row; gap: 4px"
|
||||
>
|
||||
<span style="flex-basis: 3em"
|
||||
>${x?.content?.vote?.expression}</span
|
||||
>
|
||||
<tf-user
|
||||
style="flex: 1 1; overflow: hidden"
|
||||
id=${x.author}
|
||||
.users=${this.users}
|
||||
></tf-user>
|
||||
<span
|
||||
style="flex-shrink: 1; white-space: nowrap; overflow: hidden; text-overflow: ellipsis"
|
||||
>${new Date(x?.timestamp).toLocaleString()}</span
|
||||
>
|
||||
</li>
|
||||
`
|
||||
)}
|
||||
</ul>
|
||||
<footer class="w3-container w3-padding">
|
||||
<button class="w3-button" @click=${this.clear}>Close</button>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>`
|
||||
</div>`
|
||||
: undefined;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import {css} from './lit-all.min.js';
|
||||
import {css, unsafeCSS, until} from './lit-all.min.js';
|
||||
import * as tfrpc from '/static/tfrpc.js';
|
||||
|
||||
const tf = css`
|
||||
img {
|
||||
@@ -43,12 +44,14 @@ const tf = css`
|
||||
border-left: 4px solid #fff;
|
||||
padding: 8px;
|
||||
padding-left: 12px;
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
}
|
||||
`;
|
||||
|
||||
// prettier-ignore
|
||||
const w3 = css`
|
||||
/* W3.CSS 4.15 December 2020 by Jan Egil and Borge Refsnes */
|
||||
/* W3.CSS 5.01 March 14 2025 by Jan Egil and Borge Refsnes */
|
||||
html{box-sizing:border-box}*,*:before,*:after{box-sizing:inherit}
|
||||
/* Extract from normalize.css by Nicolas Gallagher and Jonathan Neal git.io/normalize */
|
||||
html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}
|
||||
@@ -88,7 +91,7 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
|
||||
.w3-table th:first-child,.w3-table td:first-child,.w3-table-all th:first-child,.w3-table-all td:first-child{padding-left:16px}
|
||||
.w3-btn,.w3-button{border:none;display:inline-block;padding:8px 16px;vertical-align:middle;overflow:hidden;text-decoration:none;color:inherit;background-color:inherit;text-align:center;cursor:pointer;white-space:nowrap}
|
||||
.w3-btn:hover{box-shadow:0 8px 16px 0 rgba(0,0,0,0.2),0 6px 20px 0 rgba(0,0,0,0.19)}
|
||||
.w3-btn,.w3-button{-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}
|
||||
.w3-btn,.w3-button{-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}
|
||||
.w3-disabled,.w3-btn:disabled,.w3-button:disabled{cursor:not-allowed;opacity:0.3}.w3-disabled *,:disabled *{pointer-events:none}
|
||||
.w3-btn.w3-disabled:hover,.w3-btn:disabled:hover{box-shadow:none}
|
||||
.w3-badge,.w3-tag{background-color:#000;color:#fff;display:inline-block;padding-left:8px;padding-right:8px;text-align:center}.w3-badge{border-radius:50%}
|
||||
@@ -136,7 +139,7 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
|
||||
.w3-hide{display:none!important}.w3-show-block,.w3-show{display:block!important}.w3-show-inline-block{display:inline-block!important}
|
||||
@media (max-width:1205px){.w3-auto{max-width:95%}}
|
||||
@media (max-width:600px){.w3-modal-content{margin:0 10px;width:auto!important}.w3-modal{padding-top:30px}
|
||||
.w3-dropdown-hover.w3-mobile .w3-dropdown-content,.w3-dropdown-click.w3-mobile .w3-dropdown-content{position:relative}
|
||||
.w3-dropdown-hover.w3-mobile .w3-dropdown-content,.w3-dropdown-click.w3-mobile .w3-dropdown-content{position:relative}
|
||||
.w3-hide-small{display:none!important}.w3-mobile{display:block;width:100%!important}.w3-bar-item.w3-mobile,.w3-dropdown-hover.w3-mobile,.w3-dropdown-click.w3-mobile{text-align:center}
|
||||
.w3-dropdown-hover.w3-mobile,.w3-dropdown-hover.w3-mobile .w3-btn,.w3-dropdown-hover.w3-mobile .w3-button,.w3-dropdown-click.w3-mobile,.w3-dropdown-click.w3-mobile .w3-btn,.w3-dropdown-click.w3-mobile .w3-button{width:100%}}
|
||||
@media (max-width:768px){.w3-modal-content{width:500px}.w3-modal{padding-top:50px}}
|
||||
@@ -158,6 +161,10 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
|
||||
.w3-round-small{border-radius:2px}.w3-round,.w3-round-medium{border-radius:4px}.w3-round-large{border-radius:8px}.w3-round-xlarge{border-radius:16px}.w3-round-xxlarge{border-radius:32px}
|
||||
.w3-row-padding,.w3-row-padding>.w3-half,.w3-row-padding>.w3-third,.w3-row-padding>.w3-twothird,.w3-row-padding>.w3-threequarter,.w3-row-padding>.w3-quarter,.w3-row-padding>.w3-col{padding:0 8px}
|
||||
.w3-container,.w3-panel{padding:0.01em 16px}.w3-panel{margin-top:16px;margin-bottom:16px}
|
||||
|
||||
.w3-grid{display:grid}.w3-grid-padding{display:grid;gap:16px}.w3-flex{display:flex}
|
||||
.w3-text-center{text-align:center}.w3-text-bold,.w3-bold{font-weight:bold}.w3-text-italic,.w3-italic{font-style:italic}
|
||||
|
||||
.w3-code,.w3-codespan{font-family:Consolas,"courier new";font-size:16px}
|
||||
.w3-code{width:auto;background-color:#fff;padding:8px 12px;border-left:4px solid #4CAF50;word-wrap:break-word}
|
||||
.w3-codespan{color:crimson;background-color:#f1f1f1;padding-left:4px;padding-right:4px;font-size:110%}
|
||||
@@ -199,9 +206,9 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
|
||||
.w3-transparent,.w3-hover-none:hover{background-color:transparent!important}
|
||||
.w3-hover-none:hover{box-shadow:none!important}
|
||||
/* Colors */
|
||||
.w3-amber,.w3-hover-amber:hover{color:#000!important;background-color:#ffc107!important}
|
||||
.w3-amber,.w3-hover-amber:hover,.w3-warning{color:#000!important;background-color:#ffc107!important}
|
||||
.w3-aqua,.w3-hover-aqua:hover{color:#000!important;background-color:#00ffff!important}
|
||||
.w3-blue,.w3-hover-blue:hover{color:#fff!important;background-color:#2196F3!important}
|
||||
.w3-blue,.w3-hover-blue:hover,.w3-info,.w3-primary{color:#fff!important;background-color:#2196F3!important}
|
||||
.w3-light-blue,.w3-hover-light-blue:hover{color:#000!important;background-color:#87CEEB!important}
|
||||
.w3-brown,.w3-hover-brown:hover{color:#fff!important;background-color:#795548!important}
|
||||
.w3-cyan,.w3-hover-cyan:hover{color:#000!important;background-color:#00bcd4!important}
|
||||
@@ -216,15 +223,24 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
|
||||
.w3-pink,.w3-hover-pink:hover{color:#fff!important;background-color:#e91e63!important}
|
||||
.w3-purple,.w3-hover-purple:hover{color:#fff!important;background-color:#9c27b0!important}
|
||||
.w3-deep-purple,.w3-hover-deep-purple:hover{color:#fff!important;background-color:#673ab7!important}
|
||||
.w3-red,.w3-hover-red:hover{color:#fff!important;background-color:#f44336!important}
|
||||
.w3-red,.w3-hover-red:hover,.w3-danger{color:#fff!important;background-color:#f44336!important}
|
||||
.w3-sand,.w3-hover-sand:hover{color:#000!important;background-color:#fdf5e6!important}
|
||||
.w3-teal,.w3-hover-teal:hover{color:#fff!important;background-color:#009688!important}
|
||||
.w3-yellow,.w3-hover-yellow:hover{color:#000!important;background-color:#ffeb3b!important}
|
||||
.w3-yellow,.w3-hover-yellow:hover,.w3-note{color:#000!important;background-color:#ffeb3b!important}
|
||||
.w3-white,.w3-hover-white:hover{color:#000!important;background-color:#fff!important}
|
||||
.w3-black,.w3-hover-black:hover{color:#fff!important;background-color:#000!important}
|
||||
.w3-grey,.w3-hover-grey:hover,.w3-gray,.w3-hover-gray:hover{color:#000!important;background-color:#9e9e9e!important}
|
||||
|
||||
.w3-grey,.w3-hover-grey:hover,.w3-gray,.w3-hover-gray:hover,.w3-secondary{color:#000!important;background-color:#9e9e9e!important}
|
||||
.w3-light-grey,.w3-hover-light-grey:hover,.w3-light-gray,.w3-hover-light-gray:hover{color:#000!important;background-color:#f1f1f1!important}
|
||||
.w3-dark-grey,.w3-hover-dark-grey:hover,.w3-dark-gray,.w3-hover-dark-gray:hover{color:#fff!important;background-color:#616161!important}
|
||||
|
||||
.w3-asphalt,.w3-hover-asphalt:hover{color:#fff!important;background-color:#343a40!important}.w3-crimson,.w3-hover-crimson:hover{color:#fff!important;background-color:#a20025!important}
|
||||
.w3-cobalt,w3-hover-cobalt:hover{color:#fff!important;background-color:#0050ef!important}
|
||||
.w3-emerald,.w3-hover-emerald:hover,.w3-success{color:#fff!important;background-color:#008a00!important}
|
||||
.w3-olive,.w3-hover-olive:hover{color:#fff!important;background-color:#6d8764!important}
|
||||
.w3-paper,.w3-hover-paper:hover{color:#000!important;background-color:#f8f9fa!important}.w3-sienna,.w3-hover-sienna:hover{color:#fff!important;background-color:#a0522d!important}
|
||||
.w3-taupe,.w3-hover-taupe:hover{color:#fff!important;background-color:#87794e!important}
|
||||
|
||||
.w3-pale-red,.w3-hover-pale-red:hover{color:#000!important;background-color:#ffdddd!important}
|
||||
.w3-pale-green,.w3-hover-pale-green:hover{color:#000!important;background-color:#ddffdd!important}
|
||||
.w3-pale-yellow,.w3-hover-pale-yellow:hover{color:#000!important;background-color:#ffffcc!important}
|
||||
@@ -285,30 +301,181 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
|
||||
.w3-border-pale-yellow,.w3-hover-border-pale-yellow:hover{border-color:#ffffcc!important}.w3-border-pale-blue,.w3-hover-border-pale-blue:hover{border-color:#e7ffff!important}
|
||||
`;
|
||||
|
||||
// prettier-ignore
|
||||
const w3_2016_riverside = css`
|
||||
.w3-theme-l5 {color:#000 !important; background-color:#f4f6f9 !important}
|
||||
.w3-theme-l4 {color:#000 !important; background-color:#d9e1ec !important}
|
||||
.w3-theme-l3 {color:#000 !important; background-color:#b4c3d8 !important}
|
||||
.w3-theme-l2 {color:#fff !important; background-color:#8ea6c5 !important}
|
||||
.w3-theme-l1 {color:#fff !important; background-color:#6888b1 !important}
|
||||
.w3-theme-d1 {color:#fff !important; background-color:#456185 !important}
|
||||
.w3-theme-d2 {color:#fff !important; background-color:#3d5676 !important}
|
||||
.w3-theme-d3 {color:#fff !important; background-color:#354b68 !important}
|
||||
.w3-theme-d4 {color:#fff !important; background-color:#2e4059 !important}
|
||||
.w3-theme-d5 {color:#fff !important; background-color:#26364a !important}
|
||||
function rgb_to_hsl(r, g, b) {
|
||||
let min,
|
||||
max,
|
||||
i,
|
||||
l,
|
||||
s,
|
||||
maxcolor,
|
||||
h,
|
||||
rgb = [];
|
||||
rgb[0] = r / 255;
|
||||
rgb[1] = g / 255;
|
||||
rgb[2] = b / 255;
|
||||
min = rgb[0];
|
||||
max = rgb[0];
|
||||
maxcolor = 0;
|
||||
for (i = 0; i < rgb.length - 1; i++) {
|
||||
if (rgb[i + 1] <= min) {
|
||||
min = rgb[i + 1];
|
||||
}
|
||||
if (rgb[i + 1] >= max) {
|
||||
max = rgb[i + 1];
|
||||
maxcolor = i + 1;
|
||||
}
|
||||
}
|
||||
if (maxcolor == 0) {
|
||||
h = (rgb[1] - rgb[2]) / (max - min);
|
||||
}
|
||||
if (maxcolor == 1) {
|
||||
h = 2 + (rgb[2] - rgb[0]) / (max - min);
|
||||
}
|
||||
if (maxcolor == 2) {
|
||||
h = 4 + (rgb[0] - rgb[1]) / (max - min);
|
||||
}
|
||||
if (isNaN(h)) {
|
||||
h = 0;
|
||||
}
|
||||
h = h * 60;
|
||||
if (h < 0) {
|
||||
h = h + 360;
|
||||
}
|
||||
l = (min + max) / 2;
|
||||
if (min == max) {
|
||||
s = 0;
|
||||
} else {
|
||||
if (l < 0.5) {
|
||||
s = (max - min) / (max + min);
|
||||
} else {
|
||||
s = (max - min) / (2 - max - min);
|
||||
}
|
||||
}
|
||||
s = s;
|
||||
return [h, s, l];
|
||||
}
|
||||
|
||||
.w3-theme-light {color:#000 !important; background-color:#f4f6f9 !important}
|
||||
.w3-theme-dark {color:#fff !important; background-color:#26364a !important}
|
||||
.w3-theme-action {color:#fff !important; background-color:#26364a !important}
|
||||
function hex_to_rgb(hex) {
|
||||
if (hex.charAt(0) == '#') {
|
||||
hex = hex.substring(1);
|
||||
}
|
||||
return [
|
||||
parseInt(hex.substring(0, 2), 16),
|
||||
parseInt(hex.substring(2, 4), 16),
|
||||
parseInt(hex.substring(4, 6), 16),
|
||||
];
|
||||
}
|
||||
|
||||
.w3-theme {color:#fff !important; background-color:#4c6a92 !important}
|
||||
.w3-text-theme {color:#4c6a92 !important}
|
||||
.w3-border-theme {border-color:#4c6a92 !important}
|
||||
function hsl_to_rgb(hue, sat, light) {
|
||||
let t2;
|
||||
hue /= 60;
|
||||
if (light <= 0.5) {
|
||||
t2 = light * (sat + 1);
|
||||
} else {
|
||||
t2 = light + sat - light * sat;
|
||||
}
|
||||
let t1 = light * 2 - t2;
|
||||
return [
|
||||
hue_to_rgb(t1, t2, hue + 2) * 255,
|
||||
hue_to_rgb(t1, t2, hue) * 255,
|
||||
hue_to_rgb(t1, t2, hue - 2) * 255,
|
||||
];
|
||||
}
|
||||
function hue_to_rgb(t1, t2, hue) {
|
||||
if (hue < 0) {
|
||||
hue += 6;
|
||||
}
|
||||
if (hue >= 6) {
|
||||
hue -= 6;
|
||||
}
|
||||
if (hue < 1) {
|
||||
return (t2 - t1) * hue + t1;
|
||||
} else if (hue < 3) {
|
||||
return t2;
|
||||
} else if (hue < 4) {
|
||||
return (t2 - t1) * (4 - hue) + t1;
|
||||
} else {
|
||||
return t1;
|
||||
}
|
||||
}
|
||||
|
||||
.w3-hover-theme:hover {color:#fff !important; background-color:#4c6a92 !important}
|
||||
.w3-hover-text-theme:hover {color:#4c6a92 !important}
|
||||
.w3-hover-border-theme:hover {border-color:#4c6a92 !important}
|
||||
`;
|
||||
function rgb_to_hex(rgb) {
|
||||
const hex_pair = (x) => Math.floor(x).toString(16).padStart(2, '0');
|
||||
return `#${hex_pair(rgb[0])}${hex_pair(rgb[1])}${hex_pair(rgb[2])}`;
|
||||
}
|
||||
|
||||
export let styles = [tf, w3, w3_2016_riverside];
|
||||
function is_dark(hex, value) {
|
||||
let [r, g, b] = hex_to_rgb(hex);
|
||||
return (r * 299 + g * 587 + b * 114) / 1000 < value;
|
||||
}
|
||||
|
||||
export function generate(color) {
|
||||
let [r, g, b] = hex_to_rgb(color);
|
||||
let [h, s, l] = rgb_to_hsl(r, g, b);
|
||||
|
||||
let theme1 = {
|
||||
l5: rgb_to_hex(hsl_to_rgb(h, s, l + ((1.0 - l) / 5) * 4.7)),
|
||||
l4: rgb_to_hex(hsl_to_rgb(h, s, l + ((1.0 - l) / 5) * 4)),
|
||||
l3: rgb_to_hex(hsl_to_rgb(h, s, l + ((1.0 - l) / 5) * 3)),
|
||||
l2: rgb_to_hex(hsl_to_rgb(h, s, l + ((1.0 - l) / 5) * 2)),
|
||||
l1: rgb_to_hex(hsl_to_rgb(h, s, l + ((1.0 - l) / 5) * 1)),
|
||||
d0: rgb_to_hex(hsl_to_rgb(h, s, l)),
|
||||
d1: rgb_to_hex(hsl_to_rgb(h, s, l - (l / 5) * 0.5)),
|
||||
d2: rgb_to_hex(hsl_to_rgb(h, s, l - (l / 5) * 1)),
|
||||
d3: rgb_to_hex(hsl_to_rgb(h, s, l - (l / 5) * 1.5)),
|
||||
d4: rgb_to_hex(hsl_to_rgb(h, s, l - (l / 5) * 2)),
|
||||
d5: rgb_to_hex(hsl_to_rgb(h, s, l - (l / 5) * 2.5)),
|
||||
};
|
||||
for (let [k, v] of Object.entries(theme1)) {
|
||||
theme1['t' + k] = is_dark(v, 165) ? '#fff' : '#000';
|
||||
}
|
||||
|
||||
let result = `
|
||||
.w3-theme-l5 {color: ${theme1.tl5} !important; background-color: ${theme1.l5} !important}
|
||||
.w3-theme-l4 {color: ${theme1.tl4} !important; background-color: ${theme1.l4} !important}
|
||||
.w3-theme-l3 {color: ${theme1.tl3} !important; background-color: ${theme1.l3} !important}
|
||||
.w3-theme-l2 {color: ${theme1.tl2} !important; background-color: ${theme1.l2} !important}
|
||||
.w3-theme-l1 {color: ${theme1.tl1} !important; background-color: ${theme1.l1} !important}
|
||||
.w3-theme-d1 {color: ${theme1.td1} !important; background-color: ${theme1.d1} !important}
|
||||
.w3-theme-d2 {color: ${theme1.td2} !important; background-color: ${theme1.d2} !important}
|
||||
.w3-theme-d3 {color: ${theme1.td3} !important; background-color: ${theme1.d3} !important}
|
||||
.w3-theme-d4 {color: ${theme1.td4} !important; background-color: ${theme1.d4} !important}
|
||||
.w3-theme-d5 {color: ${theme1.td5} !important; background-color: ${theme1.d5} !important}
|
||||
.w3-theme-light {color: ${theme1.tl5} !important; background-color: ${theme1.l5} !important}
|
||||
.w3-theme-dark {color: ${theme1.td5} !important; background-color: ${theme1.d5} !important}
|
||||
.w3-theme-action {color: ${theme1.td5} !important; background-color: ${theme1.d5} !important}
|
||||
.w3-theme {color: ${theme1.td0} !important; background-color: ${theme1.d0} !important}
|
||||
.w3-text-theme {color: ${theme1.d0} !important}
|
||||
.w3-border-theme {border-color: ${theme1.d0} !important}
|
||||
.w3-hover-theme:hover {color: ${theme1.td0} !important; background-color: ${theme1.d0} !important}
|
||||
.w3-hover-text-theme:hover {color: ${theme1.d0} !important}
|
||||
.w3-hover-border-theme:hover {border-color: ${theme1.d0} !important}
|
||||
`;
|
||||
return unsafeCSS(result);
|
||||
}
|
||||
|
||||
let g_theme;
|
||||
export function generate_theme() {
|
||||
return g_theme
|
||||
? g_theme
|
||||
: until(
|
||||
tfrpc.rpc.localStorageGet('color').then(function (value) {
|
||||
g_theme = generate(value ?? '#034f84');
|
||||
return g_theme;
|
||||
}),
|
||||
generated_now()
|
||||
);
|
||||
}
|
||||
|
||||
function generated_now() {
|
||||
let now = new Date();
|
||||
return generate(
|
||||
rgb_to_hex([
|
||||
(now.getDay() * 128) / 6,
|
||||
(now.getHours() * 128) / 23,
|
||||
(now.getSeconds() * 128) / 59,
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
export let styles = [tf, w3];
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {LitElement, html} from './lit-all.min.js';
|
||||
import * as tfrpc from '/static/tfrpc.js';
|
||||
import {styles} from './tf-styles.js';
|
||||
import {styles, generate_theme} from './tf-styles.js';
|
||||
|
||||
class TfTabConnectionsElement extends LitElement {
|
||||
static get properties() {
|
||||
@@ -12,6 +12,10 @@ class TfTabConnectionsElement extends LitElement {
|
||||
stored_connections: {type: Array},
|
||||
users: {type: Object},
|
||||
server_identity: {type: String},
|
||||
connect_attempt: {type: Object},
|
||||
connect_message: {type: String},
|
||||
connect_success: {type: Boolean},
|
||||
peer_exchange: {type: Boolean},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -44,6 +48,20 @@ class TfTabConnectionsElement extends LitElement {
|
||||
tfrpc.rpc.getServerIdentity().then(function (identity) {
|
||||
self.server_identity = identity;
|
||||
});
|
||||
this.check_peer_exchange();
|
||||
}
|
||||
|
||||
async check_peer_exchange() {
|
||||
if (await tfrpc.rpc.isAdministrator()) {
|
||||
this.peer_exchange = await tfrpc.rpc.globalSettingsGet('peer_exchange');
|
||||
} else {
|
||||
this.peer_exchange = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
async enable_peer_exchange() {
|
||||
await tfrpc.rpc.globalSettingsSet('peer_exchange', true);
|
||||
await this.check_peer_exchange();
|
||||
}
|
||||
|
||||
render_connection_summary(connection) {
|
||||
@@ -88,20 +106,53 @@ class TfTabConnectionsElement extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
render_broadcast(connection) {
|
||||
return html`
|
||||
<li class="w3-bar" style="overflow: hidden; overflow-wrap: nowrap">
|
||||
<button
|
||||
class="w3-bar-item w3-button w3-theme-d1"
|
||||
@click=${() => tfrpc.rpc.connect(connection)}
|
||||
>
|
||||
Connect
|
||||
</button>
|
||||
<div class="w3-bar-item">
|
||||
${TfTabConnectionsElement.k_broadcast_emojis[connection.origin]}
|
||||
<tf-user id=${connection.pubkey} .users=${this.users}></tf-user>
|
||||
${this.render_connection_summary(connection)}
|
||||
render_message(connection) {
|
||||
return html`<div
|
||||
?hidden=${this.connect_message === undefined ||
|
||||
this.connect_attempt != connection}
|
||||
style="cursor: pointer"
|
||||
class=${'w3-panel ' + (this.connect_success ? 'w3-green' : 'w3-red')}
|
||||
@click=${() => (this.connect_attempt = undefined)}
|
||||
>
|
||||
<p>${this.connect_message}</p>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
render_progress(name, value, max) {
|
||||
if (max && value != max) {
|
||||
return html`
|
||||
<div class="w3-theme-d1 w3-small">
|
||||
<div
|
||||
class="w3-container w3-theme-l1"
|
||||
style="width: ${Math.floor(
|
||||
(100.0 * value) / max
|
||||
)}%; text-wrap: nowrap"
|
||||
>
|
||||
${name} ${value} / ${max} (${Math.round((100.0 * value) / max)}%)
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
render_broadcast(connection) {
|
||||
let self = this;
|
||||
return html`
|
||||
<li>
|
||||
<div class="w3-bar" style="overflow: hidden; overflow-wrap: nowrap">
|
||||
<button
|
||||
class="w3-bar-item w3-button w3-theme-d1"
|
||||
@click=${() => self.connect(connection)}
|
||||
>
|
||||
Connect
|
||||
</button>
|
||||
<div class="w3-bar-item">
|
||||
${TfTabConnectionsElement.k_broadcast_emojis[connection.origin]}
|
||||
<tf-user id=${connection.pubkey} .users=${this.users}></tf-user>
|
||||
${this.render_connection_summary(connection)}
|
||||
</div>
|
||||
</div>
|
||||
${this.render_message(connection)}
|
||||
</li>
|
||||
`;
|
||||
}
|
||||
@@ -123,14 +174,28 @@ class TfTabConnectionsElement extends LitElement {
|
||||
}, {})
|
||||
);
|
||||
return html`
|
||||
<button
|
||||
class="w3-button w3-theme-d1"
|
||||
@click=${() => tfrpc.rpc.closeConnection(connection.id)}
|
||||
>
|
||||
Close
|
||||
</button>
|
||||
${connection.connected
|
||||
? html`
|
||||
<button
|
||||
class="w3-button w3-theme-d1"
|
||||
@click=${() => tfrpc.rpc.closeConnection(connection.id)}
|
||||
>
|
||||
Close
|
||||
</button>
|
||||
`
|
||||
: undefined}
|
||||
${connection.flags.one_shot ? '🔃' : undefined}
|
||||
<tf-user id=${connection.id} .users=${this.users}></tf-user>
|
||||
${this.render_progress(
|
||||
'recv',
|
||||
connection.progress.in.total - connection.progress.in.current,
|
||||
connection.progress.in.total
|
||||
)}
|
||||
${this.render_progress(
|
||||
'send',
|
||||
connection.progress.out.total - connection.progress.out.current,
|
||||
connection.progress.out.total
|
||||
)}
|
||||
${connection.tunnel !== undefined
|
||||
? '🚇'
|
||||
: html`(${connection.host}:${connection.port})`}
|
||||
@@ -156,92 +221,167 @@ class TfTabConnectionsElement extends LitElement {
|
||||
.map((x) => html`<li>${this.render_connection(x)}</li>`)}
|
||||
${this.render_room_peers(connection.id)}
|
||||
</ul>
|
||||
<div ?hidden=${!connection.destroy_reason} class="w3-panel w3-red">
|
||||
<p>${connection.destroy_reason}</p>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
refresh() {
|
||||
tfrpc.rpc.sync();
|
||||
connect(address) {
|
||||
let self = this;
|
||||
self.connect_attempt = address;
|
||||
self.connect_message = undefined;
|
||||
self.connect_success = false;
|
||||
tfrpc.rpc
|
||||
.connect(address)
|
||||
.then(function () {
|
||||
if (self.connect_attempt == address) {
|
||||
self.connect_message = 'Connected.';
|
||||
self.connect_success = true;
|
||||
}
|
||||
})
|
||||
.catch(function (error) {
|
||||
if (self.connect_attempt == address) {
|
||||
self.connect_message = 'Error: ' + error;
|
||||
self.connect_success = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
toggle_accordian(id) {
|
||||
let element = this.renderRoot.getElementById(id);
|
||||
element.classList.toggle('w3-hide');
|
||||
}
|
||||
|
||||
valid_connections() {
|
||||
return this.connections.filter((x) => x.tunnel === undefined);
|
||||
}
|
||||
|
||||
valid_broadcasts() {
|
||||
return this.broadcasts
|
||||
.filter((x) => x.address)
|
||||
.filter((x) => this.connections.map((c) => c.id).indexOf(x.pubkey) == -1);
|
||||
}
|
||||
|
||||
render() {
|
||||
let self = this;
|
||||
return html`
|
||||
<style>
|
||||
${generate_theme()}
|
||||
</style>
|
||||
<div class="w3-container" style="box-sizing: border-box">
|
||||
<button
|
||||
class="w3-button w3-theme-l3 w3-circle w3-ripple w3-large"
|
||||
@click=${this.refresh}
|
||||
<div
|
||||
class=${'w3-panel w3-padding w3-theme-l3' +
|
||||
(this.peer_exchange !== false ? ' w3-hide' : '')}
|
||||
>
|
||||
🔃
|
||||
</button>
|
||||
<p>
|
||||
Looking for connections? Enabling this option will include publicly
|
||||
advertised rooms and pubs among the list of discovered connections
|
||||
to help you replicate.
|
||||
</p>
|
||||
<button
|
||||
class="w3-button w3-theme-d1"
|
||||
@click=${this.enable_peer_exchange}
|
||||
>
|
||||
🔍🌐 Use publicly advertised peers
|
||||
</button>
|
||||
</div>
|
||||
<h2>New Connection</h2>
|
||||
<textarea class="w3-input w3-theme-d1" id="code"></textarea>
|
||||
${this.render_message(this.renderRoot.getElementById('code')?.value)}
|
||||
<button
|
||||
class="w3-button w3-theme-d1"
|
||||
@click=${() =>
|
||||
tfrpc.rpc.connect(self.renderRoot.getElementById('code').value)}
|
||||
self.connect(self.renderRoot.getElementById('code')?.value)}
|
||||
>
|
||||
Connect
|
||||
</button>
|
||||
<h2>Broadcasts</h2>
|
||||
<ul class="w3-ul w3-border">
|
||||
${this.broadcasts
|
||||
.filter((x) => x.address)
|
||||
.map((x) => self.render_broadcast(x))}
|
||||
<h2
|
||||
class="w3-button w3-block w3-theme-d1"
|
||||
@click=${() => self.toggle_accordian('connections')}
|
||||
>
|
||||
Connections (${this.valid_connections().length})
|
||||
</h2>
|
||||
<ul class="w3-ul w3-border" id="connections">
|
||||
${this.valid_connections().map(
|
||||
(x) => html` <li class="w3-bar">${this.render_connection(x)}</li> `
|
||||
)}
|
||||
</ul>
|
||||
<h2>Connections</h2>
|
||||
<ul class="w3-ul w3-border">
|
||||
${this.connections
|
||||
.filter((x) => x.tunnel === undefined)
|
||||
.map(
|
||||
(x) => html`
|
||||
<li class="w3-bar">${this.render_connection(x)}</li>
|
||||
`
|
||||
)}
|
||||
<h2
|
||||
class="w3-button w3-block w3-theme-d1"
|
||||
@click=${() => self.toggle_accordian('broadcasts')}
|
||||
>
|
||||
Discovery (${this.valid_broadcasts().length})
|
||||
</h2>
|
||||
<ul class="w3-ul w3-border w3-hide" id="broadcasts">
|
||||
${this.valid_broadcasts().map((x) => self.render_broadcast(x))}
|
||||
</ul>
|
||||
<h2>Stored Connections</h2>
|
||||
<ul class="w3-ul w3-border">
|
||||
<h2
|
||||
class="w3-button w3-block w3-theme-d1"
|
||||
@click=${() => self.toggle_accordian('stored_connections')}
|
||||
>
|
||||
Stored Connections (${this.stored_connections.length})
|
||||
</h2>
|
||||
<ul class="w3-ul w3-border w3-hide" id="stored_connections">
|
||||
${this.stored_connections.map(
|
||||
(x) => html`
|
||||
<li class="w3-bar">
|
||||
<button
|
||||
class="w3-bar-item w3-button w3-theme-d1"
|
||||
@click=${() => self.forget_stored_connection(x)}
|
||||
>
|
||||
Forget
|
||||
</button>
|
||||
<button
|
||||
class="w3-bar-item w3-button w3-theme-d1"
|
||||
@click=${() => tfrpc.rpc.connect(x)}
|
||||
>
|
||||
Connect
|
||||
</button>
|
||||
<div class="w3-bar-item">
|
||||
<tf-user id=${x.pubkey} .users=${self.users}></tf-user>
|
||||
<div><small>${x.address}:${x.port}</small></div>
|
||||
<li>
|
||||
<div class="w3-bar">
|
||||
<button
|
||||
class="w3-bar-item w3-button w3-theme-d1"
|
||||
@click=${() => self.forget_stored_connection(x)}
|
||||
>
|
||||
Forget
|
||||
</button>
|
||||
<button
|
||||
class="w3-bar-item w3-button w3-theme-d1"
|
||||
@click=${() => this.connect(x)}
|
||||
>
|
||||
Connect
|
||||
</button>
|
||||
<div class="w3-bar-item">
|
||||
<tf-user id=${x.pubkey} .users=${self.users}></tf-user>
|
||||
<div><small>${x.address}:${x.port}</small></div>
|
||||
<div>
|
||||
<small
|
||||
>Last connection:
|
||||
${new Date(x.last_success * 1000)}</small
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
${this.render_message(x)}
|
||||
</li>
|
||||
`
|
||||
)}
|
||||
</ul>
|
||||
<h2>Local Accounts</h2>
|
||||
<ul class="w3-ul w3-border">
|
||||
<h2
|
||||
class="w3-button w3-block w3-theme-d1"
|
||||
@click=${() => self.toggle_accordian('local_accounts')}
|
||||
>
|
||||
Local Accounts (${this.identities.length})
|
||||
</h2>
|
||||
<div class="w3-container w3-hide" id="local_accounts">
|
||||
${this.identities.map(
|
||||
(x) =>
|
||||
html`<li class="w3-bar">
|
||||
html`<div
|
||||
class="w3-tag w3-round w3-theme-l3"
|
||||
style="padding: 4px; margin: 2px; max-width: 100%; text-wrap: nowrap; overflow: hidden"
|
||||
>
|
||||
${x == this.server_identity
|
||||
? html`<span class="w3-tag w3-medium w3-round w3-theme-l1"
|
||||
>🖥 local server</span
|
||||
>`
|
||||
? html`<div class="w3-tag w3-medium w3-round w3-theme-l1">
|
||||
🖥 local server
|
||||
</div>`
|
||||
: undefined}
|
||||
${this.my_identities.indexOf(x) != -1
|
||||
? html`<span class="w3-tag w3-medium w3-round w3-theme-d1"
|
||||
>😎 you</span
|
||||
>`
|
||||
? html`<div class="w3-tag w3-medium w3-round w3-theme-d1">
|
||||
😎 you
|
||||
</div>`
|
||||
: undefined}
|
||||
<tf-user id=${x} .users=${this.users}></tf-user>
|
||||
</li>`
|
||||
</div>`
|
||||
)}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
import {LitElement, html, unsafeHTML} from './lit-all.min.js';
|
||||
import * as tfrpc from '/static/tfrpc.js';
|
||||
import {styles} from './tf-styles.js';
|
||||
|
||||
class TfTabMentionsElement extends LitElement {
|
||||
static get properties() {
|
||||
return {
|
||||
whoami: {type: String},
|
||||
users: {type: Object},
|
||||
following: {type: Array},
|
||||
expanded: {type: Object},
|
||||
messages: {type: Array},
|
||||
};
|
||||
}
|
||||
|
||||
static styles = styles;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
let self = this;
|
||||
this.whoami = null;
|
||||
this.users = {};
|
||||
this.following = [];
|
||||
this.expanded = {};
|
||||
this.messages = [];
|
||||
}
|
||||
|
||||
async load() {
|
||||
console.log('Loading...', this.whoami);
|
||||
let results = await tfrpc.rpc.query(
|
||||
`
|
||||
SELECT messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
|
||||
FROM messages_fts(?)
|
||||
JOIN messages ON messages.rowid = messages_fts.rowid
|
||||
JOIN json_each(?) AS following ON messages.author = following.value
|
||||
WHERE messages.author != ?
|
||||
ORDER BY timestamp DESC limit 20
|
||||
`,
|
||||
[
|
||||
'"' + this.whoami.replace('"', '""') + '"',
|
||||
JSON.stringify(this.following),
|
||||
this.whoami,
|
||||
]
|
||||
);
|
||||
console.log('Done.');
|
||||
this.messages = results;
|
||||
}
|
||||
|
||||
on_expand(event) {
|
||||
if (event.detail.expanded) {
|
||||
let expand = {};
|
||||
expand[event.detail.id] = true;
|
||||
this.expanded = Object.assign({}, this.expanded, expand);
|
||||
} else {
|
||||
delete this.expanded[event.detail.id];
|
||||
this.expanded = Object.assign({}, this.expanded);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
let self = this;
|
||||
if (!this.loading) {
|
||||
this.loading = true;
|
||||
this.load();
|
||||
}
|
||||
return html`
|
||||
<tf-news
|
||||
id="news"
|
||||
whoami=${this.whoami}
|
||||
.messages=${this.messages}
|
||||
.users=${this.users}
|
||||
.expanded=${this.expanded}
|
||||
@tf-expand=${this.on_expand}
|
||||
></tf-news>
|
||||
`;
|
||||
}
|
||||
}
|
||||
customElements.define('tf-tab-mentions', TfTabMentionsElement);
|
||||
@@ -1,6 +1,6 @@
|
||||
import {LitElement, html, unsafeHTML, until} from './lit-all.min.js';
|
||||
import {LitElement, cache, html, unsafeHTML, until} from './lit-all.min.js';
|
||||
import * as tfrpc from '/static/tfrpc.js';
|
||||
import {styles} from './tf-styles.js';
|
||||
import {styles, generate_theme} from './tf-styles.js';
|
||||
|
||||
class TfTabNewsFeedElement extends LitElement {
|
||||
static get properties() {
|
||||
@@ -12,6 +12,14 @@ class TfTabNewsFeedElement extends LitElement {
|
||||
messages: {type: Array},
|
||||
drafts: {type: Object},
|
||||
expanded: {type: Object},
|
||||
channels_unread: {type: Object},
|
||||
channels_latest: {type: Object},
|
||||
loading: {type: Number},
|
||||
time_range: {type: Array},
|
||||
time_loading: {type: Array},
|
||||
private_messages: {type: Array},
|
||||
grouped_private_messages: {type: Object},
|
||||
recent_reactions: {type: Array},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -26,112 +34,308 @@ class TfTabNewsFeedElement extends LitElement {
|
||||
this.following = [];
|
||||
this.drafts = {};
|
||||
this.expanded = {};
|
||||
this.start_time = new Date().valueOf() - 24 * 60 * 60 * 1000;
|
||||
this.channels_unread = {};
|
||||
this.channels_latest = {};
|
||||
this.start_time = new Date().valueOf();
|
||||
this.time_range = [0, 0];
|
||||
this.time_loading = undefined;
|
||||
this.recent_reactions = [];
|
||||
this.loading = 0;
|
||||
}
|
||||
|
||||
async fetch_messages() {
|
||||
if (this.hash.startsWith('#@')) {
|
||||
let r = await tfrpc.rpc.query(
|
||||
channel() {
|
||||
return this.hash.startsWith('##')
|
||||
? this.hash.substring(2)
|
||||
: this.hash.substring(1);
|
||||
}
|
||||
|
||||
async _fetch_related_messages(messages) {
|
||||
let refs = await tfrpc.rpc.query(
|
||||
`
|
||||
WITH
|
||||
news AS (
|
||||
SELECT value AS id FROM json_each(?)
|
||||
)
|
||||
SELECT refs_out.ref AS ref FROM messages_refs refs_out JOIN news ON refs_out.message = news.id
|
||||
UNION
|
||||
SELECT refs_in.message AS ref FROM messages_refs refs_in JOIN news ON refs_in.ref = news.id
|
||||
`,
|
||||
[JSON.stringify(messages.map((x) => x.id))]
|
||||
);
|
||||
let related_messages = await tfrpc.rpc.query(
|
||||
`
|
||||
SELECT FALSE AS is_primary, messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
|
||||
FROM messages
|
||||
JOIN json_each(?2) refs ON messages.id = refs.value
|
||||
JOIN json_each(?1) AS following ON messages.author = following.value
|
||||
`,
|
||||
[JSON.stringify(this.following), JSON.stringify(refs.map((x) => x.ref))]
|
||||
);
|
||||
let combined = [].concat(messages, related_messages);
|
||||
let refs2 = await tfrpc.rpc.query(
|
||||
`
|
||||
WITH
|
||||
news AS (
|
||||
SELECT value AS id FROM json_each(?)
|
||||
)
|
||||
SELECT refs_out.ref AS ref FROM messages_refs refs_out JOIN news ON refs_out.message = news.id
|
||||
UNION
|
||||
SELECT refs_in.message AS ref FROM messages_refs refs_in JOIN news ON refs_in.ref = news.id
|
||||
`,
|
||||
[JSON.stringify(combined.map((x) => x.id))]
|
||||
);
|
||||
let t0 = new Date();
|
||||
let result = [].concat(
|
||||
combined,
|
||||
await tfrpc.rpc.query(
|
||||
`
|
||||
WITH mine AS (SELECT id, previous, author, sequence, timestamp, hash, json(content) AS content, signature
|
||||
FROM messages
|
||||
WHERE messages.author = ?
|
||||
ORDER BY sequence DESC
|
||||
LIMIT 20)
|
||||
SELECT messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
|
||||
FROM mine
|
||||
JOIN messages_refs ON mine.id = messages_refs.ref
|
||||
SELECT FALSE AS is_primary, messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
|
||||
FROM json_each(?2) refs
|
||||
JOIN messages ON messages.id = refs.value
|
||||
JOIN json_each(?1) following ON messages.author = following.value
|
||||
WHERE messages.content ->> 'type' != 'post'
|
||||
`,
|
||||
[
|
||||
JSON.stringify(this.following),
|
||||
JSON.stringify(refs2.map((x) => x.ref)),
|
||||
]
|
||||
)
|
||||
);
|
||||
let t1 = new Date();
|
||||
console.log((t1 - t0) / 1000);
|
||||
return result;
|
||||
}
|
||||
|
||||
async fetch_messages(start_time, end_time) {
|
||||
this.dispatchEvent(
|
||||
new CustomEvent('loadmessages', {
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
})
|
||||
);
|
||||
this.time_loading = [start_time, end_time];
|
||||
let result;
|
||||
const k_max_results = 64;
|
||||
if (this.hash == '#@') {
|
||||
result = await tfrpc.rpc.query(
|
||||
`
|
||||
WITH mentions AS (SELECT messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
|
||||
FROM messages_fts(?1)
|
||||
JOIN messages ON messages.rowid = messages_fts.rowid
|
||||
JOIN json_each(?2) AS following ON messages.author = following.value
|
||||
WHERE
|
||||
messages.author != ?1 AND
|
||||
(?3 IS NULL OR messages.timestamp >= ?3) AND messages.timestamp < ?4
|
||||
ORDER BY timestamp DESC limit ?5)
|
||||
SELECT FALSE AS is_primary, messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
|
||||
FROM mentions
|
||||
JOIN messages_refs ON mentions.id = messages_refs.ref
|
||||
JOIN messages ON messages_refs.message = messages.id
|
||||
UNION
|
||||
SELECT * FROM mine
|
||||
SELECT TRUE AS is_primary, * FROM mentions
|
||||
`,
|
||||
[this.hash.substring(1)]
|
||||
[
|
||||
'"' + this.whoami.replace('"', '""') + '"',
|
||||
JSON.stringify(this.following),
|
||||
start_time,
|
||||
end_time,
|
||||
k_max_results,
|
||||
]
|
||||
);
|
||||
return r;
|
||||
} else if (this.hash.startsWith('#%')) {
|
||||
return await tfrpc.rpc.query(
|
||||
} else if (this.hash.startsWith('#@')) {
|
||||
result = await tfrpc.rpc.query(
|
||||
`
|
||||
SELECT id, previous, author, sequence, timestamp, hash, json(content) AS content, signature
|
||||
FROM messages
|
||||
WHERE id = ?1
|
||||
WITH
|
||||
selected AS (SELECT rowid, id, previous, author, sequence, timestamp, hash, json(content) AS content, signature
|
||||
FROM messages
|
||||
WHERE messages.author = ?1 AND (?2 IS NULL OR messages.timestamp >= 2) AND messages.timestamp < ?3
|
||||
ORDER BY sequence DESC LIMIT ?4
|
||||
)
|
||||
SELECT FALSE AS is_primary, messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
|
||||
FROM selected
|
||||
JOIN messages_refs ON selected.id = messages_refs.ref
|
||||
JOIN messages ON messages_refs.message = messages.id
|
||||
UNION
|
||||
SELECT id, previous, author, sequence, timestamp, hash, json(content) AS content, signature
|
||||
SELECT TRUE AS is_primary, * FROM selected
|
||||
`,
|
||||
[this.hash.substring(1), start_time, end_time, k_max_results]
|
||||
);
|
||||
} else if (this.hash.startsWith('#%')) {
|
||||
result = await tfrpc.rpc.query(
|
||||
`
|
||||
SELECT TRUE AS is_primary, id, previous, author, sequence, timestamp, hash, json(content) AS content, signature
|
||||
FROM messages
|
||||
WHERE messages.id = ?1
|
||||
UNION
|
||||
SELECT FALSE AS is_primary, id, previous, author, sequence, timestamp, hash, json(content) AS content, signature
|
||||
FROM messages JOIN messages_refs
|
||||
ON messages.id = messages_refs.message
|
||||
WHERE messages_refs.ref = ?1
|
||||
`,
|
||||
[this.hash.substring(1)]
|
||||
);
|
||||
} else {
|
||||
let promises = [];
|
||||
const k_following_limit = 256;
|
||||
for (let i = 0; i < this.following.length; i += k_following_limit) {
|
||||
promises.push(
|
||||
tfrpc.rpc.query(
|
||||
`
|
||||
WITH news AS (SELECT messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
|
||||
} else if (this.hash.startsWith('##')) {
|
||||
let t0 = new Date();
|
||||
let initial_messages = await tfrpc.rpc.query(
|
||||
`
|
||||
WITH
|
||||
all_news AS (
|
||||
SELECT messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
|
||||
FROM messages
|
||||
JOIN json_each(?) AS following ON messages.author = following.value
|
||||
WHERE messages.content ->> 'channel' = ?4 AND messages.content ->> 'type' != 'vote'
|
||||
UNION
|
||||
SELECT messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
|
||||
FROM messages_refs
|
||||
JOIN messages ON messages.id = messages_refs.message
|
||||
JOIN json_each(?1) AS following ON messages.author = following.value
|
||||
WHERE messages_refs.ref = '#' || ?4 AND messages.content ->> 'type' != 'vote'
|
||||
)
|
||||
SELECT TRUE AS is_primary, all_news.* FROM all_news
|
||||
WHERE (?2 IS NULL OR all_news.timestamp >= ?2) AND all_news.timestamp < ?3
|
||||
ORDER BY all_news.timestamp DESC LIMIT ?5
|
||||
`,
|
||||
[
|
||||
JSON.stringify(this.following),
|
||||
start_time,
|
||||
end_time,
|
||||
this.hash.substring(2),
|
||||
k_max_results,
|
||||
]
|
||||
);
|
||||
let t1 = new Date();
|
||||
result = await this._fetch_related_messages(initial_messages);
|
||||
let t2 = new Date();
|
||||
console.log(
|
||||
`load of ${result.length} rows took ${(t2 - t0) / 1000} (${(t1 - t0) / 1000} to find ${initial_messages.length} initial messages, ${(t2 - t1) / 1000} to find ${result.length} total messages) following=${this.following.length} st=${start_time} et=${end_time}`
|
||||
);
|
||||
} else if (this.hash.startsWith('#🔐')) {
|
||||
let ids =
|
||||
this.hash == '#🔐' ? [] : this.hash.substring('#🔐'.length).split(',');
|
||||
result = await tfrpc.rpc.query(
|
||||
`
|
||||
SELECT TRUE AS is_primary, messages.rowid, messages.id, previous, author, sequence, timestamp, hash, json(content) AS content, signature
|
||||
FROM messages
|
||||
JOIN json_each(?1) AS private_messages ON messages.id = private_messages.value
|
||||
WHERE
|
||||
(?2 IS NULL OR (messages.timestamp >= ?2)) AND messages.timestamp < ?3 AND
|
||||
json(messages.content) LIKE '"%'
|
||||
ORDER BY messages.rowid DESC LIMIT ?4
|
||||
`,
|
||||
[
|
||||
JSON.stringify(
|
||||
this.grouped_private_messages?.[JSON.stringify(ids)]?.map(
|
||||
(x) => x.id
|
||||
) ?? []
|
||||
),
|
||||
start_time,
|
||||
end_time,
|
||||
k_max_results,
|
||||
]
|
||||
);
|
||||
result = (await this.decrypt(result)).filter((x) => x.decrypted);
|
||||
} else if (this.hash == '#👍') {
|
||||
result = await tfrpc.rpc.query(
|
||||
`
|
||||
WITH votes AS (SELECT messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
|
||||
FROM messages
|
||||
JOIN json_each(?) AS following ON messages.author = following.value
|
||||
WHERE messages.timestamp > ? AND messages.timestamp < ?
|
||||
ORDER BY messages.timestamp DESC)
|
||||
SELECT messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
|
||||
FROM news
|
||||
JOIN messages_refs ON news.id = messages_refs.ref
|
||||
JOIN messages ON messages_refs.message = messages.id
|
||||
UNION
|
||||
SELECT messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
|
||||
FROM news
|
||||
JOIN messages_refs ON news.id = messages_refs.message
|
||||
JOIN messages ON messages_refs.ref = messages.id
|
||||
UNION
|
||||
SELECT news.* FROM news
|
||||
`,
|
||||
[
|
||||
JSON.stringify(this.following.slice(i, i + k_following_limit)),
|
||||
this.start_time,
|
||||
/*
|
||||
** Don't show messages more than a day into the future to prevent
|
||||
** messages with far-future timestamps from staying at the top forever.
|
||||
*/
|
||||
new Date().valueOf() + 24 * 60 * 60 * 1000,
|
||||
]
|
||||
)
|
||||
);
|
||||
}
|
||||
return [].concat(...(await Promise.all(promises)));
|
||||
JOIN json_each(?1) AS following ON messages.author = following.value
|
||||
WHERE
|
||||
messages.content ->> 'type' = 'vote' AND
|
||||
(?2 IS NULL OR messages.timestamp >= ?2) AND messages.timestamp < ?3
|
||||
ORDER BY timestamp DESC limit ?4)
|
||||
SELECT FALSE AS is_primary, messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
|
||||
FROM votes
|
||||
JOIN messages ON messages.id = votes.content ->> '$.vote.link'
|
||||
UNION
|
||||
SELECT TRUE AS is_primary, * FROM votes
|
||||
`,
|
||||
[JSON.stringify(this.following), start_time, end_time, k_max_results]
|
||||
);
|
||||
} else {
|
||||
let t0 = new Date();
|
||||
let initial_messages = await tfrpc.rpc.query(
|
||||
`
|
||||
WITH
|
||||
channels AS (SELECT '#' || value AS value FROM json_each(?5))
|
||||
SELECT TRUE AS is_primary, messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
|
||||
FROM messages
|
||||
JOIN json_each(?1) AS following ON messages.author = following.value
|
||||
WHERE messages.timestamp < ?3 AND (?2 IS NULL OR messages.timestamp >= ?2) AND
|
||||
messages.content ->> 'type' != 'vote' AND
|
||||
(messages.content ->> 'root' IS NULL OR (
|
||||
NOT EXISTS (SELECT * FROM messages root JOIN channels ON ('#' || (root.content ->> 'channel')) = channels.value WHERE root.id = messages.content ->> 'root') AND
|
||||
NOT EXISTS (SELECT * FROM messages root JOIN messages_refs ON root.id = messages.content ->> 'root' JOIN channels ON messages_refs.message = root.id AND messages_refs.ref = channels.value)
|
||||
)) AND
|
||||
(messages.content ->> 'channel' IS NULL OR ('#' || (messages.content ->> 'channel')) NOT IN (SELECT * FROM channels)) AND
|
||||
NOT EXISTS (SELECT * FROM messages_refs JOIN channels ON messages_refs.message = messages.id AND messages_refs.ref = channels.value)
|
||||
ORDER BY timestamp DESC LIMIT ?4
|
||||
`,
|
||||
[
|
||||
JSON.stringify(this.following),
|
||||
start_time,
|
||||
end_time,
|
||||
k_max_results,
|
||||
JSON.stringify(Object.keys(this.channels_latest)),
|
||||
]
|
||||
);
|
||||
let t1 = new Date();
|
||||
result = await this._fetch_related_messages(initial_messages);
|
||||
let t2 = new Date();
|
||||
console.log(
|
||||
`load of ${result.length} rows took ${(t2 - t0) / 1000} (${(t1 - t0) / 1000} to find ${initial_messages.length} initial messages, ${(t2 - t1) / 1000} to find ${result.length} total messages) following=${this.following.length} st=${start_time} et=${end_time}`
|
||||
);
|
||||
}
|
||||
this.time_loading = undefined;
|
||||
return result;
|
||||
}
|
||||
|
||||
update_time_range_from_messages(messages) {
|
||||
let only_primary = messages.filter((x) => x.is_primary);
|
||||
this.time_range = [
|
||||
only_primary.reduce(
|
||||
(accumulator, current) => Math.min(accumulator, current.timestamp),
|
||||
this.time_range[0]
|
||||
),
|
||||
only_primary.reduce(
|
||||
(accumulator, current) => Math.max(accumulator, current.timestamp),
|
||||
this.time_range[1]
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
unread_allowed() {
|
||||
return (
|
||||
this.hash == '#@' ||
|
||||
(!this.hash.startsWith('#%') && !this.hash.startsWith('#@'))
|
||||
);
|
||||
}
|
||||
|
||||
async load_more() {
|
||||
let last_start_time = this.start_time;
|
||||
this.start_time = last_start_time - 24 * 60 * 60 * 1000;
|
||||
let more = await tfrpc.rpc.query(
|
||||
`
|
||||
WITH news AS (SELECT messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
|
||||
FROM messages
|
||||
JOIN json_each(?) AS following ON messages.author = following.value
|
||||
WHERE messages.timestamp > ?
|
||||
AND messages.timestamp <= ?
|
||||
ORDER BY messages.timestamp DESC)
|
||||
SELECT messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
|
||||
FROM news
|
||||
JOIN messages_refs ON news.id = messages_refs.ref
|
||||
JOIN messages ON messages_refs.message = messages.id
|
||||
UNION
|
||||
SELECT messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
|
||||
FROM news
|
||||
JOIN messages_refs ON news.id = messages_refs.message
|
||||
JOIN messages ON messages_refs.ref = messages.id
|
||||
UNION
|
||||
SELECT news.* FROM news
|
||||
`,
|
||||
[JSON.stringify(this.following), this.start_time, last_start_time]
|
||||
);
|
||||
this.messages = await this.decrypt([...more, ...this.messages]);
|
||||
this.loading++;
|
||||
this.loading_canceled = false;
|
||||
try {
|
||||
let more = [];
|
||||
let last_start_time = this.time_range[0];
|
||||
try {
|
||||
more = await this.fetch_messages(null, last_start_time);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
this.update_time_range_from_messages(
|
||||
more.filter((x) => x.timestamp < last_start_time)
|
||||
);
|
||||
this.messages = await this.decrypt([...more, ...this.messages]);
|
||||
} finally {
|
||||
this.loading--;
|
||||
}
|
||||
}
|
||||
|
||||
cancel_load() {
|
||||
this.loading_canceled = true;
|
||||
}
|
||||
|
||||
async decrypt(messages) {
|
||||
console.log('decrypt');
|
||||
let result = [];
|
||||
for (let message of messages) {
|
||||
let content;
|
||||
@@ -156,44 +360,200 @@ class TfTabNewsFeedElement extends LitElement {
|
||||
return result;
|
||||
}
|
||||
|
||||
async add_messages(messages) {
|
||||
this.messages = await this.decrypt([...messages, ...this.messages]);
|
||||
merge_messages(old_messages, new_messages) {
|
||||
let old_by_id = Object.fromEntries(old_messages.map((x) => [x.id, x]));
|
||||
return new_messages.map((x) => (old_by_id[x.id] ? old_by_id[x.id] : x));
|
||||
}
|
||||
|
||||
async load_latest() {
|
||||
this.loading++;
|
||||
let now = new Date().valueOf();
|
||||
let end_time = now + 24 * 60 * 60 * 1000;
|
||||
let messages = [];
|
||||
try {
|
||||
messages = await this.fetch_messages(this.time_range[0], end_time);
|
||||
messages = await this.decrypt(messages);
|
||||
this.update_time_range_from_messages(
|
||||
messages.filter(
|
||||
(x) => x.timestamp >= this.time_range[0] && x.timestamp < end_time
|
||||
)
|
||||
);
|
||||
} finally {
|
||||
this.loading--;
|
||||
}
|
||||
this.messages = this.merge_messages(
|
||||
this.messages,
|
||||
Object.values(
|
||||
Object.fromEntries(
|
||||
[...this.messages, ...messages]
|
||||
.sort((x, y) => x.timestamp - y.timestamp)
|
||||
.slice(-1024)
|
||||
.map((x) => [x.id, x])
|
||||
)
|
||||
)
|
||||
);
|
||||
console.log('done loading latest messages.');
|
||||
}
|
||||
|
||||
async load_messages() {
|
||||
let start_time = new Date();
|
||||
let self = this;
|
||||
this.loading++;
|
||||
let messages = [];
|
||||
let original_hash = this.hash;
|
||||
try {
|
||||
if (this._messages_hash !== this.hash) {
|
||||
this.messages = [];
|
||||
this._messages_hash = this.hash;
|
||||
}
|
||||
this._messages_following = JSON.stringify(this.following);
|
||||
this._private_messages =
|
||||
JSON.stringify(this.private_messages) +
|
||||
JSON.stringify(this.grouped_private_messages);
|
||||
this._channels_latest = JSON.stringify(
|
||||
Object.keys(this.channels_latest ?? {})
|
||||
);
|
||||
let now = new Date().valueOf();
|
||||
let start_time = now - 24 * 60 * 60 * 1000;
|
||||
this.start_time = start_time;
|
||||
this.time_range = [now + 24 * 60 * 60 * 1000, now + 24 * 60 * 60 * 1000];
|
||||
messages = await this.fetch_messages(null, this.time_range[1]);
|
||||
this.update_time_range_from_messages(
|
||||
messages.filter((x) => x.timestamp < this.time_range[1])
|
||||
);
|
||||
messages = await this.decrypt(messages);
|
||||
} finally {
|
||||
this.loading--;
|
||||
}
|
||||
if (this.hash == original_hash) {
|
||||
this.messages = this.merge_messages(this.messages, messages);
|
||||
}
|
||||
this.time_loading = undefined;
|
||||
console.log(
|
||||
`loading ${messages.length} messages done for ${self.whoami} in ${(new Date() - start_time) / 1000}s`
|
||||
);
|
||||
}
|
||||
|
||||
mark_all_read() {
|
||||
let newest = this.messages.reduce(
|
||||
(accumulator, current) => Math.max(accumulator, current.rowid),
|
||||
this.channels_latest[this.channel()] ?? -1
|
||||
);
|
||||
if (newest >= 0) {
|
||||
this.dispatchEvent(
|
||||
new CustomEvent('channelsetunread', {
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
detail: {
|
||||
channel: this.channel(),
|
||||
unread: newest + 1,
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
close_private_chat() {
|
||||
this.mark_all_read();
|
||||
this.dispatchEvent(
|
||||
new CustomEvent('closeprivatechat', {
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
detail: {
|
||||
key: JSON.stringify(
|
||||
this.hash == '#🔐'
|
||||
? []
|
||||
: this.hash.substring('#🔐'.length).split(',')
|
||||
),
|
||||
},
|
||||
})
|
||||
);
|
||||
tfrpc.rpc.setHash('#');
|
||||
}
|
||||
|
||||
render_close_chat_button() {
|
||||
if (this.hash.startsWith('#🔐')) {
|
||||
return html`
|
||||
<button class="w3-button w3-theme-d1" @click=${this.close_private_chat}>
|
||||
Close Chat
|
||||
</button>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
if (
|
||||
!this.messages ||
|
||||
this._messages_hash !== this.hash ||
|
||||
this._messages_following !== this.following
|
||||
this._messages_following !== JSON.stringify(this.following) ||
|
||||
this._private_messages !==
|
||||
JSON.stringify(this.private_messages) +
|
||||
JSON.stringify(this.grouped_private_messages) ||
|
||||
this._channels_latest !==
|
||||
JSON.stringify(Object.keys(this.channels_latest))
|
||||
) {
|
||||
console.log(
|
||||
`loading messages for ${this.whoami} (following ${this.following.length})`
|
||||
);
|
||||
let self = this;
|
||||
this.messages = [];
|
||||
this._messages_hash = this.hash;
|
||||
this._messages_following = this.following;
|
||||
this.fetch_messages()
|
||||
.then(this.decrypt.bind(this))
|
||||
.then(function (messages) {
|
||||
self.messages = messages;
|
||||
console.log(`loading mesages done for ${self.whoami}`);
|
||||
})
|
||||
.catch(function (error) {
|
||||
alert(JSON.stringify(error, null, 2));
|
||||
});
|
||||
this.load_messages();
|
||||
}
|
||||
let more;
|
||||
if (!this.hash.startsWith('#@') && !this.hash.startsWith('#%')) {
|
||||
if (!this.hash.startsWith('#%')) {
|
||||
more = html`
|
||||
<p>
|
||||
<button class="w3-button w3-theme-d1" @click=${this.load_more}>
|
||||
${this.unread_allowed()
|
||||
? html`
|
||||
<button
|
||||
class="w3-button w3-theme-d1"
|
||||
@click=${this.mark_all_read}
|
||||
>
|
||||
Mark All Read
|
||||
</button>
|
||||
`
|
||||
: undefined}
|
||||
<button
|
||||
?disabled=${this.loading}
|
||||
class="w3-button w3-theme-d1"
|
||||
@click=${this.load_more}
|
||||
>
|
||||
Load More
|
||||
</button>
|
||||
<button
|
||||
class=${'w3-button w3-theme-d1' + (this.loading ? '' : ' w3-hide')}
|
||||
@click=${this.cancel_load}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<span
|
||||
>Showing
|
||||
${new Date(
|
||||
this.time_loading
|
||||
? Math.min(this.time_loading[0], this.time_range[0])
|
||||
: this.time_range[0]
|
||||
).toLocaleDateString()}
|
||||
-
|
||||
${new Date(
|
||||
this.time_loading
|
||||
? Math.max(this.time_loading[1], this.time_range[1])
|
||||
: this.time_range[1]
|
||||
).toLocaleDateString()}.</span
|
||||
>
|
||||
</p>
|
||||
`;
|
||||
}
|
||||
return html`
|
||||
return cache(html`
|
||||
<style>
|
||||
${generate_theme()}
|
||||
</style>
|
||||
${this.unread_allowed()
|
||||
? html`<button
|
||||
class="w3-button w3-theme-d1"
|
||||
@click=${this.mark_all_read}
|
||||
>
|
||||
Mark All Read
|
||||
</button>`
|
||||
: undefined}
|
||||
${this.render_close_chat_button()}
|
||||
<tf-news
|
||||
id="news"
|
||||
whoami=${this.whoami}
|
||||
@@ -202,9 +562,14 @@ class TfTabNewsFeedElement extends LitElement {
|
||||
.following=${this.following}
|
||||
.drafts=${this.drafts}
|
||||
.expanded=${this.expanded}
|
||||
hash=${this.hash}
|
||||
channel=${this.channel()}
|
||||
channel_unread=${this.channels_unread?.[this.channel()]}
|
||||
.recent_reactions=${this.recent_reactions}
|
||||
@mark_all_read=${this.mark_all_read}
|
||||
></tf-news>
|
||||
${more}
|
||||
`;
|
||||
`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
import {LitElement, html, unsafeHTML, until} from './lit-all.min.js';
|
||||
import {
|
||||
LitElement,
|
||||
cache,
|
||||
keyed,
|
||||
html,
|
||||
unsafeHTML,
|
||||
until,
|
||||
} from './lit-all.min.js';
|
||||
import * as tfrpc from '/static/tfrpc.js';
|
||||
import {styles} from './tf-styles.js';
|
||||
import {styles, generate_theme} from './tf-styles.js';
|
||||
|
||||
class TfTabNewsElement extends LitElement {
|
||||
static get properties() {
|
||||
@@ -8,11 +15,21 @@ class TfTabNewsElement extends LitElement {
|
||||
whoami: {type: String},
|
||||
users: {type: Object},
|
||||
hash: {type: String},
|
||||
unread: {type: Array},
|
||||
following: {type: Array},
|
||||
drafts: {type: Object},
|
||||
expanded: {type: Object},
|
||||
loading: {type: Boolean},
|
||||
channels: {type: Array},
|
||||
channels_unread: {type: Object},
|
||||
channels_latest: {type: Object},
|
||||
connections: {type: Array},
|
||||
private_messages: {type: Array},
|
||||
grouped_private_messages: {type: Object},
|
||||
visible_private_messages: {type: Object},
|
||||
recent_reactions: {type: Array},
|
||||
peer_exchange: {type: Boolean},
|
||||
is_administrator: {type: Boolean},
|
||||
stay_connected: {type: Boolean},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -24,14 +41,19 @@ class TfTabNewsElement extends LitElement {
|
||||
this.whoami = null;
|
||||
this.users = {};
|
||||
this.hash = '#';
|
||||
this.unread = [];
|
||||
this.following = [];
|
||||
this.cache = {};
|
||||
this.drafts = {};
|
||||
this.expanded = {};
|
||||
this.channels_unread = {};
|
||||
this.channels_latest = {};
|
||||
this.channels = [];
|
||||
this.connections = [];
|
||||
this.recent_reactions = [];
|
||||
tfrpc.rpc.localStorageGet('drafts').then(function (d) {
|
||||
self.drafts = JSON.parse(d || '{}');
|
||||
});
|
||||
this.check_peer_exchange();
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
@@ -44,37 +66,19 @@ class TfTabNewsElement extends LitElement {
|
||||
document.body.removeEventListener('keypress', this.on_keypress.bind(this));
|
||||
}
|
||||
|
||||
show_more() {
|
||||
let unread = this.unread;
|
||||
let news = this.shadowRoot?.getElementById('news');
|
||||
if (news) {
|
||||
console.log('injecting messages', news.messages);
|
||||
news.add_messages(
|
||||
Object.values(Object.fromEntries(this.unread.map((x) => [x.id, x])))
|
||||
);
|
||||
this.dispatchEvent(new CustomEvent('refresh'));
|
||||
async check_peer_exchange() {
|
||||
if (await tfrpc.rpc.isAdministrator()) {
|
||||
this.peer_exchange = await tfrpc.rpc.globalSettingsGet('peer_exchange');
|
||||
} else {
|
||||
this.peer_exchange = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
new_messages_text() {
|
||||
if (!this.unread?.length) {
|
||||
return 'No new messages.';
|
||||
load_latest() {
|
||||
let news = this.shadowRoot?.getElementById('news');
|
||||
if (news) {
|
||||
news.load_latest();
|
||||
}
|
||||
let counts = {};
|
||||
for (let message of this.unread) {
|
||||
let type = 'private';
|
||||
try {
|
||||
type = JSON.parse(message.content).type || type;
|
||||
} catch {}
|
||||
counts[type] = (counts[type] || 0) + 1;
|
||||
}
|
||||
return (
|
||||
'↻ Show New: ' +
|
||||
Object.keys(counts)
|
||||
.sort()
|
||||
.map((x) => counts[x].toString() + ' ' + x + 's')
|
||||
.join(', ')
|
||||
);
|
||||
}
|
||||
|
||||
draft(event) {
|
||||
@@ -106,14 +110,278 @@ class TfTabNewsElement extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
unread_status(channel) {
|
||||
if (channel === undefined) {
|
||||
if (
|
||||
Object.keys(this.channels_unread).some((x) => this.unread_status(x))
|
||||
) {
|
||||
return '✉️ ';
|
||||
}
|
||||
} else if (channel?.startsWith('🔐')) {
|
||||
let key = JSON.stringify(channel.substring('🔐'.length).split(','));
|
||||
if (this.grouped_private_messages?.[key]) {
|
||||
let grouped_latest = Math.max(
|
||||
...this.grouped_private_messages?.[key]?.map((x) => x.rowid)
|
||||
);
|
||||
if (
|
||||
this.channels_unread[channel] === undefined ||
|
||||
grouped_latest > this.channels_unread[channel]
|
||||
) {
|
||||
return '✉️ ';
|
||||
}
|
||||
}
|
||||
} else if (
|
||||
this.channels_latest[channel] &&
|
||||
this.channels_latest[channel] > 0 &&
|
||||
(this.channels_unread[channel] === undefined ||
|
||||
this.channels_unread[channel] <= this.channels_latest[channel])
|
||||
) {
|
||||
return '✉️ ';
|
||||
}
|
||||
}
|
||||
|
||||
show_sidebar() {
|
||||
this.renderRoot.getElementById('sidebar').style.display = 'block';
|
||||
this.renderRoot.getElementById('sidebar_overlay').style.display = 'block';
|
||||
}
|
||||
|
||||
hide_sidebar() {
|
||||
this.renderRoot.getElementById('sidebar').style.display = 'none';
|
||||
this.renderRoot.getElementById('sidebar_overlay').style.display = 'none';
|
||||
}
|
||||
|
||||
async channel_toggle_subscribed() {
|
||||
let channel = this.hash.substring(2);
|
||||
let subscribed = this.channels.indexOf(channel) != -1;
|
||||
subscribed = !subscribed;
|
||||
|
||||
await tfrpc.rpc.appendMessage(this.whoami, {
|
||||
type: 'channel',
|
||||
channel: channel,
|
||||
subscribed: subscribed,
|
||||
});
|
||||
if (subscribed) {
|
||||
this.channels = [].concat([channel], this.channels).sort();
|
||||
} else {
|
||||
this.channels = this.channels.filter((x) => x != channel);
|
||||
}
|
||||
}
|
||||
|
||||
channel() {
|
||||
return this.hash.startsWith('##') ? this.hash.substring(2) : undefined;
|
||||
}
|
||||
|
||||
compare_follows(a, b) {
|
||||
return b[1].ts > a[1].ts ? 1 : b[1].ts < a[1].ts ? -1 : 0;
|
||||
}
|
||||
|
||||
suggested_follows() {
|
||||
/*
|
||||
** Filter out people who have used future timestamps so that they aren't
|
||||
** pinned at the top.
|
||||
*/
|
||||
let self = this;
|
||||
let now = new Date().valueOf();
|
||||
return Object.entries(this.users)
|
||||
.filter((x) => x[1].ts < now)
|
||||
.filter((x) => x[1].follow_depth > 1)
|
||||
.sort(self.compare_follows)
|
||||
.slice(0, 8)
|
||||
.map((x) => x[0]);
|
||||
}
|
||||
|
||||
async enable_peer_exchange() {
|
||||
await tfrpc.rpc.globalSettingsSet('peer_exchange', true);
|
||||
await this.check_peer_exchange();
|
||||
}
|
||||
|
||||
is_loading() {
|
||||
return this.shadowRoot?.getElementById('news')?.loading;
|
||||
}
|
||||
|
||||
render_sidebar() {
|
||||
return html`
|
||||
<div
|
||||
class="w3-sidebar w3-bar-block w3-theme-d1 w3-collapse w3-animate-left"
|
||||
style="width: 2in; left: 0; z-index: 5; box-sizing: border-box; top: 0"
|
||||
id="sidebar"
|
||||
>
|
||||
<div
|
||||
class="w3-right w3-button w3-hide-large"
|
||||
@click=${this.hide_sidebar}
|
||||
>
|
||||
×
|
||||
</div>
|
||||
${this.hash.startsWith('##') &&
|
||||
this.channels.indexOf(this.hash.substring(2)) == -1
|
||||
? html`
|
||||
<div class="w3-bar-item w3-theme-d2">Viewing</div>
|
||||
<a
|
||||
href="#"
|
||||
class="w3-bar-item w3-button"
|
||||
style="font-weight: bold"
|
||||
>${this.hash.substring(1)}</a
|
||||
>
|
||||
`
|
||||
: undefined}
|
||||
<h4 class="w3-bar-item w3-theme-d2">Channels</h4>
|
||||
<a
|
||||
href="#"
|
||||
class="w3-bar-item w3-button"
|
||||
style=${this.hash == '#' ? 'font-weight: bold' : undefined}
|
||||
>${this.unread_status('')}general</a
|
||||
>
|
||||
<a
|
||||
href="#@"
|
||||
class="w3-bar-item w3-button"
|
||||
style=${this.hash == '#@' ? 'font-weight: bold' : undefined}
|
||||
>${this.unread_status('@')}@mentions</a
|
||||
>
|
||||
<a
|
||||
href="#👍"
|
||||
class="w3-bar-item w3-button"
|
||||
style=${this.hash == '#👍' ? 'font-weight: bold' : undefined}
|
||||
>${this.unread_status('👍')}👍votes</a
|
||||
>
|
||||
${Object.keys(this?.visible_private_messages ?? [])
|
||||
?.sort()
|
||||
?.map(
|
||||
(key) => html`
|
||||
<a
|
||||
href=${'#🔐' + JSON.parse(key).join(',')}
|
||||
class="w3-bar-item w3-button"
|
||||
style=${this.hash == '#🔐' + JSON.parse(key).join(',')
|
||||
? 'font-weight: bold'
|
||||
: undefined}
|
||||
>${this.unread_status('🔐' + JSON.parse(key).join(','))}
|
||||
${(key != '[]' ? JSON.parse(key) : [this.whoami]).map(
|
||||
(id) => html`
|
||||
<tf-user
|
||||
id=${id}
|
||||
nolink="true"
|
||||
.users=${this.users}
|
||||
></tf-user>
|
||||
`
|
||||
)}</a
|
||||
>
|
||||
`
|
||||
)}
|
||||
${Object.keys(this.drafts)
|
||||
.sort()
|
||||
.map(
|
||||
(x) => html`
|
||||
<a
|
||||
href=${'#' + encodeURIComponent(x)}
|
||||
class="w3-bar-item w3-button"
|
||||
style="text-wrap: nowrap; text-overflow: ellipsis"
|
||||
>📝 ${this.drafts[x]?.text ?? x}</a
|
||||
>
|
||||
`
|
||||
)}
|
||||
${this.channels.map(
|
||||
(x) => html`
|
||||
<a
|
||||
href=${'#' + encodeURIComponent('#' + x)}
|
||||
class="w3-bar-item w3-button"
|
||||
style=${this.hash == '##' + x ? 'font-weight: bold' : undefined}
|
||||
>${this.unread_status(x)}#${x}</a
|
||||
>
|
||||
`
|
||||
)}
|
||||
|
||||
<a class="w3-bar-item w3-theme-d2 w3-button" href="#connections">
|
||||
<h4 style="margin: 0">Connections</h4>
|
||||
</a>
|
||||
${this.connections?.filter((x) => x.id)?.length == 0
|
||||
? html`
|
||||
<button
|
||||
class=${'w3-bar-item w3-button' +
|
||||
(this.connections?.some((x) => x.flags.one_shot)
|
||||
? ' w3-spin'
|
||||
: '')}
|
||||
@click=${() =>
|
||||
this.dispatchEvent(
|
||||
new Event('refresh', {bubbles: true, composed: true})
|
||||
)}
|
||||
>
|
||||
↻ Sync now
|
||||
</button>
|
||||
<button
|
||||
class="w3-bar-item w3-button w3-ripple"
|
||||
@click=${() =>
|
||||
this.dispatchEvent(
|
||||
new Event('toggle_stay_connected', {
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
})
|
||||
)}
|
||||
>
|
||||
<span style="display: inline-block; width: 1.8em"
|
||||
>${this.stay_connected ? '🔗' : '⛓️💥'}</span
|
||||
>
|
||||
${this.stay_connected ? 'Online mode' : 'Passive mode'}
|
||||
</button>
|
||||
<button
|
||||
class=${'w3-bar-item w3-button w3-border w3-leftbar w3-rightbar' +
|
||||
(this.peer_exchange !== false ? ' w3-hide' : '')}
|
||||
@click=${this.enable_peer_exchange}
|
||||
>
|
||||
🔍🌐 Use publicly advertised peers
|
||||
</button>
|
||||
`
|
||||
: undefined}
|
||||
${this.connections
|
||||
.filter((x) => x.id)
|
||||
.map(
|
||||
(x) => html`
|
||||
<tf-user
|
||||
class="w3-bar-item"
|
||||
style=${x.destroy_reason
|
||||
? 'border-left: 4px solid red; border-right: 4px solid red'
|
||||
: x.connected
|
||||
? x.flags?.one_shot
|
||||
? 'border-left: 4px solid blue; border-right: 4px solid blue'
|
||||
: 'border-left: 4px solid green; border-right: 4px solid green'
|
||||
: ''}
|
||||
id=${x.id}
|
||||
fallback_name=${x.host}
|
||||
.users=${this.users}
|
||||
></tf-user>
|
||||
`
|
||||
)}
|
||||
<h4 class="w3-bar-item w3-theme-d2">Suggested Follows</h4>
|
||||
${this.suggested_follows().map(
|
||||
(x) => html`
|
||||
<tf-user
|
||||
class="w3-bar-item"
|
||||
style="max-width: 100%"
|
||||
id=${x}
|
||||
.users=${this.users}
|
||||
></tf-user>
|
||||
`
|
||||
)}
|
||||
</div>
|
||||
<div
|
||||
class="w3-overlay"
|
||||
id="sidebar_overlay"
|
||||
@click=${this.hide_sidebar}
|
||||
></div>
|
||||
`;
|
||||
}
|
||||
|
||||
render() {
|
||||
let profile = this.hash.startsWith('#@')
|
||||
? html`<tf-profile
|
||||
id=${this.hash.substring(1)}
|
||||
whoami=${this.whoami}
|
||||
.users=${this.users}
|
||||
></tf-profile>`
|
||||
: undefined;
|
||||
let profile =
|
||||
this.hash.startsWith('#@') && this.hash != '#@'
|
||||
? keyed(
|
||||
this.hash.substring(1),
|
||||
html`<tf-profile
|
||||
class="tf-profile"
|
||||
id=${this.hash.substring(1)}
|
||||
whoami=${this.whoami}
|
||||
.users=${this.users}
|
||||
></tf-profile>`
|
||||
)
|
||||
: undefined;
|
||||
let edit_profile;
|
||||
if (
|
||||
!this.loading &&
|
||||
@@ -127,41 +395,75 @@ class TfTabNewsElement extends LitElement {
|
||||
name.
|
||||
</div>`;
|
||||
}
|
||||
return html`
|
||||
<p class="w3-bar">
|
||||
<button
|
||||
class="w3-bar-item w3-button w3-theme-d1"
|
||||
@click=${this.show_more}
|
||||
>
|
||||
${this.new_messages_text()}
|
||||
</button>
|
||||
</p>
|
||||
<div class="w3-bar">
|
||||
Welcome, <tf-user id=${this.whoami} .users=${this.users}></tf-user>!
|
||||
${edit_profile}
|
||||
return cache(html`
|
||||
<style>
|
||||
${generate_theme()}
|
||||
</style>
|
||||
${this.render_sidebar()}
|
||||
<div
|
||||
style="margin-left: 2in; padding: 0px; top: 0; height: 100vh; max-height: 100%; overflow: auto; contain: layout"
|
||||
id="main"
|
||||
class="w3-main"
|
||||
>
|
||||
<div style="padding: 8px">
|
||||
<p>
|
||||
${this.hash.startsWith('##')
|
||||
? html`
|
||||
<button
|
||||
class="w3-button w3-theme-d1"
|
||||
@click=${this.channel_toggle_subscribed}
|
||||
>
|
||||
${this.channels.indexOf(this.hash.substring(2)) != -1
|
||||
? 'Unsubscribe from #'
|
||||
: 'Subscribe to #'}${this.hash.substring(2)}
|
||||
</button>
|
||||
`
|
||||
: undefined}
|
||||
</p>
|
||||
<div>
|
||||
<div
|
||||
id="show_sidebar"
|
||||
class="w3-button w3-hide-large"
|
||||
@click=${this.show_sidebar}
|
||||
>
|
||||
${this.unread_status()}☰
|
||||
</div>
|
||||
Welcome, <tf-user id=${this.whoami} .users=${this.users}></tf-user>!
|
||||
${edit_profile}
|
||||
</div>
|
||||
<div>
|
||||
<tf-compose
|
||||
id="tf-compose"
|
||||
whoami=${this.whoami}
|
||||
.users=${this.users}
|
||||
.drafts=${this.drafts}
|
||||
@tf-draft=${this.draft}
|
||||
.channel=${this.channel()}
|
||||
.recipients=${this.hash.startsWith('#🔐')
|
||||
? this.hash.substring('#🔐'.length).split(',')
|
||||
: undefined}
|
||||
></tf-compose>
|
||||
</div>
|
||||
${profile}
|
||||
<tf-tab-news-feed
|
||||
id="news"
|
||||
whoami=${this.whoami}
|
||||
.users=${this.users}
|
||||
.following=${this.following}
|
||||
hash=${this.hash}
|
||||
.drafts=${this.drafts}
|
||||
.expanded=${this.expanded}
|
||||
@tf-draft=${this.draft}
|
||||
@tf-expand=${this.on_expand}
|
||||
.channels_unread=${this.channels_unread}
|
||||
.channels_latest=${this.channels_latest}
|
||||
.private_messages=${this.private_messages}
|
||||
.grouped_private_messages=${this.grouped_private_messages}
|
||||
.recent_reactions=${this.recent_reactions}
|
||||
></tf-tab-news-feed>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<tf-compose
|
||||
id="tf-compose"
|
||||
whoami=${this.whoami}
|
||||
.users=${this.users}
|
||||
.drafts=${this.drafts}
|
||||
@tf-draft=${this.draft}
|
||||
></tf-compose>
|
||||
</div>
|
||||
${profile}
|
||||
<tf-tab-news-feed
|
||||
id="news"
|
||||
whoami=${this.whoami}
|
||||
.users=${this.users}
|
||||
.following=${this.following}
|
||||
hash=${this.hash}
|
||||
.drafts=${this.drafts}
|
||||
.expanded=${this.expanded}
|
||||
@tf-draft=${this.draft}
|
||||
@tf-expand=${this.on_expand}
|
||||
></tf-tab-news-feed>
|
||||
`;
|
||||
`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,136 +0,0 @@
|
||||
import {LitElement, html, unsafeHTML} from './lit-all.min.js';
|
||||
import * as tfrpc from '/static/tfrpc.js';
|
||||
import {styles} from './tf-styles.js';
|
||||
|
||||
class TfTabQueryElement extends LitElement {
|
||||
static get properties() {
|
||||
return {
|
||||
whoami: {type: String},
|
||||
users: {type: Object},
|
||||
following: {type: Array},
|
||||
query: {type: String},
|
||||
expanded: {type: Object},
|
||||
results: {type: Array},
|
||||
error: {type: Object},
|
||||
duration: {type: Number},
|
||||
};
|
||||
}
|
||||
|
||||
static styles = styles;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
let self = this;
|
||||
this.whoami = null;
|
||||
this.users = {};
|
||||
this.following = [];
|
||||
this.expanded = {};
|
||||
this.duration = undefined;
|
||||
}
|
||||
|
||||
async search(query) {
|
||||
console.log('Searching...', this.whoami, query);
|
||||
this.results = [];
|
||||
this.error = undefined;
|
||||
this.duration = undefined;
|
||||
let search = this.renderRoot.getElementById('search');
|
||||
if (search) {
|
||||
search.value = query;
|
||||
search.focus();
|
||||
}
|
||||
await tfrpc.rpc.setHash('#sql=' + encodeURIComponent(query));
|
||||
let start_time = new Date();
|
||||
try {
|
||||
this.results = await tfrpc.rpc.query(query, []);
|
||||
} catch (error) {
|
||||
this.error = error;
|
||||
}
|
||||
let end_time = new Date();
|
||||
this.duration = (end_time - start_time).valueOf();
|
||||
console.log('Done.');
|
||||
search = this.renderRoot.getElementById('search');
|
||||
if (search) {
|
||||
search.value = query;
|
||||
search.focus();
|
||||
}
|
||||
}
|
||||
|
||||
search_keydown(event) {
|
||||
if (event.keyCode == 13 && event.ctrlKey) {
|
||||
this.query = this.renderRoot.getElementById('search').value;
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
on_expand(event) {
|
||||
if (event.detail.expanded) {
|
||||
let expand = {};
|
||||
expand[event.detail.id] = true;
|
||||
this.expanded = Object.assign({}, this.expanded, expand);
|
||||
} else {
|
||||
delete this.expanded[event.detail.id];
|
||||
this.expanded = Object.assign({}, this.expanded);
|
||||
}
|
||||
}
|
||||
|
||||
render_results() {
|
||||
if (!this.results?.length) {
|
||||
return html`<div>No results.</div>`;
|
||||
} else {
|
||||
let keys = Object.keys(this.results[0]).sort();
|
||||
return html`<table style="width: 100%; max-width: 100%">
|
||||
<tr>
|
||||
${keys.map((key) => html`<th>${key}</th>`)}
|
||||
</tr>
|
||||
${this.results.map(
|
||||
(row) =>
|
||||
html`<tr>
|
||||
${keys.map((key) => html`<td>${row[key]}</td>`)}
|
||||
</tr>`
|
||||
)}
|
||||
</table>`;
|
||||
}
|
||||
}
|
||||
|
||||
render_error() {
|
||||
if (this.error) {
|
||||
return html`<h2 style="color: red">${this.error.message}</h2>
|
||||
<pre style="color: red">${this.error.stack}</pre>`;
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.query !== this.last_query) {
|
||||
this.last_query = this.query;
|
||||
this.search(this.query);
|
||||
}
|
||||
let self = this;
|
||||
return html`
|
||||
<div style="display: flex; flex-direction: row; gap: 4px">
|
||||
<textarea
|
||||
id="search"
|
||||
rows="8"
|
||||
class="w3-input w3-theme-d1"
|
||||
style="flex: 1; resize: vertical"
|
||||
@keydown=${this.search_keydown}
|
||||
>
|
||||
${this.query}</textarea
|
||||
>
|
||||
<button
|
||||
class="w3-button w3-theme-d1"
|
||||
@click=${(event) =>
|
||||
self.search(self.renderRoot.getElementById('search').value)}
|
||||
>
|
||||
Execute
|
||||
</button>
|
||||
</div>
|
||||
<div ?hidden=${this.duration === undefined}>
|
||||
Took ${this.duration / 1000.0} seconds.
|
||||
</div>
|
||||
<div ?hidden=${this.duration !== undefined}>Executing...</div>
|
||||
${this.render_error()} ${this.render_results()}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('tf-tab-query', TfTabQueryElement);
|
||||
@@ -1,6 +1,6 @@
|
||||
import {LitElement, html, unsafeHTML} from './lit-all.min.js';
|
||||
import * as tfrpc from '/static/tfrpc.js';
|
||||
import {styles} from './tf-styles.js';
|
||||
import {styles, generate_theme} from './tf-styles.js';
|
||||
|
||||
class TfTabSearchElement extends LitElement {
|
||||
static get properties() {
|
||||
@@ -11,6 +11,9 @@ class TfTabSearchElement extends LitElement {
|
||||
following: {type: Array},
|
||||
query: {type: String},
|
||||
expanded: {type: Object},
|
||||
messages: {type: Array},
|
||||
results: {type: Array},
|
||||
error: {type: Object},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -38,24 +41,40 @@ class TfTabSearchElement extends LitElement {
|
||||
search.select();
|
||||
}
|
||||
await tfrpc.rpc.setHash('#q=' + encodeURIComponent(query));
|
||||
let results = await tfrpc.rpc.query(
|
||||
`
|
||||
SELECT messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
|
||||
FROM messages_fts(?)
|
||||
JOIN messages ON messages.rowid = messages_fts.rowid
|
||||
JOIN json_each(?) AS following ON messages.author = following.value
|
||||
ORDER BY timestamp DESC limit 100
|
||||
`,
|
||||
['"' + query.replace('"', '""') + '"', JSON.stringify(this.following)]
|
||||
);
|
||||
console.log('Done.');
|
||||
search = this.renderRoot.getElementById('search');
|
||||
if (search) {
|
||||
search.value = query;
|
||||
search.focus();
|
||||
search.select();
|
||||
this.error = undefined;
|
||||
this.results = [];
|
||||
this.messages = [];
|
||||
if (query.startsWith('sql:')) {
|
||||
this.messages = [];
|
||||
try {
|
||||
this.results = await tfrpc.rpc.query(
|
||||
query.substring('sql:'.length),
|
||||
[]
|
||||
);
|
||||
} catch (e) {
|
||||
this.results = [];
|
||||
this.error = e;
|
||||
}
|
||||
} else {
|
||||
let results = await tfrpc.rpc.query(
|
||||
`
|
||||
SELECT messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
|
||||
FROM messages_fts(?)
|
||||
JOIN messages ON messages.rowid = messages_fts.rowid
|
||||
JOIN json_each(?) AS following ON messages.author = following.value
|
||||
ORDER BY timestamp DESC limit 100
|
||||
`,
|
||||
['"' + query.replace('"', '""') + '"', JSON.stringify(this.following)]
|
||||
);
|
||||
console.log('Done.');
|
||||
search = this.renderRoot.getElementById('search');
|
||||
if (search) {
|
||||
search.value = query;
|
||||
search.focus();
|
||||
search.select();
|
||||
}
|
||||
this.messages = results;
|
||||
}
|
||||
this.renderRoot.getElementById('news').messages = results;
|
||||
}
|
||||
|
||||
search_keydown(event) {
|
||||
@@ -87,6 +106,39 @@ class TfTabSearchElement extends LitElement {
|
||||
tfrpc.rpc.localStorageSet('drafts', JSON.stringify(this.drafts));
|
||||
}
|
||||
|
||||
render_results() {
|
||||
if (this.error) {
|
||||
return html`<h2 style="color: red">${this.error.message}</h2>
|
||||
<pre style="color: red">${this.error.stack}</pre>`;
|
||||
} else if (this.messages?.length) {
|
||||
return html`<tf-news
|
||||
id="news"
|
||||
whoami=${this.whoami}
|
||||
.messages=${this.messages}
|
||||
.users=${this.users}
|
||||
.expanded=${this.expanded}
|
||||
.drafts=${this.drafts}
|
||||
@tf-expand=${this.on_expand}
|
||||
@tf-draft=${this.draft}
|
||||
></tf-news>`;
|
||||
} else if (this.results?.length) {
|
||||
let keys = Object.keys(this.results[0]).sort();
|
||||
return html`<table style="width: 100%; max-width: 100%">
|
||||
<tr>
|
||||
${keys.map((key) => html`<th>${key}</th>`)}
|
||||
</tr>
|
||||
${this.results.map(
|
||||
(row) =>
|
||||
html`<tr>
|
||||
${keys.map((key) => html`<td>${row[key]}</td>`)}
|
||||
</tr>`
|
||||
)}
|
||||
</table>`;
|
||||
} else {
|
||||
return html`<div>No results.</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.query !== this.last_query) {
|
||||
this.last_query = this.query;
|
||||
@@ -94,11 +146,14 @@ class TfTabSearchElement extends LitElement {
|
||||
}
|
||||
let self = this;
|
||||
return html`
|
||||
<div style="display: flex; flex-direction: row; gap: 4px">
|
||||
<input type="text" class="w3-input w3-theme-d1" id="search" value=${this.query} style="flex: 1" @keydown=${this.search_keydown}></input>
|
||||
<button class="w3-button w3-theme-d1" @click=${(event) => self.search(self.renderRoot.getElementById('search').value)}>Search</button>
|
||||
<style>${generate_theme()}</style>
|
||||
<div class="w3-padding">
|
||||
<div style="display: flex; flex-direction: row; gap: 4px">
|
||||
<input type="text" class="w3-input w3-theme-d1" id="search" value=${this.query} style="flex: 1" @keydown=${this.search_keydown}></input>
|
||||
<button class="w3-button w3-theme-d1" @click=${(event) => self.search(self.renderRoot.getElementById('search').value)}>Search</button>
|
||||
</div>
|
||||
${this.render_results()}
|
||||
</div>
|
||||
<tf-news id="news" whoami=${this.whoami} .messages=${this.messages} .users=${this.users} .expanded=${this.expanded} .drafts=${this.drafts} @tf-expand=${this.on_expand} @tf-draft=${this.draft}></tf-news>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {LitElement, html, unsafeHTML} from './lit-all.min.js';
|
||||
import {styles} from './tf-styles.js';
|
||||
import {styles, generate_theme} from './tf-styles.js';
|
||||
|
||||
class TfTagElement extends LitElement {
|
||||
static get properties() {
|
||||
@@ -17,11 +17,15 @@ class TfTagElement extends LitElement {
|
||||
|
||||
render() {
|
||||
let number = this.count ? html` (${this.count})` : undefined;
|
||||
return html`<a
|
||||
href="#q=${this.tag}"
|
||||
style="display: inline-block; margin: 3px; border: 1px solid black; background-color: #444; padding: 4px; border-radius: 3px"
|
||||
>${this.tag}${number}</a
|
||||
>`;
|
||||
return html`
|
||||
<style>
|
||||
${generate_theme()}</style
|
||||
><a
|
||||
href=${'#' + encodeURIComponent(this.tag)}
|
||||
class="w3-tag w3-theme-d1 w3-round-4 w3-button"
|
||||
>${this.tag}${number}</a
|
||||
>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
import {LitElement, html} from './lit-all.min.js';
|
||||
import * as tfrpc from '/static/tfrpc.js';
|
||||
import {styles} from './tf-styles.js';
|
||||
import {styles, generate_theme} from './tf-styles.js';
|
||||
|
||||
class TfUserElement extends LitElement {
|
||||
static get properties() {
|
||||
return {
|
||||
id: {type: String},
|
||||
fallback_name: {type: String},
|
||||
icon_only: {type: Boolean},
|
||||
users: {type: Object},
|
||||
nolink: {type: Boolean},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -15,36 +18,55 @@ class TfUserElement extends LitElement {
|
||||
constructor() {
|
||||
super();
|
||||
this.id = null;
|
||||
this.fallback_name = null;
|
||||
this.icon_only = false;
|
||||
this.users = {};
|
||||
}
|
||||
|
||||
render() {
|
||||
let user = this.users[this.id];
|
||||
let shape =
|
||||
user?.follow_depth === undefined || user.follow_depth >= 2
|
||||
? 'w3-circle'
|
||||
: 'w3-round';
|
||||
let image = html`<span
|
||||
class="w3-theme-light w3-circle"
|
||||
class=${'w3-theme-l4 ' + shape}
|
||||
style="display: inline-block; width: 2em; height: 2em; text-align: center; line-height: 2em"
|
||||
>?</span
|
||||
>😎</span
|
||||
>`;
|
||||
let name = this.users?.[this.id]?.name;
|
||||
name =
|
||||
name !== undefined
|
||||
? html`<a target="_top" href=${'#' + this.id}>${name}</a>`
|
||||
: html`<a target="_top" href=${'#' + this.id}>${this.id}</a>`;
|
||||
let name_string = name ?? this.fallback_name ?? this.id;
|
||||
name = this.icon_only
|
||||
? undefined
|
||||
: !this.nolink
|
||||
? html`<a target="_top" href=${'#' + this.id}>${name_string}</a>`
|
||||
: html`<span>${name_string}</span>`;
|
||||
|
||||
if (this.users[this.id]) {
|
||||
let image_link = this.users[this.id].image;
|
||||
image_link =
|
||||
typeof image_link == 'string' ? image_link : image_link?.link;
|
||||
if (user) {
|
||||
let image_link = user.image;
|
||||
if (typeof image_link == 'string' && !image_link.startsWith('&')) {
|
||||
try {
|
||||
image_link = JSON.parse(image_link)?.link;
|
||||
} catch {}
|
||||
}
|
||||
if (image_link !== undefined) {
|
||||
image = html`<img
|
||||
class="w3-circle"
|
||||
class=${'w3-theme-l4 ' + shape}
|
||||
style="width: 2em; height: 2em; vertical-align: middle; object-fit: cover"
|
||||
src="/${image_link}/view"
|
||||
title=${name_string + ' (' + this.id + ')'}
|
||||
/>`;
|
||||
}
|
||||
}
|
||||
return html` <div style="display: inline-block; font-weight: bold">
|
||||
${image} ${name}
|
||||
</div>`;
|
||||
return html` <style>
|
||||
${generate_theme()}
|
||||
</style>
|
||||
<div
|
||||
style=${'display: inline-block; vertical-align: middle; text-wrap: nowrap; max-width: 100%; overflow: hidden; text-overflow: ellipsis' +
|
||||
(this.nolink ? '' : '; font-weight: bold')}
|
||||
>
|
||||
${image} ${name}
|
||||
</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,12 @@ import * as hashtagify from './commonmark-hashtag.js';
|
||||
|
||||
const k_code_classes = 'w3-theme-l4 w3-theme-border w3-round';
|
||||
|
||||
var reUnsafeProtocol = /^javascript:|vbscript:|file:|data:/i;
|
||||
var reSafeDataProtocol = /^data:image\/(?:png|gif|jpeg|webp)/i;
|
||||
var potentiallyUnsafe = function (url) {
|
||||
return reUnsafeProtocol.test(url) && !reSafeDataProtocol.test(url);
|
||||
};
|
||||
|
||||
function image(node, entering) {
|
||||
if (
|
||||
node.firstChild?.type === 'text' &&
|
||||
@@ -44,9 +50,9 @@ function image(node, entering) {
|
||||
'</div>'
|
||||
);
|
||||
if (this.options.safe && potentiallyUnsafe(node.destination)) {
|
||||
this.lit('<img src="" alt="');
|
||||
this.lit('<img src="" title="');
|
||||
} else {
|
||||
this.lit('<img src="' + this.esc(node.destination) + '" alt="');
|
||||
this.lit('<img src="' + this.esc(node.destination) + '" title="');
|
||||
}
|
||||
}
|
||||
this.disableTags += 1;
|
||||
@@ -81,8 +87,8 @@ function attrs(node) {
|
||||
}
|
||||
|
||||
export function markdown(md) {
|
||||
let reader = new commonmark.Parser({safe: true});
|
||||
let writer = new commonmark.HtmlRenderer();
|
||||
let reader = new commonmark.Parser();
|
||||
let writer = new commonmark.HtmlRenderer({safe: true});
|
||||
writer.image = image;
|
||||
writer.code = code;
|
||||
writer.attrs = attrs;
|
||||
@@ -98,12 +104,12 @@ export function markdown(md) {
|
||||
node.destination.startsWith('@') &&
|
||||
node.destination.endsWith('.ed25519')
|
||||
) {
|
||||
node.destination = '#' + node.destination;
|
||||
node.destination = '#' + encodeURIComponent(node.destination);
|
||||
} else if (
|
||||
node.destination.startsWith('%') &&
|
||||
node.destination.endsWith('.sha256')
|
||||
) {
|
||||
node.destination = '#' + node.destination;
|
||||
node.destination = '#' + encodeURIComponent(node.destination);
|
||||
} else if (
|
||||
node.destination.startsWith('&') &&
|
||||
node.destination.endsWith('.sha256')
|
||||
|
||||
5
apps/storage.json
Normal file
5
apps/storage.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"type": "tildefriends-app",
|
||||
"emoji": "💾",
|
||||
"previous": "&tzZFIe7Y54O4sx1QtAPdemkXh+p5qHXSG/dlS7NP6OQ=.sha256"
|
||||
}
|
||||
126
apps/storage/app.js
Normal file
126
apps/storage/app.js
Normal file
@@ -0,0 +1,126 @@
|
||||
async function query(sql, args) {
|
||||
let rows = [];
|
||||
await ssb.sqlAsync(sql, args ?? [], function (row) {
|
||||
rows.push(row);
|
||||
});
|
||||
return rows;
|
||||
}
|
||||
|
||||
async function get_biggest() {
|
||||
return query(`
|
||||
select author, size from messages_stats group by author order by size desc limit 10;
|
||||
`);
|
||||
}
|
||||
|
||||
async function get_total() {
|
||||
return (
|
||||
await query(`
|
||||
select sum(length(content)) as size, count(distinct author) as count from messages;
|
||||
`)
|
||||
)[0];
|
||||
}
|
||||
|
||||
async function get_names(identities) {
|
||||
return query(
|
||||
`
|
||||
SELECT author, name FROM (
|
||||
SELECT
|
||||
messages.author,
|
||||
RANK() OVER (PARTITION BY messages.author ORDER BY messages.sequence DESC) AS author_rank,
|
||||
messages.content ->> 'name' AS name
|
||||
FROM messages
|
||||
JOIN json_each(?) AS identities ON identities.value = messages.author
|
||||
WHERE
|
||||
json_extract(messages.content, '$.type') = 'about' AND
|
||||
content ->> 'about' = messages.author AND name IS NOT NULL)
|
||||
WHERE author_rank = 1
|
||||
`,
|
||||
[JSON.stringify(identities)]
|
||||
);
|
||||
}
|
||||
|
||||
async function get_most_follows() {
|
||||
return query(`
|
||||
select author, count(*) as count
|
||||
from messages
|
||||
where content ->> 'type' = 'contact' and content ->> 'following' = true
|
||||
group by author
|
||||
order by count desc
|
||||
limit 10;
|
||||
`);
|
||||
}
|
||||
|
||||
function nice_size(bytes) {
|
||||
let value = bytes;
|
||||
let index = 0;
|
||||
let units = ['B', 'kB', 'MB', 'GB'];
|
||||
while (value > 1024 && index < units.length - 1) {
|
||||
value /= 1024;
|
||||
index++;
|
||||
}
|
||||
return `${Math.round(value * 10) / 10} ${units[index]}`;
|
||||
}
|
||||
|
||||
async function main() {
|
||||
await app.setDocument('<p style="color: #fff">Analyzing feeds...</p>');
|
||||
let most_follows = get_most_follows();
|
||||
let total = await get_total();
|
||||
let identities = await ssb.getAllIdentities();
|
||||
let following1 = await ssb.following(identities, 1);
|
||||
let following2 = await ssb.following(identities, 2);
|
||||
let biggest = await get_biggest();
|
||||
most_follows = await most_follows;
|
||||
let names = await get_names(
|
||||
[].concat(
|
||||
biggest.map((x) => x.author),
|
||||
most_follows.map((x) => x.author)
|
||||
)
|
||||
);
|
||||
names = Object.fromEntries(names.map((x) => [x.author, x.name]));
|
||||
for (let item of biggest) {
|
||||
item.name = names[item.author];
|
||||
item.following =
|
||||
identities.indexOf(item.author) != -1
|
||||
? 0
|
||||
: following1[item.author] !== undefined
|
||||
? 1
|
||||
: following2[item.author] !== undefined
|
||||
? 2
|
||||
: undefined;
|
||||
}
|
||||
for (let item of most_follows) {
|
||||
item.name = names[item.author];
|
||||
}
|
||||
let html = `<body style="color: #000; background-color: #ddd">\n
|
||||
<h1>Storage Summary</h1>
|
||||
<h2>Top Accounts by Size</h2>
|
||||
<ol>`;
|
||||
for (let item of biggest) {
|
||||
html += `<li>
|
||||
<span style="color: #888">${nice_size(item.size)}</span>
|
||||
<a target="_top" href="/~core/ssb/#${encodeURI(item.author)}">${item.name ?? item.author}</a>
|
||||
</li>
|
||||
\n`;
|
||||
}
|
||||
html += `
|
||||
</ol>
|
||||
<h2>Top Accounts by Follows</h2>
|
||||
<ol>`;
|
||||
for (let item of most_follows) {
|
||||
html += `<li>
|
||||
<span style="color: #888">${item.count}</span>
|
||||
${following2[item.author] ? '✅' : '🚫'}
|
||||
<a target="_top" href="/~core/ssb/#${encodeURI(item.author)}">${item.name ?? item.author}</a>
|
||||
</li>
|
||||
\n`;
|
||||
}
|
||||
html += `
|
||||
</ol>
|
||||
<p>Total <span style="color: #888">${nice_size(total.size)}</span> in ${total.count} accounts.</p>
|
||||
`;
|
||||
await app.setDocument(html);
|
||||
}
|
||||
|
||||
main().catch(function (e) {
|
||||
print(e);
|
||||
});
|
||||
5
apps/web.json
Normal file
5
apps/web.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"type": "tildefriends-app",
|
||||
"emoji": "🕸",
|
||||
"previous": "&n7hu5b8/TsfiG6FDlCRG5nPCrIdCr96+xpIJ/aQT/uM=.sha256"
|
||||
}
|
||||
100
apps/web/app.js
Normal file
100
apps/web/app.js
Normal file
@@ -0,0 +1,100 @@
|
||||
let g_hash;
|
||||
|
||||
async function query(sql, params) {
|
||||
let results = [];
|
||||
await ssb.sqlAsync(sql, params, function (row) {
|
||||
results.push(row);
|
||||
});
|
||||
return results;
|
||||
}
|
||||
|
||||
async function resolve(id) {
|
||||
try {
|
||||
let blob = await ssb.blobGet(id);
|
||||
if (blob) {
|
||||
let json;
|
||||
try {
|
||||
json = JSON.parse(utf8Decode(blob));
|
||||
} catch {
|
||||
return {id: utf8Decode(blob)};
|
||||
}
|
||||
if (json?.links) {
|
||||
for (let [key, value] of Object.entries(json.links)) {
|
||||
json.links[key] = await resolve(value);
|
||||
}
|
||||
return json;
|
||||
} else {
|
||||
return 'huh?' + json;
|
||||
}
|
||||
} else {
|
||||
return `missing<${id}>`;
|
||||
}
|
||||
} catch (e) {
|
||||
return id + ': ' + e.message;
|
||||
}
|
||||
}
|
||||
|
||||
async function get_names(identities) {
|
||||
return Object.fromEntries(
|
||||
(
|
||||
await query(
|
||||
`
|
||||
SELECT author, name FROM (
|
||||
SELECT
|
||||
messages.author,
|
||||
RANK() OVER (PARTITION BY messages.author ORDER BY messages.sequence DESC) AS author_rank,
|
||||
messages.content ->> 'name' AS name
|
||||
FROM messages
|
||||
JOIN json_each(?) AS identities ON identities.value = messages.author
|
||||
WHERE
|
||||
json_extract(messages.content, '$.type') = 'about' AND
|
||||
content ->> 'about' = messages.author AND name IS NOT NULL)
|
||||
WHERE author_rank = 1
|
||||
`,
|
||||
[JSON.stringify(identities)]
|
||||
)
|
||||
).map((x) => [x.author, x.name])
|
||||
);
|
||||
}
|
||||
|
||||
async function render(hash) {
|
||||
g_hash = hash;
|
||||
if (!hash) {
|
||||
let sites = await query(
|
||||
`
|
||||
SELECT site.author, site.id
|
||||
FROM messages site
|
||||
WHERE site.content ->> 'type' = 'web-init'
|
||||
`,
|
||||
[]
|
||||
);
|
||||
let names = await get_names(sites.map((x) => x.author));
|
||||
if (hash === g_hash) {
|
||||
await app.setDocument(
|
||||
`<ul style="background-color: #ddd">${sites.map((x) => `<li><a target="_top" href="#${encodeURIComponent(x.id)}">${names[x.author] ?? x.author} - ${x.id}</a></li>`).join('\n')}</ul>`
|
||||
);
|
||||
}
|
||||
} else {
|
||||
let site_id =
|
||||
hash.charAt(0) == '#'
|
||||
? decodeURIComponent(hash.substring(1))
|
||||
: decodeURIComponent(hash);
|
||||
await app.setDocument(`<html style="margin: 0; padding: 0; width: 100vw; height: 100vh; margin: 0; padding: 0">
|
||||
<body style="display: flex; flex-direction: column; width: 100vw; height: 100vh">
|
||||
<iframe src="${encodeURIComponent(site_id)}/index.html" style="flex: 1 1; border: 0; background-color: #fff"></iframe>
|
||||
</body>
|
||||
</html>`);
|
||||
}
|
||||
}
|
||||
|
||||
core.register('message', async function message_handler(message) {
|
||||
if (message.event == 'hashChange') {
|
||||
await render(message.hash);
|
||||
}
|
||||
});
|
||||
|
||||
async function main() {
|
||||
render(null);
|
||||
}
|
||||
|
||||
main();
|
||||
63
apps/web/handler.js
Normal file
63
apps/web/handler.js
Normal file
@@ -0,0 +1,63 @@
|
||||
async function query(sql, params) {
|
||||
let results = [];
|
||||
await ssb.sqlAsync(sql, params, function (row) {
|
||||
results.push(row);
|
||||
});
|
||||
return results;
|
||||
}
|
||||
|
||||
function guess_content_type(name) {
|
||||
if (name.endsWith('.html')) {
|
||||
return 'text/html; charset=UTF-8';
|
||||
} else if (name.endsWith('.js') || name.endsWith('.mjs')) {
|
||||
return 'text/javascript; charset=UTF-8';
|
||||
} else if (name.endsWith('.css')) {
|
||||
return 'text/stylesheet; charset=UTF-8';
|
||||
} else {
|
||||
return 'application/binary';
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
let path = request.path.replaceAll(/(%[0-9a-fA-F]{2})/g, (x) =>
|
||||
String.fromCharCode(parseInt(x.substring(1), 16))
|
||||
);
|
||||
let match = path.match(/^(%.{44}\.sha256)(?:\/)?(.*)$/);
|
||||
|
||||
let content_type = guess_content_type(request.path);
|
||||
let root = await query(
|
||||
`
|
||||
SELECT root.content ->> 'root' AS root
|
||||
FROM messages site
|
||||
JOIN messages root
|
||||
ON site.id = ? AND root.author = site.author AND root.content ->> 'site' = site.id
|
||||
ORDER BY root.sequence DESC LIMIT 1
|
||||
`,
|
||||
[match[1]]
|
||||
);
|
||||
let root_id = root[0]['root'];
|
||||
let last_id = root_id;
|
||||
let blob = await ssb.blobGet(root_id);
|
||||
try {
|
||||
for (let part of match[2]?.split('/')) {
|
||||
let dir = JSON.parse(utf8Decode(blob));
|
||||
last_id = dir?.links[part];
|
||||
blob = await ssb.blobGet(dir?.links[part]);
|
||||
content_type = guess_content_type(part);
|
||||
}
|
||||
} catch {}
|
||||
|
||||
respond({
|
||||
status_code: 200,
|
||||
data: blob ? utf8Decode(blob) : `${last_id} not found`,
|
||||
content_type: content_type,
|
||||
});
|
||||
}
|
||||
|
||||
main().catch(function (e) {
|
||||
respond({
|
||||
status_code: 200,
|
||||
data: `${e.message}\n${e.stack}`,
|
||||
content_type: 'text/plain',
|
||||
});
|
||||
});
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"type": "tildefriends-app",
|
||||
"emoji": "👋",
|
||||
"previous": "&7gFmLW5zSMhmxWWY1+jeRcHdullgujSqGJg94lVgr1k=.sha256"
|
||||
"previous": "&n1QkPkB5JoduFSx8UKOY3IlZqS2GwLiTUZv4ZrEOthQ=.sha256"
|
||||
}
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
async function main() {
|
||||
await app.setDocument(utf8Decode(getFile('index.html')));
|
||||
}
|
||||
|
||||
main();
|
||||
1
apps/welcome/gitea.svg
Normal file
1
apps/welcome/gitea.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" viewBox="0 0 640 640" width="32" height="32"><path d="m395.9 484.2-126.9-61c-12.5-6-17.9-21.2-11.8-33.8l61-126.9c6-12.5 21.2-17.9 33.8-11.8 17.2 8.3 27.1 13 27.1 13l-.1-109.2 16.7-.1.1 117.1s57.4 24.2 83.1 40.1c3.7 2.3 10.2 6.8 12.9 14.4 2.1 6.1 2 13.1-1 19.3l-61 126.9c-6.2 12.7-21.4 18.1-33.9 12" style="fill:#fff"/><path d="M622.7 149.8c-4.1-4.1-9.6-4-9.6-4s-117.2 6.6-177.9 8c-13.3.3-26.5.6-39.6.7v117.2c-5.5-2.6-11.1-5.3-16.6-7.9 0-36.4-.1-109.2-.1-109.2-29 .4-89.2-2.2-89.2-2.2s-141.4-7.1-156.8-8.5c-9.8-.6-22.5-2.1-39 1.5-8.7 1.8-33.5 7.4-53.8 26.9C-4.9 212.4 6.6 276.2 8 285.8c1.7 11.7 6.9 44.2 31.7 72.5 45.8 56.1 144.4 54.8 144.4 54.8s12.1 28.9 30.6 55.5c25 33.1 50.7 58.9 75.7 62 63 0 188.9-.1 188.9-.1s12 .1 28.3-10.3c14-8.5 26.5-23.4 26.5-23.4S547 483 565 451.5c5.5-9.7 10.1-19.1 14.1-28 0 0 55.2-117.1 55.2-231.1-1.1-34.5-9.6-40.6-11.6-42.6M125.6 353.9c-25.9-8.5-36.9-18.7-36.9-18.7S69.6 321.8 60 295.4c-16.5-44.2-1.4-71.2-1.4-71.2s8.4-22.5 38.5-30c13.8-3.7 31-3.1 31-3.1s7.1 59.4 15.7 94.2c7.2 29.2 24.8 77.7 24.8 77.7s-26.1-3.1-43-9.1m300.3 107.6s-6.1 14.5-19.6 15.4c-5.8.4-10.3-1.2-10.3-1.2s-.3-.1-5.3-2.1l-112.9-55s-10.9-5.7-12.8-15.6c-2.2-8.1 2.7-18.1 2.7-18.1L322 273s4.8-9.7 12.2-13c.6-.3 2.3-1 4.5-1.5 8.1-2.1 18 2.8 18 2.8L467.4 315s12.6 5.7 15.3 16.2c1.9 7.4-.5 14-1.8 17.2-6.3 15.4-55 113.1-55 113.1" style="fill:#609926"/><path d="M326.8 380.1c-8.2.1-15.4 5.8-17.3 13.8s2 16.3 9.1 20c7.7 4 17.5 1.8 22.7-5.4 5.1-7.1 4.3-16.9-1.8-23.1l24-49.1c1.5.1 3.7.2 6.2-.5 4.1-.9 7.1-3.6 7.1-3.6 4.2 1.8 8.6 3.8 13.2 6.1 4.8 2.4 9.3 4.9 13.4 7.3.9.5 1.8 1.1 2.8 1.9 1.6 1.3 3.4 3.1 4.7 5.5 1.9 5.5-1.9 14.9-1.9 14.9-2.3 7.6-18.4 40.6-18.4 40.6-8.1-.2-15.3 5-17.7 12.5-2.6 8.1 1.1 17.3 8.9 21.3s17.4 1.7 22.5-5.3c5-6.8 4.6-16.3-1.1-22.6 1.9-3.7 3.7-7.4 5.6-11.3 5-10.4 13.5-30.4 13.5-30.4.9-1.7 5.7-10.3 2.7-21.3-2.5-11.4-12.6-16.7-12.6-16.7-12.2-7.9-29.2-15.2-29.2-15.2s0-4.1-1.1-7.1c-1.1-3.1-2.8-5.1-3.9-6.3 4.7-9.7 9.4-19.3 14.1-29-4.1-2-8.1-4-12.2-6.1-4.8 9.8-9.7 19.7-14.5 29.5-6.7-.1-12.9 3.5-16.1 9.4-3.4 6.3-2.7 14.1 1.9 19.8z" style="fill:#609926"/></svg>
|
||||
|
After Width: | Height: | Size: 2.1 KiB |
1521
apps/welcome/hermietildefriends.svg
Normal file
1521
apps/welcome/hermietildefriends.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 86 KiB |
@@ -10,17 +10,6 @@
|
||||
<link rel="stylesheet" href="brands.min.css" />
|
||||
|
||||
<style>
|
||||
body,
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5 {
|
||||
font-family: 'Poppins', sans-serif;
|
||||
}
|
||||
body {
|
||||
font-size: 16px;
|
||||
}
|
||||
img {
|
||||
margin-bottom: -8px;
|
||||
}
|
||||
@@ -39,57 +28,39 @@
|
||||
<b>😎 Tilde Friends</b>
|
||||
</h1>
|
||||
<h1 class="w3-xxlarge w3-text-green">
|
||||
<b>Make apps and friends from the comfort of your web browser.</b>
|
||||
<b>a Secure Scuttlebutt decentralized social network client</b>
|
||||
</h1>
|
||||
<p>
|
||||
Tilde Friends is a platform for building, running, and sharing web
|
||||
applications.
|
||||
</p>
|
||||
<p>
|
||||
Available for lots of devices:
|
||||
<i class="fa-brands fa-linux w3-xlarge"></i>
|
||||
<i class="fa-brands fa-android w3-xlarge"></i>
|
||||
<i class="fa-brands fa-apple w3-xlarge"></i>
|
||||
<i class="fa fa-mobile-screen w3-xlarge"></i>
|
||||
<i class="fa-brands fa-windows w3-xlarge"></i>
|
||||
In addition to participating in Secure Scuttlebutt, Tilde Friends is
|
||||
a platform for building, running, and sharing applications.
|
||||
</p>
|
||||
<a
|
||||
class="w3-button w3-blue w3-padding-large"
|
||||
href="https://www.tildefriends.net/~core/ssb/"
|
||||
>🦀 Try It</a
|
||||
>
|
||||
<a
|
||||
class="w3-button w3-black w3-padding-large"
|
||||
href="https://dev.tildefriends.net/cory/tildefriends/releases"
|
||||
href="https://dev.tildefriends.net/cory/tildefriends/releases/latest"
|
||||
><i class="fa fa-download"></i> Download</a
|
||||
>
|
||||
<a
|
||||
class="w3-button w3-black w3-padding-large"
|
||||
href="https://www.tildefriends.net/~cory/apps/"
|
||||
><i class="fa fa-link"></i> Try It</a
|
||||
href="https://dev.tildefriends.net/cory/tildefriends"
|
||||
>
|
||||
<img src="gitea.svg" style="height: 1em; margin: 0" />
|
||||
Development
|
||||
</a>
|
||||
<a
|
||||
class="w3-button w3-black w3-padding-large"
|
||||
href="https://docs.tildefriends.net/"
|
||||
><i class="fa fa-book"></i> Documentation</a
|
||||
>
|
||||
<a
|
||||
class="w3-button w3-black w3-padding-large"
|
||||
href="https://dev.tildefriends.net/"
|
||||
><i class="fa fa-mug-hot"></i> Development</a
|
||||
href="https://www.tildefriends.net/~cory/tildeblog/"
|
||||
><i class="fa fa-solid fa-square-rss"></i> Blog</a
|
||||
>
|
||||
<p>
|
||||
<a
|
||||
class="w3-button w3-round-large w3-padding w3-blue-gray w3-margin-top"
|
||||
href="https://f-droid.org/en/packages/com.unprompted.tildefriends.fdroid/"
|
||||
><img src="f-droid.svg" style="height: 2em; margin: 0" /> Get it
|
||||
on F-Droid</a
|
||||
>
|
||||
<a
|
||||
class="w3-button w3-round-large w3-padding w3-blue-gray w3-margin-top"
|
||||
href="https://dev.tildefriends.net/releases/tildefriends-x86_64.AppImage"
|
||||
>
|
||||
<img src="appimage.svg" style="height: 2em; margin: 0" />
|
||||
Get Linux 64-bit AppImage
|
||||
</a>
|
||||
<a
|
||||
class="w3-button w3-round-large w3-padding w3-blue-gray w3-margin-top"
|
||||
href="https://play.google.com/store/apps/details?id=com.unprompted.tildefriends"
|
||||
>
|
||||
<img src="googleplay.svg" style="height: 2em; margin: 0" />
|
||||
Get it on Google Play (Open Testing)
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
<div class="w3-col l4 m6">
|
||||
<img src="tildefriends.png" class="w3-image w3-right w3-hide-small" />
|
||||
@@ -107,15 +78,119 @@
|
||||
<h2>First-time user checklist:</h2>
|
||||
<ol type="1" style="text-align: left">
|
||||
<li>
|
||||
<a href="https://dev.tildefriends.net/cory/tildefriends/releases"
|
||||
<a
|
||||
href="https://dev.tildefriends.net/cory/tildefriends/releases/latest"
|
||||
>Download</a
|
||||
>
|
||||
Tilde Friends or use
|
||||
<a href="https://www.tildefriends.net/"
|
||||
>https://www.tildefriends.net/</a
|
||||
>.
|
||||
<div class="w3-cell-row">
|
||||
<div class="w3-container w3-cell w3-mobile">
|
||||
<h3>Mobile</h3>
|
||||
<p>
|
||||
<a
|
||||
class="w3-button w3-round-large w3-padding w3-blue-gray w3-margin-top"
|
||||
href="https://f-droid.org/en/packages/com.unprompted.tildefriends.fdroid/"
|
||||
><img src="f-droid.svg" style="height: 2em; margin: 0" />
|
||||
Get it on F-Droid</a
|
||||
>
|
||||
<a
|
||||
class="w3-button w3-round-large w3-padding w3-blue-gray w3-margin-top"
|
||||
href="https://play.google.com/store/apps/details?id=com.unprompted.tildefriends"
|
||||
>
|
||||
<img
|
||||
src="googleplay.svg"
|
||||
style="height: 2em; margin: 0"
|
||||
/>
|
||||
Get it on Google Play
|
||||
</a>
|
||||
<a
|
||||
class="w3-button w3-round-large w3-padding w3-blue-gray w3-margin-top"
|
||||
href="https://testflight.apple.com/join/tXxgtSpE"
|
||||
>
|
||||
<img src="ios.svg" style="height: 2em; margin: 0" />
|
||||
Get it on iOS (TestFlight)
|
||||
</a>
|
||||
</p>
|
||||
<p>Just launch the app.</p>
|
||||
</div>
|
||||
<div class="w3-container w3-cell w3-mobile">
|
||||
<h3>Web</h3>
|
||||
<p>
|
||||
<a
|
||||
class="w3-button w3-round-large w3-blue w3-padding-large"
|
||||
href="https://www.tildefriends.net/~core/ssb/"
|
||||
>🦀 Try It</a
|
||||
>
|
||||
</p>
|
||||
<p>
|
||||
<a href="/login?return=/~core/intro"
|
||||
>Register an account with tildefriends.net</a
|
||||
>
|
||||
to take it for a spin right away.
|
||||
</p>
|
||||
<h3>PeachCloud</h3>
|
||||
<p>
|
||||
Tilde Friends is also a part of 🍑☁️<a
|
||||
href="https://peach-docs.commoninternet.net/"
|
||||
>PeachCloud</a
|
||||
>, which is available on
|
||||
<a href="https://apps.yunohost.org/app/peachpub"
|
||||
>YunoHost</a
|
||||
>
|
||||
for accessible self-hosting.
|
||||
</p>
|
||||
</div>
|
||||
<div class="w3-container w3-cell w3-mobile">
|
||||
<h3>Desktop</h3>
|
||||
<p>
|
||||
<a
|
||||
class="w3-button w3-round-large w3-black w3-padding-large"
|
||||
href="https://dev.tildefriends.net/cory/tildefriends/releases"
|
||||
><i class="fa fa-download"></i> Download</a
|
||||
>
|
||||
<a
|
||||
class="w3-button w3-round-large w3-padding w3-blue-gray"
|
||||
href="https://dev.tildefriends.net/releases/tildefriends-x86_64.AppImage"
|
||||
>
|
||||
<img src="appimage.svg" style="height: 2em; margin: 0" />
|
||||
Get Linux 64-bit AppImage
|
||||
</a>
|
||||
</p>
|
||||
<p>
|
||||
Tilde Friends is distributed as a single executable file (or
|
||||
source that you can
|
||||
<a href="http://dev.tildefriends.net">build yourself</a>)
|
||||
and stores all of its data in a single
|
||||
file(<code>db.sqlite</code>). You can generally download the
|
||||
latest executable from
|
||||
<a
|
||||
href="https://dev.tildefriends.net/cory/tildefriends/releases"
|
||||
>releases</a
|
||||
>
|
||||
for your platform, mark it as executable (<code
|
||||
>chmod +x tildefriends*</code
|
||||
>
|
||||
on macOS and Linux), and run. Run with <code>-h</code> to
|
||||
learn more.
|
||||
</p>
|
||||
<p>
|
||||
Tilde Friends will run in the console and provide a web
|
||||
interface at
|
||||
<a href="http://localhost:12345/">http://localhost:12345/</a
|
||||
>. You will have to register a username and password to sign
|
||||
into your instance.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<p>
|
||||
After a <a href="/~core/intro">brief introduction</a>, Tilde
|
||||
Friends will take you to the Secure Scuttlebutt social network
|
||||
app.
|
||||
</p>
|
||||
</li>
|
||||
<li>Create an account to identify yourself with that instance.</li>
|
||||
<li>
|
||||
Describe yourself in your profile in the <b>ssb</b> app. Give
|
||||
yourself a name and an avatar if you like.
|
||||
@@ -149,11 +224,11 @@
|
||||
<!-- SSB Section -->
|
||||
<div class="w3-light-grey">
|
||||
<div class="w3-row-padding w3-padding-64">
|
||||
<div class="w3-col l4 m6 s4">
|
||||
<div class="w3-col l4 m6 s4 w3-center">
|
||||
<a href="https://scuttlebutt.nz/"
|
||||
><img
|
||||
class="w3-image w3-round-large"
|
||||
src="ssb.png"
|
||||
class="w3-image"
|
||||
src="hermietildefriends.svg"
|
||||
alt="Secure Scuttlebutt"
|
||||
/></a>
|
||||
</div>
|
||||
@@ -223,16 +298,15 @@
|
||||
|
||||
<!-- Technlology Section -->
|
||||
<div class="w3-container w3-padding-64 w3-light-grey w3-center">
|
||||
<h1 class="w3-jumbo"><b>Boring Technology</b></h1>
|
||||
<h1 class="w3-jumbo"><b>Built to Last</b></h1>
|
||||
<p>
|
||||
Tilde Friends is built using boring, trusted tech. Unless a better
|
||||
reason presents itself, it strives to use only simple and widely adopted
|
||||
dependencies in order to keep it easy to build for all sorts of
|
||||
platforms and maintainable for a very long time.
|
||||
Tilde Friends strives to use only simple and widely adopted dependencies
|
||||
in order to keep it easy to build for all sorts of platforms and
|
||||
maintainable for a very long time.
|
||||
</p>
|
||||
<p>
|
||||
Though of course for building Tilde Friends apps, you are free to use
|
||||
whatever fits.
|
||||
whatever fits on top.
|
||||
</p>
|
||||
|
||||
<div class="w3-row" style="margin-top: 64px">
|
||||
@@ -266,10 +340,6 @@
|
||||
<i class="fa fa-lock w3-text-purple w3-jumbo"></i>
|
||||
<p>libsodium</p>
|
||||
</a>
|
||||
<a href="https://github.com/openssl/openssl/releases" class="w3-col s3">
|
||||
<i class="fa fa-shield-halved w3-text-green w3-jumbo"></i>
|
||||
<p>OpenSSL</p>
|
||||
</a>
|
||||
<a
|
||||
href="https://github.com/ianlancetaylor/libbacktrace"
|
||||
class="w3-col s3"
|
||||
@@ -277,13 +347,13 @@
|
||||
<i class="fa fa-burst w3-text-pink w3-jumbo"></i>
|
||||
<p>libbacktrace</p>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="w3-row" style="margin-top: 64px">
|
||||
<a href="https://codemirror.net/docs/changelog/" class="w3-col s3">
|
||||
<i class="fa fa-keyboard w3-text-indigo w3-jumbo"></i>
|
||||
<p>CodeMirror</p>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="w3-row" style="margin-top: 64px">
|
||||
<a href="https://github.com/jlfwong/speedscope/" class="w3-col s3">
|
||||
<i class="fa fa-microscope w3-text-orange w3-jumbo"></i>
|
||||
<p>Speedscope</p>
|
||||
@@ -296,9 +366,6 @@
|
||||
<i class="fa fa-book-atlas w3-text-purple w3-jumbo"></i>
|
||||
<p>c-ares</p>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="w3-row" style="margin-top: 64px">
|
||||
<a href="https://www.gnu.org/software/make/" class="w3-col s3">
|
||||
<i class="fa fa-hammer w3-text-teal w3-jumbo"></i>
|
||||
<p>GNU Make</p>
|
||||
|
||||
3
apps/welcome/ios.svg
Normal file
3
apps/welcome/ios.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="814" height="1000">
|
||||
<path d="M788.1 340.9c-5.8 4.5-108.2 62.2-108.2 190.5 0 148.4 130.3 200.9 134.2 202.2-.6 3.2-20.7 71.9-68.7 141.9-42.8 61.6-87.5 123.1-155.5 123.1s-85.5-39.5-164-39.5c-76.5 0-103.7 40.8-165.9 40.8s-105.6-57-155.5-127C46.7 790.7 0 663 0 541.8c0-194.4 126.4-297.5 250.8-297.5 66.1 0 121.2 43.4 162.7 43.4 39.5 0 101.1-46 176.3-46 28.5 0 130.9 2.6 198.3 99.2zm-234-181.5c31.1-36.9 53.1-88.1 53.1-139.3 0-7.1-.6-14.3-1.9-20.1-50.6 1.9-110.8 33.7-147.1 75.8-28.5 32.4-55.1 83.6-55.1 135.5 0 7.8 1.3 15.6 1.9 18.1 3.2.6 8.4 1.3 13.6 1.3 45.4 0 102.5-30.4 135.5-71.3z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 660 B |
Binary file not shown.
|
Before Width: | Height: | Size: 50 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 141 KiB After Width: | Height: | Size: 141 KiB |
@@ -1,4 +1,4 @@
|
||||
/* W3.CSS 4.15 December 2020 by Jan Egil and Borge Refsnes */
|
||||
/* W3.CSS 5.02 March 31 2025 by Jan Egil and Borge Refsnes */
|
||||
html{box-sizing:border-box}*,*:before,*:after{box-sizing:inherit}
|
||||
/* Extract from normalize.css by Nicolas Gallagher and Jonathan Neal git.io/normalize */
|
||||
html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}
|
||||
@@ -108,6 +108,8 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
|
||||
.w3-round-small{border-radius:2px}.w3-round,.w3-round-medium{border-radius:4px}.w3-round-large{border-radius:8px}.w3-round-xlarge{border-radius:16px}.w3-round-xxlarge{border-radius:32px}
|
||||
.w3-row-padding,.w3-row-padding>.w3-half,.w3-row-padding>.w3-third,.w3-row-padding>.w3-twothird,.w3-row-padding>.w3-threequarter,.w3-row-padding>.w3-quarter,.w3-row-padding>.w3-col{padding:0 8px}
|
||||
.w3-container,.w3-panel{padding:0.01em 16px}.w3-panel{margin-top:16px;margin-bottom:16px}
|
||||
.w3-grid{display:grid}.w3-grid-padding{display:grid;gap:16px}.w3-flex{display:flex}
|
||||
.w3-text-center{text-align:center}.w3-text-bold,.w3-bold{font-weight:bold}.w3-text-italic,.w3-italic{font-style:italic}
|
||||
.w3-code,.w3-codespan{font-family:Consolas,"courier new";font-size:16px}
|
||||
.w3-code{width:auto;background-color:#fff;padding:8px 12px;border-left:4px solid #4CAF50;word-wrap:break-word}
|
||||
.w3-codespan{color:crimson;background-color:#f1f1f1;padding-left:4px;padding-right:4px;font-size:110%}
|
||||
@@ -148,6 +150,7 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
|
||||
.w3-button:hover{color:#000!important;background-color:#ccc!important}
|
||||
.w3-transparent,.w3-hover-none:hover{background-color:transparent!important}
|
||||
.w3-hover-none:hover{box-shadow:none!important}
|
||||
.w3-rtl{direction:rtl}.w3-ltr{direction:ltr}
|
||||
/* Colors */
|
||||
.w3-amber,.w3-hover-amber:hover{color:#000!important;background-color:#ffc107!important}
|
||||
.w3-aqua,.w3-hover-aqua:hover{color:#000!important;background-color:#00ffff!important}
|
||||
@@ -175,6 +178,19 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
|
||||
.w3-grey,.w3-hover-grey:hover,.w3-gray,.w3-hover-gray:hover{color:#000!important;background-color:#9e9e9e!important}
|
||||
.w3-light-grey,.w3-hover-light-grey:hover,.w3-light-gray,.w3-hover-light-gray:hover{color:#000!important;background-color:#f1f1f1!important}
|
||||
.w3-dark-grey,.w3-hover-dark-grey:hover,.w3-dark-gray,.w3-hover-dark-gray:hover{color:#fff!important;background-color:#616161!important}
|
||||
.w3-asphalt,.w3-hover-asphalt:hover{color:#fff!important;background-color:#343a40!important}
|
||||
.w3-crimson,.w3-hover-crimson:hover{color:#fff!important;background-color:#a20025!important}
|
||||
.w3-cobalt,w3-hover-cobalt:hover{color:#fff!important;background-color:#0050ef!important}
|
||||
.w3-emerald,.w3-hover-emerald:hover{color:#fff!important;background-color:#008a00!important}
|
||||
.w3-olive,.w3-hover-olive:hover{color:#fff!important;background-color:#6d8764!important}
|
||||
.w3-paper,.w3-hover-paper:hover{color:#000!important;background-color:#f8f9fa!important}
|
||||
.w3-sienna,.w3-hover-sienna:hover{color:#fff!important;background-color:#a0522d!important}
|
||||
.w3-taupe,.w3-hover-taupe:hover{color:#fff!important;background-color:#87794e!important}
|
||||
.w3-danger{color:#fff!important;background-color:#dd0000!important}
|
||||
.w3-note{color:#000!important;background-color:#fff599!important}
|
||||
.w3-info{color:#fff!important;background-color:#0a6fc2!important}
|
||||
.w3-warning{color:#000!important;background-color:#ffb305!important}
|
||||
.w3-success{color:#fff!important;background-color:#008a00!important}
|
||||
.w3-pale-red,.w3-hover-pale-red:hover{color:#000!important;background-color:#ffdddd!important}
|
||||
.w3-pale-green,.w3-hover-pale-green:hover{color:#000!important;background-color:#ddffdd!important}
|
||||
.w3-pale-yellow,.w3-hover-pale-yellow:hover{color:#000!important;background-color:#ffffcc!important}
|
||||
@@ -232,4 +248,4 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
|
||||
.w3-border-light-grey,.w3-hover-border-light-grey:hover,.w3-border-light-gray,.w3-hover-border-light-gray:hover{border-color:#f1f1f1!important}
|
||||
.w3-border-dark-grey,.w3-hover-border-dark-grey:hover,.w3-border-dark-gray,.w3-hover-border-dark-gray:hover{border-color:#616161!important}
|
||||
.w3-border-pale-red,.w3-hover-border-pale-red:hover{border-color:#ffe7e7!important}.w3-border-pale-green,.w3-hover-border-pale-green:hover{border-color:#e7ffe7!important}
|
||||
.w3-border-pale-yellow,.w3-hover-border-pale-yellow:hover{border-color:#ffffcc!important}.w3-border-pale-blue,.w3-hover-border-pale-blue:hover{border-color:#e7ffff!important}
|
||||
.w3-border-pale-yellow,.w3-hover-border-pale-yellow:hover{border-color:#ffffcc!important}.w3-border-pale-blue,.w3-hover-border-pale-blue:hover{border-color:#e7ffff!important}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"type": "tildefriends-app",
|
||||
"emoji": "📝",
|
||||
"previous": "&DaYqKHRBKhjFGaOzbKZ1+/pLspJeEkDJYTF2B50tH6k=.sha256"
|
||||
"previous": "&4UHlsfQJvSh7L3D86uFtr7KUKCMRVBBTFxRIMqIc5as=.sha256"
|
||||
}
|
||||
|
||||
2
apps/wiki/commonmark.min.js
vendored
2
apps/wiki/commonmark.min.js
vendored
File diff suppressed because one or more lines are too long
@@ -2,8 +2,8 @@ import * as utils from './utils.js';
|
||||
import * as commonmark from './commonmark.min.js';
|
||||
|
||||
function markdown(md) {
|
||||
let reader = new commonmark.Parser({safe: true});
|
||||
let writer = new commonmark.HtmlRenderer();
|
||||
let reader = new commonmark.Parser();
|
||||
let writer = new commonmark.HtmlRenderer({safe: true});
|
||||
let parsed = reader.parse(md || '');
|
||||
let walker = parsed.walker();
|
||||
let event;
|
||||
|
||||
42
apps/wiki/lit-all.min.js
vendored
42
apps/wiki/lit-all.min.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -20,8 +20,8 @@ class TfWikiDocElement extends LitElement {
|
||||
}
|
||||
|
||||
markdown(md) {
|
||||
let reader = new commonmark.Parser({safe: true});
|
||||
let writer = new commonmark.HtmlRenderer();
|
||||
let reader = new commonmark.Parser();
|
||||
let writer = new commonmark.HtmlRenderer({safe: true});
|
||||
let parsed = reader.parse(md || '');
|
||||
let walker = parsed.walker();
|
||||
let event;
|
||||
|
||||
132
core/app.js
132
core/app.js
@@ -1,53 +1,48 @@
|
||||
/**
|
||||
* \file
|
||||
* \defgroup tfapp Tilde Friends App JS
|
||||
* Tilde Friends server-side app wrapper.
|
||||
* @{
|
||||
*/
|
||||
|
||||
/** \cond */
|
||||
import * as core from './core.js';
|
||||
|
||||
let g_next_id = 1;
|
||||
let g_calls = {};
|
||||
export {App};
|
||||
/** \endcond */
|
||||
|
||||
let gSessionIndex = 0;
|
||||
/** A sequence number of apps. */
|
||||
let g_session_index = 0;
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @returns
|
||||
*/
|
||||
function makeSessionId() {
|
||||
return 'session_' + (gSessionIndex++).toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @returns
|
||||
** App constructor.
|
||||
** @return An app instance.
|
||||
*/
|
||||
function App() {
|
||||
this._on_output = null;
|
||||
this._send_queue = [];
|
||||
this.calls = {};
|
||||
this._next_call_id = 1;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} callback
|
||||
*/
|
||||
App.prototype.readOutput = function (callback) {
|
||||
this._on_output = callback;
|
||||
};
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} api
|
||||
* @returns
|
||||
** Create a function wrapper that when called invokes a function on the app
|
||||
** itself.
|
||||
** @param api The function and argument names.
|
||||
** @return A function.
|
||||
*/
|
||||
App.prototype.makeFunction = function (api) {
|
||||
let self = this;
|
||||
let result = function () {
|
||||
let id = g_next_id++;
|
||||
while (!id || g_calls[id]) {
|
||||
id = g_next_id++;
|
||||
let id = self._next_call_id++;
|
||||
while (!id || self.calls[id]) {
|
||||
id = self._next_call_id++;
|
||||
}
|
||||
let promise = new Promise(function (resolve, reject) {
|
||||
g_calls[id] = {resolve: resolve, reject: reject};
|
||||
self.calls[id] = {resolve: resolve, reject: reject};
|
||||
});
|
||||
let message = {
|
||||
message: 'tfrpc',
|
||||
action: 'tfrpc',
|
||||
method: api[0],
|
||||
params: [...arguments],
|
||||
id: id,
|
||||
@@ -60,8 +55,8 @@ App.prototype.makeFunction = function (api) {
|
||||
};
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} message
|
||||
** Send a message to the app.
|
||||
** @param message The message to send.
|
||||
*/
|
||||
App.prototype.send = function (message) {
|
||||
if (this._send_queue) {
|
||||
@@ -78,12 +73,11 @@ App.prototype.send = function (message) {
|
||||
};
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} request
|
||||
* @param {*} response
|
||||
* @param {*} client
|
||||
** App socket handler.
|
||||
** @param request The HTTP request of the WebSocket connection.
|
||||
** @param response The HTTP response.
|
||||
*/
|
||||
async function socket(request, response, client) {
|
||||
exports.app_socket = async function socket(request, response) {
|
||||
let process;
|
||||
let options = {};
|
||||
let credentials = await httpd.auth_query(request.headers);
|
||||
@@ -103,10 +97,16 @@ async function socket(request, response, client) {
|
||||
try {
|
||||
message = JSON.parse(event.data);
|
||||
} catch (error) {
|
||||
print('ERROR', error, event.data, event.data.length, event.opCode);
|
||||
print(
|
||||
'WebSocket error:',
|
||||
error,
|
||||
event.data,
|
||||
event.data.length,
|
||||
event.opCode
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (message.action == 'hello') {
|
||||
if (!process && message.action == 'hello') {
|
||||
let packageOwner;
|
||||
let packageName;
|
||||
let blobId;
|
||||
@@ -123,7 +123,7 @@ async function socket(request, response, client) {
|
||||
if (!blobId) {
|
||||
response.send(
|
||||
JSON.stringify({
|
||||
message: 'tfrpc',
|
||||
action: 'tfrpc',
|
||||
method: 'error',
|
||||
params: [message.path + ' not found'],
|
||||
id: -1,
|
||||
@@ -149,7 +149,7 @@ async function socket(request, response, client) {
|
||||
parentApp: parentApp,
|
||||
id: blobId,
|
||||
},
|
||||
await ssb.getIdentityInfo(
|
||||
await ssb_internal.getIdentityInfo(
|
||||
credentials?.session?.name,
|
||||
packageOwner,
|
||||
packageName
|
||||
@@ -164,7 +164,7 @@ async function socket(request, response, client) {
|
||||
options.packageOwner = packageOwner;
|
||||
options.packageName = packageName;
|
||||
options.url = message.url;
|
||||
let sessionId = makeSessionId();
|
||||
let sessionId = 'session_' + (g_session_index++).toString();
|
||||
if (blobId) {
|
||||
if (message.edit_only) {
|
||||
response.send(
|
||||
@@ -176,9 +176,24 @@ async function socket(request, response, client) {
|
||||
}
|
||||
}
|
||||
if (process) {
|
||||
process.app.readOutput(function (message) {
|
||||
process.client_api.tfrpc = function (message) {
|
||||
if (message.id) {
|
||||
let calls = process?.app?.calls;
|
||||
if (calls) {
|
||||
let call = calls[message.id];
|
||||
if (call) {
|
||||
if (message.error !== undefined) {
|
||||
call.reject(message.error);
|
||||
} else {
|
||||
call.resolve(message.result);
|
||||
}
|
||||
delete calls[message.id];
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
process.app._on_output = (message) =>
|
||||
response.send(JSON.stringify(message), 0x1);
|
||||
});
|
||||
process.app.send();
|
||||
}
|
||||
|
||||
@@ -207,26 +222,13 @@ async function socket(request, response, client) {
|
||||
if (process && process.timeout > 0) {
|
||||
setTimeout(ping, process.timeout);
|
||||
}
|
||||
} else if (message.action == 'resetPermission') {
|
||||
if (process) {
|
||||
process.resetPermission(message.permission);
|
||||
}
|
||||
} else if (message.action == 'setActiveIdentity') {
|
||||
process.setActiveIdentity(message.identity);
|
||||
} else if (message.action == 'createIdentity') {
|
||||
await process.createIdentity();
|
||||
} else if (message.message == 'tfrpc') {
|
||||
if (message.id && g_calls[message.id]) {
|
||||
if (message.error !== undefined) {
|
||||
g_calls[message.id].reject(message.error);
|
||||
} else {
|
||||
g_calls[message.id].resolve(message.result);
|
||||
}
|
||||
delete g_calls[message.id];
|
||||
}
|
||||
} else {
|
||||
if (process && process.eventHandlers['message']) {
|
||||
await core.invoke(process.eventHandlers['message'], [message]);
|
||||
if (process) {
|
||||
if (process.client_api[message.action]) {
|
||||
process.client_api[message.action](message);
|
||||
} else if (process.eventHandlers['message']) {
|
||||
await core.invoke(process.eventHandlers['message'], [message]);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (event.opCode == 0x8) {
|
||||
@@ -245,6 +247,6 @@ async function socket(request, response, client) {
|
||||
};
|
||||
|
||||
response.upgrade(100, {});
|
||||
}
|
||||
};
|
||||
|
||||
export {socket, App};
|
||||
/** @} */
|
||||
|
||||
718
core/client.js
718
core/client.js
File diff suppressed because it is too large
Load Diff
377
core/core.js
377
core/core.js
@@ -1,33 +1,46 @@
|
||||
import * as app from './app.js';
|
||||
import * as form from './form.js';
|
||||
import * as http from './http.js';
|
||||
/**
|
||||
* \file
|
||||
* \defgroup tfcore Tilde Friends Core JS
|
||||
* Tilde Friends process management, in JavaScript.
|
||||
* @{
|
||||
*/
|
||||
|
||||
/** \cond */
|
||||
import * as app from './app.js';
|
||||
|
||||
export {invoke, getProcessBlob};
|
||||
/** \endcond */
|
||||
|
||||
/** All running processes. */
|
||||
let gProcesses = {};
|
||||
/** Whether stats are currently being sent. */
|
||||
let gStatsTimer = false;
|
||||
let kPingInterval = 60 * 1000;
|
||||
/** Effectively a process ID. */
|
||||
let g_handler_index = 0;
|
||||
/** Time between pings, in milliseconds. */
|
||||
const k_ping_interval = 60 * 1000;
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} out
|
||||
* @param {*} error
|
||||
* Print an error.
|
||||
* @param error The error.
|
||||
*/
|
||||
function printError(out, error) {
|
||||
function printError(error) {
|
||||
if (error.stackTrace) {
|
||||
out.print(error.fileName + ':' + error.lineNumber + ': ' + error.message);
|
||||
out.print(error.stackTrace);
|
||||
print(error.fileName + ':' + error.lineNumber + ': ' + error.message);
|
||||
print(error.stackTrace);
|
||||
} else {
|
||||
for (let [k, v] of Object.entries(error)) {
|
||||
out.print(k, v);
|
||||
print(k, v);
|
||||
}
|
||||
out.print(error.toString());
|
||||
print(error.toString());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} handlers
|
||||
* @param {*} argv
|
||||
* @returns
|
||||
* Invoke a handler.
|
||||
* @param handlers The handlers on which to invoke the callback.
|
||||
* @param argv Arguments to pass to the handlers.
|
||||
* @return A promise.
|
||||
*/
|
||||
function invoke(handlers, argv) {
|
||||
let promises = [];
|
||||
@@ -50,10 +63,10 @@ function invoke(handlers, argv) {
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} eventName
|
||||
* @param {*} argv
|
||||
* @returns
|
||||
* Broadcast a named event to all registered apps.
|
||||
* @param eventName the name of the event.
|
||||
* @param argv Arguments to pass to the handlers.
|
||||
* @return A promise.
|
||||
*/
|
||||
function broadcastEvent(eventName, argv) {
|
||||
let promises = [];
|
||||
@@ -66,9 +79,9 @@ function broadcastEvent(eventName, argv) {
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} message
|
||||
* @returns
|
||||
* Send a message to all other instances of the same app.
|
||||
* @param message The message.
|
||||
* @return A promise.
|
||||
*/
|
||||
function broadcast(message) {
|
||||
let sender = this;
|
||||
@@ -87,10 +100,13 @@ function broadcast(message) {
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {String} eventName
|
||||
* @param {*} argv
|
||||
* @returns
|
||||
* Send a message to all instances of the same app running as the same user.
|
||||
* @param user The user.
|
||||
* @param packageOwner The owner of the app.
|
||||
* @param packageName The name of the app.
|
||||
* @param eventName The name of the event.
|
||||
* @param argv The arguments to pass.
|
||||
* @return A promise.
|
||||
*/
|
||||
function broadcastAppEventToUser(
|
||||
user,
|
||||
@@ -115,10 +131,9 @@ function broadcastAppEventToUser(
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} caller
|
||||
* @param {*} process
|
||||
* @returns
|
||||
* Get user context information for a call.
|
||||
* @param caller The calling process.
|
||||
* @param process The receiving process.
|
||||
*/
|
||||
function getUser(caller, process) {
|
||||
return {
|
||||
@@ -131,43 +146,11 @@ function getUser(caller, process) {
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} user
|
||||
* @param {*} process
|
||||
* @returns
|
||||
*/
|
||||
async function getApps(user, process) {
|
||||
if (
|
||||
process.credentials &&
|
||||
process.credentials.session &&
|
||||
process.credentials.session.name
|
||||
) {
|
||||
if (user && user !== process.credentials.session.name && user !== 'core') {
|
||||
return {};
|
||||
} else if (!user) {
|
||||
user = process.credentials.session.name;
|
||||
}
|
||||
}
|
||||
if (user) {
|
||||
let db = new Database(user);
|
||||
try {
|
||||
let names = JSON.parse(await db.get('apps'));
|
||||
let result = {};
|
||||
for (let name of names) {
|
||||
result[name] = await db.get('path:' + name);
|
||||
}
|
||||
return result;
|
||||
} catch {}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} from
|
||||
* @param {*} to
|
||||
* @param {*} message
|
||||
* @returns
|
||||
* Send a message.
|
||||
* @param from The calling process.
|
||||
* @param to The receiving process.
|
||||
* @param message The message.
|
||||
* @return A promise.
|
||||
*/
|
||||
function postMessageInternal(from, to, message) {
|
||||
if (to.eventHandlers['message']) {
|
||||
@@ -176,14 +159,13 @@ function postMessageInternal(from, to, message) {
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} blobId
|
||||
* @param {*} key
|
||||
* @param {*} options
|
||||
* @returns
|
||||
* Get or create a process for an app blob.
|
||||
* @param blobId The blob identifier.
|
||||
* @param key A unique key for the invocation.
|
||||
* @param options Other options.
|
||||
* @return The process.
|
||||
*/
|
||||
async function getProcessBlob(blobId, key, options) {
|
||||
// TODO(tasiaiso): break this down ?
|
||||
let process = gProcesses[key];
|
||||
if (!process && !(options && 'create' in options && !options.create)) {
|
||||
let resolveReady;
|
||||
@@ -196,67 +178,27 @@ async function getProcessBlob(blobId, key, options) {
|
||||
process.task = new Task();
|
||||
process.packageOwner = options.packageOwner;
|
||||
process.packageName = options.packageName;
|
||||
process.url = options?.url;
|
||||
process.eventHandlers = {};
|
||||
if (!options?.script || options?.script === 'app.js') {
|
||||
process.app = new app.App();
|
||||
}
|
||||
process.lastActive = Date.now();
|
||||
process.lastPing = null;
|
||||
process.timeout = kPingInterval;
|
||||
process.timeout = k_ping_interval;
|
||||
process.ready = new Promise(function (resolve, reject) {
|
||||
resolveReady = resolve;
|
||||
rejectReady = reject;
|
||||
});
|
||||
gProcesses[key] = process;
|
||||
process.task.onExit = function (exitCode, terminationSignal) {
|
||||
broadcastEvent('onSessionEnd', [getUser(process, process)]);
|
||||
process.task = null;
|
||||
delete gProcesses[key];
|
||||
};
|
||||
let imports = {
|
||||
core: {
|
||||
broadcast: broadcast.bind(process),
|
||||
register: function (eventName, handler) {
|
||||
if (!process.eventHandlers[eventName]) {
|
||||
process.eventHandlers[eventName] = [];
|
||||
}
|
||||
process.eventHandlers[eventName].push(handler);
|
||||
},
|
||||
unregister: function (eventName, handler) {
|
||||
if (process.eventHandlers[eventName]) {
|
||||
let index = process.eventHandlers[eventName].indexOf(handler);
|
||||
if (index != -1) {
|
||||
process.eventHandlers[eventName].splice(index, 1);
|
||||
}
|
||||
if (process.eventHandlers[eventName].length == 0) {
|
||||
delete process.eventHandlers[eventName];
|
||||
}
|
||||
}
|
||||
},
|
||||
user: getUser(process, process),
|
||||
users: async function () {
|
||||
try {
|
||||
return JSON.parse(await new Database('auth').get('users'));
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
permissionsGranted: async function () {
|
||||
let user = process?.credentials?.session?.name;
|
||||
let settings = await loadSettings();
|
||||
if (
|
||||
user &&
|
||||
options?.packageOwner &&
|
||||
options?.packageName &&
|
||||
settings.userPermissions &&
|
||||
settings.userPermissions[user] &&
|
||||
settings.userPermissions[user][options.packageOwner]
|
||||
) {
|
||||
return settings.userPermissions[user][options.packageOwner][
|
||||
options.packageName
|
||||
];
|
||||
}
|
||||
},
|
||||
allPermissionsGranted: async function () {
|
||||
let user = process?.credentials?.session?.name;
|
||||
let settings = await loadSettings();
|
||||
@@ -270,12 +212,6 @@ async function getProcessBlob(blobId, key, options) {
|
||||
return settings.userPermissions[user];
|
||||
}
|
||||
},
|
||||
permissionsForUser: async function (user) {
|
||||
let settings = await loadSettings();
|
||||
return settings?.permissions?.[user] ?? [];
|
||||
},
|
||||
apps: (user) => getApps(user, process),
|
||||
getSockets: getSockets,
|
||||
permissionTest: async function (permission) {
|
||||
let user = process?.credentials?.session?.name;
|
||||
let settings = await loadSettings();
|
||||
@@ -336,11 +272,6 @@ async function getProcessBlob(blobId, key, options) {
|
||||
throw Error(`Permission denied: ${permission}.`);
|
||||
}
|
||||
},
|
||||
app: {
|
||||
owner: options?.packageOwner,
|
||||
name: options?.packageName,
|
||||
},
|
||||
url: options?.url,
|
||||
},
|
||||
};
|
||||
process.sendIdentities = async function () {
|
||||
@@ -349,7 +280,7 @@ async function getProcessBlob(blobId, key, options) {
|
||||
{
|
||||
action: 'identities',
|
||||
},
|
||||
await ssb.getIdentityInfo(
|
||||
await ssb_internal.getIdentityInfo(
|
||||
process?.credentials?.session?.name,
|
||||
options?.packageOwner,
|
||||
options?.packageName
|
||||
@@ -392,7 +323,7 @@ async function getProcessBlob(blobId, key, options) {
|
||||
options.packageName,
|
||||
'setActiveIdentity',
|
||||
[
|
||||
await ssb.getActiveIdentity(
|
||||
await imports.ssb.getActiveIdentity(
|
||||
process.credentials?.session?.name,
|
||||
options.packageOwner,
|
||||
options.packageName
|
||||
@@ -405,20 +336,8 @@ async function getProcessBlob(blobId, key, options) {
|
||||
}
|
||||
};
|
||||
if (process.credentials?.permissions?.administration) {
|
||||
imports.core.globalSettingsDescriptions = async function () {
|
||||
let settings = Object.assign({}, defaultGlobalSettings());
|
||||
for (let [key, value] of Object.entries(await loadSettings())) {
|
||||
if (settings[key]) {
|
||||
settings[key].value = value;
|
||||
}
|
||||
}
|
||||
return settings;
|
||||
};
|
||||
imports.core.globalSettingsGet = async function (key) {
|
||||
let settings = await loadSettings();
|
||||
return settings?.[key];
|
||||
};
|
||||
imports.core.globalSettingsSet = async function (key, value) {
|
||||
await imports.core.permissionTest('set_global_setting');
|
||||
print('Setting', key, value);
|
||||
let settings = await loadSettings();
|
||||
settings[key] = value;
|
||||
@@ -461,16 +380,15 @@ async function getProcessBlob(blobId, key, options) {
|
||||
if (process.app) {
|
||||
process.app.makeFunction(['error'])(error);
|
||||
} else {
|
||||
printError({print: print}, error);
|
||||
printError(error);
|
||||
}
|
||||
} catch (e) {
|
||||
printError({print: print}, error);
|
||||
printError(error);
|
||||
}
|
||||
};
|
||||
imports.ssb = Object.fromEntries(
|
||||
Object.keys(ssb).map((key) => [key, ssb[key].bind(ssb)])
|
||||
);
|
||||
imports.ssb.port = tildefriends.ssb_port;
|
||||
imports.ssb.createIdentity = () => process.createIdentity();
|
||||
imports.ssb.addIdentity = function (id) {
|
||||
if (
|
||||
@@ -499,26 +417,6 @@ async function getProcessBlob(blobId, key, options) {
|
||||
}
|
||||
};
|
||||
imports.ssb.setActiveIdentity = (id) => process.setActiveIdentity(id);
|
||||
imports.ssb.getActiveIdentity = () =>
|
||||
ssb.getActiveIdentity(
|
||||
process.credentials?.session?.name,
|
||||
options.packageOwner,
|
||||
options.packageName
|
||||
);
|
||||
imports.ssb.getOwnerIdentities = function () {
|
||||
if (options.packageOwner) {
|
||||
return ssb.getIdentities(options.packageOwner);
|
||||
}
|
||||
};
|
||||
imports.ssb.getIdentities = function () {
|
||||
if (
|
||||
process.credentials &&
|
||||
process.credentials.session &&
|
||||
process.credentials.session.name
|
||||
) {
|
||||
return ssb.getIdentities(process.credentials.session.name);
|
||||
}
|
||||
};
|
||||
imports.ssb.getPrivateKey = function (id) {
|
||||
if (
|
||||
process.credentials &&
|
||||
@@ -576,19 +474,6 @@ async function getProcessBlob(blobId, key, options) {
|
||||
);
|
||||
}
|
||||
};
|
||||
imports.ssb.setServerFollowingMe = function (id, following) {
|
||||
if (
|
||||
process.credentials &&
|
||||
process.credentials.session &&
|
||||
process.credentials.session.name
|
||||
) {
|
||||
return ssb.setServerFollowingMe(
|
||||
process.credentials.session.name,
|
||||
id,
|
||||
following
|
||||
);
|
||||
}
|
||||
};
|
||||
imports.ssb.swapWithServerIdentity = function (id) {
|
||||
if (
|
||||
process.credentials &&
|
||||
@@ -601,13 +486,6 @@ async function getProcessBlob(blobId, key, options) {
|
||||
);
|
||||
}
|
||||
};
|
||||
imports.ssb.addEventListener = undefined;
|
||||
imports.ssb.removeEventListener = undefined;
|
||||
imports.ssb.getIdentityInfo = undefined;
|
||||
imports.fetch = async function (url, options) {
|
||||
let settings = await loadSettings();
|
||||
return http.fetch(url, options, settings?.fetch_hosts);
|
||||
};
|
||||
|
||||
if (
|
||||
process.credentials &&
|
||||
@@ -663,17 +541,26 @@ async function getProcessBlob(blobId, key, options) {
|
||||
permissions: await imports.core.permissionsGranted(),
|
||||
});
|
||||
};
|
||||
process.resetPermission = async function resetPermission(permission) {
|
||||
let user = process?.credentials?.session?.name;
|
||||
await ssb.setUserPermission(
|
||||
user,
|
||||
options?.packageOwner,
|
||||
options?.packageName,
|
||||
permission,
|
||||
undefined
|
||||
);
|
||||
return process.sendPermissions();
|
||||
process.client_api = {
|
||||
createIdentity: function () {
|
||||
return process.createIdentity();
|
||||
},
|
||||
resetPermission: async function resetPermission(message) {
|
||||
let user = process?.credentials?.session?.name;
|
||||
await ssb.setUserPermission(
|
||||
user,
|
||||
options?.packageOwner,
|
||||
options?.packageName,
|
||||
message.permission,
|
||||
undefined
|
||||
);
|
||||
return process.sendPermissions();
|
||||
},
|
||||
setActiveIdentity: function setActiveIdentity(message) {
|
||||
return process.setActiveIdentity(message.identity);
|
||||
},
|
||||
};
|
||||
ssb.registerImports(imports, process);
|
||||
process.task.setImports(imports);
|
||||
process.task.activate();
|
||||
let source = await ssb.blobGet(blobId);
|
||||
@@ -700,9 +587,8 @@ async function getProcessBlob(blobId, key, options) {
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
printError({print: print}, e);
|
||||
printError(e);
|
||||
}
|
||||
broadcastEvent('onSessionBegin', [getUser(process, process)]);
|
||||
if (process.app) {
|
||||
process.app.send({action: 'ready', version: version()});
|
||||
await process.sendPermissions();
|
||||
@@ -714,14 +600,10 @@ async function getProcessBlob(blobId, key, options) {
|
||||
sendStats();
|
||||
}
|
||||
} catch (error) {
|
||||
if (process.app) {
|
||||
if (process?.task?.onError) {
|
||||
process.task.onError(error);
|
||||
} else {
|
||||
printError({print: print}, error);
|
||||
}
|
||||
if (process?.app && process?.task?.onError) {
|
||||
process.task.onError(error);
|
||||
} else {
|
||||
printError({print: print}, error);
|
||||
printError(error);
|
||||
}
|
||||
rejectReady(error);
|
||||
}
|
||||
@@ -729,20 +611,28 @@ async function getProcessBlob(blobId, key, options) {
|
||||
return process;
|
||||
}
|
||||
|
||||
ssb.addEventListener('message', function () {
|
||||
/**
|
||||
* SSB message added callback.
|
||||
*/
|
||||
ssb_internal.addEventListener('message', function () {
|
||||
broadcastEvent('onMessage', [...arguments]);
|
||||
});
|
||||
|
||||
ssb.addEventListener('broadcasts', function () {
|
||||
ssb_internal.addEventListener('blob', function () {
|
||||
broadcastEvent('onBlob', [...arguments]);
|
||||
});
|
||||
|
||||
ssb_internal.addEventListener('broadcasts', function () {
|
||||
broadcastEvent('onBroadcastsChanged', []);
|
||||
});
|
||||
|
||||
ssb.addEventListener('connections', function () {
|
||||
ssb_internal.addEventListener('connections', function () {
|
||||
broadcastEvent('onConnectionsChanged', []);
|
||||
});
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* Load settings from the database.
|
||||
* @return The settings as a key value pairs object.
|
||||
*/
|
||||
async function loadSettings() {
|
||||
let data = {};
|
||||
@@ -763,7 +653,7 @@ async function loadSettings() {
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* Send periodic stats to all clients.
|
||||
*/
|
||||
function sendStats() {
|
||||
let apps = Object.values(gProcesses)
|
||||
@@ -780,8 +670,16 @@ function sendStats() {
|
||||
}
|
||||
}
|
||||
|
||||
let g_handler_index = 0;
|
||||
|
||||
/**
|
||||
* Invoke an app's handler.js.
|
||||
* @param response The response object.
|
||||
* @param app_blob_id The app's blob identifier.
|
||||
* @param path The request path.
|
||||
* @param query The request query string.
|
||||
* @param headers The request headers.
|
||||
* @param package_owner The app's owner.
|
||||
* @param package_name The app's name.
|
||||
*/
|
||||
exports.callAppHandler = async function callAppHandler(
|
||||
response,
|
||||
app_blob_id,
|
||||
@@ -847,59 +745,4 @@ exports.callAppHandler = async function callAppHandler(
|
||||
response.end(answer?.data);
|
||||
};
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
*/
|
||||
loadSettings()
|
||||
.then(function (settings) {
|
||||
if (tildefriends.https_port && settings.http_redirect) {
|
||||
httpd.set_http_redirect(settings.http_redirect);
|
||||
}
|
||||
httpd.all('/app/socket', app.socket);
|
||||
let port = httpd.start(tildefriends.http_port);
|
||||
if (tildefriends.args.out_http_port_file) {
|
||||
print('Writing the port file.');
|
||||
File.writeFile(
|
||||
tildefriends.args.out_http_port_file,
|
||||
port.toString() + '\n'
|
||||
)
|
||||
.then(function (r) {
|
||||
print(
|
||||
'Wrote the port file:',
|
||||
tildefriends.args.out_http_port_file,
|
||||
r
|
||||
);
|
||||
})
|
||||
.catch(function () {
|
||||
print('Failed to write the port file.');
|
||||
});
|
||||
}
|
||||
|
||||
if (tildefriends.https_port) {
|
||||
async function start_tls() {
|
||||
const kCertificatePath = 'data/httpd/certificate.pem';
|
||||
const kPrivateKeyPath = 'data/httpd/privatekey.pem';
|
||||
let privateKey;
|
||||
let certificate;
|
||||
try {
|
||||
privateKey = utf8Decode(await File.readFile(kPrivateKeyPath));
|
||||
certificate = utf8Decode(await File.readFile(kCertificatePath));
|
||||
} catch (e) {
|
||||
print(`TLS disabled (${e.message}).`);
|
||||
return;
|
||||
}
|
||||
let context = new TlsContext();
|
||||
context.setPrivateKey(privateKey);
|
||||
context.setCertificate(certificate);
|
||||
httpd.start(tildefriends.https_port, context);
|
||||
}
|
||||
start_tls();
|
||||
}
|
||||
})
|
||||
.catch(function (error) {
|
||||
print('Failed to load settings.');
|
||||
printError({print: print}, error);
|
||||
exit(1);
|
||||
});
|
||||
|
||||
export {invoke, getProcessBlob};
|
||||
/** @} */
|
||||
|
||||
44
core/form.js
44
core/form.js
@@ -1,44 +0,0 @@
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} encoded
|
||||
* @returns
|
||||
*/
|
||||
function decode(encoded) {
|
||||
let result = '';
|
||||
for (let i = 0; i < encoded.length; i++) {
|
||||
let c = encoded[i];
|
||||
if (c == '+') {
|
||||
result += ' ';
|
||||
} else if (c == '%') {
|
||||
result += String.fromCharCode(parseInt(encoded.slice(i + 1, i + 3), 16));
|
||||
i += 2;
|
||||
} else {
|
||||
result += c;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} encoded
|
||||
* @param {*} initial
|
||||
* @returns
|
||||
*/
|
||||
function decodeForm(encoded, initial) {
|
||||
let result = initial || {};
|
||||
if (encoded) {
|
||||
encoded = encoded.trim();
|
||||
let items = encoded.split('&');
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
let item = items[i];
|
||||
let equals = item.indexOf('=');
|
||||
let key = decode(item.slice(0, equals));
|
||||
let value = decode(item.slice(equals + 1));
|
||||
result[key] = value;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export {decodeForm};
|
||||
113
core/http.js
113
core/http.js
@@ -1,113 +0,0 @@
|
||||
/**
|
||||
* TODOC
|
||||
* TODO: document so we can improve this
|
||||
* @param {*} url
|
||||
* @returns
|
||||
*/
|
||||
function parseUrl(url) {
|
||||
// XXX: Hack.
|
||||
let match = url.match(new RegExp('(\\w+)://([^/:]+)(?::(\\d+))?(.*)'));
|
||||
return {
|
||||
protocol: match[1],
|
||||
host: match[2],
|
||||
path: match[4],
|
||||
port: match[3] ? parseInt(match[3]) : match[1] == 'http' ? 80 : 443,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} data
|
||||
* @returns
|
||||
*/
|
||||
function parseResponse(data) {
|
||||
let firstLine;
|
||||
let headers = {};
|
||||
while (true) {
|
||||
let endLine = data.indexOf('\r\n');
|
||||
let line = data.substring(0, endLine);
|
||||
data = data.substring(endLine + 2);
|
||||
if (!line.length) {
|
||||
break;
|
||||
} else if (!firstLine) {
|
||||
firstLine = line;
|
||||
} else {
|
||||
let colon = line.indexOf(':');
|
||||
headers[line.substring(colon)] = line.substring(colon + 1);
|
||||
}
|
||||
}
|
||||
return {body: data};
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} url
|
||||
* @param {*} options
|
||||
* @param {*} allowed_hosts
|
||||
* @returns
|
||||
*/
|
||||
export function fetch(url, options, allowed_hosts) {
|
||||
let parsed = parseUrl(url);
|
||||
return new Promise(function (resolve, reject) {
|
||||
if ((allowed_hosts ?? []).indexOf(parsed.host) == -1) {
|
||||
throw new Error(`fetch() request to host ${parsed.host} is not allowed.`);
|
||||
}
|
||||
let socket = new Socket();
|
||||
let buffer = new Uint8Array(0);
|
||||
|
||||
return socket
|
||||
.connect(parsed.host, parsed.port)
|
||||
.then(function () {
|
||||
socket.read(function (data) {
|
||||
if (data && data.length) {
|
||||
let newBuffer = new Uint8Array(buffer.length + data.length);
|
||||
newBuffer.set(buffer, 0);
|
||||
newBuffer.set(data, buffer.length);
|
||||
buffer = newBuffer;
|
||||
} else {
|
||||
let result = parseHttpResponse(buffer);
|
||||
if (!result) {
|
||||
reject(new Exception('Parse failed.'));
|
||||
}
|
||||
if (typeof result == 'number') {
|
||||
if (result == -2) {
|
||||
reject('Incomplete request.');
|
||||
} else {
|
||||
reject('Bad request.');
|
||||
}
|
||||
} else if (typeof result == 'object') {
|
||||
resolve({
|
||||
body: buffer.slice(result.bytes_parsed),
|
||||
status: result.status,
|
||||
message: result.message,
|
||||
headers: result.headers,
|
||||
});
|
||||
} else {
|
||||
reject(new Exception('Unexpected parse result.'));
|
||||
}
|
||||
resolve(parseResponse(utf8Decode(buffer)));
|
||||
}
|
||||
});
|
||||
|
||||
if (parsed.port == 443) {
|
||||
return socket.startTls();
|
||||
}
|
||||
})
|
||||
.then(function () {
|
||||
let body =
|
||||
typeof options?.body == 'string'
|
||||
? utf8Encode(options.body)
|
||||
: options.body || new Uint8Array(0);
|
||||
let headers = utf8Encode(
|
||||
`${options?.method ?? 'GET'} ${parsed.path} HTTP/1.0\r\nHost: ${parsed.host}\r\nConnection: close\r\nContent-Length: ${body.length}\r\n\r\n`
|
||||
);
|
||||
let fullRequest = new Uint8Array(headers.length + body.length);
|
||||
fullRequest.set(headers, 0);
|
||||
fullRequest.set(body, headers.length);
|
||||
socket.write(fullRequest);
|
||||
})
|
||||
.catch(function (error) {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -6,6 +6,26 @@
|
||||
<link type="text/css" rel="stylesheet" href="/static/w3.css" />
|
||||
<link type="image/svg+xml" rel="icon" href="/static/tildefriends.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta
|
||||
name="title"
|
||||
content="Tilde Friends - Make friends and apps from your web browser."
|
||||
/>
|
||||
<meta
|
||||
name="description"
|
||||
content="Tilde Friends is a Secure Scuttlebutt client and a platform for building, running, and sharing web applications. "
|
||||
/>
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:url" content="https://metatags.io/" />
|
||||
<meta
|
||||
property="og:title"
|
||||
content="Tilde Friends - Make friends and apps from your web browser."
|
||||
/>
|
||||
<meta
|
||||
property="og:description"
|
||||
content="Tilde Friends is a Secure Scuttlebutt client and a platform for building, running, and sharing web applications. "
|
||||
/>
|
||||
<meta property="og:image" content="/static/tildefriends.svg" />
|
||||
|
||||
<script>
|
||||
function set_access_key_title(event) {
|
||||
if (!event.srcElement.title) {
|
||||
@@ -18,8 +38,8 @@
|
||||
style="
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
|
||||
@@ -7,8 +7,8 @@ html {
|
||||
|
||||
body {
|
||||
font-family: monospace;
|
||||
background-color: #002b36;
|
||||
color: #eee8d5;
|
||||
background-color: #444;
|
||||
color: #fff;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
|
||||
@@ -1,11 +1,22 @@
|
||||
/**
|
||||
* \file
|
||||
* \defgroup tfrpc Tilde Friends RPC.
|
||||
* Tilde Friends RPC.
|
||||
* @{
|
||||
*/
|
||||
|
||||
/** Whether this module is being run in a web browser. */
|
||||
const k_is_browser = get_is_browser();
|
||||
/** Registered methods. */
|
||||
let g_api = {};
|
||||
/** The next method identifier. */
|
||||
let g_next_id = 1;
|
||||
/** Identifiers of pending calls. */
|
||||
let g_calls = {};
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @returns
|
||||
* Check if being called from a browser vs. server-side.
|
||||
* @return true if called from a browser.
|
||||
*/
|
||||
function get_is_browser() {
|
||||
try {
|
||||
@@ -15,16 +26,30 @@ function get_is_browser() {
|
||||
}
|
||||
}
|
||||
|
||||
/** \cond */
|
||||
if (k_is_browser) {
|
||||
print = console.log;
|
||||
}
|
||||
|
||||
if (k_is_browser) {
|
||||
window.addEventListener('message', function (event) {
|
||||
call_rpc(event.data);
|
||||
});
|
||||
} else {
|
||||
core.register('message', function (message) {
|
||||
call_rpc(message?.message);
|
||||
});
|
||||
}
|
||||
|
||||
export let rpc = new Proxy({}, {get: make_rpc});
|
||||
/** \endcond */
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} target
|
||||
* @param {*} prop
|
||||
* @param {*} receiver
|
||||
* @returns
|
||||
* Make a function to invoke a remote procedure.
|
||||
* @param target The target.
|
||||
* @param prop The name of the function.
|
||||
* @param receiver The receiver.
|
||||
* @return A function.
|
||||
*/
|
||||
function make_rpc(target, prop, receiver) {
|
||||
return function () {
|
||||
@@ -55,8 +80,8 @@ function make_rpc(target, prop, receiver) {
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} response
|
||||
* Send a response.
|
||||
* @param response The response.
|
||||
*/
|
||||
function send(response) {
|
||||
if (k_is_browser) {
|
||||
@@ -67,8 +92,8 @@ function send(response) {
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} message
|
||||
* Invoke a remote procedure.
|
||||
* @param message An object describing the call.
|
||||
*/
|
||||
function call_rpc(message) {
|
||||
if (message && message.message === 'tfrpc') {
|
||||
@@ -112,22 +137,12 @@ function call_rpc(message) {
|
||||
}
|
||||
}
|
||||
|
||||
if (k_is_browser) {
|
||||
window.addEventListener('message', function (event) {
|
||||
call_rpc(event.data);
|
||||
});
|
||||
} else {
|
||||
core.register('message', function (message) {
|
||||
call_rpc(message?.message);
|
||||
});
|
||||
}
|
||||
|
||||
export let rpc = new Proxy({}, {get: make_rpc});
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} method
|
||||
* Register a function that to be called remotely.
|
||||
* @param method The method.
|
||||
*/
|
||||
export function register(method) {
|
||||
g_api[method.name] = method;
|
||||
}
|
||||
|
||||
/** @} */
|
||||
|
||||
20
core/w3.css
20
core/w3.css
@@ -1,4 +1,4 @@
|
||||
/* W3.CSS 4.15 December 2020 by Jan Egil and Borge Refsnes */
|
||||
/* W3.CSS 5.02 March 31 2025 by Jan Egil and Borge Refsnes */
|
||||
html{box-sizing:border-box}*,*:before,*:after{box-sizing:inherit}
|
||||
/* Extract from normalize.css by Nicolas Gallagher and Jonathan Neal git.io/normalize */
|
||||
html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}
|
||||
@@ -108,6 +108,8 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
|
||||
.w3-round-small{border-radius:2px}.w3-round,.w3-round-medium{border-radius:4px}.w3-round-large{border-radius:8px}.w3-round-xlarge{border-radius:16px}.w3-round-xxlarge{border-radius:32px}
|
||||
.w3-row-padding,.w3-row-padding>.w3-half,.w3-row-padding>.w3-third,.w3-row-padding>.w3-twothird,.w3-row-padding>.w3-threequarter,.w3-row-padding>.w3-quarter,.w3-row-padding>.w3-col{padding:0 8px}
|
||||
.w3-container,.w3-panel{padding:0.01em 16px}.w3-panel{margin-top:16px;margin-bottom:16px}
|
||||
.w3-grid{display:grid}.w3-grid-padding{display:grid;gap:16px}.w3-flex{display:flex}
|
||||
.w3-text-center{text-align:center}.w3-text-bold,.w3-bold{font-weight:bold}.w3-text-italic,.w3-italic{font-style:italic}
|
||||
.w3-code,.w3-codespan{font-family:Consolas,"courier new";font-size:16px}
|
||||
.w3-code{width:auto;background-color:#fff;padding:8px 12px;border-left:4px solid #4CAF50;word-wrap:break-word}
|
||||
.w3-codespan{color:crimson;background-color:#f1f1f1;padding-left:4px;padding-right:4px;font-size:110%}
|
||||
@@ -148,6 +150,7 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
|
||||
.w3-button:hover{color:#000!important;background-color:#ccc!important}
|
||||
.w3-transparent,.w3-hover-none:hover{background-color:transparent!important}
|
||||
.w3-hover-none:hover{box-shadow:none!important}
|
||||
.w3-rtl{direction:rtl}.w3-ltr{direction:ltr}
|
||||
/* Colors */
|
||||
.w3-amber,.w3-hover-amber:hover{color:#000!important;background-color:#ffc107!important}
|
||||
.w3-aqua,.w3-hover-aqua:hover{color:#000!important;background-color:#00ffff!important}
|
||||
@@ -175,6 +178,19 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
|
||||
.w3-grey,.w3-hover-grey:hover,.w3-gray,.w3-hover-gray:hover{color:#000!important;background-color:#9e9e9e!important}
|
||||
.w3-light-grey,.w3-hover-light-grey:hover,.w3-light-gray,.w3-hover-light-gray:hover{color:#000!important;background-color:#f1f1f1!important}
|
||||
.w3-dark-grey,.w3-hover-dark-grey:hover,.w3-dark-gray,.w3-hover-dark-gray:hover{color:#fff!important;background-color:#616161!important}
|
||||
.w3-asphalt,.w3-hover-asphalt:hover{color:#fff!important;background-color:#343a40!important}
|
||||
.w3-crimson,.w3-hover-crimson:hover{color:#fff!important;background-color:#a20025!important}
|
||||
.w3-cobalt,w3-hover-cobalt:hover{color:#fff!important;background-color:#0050ef!important}
|
||||
.w3-emerald,.w3-hover-emerald:hover{color:#fff!important;background-color:#008a00!important}
|
||||
.w3-olive,.w3-hover-olive:hover{color:#fff!important;background-color:#6d8764!important}
|
||||
.w3-paper,.w3-hover-paper:hover{color:#000!important;background-color:#f8f9fa!important}
|
||||
.w3-sienna,.w3-hover-sienna:hover{color:#fff!important;background-color:#a0522d!important}
|
||||
.w3-taupe,.w3-hover-taupe:hover{color:#fff!important;background-color:#87794e!important}
|
||||
.w3-danger{color:#fff!important;background-color:#dd0000!important}
|
||||
.w3-note{color:#000!important;background-color:#fff599!important}
|
||||
.w3-info{color:#fff!important;background-color:#0a6fc2!important}
|
||||
.w3-warning{color:#000!important;background-color:#ffb305!important}
|
||||
.w3-success{color:#fff!important;background-color:#008a00!important}
|
||||
.w3-pale-red,.w3-hover-pale-red:hover{color:#000!important;background-color:#ffdddd!important}
|
||||
.w3-pale-green,.w3-hover-pale-green:hover{color:#000!important;background-color:#ddffdd!important}
|
||||
.w3-pale-yellow,.w3-hover-pale-yellow:hover{color:#000!important;background-color:#ffffcc!important}
|
||||
@@ -232,4 +248,4 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
|
||||
.w3-border-light-grey,.w3-hover-border-light-grey:hover,.w3-border-light-gray,.w3-hover-border-light-gray:hover{border-color:#f1f1f1!important}
|
||||
.w3-border-dark-grey,.w3-hover-border-dark-grey:hover,.w3-border-dark-gray,.w3-hover-border-dark-gray:hover{border-color:#616161!important}
|
||||
.w3-border-pale-red,.w3-hover-border-pale-red:hover{border-color:#ffe7e7!important}.w3-border-pale-green,.w3-hover-border-pale-green:hover{border-color:#e7ffe7!important}
|
||||
.w3-border-pale-yellow,.w3-hover-border-pale-yellow:hover{border-color:#ffffcc!important}.w3-border-pale-blue,.w3-hover-border-pale-blue:hover{border-color:#e7ffff!important}
|
||||
.w3-border-pale-yellow,.w3-hover-border-pale-yellow:hover{border-color:#ffffcc!important}.w3-border-pale-blue,.w3-hover-border-pale-blue:hover{border-color:#e7ffff!important}
|
||||
|
||||
10
default.nix
10
default.nix
@@ -1,4 +1,8 @@
|
||||
# How to upgrade to a newer version
|
||||
# - On the june and december release, you'll have to update nixpkgs to the current branch
|
||||
# Change `nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.11";`
|
||||
# to the latest release (see https://nixos.org/)
|
||||
# - Run `$ nix flake update`
|
||||
# - Comment `src.hash`
|
||||
# - Change `version`
|
||||
# - Run `$ nix build`
|
||||
@@ -21,14 +25,14 @@
|
||||
}:
|
||||
pkgs.stdenv.mkDerivation rec {
|
||||
pname = "tildefriends";
|
||||
version = "0.0.24";
|
||||
version = "0.2025.9";
|
||||
|
||||
src = pkgs.fetchFromGitea {
|
||||
domain = "dev.tildefriends.net";
|
||||
owner = "cory";
|
||||
repo = "tildefriends";
|
||||
rev = "v${version}";
|
||||
hash = "sha256-XlmRr08UmScY//qxUEXHzagXHCFqARRYr3q8RK/jKFY=";
|
||||
hash = "sha256-1nhsfhdOO5HIiiTMb+uROB8nDPL/UpOYm52hZ/OpPyk=";
|
||||
fetchSubmodules = true;
|
||||
};
|
||||
|
||||
@@ -46,7 +50,7 @@ pkgs.stdenv.mkDerivation rec {
|
||||
];
|
||||
|
||||
buildPhase = ''
|
||||
make -j $NIX_BUILD_CORES release
|
||||
make -j $NIX_BUILD_CORES release USE_SYSTEM_SSL=1
|
||||
'';
|
||||
|
||||
installPhase = ''
|
||||
|
||||
2
deps/c-ares
vendored
2
deps/c-ares
vendored
Submodule deps/c-ares updated: a57ff692ee...d3a507e920
2
deps/codemirror/cm6.js
vendored
2
deps/codemirror/cm6.js
vendored
File diff suppressed because one or more lines are too long
453
deps/codemirror_src/package-lock.json
generated
vendored
453
deps/codemirror_src/package-lock.json
generated
vendored
@@ -19,27 +19,21 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/autocomplete": {
|
||||
"version": "6.18.2",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.18.2.tgz",
|
||||
"integrity": "sha512-wJGylKtMFR/Ds6Gh01+OovXE/pncPiKZNNBKuC39pKnH+XK5d9+WsNqcrdxPjFPFTigRBqse0rfxw9UxrfyhPg==",
|
||||
"version": "6.19.1",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.19.1.tgz",
|
||||
"integrity": "sha512-q6NenYkEy2fn9+JyjIxMWcNjzTL/IhwqfzOut1/G3PrIFkrbl4AL7Wkse5tLrQUUyqGoAKU5+Pi5jnnXxH5HGw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/language": "^6.0.0",
|
||||
"@codemirror/state": "^6.0.0",
|
||||
"@codemirror/view": "^6.17.0",
|
||||
"@lezer/common": "^1.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@codemirror/language": "^6.0.0",
|
||||
"@codemirror/state": "^6.0.0",
|
||||
"@codemirror/view": "^6.0.0",
|
||||
"@lezer/common": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/commands": {
|
||||
"version": "6.7.1",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.7.1.tgz",
|
||||
"integrity": "sha512-llTrboQYw5H4THfhN4U3qCnSZ1SOJ60ohhz+SzU0ADGtwlc533DtklQP0vSFaQuCPDn3BPpOd1GbbnUtwNjsrw==",
|
||||
"version": "6.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.10.0.tgz",
|
||||
"integrity": "sha512-2xUIc5mHXQzT16JnyOFkh8PvfeXuIut3pslWGfsGOhxP/lpgRm9HOl/mpzLErgt5mXDovqA0d11P21gofRLb9w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/language": "^6.0.0",
|
||||
@@ -49,9 +43,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/lang-css": {
|
||||
"version": "6.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/lang-css/-/lang-css-6.3.0.tgz",
|
||||
"integrity": "sha512-CyR4rUNG9OYcXDZwMPvJdtb6PHbBDKUc/6Na2BIwZ6dKab1JQqKa4di+RNRY9Myn7JB81vayKwJeQ7jEdmNVDA==",
|
||||
"version": "6.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/lang-css/-/lang-css-6.3.1.tgz",
|
||||
"integrity": "sha512-kr5fwBGiGtmz6l0LSJIbno9QrifNMUusivHbnA1H6Dmqy4HZFte3UAICix1VuKo0lMPKQr2rqB+0BkKi/S3Ejg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/autocomplete": "^6.0.0",
|
||||
@@ -62,9 +56,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/lang-html": {
|
||||
"version": "6.4.9",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/lang-html/-/lang-html-6.4.9.tgz",
|
||||
"integrity": "sha512-aQv37pIMSlueybId/2PVSP6NPnmurFDVmZwzc7jszd2KAF8qd4VBbvNYPXWQq90WIARjsdVkPbw29pszmHws3Q==",
|
||||
"version": "6.4.11",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/lang-html/-/lang-html-6.4.11.tgz",
|
||||
"integrity": "sha512-9NsXp7Nwp891pQchI7gPdTwBuSuT3K65NGTHWHNJ55HjYcHLllr0rbIZNdOzas9ztc1EUVBlHou85FFZS4BNnw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/autocomplete": "^6.0.0",
|
||||
@@ -75,13 +69,13 @@
|
||||
"@codemirror/view": "^6.17.0",
|
||||
"@lezer/common": "^1.0.0",
|
||||
"@lezer/css": "^1.1.0",
|
||||
"@lezer/html": "^1.3.0"
|
||||
"@lezer/html": "^1.3.12"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/lang-javascript": {
|
||||
"version": "6.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/lang-javascript/-/lang-javascript-6.2.2.tgz",
|
||||
"integrity": "sha512-VGQfY+FCc285AhWuwjYxQyUQcYurWlxdKYT4bqwr3Twnd5wP5WSeu52t4tvvuWmljT4EmgEgZCqSieokhtY8hg==",
|
||||
"version": "6.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/lang-javascript/-/lang-javascript-6.2.4.tgz",
|
||||
"integrity": "sha512-0WVmhp1QOqZ4Rt6GlVGwKJN3KW7Xh4H2q8ZZNGZaP6lRdxXJzmjm4FqvmOojVj6khWJHIb9sp7U/72W7xQgqAA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/autocomplete": "^6.0.0",
|
||||
@@ -94,9 +88,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/lang-json": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/lang-json/-/lang-json-6.0.1.tgz",
|
||||
"integrity": "sha512-+T1flHdgpqDDlJZ2Lkil/rLiRy684WMLc74xUnjJH48GQdfJo/pudlTRreZmKwzP8/tGdKf83wlbAdOCzlJOGQ==",
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/lang-json/-/lang-json-6.0.2.tgz",
|
||||
"integrity": "sha512-x2OtO+AvwEHrEwR0FyyPtfDUiloG3rnVTSZV1W8UteaLL8/MajQd8DpvUb2YVzC+/T18aSDv0H9mu+xw0EStoQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/language": "^6.0.0",
|
||||
@@ -104,9 +98,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/language": {
|
||||
"version": "6.10.3",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.10.3.tgz",
|
||||
"integrity": "sha512-kDqEU5sCP55Oabl6E7m5N+vZRoc0iWqgDVhEKifcHzPzjqCegcO4amfrYVL9PmPZpl4G0yjkpTpUO/Ui8CzO8A==",
|
||||
"version": "6.11.3",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.11.3.tgz",
|
||||
"integrity": "sha512-9HBM2XnwDj7fnu0551HkGdrUrrqmYq/WC5iv6nbY2WdicXdGbhR/gfbZOH73Aqj4351alY1+aoG9rCNfiwS1RA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/state": "^6.0.0",
|
||||
@@ -118,20 +112,20 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/lint": {
|
||||
"version": "6.8.2",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.8.2.tgz",
|
||||
"integrity": "sha512-PDFG5DjHxSEjOXk9TQYYVjZDqlZTFaDBfhQixHnQOEVDDNHUbEh/hstAjcQJaA6FQdZTD1hquXTK0rVBLADR1g==",
|
||||
"version": "6.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.9.1.tgz",
|
||||
"integrity": "sha512-te7To1EQHePBQQzasDKWmK2xKINIXpk+xAiSYr9ZN+VB4KaT+/Hi2PEkeErTk5BV3PTz1TLyQL4MtJfPkKZ9sw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/state": "^6.0.0",
|
||||
"@codemirror/view": "^6.0.0",
|
||||
"@codemirror/view": "^6.35.0",
|
||||
"crelt": "^1.0.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/search": {
|
||||
"version": "6.5.7",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.5.7.tgz",
|
||||
"integrity": "sha512-6+iLsXvITWKHYlkgHPCs/qiX4dNzn8N78YfhOFvPtPYCkuXqZq10rAfsUMhOq7O/1VjJqdXRflyExlfVcu/9VQ==",
|
||||
"version": "6.5.11",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.5.11.tgz",
|
||||
"integrity": "sha512-KmWepDE6jUdL6n8cAAqIpRmLPBZ5ZKnicE8oGU/s3QrAVID+0VhLFrzUucVKHG5035/BSykhExDL/Xm7dHthiA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/state": "^6.0.0",
|
||||
@@ -140,15 +134,18 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/state": {
|
||||
"version": "6.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.4.1.tgz",
|
||||
"integrity": "sha512-QkEyUiLhsJoZkbumGZlswmAhA7CBU02Wrz7zvH4SrcifbsqwlXShVXg65f3v/ts57W3dqyamEriMhij1Z3Zz4A==",
|
||||
"license": "MIT"
|
||||
"version": "6.5.2",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.5.2.tgz",
|
||||
"integrity": "sha512-FVqsPqtPWKVVL3dPSxy8wEF/ymIEuVzF1PK3VbUgrxXpJUSHQWWZz4JMToquRxnkw+36LTamCZG2iua2Ptq0fA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@marijn/find-cluster-break": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/theme-one-dark": {
|
||||
"version": "6.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/theme-one-dark/-/theme-one-dark-6.1.2.tgz",
|
||||
"integrity": "sha512-F+sH0X16j/qFLMAfbciKTxVOwkdAS336b7AXTKOZhy8BR3eH/RelsnLgLFINrpST63mmN2OuwUt0W2ndUgYwUA==",
|
||||
"version": "6.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/theme-one-dark/-/theme-one-dark-6.1.3.tgz",
|
||||
"integrity": "sha512-NzBdIvEJmx6fjeremiGp3t/okrLPYT0d9orIc7AFun8oZcRk58aejkqhv6spnz4MLAevrKNPMQYXEWMg4s+sKA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/language": "^6.0.0",
|
||||
@@ -158,29 +155,26 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/view": {
|
||||
"version": "6.34.2",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.34.2.tgz",
|
||||
"integrity": "sha512-d6n0WFvL970A9Z+l9N2dO+Hk9ev4hDYQzIx+B9tCyBP0W5wPEszi1rhuyFesNSkLZzXbQE5FPH7F/z/TMJfoPA==",
|
||||
"version": "6.38.6",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.38.6.tgz",
|
||||
"integrity": "sha512-qiS0z1bKs5WOvHIAC0Cybmv4AJSkAXgX5aD6Mqd2epSLlVJsQl8NG23jCVouIgkh4All/mrbdsf2UOLFnJw0tw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/state": "^6.4.0",
|
||||
"@codemirror/state": "^6.5.0",
|
||||
"crelt": "^1.0.6",
|
||||
"style-mod": "^4.1.0",
|
||||
"w3c-keyname": "^2.2.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/gen-mapping": {
|
||||
"version": "0.3.5",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz",
|
||||
"integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==",
|
||||
"version": "0.3.13",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
|
||||
"integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jridgewell/set-array": "^1.2.1",
|
||||
"@jridgewell/sourcemap-codec": "^1.4.10",
|
||||
"@jridgewell/sourcemap-codec": "^1.5.0",
|
||||
"@jridgewell/trace-mapping": "^0.3.24"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/resolve-uri": {
|
||||
@@ -193,20 +187,10 @@
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/set-array": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz",
|
||||
"integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/source-map": {
|
||||
"version": "0.3.6",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz",
|
||||
"integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==",
|
||||
"version": "0.3.11",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz",
|
||||
"integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -215,16 +199,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/sourcemap-codec": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
|
||||
"integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==",
|
||||
"version": "1.5.5",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
|
||||
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@jridgewell/trace-mapping": {
|
||||
"version": "0.3.25",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
|
||||
"integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
|
||||
"version": "0.3.31",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
|
||||
"integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -233,35 +217,35 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@lezer/common": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.2.3.tgz",
|
||||
"integrity": "sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA==",
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.3.0.tgz",
|
||||
"integrity": "sha512-L9X8uHCYU310o99L3/MpJKYxPzXPOS7S0NmBaM7UO/x2Kb2WbmMLSkfvdr1KxRIFYOpbY0Jhn7CfLSUDzL8arQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@lezer/css": {
|
||||
"version": "1.1.9",
|
||||
"resolved": "https://registry.npmjs.org/@lezer/css/-/css-1.1.9.tgz",
|
||||
"integrity": "sha512-TYwgljcDv+YrV0MZFFvYFQHCfGgbPMR6nuqLabBdmZoFH3EP1gvw8t0vae326Ne3PszQkbXfVBjCnf3ZVCr0bA==",
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@lezer/css/-/css-1.3.0.tgz",
|
||||
"integrity": "sha512-pBL7hup88KbI7hXnZV3PQsn43DHy6TWyzuyk2AO9UyoXcDltvIdqWKE1dLL/45JVZ+YZkHe1WVHqO6wugZZWcw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@lezer/common": "^1.2.0",
|
||||
"@lezer/highlight": "^1.0.0",
|
||||
"@lezer/lr": "^1.0.0"
|
||||
"@lezer/lr": "^1.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@lezer/highlight": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.1.tgz",
|
||||
"integrity": "sha512-Z5duk4RN/3zuVO7Jq0pGLJ3qynpxUVsh7IbUbGj88+uV2ApSAn6kWg2au3iJb+0Zi7kKtqffIESgNcRXWZWmSA==",
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.2.tgz",
|
||||
"integrity": "sha512-z8TQwaBXXQIvG6i2g3e9cgMwUUXu9Ib7jo2qRRggdhwKpM56Dw3PM3wmexn+EGaaOZ7az0K7sjc3/gcGW7sz7A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@lezer/common": "^1.0.0"
|
||||
"@lezer/common": "^1.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@lezer/html": {
|
||||
"version": "1.3.10",
|
||||
"resolved": "https://registry.npmjs.org/@lezer/html/-/html-1.3.10.tgz",
|
||||
"integrity": "sha512-dqpT8nISx/p9Do3AchvYGV3qYc4/rKr3IBZxlHmpIKam56P47RSHkSF5f13Vu9hebS1jM0HmtJIwLbWz1VIY6w==",
|
||||
"version": "1.3.12",
|
||||
"resolved": "https://registry.npmjs.org/@lezer/html/-/html-1.3.12.tgz",
|
||||
"integrity": "sha512-RJ7eRWdaJe3bsiiLLHjCFT1JMk8m1YP9kaUbvu2rMLEoOnke9mcTVDyfOslsln0LtujdWespjJ39w6zo+RsQYw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@lezer/common": "^1.2.0",
|
||||
@@ -270,9 +254,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@lezer/javascript": {
|
||||
"version": "1.4.19",
|
||||
"resolved": "https://registry.npmjs.org/@lezer/javascript/-/javascript-1.4.19.tgz",
|
||||
"integrity": "sha512-j44kbR1QL26l6dMunZ1uhKBFteVGLVCBGNUD2sUaMnic+rbTviVuoK0CD1l9FTW31EueWvFFswCKMH7Z+M3JRA==",
|
||||
"version": "1.5.4",
|
||||
"resolved": "https://registry.npmjs.org/@lezer/javascript/-/javascript-1.5.4.tgz",
|
||||
"integrity": "sha512-vvYx3MhWqeZtGPwDStM2dwgljd5smolYD2lR2UyFcHfxbBQebqx8yjmFmxtJ/E6nN6u1D9srOiVWm3Rb4tmcUA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@lezer/common": "^1.2.0",
|
||||
@@ -281,9 +265,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@lezer/json": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@lezer/json/-/json-1.0.2.tgz",
|
||||
"integrity": "sha512-xHT2P4S5eeCYECyKNPhr4cbEL9tc8w83SPwRC373o9uEdrvGKTZoJVAGxpOsZckMlEh9W23Pc72ew918RWQOBQ==",
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@lezer/json/-/json-1.0.3.tgz",
|
||||
"integrity": "sha512-BP9KzdF9Y35PDpv04r0VeSTKDeox5vVr3efE7eBbx3r4s3oNLfunchejZhjArmeieBH+nVOpgIiBJpEAv8ilqQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@lezer/common": "^1.2.0",
|
||||
@@ -300,10 +284,16 @@
|
||||
"@lezer/common": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@marijn/find-cluster-break": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.2.tgz",
|
||||
"integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@rollup/plugin-node-resolve": {
|
||||
"version": "15.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.3.0.tgz",
|
||||
"integrity": "sha512-9eO5McEICxMzJpDW9OnMYSv4Sta3hmt7VtBFz5zR9273suNOydOyq/FrGeGy+KsTRFm8w0SLVhzig2ILFT63Ag==",
|
||||
"version": "15.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.3.1.tgz",
|
||||
"integrity": "sha512-tgg6b91pAybXHJQMAAwW9VuWBO6Thi+q7BCNARLwSqlmsHz0XYURtGvh/AuwSADXSI4h/2uHbs7s4FzlZDGSGA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@rollup/pluginutils": "^5.0.1",
|
||||
@@ -348,9 +338,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rollup/pluginutils": {
|
||||
"version": "5.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.3.tgz",
|
||||
"integrity": "sha512-Pnsb6f32CD2W3uCaLZIzDmeFyQ2b8UWMFI7xtwUezpcGBDVDW6y9XgAWIlARiGAo6eNF5FK5aQTr0LFyNyqq5A==",
|
||||
"version": "5.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.3.0.tgz",
|
||||
"integrity": "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/estree": "^1.0.0",
|
||||
@@ -370,9 +360,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rollup/rollup-android-arm-eabi": {
|
||||
"version": "4.24.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.24.4.tgz",
|
||||
"integrity": "sha512-jfUJrFct/hTA0XDM5p/htWKoNNTbDLY0KRwEt6pyOA6k2fmk0WVwl65PdUdJZgzGEHWx+49LilkcSaumQRyNQw==",
|
||||
"version": "4.52.5",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.5.tgz",
|
||||
"integrity": "sha512-8c1vW4ocv3UOMp9K+gToY5zL2XiiVw3k7f1ksf4yO1FlDFQ1C2u72iACFnSOceJFsWskc2WZNqeRhFRPzv+wtQ==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -383,9 +373,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-android-arm64": {
|
||||
"version": "4.24.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.24.4.tgz",
|
||||
"integrity": "sha512-j4nrEO6nHU1nZUuCfRKoCcvh7PIywQPUCBa2UsootTHvTHIoIu2BzueInGJhhvQO/2FTRdNYpf63xsgEqH9IhA==",
|
||||
"version": "4.52.5",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.52.5.tgz",
|
||||
"integrity": "sha512-mQGfsIEFcu21mvqkEKKu2dYmtuSZOBMmAl5CFlPGLY94Vlcm+zWApK7F/eocsNzp8tKmbeBP8yXyAbx0XHsFNA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -396,9 +386,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-darwin-arm64": {
|
||||
"version": "4.24.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.24.4.tgz",
|
||||
"integrity": "sha512-GmU/QgGtBTeraKyldC7cDVVvAJEOr3dFLKneez/n7BvX57UdhOqDsVwzU7UOnYA7AAOt+Xb26lk79PldDHgMIQ==",
|
||||
"version": "4.52.5",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.52.5.tgz",
|
||||
"integrity": "sha512-takF3CR71mCAGA+v794QUZ0b6ZSrgJkArC+gUiG6LB6TQty9T0Mqh3m2ImRBOxS2IeYBo4lKWIieSvnEk2OQWA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -409,9 +399,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-darwin-x64": {
|
||||
"version": "4.24.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.24.4.tgz",
|
||||
"integrity": "sha512-N6oDBiZCBKlwYcsEPXGDE4g9RoxZLK6vT98M8111cW7VsVJFpNEqvJeIPfsCzbf0XEakPslh72X0gnlMi4Ddgg==",
|
||||
"version": "4.52.5",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.52.5.tgz",
|
||||
"integrity": "sha512-W901Pla8Ya95WpxDn//VF9K9u2JbocwV/v75TE0YIHNTbhqUTv9w4VuQ9MaWlNOkkEfFwkdNhXgcLqPSmHy0fA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -422,9 +412,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-freebsd-arm64": {
|
||||
"version": "4.24.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.24.4.tgz",
|
||||
"integrity": "sha512-py5oNShCCjCyjWXCZNrRGRpjWsF0ic8f4ieBNra5buQz0O/U6mMXCpC1LvrHuhJsNPgRt36tSYMidGzZiJF6mw==",
|
||||
"version": "4.52.5",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.52.5.tgz",
|
||||
"integrity": "sha512-QofO7i7JycsYOWxe0GFqhLmF6l1TqBswJMvICnRUjqCx8b47MTo46W8AoeQwiokAx3zVryVnxtBMcGcnX12LvA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -435,9 +425,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-freebsd-x64": {
|
||||
"version": "4.24.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.24.4.tgz",
|
||||
"integrity": "sha512-L7VVVW9FCnTTp4i7KrmHeDsDvjB4++KOBENYtNYAiYl96jeBThFfhP6HVxL74v4SiZEVDH/1ILscR5U9S4ms4g==",
|
||||
"version": "4.52.5",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.52.5.tgz",
|
||||
"integrity": "sha512-jr21b/99ew8ujZubPo9skbrItHEIE50WdV86cdSoRkKtmWa+DDr6fu2c/xyRT0F/WazZpam6kk7IHBerSL7LDQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -448,9 +438,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
|
||||
"version": "4.24.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.24.4.tgz",
|
||||
"integrity": "sha512-10ICosOwYChROdQoQo589N5idQIisxjaFE/PAnX2i0Zr84mY0k9zul1ArH0rnJ/fpgiqfu13TFZR5A5YJLOYZA==",
|
||||
"version": "4.52.5",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.52.5.tgz",
|
||||
"integrity": "sha512-PsNAbcyv9CcecAUagQefwX8fQn9LQ4nZkpDboBOttmyffnInRy8R8dSg6hxxl2Re5QhHBf6FYIDhIj5v982ATQ==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -461,9 +451,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
|
||||
"version": "4.24.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.24.4.tgz",
|
||||
"integrity": "sha512-ySAfWs69LYC7QhRDZNKqNhz2UKN8LDfbKSMAEtoEI0jitwfAG2iZwVqGACJT+kfYvvz3/JgsLlcBP+WWoKCLcw==",
|
||||
"version": "4.52.5",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.52.5.tgz",
|
||||
"integrity": "sha512-Fw4tysRutyQc/wwkmcyoqFtJhh0u31K+Q6jYjeicsGJJ7bbEq8LwPWV/w0cnzOqR2m694/Af6hpFayLJZkG2VQ==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -474,9 +464,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm64-gnu": {
|
||||
"version": "4.24.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.24.4.tgz",
|
||||
"integrity": "sha512-uHYJ0HNOI6pGEeZ/5mgm5arNVTI0nLlmrbdph+pGXpC9tFHFDQmDMOEqkmUObRfosJqpU8RliYoGz06qSdtcjg==",
|
||||
"version": "4.52.5",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.52.5.tgz",
|
||||
"integrity": "sha512-a+3wVnAYdQClOTlyapKmyI6BLPAFYs0JM8HRpgYZQO02rMR09ZcV9LbQB+NL6sljzG38869YqThrRnfPMCDtZg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -487,9 +477,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm64-musl": {
|
||||
"version": "4.24.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.24.4.tgz",
|
||||
"integrity": "sha512-38yiWLemQf7aLHDgTg85fh3hW9stJ0Muk7+s6tIkSUOMmi4Xbv5pH/5Bofnsb6spIwD5FJiR+jg71f0CH5OzoA==",
|
||||
"version": "4.52.5",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.52.5.tgz",
|
||||
"integrity": "sha512-AvttBOMwO9Pcuuf7m9PkC1PUIKsfaAJ4AYhy944qeTJgQOqJYJ9oVl2nYgY7Rk0mkbsuOpCAYSs6wLYB2Xiw0Q==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -499,10 +489,23 @@
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
|
||||
"version": "4.24.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.24.4.tgz",
|
||||
"integrity": "sha512-q73XUPnkwt9ZNF2xRS4fvneSuaHw2BXuV5rI4cw0fWYVIWIBeDZX7c7FWhFQPNTnE24172K30I+dViWRVD9TwA==",
|
||||
"node_modules/@rollup/rollup-linux-loong64-gnu": {
|
||||
"version": "4.52.5",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.52.5.tgz",
|
||||
"integrity": "sha512-DkDk8pmXQV2wVrF6oq5tONK6UHLz/XcEVow4JTTerdeV1uqPeHxwcg7aFsfnSm9L+OO8WJsWotKM2JJPMWrQtA==",
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-ppc64-gnu": {
|
||||
"version": "4.52.5",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.52.5.tgz",
|
||||
"integrity": "sha512-W/b9ZN/U9+hPQVvlGwjzi+Wy4xdoH2I8EjaCkMvzpI7wJUs8sWJ03Rq96jRnHkSrcHTpQe8h5Tg3ZzUPGauvAw==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
@@ -513,9 +516,22 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
|
||||
"version": "4.24.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.24.4.tgz",
|
||||
"integrity": "sha512-Aie/TbmQi6UXokJqDZdmTJuZBCU3QBDA8oTKRGtd4ABi/nHgXICulfg1KI6n9/koDsiDbvHAiQO3YAUNa/7BCw==",
|
||||
"version": "4.52.5",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.52.5.tgz",
|
||||
"integrity": "sha512-sjQLr9BW7R/ZiXnQiWPkErNfLMkkWIoCz7YMn27HldKsADEKa5WYdobaa1hmN6slu9oWQbB6/jFpJ+P2IkVrmw==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-riscv64-musl": {
|
||||
"version": "4.52.5",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.52.5.tgz",
|
||||
"integrity": "sha512-hq3jU/kGyjXWTvAh2awn8oHroCbrPm8JqM7RUpKjalIRWWXE01CQOf/tUNWNHjmbMHg/hmNCwc/Pz3k1T/j/Lg==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
@@ -526,9 +542,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-s390x-gnu": {
|
||||
"version": "4.24.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.24.4.tgz",
|
||||
"integrity": "sha512-P8MPErVO/y8ohWSP9JY7lLQ8+YMHfTI4bAdtCi3pC2hTeqFJco2jYspzOzTUB8hwUWIIu1xwOrJE11nP+0JFAQ==",
|
||||
"version": "4.52.5",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.52.5.tgz",
|
||||
"integrity": "sha512-gn8kHOrku8D4NGHMK1Y7NA7INQTRdVOntt1OCYypZPRt6skGbddska44K8iocdpxHTMMNui5oH4elPH4QOLrFQ==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
@@ -539,9 +555,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-x64-gnu": {
|
||||
"version": "4.24.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.24.4.tgz",
|
||||
"integrity": "sha512-K03TljaaoPK5FOyNMZAAEmhlyO49LaE4qCsr0lYHUKyb6QacTNF9pnfPpXnFlFD3TXuFbFbz7tJ51FujUXkXYA==",
|
||||
"version": "4.52.5",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.5.tgz",
|
||||
"integrity": "sha512-hXGLYpdhiNElzN770+H2nlx+jRog8TyynpTVzdlc6bndktjKWyZyiCsuDAlpd+j+W+WNqfcyAWz9HxxIGfZm1Q==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -552,9 +568,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-x64-musl": {
|
||||
"version": "4.24.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.24.4.tgz",
|
||||
"integrity": "sha512-VJYl4xSl/wqG2D5xTYncVWW+26ICV4wubwN9Gs5NrqhJtayikwCXzPL8GDsLnaLU3WwhQ8W02IinYSFJfyo34Q==",
|
||||
"version": "4.52.5",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.52.5.tgz",
|
||||
"integrity": "sha512-arCGIcuNKjBoKAXD+y7XomR9gY6Mw7HnFBv5Rw7wQRvwYLR7gBAgV7Mb2QTyjXfTveBNFAtPt46/36vV9STLNg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -564,10 +580,23 @@
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-openharmony-arm64": {
|
||||
"version": "4.52.5",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.52.5.tgz",
|
||||
"integrity": "sha512-QoFqB6+/9Rly/RiPjaomPLmR/13cgkIGfA40LHly9zcH1S0bN2HVFYk3a1eAyHQyjs3ZJYlXvIGtcCs5tko9Cw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openharmony"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-arm64-msvc": {
|
||||
"version": "4.24.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.24.4.tgz",
|
||||
"integrity": "sha512-ku2GvtPwQfCqoPFIJCqZ8o7bJcj+Y54cZSr43hHca6jLwAiCbZdBUOrqE6y29QFajNAzzpIOwsckaTFmN6/8TA==",
|
||||
"version": "4.52.5",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.52.5.tgz",
|
||||
"integrity": "sha512-w0cDWVR6MlTstla1cIfOGyl8+qb93FlAVutcor14Gf5Md5ap5ySfQ7R9S/NjNaMLSFdUnKGEasmVnu3lCMqB7w==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -578,9 +607,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-ia32-msvc": {
|
||||
"version": "4.24.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.24.4.tgz",
|
||||
"integrity": "sha512-V3nCe+eTt/W6UYNr/wGvO1fLpHUrnlirlypZfKCT1fG6hWfqhPgQV/K/mRBXBpxc0eKLIF18pIOFVPh0mqHjlg==",
|
||||
"version": "4.52.5",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.52.5.tgz",
|
||||
"integrity": "sha512-Aufdpzp7DpOTULJCuvzqcItSGDH73pF3ko/f+ckJhxQyHtp67rHw3HMNxoIdDMUITJESNE6a8uh4Lo4SLouOUg==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
@@ -590,10 +619,23 @@
|
||||
"win32"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-x64-gnu": {
|
||||
"version": "4.52.5",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.52.5.tgz",
|
||||
"integrity": "sha512-UGBUGPFp1vkj6p8wCRraqNhqwX/4kNQPS57BCFc8wYh0g94iVIW33wJtQAx3G7vrjjNtRaxiMUylM0ktp/TRSQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-x64-msvc": {
|
||||
"version": "4.24.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.24.4.tgz",
|
||||
"integrity": "sha512-LTw1Dfd0mBIEqUVCxbvTE/LLo+9ZxVC9k99v1v4ahg9Aak6FpqOfNu5kRkeTAn0wphoC4JU7No1/rL+bBCEwhg==",
|
||||
"version": "4.52.5",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.52.5.tgz",
|
||||
"integrity": "sha512-TAcgQh2sSkykPRWLrdyy2AiceMckNf5loITqXxFI5VuQjS5tSuw3WlwdN8qv8vzjLAUTvYaH/mVjSFpbkFbpTg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -604,9 +646,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@types/estree": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz",
|
||||
"integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==",
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
|
||||
"integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/resolve": {
|
||||
@@ -616,9 +658,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/acorn": {
|
||||
"version": "8.14.0",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz",
|
||||
"integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==",
|
||||
"version": "8.15.0",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
|
||||
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
@@ -636,9 +678,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/codemirror": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/codemirror/-/codemirror-6.0.1.tgz",
|
||||
"integrity": "sha512-J8j+nZ+CdWmIeFIGXEFbFPtpiYacFMDR8GlHK3IyHQJMCaVRfGx9NT+Hxivv1ckLWPvNdZqndbr/7lVhrf/Svg==",
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/codemirror/-/codemirror-6.0.2.tgz",
|
||||
"integrity": "sha512-VhydHotNW5w1UGK0Qj96BwSk/Zqbp9WbnyK2W/eVMv4QyF41INRGpjUhFJY7/uDNuudSc33a/PKr4iDqRduvHw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/autocomplete": "^6.0.0",
|
||||
@@ -714,9 +756,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/is-core-module": {
|
||||
"version": "2.15.1",
|
||||
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz",
|
||||
"integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==",
|
||||
"version": "2.16.1",
|
||||
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
|
||||
"integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"hasown": "^2.0.2"
|
||||
@@ -741,9 +783,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/picomatch": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
|
||||
"integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
@@ -763,29 +805,32 @@
|
||||
}
|
||||
},
|
||||
"node_modules/resolve": {
|
||||
"version": "1.22.8",
|
||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
|
||||
"integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==",
|
||||
"version": "1.22.11",
|
||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz",
|
||||
"integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"is-core-module": "^2.13.0",
|
||||
"is-core-module": "^2.16.1",
|
||||
"path-parse": "^1.0.7",
|
||||
"supports-preserve-symlinks-flag": "^1.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"resolve": "bin/resolve"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/rollup": {
|
||||
"version": "4.24.4",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.24.4.tgz",
|
||||
"integrity": "sha512-vGorVWIsWfX3xbcyAS+I047kFKapHYivmkaT63Smj77XwvLSJos6M1xGqZnBPFQFBRZDOcG1QnYEIxAvTr/HjA==",
|
||||
"version": "4.52.5",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.5.tgz",
|
||||
"integrity": "sha512-3GuObel8h7Kqdjt0gxkEzaifHTqLVW56Y/bjN7PSQtkKr0w3V/QYSdt6QWYtd7A1xUtYQigtdUfgj1RvWVtorw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/estree": "1.0.6"
|
||||
"@types/estree": "1.0.8"
|
||||
},
|
||||
"bin": {
|
||||
"rollup": "dist/bin/rollup"
|
||||
@@ -795,24 +840,28 @@
|
||||
"npm": ">=8.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@rollup/rollup-android-arm-eabi": "4.24.4",
|
||||
"@rollup/rollup-android-arm64": "4.24.4",
|
||||
"@rollup/rollup-darwin-arm64": "4.24.4",
|
||||
"@rollup/rollup-darwin-x64": "4.24.4",
|
||||
"@rollup/rollup-freebsd-arm64": "4.24.4",
|
||||
"@rollup/rollup-freebsd-x64": "4.24.4",
|
||||
"@rollup/rollup-linux-arm-gnueabihf": "4.24.4",
|
||||
"@rollup/rollup-linux-arm-musleabihf": "4.24.4",
|
||||
"@rollup/rollup-linux-arm64-gnu": "4.24.4",
|
||||
"@rollup/rollup-linux-arm64-musl": "4.24.4",
|
||||
"@rollup/rollup-linux-powerpc64le-gnu": "4.24.4",
|
||||
"@rollup/rollup-linux-riscv64-gnu": "4.24.4",
|
||||
"@rollup/rollup-linux-s390x-gnu": "4.24.4",
|
||||
"@rollup/rollup-linux-x64-gnu": "4.24.4",
|
||||
"@rollup/rollup-linux-x64-musl": "4.24.4",
|
||||
"@rollup/rollup-win32-arm64-msvc": "4.24.4",
|
||||
"@rollup/rollup-win32-ia32-msvc": "4.24.4",
|
||||
"@rollup/rollup-win32-x64-msvc": "4.24.4",
|
||||
"@rollup/rollup-android-arm-eabi": "4.52.5",
|
||||
"@rollup/rollup-android-arm64": "4.52.5",
|
||||
"@rollup/rollup-darwin-arm64": "4.52.5",
|
||||
"@rollup/rollup-darwin-x64": "4.52.5",
|
||||
"@rollup/rollup-freebsd-arm64": "4.52.5",
|
||||
"@rollup/rollup-freebsd-x64": "4.52.5",
|
||||
"@rollup/rollup-linux-arm-gnueabihf": "4.52.5",
|
||||
"@rollup/rollup-linux-arm-musleabihf": "4.52.5",
|
||||
"@rollup/rollup-linux-arm64-gnu": "4.52.5",
|
||||
"@rollup/rollup-linux-arm64-musl": "4.52.5",
|
||||
"@rollup/rollup-linux-loong64-gnu": "4.52.5",
|
||||
"@rollup/rollup-linux-ppc64-gnu": "4.52.5",
|
||||
"@rollup/rollup-linux-riscv64-gnu": "4.52.5",
|
||||
"@rollup/rollup-linux-riscv64-musl": "4.52.5",
|
||||
"@rollup/rollup-linux-s390x-gnu": "4.52.5",
|
||||
"@rollup/rollup-linux-x64-gnu": "4.52.5",
|
||||
"@rollup/rollup-linux-x64-musl": "4.52.5",
|
||||
"@rollup/rollup-openharmony-arm64": "4.52.5",
|
||||
"@rollup/rollup-win32-arm64-msvc": "4.52.5",
|
||||
"@rollup/rollup-win32-ia32-msvc": "4.52.5",
|
||||
"@rollup/rollup-win32-x64-gnu": "4.52.5",
|
||||
"@rollup/rollup-win32-x64-msvc": "4.52.5",
|
||||
"fsevents": "~2.3.2"
|
||||
}
|
||||
},
|
||||
@@ -876,9 +925,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/style-mod": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.2.tgz",
|
||||
"integrity": "sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw==",
|
||||
"version": "4.1.3",
|
||||
"resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.3.tgz",
|
||||
"integrity": "sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/supports-preserve-symlinks-flag": {
|
||||
@@ -894,14 +943,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/terser": {
|
||||
"version": "5.36.0",
|
||||
"resolved": "https://registry.npmjs.org/terser/-/terser-5.36.0.tgz",
|
||||
"integrity": "sha512-IYV9eNMuFAV4THUspIRXkLakHnV6XO7FEdtKjf/mDyrnqUg9LnlOn6/RwRvM9SZjR4GUq8Nk8zj67FzVARr74w==",
|
||||
"version": "5.44.0",
|
||||
"resolved": "https://registry.npmjs.org/terser/-/terser-5.44.0.tgz",
|
||||
"integrity": "sha512-nIVck8DK+GM/0Frwd+nIhZ84pR/BX7rmXMfYwyg+Sri5oGVE99/E3KvXqpC2xHFxyqXyGHTKBSioxxplrO4I4w==",
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause",
|
||||
"dependencies": {
|
||||
"@jridgewell/source-map": "^0.3.3",
|
||||
"acorn": "^8.8.2",
|
||||
"acorn": "^8.15.0",
|
||||
"commander": "^2.20.0",
|
||||
"source-map-support": "~0.5.20"
|
||||
},
|
||||
|
||||
2
deps/libbacktrace
vendored
2
deps/libbacktrace
vendored
Submodule deps/libbacktrace updated: d48f84034c...2f67a3abfd
2
deps/libuv
vendored
2
deps/libuv
vendored
Submodule deps/libuv updated: e1095c7a43...5152db2cbf
42
deps/lit/lit-all.min.js
vendored
42
deps/lit/lit-all.min.js
vendored
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user