Compare commits

...

959 Commits
v0.0.9 ... main

Author SHA1 Message Date
e5f58c2898 Produce user info for the server identity for admin users.
Some checks are pending
Build Tilde Friends / Build-All (push) Waiting to run
2024-09-19 12:22:38 -04:00
f83863ef01 This doc was ancient, so paste some of the latest from the wiki in. It's something.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 19m18s
2024-09-18 20:16:35 -04:00
837f069cf5 Update CodeMirror. 2024-09-18 20:16:35 -04:00
9f057dc29a Add F-Droid and c-ares to the welcome page.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2024-09-18 20:01:36 -04:00
c4904f176c Distinguish the server identity in the identity app.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 16m46s
2024-09-18 19:09:44 -04:00
d3a5aba703 A brave new world where admin users can use the server identity.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 16m31s
2024-09-17 12:47:28 -04:00
9e283e427c Fix viewing apps by blob ID URL.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 16m18s
2024-09-16 12:45:06 -04:00
133ba31d66 c-ares 1.33.1. 2024-09-15 08:57:22 -04:00
241a65a92a sus. Disable a warning in c-ares during ltcg with gcc 13 on Haiku.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 19m2s
2024-09-15 12:52:28 -04:00
0b54795bab Merge branch 'main' of https://dev.tildefriends.net/cory/tildefriends
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 16m29s
2024-09-11 20:28:06 -04:00
6208193de5 Fix plumbing for replies on the search tab.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2024-09-11 20:25:55 -04:00
c53321532f Update CodeMirror. 2024-09-11 20:18:57 -04:00
34f25e3e06 How did I not have an index on type? Wow.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2024-09-11 19:53:07 -04:00
c46244366e I don't know what GHES is, but it says to use this version, so sure.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 15m50s
2024-09-11 19:52:43 -04:00
6518af04fc It finally built. Now let's upload some artifacts.
Some checks failed
Build Tilde Friends / Build-All (push) Failing after 16m15s
2024-09-11 19:33:37 -04:00
bf137ff1f7 Seriously? More volume mount config.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 20m30s
2024-09-11 19:00:30 -04:00
1877955b62 Syntax?
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2024-09-11 18:54:39 -04:00
50d0875de2 Mount volumes across another container?
Some checks are pending
Build Tilde Friends / Build-All (push) Waiting to run
2024-09-11 18:49:06 -04:00
bf151e6b7d I am confused.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2024-09-11 18:41:55 -04:00
82893402d0 Maybe we can sign here?
Some checks failed
Build Tilde Friends / Build-All (push) Failing after 17m55s
2024-09-10 21:54:36 -04:00
8049102787 More silent OpenSSL build for mingw.
Some checks failed
Build Tilde Friends / Build-All (push) Failing after 19m24s
2024-09-10 21:19:15 -04:00
f42cc3d9fd Find the Android SDKs you just installed.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2024-09-10 21:01:20 -04:00
5f9a5208db Appease the sdkmanager version number whatever.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 21m56s
2024-09-10 20:34:25 -04:00
6df506d238 Spelling.
Some checks failed
Build Tilde Friends / Build-All (push) Failing after 5m7s
2024-09-10 20:25:51 -04:00
2bd3354256 yaml better??
Some checks failed
Build Tilde Friends / Build-All (push) Failing after 2m32s
2024-09-10 20:22:06 -04:00
b55aaa1d18 Maybe CI android?? 2024-09-10 20:20:40 -04:00
34e19505bd No longer need this test. 2024-09-09 15:57:43 -04:00
6e06ec0904 Clean up connections that don't handshake in time.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 11m26s
2024-09-09 15:25:10 -04:00
a5814074fe OpenSSL 3.3.2.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 7m26s
2024-09-04 20:24:32 -04:00
d7479df5a2 Latest CodeMirror.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 8m23s
2024-09-04 20:11:28 -04:00
34508aa0ae dist slightly more in parallel. Exclude dotfiles from data.zip.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2024-09-04 20:07:26 -04:00
ae096b2c9c Try harder to make webview localStorage work on different versions. I suspect that's what #73 is about.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 7m43s
2024-09-04 12:50:12 -04:00
95d036e34a Build an AppImage. Why not?
Some checks failed
Build Tilde Friends / Build-All (push) Failing after 6m27s
2024-08-28 20:55:52 -04:00
4af5e8ec42 I guess prettier says this, now.
Some checks failed
Build Tilde Friends / Build-All (push) Failing after 6m30s
2024-08-28 20:24:26 -04:00
2a5f71bd5d This now lives in the fdroiddata repository.
Some checks failed
Build Tilde Friends / Build-All (push) Failing after 7m26s
2024-08-28 19:59:49 -04:00
97fb63dda1 Actually 0.0.23-wip. 2024-08-28 19:59:34 -04:00
87d42e3b3b 0.0.23-wip again. Let's gooooo.
Some checks failed
Build Tilde Friends / Build-All (push) Failing after 7m45s
2024-08-28 19:49:36 -04:00
0394129a4c nix => 0.0.22 again.
Some checks are pending
Build Tilde Friends / Build-All (push) Waiting to run
2024-08-28 19:42:31 -04:00
3c499c834b Fix stale data being saved when setting global settings.
Some checks failed
Build Tilde Friends / Build-All (push) Failing after 8m53s
2024-08-28 19:39:05 -04:00
17d6cc7d46 Let's try 0.0.22 again.
Some checks failed
Build Tilde Friends / Build-All (push) Failing after 7m47s
2024-08-28 19:20:55 -04:00
646bd7dc38 Fix changing boolean settings. 2024-08-28 19:16:19 -04:00
56e483782d Let's start work on 0.0.23. Clean out some libuv non-submodule cruft while I'm in here. 2024-08-28 19:10:16 -04:00
e1b9066b26 nix => 0.0.22.
Some checks failed
Build Tilde Friends / Build-All (push) Failing after 8m56s
2024-08-28 18:49:35 -04:00
7114ce2516 Let's release 0.0.22.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 8m54s
2024-08-28 18:40:10 -04:00
9240c6570a Changelog updates. Almost ready for a release.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 8m48s
2024-08-26 12:27:46 -04:00
f80a44ccd7 Title case settings names in the admin app.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2024-08-26 12:24:36 -04:00
e6f5eb244e Missing port.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 9m6s
2024-08-25 22:00:35 -04:00
ab62e83110 Fixed some peer ID brokenness.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2024-08-25 21:56:01 -04:00
aeefb9e536 Configure c-ares for haiku a bit better.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 7m16s
2024-08-25 13:48:57 -04:00
ee0efa536a Fix and assert against some more unsafe cross-thread JSContext use.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 7m10s
2024-08-25 13:30:46 -04:00
2523130fdc Fix some weird layout in the admin app on mobile.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 7m16s
2024-08-25 13:03:19 -04:00
c024777184 #buildfix
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 6m58s
2024-08-25 12:45:42 -04:00
5951d7cd2d Kill some warnings.
Some checks failed
Build Tilde Friends / Build-All (push) Failing after 3m41s
2024-08-25 10:07:44 -04:00
011670c70b Pass along and use the actual port we're listening on for peers.exchange.
Some checks failed
Build Tilde Friends / Build-All (push) Failing after 4m5s
2024-08-25 09:50:28 -04:00
6cebd6c769 Try to be more static.
Some checks failed
Build Tilde Friends / Build-All (push) Failing after 3m49s
2024-08-25 09:39:05 -04:00
546ae5cbf1 Latest CodeMirror. 2024-08-24 21:57:13 -04:00
f543cc642e Clean up some error'd RPC requests. Don't send blobs.createWants if we're not replicating.
Some checks failed
Build Tilde Friends / Build-All (push) Failing after 3m59s
2024-08-24 10:39:47 -04:00
8ac3c5ea22 Keep c-ares initialized. Fixes android, which can't just be re-initialized.
Some checks failed
Build Tilde Friends / Build-All (push) Failing after 3m48s
2024-08-22 12:43:20 -04:00
63918f0680 Make blobs.has do its work off the main thread so it doesn't violate that assert, and make the test cover such things a bit better.
Some checks failed
Build Tilde Friends / Build-All (push) Failing after 3m48s
2024-08-21 22:55:40 -04:00
bfb3d8b8a2 Add an option to disable account registation, and fix use of a JSContext from the wrong thread along the way.
Some checks are pending
Build Tilde Friends / Build-All (push) Waiting to run
2024-08-21 20:56:21 -04:00
e38ff99607 Special treatment to make TXT record lookup work on android.
Some checks are pending
Build Tilde Friends / Build-All (push) Waiting to run
2024-08-21 20:27:43 -04:00
b0e3d922c8 libuv busy loop in uv__run_timers with -flto. Sigh.
Some checks are pending
Build Tilde Friends / Build-All (push) Waiting to run
2024-08-21 19:40:07 -04:00
a15bb8e994 Don't rely on being idle to do anything. Fixes JS job starvation on slow machines more.
Some checks are pending
Build Tilde Friends / Build-All (push) Waiting to run
2024-08-21 12:53:38 -04:00
6f487100cd Format. 2024-08-20 12:35:42 -04:00
0693a2315f Fix async job starvation if everything is running too slowly.
Some checks are pending
Build Tilde Friends / Build-All (push) Waiting to run
2024-08-20 12:26:34 -04:00
f360e886ff Make -t peer_exchange complete and test that something happened.
Some checks are pending
Build Tilde Friends / Build-All (push) Waiting to run
2024-08-19 12:29:40 -04:00
6ea08cc5dc Add the beginnings of a peers.exchange test and begin to fix fallout.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 7m3s
2024-08-15 12:48:24 -04:00
347c706d6f ci: undefined reference to arc4random_buf
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 7m11s
2024-08-15 12:12:58 -04:00
5f5e6616c7 Install graphviz for building docs.
Some checks failed
Build Tilde Friends / Build-All (push) Failing after 6m1s
2024-08-14 21:16:31 -04:00
657bcadc7e Work-in-progress, untested, naive peer exchange. Intended to be disabled by default by a setting.
Some checks failed
Build Tilde Friends / Build-All (push) Failing after 5m56s
2024-08-14 21:07:16 -04:00
107666cc60 Add a setting to toggle whether replication is allowed, to be able to make a pure room, or even less, node. 2024-08-14 20:02:46 -04:00
b37669184a doxygen -u # 1.9.8 2024-08-14 20:01:21 -04:00
163a01f224 sqlite 3.46.1. 2024-08-14 19:43:57 -04:00
3d58094199 Fix some sanitizer issues, and disable LTO in debug builds to save some iteration time. 2024-08-14 19:40:20 -04:00
463951a4f1 Track/show the origin of each broadcast (discovery/room/peer exchange). 2024-08-14 19:23:01 -04:00
34804d5162 Fix android crashing in c-ares and a makefile typo.
Some checks are pending
Build Tilde Friends / Build-All (push) Waiting to run
2024-08-14 18:55:34 -04:00
3895c33915 Implement prompt() for android. #72
Some checks are pending
Build Tilde Friends / Build-All (push) Waiting to run
2024-08-14 12:45:22 -04:00
17f4eb1a56 Make it easier to copy ids from the profile view.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2024-08-11 16:26:24 -04:00
0abdffdea6 Fix OpenBSD.
Some checks are pending
Build Tilde Friends / Build-All (push) Waiting to run
2024-08-11 11:17:49 -04:00
d32999f178 Decouple DNS-based seed discovery from the broadcast timer.
Some checks are pending
Build Tilde Friends / Build-All (push) Waiting to run
2024-08-08 18:50:54 -04:00
f621feb843 Fix some builds and make the windows build actually succeed at resolving what I want.
Some checks failed
Build Tilde Friends / Build-All (push) Failing after 4m13s
2024-08-07 22:25:38 -04:00
8d277f029d Support using a seeds host for bootstrapping connections. 2024-08-07 21:03:39 -04:00
1788a02338 Add c-ares. These are the hoops I have to jump through to be able to provide some bootstrap nodes. 2024-08-07 20:21:39 -04:00
ba0800d16c Lit 3.2.0. 2024-08-06 12:19:10 -04:00
4008c7d8f6 Latest CodeMirror. 2024-08-06 12:18:54 -04:00
610a2e2afc Latest libbacktrace. 2024-08-06 12:18:32 -04:00
6f3715d1eb Latest libsodium stable. 2024-08-06 12:18:21 -04:00
b78ecaa814 F-Droid looks all set for now. Let's start 0.0.22. 2024-08-06 12:17:26 -04:00
e6f5399d53 Clear out timestamp and file modes on classes.dex, too.
Some checks failed
Build Tilde Friends / Build-All (push) Failing after 5m21s
2024-08-05 12:43:22 -04:00
0e5806cadd Re-add classes.dex to the F-Droid APK. (!)
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 6m55s
2024-08-05 12:26:10 -04:00
68c9d4afa7 Found some docs that say the icon.png max size is 512x512.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 5m23s
2024-08-04 21:54:42 -04:00
f0ea38fe49 Just set SOURCE_DATE_EPOCH=1. Using the last commit time is complicated (have to rebuild OpenSSL every commit/release). This only affects a debug string that we don't expose.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 6m51s
2024-08-04 12:54:02 -04:00
b0332f923e Debugging a SOURCE_DATE_EPOCH thing.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 6m58s
2024-08-04 12:16:46 -04:00
8a76c25394 Silence some OpenSSL build output so I can see what else is going on. Also install the signed fdroid APK in dist.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2024-08-04 12:10:52 -04:00
fd96126e3e Ooh, can I just exclude OpenSSL submodules? I don't want to see those.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 6m51s
2024-08-04 11:49:04 -04:00
ff3fbedc18 Fix inconsistent file modes in zip.
Some checks failed
Build Tilde Friends / Build-All (push) Failing after 4m4s
2024-08-04 11:25:06 -04:00
8791419f8e Sort better, and actually use ndk r26d.
Some checks failed
Build Tilde Friends / Build-All (push) Failing after 5m8s
2024-08-04 10:21:04 -04:00
5447b247a0 Back to r26d, and pin the timezome to get SOURCE_DATE_EPOCH to work correctly.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 7m16s
2024-08-04 09:54:33 -04:00
aabbb10564 for fdroid: Use android ndk r27, set SOURCE_DATE_EPOCH for the android ssl build, and remove a non-determinism in AndroidManifest.xml.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 6m50s
2024-08-04 09:36:46 -04:00
3ccd6c9a3e I missed.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 7m3s
2024-08-02 22:26:01 -04:00
c290240de7 Make a release to make sure F-Droid can pick it up.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2024-08-02 22:20:18 -04:00
8e799b174b Address some fdroid zip non-determinism.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 5m15s
2024-08-02 21:55:00 -04:00
a9c3a93989 Add some images for F-Droid.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 7m1s
2024-08-02 20:37:27 -04:00
3ef8698f42 Put android:versionCode and such back in the static AndroidManifest.xml. I forgot that F-Droid needs to see it. 2024-08-02 20:37:05 -04:00
fa4e843c30 Update default.nix. Did I do it right finally?
Some checks failed
Build Tilde Friends / Build-All (push) Failing after 5m34s
2024-07-31 20:14:40 -04:00
9a4d11f4d9 Attempt to shrink OpenSSL on android again, ineffectively.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 7m14s
2024-07-31 19:58:41 -04:00
eed2b8d618 Latest CodeMirror. 2024-07-31 19:49:52 -04:00
13f02c2aca Preparing to release 0.0.21.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 5m33s
2024-07-31 12:50:35 -04:00
d50f8fbc8b ios: ssl fix.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 5m15s
2024-07-27 21:31:31 -04:00
155238a516 build: I mean -flto=auto.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 5m19s
2024-07-27 11:08:28 -04:00
427fcdbdca build: -flto all the things.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 6m34s
2024-07-25 16:02:14 -04:00
ca05d402a7 An exercise in stripping down the win32 .exe size.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 7m12s
2024-07-24 15:25:36 -04:00
c5a80b68ca Fixed more aab build issues. 2024-07-24 14:03:21 -04:00
c1fb15b135 ci tweaks and aab fixes.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 6m24s
2024-07-24 13:50:48 -04:00
4b2c131836 ci: Install doxygen for docs.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 7m30s
2024-07-24 13:04:52 -04:00
9ca1e69b3c Let's try to build in docker, too.
Some checks failed
Build Tilde Friends / Build-All (push) Failing after 6m48s
2024-07-24 12:56:27 -04:00
082d041d44 Update the android app icon / launch icon.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2024-07-24 12:50:31 -04:00
221f276c4b Simplify stats sending.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 6m16s
2024-07-24 12:15:05 -04:00
24cec21465 Move last remnant of static file handling from core to C. 2024-07-24 12:06:24 -04:00
9f71ec6194 Minor android cleanup.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 6m12s
2024-07-24 11:27:37 -04:00
bb36afc390 Use android ndk r27 (LTS) if available. 2024-07-24 11:20:35 -04:00
b53bf0ff64 Disallow rich text in the ssb compose box.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 6m13s
2024-07-22 14:42:37 -04:00
3ebc6f2436 Prettier.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 6m14s
2024-07-22 14:19:12 -04:00
2eef6778a6 Latest CodeMirror.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 6m20s
2024-07-20 17:13:43 -04:00
81fabec810 Latest libbacktrace. 2024-07-20 17:12:43 -04:00
dc6e7924b5 Is this how I install dependencies on the gitea runner?
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 6m14s
2024-07-18 12:42:08 -04:00
48dec5a2c8 More submodules.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 2m22s
2024-07-16 22:32:26 -04:00
9b500e1da9 Now actually build something.
Some checks failed
Build Tilde Friends / Build-All (push) Failing after 7s
2024-07-16 22:30:36 -04:00
a038820112 Add the demo gitea action.
All checks were successful
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 47s
2024-07-16 22:20:45 -04:00
70a15973b6 An fdroid build config that worked locally, for me. 2024-07-16 21:45:29 -04:00
09b6a00731 Fix android build with not enough -j. 2024-07-16 20:22:07 -04:00
883c3cf0e9 Clean up this core file. 2024-07-16 19:01:20 -04:00
a46bb8183c Fix OpenBSD compile. 2024-07-14 16:59:23 -04:00
d5d5a7b012 Build a separate .apk for fdroid with its own app ID. 2024-07-14 16:18:47 -04:00
a120efdc91 May as well dist the .aab. 2024-07-10 20:52:40 -04:00
d48f4b06eb Another f-droid directory. 2024-07-10 20:42:50 -04:00
f078912736 Add some recommended fdroid metadata. 2024-07-10 19:45:04 -04:00
63b0f0dedd Fix fdroid build with OpenSSL in-tree. 2024-07-10 19:35:49 -04:00
84c22dbf5f Move to OpenSSL as a git submodule. Redundant for platforms where it's not used, but makes fdroid easier. 2024-07-10 19:25:01 -04:00
b8cd1232be Have a little category, as a treat. 2024-07-09 19:30:33 -04:00
a518ab07f4 Add a WIP .fdroid.yml that seems to actually build something. 2024-07-09 19:28:45 -04:00
9e5a1ee975 Ugg. 2024-07-09 19:21:27 -04:00
95bf3f0316 This is almost doing something. 2024-07-09 19:19:01 -04:00
d69dd513bc Another silly fdroid test. 2024-07-09 19:08:40 -04:00
525cdf571a Testing a thing for fdroid. 2024-07-07 17:14:13 -04:00
9cfe0a8804 Add a 'JavaScript disabled' message. #56 2024-07-04 14:35:53 -04:00
50b54599ef Minor cleanup. 2024-07-04 13:18:23 -04:00
ed6bef6d24 Get android running its sandbox in a seprate, isolated service process. So that we support not extracting the native code from the APK, so that we support distributing as an .aab file, so that we may one day release on the app store. 2024-07-04 13:02:39 -04:00
71268636df Steps toward following all the inconvenient, changing android rules:
* Set android:debuggable=false.
 * Call native code through JNI only.  Having a native executable on disk and exec-ing it no longer seems possible.
 * Do all the Tilde Friends things in one process, without a proper sandbox, until I can wire up a restricted service worker process.
 * Jam Android App Bundle (.aab) building into the makefile.
 * Yuck.
2024-06-30 13:32:17 -04:00
568729ecd6 Stop auto-updating the version in default.nix. Will do it manually only on release. 2024-06-29 08:33:51 -04:00
9139725be6 Merge pull request 'build: fix the nix derivation' (#69) from tasiaiso/tildefriends:tasiaiso-0-0-20 into main
Reviewed-on: #69
2024-06-29 08:33:48 -04:00
969a8da6bf
build: update nix package to 0.0.20 2024-06-28 10:37:26 +02:00
2338b26329 Start working on 0.0.21. 2024-06-26 20:47:44 -04:00
d4df206740 Oh, I think I see how to nix this now. 2024-06-26 20:36:54 -04:00
8a93cdd33c Let's release 0.0.20. 2024-06-26 20:29:07 -04:00
92b31de4a9 Latest libbacktrace. 2024-06-26 20:20:41 -04:00
5452f3f623 Appease -fsanitize. 2024-06-26 20:20:34 -04:00
256614dbaf Actually stop stomping settings. 2024-06-26 19:58:59 -04:00
049449b213 I think this is how I lost settings. 2024-06-26 19:44:45 -04:00
85b46336b1 Better draft cleanup on submit. 2024-06-26 19:30:58 -04:00
590afa7b01 Fix content warnings. 2024-06-26 19:27:15 -04:00
574292b798 Reduce some common log noise. 2024-06-23 15:11:18 -04:00
21cf503a59 Fix a navigation bar option I neglected to button-ify. 2024-06-23 11:47:12 -04:00
3630cdbfe0 Consolidate the acount/login navigation bar options to try to save some space on mobile. 2024-06-20 20:41:27 -04:00
0f3be229e6 Actually, let's minify this thing using svgomg. 2024-06-20 20:07:58 -04:00
8e5a024d3d SVG favicon. 2024-06-20 20:05:00 -04:00
410bb7c09d Fix a ref count mistake and add a long-overdue tf_util_print_backtrace() that helped me find it. 2024-06-20 19:49:21 -04:00
9de8b0f449 Oops. 2024-06-20 12:36:21 -04:00
d47c3a1222 Fix a ref/unref mismatch. 2024-06-17 21:45:51 -04:00
df99b3aa90 Trying to catch an issue I think I saw in the debugger. 2024-06-17 21:23:48 -04:00
0090850e10 Forgot the other end of blobs.get. 2024-06-17 20:59:25 -04:00
9efd64bd18 Actually enforce _tf_ssb_assert_not_main_thread. 2024-06-17 12:36:54 -04:00
b16c37e48b Make ssb.privateMessageDecrypt do its work not on the main thread. I think that's finally everything for real. 2024-06-16 17:22:26 -04:00
3ee2c00726 Build fix. 2024-06-16 17:08:10 -04:00
d5a7e19f1a Move the bulk of ssb.privateMessageEncrypt work (CPU + DB) off the main thread. 2024-06-16 17:07:12 -04:00
9b52415b35 Make ssb.setServerFollowingMe not use the DB from the main thread. Two left?? 2024-06-16 16:22:59 -04:00
dbe24494d9 Remove ssb.messageContentGet. It's easy to do this with ssb.sqlAsync, and this wasn't being used productively. Three uses of DB on the main thread remaining. 2024-06-16 16:02:39 -04:00
3eab5a5f70 Make ssb.forgetStoredConnection not use the DB on the main thread. Four remaining? 2024-06-16 15:57:19 -04:00
548febfb22 Make ssb.storedConnections do its DB work not on the main thread. Five remaining by my new count? 2024-06-16 15:29:59 -04:00
b40f72443a A little format, as a treat. 2024-06-16 12:18:19 -04:00
2c03496373 Make databases.list, database.remove, and database.getLike all do their DB work off the main thread. That's the last thing I'm aware of. 2024-06-16 12:17:51 -04:00
b6a937c954 Move db.exchange DB work off of the main thread. 2024-06-16 10:16:39 -04:00
63776d40bd Update codemirror. 2024-06-16 09:23:14 -04:00
cb3c7afade Move ssb.getPrivateKey's DB work off the main thread. 2024-06-16 08:07:02 -04:00
991022adfc Move ssb.appendMessageWithIdentity's DB work off the main thread. 2024-06-16 07:51:06 -04:00
2bc71a18a6 Make ssb.deleteIdentity not block the main thread with DB work. 2024-06-14 17:39:24 -04:00
57ca864fbb Build fix. 2024-06-12 21:08:41 -04:00
a09edfb612 ssb.addIdentity without hitting the DB from the main thread. 2024-06-12 21:06:30 -04:00
7997a739ab ssb.createIdentity without hitting the database from the main thread. 2024-06-12 20:47:48 -04:00
248b258413 Make database.getAll() not block the main thread on database access. 2024-06-12 20:29:39 -04:00
0423ed7fb4 Login without hitting the DB from the main thread. 2024-06-12 20:12:35 -04:00
c29378c2f8 Yes, curl, follow redirects. 2024-06-10 21:19:06 -04:00
163fbd85e7 Fix docs. 2024-06-10 20:23:11 -04:00
58bb86ebe1 Make http.auth_query async and get its DB work off the main thread. 2024-06-10 20:22:28 -04:00
c5140ee8e8 Move DB work for ssb.getIdentities() and ssb.getAllIdentities() off the main thread. 2024-06-10 17:18:29 -04:00
6270fd8118 We don't need to go to the DB to get our public key. 2024-06-10 16:56:21 -04:00
3fff706848 Get the code of conduct and JWT signing key without hitting the database from the main thread. 2024-06-10 16:37:12 -04:00
c259defab5 Move database.get and database.set off the main thread. 2024-06-10 15:30:14 -04:00
e5fee5c306 Buildfix. 2024-06-10 12:01:49 -04:00
9d35b4bdfb Resuming work to move all DB access off the main thread. 2024-06-10 11:45:20 -04:00
9497d7cf64 Fix some shutdown hangs/leaks. 2024-06-06 20:31:24 -04:00
c7d3e602cb Fix &-mentions while I'm at it. 2024-06-06 20:14:00 -04:00
0076eb4ed4 Fix autocomplete again/more. #65 2024-06-06 20:05:24 -04:00
6070bde413 Avoid a null dereference. 2024-06-06 19:57:36 -04:00
c7a6d426f0 Fix autocomplete on Chrome, because contenteditable and shadowRoots are tricksy, and this module for @mentions is aging. #65 2024-06-06 19:52:37 -04:00
f66cf0f802 Unused. 2024-06-06 19:11:48 -04:00
e4b6c81024 No need to show your identity in the navigation bar if you have a name. 2024-06-06 18:51:40 -04:00
44d784cd04 OpenSSL 3.3.1. 2024-06-05 12:51:26 -04:00
0394201113 Merge pull request 'buld(nix): Misc Nix-related improvements' (#68) from tasiaiso/tildefriends:tasiaiso-nix-misc into main
Reviewed-on: #68
2024-06-04 20:16:15 -04:00
e270c16516 lit 3.1.4. 2024-06-04 20:10:27 -04:00
4c10538632
buld(nix): Misc Nix-related improvements
- Nixpkgs 23.11 is deprecated, use 24.05 instead
- update flake.lock
- add glibc as a build dependency
- add doxygen and graphviz as development dependencies for `make format`
2024-06-04 15:22:18 +02:00
71329c5532 format+prettier 2024-06-03 12:36:34 -04:00
feb4bf9e87 Limit message sends in a continued attempt to fix intermittent runaway memory usage. #64 2024-06-02 12:38:12 -04:00
5d5567e94c Reworking the emoji picker to use w3-modal, in a step toward doing the same for the currently broken @autocomplete. 2024-05-30 12:40:21 -04:00
684e6fb9cb Merge pull request 'nix: update version to 0.0.19' (#66) from tasiaiso/tildefriends:tasiaiso-nix-update into main
Reviewed-on: #66
2024-05-30 12:12:45 -04:00
ee21fa6d03
nix: update version to 0.0.19 2024-05-30 11:34:57 +02:00
7a2974e54f Working on 0.0.20. 2024-05-29 20:17:33 -04:00
f4dfc1dd98 Let's release 0.0.19. 2024-05-29 19:50:59 -04:00
2eebfa9a7a Make the websocket disconnect message not pop up a modal dialog so that it's less annoying when it happens in the normal course of events. #60 2024-05-27 20:35:40 -04:00
10097ffeb8 Update codemirror. 2024-05-27 08:23:45 -04:00
cbe1f54a2a libsodium 1.0.20. 2024-05-27 08:21:48 -04:00
4d8f081a59 Update libbacktrace to latest. 2024-05-27 08:21:10 -04:00
29e79c9484 Fix collapsing images taking extra clicks. 2024-05-25 08:09:44 -04:00
ba35869b0a sqlite 3.46.0. 2024-05-25 07:46:15 -04:00
580688381e prettier 2024-05-22 20:52:10 -04:00
e63d69a440 Missing generated semicolon. Sigh. 2024-05-22 20:44:28 -04:00
be64fe04fb Auto-update all the versions. 2024-05-22 20:35:48 -04:00
801ab20723 Merge pull request 'Add Nix support' (#62) from tasiaiso/tildefriends:tasiaiso-nix into main
Reviewed-on: #62
2024-05-22 20:31:11 -04:00
d974a5e044 An experiment in controlling memory usage when syncing. uv_read_stop when we have too active message/blob writes to the database and uv_read_start when we're back under control. #64 2024-05-22 19:53:33 -04:00
1be94ae0be Removed ssb.addEventListener and ssb.removeEventListener from the public API. Can do the same thing with core.register. 2024-05-22 18:51:21 -04:00
b883e6a485 Fix username/id extending off the screen in the welcome line. 2024-05-22 12:33:18 -04:00
a0210379ae Avoid confusing log output when responding with a method not found error. 2024-05-20 12:39:21 -04:00
e56dc207d1 Fix some shutdown issues in connection tracker code. 2024-05-16 12:41:48 -04:00
523c9c9ad2 Move mime type shenanigans from JS => C. 2024-05-15 19:25:48 -04:00
74bb2151c1 Fix shutdown issues with in-flight SSB connection attempts. 2024-05-15 12:37:13 -04:00
f79d7b35a4 Disallow creating accounts as a guest. #52 2024-05-14 12:41:17 -04:00
3b36496dac
chore: a bit more doc 2024-05-12 21:17:38 +02:00
4ebd6c24a9
chore: missing period in description 2024-05-12 21:15:30 +02:00
05451d98b3
Merge branch 'tasiaiso-nix' of https://dev.tildefriends.net/tasiaiso/tildefriends into tasiaiso-nix 2024-05-12 21:13:43 +02:00
22a4bce3c8
docs(nix): add documentation in default.nix 2024-05-12 21:13:22 +02:00
76d499f00b Merge branch 'main' into tasiaiso-nix 2024-05-12 14:56:12 -04:00
f0772f9b99
build(nix): add Nix support 2024-05-12 20:34:03 +02:00
46e711f0a5 Merge branch 'main' of https://dev.tildefriends.net/cory/tildefriends 2024-05-12 10:40:14 -04:00
abffac3f82 Show missing profile images more deliberately. 2024-05-12 10:40:06 -04:00
27b275548e Fix docs. 2024-05-12 08:37:14 -04:00
93ce253d1e prettier 2024-05-12 08:23:34 -04:00
a5af312b39 Merge branch 'main' of https://dev.tildefriends.net/cory/tildefriends 2024-05-12 08:23:23 -04:00
4b5e8e8a43 Consolidate similar request tags in the connection list. #59 2024-05-12 08:21:47 -04:00
443dd4d168 Merge pull request 'chore: code formatting' (#58) from tasiaiso/tildefriends:tasiaiso-format into main
Reviewed-on: #58
2024-05-12 08:05:02 -04:00
907479df84 Merge branch 'main' into tasiaiso-format 2024-05-12 07:52:33 -04:00
9887a78e98 prettier 2024-05-12 07:48:34 -04:00
f669371349 Show tab names on large enough screens. Inspired by tasio's #61. 2024-05-12 06:58:01 -04:00
24c720c79a Merge branch 'main' into tasiaiso-format 2024-05-12 02:06:09 -04:00
4485234980
chore(style): tell prettier to ignore code block 2024-05-12 08:01:37 +02:00
b6871c0b1f
chore: code formatting 2024-05-11 23:44:09 +02:00
47838d5e48 More name info issues. 2024-05-11 10:53:21 -04:00
69fccd56d3 Add a little guidance about how to set your name. It's a common confusion. 2024-05-11 10:40:34 -04:00
ca00c4fb5d Fix multiple issues getting identity info. 2024-05-11 10:23:07 -04:00
427ca3f265 Indicate both the server account and your own accounts in the ssb connections tab. 2024-05-11 09:58:24 -04:00
c1a80e50e7 Merge branch 'main' of https://dev.tildefriends.net/cory/tildefriends 2024-05-11 09:50:06 -04:00
52962f3a5e Remove the :auth key. We can sign JWTs with :admin, and it's one less magic key. 2024-05-11 09:50:00 -04:00
b3f095b61f Merge branch 'main' of https://dev.tildefriends.net/cory/tildefriends 2024-05-11 09:33:48 -04:00
a5004c8ba9 Indicate the local server identity. 2024-05-11 09:33:38 -04:00
7d9b1b508b Print a little colorful message when we've started about where to connect. Multiple people have pointed out that it's not obvious that it's working. 2024-05-11 09:18:30 -04:00
5e265dfc83 Make sure the first user can admin. 2024-05-11 09:03:56 -04:00
3a43d6f8ac Build fix. 2024-05-11 09:03:37 -04:00
11a6649847 Add back a verify command. Remove unused and not very useful ssb.getMessage(). Make field ordering shenanigans more explicit. 2024-05-11 08:48:50 -04:00
7caf4a0173 Fix numerous issues around setting the first registered used as an admin. 2024-05-10 22:21:59 -04:00
385524352c Refactor most uses of uv_queue_work to go through a helper that keeps track of thread business, traces, and is generally less code. 2024-05-08 21:00:37 -04:00
5ca5323782 Fix /speedscope/ => deps/speedscope/index.html. 2024-05-08 20:57:53 -04:00
ba6da856bb Let trace truncate names more if it means we can generate valid JSON. 2024-05-08 20:56:44 -04:00
c0e72246cc Trying to understand a lingering 'previous message doesn't exist.' And format. 2024-05-08 12:20:57 -04:00
c7ab5447ea Move / redirect handling to C 2024-05-05 15:24:15 -04:00
5fdd461159 Fix setting multiline admin settings. 2024-05-05 15:19:00 -04:00
421955f2a0 getIdentityInfo => C. 2024-05-05 13:48:22 -04:00
a28f6985ed getActiveIdentity => C. 2024-05-05 12:55:32 -04:00
8244dddab7 Latest libbacktrace. 2024-05-05 12:03:57 -04:00
a5ca436eaa Merge branch 'main' of https://dev.tildefriends.net/cory/tildefriends 2024-05-02 20:35:24 -04:00
d7fc1c2c88 Minor style/layout changes. 2024-05-02 20:35:00 -04:00
382627ef8d Latest codemirror. 2024-05-02 20:11:54 -04:00
17667b4cf8 make format 2024-05-02 20:10:56 -04:00
5231ec22e7 More trying to clean up lingering requests. 2024-05-02 19:59:54 -04:00
929ae1b709 After eyeballing lingering requests, clean up requests after the response to an async (non-streaming) request is done. 2024-05-02 19:37:38 -04:00
f01f7a5ab9 Show active RPC requests in the connections tab. Probably TMI, but I want greater introspection into what is going on, and this seemed like a positive step. 2024-05-02 19:02:23 -04:00
a2dce833f8 Fix another shutdown issue. 2024-05-02 12:30:22 -04:00
de6c7a4fd4 SSB app stylin'. 2024-05-01 12:34:36 -04:00
4edee0f7f6 Allow importing from a single app .json. 2024-04-30 21:43:14 -04:00
988a807fa4 Admin app styles. 2024-04-30 19:18:33 -04:00
5258e4253d Better identity app layout on mobile. 2024-04-29 12:24:35 -04:00
09ba86dec5 Show replies to gatherings. 2024-04-28 12:55:17 -04:00
78d8a1aa23 Set the core room app icon. 2024-04-28 12:33:19 -04:00
22def15209 Merge branch 'main' of https://dev.tildefriends.net/cory/tildefriends 2024-04-28 12:25:31 -04:00
4cbda7a849 Improve file errors so that it doesn't look like everything has failed when we see there's no https cert available. 2024-04-28 12:25:12 -04:00
be85a620ef Fix app delete. 2024-04-28 12:11:13 -04:00
0b07b678b4 Theme the identity app a bit. 2024-04-28 11:57:35 -04:00
4733ce9287 Fix ssb draft discard. 2024-04-28 11:23:28 -04:00
48d6bf4c15 Hook up onJsAlert on android. 2024-04-28 11:04:29 -04:00
8c759bcbac Hide the reactions button if there aren't any. 2024-04-26 18:19:13 -04:00
b5ed7014f6 Fix attaching files (aka WebView file picking) on Android. 2024-04-26 18:10:22 -04:00
6cd9dea186 Merge v0.0.18 compose fixes. 2024-04-24 20:35:36 -04:00
202b416acf More ssb compose fixes. 2024-04-24 20:32:09 -04:00
93d46f5610 Fix some ssb compose issues. 2024-04-24 20:20:18 -04:00
c5ddf3ac99 Fix some ssb compose issues. 2024-04-24 20:19:14 -04:00
a9cb913a47 Working on 0.0.19. 2024-04-24 19:29:17 -04:00
b7b5d4f1a5 Calling it 0.0.18. 2024-04-24 19:24:10 -04:00
a947396bad Prettier. 2024-04-24 19:23:13 -04:00
d528bc808e Add retries around some 'test -t auto' intermittent failures. 2024-04-22 12:37:06 -04:00
c6fd05c2cf ssb tf-compose fixes. 2024-04-21 18:53:47 -04:00
d6bb9d311a An experiment in improving ssb text entry. 2024-04-21 18:24:57 -04:00
53b4cbbf8c CSS, ugh. 2024-04-21 15:24:23 -04:00
628716ec28 Add a thing to inspect reactions. #48 2024-04-21 14:18:06 -04:00
bd14168627 Don't pop up the error modal for connecting/status messages. 2024-04-18 12:46:06 -04:00
96037d4da6 Android pull refresh fixes. Sigh. 2024-04-17 22:37:24 -04:00
5448e773d8 Rejiggled error display. 2024-04-17 20:56:33 -04:00
848ef21c7c Build a windows standalone executable with attached data for dist. #28 2024-04-17 20:16:07 -04:00
2ecae7da93 Implement my own hokey pull to refresh on Android. Nobody's got time for all those dependencies. 2024-04-17 19:55:14 -04:00
d9ce569eb9 Add a thing to encourage editing your profile. 2024-04-17 17:21:44 +01:00
eacaf392b1 Lit 3.1.3. 2024-04-16 12:31:53 -04:00
ce16592b6a Update codemirror. 2024-04-16 12:30:27 -04:00
295d76d354 sqlite 3.45.3. 2024-04-16 12:16:36 -04:00
23b3c998bd Re-CSS'd the identity dropdown. 2024-04-14 17:47:47 -04:00
b5e966c9a1 Make the wiki app use the global id picker. 2024-04-14 13:53:51 +01:00
96cb6f4b12 Make the issues app use the global id picker, fix jsonb issues, and fix the global id picker. 2024-04-14 13:47:28 +01:00
e2c0f82ec0 Size the identity picker a tad better. 2024-04-14 13:10:32 +01:00
dbf28c03e6 Let's get account names to the UI. 2024-04-13 21:51:18 -04:00
26165e30de Fix -t auto. 2024-04-13 20:32:17 -04:00
c52331a23a format/prettier 2024-04-13 20:07:39 -04:00
8007e71e1d Make the ssb app use the global identity picker. 2024-04-13 19:52:40 -04:00
28d08e013f Trying to make the ssb connections tab not overflow all the layouts. Dunno. 2024-04-13 19:29:31 -04:00
64bbd383de Trying to make the navigation bar fit again with a new dropdown. Good grief, CSS. 2024-04-13 16:52:30 -04:00
8a9f53102b Needs some color. 2024-04-13 13:33:57 -04:00
0412b97170 WIP managing a per-app current identity from the Tilde Friends navigation bar. 2024-04-13 13:22:59 -04:00
c8b8a8fc03 Oops, the tf-auth lit was all wrong. 2024-04-13 10:28:35 -04:00
95d3090b9b Fix the app permissions list missing its reset buttons. 2024-04-13 10:03:06 -04:00
49129ee6dd Welcome changes. Added a first steps blurb. 2024-04-12 02:32:21 +01:00
6a7ecb0d4a Update codemirror to latest. 2024-04-11 20:36:14 -04:00
1ceeed1007 prettier + clang-format. 2024-04-11 18:36:31 -04:00
a7922ff44e Get commonmark blockquotes on-theme. 2024-04-11 01:30:49 +01:00
a421604ed5 OpenSSL 3.3.0. 2024-04-10 19:44:49 -04:00
7d182db32f Move to submodules: libsodium, quickjs, crypt_blowfish, libbacktrace, libuv, picohttpparser. Kudos to @tasioiso in #45. 2024-04-10 19:25:18 -04:00
c5cb9979d3 Move zlib to a submodule, informed by @tasiaiso's #45. And fixup [archive] dist/tildefriends-0.0.18-wip.tar.xz
[cp] TildeFriends-x86-0.0.18-wip.apk
[cp] TildeFriends-arm-0.0.18-wip.apk
[cp] TildeFriends-0.0.18-wip.ipa to support it.
2024-04-10 19:09:31 -04:00
b9a73106ed Better CSS for the ssb app to fill the iframe? 2024-04-10 18:43:48 -04:00
c674cca482 Move some DB things out of httpd. 2024-04-04 21:00:59 -04:00
81d1228b92 Experimenting with w3.css themes. 2024-04-04 20:35:09 -04:00
6ae61d5b81 Fix wiki vs. JSONB. 2024-04-05 00:11:55 +01:00
9cb872eec2 Remove JS functions: hmacsha256sign, hmac2ha256verify, parseHttpRequest, sha1Digest, and maskBytes. These are no longer needed with httpd and auth in C 2024-04-03 21:14:52 -04:00
68e8c010b7 Show recently used emojis in the emoji picker. #16 2024-04-04 02:12:43 +01:00
9671413906 Make it easier to @mention the person to whom you are replying. 2024-04-04 00:50:59 +01:00
4c8d24c319 Consolidate markdown linkification, and add support for authors, blobs, and messages. 2024-04-04 00:18:39 +01:00
e50144bd34 Validate exit codes more thoroughly. C'mon, Cory. 2024-04-02 20:32:47 -04:00
9f3171e3f1 Remove auth.js. #7 2024-04-02 20:11:36 -04:00
cc92748747 Move sending refresh tokens out of JS. 2024-04-02 12:42:31 -04:00
0a0b0c1adb Make sure we don't leak the session string when reassigning it. 2024-04-02 12:20:59 -04:00
92a74026a6 Format the new auth code. 2024-04-01 12:53:47 -04:00
3fa1c6c420 Tidied up getting an auth key slightly. 2024-04-01 12:53:00 -04:00
b04eccdbda Move the auth handler out of JS. #7 2024-03-31 16:15:50 -04:00
9ce30dee70 Start working on 0.0.18. 2024-03-27 19:08:10 -04:00
3c0b680b8e Let's release 0.0.17. 2024-03-27 18:59:40 -04:00
895356897b archive whichever branch. 2024-03-25 18:10:07 -04:00
9164be2f37 Fix loading from not standalone zip. 2024-03-25 16:34:27 -04:00
5385264f94 Fix an http use after free during shutdown. 2024-03-25 16:31:09 -04:00
610e756c07 Ever closer to the elusive clean http shutdown. 2024-03-25 16:23:45 -04:00
15c9f8f458 Rudimentary support for building the executable with data attached. Pushed some things around in the makefile to fix issues along the way. #46 2024-03-25 13:50:17 -04:00
fb704a5b83 I will get better at keeping this tree clean. 2024-03-25 12:56:33 -04:00
fdda628be8 Fix paths in the source tarball. 2024-03-20 20:43:41 -04:00
2b45d8aa05 Doh. Never mean to add that. 2024-03-20 20:37:52 -04:00
0e2fc65301 Document run -k flag. 2024-03-20 20:33:23 -04:00
e8ef7e74de Fixed a leak in JS blob store. 2024-03-18 12:46:12 -04:00
c32e1b9583 http request cleanup crash fix. 2024-03-18 16:34:07 +00:00
9d0f6ec155 Fix the sneaker app RE: JSONB. 2024-03-18 12:32:40 -04:00
855d603795 docs + prettier 2024-03-17 13:21:33 -04:00
af25782185 More http/request shutdown issues. 2024-03-17 12:38:37 -04:00
e5ba51b80a Chasing a leak that looks like an EBT clock. Deleted some unneeded code and adding a missing JS free. 2024-03-17 13:44:05 +00:00
5e240de677 Fix requesting blobs from blob_wants. ids were trucated. Yikes. 2024-03-17 09:16:06 -04:00
418cfac0e3 Add a stock app with local room connection info. #15 2024-03-14 00:43:11 +00:00
9d09607013 Update CodeMirror. 2024-03-13 20:23:48 -04:00
eddf25b622 Give libuv the same download treatment as sqlite. 2024-03-13 19:53:57 -04:00
537a8654fa Rename sequence_before_author => flags. #29 2024-03-13 19:40:09 -04:00
9de33d06d2 Specifying -fsanitize=... early seems good. 2024-03-13 18:26:24 -04:00
0e5f320664 sqlite 3.45.2. 2024-03-13 12:30:14 -04:00
88d8e60511 Some minor paranoia to appease valgrind. 2024-03-12 21:44:20 -04:00
439f07162e Disable Haiku automation tests until I find a way to automate a browser on Haiku. 2024-03-09 08:44:06 -05:00
efe2b6cbd9 A make target to run prettier. 2024-03-08 21:43:08 -05:00
0aa1ed9464 Fix a failure requesting more blobs. 2024-03-08 21:38:31 -05:00
cb94ed6a2a Some plumbing to expose the actual bound SHS port so that I can make a dynamic room app. 2024-03-07 21:03:14 -05:00
cf187ee46b Reorder things so that we only zipalign -z during a dist build. To slow for make all. 2024-03-07 20:42:08 -05:00
3e71fc20fd Prettier. 2024-03-06 21:14:09 -05:00
f3601321f7 That's all the doxygen warnings. #27 2024-03-06 21:13:16 -05:00
540059368c 11 make docs warnings left, but I'm out of time for tonight. 2024-03-06 20:57:38 -05:00
7ce89123f7 85 make docs warnings remain. 2024-03-06 12:46:27 -05:00
e3c7c86212 All but the two biggest .h files have docs. 2024-03-06 12:31:17 -05:00
794804e27f A few more .h file docs. 2024-03-05 21:17:20 -05:00
6d89c1da6e Format. 2024-03-05 20:49:30 -05:00
d059554464 Some workarounds for Haiku. uv_fs_scandir can't tell if a dirent is a file. setrlimit doesn't do anything productive for us. 2024-03-05 20:49:16 -05:00
3a392d4a9f More .h docs. 2024-03-05 12:47:58 -05:00
e3071b372a Poking at TCP binds from Haiku. 2024-03-04 21:51:27 -05:00
18bd279b0c Some progress on .h docs, and add a preliminary CONTRIBUTING.md. 2024-03-04 12:23:00 -05:00
5b93db7463 A buncha muncha cruncha .h docs. Also add vim temporary files to .gitignore. 2024-03-03 18:12:44 -05:00
5b7e5eb91b Give fts a better chance of working with jsonb messages.content. 2024-03-03 18:55:58 +00:00
78ca383e3c http.h docs. 2024-03-03 12:35:10 -05:00
c1eed9ada3 Fixed a leak in ssb.getServerIdentity(). 2024-03-03 12:20:03 -05:00
8d6feb5394 Set the root of private messages correct so that other clients show them. 2024-03-03 12:09:03 -05:00
42994f8977 Make the SSB network key configurable by command-line argument. 2024-03-02 15:01:09 -05:00
f0a871e1f8 More docs. 2024-03-01 21:18:12 -05:00
a710c30572 Fix apps for jsonb. 2024-02-29 19:26:56 -05:00
c991763b00 tests.h and tlscontext.js.h docs. 2024-02-28 21:18:59 -05:00
72dae14f87 Android NDK update. 2024-02-28 21:04:22 -05:00
5800340762 Fix up some more jsonb references. 2024-02-28 20:41:27 -05:00
c5f5adcac6 Missed some more jsonb messages.content use issues. 2024-02-28 20:31:25 -05:00
591642efb3 Convert messages.content to JSONB. This is a very disruptive change. 2024-02-28 20:01:52 -05:00
6182ffa1d4 Docs for tls.h and trace.h. 2024-02-28 19:12:41 -05:00
402a898d96 Let's start working on 0.0.17. 2024-02-28 18:47:21 -05:00
13d43d8319 Let's release 0.0.16. 2024-02-28 18:24:12 -05:00
7bcdbd3813 Revert "Update commonmark.js to 0.31.0."
This reverts commit 165f25db69.
2024-02-26 12:34:50 -05:00
60ada22674 Add a button to toggle visible whitespace for now. Not yet persisted. 2024-02-25 22:31:31 -05:00
637119d46d ideviceinstaller makes this unnecessary. 2024-02-25 21:41:32 -05:00
40f3da6a65 Fix a leak in returning HTTP responses. 2024-02-25 19:38:00 -05:00
f4697fe7f7 Update CodeMirror. 2024-02-25 18:54:35 -05:00
3bc18b9021 Docs for util.js.h. 2024-02-25 18:52:34 -05:00
c21581aefa Use zipalign w/zopfli for APKs to save a little on size. 2024-02-25 18:29:10 -05:00
165f25db69 Update commonmark.js to 0.31.0. 2024-02-25 16:25:23 -05:00
9aa0617aa1 Fix android argv. 2024-02-25 16:02:56 -05:00
ddce88dce6 Merge branch 'tasiaiso-wiki-improvements' 2024-02-25 15:37:56 -05:00
6aa2bce2be Merge branch 'wiki-improvements' of https://dev.tildefriends.net/tasiaiso/tildefriends into tasiaiso-wiki-improvements 2024-02-25 15:37:13 -05:00
a43c1d3d1e Format. 2024-02-25 15:03:43 -05:00
1ed0e817e8 BSD compile fix. 2024-02-25 14:57:14 -05:00
709ca55e65 Fix overbuild on macos. 2024-02-25 14:52:35 -05:00
8c13f5dbba xopt => getopt_long. I give up on xopt. It didn't help me as much as I had hoped, and I had problems building for mingw with only some versions of GCC. Not worth any further time. 2024-02-25 14:45:31 -05:00
4cb82d81b7 apps/gg doesn't belong here and isn't ready for prime time.. 2024-02-24 11:19:36 -05:00
0c42921387 Appease prettier in index.html. 2024-02-24 11:16:07 -05:00
70a3e7fc7d Make app export append a trailing newline to the app.json files so that we match prettier. 2024-02-24 11:12:35 -05:00
d5267be38c Run prettier. 2024-02-24 11:09:34 -05:00
8e7e0ed490 Merge branch 'tasiaiso-prettier' 2024-02-24 11:03:36 -05:00
8cf2837725 Export app json files indented with tabs. 2024-02-24 10:58:53 -05:00
63ae186c76 Export app json files indented with tabs. 2024-02-24 10:55:09 -05:00
dbf5c7b832 Merge branch 'main' into prettier 2024-02-23 09:50:49 +00:00
bfbfc01e99 keep the new config files 2024-02-23 10:42:26 +01:00
8fa9d0e843 Revert "build: Add prettier to the project"
This reverts commit 41024ddb79.
2024-02-23 10:35:39 +01:00
2d3e108fd9 Reapply "build: Add prettier to the project"
This reverts commit 7822b30dcb.
2024-02-23 10:29:46 +01:00
7822b30dcb Revert "build: Add prettier to the project"
This reverts commit 41024ddb79.
2024-02-23 10:25:51 +01:00
2701b7d04e Address some gcc-13 analyzer warnings. #33 2024-02-22 20:13:51 -05:00
e361c3f975 chore(wiki): the button class is now optional for input elements 2024-02-22 22:34:11 +01:00
260706c172 chore: copy .prettierrc.yaml over to client.js 2024-02-22 21:31:15 +01:00
390668ec34 Merge branch 'master' into prettier 2024-02-22 21:23:39 +01:00
1d5cdf9607 feat(wiki): improvements to the wiki's UI 2024-02-22 18:39:53 +01:00
a4bf3542e0 Merge branch 'main' into wiki-improvements 2024-02-22 15:14:49 +00:00
df82cfe66b chore rename core.css to tildefriends.css, remove license from tildefriends.css 2024-02-22 16:11:49 +01:00
f23414adaf prevent previous commits from appearing in git blame 2024-02-22 15:37:40 +01:00
41024ddb79 build: Add prettier to the project 2024-02-22 15:36:45 +01:00
53f9547cc5 style(wiki): use core.js 2024-02-22 13:03:21 +01:00
4bfd9de100 Give iOS the same openssl build treatment as android and mingw. #11 2024-02-21 20:23:35 -05:00
c01e00d77d Get geckodriver.log from 'tildefriends test -t auto' out of the root. 2024-02-21 20:06:25 -05:00
825191c08f Fix 'make dist'. 2024-02-21 19:59:26 -05:00
9dc6670795 Fetch and build OpenSSL as part of the code build. 2024-02-21 19:46:28 -05:00
1db8eee9f7 Merge pull request 'Documentation, build improvements' (#1) from tasiaiso/tildefriends:dev_tasia into main
Reviewed-on: #1
2024-02-21 23:51:01 +00:00
1bc50cb62c Remove prebuilt OpenSSL from source control. #11 2024-02-21 12:31:05 -05:00
450b07fd08 Add a Doxyfile and preliminary module-level docs. 2024-02-20 21:41:37 -05:00
12c7515ee8 chore: revert changes to codemirror's config 2024-02-20 18:45:46 +01:00
ed65da4340 doc: typos 2024-02-19 19:26:15 +01:00
d9d2917cf5 chore: add folders to .gitignore 2024-02-19 19:16:05 +01:00
ce5ca1875b doc: add JSDoc annotations in the core folder
start documenting a bit, mostly inconsequential changes
2024-02-19 19:12:42 +01:00
4f869252a2 codemirror: add build script, edit build settings
Disabled `highlightWhitespace()` and `highlightTrailingWhitespace()`, could be reverted if necessary
2024-02-19 15:15:41 +01:00
17b92126de Fill in some blanks in the readme. 2024-02-18 21:31:07 -05:00
6e88c44229 Fiddle with some deps as I'm building from a clean tree. 2024-02-18 21:22:42 -05:00
6c3d338c12 How long has this extra character been in the readme? 2024-02-18 20:48:10 -05:00
4ebd44cb4e Avoid leaking some console colors.f
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4859 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-02-18 15:39:32 +00:00
75cb9f7fd2 Make things workable with a reverse proxy in front.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4858 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-02-18 15:27:44 +00:00
eacca9d2ab Ignore some files in subversion.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4857 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-02-17 20:45:22 +00:00
d0e11bc68b Add missing .clang-format, and fix some spaces that slipped through.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4856 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-02-17 19:22:02 +00:00
1958623a7a Package prettier.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4855 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-02-17 18:59:36 +00:00
498d8b6520 Hook up prettier in the editor.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4854 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-02-17 18:53:21 +00:00
a12f2fec5a Grarrgh. Build.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4853 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-02-17 16:36:40 +00:00
22bf046643 Fix fix fix tests.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4852 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-02-17 15:33:08 +00:00
dca48fae36 Some test fixes, and introduce some pledge and unveil for OpenBSD.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4851 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-02-17 14:55:39 +00:00
9af4068bb6 This apparently works around errors if I remove a header.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4850 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-02-16 03:27:41 +00:00
2992d8ec12 Fix showing decrypted messages.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4849 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-02-16 01:09:08 +00:00
33dd2560e0 Format.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4848 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-02-16 00:48:49 +00:00
aeb5c6ee25 Run the selenium automation tests from C, so that they all run in once place, and because I get better errors for some reason. Fix more issues along the way.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4847 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-02-16 00:47:54 +00:00
08a2436b8f Updates to 'apps' from Tasia.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4846 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-02-16 00:13:39 +00:00
fbc3cfeda4 clang-format the source. Not exactly how I want it, but automated is better than perfect.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4845 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-02-15 23:35:01 +00:00
c8812b1add Make the tests not do the imports all the time.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4844 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-02-15 03:00:34 +00:00
8d82e80639 Nope. JS_EVAL_FLAG_STRIP loses line numbers and other debug information. Need those.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4843 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-02-14 17:45:28 +00:00
ed741d53d7 Enable top-level async and JS_EVAL_FLAG_STRIP.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4842 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-02-14 17:39:27 +00:00
685754895b Have we achieved clean shutdown?
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4841 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-02-13 23:07:36 +00:00
e7791d38ff Continuing the fight against http lifetime issues.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4840 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-02-10 22:09:52 +00:00
9f14653001 No, yikes, Cory.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4839 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-02-10 21:58:02 +00:00
6c5a7b0751 Add missing statics, and remove the 'tildefriends check' command.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4838 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-02-10 16:50:00 +00:00
51a327c52d .well-known => C.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4837 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-02-10 16:10:58 +00:00
5a978bb30d CodeMirror update.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4836 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-02-10 15:10:33 +00:00
6801758cb3 staticDirectoryHandler => C.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4835 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-02-10 03:14:02 +00:00
14de3dd9e5 Linkify better?
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4834 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-02-09 02:05:49 +00:00
ed2d57fb4b Serve core static files without leaving C.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4833 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-02-09 01:21:57 +00:00
e87acc6286 robots.txt => C
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4832 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-02-08 01:08:05 +00:00
0de932bc9e Update CodeMirror.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4831 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-02-08 00:54:31 +00:00
d021d9f757 Automate fetching sqlite.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4830 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-02-08 00:17:30 +00:00
eb5da26004 Now this OpenSSL use is perfect.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4829 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-02-07 23:45:04 +00:00
6765254f43 libuv 1.48.0.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4828 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-02-07 23:12:32 +00:00
e98802f5b2 Fix https somehow.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4827 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-02-07 22:59:51 +00:00
af54b6483e Some success exporting and importing apps on android.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4826 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-02-07 00:05:07 +00:00
96167c3167 Fix more memory leaks and ssb shutdown order issues.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4825 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-02-06 17:42:17 +00:00
eecfdf482f Lit 3.1.2.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4824 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-02-01 01:18:11 +00:00
7ceb865206 ios OpenSSL => 3.2.1.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4823 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-01-31 04:45:17 +00:00
b919670706 Updated CodeMirror.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4822 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-01-31 03:11:38 +00:00
72120b8842 OpenSSL 3.2.1, and some android SDK updates.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4821 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-01-31 03:06:47 +00:00
1e53c08d9d Android OpenSSL => 3.2.1.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4820 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-01-31 02:22:16 +00:00
2d1b6a09e9 sqlite-amalgamation-3450100.zip
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4819 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-01-31 02:15:24 +00:00
7f661d9af9 Appease the analyzer, but also how did this ever work?
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4818 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-01-31 02:04:10 +00:00
81c66bdddd Allow downloads and top navigation from sandboxed pages. Trying to make the wiki more sensible.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4817 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-01-29 00:00:20 +00:00
4bd46a1657 Working on 0.0.16 now.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4816 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-01-28 23:55:17 +00:00
244a752ae1 Some wiki link fixes. Not enough.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4815 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-01-28 23:54:28 +00:00
72369ab745 Let's release 0.0.15.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4814 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-01-28 23:35:28 +00:00
b62a05f627 Latest picohttpparser 4e7bc76.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4813 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-01-28 21:32:12 +00:00
03eb8e7fae Change that mentions icon.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4812 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-01-28 21:11:24 +00:00
a5a00b6987 Make garbage collecting blobs ease up on my phone.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4811 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-01-28 21:08:08 +00:00
542162c78a One editor at a time.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4810 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-01-28 20:59:46 +00:00
8cfe0fb7d2 -Os => -Oz for android.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4809 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-01-28 13:54:51 +00:00
49c831cb62 Fix seeding files for a new app.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4808 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-01-28 13:09:31 +00:00
2c79e03094 A little paranoia as I stare at this code and some analyzer nonsense.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4807 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-01-28 12:50:47 +00:00
21e6cf10b6 Sigh. Linked list bugs.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4806 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-01-27 21:53:57 +00:00
dc655bb359 Prefer tf_resize_vec many places over tf_realloc.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4805 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-01-27 21:29:06 +00:00
b9987580ee Now all the tests run clean.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4804 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-01-27 21:01:10 +00:00
cb2dfc696d Fixed a few more good leaks. Now there are just some unclean shutdown issues.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4803 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-01-27 18:26:01 +00:00
7f0643f9c0 Stop leaking the TLS context.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4802 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-01-27 17:27:56 +00:00
14a4117aff Don't leak the http handlers.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4801 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-01-27 17:11:24 +00:00
55fb5dce1a Whoa, leaked messages.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4800 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-01-27 16:37:22 +00:00
923d6f9835 I think that's all the leaks accounted for though not yet fixed.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4799 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-01-27 15:45:51 +00:00
08b5ade8ec Getting closer on lifetime issues.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4798 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-01-27 14:44:17 +00:00
91f41c7497 Fix the windows build.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4797 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-01-27 13:51:08 +00:00
fa06282ff9 Make it so we don't have to wait ages for a timer to be able to shutdown with ^C.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4796 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-01-27 13:48:16 +00:00
48b967f5b6 Tryingn to button down websocket lifetime issues.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4795 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-01-27 02:36:08 +00:00
f479165aac Fixes 'tildefriends test'.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4794 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-01-27 01:47:51 +00:00
2f83ecc1ac At least one legit memory leak, but also add a SIGTERM handler that attempts a clean shutdown so that I can ensure that it succeeds. It currently does not.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4793 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-01-27 01:25:30 +00:00
01efc215fd Update codemirror.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4792 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-01-26 02:47:47 +00:00
00ba74a6c4 This simplifies upgrading an HTTP request to a websocket, I believe, and fixes sending refresh auth tokens.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4791 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-01-25 18:00:23 +00:00
44b5ba1a9a Fix excessive scroll bars.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4790 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-01-24 03:11:49 +00:00
843e172e56 Allow pasting non-image files into the wiki editor.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4789 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-01-24 02:58:53 +00:00
a0df336abe Latest libsodium-1.0.19-stable.tar.gz
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4788 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-01-23 02:18:59 +00:00
0db4bb06c9 zlib 1.3.1.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4787 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-01-23 02:14:27 +00:00
ad2b49b838 App import/export from the editor.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4786 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-01-21 23:56:36 +00:00
ab519342e8 Styling the files pane.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4785 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-01-20 16:05:00 +00:00
1f0b9012e3 Use some w3.css in the core HTML.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4784 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-01-19 02:32:55 +00:00
1ddad6be93 Null check around my apps change.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4783 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-01-19 02:17:09 +00:00
cf311003c0 Fix some weird spacing.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4782 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-01-19 02:12:17 +00:00
64249976a8 Fix https requests redirecting to http.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4781 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-01-19 00:48:42 +00:00
6ecb3ccd08 Some var => let.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4780 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-01-18 00:38:38 +00:00
4867bacb72 Remove the docs app. Will figure out how to do this all via the wiki app.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4779 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-01-18 00:37:49 +00:00
7d029d3d7a Remove the appstore app. apps does most of what it used to do, now.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4778 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-01-17 23:36:08 +00:00
455befc18f List shared apps in the apps app. App.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4777 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-01-17 22:43:32 +00:00
6e57845512 sqlite-amalgamation-3450000.zip
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4776 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-01-15 20:58:15 +00:00
1335a6e1e5 Fixed the create account falling off the screen.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4775 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-01-15 02:13:21 +00:00
1eab44464c More style.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4774 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-01-15 02:01:36 +00:00
c3e2da3d51 Oops, this is the rebuild of cm6 with that last change.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4773 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-01-15 01:57:37 +00:00
1716f71c12 Make the editor theme background a bit darker.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4772 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-01-15 01:56:43 +00:00
b52e99c958 Fix indentation?
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4771 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-01-14 03:07:08 +00:00
86531bfd7e Fix some sizes.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4770 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-01-14 03:06:59 +00:00
874a22325e Gotta highlight that whitespace.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4769 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-01-14 02:58:46 +00:00
2380b65853 Man, CSS.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4768 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-01-14 02:37:15 +00:00
f72e8cbd91 CodeMirror 6.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4767 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-01-13 17:40:47 +00:00
24e418344e Make malloc_usable_size() go away with CFLAGS.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4766 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-01-13 13:19:08 +00:00
2b7077ca70 quickjs-2024-01-13.tar.xz
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4765 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-01-13 13:04:19 +00:00
10d438e723 Eh? Windows fix.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4764 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-01-13 13:00:39 +00:00
331846ee2e Fiddling with buttons.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4763 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-01-13 03:03:03 +00:00
dc0e58afc1 w3.css-ified ssb more.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4762 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-01-13 02:55:52 +00:00
18e9252998 speedscope 1.20.0.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4761 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-01-13 02:34:41 +00:00
b2e3c04036 I did some CSS, and it was kind of OK.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4760 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-01-12 04:23:31 +00:00
4fd155e68a Make haiku compile again, though I'm not happy about its lack of INADDR_ANY support.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4759 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-01-12 00:11:03 +00:00
59ac0b5f20 Print a colored result at the end of autotest.py.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4758 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-01-11 23:57:02 +00:00
f4979c841a Cleanup of some minor old cruft in the js code.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4757 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-01-11 02:11:24 +00:00
74eb74deb1 Playing with pahole.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4756 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-01-11 01:38:30 +00:00
9e5e7b70d4 Let's try only showing my own blog posts.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4755 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-01-11 01:02:47 +00:00
2384fc9fa9 Improve some more blog links.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4754 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-01-11 00:50:12 +00:00
576e58b1e3 Make blogs semi-navigable.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4753 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-01-11 00:33:53 +00:00
a0af058f5e Don't leak promises.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4752 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-01-10 02:49:44 +00:00
b40457d774 Disable storing messages for disconnection debug by default, and add another environment variable for logging SSB RPC messages.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4751 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-01-10 02:41:28 +00:00
2353b43514 Attempt to release sqlite memory.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4750 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-01-10 02:36:27 +00:00
b11d5192c2 Fiddling with blog links.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4749 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-01-10 02:23:40 +00:00
d38c58ce1d lit 3.1.1
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4748 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-01-10 00:00:14 +00:00
a0f390b7dc Fix a memory leak in httpd.js.c.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4747 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-01-09 17:22:39 +00:00
cb12799111 Add audio/midi mime type.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4746 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-01-09 17:22:09 +00:00
86fb5c53a1 Fix wiki links within the standalone pages.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4745 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-01-08 17:42:56 +00:00
29fc728509 These look like potential leaks.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4744 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-01-08 02:30:08 +00:00
0fb341f378 Enable memory tracking on an environment variable.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4743 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-01-08 02:18:10 +00:00
8a1a182479 Fix mingw build?
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4742 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-01-07 21:35:51 +00:00
49907bc8ee Oops.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4741 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-01-07 21:08:37 +00:00
21d4a9b328 Appease gcc 12's analyzer.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4740 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-01-07 21:08:20 +00:00
d5ede43a13 Update the welcome links to all go to pages with versions for my own convenience.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4739 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-01-07 15:23:09 +00:00
b73f5011cf Continuing trying to crunch android openssl sizes.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4738 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-01-06 21:33:20 +00:00
32ebfa78cd Some automation for the identity app.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4737 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-01-06 19:52:14 +00:00
39c942a205 Support deleting identities, too.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4736 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-01-06 19:22:49 +00:00
ebc4533b10 Minor identity interface improvements.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4735 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-01-06 18:40:57 +00:00
4e5f9c86a8 Fix the app emoji button.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4734 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-01-06 15:47:14 +00:00
d89a7a5556 Looks like I can round-trip an SSB identity, now.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4733 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-01-04 01:17:30 +00:00
8ab53f2da3 Some plumbing to export an SSB identity from Tilde Friends.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4732 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-01-04 00:21:15 +00:00
4c8eab2692 Set more button tooltips.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4731 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-01-03 23:52:05 +00:00
08989f54d9 Wiki link colors, and determine the thumbnail better.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4730 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-01-03 23:24:24 +00:00
c78753f3ff Expose bip39 to script, and fix some things around base64 so that I can round trip it properly.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4729 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-01-03 17:25:34 +00:00
34a87d8b3b Minor cleanup. Make http.c trace its callbacks.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4728 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-01-03 02:14:17 +00:00
7516524d69 Implement the rest of the endpoints that were already mostly C in C.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4727 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-01-02 23:26:42 +00:00
549d7ffec8 Minor blog changes I've apparently been sitting on.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4726 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-01-02 22:21:13 +00:00
ccafc23d3c Adding bip39 so I can use it to move around private keys.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4725 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-01-02 20:25:11 +00:00
709b57d84f Move /trace and /mem to C.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4724 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-01-02 15:43:17 +00:00
9ef909c9a1 Reimplement http -> https redirects. Remove request phases. With just a little extra storage, it wasn't needed.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4723 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-01-02 15:02:47 +00:00
d7c0ffaac4 speedscope 1.19.0.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4722 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-01-02 01:09:05 +00:00
e4cd5312f1 Oops, fix websockets.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4721 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-01-01 22:22:03 +00:00
197fca6d3b Fix/cleanup around a crash I'm seeing.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4720 ed5197a5-7fde-0310-b194-c3ffbd925b24
2024-01-01 22:14:27 +00:00
04af1f0053 I think it we ask for AF_INET6, we get 4+6. Let's do that.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4719 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-12-31 03:42:07 +00:00
93d9b1ed93 I think we can assume curl on all platforms for tests.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4718 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-12-31 03:24:20 +00:00
2d73116bc0 Don't free an undefined JSValue.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4717 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-12-31 03:09:32 +00:00
f2f6d78790 Fine, whatever.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4716 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-12-31 03:09:15 +00:00
797509fc11 Fix a crash processing TLS while a session is closing.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4715 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-12-31 03:05:52 +00:00
6920504762 Work around this test failure. Dunno.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4714 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-12-31 02:41:16 +00:00
9d1476a760 Slight memcpy paranoia.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4713 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-12-30 21:41:48 +00:00
c1890775dc Fixes for fragmented websocket messages. Android is happy, now.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4712 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-12-30 21:35:53 +00:00
72e5fe5b8f Allow receiving fragmented websocket messages. I thought this was what was breaking me on Android, but it's not.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4711 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-12-30 20:35:03 +00:00
c81ec214e2 Missing thread busy indicator.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4710 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-12-30 20:34:35 +00:00
0dcc879eb1 Delete httpd.js.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4709 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-12-30 19:47:36 +00:00
4f3f4295ea Some HTTP fixes.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4708 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-12-30 19:18:09 +00:00
d02f17a8cf I think the new HTTP implementation is basically working, now.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4707 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-12-30 18:59:02 +00:00
2f6a92168e Implement connection activity timeouts.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4706 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-12-30 16:52:05 +00:00
b6a3923b27 Some quick http refactors to make websockets less magic.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4705 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-12-30 16:29:16 +00:00
d556cbc835 Let's start 0.0.15.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4704 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-12-30 16:08:15 +00:00
f186806117 wiki size fix and allow replying/reacting to blog posts in the ssb app.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4703 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-12-29 20:13:35 +00:00
f4f560b164 Let's call this 0.0.14. Cut some apps to squeeze in under 5MB.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4702 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-12-29 19:33:46 +00:00
14b7f9237b A uv_connect_t is not a handle that can be closed. Fixes a crash.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4701 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-12-29 18:58:58 +00:00
f3518b3d0f Fix some broken tooltips.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4700 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-12-29 18:12:14 +00:00
7964524e0a Fix websocket unmasking issues. Autotest works with C httpd, now.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4699 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-12-29 17:45:07 +00:00
8ab8335baa This is exchanging some websocket messages, now.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4698 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-12-25 23:50:55 +00:00
cd43bf9dfa Bugs galore, but this is sending and receiving some websocket messages.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4697 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-12-25 23:39:16 +00:00
ccebf831e7 A bit closer to websockets.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4696 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-12-25 22:53:05 +00:00
9f2f9bd8b0 Fixed some package math.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4695 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-12-24 22:09:09 +00:00
adf8c14536 Saw a websocket message go across the wire with this.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4694 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-12-24 22:06:11 +00:00
606e82d718 Saw a websocket message go across the wire with this.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4693 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-12-24 21:39:51 +00:00
1621f1753a WebSocket request/response header dance. Feels like the loop is getting close to closed, but I want to refactor everything.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4692 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-12-24 17:43:33 +00:00
196ab66e14 Treat the ?query string and body the same as httpd.js does. Now I can auth.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4691 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-12-23 19:52:59 +00:00
38ab32dad9 Fix some http request lifetime issues, and follow the same lowercase convention for headers.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4690 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-12-21 17:45:06 +00:00
86046e52f0 One less dynamic http allocation. Also one less crash.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4689 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-12-21 17:15:59 +00:00
9e7c860414 Compile fix.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4688 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-12-21 02:06:17 +00:00
7dc8b86ee2 Return legit responses for some static files.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4687 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-12-21 02:04:20 +00:00
6ecbfe3de6 Sort of barely starting to call httpd callbacks with the new implementation.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4686 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-12-21 01:27:57 +00:00
f9940fc436 Add a runtime switch between httpd implementions. One of which is totally not hooked up yet.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4685 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-12-21 00:56:16 +00:00
58e75ee276 I think we did some keep-alive.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4684 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-12-21 00:13:03 +00:00
e7771f539d Now we're uploading some data.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4683 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-12-21 00:00:15 +00:00
c2f62cd8e0 Now we're uploading some data.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4682 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-12-20 23:58:28 +00:00
f4b6812675 Auto-add a content-length header.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4681 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-12-20 23:13:03 +00:00
03e4b37c04 Make the http test complete successfully.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4680 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-12-18 17:51:15 +00:00
7b3a9e0f63 Send a valid HTTP response and shutdown the connection.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4679 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-12-17 17:44:54 +00:00
067f546580 Send a canned HTTP response.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4678 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-12-14 01:59:23 +00:00
2f7697b7ec These colors were bugging me.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4677 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-12-14 00:02:18 +00:00
1d214f89ed Work in progress HTTP server in C.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4676 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-12-13 23:59:11 +00:00
0b47207949 Show selected and hovered items in the wiki table of contents.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4675 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-12-12 17:38:10 +00:00
94dd573a81 Show a tree of wikis and docs in the wiki app.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4674 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-12-11 17:48:08 +00:00
6fa4896155 Fix OpenBSD.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4673 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-12-10 23:16:00 +00:00
28c99f9d8b We often have alloca.h.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4672 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-12-10 23:07:05 +00:00
88fbb5f73b Unused code.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4671 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-12-10 01:50:33 +00:00
402c185dd4 Let's be clear about our C standard.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4670 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-12-10 00:49:53 +00:00
ae2015a604 Add the blog app.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4669 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-12-09 20:25:49 +00:00
023731fc3f Make the wiki app produce the blog title.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4668 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-12-09 19:35:41 +00:00
99998aac8a Fixed some publicWebHostring UI issues in the ssb app.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4667 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-12-09 19:26:33 +00:00
360d0bc110 Let the wiki app post blog messages.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4666 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-12-09 19:13:06 +00:00
817838e522 Call out a summary and thumbnail for wiki pages, for the purpose of using in blog posts.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4665 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-12-09 18:35:42 +00:00
deb3cfb4b6 quickjs-2023-12-09.tar.xz with Haiku+OpenBSD tweaks.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4664 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-12-09 15:18:26 +00:00
af61519632 Compile fix.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4663 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-12-07 02:29:30 +00:00
b1714cf554 I think I fixed following calculations, again. Revived the test, though it's still very not thorough.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4662 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-12-07 02:28:49 +00:00
f0984b19f2 Encrypt wiki docs to all editors..
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4661 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-12-06 17:48:44 +00:00
eb3c9cd6f3 Compile fix.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4660 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-12-06 00:52:47 +00:00
e677b0ac3c Fix tf_min and some crashes in trace. Wow.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4659 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-12-06 00:40:34 +00:00
dd909bfe53 Give some feedback about encrypted wiki pages.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4658 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-12-05 23:39:45 +00:00
b13b111614 Make privateness of wiki pages sticky.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4657 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-12-04 17:50:13 +00:00
5511530926 Fix a mention of a renamed make target.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4656 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-12-03 18:06:50 +00:00
5e1ef01bc0 Support pasting and showing images in the wiki.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4655 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-12-03 18:03:42 +00:00
a060eadab7 ios build is part of the makefile.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4654 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-12-03 17:12:52 +00:00
70db31bb8f Add an index_map which can be used to redirect different hostnames to different app paths so that I can host multiple domains of the same device.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4653 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-12-03 17:03:17 +00:00
1292775a75 Now we're 0.0.14-wip.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4652 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-12-03 16:29:49 +00:00
0fbc84d364 Let's release 0.0.13.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4651 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-11-29 23:40:04 +00:00
0dd0b835ec wiki new message fix and sorting.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4650 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-11-27 17:26:02 +00:00
d96e0a7497 wiki cruft.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4649 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-11-27 17:23:48 +00:00
625504b8eb Fix an httpd error log.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4648 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-11-27 17:23:31 +00:00
a185ded47e Exclude the sequence number from the request in the response to createHistoryStream and ebt.replicate. I believe I had done this to match a broken client when I implemented it.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4647 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-11-26 18:01:43 +00:00
5a0c77d06a Trim down dist sizes by filtering zip contents more effectively.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4646 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-11-26 17:36:35 +00:00
e54bd316d5 iOS OpenSSL => 3.2.0.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4645 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-11-25 17:40:08 +00:00
f908b45cc7 mingw64 OpenSSL => 3.2.0.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4644 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-11-25 17:32:12 +00:00
d02751ee08 Android OpenSSL => 3.2.0.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4643 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-11-25 17:30:55 +00:00
13ab9786f7 TIL HttpOnly https://owasp.org/www-community/HttpOnly.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4642 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-11-25 13:25:41 +00:00
ba13a08e78 sqlite-amalgamation-3440200.zip
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4641 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-11-25 13:21:49 +00:00
8c92a5ff7b CodeMirror 5.65.16.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4640 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-11-22 18:11:52 +00:00
37f728835b sqlite-amalgamation-3440100.zip
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4639 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-11-22 18:08:22 +00:00
a6f1eaa09e Put a cap on how many new messages we store browser-side. Unbounded growth is leading to crashed tabs and unresponsiveness.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4638 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-11-18 15:58:16 +00:00
dff8eca16e lit => 3.1.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4637 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-11-18 14:01:10 +00:00
a3cca9aae6 Slight wiki formatting tweaks.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4636 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-11-16 01:35:36 +00:00
edabd7735c Forgot this file.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4635 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-11-16 01:33:15 +00:00
461e7b7d5a Progress toward generating static wiki pages.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4634 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-11-16 01:33:00 +00:00
06ea8d4781 Have jshint discourage 'var'.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4633 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-11-15 03:29:39 +00:00
62ef0bcb42 Rename Makefile so it won't seem like it can be run with BSD make or other.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4632 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-11-15 02:41:05 +00:00
a0043ec49f Fix up links to other wiki-doc pages, and reduce the following depth to improve load times.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4631 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-11-14 17:38:48 +00:00
305f5232e7 Add a missing order by to fix missing wiki changes.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4630 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-11-13 17:32:15 +00:00
d467c4dd8a Support removing editors, I think, and include wikis from the app owner and from the authenticated user.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4629 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-11-12 20:17:23 +00:00
5b2ace80d4 Made it possible to add wiki editors.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4628 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-11-11 13:44:20 +00:00
1484a87cad Fix wiki following, and shows wiki-docs from editors of the wiki.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4627 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-11-11 02:22:53 +00:00
b23bc0b16b Trying to work toward multiple users. List wikis of people you are following. Also hook in to the lit updated event.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4626 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-11-09 17:39:05 +00:00
a6c8dd846c Put verbose messages on a command-line argument, finally, and format the messages a bit better.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4625 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-11-09 00:28:34 +00:00
5fff3b8161 Following asan fixes.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4624 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-11-09 00:08:04 +00:00
b4236d0ec0 Show names in the id picker.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4623 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-11-08 23:51:54 +00:00
5e8cd12760 Latest libsodium-1.0.19-stable.tar.gz
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4622 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-11-08 23:17:32 +00:00
699438602c Make import and export commands complete reliably.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4621 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-11-08 23:03:21 +00:00
52aa6eed0d Builds for OpenBSD!
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4620 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-11-08 03:36:08 +00:00
6cdf207dcd Encrypt draft wiki blobs.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4619 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-11-08 01:58:02 +00:00
607e9ef71b Show more information about encrypted messages.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4618 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-11-08 01:43:58 +00:00
367c489fc3 Hide wiki things that don't apply when you're not logged in.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4617 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-11-07 23:34:55 +00:00
b3c9ad2fcb Two wiki fixes that I've redone multiple times. :/
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4616 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-11-07 23:31:12 +00:00
ee9cb63327 libuv 1.47.0.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4615 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-11-07 17:30:39 +00:00
889773c38d Re-hook up all the wiki-related modification events.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4614 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-11-06 00:59:30 +00:00
b83704a218 Fix a runaway thing in journal, too, same as wiki.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4613 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-11-05 23:26:54 +00:00
dfe5d51d04 Fix a death spiral.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4612 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-11-05 23:25:55 +00:00
280dee0438 Might as well benefit from a little parallelism.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4611 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-11-05 23:05:26 +00:00
aa10ab69f6 Hitting some limit with following too many people. Working around it.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4610 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-11-05 13:07:07 +00:00
6ef14d985d Maybe avoid increating load with the wiki app as requested accrue.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4609 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-11-05 01:07:08 +00:00
61a3226e14 Fix some wiki issues I found while making journal.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4608 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-11-04 23:03:14 +00:00
f9c370212b Add the journal app.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4607 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-11-04 18:15:58 +00:00
021f3ad5bc Fix wiki doc confusingly and incorrectly being pre-selected.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4606 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-11-04 16:52:49 +00:00
8bdc27bf5c lit => 3.0.2.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4605 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-11-04 16:37:12 +00:00
00eb5222f8 Fumbling with wiki some more.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4604 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-11-04 16:32:21 +00:00
d06f490cc2 Work in progress wiki simplification, I hope.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4603 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-11-04 02:00:35 +00:00
b087a09d37 Fix blocking the same way, too.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4602 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-11-03 00:49:17 +00:00
4cedc54d2d Reworked some following math.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4601 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-11-03 00:45:30 +00:00
8fe7adc50e wiki: Make it possible for links to work. Minor cleanup.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4600 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-11-02 01:43:32 +00:00
bf5236e68b collections -> wiki
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4599 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-11-02 01:20:15 +00:00
da1d686705 Some fixes around drafts, and set the app icon.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4598 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-11-02 01:19:14 +00:00
2ce5bc73d5 Drafts. Boom.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4597 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-11-02 00:45:20 +00:00
c6ae9313cc More wiki layout/navigation fixes, and use markdown for the wiki.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4596 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-11-02 00:29:07 +00:00
8f3883563f Close to the general experience I want for editing wiki pages. Bugs galore.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4595 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-11-01 23:39:34 +00:00
950273da41 If you can get all identities, you might as well be able to see the identities of the owner of a package.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4594 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-11-01 23:10:29 +00:00
391da742fd Collections progress.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4593 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-11-01 22:21:42 +00:00
4414676076 sqlite-amalgamation-3440000.zip
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4592 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-11-01 22:00:08 +00:00
0da45b7b40 lit => 3.0.1.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4591 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-10-30 16:42:30 +00:00
7e02cb90f6 Use ssb.following() in the ssb app.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4590 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-10-30 16:18:07 +00:00
01ba90fdba Add a work-in-progress collections app.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4589 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-10-30 00:22:30 +00:00
b394140f9e Expose ssb.following to JS.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4588 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-10-29 19:05:32 +00:00
4a1d136721 Report 'haiku' as a platform, and don't bind to :: on it, because that doesn't seem to be working.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4587 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-10-26 16:41:57 +00:00
ab3009f771 This seems to be a safer way to generate version.h.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4586 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-10-26 03:03:25 +00:00
bf340f3de4 Working on 0.0.13.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4585 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-10-26 02:59:37 +00:00
68f5827dee Builds for Haiku.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4584 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-10-26 02:56:33 +00:00
b695a4ba3b Sort issues open first, and show checkboxes for open/closed.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4583 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-10-25 23:13:46 +00:00
d84ab2734e This is 0.0.12.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4582 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-10-25 22:31:29 +00:00
4f0cc793c7 mingw64 OpenSSL => 3.1.4.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4581 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-10-25 21:56:29 +00:00
b7a4ac22b2 OpenSSL => 3.1.4 for iOS.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4580 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-10-25 21:46:16 +00:00
5db9acae1d Android OpenSSL => 3.1.4.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4579 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-10-25 16:45:38 +00:00
c299c1432c Report more platform names, and clean up blobs on default on iOS like on Android.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4578 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-10-25 16:41:39 +00:00
d8551ab732 sqlite thread safety back on. Trying to fix a weird issue.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4577 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-10-24 01:34:59 +00:00
25bc1279c2 This is how we bind sqlite variables by index.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4576 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-10-24 01:27:35 +00:00
f6d9d23456 Test docker before release.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4575 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-10-24 00:30:07 +00:00
b20d95d616 Ugg, don't clean up just-added app file blobs. Need a better solution for this.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4574 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-10-22 19:33:04 +00:00
071c2f1c20 This looks like it could miss files on import.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4573 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-10-22 19:25:52 +00:00
566d00f0df Put the native executable in the lib directory, to appease recent R^X requirements, and bump the Android target version back up to 34.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4572 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-10-22 18:52:20 +00:00
0550aa4e98 Oh for crying out loud. Android started disallowing executing files from the private data directory in SDK 29.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4571 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-10-22 18:07:23 +00:00
baf69355a5 Target android min sdk version 24 (the lowest that libuv claims to support), and specify the target SDK version as 34.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4570 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-10-22 17:51:16 +00:00
17c0266998 Lower android min sdk version to 26, and use libuv's random code.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4569 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-10-22 17:26:53 +00:00
14a2207064 Add a Server: header.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4568 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-10-22 12:44:12 +00:00
7c721fc6eb Add an emoji of the day to gg, and add a tropical fish for reasons.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4567 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-10-22 00:11:14 +00:00
ab03692a4c Lay out the profile editing form better so it feels less janky.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4566 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-10-21 21:31:46 +00:00
626fa4f27b Address some lifetime issues in the single-process case.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4565 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-10-21 19:47:47 +00:00
15676e0f4f Resize on rotation on iOS.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4564 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-10-21 18:32:23 +00:00
8709ce8ad5 Added a missing filesaver.min.js.map file.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4563 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-10-21 12:53:31 +00:00
bb924d79d6 Bring back logs on macos.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4562 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-10-21 02:12:27 +00:00
8e075e33d9 Work around log obfuscation on iOS.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4561 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-10-21 00:56:00 +00:00
b0b002104a Updated libbacktrace to latest.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4560 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-10-20 20:11:01 +00:00
43d1f34fa3 Build separate arm+x86 android APKs.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4559 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-10-20 19:46:20 +00:00
6db1a097aa Add a button in the profile editor to ask the server to follow you. I'm hoping this helps replicating accounts that are otherwise difficult to discover.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4558 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-10-20 14:37:24 +00:00
6dae2f0749 Expose the server's public key. Going to use this to make local accounts visible externally and also to make it easy to show the room link.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4557 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-10-20 12:55:05 +00:00
dc87c26b99 Missing files.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4556 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-10-20 01:40:58 +00:00
234d597083 Bump some android SDK versions.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4555 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-10-20 01:10:58 +00:00
b74c347c7c Fix some issues with multiple supported android architectures.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4554 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-10-20 00:54:55 +00:00
996996e609 Mention some other platforms in the README.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4553 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-10-20 00:25:51 +00:00
cd9050f61f Quiet zsign, and simplify an ifdef chain.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4552 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-10-20 00:16:01 +00:00
b70b309977 Fix the windows build, and update its OpenSSL to 3.1.3 like the rest.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4551 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-10-20 00:13:00 +00:00
ee510f3f3f Got the APK sizes down again.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4550 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-10-19 20:21:17 +00:00
8a70b8ea3e Trying to bring the APK size down again.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4549 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-10-19 19:47:55 +00:00
27ee73bb89 jshint.js -> jshint.min.js.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4548 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-10-19 19:46:48 +00:00
3aeb47e447 Set the iOS app icon.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4547 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-10-19 19:21:13 +00:00
b9ceb30ecf Sign when we're building the .app, but only on Linux. Support building .ipa files.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4546 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-10-19 19:18:16 +00:00
5b6ee20b2d To make the black bars go away, you have to specify a launch image. Should be a storyboard, but not now.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4545 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-10-19 18:52:56 +00:00
d062ec0dfe Helper for building and installing on iOS, because I keep forgetting.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4544 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-10-19 18:21:36 +00:00
d5a0daa0d3 Target an old enough iOS version to ignore deprecation issues for now.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4543 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-10-19 18:06:32 +00:00
8c4ec71e26 Retry the webkit load until we've connected to the server.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4542 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-10-19 18:05:34 +00:00
1e5aa0ba93 Same iOS version min.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4541 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-10-19 13:10:34 +00:00
0326a1f8cc Quash these warnings on macos, too.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4540 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-10-19 12:41:43 +00:00
8311404a09 More layout and wordsmith-ing of welcome.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4539 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-10-19 00:53:14 +00:00
c81111c2cf More welcome tweaks.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4538 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-10-18 20:57:41 +00:00
e499be12ae Welcome updates.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4537 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-10-18 20:49:53 +00:00
e785f7f10a A welcome app?
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4536 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-10-18 18:54:37 +00:00
c3da10bef6 Saving non-UTF8 sort of works.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4535 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-10-18 17:51:26 +00:00
c8c8cb305e Fix an android startup crash.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4534 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-10-18 16:58:11 +00:00
efdd046ef8 Silence some warnings.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4533 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-10-18 14:46:58 +00:00
496eefd2ee Whoops. Fix android.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4532 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-10-18 14:39:20 +00:00
9da79b3a21 I think I made all build types conditional on having the appropriate build tools available, and make all make everything currently possible.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4531 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-10-18 14:24:44 +00:00
1b2b0970fb Give android its own main, like ios, so we can deadstrip some things.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4530 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-10-17 22:43:13 +00:00
0bb45a7fa8 Whoa. Allow building the ios executable on Linux.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4529 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-10-17 22:25:31 +00:00
15a25a41aa Add some deps I apparently missed.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4528 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-10-17 22:06:41 +00:00
76b6ff78cd File drag and drop, sort of.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4527 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-10-17 21:37:42 +00:00
5285b3f222 Fix the build.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4526 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-10-17 01:30:38 +00:00
0c993c251b Implement prompt, confirm, and query for ios.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4525 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-10-16 14:16:21 +00:00
b3a1f17452 Add apple.py, fix some global initialization on iOS, and add some missing bundle-related files.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4524 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-10-16 13:57:40 +00:00
11929e8c68 iOS makefile tweaks to build more .app bundles.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4523 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-10-15 18:06:31 +00:00
fc9a081250 Need to find an alternative to uv_process_kill when using one process.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4522 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-10-15 17:43:08 +00:00
2583221117 I guess this works? Not sure what's wrong with AF_UNIX on iOS.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4521 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-10-15 17:42:04 +00:00
a69e551968 Added some questionable support for running everything in one process, because iOS.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4520 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-10-15 17:33:36 +00:00
8bd0027e71 Enough glue to load a web page from our web server in the iOS simulator. Next challenge is uv_spawn: permission denied.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4519 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-10-15 16:55:25 +00:00
84eaa3e2fd Now it builds for ios with some objective-c.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4518 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-10-14 02:11:20 +00:00
a57916b3db Link something for iossim.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4517 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-10-14 01:56:40 +00:00
87e769786a Forgotten file.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4516 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-10-14 01:37:34 +00:00
05a7e941cf More starting diagnostics on android.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4515 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-10-13 01:14:13 +00:00
ed307e6b3b Oops, don't call xcrun on linux.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4514 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-10-13 01:13:35 +00:00
e8aa957209 :O This builds an app which brings up the web site in the simulator.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4513 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-10-13 01:09:15 +00:00
a39f820ff1 Oops.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4512 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-10-12 01:33:49 +00:00
1c621a602f This builds an executable for ios.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4511 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-10-12 00:29:17 +00:00
7169f4a6cb ios64-xcrun openssl build.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4510 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-10-12 00:19:16 +00:00
e202c1a40e Make some buttons bigger.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4509 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-10-11 19:26:52 +00:00
0f4b4da0aa OpenSSL 3.1.3 built for android.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4508 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-10-11 18:44:50 +00:00
1f96413bd3 A few places I missed tracking busy worker threads.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4507 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-10-11 16:43:48 +00:00
9695621c91 Lit 3.0.0.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4506 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-10-11 16:00:38 +00:00
2d67f5ead6 sqlite-amalgamation-3430200.zip
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4505 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-10-11 15:52:41 +00:00
29d2a45abc Move reading settings from the database off of the main thread. It now happens periodically in a worker, which means I don't think there's anything blocking the main thread anymore.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4504 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-10-11 15:44:40 +00:00
13c8b05f9a Moved connections DB access to worker threads. I think global settings access is the only remaining thing on the main thread.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4503 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-10-10 01:11:32 +00:00
2c1a5359c6 Show more context in the Android app when starting the server.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4502 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-10-10 00:46:17 +00:00
d8530f228e EBT replicate to the worker threads. Almost there.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4501 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-10-08 16:17:56 +00:00
575f6c2f0a Move blob wants to the worker threads.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4500 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-10-08 15:40:20 +00:00
62cdc592c0 Move sending history streams to the worker threads.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4499 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-10-08 15:14:42 +00:00
11373faf23 Log during -t=bench every database access from the main thread.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4498 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-10-08 14:25:22 +00:00
0473eec0a2 Remove DB work from tf_ssb_notify_message_added, which runs on the main thread.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4497 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-10-08 13:52:49 +00:00
7fc23dc085 Try all supported ABIs for the executable on Android.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4496 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-10-07 13:01:21 +00:00
c741cc06b2 Exclude the 32-bit executables from the APK for now.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4495 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-10-07 12:45:45 +00:00
39dbfdec82 If we can't parse the port file, try again. Maybe we are catching it between create and write?
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4494 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-10-07 12:38:30 +00:00
0e5d6056e4 codemirror 5.65.15.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4493 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-10-05 00:56:26 +00:00
d711993b3b I need to configure libsodium better.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4492 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-10-05 00:52:59 +00:00
615bf7fe43 Fix the build.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4491 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-10-05 00:39:57 +00:00
424b9b5a2f libsodium-1.0.19-stable.tar.gz
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4490 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-10-05 00:32:37 +00:00
d1e494b730 More docs.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4489 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-10-05 00:24:33 +00:00
623e4b8fff Oh yeah, we're working on 0.0.12.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4488 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-10-05 00:14:36 +00:00
b5dd1f2f86 Fix macos build again.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4487 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-10-04 23:41:30 +00:00
ec83f9c747 Fix things building on linux again.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4486 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-10-04 23:38:42 +00:00
31af27529e This compiles on macos for x86_64, at least.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4485 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-10-04 23:20:57 +00:00
6302565942 Store app blob history.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4484 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-10-04 22:57:39 +00:00
cda724b2da Some gg snapping fixes.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4483 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-09-29 02:27:41 +00:00
c2e2ba2a40 Actually include the 32-bit executables.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4482 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-09-29 00:32:22 +00:00
b5d3f5faa7 This builds for 32-bit android. Untested.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4481 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-09-29 00:07:34 +00:00
71f3910055 More deadstripping.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4480 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-09-28 00:32:52 +00:00
70ed8c3b32 How did these get removed?
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4479 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-09-27 22:39:01 +00:00
af13bfc920 Let's release 0.0.11.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4478 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-09-27 22:26:19 +00:00
e24fd92f85 OpenSSL 1.1.1w for Android.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4477 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-09-27 22:23:52 +00:00
7e27cefe6a This made 32-bit happier.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4476 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-09-25 16:43:56 +00:00
450cf6424e Oops! Passwords go in password fields.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4475 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-09-23 21:29:01 +00:00
54898d3dbb More thorough checkpoint.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4474 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-09-23 18:49:24 +00:00
dd851a2b25 Let's try some wal truncating.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4473 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-09-23 18:41:58 +00:00
4c6b44eb30 Implemented password changing.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4472 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-09-22 22:59:26 +00:00
74a3efe78d Let's restrict valid usernames.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4471 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-09-22 22:41:47 +00:00
51301fc49e More room robustifying?
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4470 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-09-22 22:20:25 +00:00
02dd8c3dd0 Try to isolate my instability with go-ssb-room. I guess I did? Haven't found a resolution.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4469 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-09-21 23:38:55 +00:00
26a778c3b2 Might as well make the case consistent.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4468 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-09-21 00:40:47 +00:00
9fecbd97e8 Autocomplete blob refs. That's too cool.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4467 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-09-21 00:26:29 +00:00
e1383e3903 Move the HTTP timeout into C where we can manage it better as writes are active. Fixes an accidental 45 second GET timeout from httpd.js.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4466 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-09-20 23:30:29 +00:00
47532b8512 Snapping to grid.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4465 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-09-17 20:52:12 +00:00
3c4959433a Proof of concept of building emojis.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4464 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-09-17 13:22:29 +00:00
e921b4a86a Actually serialize doubles. Yikes.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4463 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-09-17 00:13:31 +00:00
b23b0ca239 Some include-what-you-use progress.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4462 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-09-13 23:39:52 +00:00
191b45f054 Better map link.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4461 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-09-13 23:01:15 +00:00
15d0383349 api docs work in progress.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4460 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-09-13 23:00:47 +00:00
d2485583fd sqlite-amalgamation-3430100.zip
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4459 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-09-13 21:19:37 +00:00
2b94704916 If a user visiting /login is already authenticated, bounce them away. This is me trying to avoid hassle for people who bookmarked the login page.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4458 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-09-11 16:52:17 +00:00
85ac6c215a Populate host and port for incoming SSB connections, too.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4457 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-09-09 14:45:55 +00:00
e83e665db9 Try harder to not reply to errors. Dunno.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4456 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-09-09 14:28:06 +00:00
645aafef16 zlib 1.3.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4455 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-09-07 22:44:49 +00:00
152c893a6f Fix wrong argument count on ssb.blobStore.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4454 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-09-07 22:44:18 +00:00
7c130dda56 Beef up the API docs.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4453 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-09-07 00:40:43 +00:00
2d82dad806 Restore the connections tab.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4452 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-09-06 22:48:06 +00:00
e8ac5b759d Fix emoji search case again, and render issues.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4451 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-09-06 22:35:20 +00:00
4833d18968 Fix android app version.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4450 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-09-05 16:21:28 +00:00
6eafded1f6 Added the memory sparkline and tried to CSS them differently.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4449 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-09-04 20:13:17 +00:00
7b440b720e Set issues app icon.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4448 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-09-04 19:47:07 +00:00
e20ba7384f Put the latlng popup on the contextmenu event.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4447 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-09-04 19:26:48 +00:00
45231c6ede Add some GPS game tabs.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4446 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-09-04 19:21:00 +00:00
35475defb5 Faster activity load through concurrency.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4445 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-09-04 18:01:06 +00:00
8741841f27 Add the missing leaflet .js.map file. Link to Google Maps from clicked map locations. Allow poup from the sandbox to support target="_blank" links, though that does not work here.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4444 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-09-04 17:44:05 +00:00
5282d19b55 Structure replies to issues as replies so that their posts don't appear so unusual.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4443 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-09-04 17:12:32 +00:00
d9782aa0fb Automatically reply encrypted to the same recipients.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4442 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-09-02 13:37:50 +00:00
9751facfb4 Add recps field to encrypted messages.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4441 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-09-02 13:25:31 +00:00
e0110203e7 More reliable addition of encrypted messages. There's something I'm not understanding with lit.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4440 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-09-02 02:06:29 +00:00
088b44cc2c Fix the admin app.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4439 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-09-01 16:26:24 +00:00
8f63bcbfbf Sending encrypted messages. Revealing some weird behavior, but it's working.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4438 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-09-01 01:34:01 +00:00
c8029388c9 Blockquote and connections tab tweaks.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4437 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-08-31 16:46:40 +00:00
d9c4d847a1 Fix Strava account linking.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4436 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-08-31 16:31:24 +00:00
df9d9425ec Add issues app.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4435 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-08-31 00:12:18 +00:00
90bb3c684e OK, no -fanalyzer on raspi, yet.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4434 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-08-30 23:58:01 +00:00
9c81b6de8a This should have gone away in a previous change.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4433 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-08-30 23:50:28 +00:00
6383498041 Oops. Makefile debugging.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4432 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-08-30 21:38:59 +00:00
daeb88785d Let's try -fanalyzer.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4431 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-08-25 20:57:55 +00:00
dcea08f73b Enforce a timeout on user SQL queries.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4430 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-08-25 20:23:40 +00:00
b252b921f8 Call out restricted DB access when we acquire the reader.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4429 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-08-25 19:41:54 +00:00
172826bf13 Use the right default port now that I'm not always running two different clients side-by-side.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4428 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-08-25 18:51:14 +00:00
060f1980f5 A little more paranoia around the file watcher.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4427 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-08-25 18:27:19 +00:00
e223d35252 Make the connections tab know more about tunnels and such.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4426 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-08-25 18:22:09 +00:00
99dba1a4c6 svn didn't notice that these files changed.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4425 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-08-25 17:19:59 +00:00
b52026c81f Give quoted text a bar on the side, like I'm used to.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4424 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-08-25 17:06:32 +00:00
47b8c86426 sqlite-amalgamation-3430000.zip
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4423 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-08-25 16:20:32 +00:00
2e55c68648 Get ready for 0.0.11.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4422 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-08-23 22:40:00 +00:00
b7362dd84d 0.0.10.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4421 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-08-23 22:37:15 +00:00
01637b31e1 Let's release 0.0.10.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4420 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-08-23 21:55:22 +00:00
0e9a39608a Exclude gg from the release.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4419 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-08-23 21:54:43 +00:00
79404e4d41 Set android min sdk version and api version all to 28. That's all we need. Should fix reported crashes.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4418 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-08-23 21:37:23 +00:00
35c21fbdaf No really, specify the API version for android.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4417 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-08-23 02:16:22 +00:00
8c7bd7dc11 Fix windows build.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4416 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-08-22 16:49:42 +00:00
09ad4f0320 More callstacks on android.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4415 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-08-22 16:48:12 +00:00
d96b836bef Better lifetimes still in the Java code, and turn on some strict vm policy messages.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4414 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-08-22 16:43:04 +00:00
59b2ffaf95 Ohh, Java does scoped resources.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4413 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-08-22 02:45:22 +00:00
f1b55ddd64 Attempt to track requests better. New requests need to be flagged as such. Still trying to chase tunnel instability.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4412 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-08-20 19:55:59 +00:00
85acac3a30 Save more context about closed connections, and include the timestamp.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4411 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-08-20 18:36:46 +00:00
befff5c1e5 Apps are not allowed to read directly from the blobs table.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4410 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-08-20 18:26:26 +00:00
d72ba81a67 Try to respond to tunnel errors I'm seeing instead of forwarding them over the tunnel, which obviously won't work. Allow creating multiple connections to the same ID if it's for the sake of a tunnel. I think this explains timeouts I'm seeing with tunnels. More error handling, too. C'mon, fix tunnels.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4409 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-08-20 18:25:15 +00:00
fef88e2032 Prevent the watcher's finalizer from being called before we're done with it.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4408 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-08-20 11:25:49 +00:00
20557e8ce4 Respond better when somebody disconnects from us with a tunnel. Trying to robustify tunnels. This is largely untested.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4407 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-08-17 16:54:33 +00:00
99c905e908 Fix build with the removal of split.js.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4406 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-08-17 00:50:22 +00:00
d7b58ee2c5 Support an edit-only mode, which brings up the editor without running the app. Helpful if the app hangs the client. Also on mobile, where both don't fit side-by-side.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4405 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-08-17 00:49:02 +00:00
faca2d387b Calculate thread busyness as the current concurrent running threads vs. the max number of threads ever seen running concurrently.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4404 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-08-17 00:01:59 +00:00
358d02d97f Another index I've wanted, and better error display for queries in the ssb app.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4403 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-08-16 22:57:16 +00:00
b66dac7465 Add an SQL query tab.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4402 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-08-16 22:48:59 +00:00
f7d201859a Use the sqlite authorizer for async requests, too.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4401 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-08-16 22:43:08 +00:00
61d2ef5469 Yeah, the ping comes in a later message. Whoops.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4400 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-08-15 23:56:58 +00:00
ac994b9c62 Were we not responding to ping?
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4399 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-08-14 16:26:06 +00:00
264dcbc331 codemirror 5.65.14.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4398 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-08-13 01:11:14 +00:00
e5425c0ffb Apparently the MUXRPC maximum segment size is 4096: bd350c6f9e/boxstream/box.go (L23). Reducing the send size seems to keep me connected to/through rooms longer.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4397 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-08-12 19:57:00 +00:00
e10803de68 Fix GPX upload.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4396 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-08-12 18:37:15 +00:00
07b1a0e403 Fiddling with login CSS.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4395 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-08-12 11:54:27 +00:00
6ed2c702d8 Hide too-new messages, and cycle between message, raw, and markdown views of messages.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4394 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-08-09 23:39:17 +00:00
5c1c33d33e Put the release process in the makefile, and including building the tarball contents to avoid another snafu.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4393 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-08-09 23:14:16 +00:00
70d37c88b5 Redo auth flow with lit. Beef up the test a bit, accordingly.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4392 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-08-09 22:38:41 +00:00
1ba37d95b5 More concise wait.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4391 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-08-07 16:13:02 +00:00
0d82198849 Remove old, broken drag+drop code.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4390 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-08-06 12:03:37 +00:00
39927e75f2 Attempt to support .gpx files.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4389 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-08-06 12:03:22 +00:00
e6fd33b969 Sure, let's add GPS game.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4388 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-08-06 00:26:56 +00:00
e8fe32d5af Fix a crash on android three different ways?
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4387 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-08-05 03:10:24 +00:00
bfc8bb864d I suspect this is necessary to prevent sending an error when we get the final response to blobs.get.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4386 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-08-05 02:30:45 +00:00
9179746763 Freaking CSS. Trying to make the admin page...work.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4385 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-08-05 01:22:27 +00:00
d0177d24cb Clean up some test cruft.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4384 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-08-04 23:44:48 +00:00
0573008c9c Set some blob auto-delete option defaults on android only.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4383 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-08-04 23:35:02 +00:00
9506f518c2 +x and shebang
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4382 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-08-04 23:33:57 +00:00
0f0ae9153b lit-html 2.8.0.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4381 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-08-04 23:10:53 +00:00
09c7c8ac64 GPS game.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4380 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-08-04 23:08:16 +00:00
5e2dfff148 Remembering permissions never worked???
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4379 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-08-04 00:50:13 +00:00
958b47548d There, I can wait for a thing in a shadow root like I want.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4378 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-08-03 00:44:35 +00:00
16155ef746 Automated enough with selenium to be able to create a Tilde Friends account, create an SSB identity, and post a first message. I'm still confused on some things, but this is progress, and I fixed a longstanding issue creating the first identity.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4377 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-08-03 00:30:48 +00:00
5755b61ea6 Oops, one more reference to smoothie.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4376 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-08-02 16:25:41 +00:00
353847a77f Remove the smoothie graphs. The sparklines are too good. I will rebuild whatever I'm missing with these.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4375 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-07-31 16:32:21 +00:00
bdf64edeb8 Expose the client's requesting URL to apps.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4374 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-07-31 00:26:09 +00:00
b5768dd927 Capture (almost) all worker thread time.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4373 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-07-29 22:29:09 +00:00
3e5abf3a4d Enable auto vacuum.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4372 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-07-27 12:22:37 +00:00
d3029639de We can't exclude libsodium's version.h!
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4371 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-07-27 12:22:16 +00:00
d21d7e4add Delete more aggressively.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4370 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-07-27 03:04:49 +00:00
afde69b5d9 Took a whack at cleaning up old blobs.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4369 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-07-27 02:51:42 +00:00
3319df3df0 Oh yeah, I was playing with the follow app.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4368 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-07-27 01:47:50 +00:00
1102feaac3 speedscope 1.16.0.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4367 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-07-27 00:42:51 +00:00
deede728be Now we're 0.0.10-wip.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4366 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-07-27 00:37:09 +00:00
19144 changed files with 57222 additions and 1319214 deletions

20
.clang-format Normal file
View File

@ -0,0 +1,20 @@
# Format Style Options - Created with Clang Power Tools
---
BasedOnStyle: WebKit
AlignEscapedNewlines: DontAlign
AlignOperands: DontAlign
AllowShortCaseLabelsOnASingleLine: false
AllowShortFunctionsOnASingleLine: false
BreakBeforeBinaryOperators: None
BreakBeforeBraces: Allman
ColumnLimit: 180
ContinuationIndentWidth: 4
IndentCaseBlocks: true
IndentWidth: 4
MaxEmptyLinesToKeep: 1
ObjCBlockIndentWidth: 4
ObjCBreakBeforeNestedBlockParam: false
SortIncludes: false
TabWidth: 4
UseTab: Always
...

2
.git-blame-ignore-revs Normal file
View File

@ -0,0 +1,2 @@
# Add prettier to the project
41024ddb7961b04a5688bbc997cb74de6fab4763

View File

@ -0,0 +1,35 @@
name: Build Tilde Friends
run-name: ${{ gitea.actor }} running 🚀
on: [push]
jobs:
Build-All:
runs-on: ubuntu-latest
container:
valid_volumes: ['/opt/keys']
volumes:
- /opt/keys:/opt/keys
steps:
- name: check out code
uses: actions/checkout@v4
with:
submodules: true
- run: ln -s /opt/keys .keys
- name: Setup JDK
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
- 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
- run: ANDROID_SDK=$HOME/.android/sdk make -j`nproc` all docs
- run: docker build .
- uses: actions/upload-artifact@v3
with:
path: out/TildeFriends-release.fdroid.apk
- uses: actions/upload-artifact@v3
with:
path: out/winrelease/tildefriends.exe

17
.gitignore vendored Normal file
View File

@ -0,0 +1,17 @@
build/
*.core
db.*
deps/ios_toolchain/
deps/openssl/
dist/
.keys
logs/
**/node_modules
out
repo/
result
*.swo
*.swp
tmp/
unsigned/
.zsign_cache/

28
.gitmodules vendored Normal file
View File

@ -0,0 +1,28 @@
[submodule "deps/zlib"]
path = deps/zlib
url = https://github.com/madler/zlib.git
[submodule "deps/libsodium"]
path = deps/libsodium
url = https://github.com/jedisct1/libsodium.git
[submodule "deps/quickjs"]
path = deps/quickjs
url = https://github.com/bellard/quickjs.git
[submodule "deps/crypt_blowfish"]
path = deps/crypt_blowfish
url = https://github.com/openwall/crypt_blowfish.git
[submodule "deps/libbacktrace"]
path = deps/libbacktrace
url = https://github.com/ianlancetaylor/libbacktrace.git
[submodule "deps/libuv"]
path = deps/libuv
url = https://github.com/libuv/libuv.git
[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

15
.prettierignore Normal file
View File

@ -0,0 +1,15 @@
node_modules
src
deps
.clang-format
flake.lock
# Minified files
**/*.min.css
**/*.min.js
**/leaflet.*
**/commonmark*
**/w3.css
apps/ssb/tribute.esm.js
apps/api/app.js
**/emojis.json

5
.prettierrc.yaml Normal file
View File

@ -0,0 +1,5 @@
trailingComma: 'es5'
useTabs: true
semi: true
singleQuote: true
bracketSpacing: false

37
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,37 @@
# Contributing to Tilde Friends
Thank you for your interest in Tilde Friends.
Above all, Tilde Friends aims to be a fun, safe place to play. When that is at
odds with the course of development, we will work through it with respectful
communication.
## How can I contribute?
The nature of Tilde Friends makes for a wide range of ways to contribute
- Just use it. Really, just kicking the tires will probably shake out issues
in useful ways at this point.
- Report and comment on bugs: https://dev.tildefriends.net/issues.
- Make apps. You don't need my permission to make and share apps with Tilde
Friends. I hope that an ecosystem of good apps grows outside of this
repository. If you want to recreate better versions of the stock apps, just
do it. If you make a better ssb app or whatever and drop me a line however
is most convenient for you, I will probably take a look and consider
replacing the stock one with it.
- Write about it. Docs in the git repository, blog posts, private messages to
me with ideas...really there is no wrong answer. Just make some noise, and
I'll do my best to incorporate or otherwise link your feedback and make the
most of it.
- Write C code in the git repository. I'm really striving for it to be the
case that other people don't really need to meddle in there, but if you can
help out, I will gladly review your pull requests via
https://dev.tildefriends.net/pulls.
## Best practices
- The C code is formatted with clang-format. Run `make format`.
- The rest is formatted with prettier. Run `npm run prettier`.
- We strive to have code compile on all platforms with no warnings and run with
no sanitizer issues.
- There are tests. Run `out/debug/tildefriends test`.

2815
Doxyfile Normal file

File diff suppressed because it is too large Load Diff

1152
GNUmakefile Normal file

File diff suppressed because it is too large Load Diff

526
Makefile
View File

@ -1,526 +0,0 @@
.ONESHELL:
.DELETE_ON_ERROR:
MAKEFLAGS += --warn-undefined-variables
MAKEFLAGS += --no-builtin-rules
VERSION_CODE := 9
VERSION_NUMBER := 0.0.9
VERSION_NAME := Failure is the only opportunity to begin again.
PROJECT = tildefriends
BUILD_DIR ?= out
BUILD_TYPES := debug release windebug winrelease androiddebug androidrelease androiddebug-x86_64 androidrelease-x86_64
UNAME_M := $(shell uname -m)
CFLAGS += \
-Wall \
-Wextra \
-Wno-unused-parameter \
-Wno-cast-function-type \
-MMD \
-ffunction-sections \
-fdata-sections \
-fno-exceptions \
-g
LDFLAGS += -Wl,--gc-sections
ANDROID_SDK ?= ~/Android/Sdk
ANDROID_BUILD_TOOLS := $(ANDROID_SDK)/build-tools/33.0.1
ANDROID_PLATFORM := $(ANDROID_SDK)/platforms/android-33
ANDROID_NDK ?= $(ANDROID_SDK)/ndk/23.1.7779620
ANDROID_NDK_API_VERSION := 31
ANDROID_MIN_SDK_VERSION := 26
ANDROID_ARM64_TARGETS := \
out/androiddebug/tildefriends \
out/androidrelease/tildefriends
ANDROID_X86_64_TARGETS := \
out/androiddebug-x86_64/tildefriends \
out/androidrelease-x86_64/tildefriends
ANDROID_TARGETS := \
$(ANDROID_X86_64_TARGETS) \
$(ANDROID_ARM64_TARGETS)
DEBUG_TARGETS := \
out/debug/tildefriends \
out/windebug/tildefriends \
out/androiddebug/tildefriends \
out/androiddebug-x86_64/tildefriends
RELEASE_TARGETS := \
out/release/tildefriends \
out/winrelease/tildefriends \
out/androidrelease/tildefriends \
out/androidrelease-x86_64/tildefriends
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),$(DEBUG_TARGETS) $(RELEASE_TARGETS))
$(NONANDROID_TARGETS): CFLAGS += -fno-omit-frame-pointer
$(NONANDROID_TARGETS): LDFLAGS += -rdynamic
$(ANDROID_TARGETS): CFLAGS += \
--sysroot $(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/sysroot \
-fPIC \
-fdebug-compilation-dir . \
-fomit-frame-pointer \
-fno-asynchronous-unwind-tables
$(ANDROID_TARGETS): LDFLAGS += --sysroot $(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/sysroot -fPIC
$(DEBUG_TARGETS): CFLAGS += -DDEBUG -Og
$(RELEASE_TARGETS): CFLAGS += -DNDEBUG
$(NONANDROID_RELEASE_TARGETS): CFLAGS += -O3
$(ANDROID_RELEASE_TARGETS): CFLAGS += -Os
windebug winrelease: CC = x86_64-w64-mingw32-gcc-win32
windebug winrelease: AS = $(CC)
windebug winrelease: CFLAGS += \
-D_WIN32_WINNT=0x0A00 \
-DWINVER=0x0A00 \
-DNTDDI_VERSION=NTDDI_WIN10 \
-Ideps/openssl/mingw64/include
windebug winrelease: LDFLAGS += \
-static \
-lm \
-Ldeps/openssl/mingw64/lib
$(ANDROID_X86_64_TARGETS): ANDROID_NDK_TARGET_TRIPLE := x86_64-linux-android
$(ANDROID_ARM64_TARGETS): ANDROID_NDK_TARGET_TRIPLE := aarch64-linux-android
$(ANDROID_TARGETS): CC = $(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/clang
$(ANDROID_TARGETS): AS = $(CC)
$(ANDROID_TARGETS): CFLAGS += \
-target $(ANDROID_NDK_TARGET_TRIPLE)$(ANDROID_NDK_API_VERSION) \
-Wno-unknown-warning-option
$(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_64_TARGETS): CFLAGS += -Ideps/openssl/android/x86_64/usr/local/include
$(ANDROID_X86_64_TARGETS): LDFLAGS += -Ldeps/openssl/android/x86_64/usr/local/lib
ifeq ($(UNAME_M),x86_64)
debug: CFLAGS += -fsanitize=address -fsanitize=undefined -fno-common
debug: LDFLAGS += -fsanitize=address -fsanitize=undefined
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_64 androidrelease-x86_64,$(addprefix $(BUILD_DIR)/$(build_type)/,$(addsuffix .o,$(basename $(value $(1)_android))))) \
$(foreach build_type,androiddebug androidrelease androiddebug-x86_64 androidrelease-x86_64,$(addprefix $(BUILD_DIR)/$(build_type)/,$(addsuffix .o,$(basename $(value $(1)_unix)))))
APP_SOURCES := $(wildcard src/*.c)
APP_OBJS := $(call get_objs,APP_SOURCES)
$(APP_OBJS): CFLAGS += \
-Ideps/base64c/include \
-Ideps/crypt_blowfish \
-Ideps/libbacktrace \
-Ideps/libsodium \
-Ideps/libsodium/src/libsodium/include \
-Ideps/libuv/include \
-Ideps/zlib \
-Ideps/zlib/contrib/minizip \
-Ideps/picohttpparser \
-Ideps/quickjs \
-Ideps/sqlite \
-Ideps/valgrind \
-Ideps/xopt \
-Wdouble-promotion \
-Werror
BLOWFISH_SOURCES := \
deps/crypt_blowfish/crypt_blowfish.c \
deps/crypt_blowfish/crypt_gensalt.c \
deps/crypt_blowfish/wrapper.c
BLOWFISH_SOURCES_win = \
deps/crypt_blowfish/x86.S
BLOWFISH_OBJS := $(call get_objs,BLOWFISH_SOURCES)
UV_SOURCES := \
deps/libuv/src/fs-poll.c \
deps/libuv/src/idna.c \
deps/libuv/src/inet.c \
deps/libuv/src/random.c \
deps/libuv/src/strscpy.c \
deps/libuv/src/strtok.c \
deps/libuv/src/threadpool.c \
deps/libuv/src/timer.c \
deps/libuv/src/uv-common.c \
deps/libuv/src/uv-data-getter-setters.c \
deps/libuv/src/version.c
UV_SOURCES_unix := \
deps/libuv/src/unix/async.c \
deps/libuv/src/unix/core.c \
deps/libuv/src/unix/dl.c \
deps/libuv/src/unix/fs.c \
deps/libuv/src/unix/getaddrinfo.c \
deps/libuv/src/unix/getnameinfo.c \
deps/libuv/src/unix/linux.c \
deps/libuv/src/unix/loop-watcher.c \
deps/libuv/src/unix/loop.c \
deps/libuv/src/unix/pipe.c \
deps/libuv/src/unix/poll.c \
deps/libuv/src/unix/process.c \
deps/libuv/src/unix/procfs-exepath.c \
deps/libuv/src/unix/proctitle.c \
deps/libuv/src/unix/random-devurandom.c \
deps/libuv/src/unix/random-getrandom.c \
deps/libuv/src/unix/random-sysctl-linux.c \
deps/libuv/src/unix/signal.c \
deps/libuv/src/unix/stream.c \
deps/libuv/src/unix/tcp.c \
deps/libuv/src/unix/thread.c \
deps/libuv/src/unix/tty.c \
deps/libuv/src/unix/udp.c
UV_SOURCES_android := \
deps/libuv/src/unix/random-getentropy.c
UV_SOURCES_win := \
deps/libuv/src/win/async.c \
deps/libuv/src/win/core.c \
deps/libuv/src/win/detect-wakeup.c \
deps/libuv/src/win/dl.c \
deps/libuv/src/win/error.c \
deps/libuv/src/win/fs-event.c \
deps/libuv/src/win/fs.c \
deps/libuv/src/win/getaddrinfo.c \
deps/libuv/src/win/getnameinfo.c \
deps/libuv/src/win/handle.c \
deps/libuv/src/win/loop-watcher.c \
deps/libuv/src/win/pipe.c \
deps/libuv/src/win/poll.c \
deps/libuv/src/win/process-stdio.c \
deps/libuv/src/win/process.c \
deps/libuv/src/win/signal.c \
deps/libuv/src/win/snprintf.c \
deps/libuv/src/win/stream.c \
deps/libuv/src/win/tcp.c \
deps/libuv/src/win/thread.c \
deps/libuv/src/win/tty.c \
deps/libuv/src/win/udp.c \
deps/libuv/src/win/util.c \
deps/libuv/src/win/winapi.c \
deps/libuv/src/win/winsock.c
UV_OBJS := $(call get_objs,UV_SOURCES)
$(UV_OBJS): CFLAGS += \
-Ideps/libuv/include \
-Ideps/libuv/src \
-Wno-dangling-pointer \
-Wno-incompatible-pointer-types \
-Wno-maybe-uninitialized \
-Wno-sign-compare \
-Wno-unused-but-set-variable \
-Wno-unused-result \
-Wno-unused-variable \
-D_GNU_SOURCE
SODIUM_SOURCES := \
deps/libsodium/src/libsodium/crypto_auth/hmacsha512/auth_hmacsha512.c \
deps/libsodium/src/libsodium/crypto_auth/hmacsha512256/auth_hmacsha512256.c \
deps/libsodium/src/libsodium/crypto_box/crypto_box.c \
deps/libsodium/src/libsodium/crypto_box/curve25519xsalsa20poly1305/box_curve25519xsalsa20poly1305.c \
deps/libsodium/src/libsodium/crypto_core/ed25519/ref10/ed25519_ref10.c \
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_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 \
deps/libsodium/src/libsodium/crypto_hash/sha256/cp/hash_sha256_cp.c \
deps/libsodium/src/libsodium/crypto_hash/sha256/hash_sha256.c \
deps/libsodium/src/libsodium/crypto_hash/sha512/cp/hash_sha512_cp.c \
deps/libsodium/src/libsodium/crypto_onetimeauth/poly1305/donna/poly1305_donna.c \
deps/libsodium/src/libsodium/crypto_onetimeauth/poly1305/onetimeauth_poly1305.c \
deps/libsodium/src/libsodium/crypto_pwhash/argon2/argon2-core.c \
deps/libsodium/src/libsodium/crypto_pwhash/argon2/argon2-fill-block-ref.c \
deps/libsodium/src/libsodium/crypto_pwhash/argon2/blake2b-long.c \
deps/libsodium/src/libsodium/crypto_scalarmult/crypto_scalarmult.c \
deps/libsodium/src/libsodium/crypto_scalarmult/curve25519/ref10/x25519_ref10.c \
deps/libsodium/src/libsodium/crypto_scalarmult/curve25519/scalarmult_curve25519.c \
deps/libsodium/src/libsodium/crypto_secretbox/crypto_secretbox_easy.c \
deps/libsodium/src/libsodium/crypto_secretbox/xsalsa20poly1305/secretbox_xsalsa20poly1305.c \
deps/libsodium/src/libsodium/crypto_sign/crypto_sign.c \
deps/libsodium/src/libsodium/crypto_sign/ed25519/ref10/keypair.c \
deps/libsodium/src/libsodium/crypto_sign/ed25519/ref10/open.c \
deps/libsodium/src/libsodium/crypto_sign/ed25519/ref10/sign.c \
deps/libsodium/src/libsodium/crypto_sign/ed25519/sign_ed25519.c \
deps/libsodium/src/libsodium/crypto_stream/chacha20/ref/chacha20_ref.c \
deps/libsodium/src/libsodium/crypto_stream/chacha20/stream_chacha20.c \
deps/libsodium/src/libsodium/crypto_stream/salsa20/ref/salsa20_ref.c \
deps/libsodium/src/libsodium/crypto_stream/salsa20/stream_salsa20.c \
deps/libsodium/src/libsodium/crypto_stream/xsalsa20/stream_xsalsa20.c \
deps/libsodium/src/libsodium/crypto_verify/sodium/verify.c \
deps/libsodium/src/libsodium/randombytes/randombytes.c \
deps/libsodium/src/libsodium/randombytes/sysrandom/randombytes_sysrandom.c \
deps/libsodium/src/libsodium/sodium/core.c \
deps/libsodium/src/libsodium/sodium/codecs.c \
deps/libsodium/src/libsodium/sodium/runtime.c \
deps/libsodium/src/libsodium/sodium/utils.c \
deps/libsodium/src/libsodium/sodium/version.c
SODIUM_OBJS := $(call get_objs,SODIUM_SOURCES)
$(SODIUM_OBJS): CFLAGS += \
-DCONFIGURED=1 \
-DMINIMAL=1 \
-Wno-unused-function \
-Wno-unused-variable \
-Wno-type-limits \
-Wno-unknown-pragmas \
-Ideps/libsodium/builds/msvc \
-Ideps/libsodium/src/libsodium/include/sodium
SQLITE_SOURCES := deps/sqlite/sqlite3.c
SQLITE_OBJS := $(call get_objs,SQLITE_SOURCES)
$(SQLITE_OBJS): CFLAGS += \
-DSQLITE_DBCONFIG_DEFAULT_DEFENSIVE \
-DSQLITE_DEFAULT_MEMSTATUS=0 \
-DSQLITE_DQS=0 \
-DSQLITE_ENABLE_MEMSYS5 \
-DSQLITE_ENABLE_FTS5 \
-DSQLITE_ENABLE_JSON1 \
-DSQLITE_LIKE_DOESNT_MATCH_BLOBS \
-DSQLITE_MAX_ATTACHED=0 \
-DSQLITE_MAX_COLUMN=100 \
-DSQLITE_MAX_COMPOUND_SELECT=300 \
-DSQLITE_MAX_EXPR_DEPTH=40 \
-DSQLITE_MAX_FUNCTION_ARG=8 \
-DSQLITE_MAX_LENGTH=5242880 \
-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_OMIT_DEPRECATED \
-DSQLITE_OMIT_DESERIALIZE \
-DSQLITE_OMIT_LOAD_EXTENSION \
-DSQLITE_OMIT_TCL_VARIABLE \
-DSQLITE_PRAGMA_DEFAULT_WAL_SYNCHRONOUS=1 \
-DSQLITE_SECURE_DELETE \
-DSQLITE_THREADSAFE=0 \
-DSQLITE_UNTESTABLE \
-DSQLITE_USE_ALLOCA \
-DHAVE_ISNAN \
-Wno-implicit-fallthrough \
-Wno-unused-but-set-variable \
-Wno-unused-function \
-Wno-unused-variable
XOPT_SOURCES := deps/xopt/xopt.c
XOPT_OBJS := $(call get_objs,XOPT_SOURCES)
$(filter $(BUILD_DIR)/win%,$(XOPT_OBJS)): CFLAGS += \
-DHAVE_SNPRINTF \
-DHAVE_VSNPRINTF \
-DHAVE_VASNPRINTF \
-DHAVE_VASPRINTF \
-Dvsnprintf=rpl_vsnprintf
$(XOPT_OBJS): CFLAGS += \
-Wno-implicit-const-int-float-conversion
QUICKJS_SOURCES := \
deps/quickjs/cutils.c \
deps/quickjs/libbf.c \
deps/quickjs/libregexp.c \
deps/quickjs/libunicode.c \
deps/quickjs/quickjs.c
QUICKJS_OBJS := $(call get_objs,QUICKJS_SOURCES)
$(QUICKJS_OBJS): CFLAGS += \
-DCONFIG_VERSION=\"$(shell cat deps/quickjs/VERSION)\" \
-DCONFIG_BIGNUM \
-D_GNU_SOURCE \
-Wno-enum-conversion \
-Wno-implicit-const-int-float-conversion \
-Wno-implicit-fallthrough \
-Wno-sign-compare \
-Wno-unused-but-set-variable \
-Wno-unused-variable
$(NONANDROID_TARGETS): CFLAGS += -DDUMP_LEAKS
LIBBACKTRACE_SOURCES := \
deps/libbacktrace/atomic.c \
deps/libbacktrace/backtrace.c \
deps/libbacktrace/dwarf.c \
deps/libbacktrace/fileline.c \
deps/libbacktrace/print.c \
deps/libbacktrace/simple.c \
deps/libbacktrace/sort.c \
deps/libbacktrace/state.c
LIBBACKTRACE_SOURCES_unix := \
deps/libbacktrace/elf.c \
deps/libbacktrace/mmap.c \
deps/libbacktrace/mmapio.c \
deps/libbacktrace/posix.c
LIBBACKTRACE_SOURCES_win := \
deps/libbacktrace/alloc.c \
deps/libbacktrace/pecoff.c \
deps/libbacktrace/posix.c \
deps/libbacktrace/read.c
LIBBACKTRACE_OBJS := $(call get_objs,LIBBACKTRACE_SOURCES)
$(LIBBACKTRACE_OBJS): CFLAGS += \
-Ideps/libbacktrace_config \
-Wno-unused-but-set-variable \
-Wno-maybe-initialized \
-Wno-unused-function \
-DBACKTRACE_ELF_SIZE=64
PICOHTTPPARSER_SOURCES := \
deps/picohttpparser/picohttpparser.c
PICOHTTPPARSER_OBJS := $(call get_objs,PICOHTTPPARSER_SOURCES)
MINIUNZIP_SOURCES := \
deps/zlib/contrib/minizip/unzip.c \
deps/zlib/contrib/minizip/ioapi.c \
deps/zlib/adler32.c \
deps/zlib/crc32.c \
deps/zlib/inffast.c \
deps/zlib/inflate.c \
deps/zlib/inftrees.c \
deps/zlib/zutil.c
MINIUNZIP_OBJS := $(call get_objs,MINIUNZIP_SOURCES)
$(MINIUNZIP_OBJS): CFLAGS += \
-Ideps/zlib \
-Wno-maybe-uninitialized
LDFLAGS += \
-pthread \
-lm
debug release: LDFLAGS += \
-ldl \
-lssl \
-lcrypto
windebug winrelease: LDFLAGS += \
-lssl \
-lcrypto \
-lcrypt32 \
-ldbghelp \
-liphlpapi \
-lkernel32 \
-lole32 \
-luserenv \
-luuid \
-lws2_32 \
-lwsock32
$(ANDROID_TARGETS): LDFLAGS += \
-target $(ANDROID_NDK_TARGET_TRIPLE)$(ANDROID_NDK_API_VERSION) \
-ldl \
-llog \
-lssl \
-lcrypto
unix: debug release
win: windebug winrelease
all: $(BUILD_TYPES) out/TildeFriends-debug.apk out/TildeFriends-release.apk
.PHONY: all win unix
ALL_APP_OBJS := \
$(APP_OBJS) \
$(BLOWFISH_OBJS) \
$(LIBBACKTRACE_OBJS) \
$(MINIUNZIP_OBJS) \
$(PICOHTTPPARSER_OBJS) \
$(QUICKJS_OBJS) \
$(SODIUM_OBJS) \
$(SQLITE_OBJS) \
$(UV_OBJS) \
$(XOPT_OBJS)
DEPS = $(ALL_APP_OBJS:.o=.d)
-include $(DEPS)
define build_rules
$(1): $(BUILD_DIR)/$(1)/$(PROJECT)$(if $(filter win%,$(1)),.exe)
.PHONY: $(1)
$(BUILD_DIR)/$(1)/$(PROJECT)$(if $(filter win%,$(1)),.exe): $(filter $(BUILD_DIR)/$(1)/%,$(ALL_APP_OBJS))
@echo [link] $$@
@$$(CC) -o $$@ -Wl,-Map,$$@.map $$^ $$(LDFLAGS)
$(BUILD_DIR)/$(1)/%.o: %.c
@mkdir -p $$(dir $$@)
@echo [c] $$@
@$$(CC) $$(CFLAGS) -c $$< -o $$@
$(BUILD_DIR)/$(1)/%.o: %.S
@mkdir -p $$(dir $$@)
@echo [as] $$@
@$$(AS) -c $$< -o $$@
endef
$(foreach build_type,$(BUILD_TYPES),$(eval $(call build_rules,$(build_type))))
src/version.h : $(firstword $(MAKEFILE_LIST))
@echo [version] $@
@echo "#define VERSION_NUMBER \"$(VERSION_NUMBER)\"\n#define VERSION_NAME \"$(VERSION_NAME)\"\n" > $@
src/android/AndroidManifest.xml : $(firstword $(MAKEFILE_LIST))
@echo [android_version] $@
@sed -i \
-e 's/versionCode=".*"/versionCode="$(VERSION_CODE)"/' \
-e 's/versionName=".*"/versionName="$(VERSION_NUMBER)"/' \
-e 's/android:minSdkVersion=".*"/android:minSdkVersion="$(ANDROID_MIN_SDK_VERSION)"/' \
$@
# Android support.
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
@mkdir -p $(dir $@)
@echo [aapt2] $@
@$(ANDROID_BUILD_TOOLS)/aapt2 compile -o out/res/ src/android/res/drawable/icon.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 src/android/AndroidManifest.xml
@mkdir -p $(dir $@)
@$(ANDROID_BUILD_TOOLS)/aapt2 link -I $(ANDROID_PLATFORM)/android.jar out/res/layout_activity_main.xml.flat out/res/drawable_icon.xml.flat --manifest src/android/AndroidManifest.xml -o out/apk/res.apk --java out/gen/
JAVA_FILES := out/gen/com/unprompted/tildefriends/R.java $(wildcard src/android/com/unprompted/tildefriends/*.java)
CLASS_FILES := $(foreach src,$(JAVA_FILES),out/classes/com/unprompted/tildefriends/$(notdir $(src:.java=.class)))
$(CLASS_FILES) &: $(JAVA_FILES)
@echo [javac] $(CLASS_FILES)
@javac --release 8 -Xlint:deprecation -classpath $(ANDROID_PLATFORM)/android.jar -d out/classes $(JAVA_FILES)
out/apk/classes.dex: $(CLASS_FILES)
@mkdir -p $(dir $@)
@echo [d8] $@
@$(ANDROID_BUILD_TOOLS)/d8 --$(BUILD_TYPE) --lib $(ANDROID_PLATFORM)/android.jar --output $(dir $@) out/classes/com/unprompted/tildefriends/*.class
PACKAGE_DIRS := \
apps/ \
core/ \
deps/codemirror/ \
deps/lit/ \
deps/split/ \
deps/smoothie/
RAW_FILES := $(shell find $(PACKAGE_DIRS) -type f)
out/apk/TildeFriends-debug.unsigned.apk: BUILD_TYPE := debug
out/apk/TildeFriends-release.unsigned.apk: BUILD_TYPE := release
out/apk/TildeFriends-debug.unsigned.apk: out/apk/classes.dex out/androiddebug/tildefriends out/androiddebug-x86_64/tildefriends $(RAW_FILES) out/apk/res.apk
out/apk/TildeFriends-release.unsigned.apk: out/apk/classes.dex out/androidrelease/tildefriends out/androidrelease-x86_64/tildefriends $(RAW_FILES) out/apk/res.apk
out/%.unsigned.apk:
@mkdir -p $(dir $@) out/apk$(BUILD_TYPE)/bin/aarch64/ out/apk$(BUILD_TYPE)/bin/x86_64/
@echo [aapt] $@
@cp out/android$(BUILD_TYPE)/tildefriends out/apk$(BUILD_TYPE)/bin/aarch64/
@cp out/android$(BUILD_TYPE)-x86_64/tildefriends out/apk$(BUILD_TYPE)/bin/x86_64/
@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip out/apk$(BUILD_TYPE)/bin/aarch64/tildefriends
@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip out/apk$(BUILD_TYPE)/bin/x86_64/tildefriends
@cp out/apk/res.apk $@
@cp out/apk/classes.dex out/apk$(BUILD_TYPE)/
@cd out/apk$(BUILD_TYPE) && zip -u ../../$@ -q -9 -r . && cd ../../
@zip -u $@ -q -9 -x '*.map' -r $(PACKAGE_DIRS) $(RAW_FILES)
out/%.apk: out/apk/%.unsigned.apk
@echo [apksigner] $(notdir $@)
@$(ANDROID_BUILD_TOOLS)/apksigner sign --ks keystore.jks --ks-key-alias androidKey --ks-pass pass:android --key-pass pass:android --out $@ $<
apk: out/TildeFriends-release.apk
.PHONY: apk
apkgo: out/TildeFriends-release.apk
@adb install $<
@adb shell am start com.unprompted.tildefriends/.MainActivity
.PHONY: apkgo
apklog:
@adb logcat *:S tildefriends
.PHONY: apklog
clean:
rm -rf $(BUILD_DIR)
.PHONY: clean

View File

@ -1,36 +1,49 @@
# Tilde Friends
Tilde Friends is a tool for making and sharing.
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.
## Building
1. Requires openssl (`libssl-dev`, in debian-speak). All other dependencies
Builds on Linux (x86_64 and aarch64), MacOS, OpenBSD, and Haiku. Builds for
all of those host platforms plus mingw64, iOS, and android.
1. Requires openssl (`libssl-dev`, in debian-speak). All other dependencies
are kept up to date in the tree.
2. To build, run `make debug` or `make release`. An executable will be
2. To build, run `make debug` or `make release`. An executable will be
generated in a subdirectory of `out/`.
3. `make windebug` or `make winrelease` will generate a windows executable
which might work.
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.
## Running
By default, running the built `tildefriends` executable will start a web server
at <http://localhost:12345/>. `tildefriends -h` lists further options.
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
<http://localhost:12345/~core/admin/`>.
privileges. Further administration can be done at
<http://localhost:12345/~core/admin/>.
## Documentation
There are the very beginnings of developer documentation in `apps/docs/`
that can be read in-place or at <http://localhost:12345/~core/docs/>.
Docs are a work in progress:
<https://www.tildefriends.net/~cory/wiki/#test-wiki/tf-app-quick-reference>.
## License
All code unless otherwise noted in is provided under the
[MIT](https://opensource.org/licenses/MIT) license.

View File

@ -1,4 +1,5 @@
{
"type": "tildefriends-app",
"emoji": "🎛"
}
"type": "tildefriends-app",
"emoji": "🎛",
"previous": "&R49FywYF8CXPhoSEydLbSCgvCddeyTiBwGuDU/gqY+M=.sha256"
}

View File

@ -9,14 +9,22 @@ tfrpc.register(function global_settings_set(key, value) {
});
async function main() {
let data = {
users: {},
granted: await core.allPermissionsGranted(),
settings: await core.globalSettingsDescriptions(),
};
for (let user of await core.users()) {
data.users[user] = await core.permissionsForUser(user);
try {
let data = {
users: {},
granted: await core.allPermissionsGranted(),
settings: await core.globalSettingsDescriptions(),
};
for (let user of await core.users()) {
data.users[user] = await core.permissionsForUser(user);
}
await app.setDocument(
utf8Decode(getFile('index.html')).replace('$data', JSON.stringify(data))
);
} catch {
await app.setDocument(
'<span style="color: #f00">Only an administrator can modify these settings.</span>'
);
}
await app.setDocument(utf8Decode(getFile('index.html')).replace('$data', JSON.stringify(data)));
}
main();
main();

View File

@ -1,10 +1,41 @@
<!DOCTYPE html>
<html>
<!doctype html>
<html style="width: 100%">
<head>
<script>const g_data = $data;</script>
<script>
const g_data = $data;
</script>
<link rel="stylesheet" href="w3.css" />
<!-- prettier-ignore -->
<style>
/* 2018 Valiant Poppy */
.w3-theme-l5 {color:#000 !important; background-color:#fbf3f3 !important}
.w3-theme-l4 {color:#000 !important; background-color:#f3d7d6 !important}
.w3-theme-l3 {color:#000 !important; background-color:#e6afae !important}
.w3-theme-l2 {color:#fff !important; background-color:#da8785 !important}
.w3-theme-l1 {color:#fff !important; background-color:#cd5f5d !important}
.w3-theme-d1 {color:#fff !important; background-color:#a93634 !important}
.w3-theme-d2 {color:#fff !important; background-color:#96302e !important}
.w3-theme-d3 {color:#fff !important; background-color:#832a28 !important}
.w3-theme-d4 {color:#fff !important; background-color:#702423 !important}
.w3-theme-d5 {color:#fff !important; background-color:#5e1e1d !important}
.w3-theme-light {color:#000 !important; background-color:#fbf3f3 !important}
.w3-theme-dark {color:#fff !important; background-color:#5e1e1d !important}
.w3-theme-action {color:#fff !important; background-color:#5e1e1d !important}
.w3-theme {color:#fff !important; background-color:#bd3d3a !important}
.w3-text-theme {color:#bd3d3a !important}
.w3-border-theme {border-color:#bd3d3a !important}
.w3-hover-theme:hover {color:#fff !important; background-color:#bd3d3a !important}
.w3-hover-text-theme:hover {color:#bd3d3a !important}
.w3-hover-border-theme:hover {border-color:#bd3d3a !important}
</style>
</head>
<body style="color: #fff">
<h1>Tilde Friends Administration</h1>
<body class="w3-theme-l4">
<header class="w3-row w3-padding w3-header w3-theme-l1">
<h1>Tilde Friends Administration</h1>
</header>
</body>
<script type="module" src="script.js"></script>
</html>
</html>

View File

@ -3,76 +3,113 @@ import * as tfrpc from '/static/tfrpc.js';
function delete_user(user) {
if (confirm(`Are you sure you want to delete the user "${user}"?`)) {
tfrpc.rpc.delete_user(user).then(function() {
alert(`User "${user}" deleted successfully.`);
}).catch(function(error) {
alert(`Failed to delete user "${user}": ${JSON.stringify(error, null, 2)}.`);
});
tfrpc.rpc
.delete_user(user)
.then(function () {
alert(`User "${user}" deleted successfully.`);
})
.catch(function (error) {
alert(
`Failed to delete user "${user}": ${JSON.stringify(error, null, 2)}.`
);
});
}
}
function global_settings_set(key, value) {
tfrpc.rpc.global_settings_set(key, value).then(function() {
alert(`Set "${key}" to "${value}".`);
}).catch(function(error) {
alert(`Failed to set "${key}": ${JSON.stringify(error, null, 2)}.`);
});
tfrpc.rpc
.global_settings_set(key, value)
.then(function () {
alert(`Set "${key}" to "${value}".`);
})
.catch(function (error) {
alert(`Failed to set "${key}": ${JSON.stringify(error, null, 2)}.`);
});
}
window.addEventListener('load', function() {
const permission_template = (permission) =>
html` <code>${permission}</code>`;
function title_case(name) {
return name
.split('_')
.map((x) => x.charAt(0).toUpperCase() + x.substring(1))
.join(' ');
}
window.addEventListener('load', function () {
const permission_template = (permission) => html` <code>${permission}</code>`;
function input_template(key, description) {
if (description.type === 'boolean') {
return html`
<label ?for=${'gs_' + key} style="grid-column: 1">${key}: </label>
<input type="checkbox" ?checked=${description.value} ?id=${'gs_' + key} style="grid-column: 2"></input>
<div style="grid-column: 3">
<button @click=${(e) => global_settings_set(key, e.srcElement.parentElement.previousElementSibling.checked)}>Set</button>
<span>${description.description}</span>
</div>
<li class="w3-row">
<label class="w3-quarter" for=${'gs_' + key} style="font-weight: bold">${title_case(key)}</label>
<div class="w3-quarter w3-padding">${description.description}</div>
<div class="w3-quarter w3-padding w3-center"><input class="w3-check" type="checkbox" ?checked=${description.value} id=${'gs_' + key}></input></div>
<button class="w3-quarter w3-button w3-theme-action" @click=${(e) => global_settings_set(key, e.srcElement.previousElementSibling.firstChild.checked)}>Set</button>
</li>
`;
} else if (description.type === 'textarea') {
return html`
<label ?for=${'gs_' + key} style="grid-column: 1">${key}: </label>
<textarea style="vertical-align: top" rows=20 cols=80 ?id=${'gs_' + key}>${description.value}</textarea>
<div style="grid-column: 3">
<button @click=${(e) => global_settings_set(key, e.srcElement.parentElement.previousElementSibling.value)}>Set</button>
<span>${description.description}</span>
</div>
<li class="w3-row">
<label class="w3-quarter" for=${'gs_' + key} style="font-weight: bold"
>${title_case(key)}</label
>
<div class="w3-rest w3-padding">${description.description}</div>
<textarea
class="w3-input"
style="vertical-align: top; resize: vertical"
id=${'gs_' + key}
>
${description.value}</textarea
>
<button
class="w3-button w3-right w3-quarter w3-theme-action"
@click=${(e) =>
global_settings_set(
key,
e.srcElement.previousElementSibling.value
)}
>
Set
</button>
</li>
`;
} else {
return html`
<label ?for=${'gs_' + key} style="grid-column: 1">${key}: </label>
<input type="text" value="${description.value}" ?id=${'gs_' + key}></input>
<div style="grid-column: 3">
<button @click=${(e) => global_settings_set(key, e.srcElement.parentElement.previousElementSibling.value)}>Set</button>
<span>${description.description}</span>
</div>
<li class="w3-row">
<label class="w3-quarter" for=${'gs_' + key} style="font-weight: bold">${title_case(key)}</label>
<div class="w3-quarter w3-padding">${description.description}</div>
<input class="w3-input w3-quarter" type="text" value="${description.value}" id=${'gs_' + key}></input>
<button class="w3-button w3-quarter w3-theme-action" @click=${(e) => global_settings_set(key, e.srcElement.previousElementSibling.value)}>Set</button>
</li>
`;
}
}
const user_template = (user, permissions) => html`
<li>
<button @click=${(e) => delete_user(user)}>
<li class="w3-card w3-margin">
<button
class="w3-button w3-theme-action"
@click=${(e) => delete_user(user)}
>
Delete
</button>
${user}:
${permissions.map(x => permission_template(x))}
${user}: ${permissions.map((x) => permission_template(x))}
</li>
`;
const users_template = (users) =>
html`<h2>Users</h2>
<ul>
${Object.entries(users).map(u => user_template(u[0], u[1]))}
</ul>`;
html` <header class="w3-container w3-theme-l2"><h2>Users</h2></header>
<ul class="w3-ul">
${Object.entries(users).map((u) => user_template(u[0], u[1]))}
</ul>`;
const page_template = (data) =>
html`<div>
<h2>Global Settings</h2>
<div style="display: grid">
${Object.keys(data.settings).sort().map(x => html`${input_template(x, data.settings[x])}`)}
</div>
${users_template(data.users)}
</div>`;
html`<div style="padding: 0; margin: 0; width: 100%; max-width: 100%">
<header class="w3-container w3-theme-l2"><h2>Global Settings</h2></header>
<div class="w3-container">
<ul class="w3-ul">
${Object.keys(data.settings)
.sort()
.map((x) => html`${input_template(x, data.settings[x])}`)}
</ul>
</div>
${users_template(data.users)}
</div> `;
render(page_template(g_data), document.body);
});
});

235
apps/admin/w3.css Normal file
View File

@ -0,0 +1,235 @@
/* W3.CSS 4.15 December 2020 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-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}
/* 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-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}

View File

@ -1,4 +1,5 @@
{
"type": "tildefriends-app",
"emoji": "📜"
}
"type": "tildefriends-app",
"emoji": "📜",
"previous": "&miGORZ8BwjHg2YO0t4bms6SI28XWPYqnqOZ8u9zsbZc=.sha256"
}

File diff suppressed because one or more lines are too long

356
apps/api/docs.js Normal file
View File

@ -0,0 +1,356 @@
export const docs = {};
docs.global = `# Tilde Friends API Documentation
Welcome to the Tilde Friends API documentation.
* [App Globals](#App_Globals)
* [Database Interface](#Database)
* [Remote Procedure Calls](#tfrpc)
<a id="App_Globals"></a>
## <span style="color: #aaf">App Globals</span>
The following are functions and values exposed to all apps in their \`app.js\` or \`handler.js\`. Most
of these are asynchronous, returning a \`Promise\` that will be resolved when the call completes, unless
noted otherwise.
This is all a work in progess. These are liable to change without warning. Feedback is welcome.
The exposed functions in this API balance multiple competing needs:
* The surface area of the exposed API ought to be fairly minimal. If something can be implemented entirely app-side, that is
generally preferred over building it into the core.
* Everything is built on this API. Ideally the admin app, the SSB app, and the editor all use standard API exposed to all
apps, with appropriate permission guards in place making it so that only trusted apps do potentially destructive operations.
There will be some things here that aren't necessarily general use to support what's required.
If you are looking at the [Tilde Friends source code](https://www.tildefriends.net/~cory/releases/),
the vast majority of these are implemented in \`src/*.js.c\` files, and exposed to apps via \`core/core.js\`.
`;
docs['core.user.credentials.session.name'] = `
*String* The name of the authenticated user.
`;
docs['app.setDocument()'] = `
Set the contents of the client &lt;iframe/&gt;.
### Parameters
* *String* **html** The HTML contents.
`;
docs['core.apps()'] = `
Gets a list of apps owned by the current user.
### Returns
*Array* An array of string names of the apps owned by the current user.
`;
docs['core.url'] = `
The url by which the running app is being invoked.
`;
docs['app.localStorageSet()'] = `
Set a value in browser local storage.
### Parameters
*String* **key** The localStorage key to set.
*String* **value** The localStorage value to set.
`;
docs['app.localStorageGet()'] = `
Gets a value from browser local storage.
### Parameters
*String* **key** The key with which the value was set.
### Returns
*String* The value, or undefined.
`;
docs['app.print()'] = `
Log information for debugging purposes to the server and to the connected browser console.
### Parameters
* ... Any args to print.
`;
docs['ssb.createIdentity()'] = `
Create a new SSB identity.
### Returns
*String* The created identity public key.
`;
docs['ssb.getIdentities()'] = `
Get all SSB identities owned by the current user.
### Returns
*Array* An array of public key strings.
`;
docs['ssb.sqlAsync()'] = `
Run an SQL query against the sqlite database.
### Parameters
* *String* **query** The sqlite query.
* *Array* **args** The query arguments to bind.
* *Function* **callback** Callback called for each row result.
`;
docs['ssb.appendMessageWithIdentity()'] = `
Signs and stores a message in the SSB database.
### Parameters
* *String* **id** The public key of an SSB identity owned by the authenticated user.
* *Object* **message** The unsigned message.
`;
docs['ssb.storeMessage()'] = `
Verifies and stores a signed message in the SSB database.
### Parameters
* *Object* **message** The valid, signed message to store.
`;
docs['ssb.blobStore()'] = `
Store a blob in the SSB database.
### Parameters
* *String*/*Uint8Array* **blob** The blob contents to store
### Returns
*String* The stored blob ID.
`;
docs['ssb.blobGet()'] = `
Fetches a blob from the database.
### Parameters
* *String* **blob_id** The blob identifier to fetch (\`&....sha256\`).
### Returns
*ArrayBuffer* The blob data.
`;
docs['print()'] = `
Log debug information both to the server's console and to the visiting user's browser console when possible.
### Parameters
* **...** Whatever you want to log. Will be joined with spaces.
`;
docs['database()'] = `
Returns a database instance that is specific to the authenticated user and the given key.
### Parameters
* *String* **key** The database key.
### Returns
*Database* A database.
`;
docs['my_shared_database()'] = `
Returns a database instance that is specific to the authenticated user and the given key.
### Parameters
* *String* **package_name** The database package name.
* *String* **key** The database key.
### Returns
*Database* A database.
`;
docs['shared_database()'] = `
Returns a database instance that is shared between all users of the app, determined by its owner and app name.
### Parameters
* *String* **key** The database key.
### Returns
*Database* A database.
`;
docs['base64Decode()'] = `
Decode a base64 string to bytes.
Completes synchronously.
### Parameters
* *String* value The base64-encoded string.
### Returns
*Uint8Array* The decoded bytes.
`;
docs['base64Encode()'] = `
Encode bytes to a base64 string.
Completes synchronously.
### Parameters
* *Uint8Array* The bytes to encode.
### Returns
*String* The base64-encoded string.
`;
docs['utf8Decode()'] = `
Decode UTF-8 bytes to a string.
Completes synchronously.
### Parameters
* *Uint8Array* **value** The value to decode.
### Returns
*String* The value as a string.
`;
docs['utf8Encode()'] = `
Encodes a string to UTF-8 bytes.
Completes synchronously.
### Parameters
* *String* **value** The value to encode.
### Returns
*Uint8Array* The encoded \`value\`.
`;
docs['setTimeout()'] = `
Call a function after some delay.
### Parameters
* *Function* **callback** The function to call.
* *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?
Completes synchronously.
### Parameters
* *Integer* **exit_code** System exit code.
`;
docs['version()'] = `
Gets version information for the running server.
### Returns
*Object* Keys are things like \`name\` and \`number\` for the server itself and \`libuv\` and \`openssl\` for
dependencies. Values are *String* version numbers.
`;
docs['platform()'] = `
Gets the host operating system platform of the running server.
### Returns
*String* The platform, one of \`windows\`, \`android\`, \`linux\`, or \`other\`.
`;
docs['getFile()'] = `
Gets a file from the running app.
### Parameters
* *String* **name** Name of the file to retrieve.
### Returns
*Uint8Array* The contents of a file from the app with the given name, or *undefined*.
`;
docs.database = `
# <span style="color: #aaf">Database</span>
Local-only storage is provided by a \`Database\` type representing a key-value store.
`;
docs['database.get()'] = `
Gets a value from the database.
### Parameters
* *String* **key** The key.
### Returns
*String* The value from the database or undefined if not found.
`;
docs['database.getAll()'] = `
Gets all keys from the database.
### Returns
*Array* An array of *String* key names for all keys in the given database.
`;
docs['database.getLike()'] = `
Gets all keys and values from the database matching a pattern.
### Parameters
* *String* **pattern** An sqlite \`LIKE\` pattern to match keys against.
### Returns
*Object* An object whose keys are the database keys and values are the database values that match the given pattern.
`;
docs['database.set()'] = `
Sets a value in the database, creating a new entry or replacing an existing entry.
### Parameters
* *String* **key** The key.
* *String* **value** The value.
`;
docs['database.exchange()'] = `
Performs an atomic compare and exchange operation, setting a value in the database only if its current value matches what is expected.
### Parameters
* *String* **key** The key.
* *String* **expected** The expected value.
* *String* **value** The new value.
### Returns
*Boolean* true if the value is now the given value.
`;
docs['database.remove()'] = `
Removes an entry from the database if it exists.
### Parameters
* *String* **key** The key.
`;
docs.tfrpc = `
# <span style="color: #aaf" id="tfrpc">tfrpc</span>
\`tfrpc.js\` is a small helper script that is available to be used to facilitate communication between parts of an application.
\`tfrpc.js\` can be used to asynchronously make calls between the app code running in a sandboxed iframe in the browser
and the app process on the server.
From \`app.js\`:
\`\`\`
import * as tfrpc from '/tfrpc.js';
\`\`\`
From script running in the browser:
\`\`\`
import * as tfrpc from '/static/tfrpc.js';
\`\`\`
Either side can register or call functions, though they must be registered before they can be called. Arguments and return
values are ultimately serialized by means that attempt to preserve most JSON-serializable values as well as functions themselves.
`;
docs['tfrpc.register()'] = `
Register a function, allowing it to be called remotely.
### Parameters
* *Function* **function** The function to register. Its name will be how it will be called.
`;
docs['tfrpc.rpc.*()'] = `
Call a remote function.
### Parameters
* **...** Parameters to pass to the function.
### Returns
The return value of the called function.
`;

View File

@ -1,4 +1,5 @@
{
"type": "tildefriends-app",
"emoji": "💻"
}
"type": "tildefriends-app",
"emoji": "💻",
"previous": "&icsPplXHgmpkbNWyo/WTvRdT/A8BXxW4lJixOtP4ueQ=.sha256"
}

View File

@ -1,25 +1,87 @@
/**
* Fetches information about the applications
* @param apps Record<appName, blobId>
* @returns an object including the apps' name, emoji, and blobs ids
*/
async function fetch_info(apps) {
let result = {};
// For each app
for (let [key, value] of Object.entries(apps)) {
// Get it's blob and parse it
let blob = await ssb.blobGet(value);
blob = blob ? utf8Decode(blob) : '{}';
// Add it to the result object
result[key] = JSON.parse(blob);
}
return result;
}
/**
*
*
*/
async function fetch_shared_apps() {
let messages = {};
await ssb.sqlAsync(
`
SELECT messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
FROM messages_fts('"application/tildefriends"')
JOIN messages ON messages.rowid = messages_fts.rowid
ORDER BY messages.timestamp
`,
[],
function (row) {
let content = JSON.parse(row.content);
for (let mention of content.mentions) {
if (mention?.type === 'application/tildefriends') {
messages[JSON.stringify([row.author, mention.name])] = {
message: row,
blob: mention.link,
name: mention.name,
};
}
}
}
);
let result = {};
for (let app of Object.values(messages).sort(
(x, y) => y.message.timestamp - x.message.timestamp
)) {
let app_object = JSON.parse(utf8Decode(await ssb.blobGet(app.blob)));
if (app_object) {
app_object.blob_id = app.blob;
result[app.name] = app_object;
}
}
return result;
}
async function main() {
var apps = await fetch_info(await core.apps());
var core_apps = await fetch_info(await core.apps('core'));
var doc = `<!DOCTYPE html>
<html>
<head>
<style>
const apps = await fetch_info(await core.apps());
const core_apps = await fetch_info(await core.apps('core'));
const shared_apps = await fetch_shared_apps();
const stylesheet = `
body {
color: whitesmoke;
font-family: sans-serif;
margin: 16px;
}
.container {
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;
}
.app {
height: 96px;
width: 64px;
@ -34,44 +96,87 @@ async function main() {
max-width: 64px;
text-overflow: ellipsis ellipsis;
overflow: hidden;
color: whitesmoke;
}
</style>
</head>
<body style="background: #888">
<h1 id="apps_title">Apps</h1>
<div id="apps" class="container"></div>
<h1>Core Apps</h1>
<div id="core_apps" class="container"></div>
</body>
<script>
function populate_apps(id, name, apps) {
var list = document.getElementById(id);
for (let app of Object.keys(apps).sort()) {
let div = list.appendChild(document.createElement('div'));
div.classList.add('app');
`;
let icon_a = document.createElement('a');
let icon = document.createElement('div');
icon.appendChild(document.createTextNode(apps[app].emoji || '📦'));
icon.style.fontSize = 'xxx-large';
icon_a.appendChild(icon);
icon_a.href = '/~' + name + '/' + app + '/';
icon_a.target = '_top';
div.appendChild(icon_a);
const body = `
<h1 style="text-shadow: #808080 0 0 10px;">Welcome to Tilde Friends.</h1>
let a = document.createElement('a');
a.appendChild(document.createTextNode(app));
a.href = '/~' + name + '/' + app + '/';
a.target = '_top';
div.appendChild(a);
<h2>your apps</h2>
<div id="apps" class="container"></div>
<h2>shared apps</h2>
<div id="shared_apps" class="container"></div>
<h2>core apps</h2>
<div id="core_apps" class="container"></div>
`;
const script = `
/*
* Creates a list of apps
* @param id the id of the element to populate
* @param name (a username, 'core' or undefined)
* @param apps Object, a list of apps
*/
function populate_apps(id, name, apps) {
// Our target
var list = document.getElementById(id);
// 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'));
div.classList.add('app');
// The app's icon
let href = name ? '/~' + name + '/' + app + '/' : ('/' + apps[app].blob_id + '/');
let icon_a = document.createElement('a');
let icon = document.createElement('div');
icon.appendChild(document.createTextNode(apps[app].emoji || '📦'));
icon.style.fontSize = 'xxx-large';
icon_a.appendChild(icon);
icon_a.href = href;
icon_a.target = '_top';
div.appendChild(icon_a);
// The app's name
let a = document.createElement('a');
a.appendChild(document.createTextNode(app));
a.href = href;
a.target = '_top';
div.appendChild(a);
}
}
}
document.getElementById('apps_title').innerText = "~${escape(core.user.credentials?.session?.name || 'guest')}'s Apps";
populate_apps('apps', '${core.user.credentials?.session?.name}', ${JSON.stringify(apps)});
populate_apps('core_apps', 'core', ${JSON.stringify(core_apps)});
</script>
</html>`;
app.setDocument(doc);
populate_apps('apps', '${core.user.credentials?.session?.name}', ${JSON.stringify(apps)});
populate_apps('core_apps', 'core', ${JSON.stringify(core_apps)});
populate_apps('shared_apps', undefined, ${JSON.stringify(shared_apps)});
`;
// Build the document
const document = `
<!DOCTYPE html>
<html>
<head>
<style>
${stylesheet}
</style>
</head>
<body>
${body}
</body>
<script>
${script}
</script>
</html>`;
// Send it to the browser
app.setDocument(document);
}
main();

View File

@ -1,4 +0,0 @@
{
"type": "tildefriends-app",
"emoji": "🛍"
}

View File

@ -1,55 +0,0 @@
async function get_apps() {
let results = {};
await ssb.sqlAsync(`
SELECT messages.*
FROM messages_fts('"application/tildefriends"')
JOIN messages ON messages.rowid = messages_fts.rowid
ORDER BY timestamp
`,
[],
function(row) {
let content = JSON.parse(row.content);
for (let mention of content.mentions) {
if (mention?.type === 'application/tildefriends') {
results[JSON.stringify([row.author, mention.name])] = {
message: row,
blob: mention.link,
name: mention.name,
};
}
}
});
return Object.values(results).sort((x, y) => y.message.timestamp - x.message.timestamp);
}
function render_app(app) {
return `
<div style="border: 2px solid white; display: inline-block; margin: 8px; padding: 8px">
<a href="/~cory/ssb/#${app.message.author}">@</a>
<a href="/~cory/ssb/#${app.message.id}">%</a>
<a href="/${app.blob}/">${app.name}</a>
</div>
`;
}
async function main() {
let apps = await get_apps();
app.setDocument(`
<html>
<head>
<base target="_top">
<style>
a:link { color: #bbf; }
a:visited { color: #ddd; }
a:hover { color: #ddf; }
</style>
</head>
<body style="color: #fff">
<h1>${apps.length} apps</h1>
${apps.map(render_app).join('\n')}
</body>
</html>
`);
}
main();

5
apps/blog.json Normal file
View File

@ -0,0 +1,5 @@
{
"type": "tildefriends-app",
"emoji": "🪵",
"previous": "&TIrBnpN3iz3O9L9MCCteAcVJZjA83EKdcfu4SCM76VE=.sha256"
}

8
apps/blog/app.js Normal file
View File

@ -0,0 +1,8 @@
import * as blog from './blog.js';
async function main() {
let blogs = await blog.get_posts();
await app.setDocument(blog.render_html(blogs));
}
main();

207
apps/blog/blog.js Normal file
View File

@ -0,0 +1,207 @@
import * as commonmark from './commonmark.min.js';
function escape(text) {
return (text ?? '')
.replaceAll('&', '&amp;')
.replaceAll('<', '&lt;')
.replaceAll('>', '&gt;');
}
function escapeAttribute(text) {
return (text ?? '')
.replaceAll('&', '&amp;')
.replaceAll('<', '&lt;')
.replaceAll('>', '&gt;')
.replaceAll('"', '&quot;')
.replaceAll("'", '&#39;');
}
export async function get_blog_message(id) {
let message;
await ssb.sqlAsync(
'SELECT author, timestamp, content FROM messages WHERE id = ?',
[id],
function (row) {
let content = JSON.parse(row.content);
message = {
author: row.author,
timestamp: row.timestamp,
blog: content?.blog,
title: content?.title,
};
}
);
if (message) {
await ssb.sqlAsync(
`
SELECT json_extract(content, '$.name') AS name
FROM messages
WHERE author = ?
AND json_extract(content, '$.type') = 'about'
AND json_extract(content, '$.about') = author
AND name IS NOT NULL
ORDER BY sequence DESC LIMIT 1
`,
[message.author],
function (row) {
message.name = row.name;
}
);
}
return message;
}
export function markdown(md) {
let reader = new commonmark.Parser({safe: true});
let writer = new commonmark.HtmlRenderer();
let parsed = reader.parse(md || '');
let walker = parsed.walker();
let event, node;
while ((event = walker.next())) {
node = event.node;
if (event.entering) {
if (node.destination?.startsWith('&')) {
node.destination =
'/' + node.destination + '/view?filename=' + node.firstChild?.literal;
} else if (
node.destination?.startsWith('@') ||
node.destination?.startsWith('%')
) {
node.destination = '/~core/ssb/#' + escape(node.destination);
}
}
}
return writer.render(parsed);
}
export async function render_blog_post_html(blog_post) {
let blob = utf8Decode(await ssb.blobGet(blog_post.blog));
return `<!DOCTYPE html>
<html>
<head>
<title>🪵Tilde Friends Blog - ${markdown(blog_post.title)}</title>
<base target="_top">
</head>
<body>
<h1><a href="./">🪵Tilde Friends Blog</a></h1>
<div>
<div><a href="../ssb/#${escapeAttribute(blog_post.author)}">${escape(blog_post.name)}</a> ${escape(new Date(blog_post.timestamp).toString())}</div>
<div>${markdown(blob)}</div>
</div>
</body>
</html>
`;
}
function render_blog_post(blog_post) {
return `
<div>
<h2><a href="/~${core.app.owner}/${core.app.name}/${escapeAttribute(blog_post.id)}">${escape(blog_post.title)}</a></h2>
<div><a href="../ssb/#${escapeAttribute(blog_post.author)}">${escape(blog_post.name)}</a> ${escape(new Date(blog_post.timestamp).toString())}</div>
<div>${markdown(blog_post.summary)}</div>
</div>
`;
}
export function render_html(blogs) {
return `<!DOCTYPE html>
<html>
<head>
<title>🪵Tilde Friends Blog</title>
<link href="./atom" type="application/atom+xml" rel="alternate" title="🪵Tilde Blog"/>
<style>
html {
background-color: #ccc;
}
</style>
<base target="_top">
</head>
<body>
<div style="display: flex; flex-direction: row; align-items: center; gap: 1em">
<h1>🪵Tilde Friends Blog</h1>
<div style="font-size: xx-small; vertical-align: middle"><a href="/~cory/blog/atom">atom feed</a></div>
</div>
${blogs.map((blog_post) => render_blog_post(blog_post)).join('\n')}
</body>
</html>`;
}
function render_blog_post_atom(blog_post) {
return `<entry>
<title>${escape(blog_post.title)}</title>
<link href="/~cory/ssb/#${blog_post.id}" />
<id>${blog_post.id}</id>
<published>${escape(new Date(blog_post.timestamp).toString())}</published>
<summary>${escape(blog_post.summary)}</summary>
<author>
<name>${escape(blog_post.name)}</name>
<feed>${escape(blog_post.author)}</feed>
</author>
</entry>`;
}
export function render_atom(blogs) {
return `<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>🪵Tilde Blog</title>
<subtitle>A subtitle.</subtitle>
<link href="${core.url}/atom" rel="self"/>
<link href="${core.url}"/>
<id>${core.url}</id>
<updated>${new Date().toString()}</updated>
${blogs.map((blog_post) => render_blog_post_atom(blog_post)).join('\n')}
</feed>`;
}
export async function get_posts() {
let blogs = [];
let ids = await ssb.getIdentities();
await ssb.sqlAsync(
`
WITH
blogs AS (
SELECT
messages.author,
messages.id,
json_extract(messages.content, '$.title') AS title,
json_extract(messages.content, '$.summary') AS summary,
json_extract(messages.content, '$.blog') AS blog,
messages.timestamp
FROM messages_fts('blog')
JOIN messages ON messages.rowid = messages_fts.rowid
WHERE json_extract(messages.content, '$.type') = 'blog'),
public AS (
SELECT author FROM (
SELECT
messages.author,
RANK() OVER (PARTITION BY messages.author ORDER BY messages.sequence DESC) AS author_rank,
json_extract(messages.content, '$.publicWebHosting') AS is_public
FROM messages_fts('about')
JOIN messages ON messages.rowid = messages_fts.rowid
WHERE json_extract(messages.content, '$.type') = 'about' AND is_public IS NOT NULL)
WHERE author_rank = 1 AND is_public),
names AS (
SELECT author, name FROM (
SELECT
messages.author,
RANK() OVER (PARTITION BY messages.author ORDER BY messages.sequence DESC) AS author_rank,
json_extract(messages.content, '$.name') AS name
FROM messages_fts('about')
JOIN messages ON messages.rowid = messages_fts.rowid
WHERE json_extract(messages.content, '$.type') = 'about' AND
json_extract(messages.content, '$.about') = messages.author AND
name IS NOT NULL)
WHERE author_rank = 1)
SELECT blogs.*, names.name FROM blogs
JOIN json_each(?) AS self ON self.value = blogs.author
JOIN public ON public.author = blogs.author
LEFT OUTER JOIN names ON names.author = blogs.author
ORDER BY blogs.timestamp DESC LIMIT 20
`,
[JSON.stringify(ids)],
function (row) {
blogs.push(row);
}
);
return blogs;
}

1
apps/blog/commonmark.min.js vendored Normal file

File diff suppressed because one or more lines are too long

51
apps/blog/handler.js Normal file
View File

@ -0,0 +1,51 @@
import * as blog from './blog.js';
async function main() {
if (request.path.startsWith('%') && request.path.endsWith('.sha256')) {
let id = request.path.startsWith('%25')
? '%' + request.path.substring(3)
: request.path;
let message = await blog.get_blog_message(id);
if (message) {
respond({
data: await blog.render_blog_post_html(message),
content_type: 'text/html; charset=utf-8',
});
} else {
respond({
data: `Message ${id} not found.`,
content_type: 'text/html; charset=utf-8',
});
}
} else if (request.path == 'atom') {
let blogs = await blog.get_posts();
respond({
data: blog.render_atom(blogs),
content_type: 'application/atom+xml',
});
} else {
let blogs = await blog.get_posts();
for (let blog_post of blogs) {
let title = (blog_post.title || '').replaceAll(/\W/g, '_').toLowerCase();
if (request.path === title) {
respond({
data: await blog.render_blog_post_html(blog_post),
content_type: 'text/html; charset=utf-8',
});
return;
}
}
respond({
data: blog.render_html(blogs),
content_type: 'text/html; charset=utf-8',
});
}
}
main().catch(function (error) {
respond({
data: `<!DOCTYPE html>
<pre style="color: #f00">${error.message}\n${error.stack}</pre>`,
content_type: 'text/html',
});
});

120
apps/blog/lit-all.min.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,4 +1,4 @@
{
"type": "tildefriends-app",
"emoji": "💽"
}
"type": "tildefriends-app",
"emoji": "💽"
}

View File

@ -51,7 +51,7 @@ async function key_list(db) {
app.setDocument(doc);
}
core.register('message', async function(message) {
core.register('message', async function (message) {
if (message.event == 'hashChange') {
let hash = message.hash.substring(1);
if (hash.startsWith(':shared:')) {
@ -67,4 +67,4 @@ core.register('message', async function(message) {
}
});
database_list();
database_list();

View File

@ -1,4 +0,0 @@
{
"type": "tildefriends-app",
"emoji": "📚"
}

File diff suppressed because one or more lines are too long

View File

@ -1,16 +0,0 @@
# Tilde Friends Developer's Guide
[Back to index](#index)
A Tilde Friends application runs on the server. To make an interesting
application that interacts with the client, it's necessary to understand
how the parts work together.
## Hello, world!
A simple starting point. Presents `Hello, world!` in the browser when
visited.
**app.js**:
```
app.setDocument('<h1>Hello, world!</h1>');
```

View File

@ -1,12 +0,0 @@
# Tilde Friends Documentation
Tilde Friends is a participating member of a greater social
network, [Secure Scuttlebutt](https://scuttlebutt.nz/),
adding a way to safely and securely write, share,
and run code in the form of server-side web applications.
- [Tilde Friends Vision](#vision)
- [Secure Scuttlebutt from Scratch](#ssb)
- [Structure](#structure)
- [Guide](#guide)
- [TODO](#todo)

View File

@ -1,41 +0,0 @@
# Secure Scuttlebutt from Scratch
[Back to index](#index)
This aims to be the missing reference for those who wish to create a Secure
Scuttlebutt client from scratch.
## Discovery
A good way to get started is to participate in local network discovery with a known working
client on the same network. The
[Scuttlebutt Programming Guide](https://ssbc.github.io/scuttlebutt-protocol-guide/#local-network)
is a good start, here, with a few things to note:
1. Some clients advertise multiple addresses separated by semicolons (`;`).
2. Some clients advertise alternative protocols than `shs` and use hostnames instead of
IPv4 addresses.
So be prepared to accept variations.
There also an undocumented "new" style of discovery message.
## Secret Handshake, Box Stream, and RPC Protocol
Now that two clients are aware of eachother, they need to complete a secret handshake.
The [programming guide](https://ssbc.github.io/scuttlebutt-protocol-guide/#handshake)
is once again a good reference.
The box stream and RPC protocol can both be implemented from the
[same documentation](https://ssbc.github.io/scuttlebutt-protocol-guide/#box-stream)
without surprises.
## Synchronizing Data
... `ebt.replicate` or `createHistoryStream` ...
## Rooms
TODO
## References
* [https://ssbc.github.io/scuttlebutt-protocol-guide/](https://ssbc.github.io/scuttlebutt-protocol-guide/)
* [https://dev.planetary.social/](https://dev.planetary.social/)
* [https://dev.scuttlebutt.nz/#/golang/?id=muxrpc-endpoints](https://dev.scuttlebutt.nz/#/golang/?id=muxrpc-endpoints)

View File

@ -1,65 +0,0 @@
# Tilde Friends Structure
[Back to index](#index)
Tilde Friends is a mostly-self-contained executable written in C.
In combines the following key components:
- A Secure Scuttlebutt (SSB) client/server. This talks with other SSB
instances, storing messages and blobs for anyone visible to local
users as they are encountered and sharing anything published locally
as appropriate.
- An sqlite database. This is where the SSB instance stores its data.
The general schema involves a `messages` table, storing mostly JSON,
a `blobs` table storing arbitrary blob data, and a `properties` table,
storing arbitrary state gleaned from `messages` and `blobs`, generally
updated on demand and incrementally.
- A QuickJS runtime. The core process runs stock scripts and has access
and permission to use all resources. All other processes, which
includes everything which runs untrusted code created by Tilde Friends
users, are strictly sandboxed in ways similar to how web browsers run
untrusted code. All attempts to access potentially sensitive resources
are mediated through the core process.
When run with no arguments, it starts a web server on
[http://localhost:12345/](http://localhost:12345/) and an SSB node.
## Web Interface
The Tilde Friends web server provides access to Tilde Friends applications,
which are arbitrary user-defined web applications.
At the top left, in addition to some basic navigation links, is an `edit`
link. Anyone can view, modify, and run in-place the code to any Tilde
Friends application by using the in-browser editor.
At the top right, one can `login` (to save work in their own space)
or `logout` (proceeding as a guest).
The rest of the page is an iframe belonging to the application.
## Special Paths
- `/~user/app/` - Tilde Friends application paths take the form `/~user/app/`, where `user`
is a username of a Tilde Friends account, and `app` is an arbitrary name
of an application saved by the given user.
- `/~user/app/file` - A raw file in an app.
- `/&blobid.ed25519` - A raw blob. Content-Type is inferred for at least
a few common image types.
## Communication Channels
Web Browser <-> Core <-> Sandbox
Visiting an application path delivers stock HTML and JavaScript which
establishes a WebSocket connection back to the server.
At this point, a new sandbox process is started in Tilde Friends, much
as a new sandboxed process might be started for a new tab in a web
browser. This process has a custom RPC connection to the core process
which holds the WebSocket connection to the browser.
The custom RPC communication between the sandbox process and the core
process facilitates passing and calling functions remotely. Calling a
function in another process returns a `Promise`.
An application will typically call `app.setDocument()` at startup to
populate the app's iframe in the web browser with its own client web
application resources.

View File

@ -1,63 +0,0 @@
# Tilde Friends TODO
[Back to index](#index)
## MVP3
- Sync status (problem feeds, messages/seconds stats, ...)
- app: wiki
- app: public blog
- Content-Disposition: download
- remove SSB credentials
- export SSB credentials
- initial: better empty news screen
- initial: remembered wrong user across login/logout
- initial: bad experience when following nobody
- make a cool independent app
- indicate when workspace differs from installed
- / => Something good.
- update docs
- audit + document API exposed to apps
- fix weird HTTP warnings
- channels
- placeholder/missing images
- no denial of service
- package standalone executable
- editor without app iframe
- sequence_before_author -> flags
- linkify ssb: links
- perfect rooms support
- connections 2.0
- make a better connections API
## Maybe Done
- blob_wants 2.0
- image downsample
- app: todo
- app: build archive
- update README
- administrators config
- apps name characters
- initial: can't switch to account when there is only one
- get tarball under 5MB
- rooms
- initial: doesn't refresh when create identity
- tf account timeout why
- ssb don't overflow boxes
- jwt for session tokens
- linkify https://...
- emoji reaction picker
- expose loads of stats
- confirm posting all new messages
- multiple identities per user, in database
- auto-populate data on initial launch
- make the docker image good / test it / use it
- leaking imports / exports
- file upload widget
- keep working on good error feedback
- build for windows
- installable apps (bring back an app message?)
- sqlStream => sqlExec or something
- !ssb from child process?
## Done
- update LICENSE
- logging to browser

View File

@ -1,62 +0,0 @@
# Tilde Friends Vision
[Back to index](#index)
Tilde Friends is a tool for making and sharing.
It is both a peer-to-peer social network client, participating in Secure
Scuttlebutt, and an environment for creating and running web applications.
## Why
This is a thing that I wanted to exist and wanted to work on. No other reason.
There is not a business model. I believe it is interesting and unique.
## 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.
## Ways to Use Tilde Friends
1. **Social Network User**: This is a social network first. You are just here,
because your friends are. Or you like how we limit your message length or
short videos or whatever the trend is. If you are ambitious, you click links
and see interactive experiences (apps) that you wouldn't see elsewhere.
2. **Web Visitor**: You get links from a friend to meeting invites, polls, games,
lists, wiki pages, ..., and you interact with them as though they were
cloud-hosted by a megacorporation. They just work, and you don't think twice.
3. **Group leader**: You host or use a small public instance, installing apps for
a group of friends to use as web visitors.
4. **Developer**: You like to write code and make or improve apps for fun or to
solve problems. When you encounter a Tilde Friends app on a strange server,
you know you can trivially modify it or download it to your own instance.
## Future Goals / Endgame
1. Mobile apps. This can run on your old phone. Maybe you won't be hosting
the web interface publicly, but you can sync, install and edit apps, and
otherwise get the full experience from a tiny touch screen.
2. The universal application runtime. The web browser is the universal
platform, but even for the simplest application that you might want to host
for your friends, cloud hosting, containers, and complicated dependencies might
all enter the mix. Tilde Friends, though it is yet another thing to host,
includes everything you need out of the box to run a vast variety of interesting
apps.
Tilde Friends will be built out, gradually providing safe access to host
resources and client resources the same way web browsers extended access to
resources like GPU, persistent storage, cameras, ... over the years.
Not much effort has been put forward yet to having a robust, long-lasting API,
but since the client side longevity is already handled by web browsers, it
seems possible that the server-side API can be managed in a similar way.
3. An awesome development environment. Right now it runs JavaScript from the
first embeddable text editor I could poorly configure enough to edit code,
but it could incorporate a debugger, source control integration a la ssb-git,
merge tools, and transpiling from all sorts of different languages.

View File

@ -1,4 +1,4 @@
{
"type": "tildefriends-app",
"emoji": "➡️"
}
"type": "tildefriends-app",
"emoji": "➡️"
}

View File

@ -1,105 +1,233 @@
var g_following_cache = {};
var g_following_deep_cache = {};
var g_about_cache = {};
let g_about_cache = {};
async function following(db, id) {
if (g_following_cache[id]) {
return g_following_cache[id];
}
var o = await db.get(id + ":following");
const k_version = 5;
var f = o ? JSON.parse(o) : o;
if (!f || f.version != k_version) {
f = {users: [], sequence: 0, version: k_version};
}
f.users = new Set(f.users);
await ssb.sqlAsync(
"SELECT "+
" sequence, "+
" json_extract(content, '$.contact') AS contact, "+
" json_extract(content, '$.following') AS following "+
"FROM messages "+
"WHERE "+
" author = ?1 AND "+
" sequence > ?2 AND "+
" json_extract(content, '$.type') = 'contact' "+
"UNION SELECT MAX(sequence) AS sequence, NULL, NULL FROM messages WHERE author = ?1 "+
"ORDER BY sequence",
[id, f.sequence],
function(row) {
if (row.following) {
f.users.add(row.contact);
} else {
f.users.delete(row.contact);
}
f.sequence = row.sequence;
});
var as_set = f.users;
f.users = Array.from(f.users).sort();
var j = JSON.stringify(f);
if (o != j) {
await db.set(id + ":following", j);
}
f.users = as_set;
g_following_cache[id] = f.users;
return f.users;
async function query(sql, args) {
let result = [];
await ssb.sqlAsync(sql, args, function (row) {
result.push(row);
});
return result;
}
async function followingDeep(db, seed_ids, depth) {
if (depth <= 0) {
return seed_ids;
async function contacts_internal(id, last_row_id, following, max_row_id) {
let result = Object.assign({}, following[id] || {});
result.following = result.following || {};
result.blocking = result.blocking || {};
let contacts = await query(
`
SELECT content FROM messages
WHERE author = ? AND
rowid > ? AND
rowid <= ? AND
json_extract(content, '$.type') = 'contact'
ORDER BY sequence
`,
[id, last_row_id, max_row_id]
);
for (let row of contacts) {
let contact = JSON.parse(row.content);
if (contact.following === true) {
result.following[contact.contact] = true;
} else if (contact.following === false) {
delete result.following[contact.contact];
} else if (contact.blocking === true) {
result.blocking[contact.contact] = true;
} else if (contact.blocking === false) {
delete result.blocking[contact.contact];
}
}
var key = JSON.stringify([seed_ids, depth]);
if (g_following_deep_cache[key]) {
return g_following_deep_cache[key];
following[id] = result;
return result;
}
async function contact(id, last_row_id, following, max_row_id) {
return await contacts_internal(id, last_row_id, following, max_row_id);
}
async function following_deep_internal(
ids,
depth,
blocking,
last_row_id,
following,
max_row_id
) {
let contacts = await Promise.all(
[...new Set(ids)].map((x) => contact(x, last_row_id, following, max_row_id))
);
let result = {};
for (let i = 0; i < ids.length; i++) {
let id = ids[i];
let contact = contacts[i];
let all_blocking = Object.assign({}, contact.blocking, blocking);
let found = Object.keys(contact.following).filter((y) => !all_blocking[y]);
let deeper =
depth > 1
? await following_deep_internal(
found,
depth - 1,
all_blocking,
last_row_id,
following,
max_row_id
)
: [];
result[id] = [id, ...found, ...deeper];
}
var f = await Promise.all(seed_ids.map(x => following(db, x).then(x => [...x])));
var ids = [].concat(...f);
var x = await followingDeep(db, [...new Set(ids)].sort(), depth - 1);
x = [...new Set([].concat(...x, ...seed_ids))].sort();
g_following_deep_cache[key] = x;
return x;
return [...new Set(Object.values(result).flat())];
}
async function following_deep(ids, depth, blocking) {
let db = await database('cache');
const k_cache_version = 5;
let cache = await db.get('following');
cache = cache ? JSON.parse(cache) : {};
if (cache.version !== k_cache_version) {
cache = {
version: k_cache_version,
following: {},
last_row_id: 0,
};
}
let max_row_id = (
await query(
`
SELECT MAX(rowid) AS max_row_id FROM messages
`,
[]
)
)[0].max_row_id;
let result = await following_deep_internal(
ids,
depth,
blocking,
cache.last_row_id,
cache.following,
max_row_id
);
cache.last_row_id = max_row_id;
let store = JSON.stringify(cache);
await db.set('following', store);
return result;
}
async function fetch_about(db, ids, users) {
const k_cache_version = 1;
let cache = await db.get('about');
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 = 0;
await ssb.sqlAsync(
`
SELECT MAX(rowid) AS max_row_id FROM messages
`,
[],
function (row) {
max_row_id = row.max_row_id;
}
);
for (let id of Object.keys(cache.about)) {
if (ids.indexOf(id) == -1) {
delete cache.about[id];
}
}
let abouts = [];
await ssb.sqlAsync(
`
SELECT
messages.*
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.*
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,
]
);
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
);
}
}
cache.last_row_id = max_row_id;
await db.set('about', JSON.stringify(cache));
users = users || {};
for (let id of Object.keys(cache.about)) {
users[id] = Object.assign(users[id] || {}, cache.about[id]);
}
return Object.assign({}, users);
}
async function getAbout(db, id) {
if (g_about_cache[id]) {
return g_about_cache[id];
}
var o = await db.get(id + ":about");
let o = await db.get(id + ':about');
const k_version = 4;
var f = o ? JSON.parse(o) : o;
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",
'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) {
function (row) {
f.sequence = row.sequence;
if (row.content) {
var about = {};
let about = {};
try {
about = JSON.parse(row.content);
} catch {
}
} catch {}
delete about.about;
delete about.type;
f.about = Object.assign(f.about, about);
}
});
var j = JSON.stringify(f);
}
);
let j = JSON.stringify(f);
if (o != j) {
await db.set(id + ":about", j);
await db.set(id + ':about', j);
}
g_about_cache[id] = f.about;
return f.about;
@ -108,14 +236,34 @@ async function getAbout(db, id) {
async function getSize(db, id) {
let size = 0;
await ssb.sqlAsync(
"SELECT (SUM(LENGTH(content)) + SUM(LENGTH(author)) + SUM(LENGTH(id))) AS size FROM messages WHERE author = ?1",
'SELECT (LENGTH(author) * COUNT(*) + SUM(LENGTH(content)) + SUM(LENGTH(id))) AS size FROM messages WHERE author = ?1',
[id],
function (row) {
size += row.size;
});
}
);
return size;
}
async function getSizes(ids) {
let sizes = {};
await ssb.sqlAsync(
`
SELECT
author,
(SUM(LENGTH(content)) + SUM(LENGTH(author)) + SUM(LENGTH(messages.id))) AS size
FROM messages
JOIN json_each(?) AS ids ON author = ids.value
GROUP BY author
`,
[JSON.stringify(ids)],
function (row) {
sizes[row.author] = row.size;
}
);
return sizes;
}
function niceSize(bytes) {
let value = bytes;
let unit = 'B';
@ -131,27 +279,39 @@ function niceSize(bytes) {
return Math.round(value * 10) / 10 + ' ' + unit;
}
async function buildTree(db, root, indent, depth) {
var f = await following(db, root);
var result = indent + '[' + f.size + '] ' + '<a target="_top" href="../index/#' + root + '">' + ((await getAbout(db, root)).name || root) + '</a> ' + niceSize(await getSize(db, root)) + '\n';
if (depth > 0) {
for (let next of f) {
result += await buildTree(db, next, indent + ' ', depth - 1);
}
}
return result;
function escape(value) {
return value
.replaceAll('&', '&amp;')
.replaceAll('<', '&lt;')
.replaceAll('>', '&gt;');
}
async function main() {
await app.setDocument('<pre style="color: #fff">building...</pre>');
var db = await database('ssb');
var whoami = await ssb.getIdentities();
var tree = '';
for (let id of whoami) {
await app.setDocument(`<pre style="color: #fff">building... ${id}</pre>`);
tree += await buildTree(db, id, '', 2);
let db = await database('ssb');
let whoami = await ssb.getIdentities();
let tree = '';
await app.setDocument(
`<pre style="color: #fff">Enumerating followed users...</pre>`
);
let following = await following_deep(whoami, 2, {});
await app.setDocument(
`<pre style="color: #fff">Getting names and sizes...</pre>`
);
let [about, sizes] = await Promise.all([
fetch_about(db, following, {}),
getSizes(following),
]);
await app.setDocument(`<pre style="color: #fff">Finishing...</pre>`);
following.sort((a, b) => (sizes[b] ?? 0) - (sizes[a] ?? 0));
for (let id of following) {
tree += `<li><a href="/~core/ssb/#${id}">${escape(about[id]?.name ?? id)}</a> ${niceSize(sizes[id] ?? 0)}</li>\n`;
}
await app.setDocument('<pre style="color: #fff">FOLLOWING:\n' + tree + '</pre>');
await app.setDocument(
'<!DOCTYPE html>\n<html>\n<body style="color: #fff"><h1>Following</h1>\n<ul>' +
tree +
'</ul>\n</body>\n</html>'
);
}
main();
main();

5
apps/identity.json Normal file
View File

@ -0,0 +1,5 @@
{
"type": "tildefriends-app",
"emoji": "🪪",
"previous": "&zxsmzdLKsiG/WZt/Gw7JOxepgypoktNNbIyWiyFiJVc=.sha256"
}

137
apps/identity/app.js Normal file
View File

@ -0,0 +1,137 @@
import * as tfrpc from '/tfrpc.js';
tfrpc.register(async function get_private_key(id) {
return bip39Words(await ssb.getPrivateKey(id));
});
tfrpc.register(async function create_id(id) {
return await ssb.createIdentity();
});
tfrpc.register(async function add_id(id) {
return await ssb.addIdentity(bip39Bytes(id));
});
tfrpc.register(async function delete_id(id) {
return await ssb.deleteIdentity(id);
});
tfrpc.register(async function reload() {
await main();
});
async function main() {
let ids = await ssb.getIdentities();
let server_id = await ssb.getServerIdentity();
await app.setDocument(
`
<head>
<link rel="stylesheet" href="w3.css"></link>
<style>
/* "2018 Sargasso Sea" */
.w3-theme-l5 {color:#000 !important; background-color:#f3f4f7 !important}
.w3-theme-l4 {color:#000 !important; background-color:#d7dbe3 !important}
.w3-theme-l3 {color:#000 !important; background-color:#b0b6c8 !important}
.w3-theme-l2 {color:#fff !important; background-color:#8892ac !important}
.w3-theme-l1 {color:#fff !important; background-color:#636f8e !important}
.w3-theme-d1 {color:#fff !important; background-color:#40485c !important}
.w3-theme-d2 {color:#fff !important; background-color:#394052 !important}
.w3-theme-d3 {color:#fff !important; background-color:#323848 !important}
.w3-theme-d4 {color:#fff !important; background-color:#2b303d !important}
.w3-theme-d5 {color:#fff !important; background-color:#242833 !important}
.w3-theme-light {color:#000 !important; background-color:#f3f4f7 !important}
.w3-theme-dark {color:#fff !important; background-color:#242833 !important}
.w3-theme-action {color:#fff !important; background-color:#242833 !important}
.w3-theme {color:#fff !important; background-color:#485167 !important}
.w3-text-theme {color:#485167 !important}
.w3-border-theme {border-color:#485167 !important}
.w3-hover-theme:hover {color:#fff !important; background-color:#485167 !important}
.w3-hover-text-theme:hover {color:#485167 !important}
.w3-hover-border-theme:hover {border-color:#485167 !important}
</style>
</head>
<body class="w3-theme-l3">
<script>const handler = {};</script>
<script type="module">
import * as tfrpc from '/static/tfrpc.js';
handler.export_id = async function export_id(event) {
let id = event.srcElement.dataset.id;
let element = document.createElement('textarea');
element.value = await tfrpc.rpc.get_private_key(id);
element.style = 'width: 100%; height: auto; read-only: true; resize: none';
element.classList.add('w3-input');
element.readOnly = true;
event.srcElement.parentElement.appendChild(element);
event.srcElement.onclick = event => handler.hide_id(event, element);
}
handler.add_id = async function add_id(event) {
let id = document.getElementById('add_id').value;
try {
let new_id = await tfrpc.rpc.add_id(id);
alert('Successfully imported: ' + new_id);
await tfrpc.rpc.reload();
} catch (e) {
alert('Error importing identity: ' + e);
}
}
handler.create_id = async function create_id(event) {
try {
let id = await tfrpc.rpc.create_id();
alert('Successfully created: ' + id);
await tfrpc.rpc.reload();
} catch (e) {
alert('Error creating identity: ' + e.message);
}
}
handler.hide_id = function hide_id(event, element) {
element.parentNode.removeChild(element);
event.srcElement.onclick = handler.export_id;
}
handler.delete_id = async function delete_id(event) {
let id = event.srcElement.dataset.id;
try {
if (prompt('Are you sure you want to delete "' + id + '"? It cannot be recovered without the exported phrase.\\n\\nEnter the word "DELETE" to confirm you wish to delete it.') === 'DELETE') {
if (await tfrpc.rpc.delete_id(id)) {
alert('Successfully deleted ID: ' + id);
}
await tfrpc.rpc.reload();
}
} catch (e) {
alert('Error deleting ID: ' + e);
}
}
</script>
<header class="w3-theme w3-padding"><h1>SSB Identity Management</h1></header>
<div class="w3-card-4 w3-margin">
<header class="w3-container w3-theme-l2"><h2>Create a new identity</h2></header>
<footer class="w3-padding">
<button id="create_id" onclick="handler.create_id()" class="w3-button w3-theme">Create Identity</button>
</footer>
</div>
<div class="w3-card-4 w3-margin">
<header class="w3-container w3-theme-l2"><h2>Import an SSB Identity from 12 BIP39 English Words</h2></header>
<textarea id="add_id" style="width: 100%" rows="4" class="w3-input"></textarea>
<footer class="w3-padding">
<button id="add" onclick="handler.add_id(event)" class="w3-button w3-theme">Import Identity</button>
</footer>
</div>
<div class="w3-card-4 w3-margin">
<header class="w3-container w3-theme-l2"><h2>Identities</h2></header>
<ul class="w3-ul">` +
ids
.map(
(
id
) => `<li style="overflow: hidden; text-wrap: nowrap; text-overflow: ellipsis">
<button onclick="handler.export_id(event)" data-id="${id}" class="w3-button w3-theme">Export Identity</button>
<button onclick="handler.delete_id(event)" data-id="${id}" class="w3-button w3-theme">Delete Identity</button>
${id}${id == server_id ? ' <div class="w3-tag w3-theme-l4 w3-round">🖥 local server</div>' : ''}
</li>`
)
.join('\n') +
` </ul>
</div>
</body>`
);
}
main();

235
apps/identity/w3.css Normal file
View File

@ -0,0 +1,235 @@
/* W3.CSS 4.15 December 2020 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-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}
/* 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-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}

5
apps/issues.json Normal file
View File

@ -0,0 +1,5 @@
{
"type": "tildefriends-app",
"emoji": "🦟",
"previous": "&cUqvSDUls3jn0haD85LPFAGdkc8wFuy347TtATNcJgg=.sha256"
}

107
apps/issues/app.js Normal file
View File

@ -0,0 +1,107 @@
import * as tfrpc from '/tfrpc.js';
let g_database;
let g_hash;
tfrpc.register(async function localStorageGet(key) {
return app.localStorageGet(key);
});
tfrpc.register(async function localStorageSet(key, value) {
return app.localStorageSet(key, value);
});
tfrpc.register(async function databaseGet(key) {
return g_database ? g_database.get(key) : undefined;
});
tfrpc.register(async function databaseSet(key, value) {
return g_database ? g_database.set(key, value) : undefined;
});
tfrpc.register(async function createIdentity() {
return ssb.createIdentity();
});
tfrpc.register(async function getIdentities() {
return ssb.getIdentities();
});
tfrpc.register(async function getAllIdentities() {
return ssb.getAllIdentities();
});
tfrpc.register(async function getBroadcasts() {
return ssb.getBroadcasts();
});
tfrpc.register(async function getConnections() {
return ssb.connections();
});
tfrpc.register(async function getStoredConnections() {
return ssb.storedConnections();
});
tfrpc.register(async function forgetStoredConnection(connection) {
return ssb.forgetStoredConnection(connection);
});
tfrpc.register(async function createTunnel(portal, target) {
return ssb.createTunnel(portal, target);
});
tfrpc.register(async function connect(token) {
await ssb.connect(token);
});
tfrpc.register(async function closeConnection(id) {
await ssb.closeConnection(id);
});
tfrpc.register(async function query(sql, args) {
let result = [];
await ssb.sqlAsync(sql, args, function callback(row) {
result.push(row);
});
return result;
});
tfrpc.register(async function appendMessage(id, message) {
return ssb.appendMessageWithIdentity(id, message);
});
core.register('message', async function message_handler(message) {
if (message.event == 'hashChange') {
g_hash = message.hash;
await tfrpc.rpc.hashChanged(message.hash);
}
});
tfrpc.register(function getHash(id, message) {
return g_hash;
});
tfrpc.register(function setHash(hash) {
return app.setHash(hash);
});
tfrpc.register(async function store_blob(blob) {
if (Array.isArray(blob)) {
blob = Uint8Array.from(blob);
}
return await ssb.blobStore(blob);
});
tfrpc.register(async function get_blob(id) {
return utf8Decode(await ssb.blobGet(id));
});
tfrpc.register(async function store_message(message) {
return await ssb.storeMessage(message);
});
tfrpc.register(function apps() {
return core.apps();
});
tfrpc.register(function getActiveIdentity() {
return ssb.getActiveIdentity();
});
tfrpc.register(async function try_decrypt(id, content) {
return await ssb.privateMessageDecrypt(id, content);
});
core.register('onMessage', async function (id) {
await tfrpc.rpc.notifyNewMessage(id);
});
core.register('onBroadcastsChanged', async function () {
await tfrpc.rpc.set('broadcasts', await ssb.getBroadcasts());
});
core.register('onConnectionsChanged', async function () {
await tfrpc.rpc.set('connections', await ssb.connections());
});
async function main() {
if (typeof database !== 'undefined') {
g_database = await database('ssb');
}
await app.setDocument(utf8Decode(await getFile('index.html')));
}
main();

1
apps/issues/commonmark.min.js vendored Normal file

File diff suppressed because one or more lines are too long

16
apps/issues/index.html Normal file
View File

@ -0,0 +1,16 @@
<!doctype html>
<html style="color: #fff">
<head>
<title>Tilde Friends</title>
<base target="_top" />
</head>
<body>
<tf-issues-app />
<script>
window.litDisableBundleWarning = true;
</script>
<script src="commonmark.min.js"></script>
<script src="commonmark-linkify.js" type="module"></script>
<script src="script.js" type="module"></script>
</body>
</html>

120
apps/issues/lit-all.min.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

248
apps/issues/script.js Normal file
View File

@ -0,0 +1,248 @@
import {LitElement, html, unsafeHTML} from './lit-all.min.js';
import * as tfrpc from '/static/tfrpc.js';
import * as tfutils from './tf-utils.js';
const k_project = '%Hr+4xEVtjplidSKBlRWi4Aw/0Tfw7B+1OR9BzlDKmOI=.sha256';
class TfComposeElement extends LitElement {
static get properties() {
return {
value: {type: String},
};
}
input() {
let input = this.renderRoot.getElementById('input');
let preview = this.renderRoot.getElementById('preview');
if (input && preview) {
preview.innerHTML = tfutils.markdown(input.value);
}
}
submit() {
this.dispatchEvent(
new CustomEvent('tf-submit', {
bubbles: true,
composed: true,
detail: {
value: this.renderRoot.getElementById('input').value,
},
})
);
this.renderRoot.getElementById('input').value = '';
this.input();
}
render() {
return html`
<div style="display: flex; flex-direction: row">
<textarea id="input" @input=${this.input} style="flex: 1 1">${this.value}</textarea>
<div id="preview" style="flex: 1 1"></div>
</div>
<input type="submit" value="Submit" @click=${this.submit}></input>
`;
}
}
customElements.define('tf-compose', TfComposeElement);
class TfIssuesAppElement extends LitElement {
static get properties() {
return {
issues: {type: Array},
selected: {type: Object},
};
}
constructor() {
super();
this.issues = [];
this.load();
}
async load() {
let issues = {};
let messages = await tfrpc.rpc.query(
`
WITH issues AS (SELECT messages.id, json(messages.content) AS content, messages.author, messages.timestamp FROM messages_refs JOIN messages ON
messages.id = messages_refs.message
WHERE messages_refs.ref = ? AND json_extract(messages.content, '$.type') = 'issue'),
edits AS (SELECT messages.id, json(messages.content) AS content, messages.author, messages.timestamp FROM issues JOIN messages_refs ON
issues.id = messages_refs.ref JOIN messages ON
messages.id = messages_refs.message
WHERE json_extract(messages.content, '$.type') IN ('issue-edit', 'post'))
SELECT * FROM issues
UNION
SELECT * FROM edits ORDER BY timestamp
`,
[k_project]
);
for (let message of messages) {
let content = JSON.parse(message.content);
switch (content.type) {
case 'issue':
issues[message.id] = {
id: message.id,
author: message.author,
text: content.text,
updates: [],
created: message.timestamp,
open: true,
};
break;
case 'issue-edit':
case 'post':
for (let issue of content.issues || []) {
if (issues[issue.link]) {
if (issue.open !== undefined) {
issues[issue.link].open = issue.open;
message.open = issue.open;
}
issues[issue.link].updates.push(message);
issues[issue.link].updated = message.timestamp;
}
}
break;
}
}
this.issues = Object.values(issues).sort(
(x, y) => y.open - x.open || y.created - x.created
);
if (this.selected) {
for (let issue of this.issues) {
if (issue.id == this.selected.id) {
this.selected = issue;
}
}
}
}
render_issue_table_row(issue) {
return html`
<tr>
<td>${issue.open ? '☐ open' : '☑ closed'}</td>
<td
style="max-width: 8em; overflow: hidden; white-space: nowrap; text-overflow: ellipsis"
>
${issue.author}
</td>
<td
style="max-width: 40em; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; cursor: pointer"
@click=${() => (this.selected = issue)}
>
${issue.text.split('\n')?.[0]}
</td>
<td>
${new Date(issue.updated ?? issue.created).toLocaleDateString()}
</td>
</tr>
`;
}
render_update(update) {
let content = JSON.parse(update.content);
let message;
if (content.text) {
message = unsafeHTML(tfutils.markdown(content.text));
}
return html`
<div style="border-left: 2px solid #fff; padding-left: 8px">
<div>${new Date(update.timestamp).toLocaleString()}</div>
<div>${update.author}</div>
<div>${message}</div>
<div>
${update.open !== undefined
? update.open
? 'issue opened'
: 'issue closed'
: undefined}
</div>
</div>
`;
}
async set_open(id, open) {
if (
confirm(`Are you sure you want to ${open ? 'open' : 'close'} this issue?`)
) {
let whoami = await tfrpc.rpc.getActiveIdentity();
await tfrpc.rpc.appendMessage(whoami, {
type: 'issue-edit',
issues: [
{
link: id,
open: open,
},
],
});
await this.load();
}
}
async create_issue(event) {
let whoami = await tfrpc.rpc.getActiveIdentity();
await tfrpc.rpc.appendMessage(whoami, {
type: 'issue',
project: k_project,
text: event.detail.value,
});
await this.load();
}
async reply_to_issue(event) {
let whoami = await tfrpc.rpc.getActiveIdentity();
await tfrpc.rpc.appendMessage(whoami, {
type: 'post',
text: event.detail.value,
root: this.selected.id,
branch: this.selected.updates.length
? this.selected.updates[this.selected.updates.length - 1].id
: this.selected.id,
issues: [
{
link: this.selected.id,
},
],
});
await this.load();
}
render() {
let header = html` <h1>Tilde Friends Issues</h1> `;
if (this.selected) {
return html`
${header}
<div>
<input type="button" value="Back" @click=${() => (this.selected = undefined)}></input>
${
this.selected.open
? html`<input type="button" value="Close Issue" @click=${() => this.set_open(this.selected.id, false)}></input>`
: html`<input type="button" value="Reopen Issue" @click=${() => this.set_open(this.selected.id, true)}></input>`
}
</div>
<div>${new Date(this.selected.created).toLocaleString()}</div>
<div>${this.selected.author}</div>
<div>${this.selected.id}</div>
<div>${unsafeHTML(tfutils.markdown(this.selected.text))}</div>
${this.selected.updates.map((x) => this.render_update(x))}
<tf-compose @tf-submit=${this.reply_to_issue}></tf-compose>
`;
} else {
return html`
${header}
<h2>New Issue</h2>
<tf-compose @tf-submit=${this.create_issue}></tf-compose>
<table>
<tr>
<th>Status</th>
<th>Author</th>
<th>Title</th>
<th>Date</th>
</tr>
${this.issues.map((x) => this.render_issue_table_row(x))}
</table>
`;
}
}
}
customElements.define('tf-issues-app', TfIssuesAppElement);

113
apps/issues/tf-utils.js Normal file
View File

@ -0,0 +1,113 @@
import * as linkify from './commonmark-linkify.js';
function image(node, entering) {
if (
node.firstChild?.type === 'text' &&
node.firstChild.literal.startsWith('video:')
) {
if (entering) {
this.lit(
'<video style="max-width: 100%; max-height: 480px" title="' +
this.esc(node.firstChild?.literal) +
'" controls>'
);
this.lit('<source src="' + this.esc(node.destination) + '"></source>');
this.disableTags += 1;
} else {
this.disableTags -= 1;
this.lit('</video>');
}
} else if (
node.firstChild?.type === 'text' &&
node.firstChild.literal.startsWith('audio:')
) {
if (entering) {
this.lit(
'<audio style="height: 32px; max-width: 100%" title="' +
this.esc(node.firstChild?.literal) +
'" controls>'
);
this.lit('<source src="' + this.esc(node.destination) + '"></source>');
this.disableTags += 1;
} else {
this.disableTags -= 1;
this.lit('</audio>');
}
} else {
if (entering) {
if (this.disableTags === 0) {
this.lit(
'<div class="img_caption">' +
this.esc(node.firstChild?.literal || node.destination) +
'</div>'
);
if (this.options.safe && potentiallyUnsafe(node.destination)) {
this.lit('<img src="" alt="');
} else {
this.lit('<img src="' + this.esc(node.destination) + '" alt="');
}
}
this.disableTags += 1;
} else {
this.disableTags -= 1;
if (this.disableTags === 0) {
if (node.title) {
this.lit('" title="' + this.esc(node.title));
}
this.lit('" />');
}
}
}
}
export function markdown(md) {
var reader = new commonmark.Parser({safe: true});
var writer = new commonmark.HtmlRenderer();
writer.image = image;
var parsed = reader.parse(md || '');
parsed = linkify.transform(parsed);
var walker = parsed.walker();
var event, node;
while ((event = walker.next())) {
node = event.node;
if (event.entering) {
if (node.type == 'link') {
if (
node.destination.startsWith('@') &&
node.destination.endsWith('.ed25519')
) {
node.destination = '#' + node.destination;
} else if (
node.destination.startsWith('%') &&
node.destination.endsWith('.sha256')
) {
node.destination = '#' + node.destination;
} else if (
node.destination.startsWith('&') &&
node.destination.endsWith('.sha256')
) {
node.destination = '/' + node.destination + '/view';
}
} else if (node.type == 'image') {
if (node.destination.startsWith('&')) {
node.destination = '/' + node.destination + '/view';
}
}
}
}
return writer.render(parsed);
}
export function human_readable_size(bytes) {
let v = bytes;
let u = 'B';
for (let unit of ['kB', 'MB', 'GB']) {
if (v > 1024) {
v /= 1024;
u = unit;
} else {
break;
}
}
return `${Math.round(v * 10) / 10} ${u}`;
}

5
apps/journal.json Normal file
View File

@ -0,0 +1,5 @@
{
"type": "tildefriends-app",
"emoji": "📝",
"previous": "&b//KqE4Vx6kOSBRODK1p/8wjOLKZJ+CBB5IkaBt5YsM=.sha256"
}

185
apps/journal/app.js Normal file
View File

@ -0,0 +1,185 @@
import * as tfrpc from '/tfrpc.js';
let g_hash;
let g_collection_notifies = {};
tfrpc.register(async function getOwnerIdentities() {
return ssb.getOwnerIdentities();
});
tfrpc.register(async function getIdentities() {
return ssb.getIdentities();
});
tfrpc.register(async function query(sql, args) {
let result = [];
await ssb.sqlAsync(sql, args, function callback(row) {
result.push(row);
});
return result;
});
tfrpc.register(async function localStorageGet(key) {
return app.localStorageGet(key);
});
tfrpc.register(async function localStorageSet(key, value) {
return app.localStorageSet(key, value);
});
tfrpc.register(async function following(ids, depth) {
return ssb.following(ids, depth);
});
tfrpc.register(async function appendMessage(id, message) {
return ssb.appendMessageWithIdentity(id, message);
});
tfrpc.register(async function store_blob(blob) {
if (Array.isArray(blob)) {
blob = Uint8Array.from(blob);
}
return await ssb.blobStore(blob);
});
tfrpc.register(async function get_blob(id) {
return utf8Decode(await ssb.blobGet(id));
});
let g_new_message_resolve;
let g_new_message_promise = new Promise(function (resolve, reject) {
g_new_message_resolve = resolve;
});
function new_message() {
return g_new_message_promise;
}
core.register('onMessage', function (id) {
let resolve = g_new_message_resolve;
g_new_message_promise = new Promise(function (resolve, reject) {
g_new_message_resolve = resolve;
});
if (resolve) {
resolve();
}
});
core.register('message', async function message_handler(message) {
if (message.event == 'hashChange') {
print('hash change', message.hash);
g_hash = message.hash;
await tfrpc.rpc.hash_changed(message.hash);
}
});
tfrpc.register(function set_hash(hash) {
if (g_hash != hash) {
return app.setHash(hash);
}
});
tfrpc.register(function get_hash(id, message) {
return g_hash;
});
tfrpc.register(async function try_decrypt(id, content) {
return await ssb.privateMessageDecrypt(id, content);
});
tfrpc.register(async function encrypt(id, recipients, content) {
return await ssb.privateMessageEncrypt(id, recipients, content);
});
async function process_message(whoami, collection, message, kind, parent) {
let content = JSON.parse(message.content);
if (typeof content == 'string') {
let x;
for (let id of whoami) {
x = await ssb.privateMessageDecrypt(id, content);
if (x) {
content = JSON.parse(x);
break;
}
}
if (!x) {
return;
}
if (content.type !== kind || (parent && content.parent !== parent)) {
return;
}
}
if (content?.key) {
if (content?.tombstone) {
delete collection[content.key];
} else {
collection[content.key] = Object.assign(
collection[content.key] || {},
content
);
}
} else {
collection[message.id] = Object.assign(content, {id: message.id});
}
return true;
}
tfrpc.register(async function collection(ids, kind, parent, max_rowid, data) {
let whoami = await ssb.getIdentities();
data = data ?? {};
let rowid = 0;
await ssb.sqlAsync(
'SELECT MAX(rowid) AS rowid FROM messages',
[],
function (row) {
rowid = row.rowid;
}
);
while (true) {
if (rowid == max_rowid) {
await new_message();
await ssb.sqlAsync(
'SELECT MAX(rowid) AS rowid FROM messages',
[],
function (row) {
rowid = row.rowid;
}
);
}
let modified = false;
let rows = [];
await ssb.sqlAsync(
`
SELECT messages.id, author, content, timestamp
FROM messages
JOIN json_each(?1) AS id ON messages.author = id.value
WHERE
messages.rowid > ?2 AND
messages.rowid <= ?3 AND
((json_extract(messages.content, '$.type') = ?4 AND
(?5 IS NULL OR json_extract(messages.content, '$.parent') = ?5)) OR
content LIKE '"%')
`,
[JSON.stringify(ids), max_rowid ?? -1, rowid, kind, parent],
function (row) {
rows.push(row);
}
);
max_rowid = rowid;
for (let row of rows) {
if (await process_message(whoami, data, row, kind, parent)) {
modified = true;
}
}
if (modified) {
break;
}
}
return [rowid, data];
});
async function main() {
await app.setDocument(utf8Decode(await getFile('index.html')));
}
main();

1
apps/journal/commonmark.min.js vendored Normal file

File diff suppressed because one or more lines are too long

16
apps/journal/index.html Normal file
View File

@ -0,0 +1,16 @@
<!doctype html>
<html>
<head>
<base target="_top" />
</head>
<body style="color: #fff">
<tf-journal-app></tf-journal-app>
<script src="commonmark.min.js"></script>
<script>
window.litDisableBundleWarning = true;
</script>
<script src="tf-journal-app.js" type="module"></script>
<script src="tf-journal-entry.js" type="module"></script>
<script src="tf-id-picker.js" type="module"></script>
</body>
</html>

120
apps/journal/lit-all.min.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,43 @@
import {LitElement, html} from './lit-all.min.js';
import * as tfrpc from '/static/tfrpc.js';
/*
** Provide a list of IDs, and this lets the user pick one.
*/
class TfIdentityPickerElement extends LitElement {
static get properties() {
return {
ids: {type: Array},
selected: {type: String},
};
}
constructor() {
super();
this.ids = [];
}
changed(event) {
this.selected = event.srcElement.value;
this.dispatchEvent(
new Event('change', {
srcElement: this,
})
);
}
render() {
return html`
<select @change=${this.changed} style="max-width: 100%">
${(this.ids ?? []).map(
(id) =>
html`<option ?selected=${id == this.selected} value=${id}>
${id}
</option>`
)}
</select>
`;
}
}
customElements.define('tf-id-picker', TfIdentityPickerElement);

View File

@ -0,0 +1,89 @@
import {LitElement, html, keyed, live} from './lit-all.min.js';
import * as tfrpc from '/static/tfrpc.js';
class TfJournalAppElement extends LitElement {
static get properties() {
return {
ids: {type: Array},
owner_ids: {type: Array},
whoami: {type: String},
journals: {type: Object},
};
}
constructor() {
super();
this.ids = [];
this.owner_ids = [];
this.journals = {};
this.load();
}
async load() {
this.ids = await tfrpc.rpc.getIdentities();
this.whoami = await tfrpc.rpc.localStorageGet('journal_whoami');
await this.read_journals();
}
async read_journals() {
let max_rowid;
let journals;
while (true) {
[max_rowid, journals] = await tfrpc.rpc.collection(
[this.whoami],
'journal-entry',
undefined,
max_rowid,
journals
);
this.journals = Object.assign({}, journals);
console.log('JOURNALS', this.journals);
}
}
async on_whoami_changed(event) {
let new_id = event.srcElement.selected;
await tfrpc.rpc.localStorageSet('journal_whoami', new_id);
this.whoami = new_id;
}
async on_journal_publish(event) {
let key = event.detail.key;
let text = event.detail.text;
let message = {
type: 'journal-entry',
key: key,
text: text,
};
message.recps = [this.whoami];
print(message);
message = await tfrpc.rpc.encrypt(
this.whoami,
message.recps,
JSON.stringify(message)
);
print(message);
await tfrpc.rpc.appendMessage(this.whoami, message);
}
render() {
console.log('RENDER APP', this.journals);
let self = this;
return html`
<div>
<tf-id-picker
.ids=${this.ids}
selected=${this.whoami}
@change=${this.on_whoami_changed}
></tf-id-picker>
</div>
<tf-journal-entry
whoami=${this.whoami}
.journals=${this.journals}
@publish=${this.on_journal_publish}
></tf-journal-entry>
`;
}
}
customElements.define('tf-journal-app', TfJournalAppElement);

View File

@ -0,0 +1,97 @@
import {LitElement, html, unsafeHTML, range} from './lit-all.min.js';
import * as tfrpc from '/static/tfrpc.js';
class TfJournalEntryElement extends LitElement {
static get properties() {
return {
whoami: {type: String},
key: {type: String},
journals: {type: Object},
text: {type: String},
};
}
constructor() {
super();
this.journals = {};
this.key = new Date().toISOString().split('T')[0];
}
markdown(md) {
var reader = new commonmark.Parser({safe: true});
var writer = new commonmark.HtmlRenderer();
var parsed = reader.parse(md || '');
return writer.render(parsed);
}
on_discard(event) {
this.text = undefined;
}
async on_publish() {
console.log('publish', this.text);
this.dispatchEvent(
new CustomEvent('publish', {
bubbles: true,
detail: {
key: this.shadowRoot.getElementById('date_picker').value,
text: this.text,
},
})
);
}
back_dates(count) {
let now = new Date();
let result = [];
for (let i = 0; i < count; i++) {
let next = new Date(now);
next.setDate(now.getDate() - i);
result.push(next.toISOString().split('T')[0]);
}
return result;
}
on_edit(event) {
this.text = event.srcElement.value;
}
on_date_change(event) {
this.key = event.srcElement.value;
this.text = this.journals[this.key]?.text;
}
render() {
console.log('RENDER ENTRY', this.key, this.journals?.[this.key]);
return html`
<select id="date_picker" @change=${this.on_date_change}>
${this.back_dates(10).map(
(x) => html` <option value=${x}>${x}</option> `
)}
</select>
<div style="display: inline-flex; flex-direction: row">
<button
?disabled=${this.text == this.journals?.[this.key]?.text}
@click=${this.on_publish}
>
Publish
</button>
<button @click=${this.on_discard}>Discard</button>
</div>
<div style="display: flex; flex-direction: row">
<textarea
style="flex: 1 1; min-height: 10em"
@input=${this.on_edit}
.value=${this.text ?? this.journals?.[this.key]?.text ?? ''}
></textarea>
<div style="flex: 1 1">
${unsafeHTML(
this.markdown(this.text ?? this.journals?.[this.key]?.text)
)}
</div>
</div>
`;
}
}
customElements.define('tf-journal-entry', TfJournalEntryElement);

5
apps/room.json Normal file
View File

@ -0,0 +1,5 @@
{
"type": "tildefriends-app",
"emoji": "🚪",
"previous": "&HXCdDG8gGYXElTyEFbg85jqa6lDXNL2ENPIA9UoJNbI=.sha256"
}

13
apps/room/app.js Normal file
View File

@ -0,0 +1,13 @@
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=`;
await app.setDocument(`
<body style="color: #fff">
<h1>Server</h1>
<div>The local server address is:</div>
<div><input type="text" readonly value="${room}" style="width: 100%"></input></div>
</body>
`);
}
main();

View File

@ -1,4 +1,5 @@
{
"type": "tildefriends-app",
"emoji": "👟"
}
"type": "tildefriends-app",
"emoji": "👟",
"previous": "&lYZRnT2UGQxXxYISbuaZewik9AuxBpcJumakwrePw5c=.sha256"
}

View File

@ -27,4 +27,4 @@ tfrpc.register(async function store_message(message) {
async function main() {
await app.setDocument(utf8Decode(await getFile('index.html')));
}
main();
main();

View File

@ -1,14 +1,16 @@
<!DOCTYPE html>
<!doctype html>
<html style="color: #fff">
<head>
<title>Tilde Friends</title>
<base target="_top">
<base target="_top" />
</head>
<body>
<tf-sneaker-app/>
<script>window.litDisableBundleWarning = true;</script>
<tf-sneaker-app />
<script>
window.litDisableBundleWarning = true;
</script>
<script src="filesaver.min.js"></script>
<script src="jszip.min.js"></script>
<script src="script.js" type="module"></script>
</body>
</html>
</html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -19,7 +19,8 @@ class TfSneakerAppElement extends LitElement {
async search() {
let q = this.renderRoot.getElementById('search').value;
let result = await tfrpc.rpc.query(`
let result = await tfrpc.rpc.query(
`
SELECT messages.author AS id, json_extract(messages.content, '$.name') AS name
FROM messages_fts(?)
JOIN messages ON messages.rowid = messages_fts.rowid
@ -31,15 +32,17 @@ class TfSneakerAppElement extends LitElement {
HAVING MAX(messages.sequence)
ORDER BY COUNT(*) DESC
`,
[`"${q.replaceAll('"', '""')}"`]);
this.feeds = Object.fromEntries(result.map(x => [x.id, x.name]));
[`"${q.replaceAll('"', '""')}"`]
);
this.feeds = Object.fromEntries(result.map((x) => [x.id, x.name]));
}
format_message(message) {
const k_flag_sequence_before_author = 1;
let out = {
previous: message.previous ?? null,
};
if (message.sequence_before_author) {
if (message.flags & k_flag_sequence_before_author) {
out.sequence = message.sequence;
out.author = message.author;
} else {
@ -70,24 +73,104 @@ class TfSneakerAppElement extends LitElement {
return true;
}
if (startsWith(data, [0xff, 0xd8, 0xff, 0xdb]) ||
startsWith(data, [0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46, 0x49, 0x46, 0x00, 0x01]) ||
if (
startsWith(data, [0xff, 0xd8, 0xff, 0xdb]) ||
startsWith(
data,
[0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46, 0x49, 0x46, 0x00, 0x01]
) ||
startsWith(data, [0xff, 0xd8, 0xff, 0xee]) ||
startsWith(data, [0xff, 0xd8, 0xff, 0xe1, null, null, 0x45, 0x78, 0x69, 0x66, 0x00, 0x00])) {
startsWith(data, [
0xff,
0xd8,
0xff,
0xe1,
null,
null,
0x45,
0x78,
0x69,
0x66,
0x00,
0x00,
])
) {
return '.jpg';
} else if (startsWith(data, [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a])) {
} else if (
startsWith(data, [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a])
) {
return '.png';
} else if (startsWith(data, [0x47, 0x49, 0x46, 0x38, 0x37, 0x61]) ||
startsWith(data, [0x47, 0x49, 0x46, 0x38, 0x39, 0x61])) {
} else if (
startsWith(data, [0x47, 0x49, 0x46, 0x38, 0x37, 0x61]) ||
startsWith(data, [0x47, 0x49, 0x46, 0x38, 0x39, 0x61])
) {
return '.gif';
} else if (startsWith(data, [0x52, 0x49, 0x46, 0x46, null, null, null, null, 0x57, 0x45, 0x42, 0x50])) {
} else if (
startsWith(data, [
0x52,
0x49,
0x46,
0x46,
null,
null,
null,
null,
0x57,
0x45,
0x42,
0x50,
])
) {
return '.webp';
} else if (startsWith(data, [0x3c, 0x73, 0x76, 0x67])) {
return '.svg';
} else if (startsWith(data, [null, null, null, null, 0x66, 0x74, 0x79, 0x70, 0x6d, 0x70, 0x34, 0x32])) {
} else if (
startsWith(data, [
null,
null,
null,
null,
0x66,
0x74,
0x79,
0x70,
0x6d,
0x70,
0x34,
0x32,
])
) {
return '.mp3';
} else if (startsWith(data, [null, null, null, null, 0x66, 0x74, 0x79, 0x70, 0x69, 0x73, 0x6f, 0x6d]) ||
startsWith(data, [null, null, null, null, 0x66, 0x74, 0x79, 0x70, 0x6d, 0x70, 0x34, 0x32])) {
} else if (
startsWith(data, [
null,
null,
null,
null,
0x66,
0x74,
0x79,
0x70,
0x69,
0x73,
0x6f,
0x6d,
]) ||
startsWith(data, [
null,
null,
null,
null,
0x66,
0x74,
0x79,
0x70,
0x6d,
0x70,
0x34,
0x32,
])
) {
return '.mp4';
} else {
return '.bin';
@ -98,17 +181,34 @@ class TfSneakerAppElement extends LitElement {
let all_messages = '';
let sequence = -1;
let messages_done = 0;
let messages_max = (await tfrpc.rpc.query('SELECT MAX(sequence) AS total FROM messages WHERE author = ?', [id]))[0].total;
let messages_max = (
await tfrpc.rpc.query(
'SELECT MAX(sequence) AS total FROM messages WHERE author = ?',
[id]
)
)[0].total;
while (true) {
let messages = await tfrpc.rpc.query(
'SELECT * FROM messages WHERE author = ? AND SEQUENCE > ? ORDER BY sequence LIMIT 100',
[id, sequence]
`
SELECT author, id, sequence, timestamp, hash, json(content) AS content, signature, flags
FROM messages
WHERE author = ? AND SEQUENCE > ?
ORDER BY sequence LIMIT 100
`,
[id, sequence]
);
if (messages?.length) {
all_messages += messages.map(x => JSON.stringify(this.format_message(x))).join('\n') + '\n';
all_messages +=
messages
.map((x) => JSON.stringify(this.format_message(x)))
.join('\n') + '\n';
sequence = messages[messages.length - 1].sequence;
messages_done += messages.length;
this.progress = {name: 'messages', value: messages_done, max: messages_max};
this.progress = {
name: 'messages',
value: messages_done,
max: messages_max,
};
} else {
break;
}
@ -118,17 +218,27 @@ class TfSneakerAppElement extends LitElement {
zip.file(`message/classic/${this.sanitize(id)}.ndjson`, all_messages);
let blobs = await tfrpc.rpc.query(
`SELECT blobs.id
`SELECT messages_refs.ref AS id
FROM messages
JOIN messages_refs ON messages.id = messages_refs.message
JOIN blobs ON messages_refs.ref = blobs.id
WHERE messages.author = ?`,
[id]);
WHERE messages.author = ? AND messages_refs.ref LIKE '&%.sha256'`,
[id]
);
let blobs_done = 0;
for (let row of blobs) {
this.progress = {name: 'blobs', value: blobs_done, max: blobs.length};
let blob = await tfrpc.rpc.get_blob(row.id);
zip.file(`blob/classic/${this.sanitize(row.id)}${this.guess_ext(blob)}`, new Uint8Array(blob));
let blob;
try {
blob = await tfrpc.rpc.get_blob(row.id);
} catch (e) {
console.log(`Failed to get ${row.id}: ${e.message}`);
}
if (blob) {
zip.file(
`blob/classic/${this.sanitize(row.id)}${this.guess_ext(blob)}`,
new Uint8Array(blob)
);
}
blobs_done++;
}
@ -155,7 +265,7 @@ class TfSneakerAppElement extends LitElement {
file = await zip.loadAsync(file);
let messages = [];
let blobs = [];
file.forEach(function(path, entry) {
file.forEach(function (path, entry) {
if (!entry.dir) {
if (path.startsWith('message/classic/')) {
messages.push(entry);
@ -175,7 +285,11 @@ class TfSneakerAppElement extends LitElement {
continue;
}
let message = JSON.parse(line);
this.progress = {name: 'messages', value: progress++, max: total_messages};
this.progress = {
name: 'messages',
value: progress++,
max: total_messages,
};
if (await tfrpc.rpc.store_message(message.value)) {
success.messages++;
}
@ -196,7 +310,13 @@ class TfSneakerAppElement extends LitElement {
let progress;
if (this.progress) {
if (this.progress.max) {
progress = html`<div><label for="progress">${this.progress.name}</label><progress value=${this.progress.value} max=${this.progress.max}></progress></div>`;
progress = html`<div>
<label for="progress">${this.progress.name}</label
><progress
value=${this.progress.value}
max=${this.progress.max}
></progress>
</div>`;
} else {
progress = html`<div><span>${this.progress.name}</span></div>`;
}
@ -212,15 +332,19 @@ class TfSneakerAppElement extends LitElement {
<input type="text" id="search" @keypress=${this.keypress}></input>
<input type="button" value="Search Users" @click=${this.search}></input>
<ul>
${Object.entries(this.feeds).map(([id, name]) => html`
<li>
${this.progress ? undefined : html`<input type="button" value="Export" @click=${() => this.export(id)}></input>`}
${name}
<code style="color: #ccc">${id}</code>
</li>
`)}
${Object.entries(this.feeds).map(
([id, name]) => html`
<li>
${this.progress
? undefined
: html`<input type="button" value="Export" @click=${() => this.export(id)}></input>`}
${name}
<code style="color: #ccc">${id}</code>
</li>
`
)}
</ul>
`;
}
}
customElements.define('tf-sneaker-app', TfSneakerAppElement);
customElements.define('tf-sneaker-app', TfSneakerAppElement);

View File

@ -1,4 +1,5 @@
{
"type": "tildefriends-app",
"emoji": "🐌"
}
"type": "tildefriends-app",
"emoji": "🐌",
"previous": "&6oHPQCA26v+4nBXv+YUdCT43j2DpXDspxhHSSRydkiw=.sha256"
}

View File

@ -18,12 +18,21 @@ tfrpc.register(async function databaseSet(key, value) {
tfrpc.register(async function createIdentity() {
return ssb.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();
});
tfrpc.register(async function getAllIdentities() {
return ssb.getAllIdentities();
});
tfrpc.register(async function following(ids, depth) {
return ssb.following(ids, depth);
});
tfrpc.register(async function getBroadcasts() {
return ssb.getBroadcasts();
});
@ -67,7 +76,7 @@ tfrpc.register(function getHash(id, message) {
tfrpc.register(function setHash(hash) {
return app.setHash(hash);
});
ssb.addEventListener('message', async function(id) {
core.register('onMessage', async function (id) {
await tfrpc.rpc.notifyNewMessage(id);
});
tfrpc.register(async function store_blob(blob) {
@ -88,18 +97,27 @@ tfrpc.register(function apps() {
tfrpc.register(async function try_decrypt(id, content) {
return await ssb.privateMessageDecrypt(id, content);
});
ssb.addEventListener('broadcasts', async function() {
tfrpc.register(async function encrypt(id, recipients, content) {
return await ssb.privateMessageEncrypt(id, recipients, content);
});
tfrpc.register(async function getActiveIdentity() {
return await ssb.getActiveIdentity();
});
core.register('onBroadcastsChanged', async function () {
await tfrpc.rpc.set('broadcasts', await ssb.getBroadcasts());
});
core.register('onConnectionsChanged', async function() {
core.register('onConnectionsChanged', async function () {
await tfrpc.rpc.set('connections', await ssb.connections());
});
core.register('setActiveIdentity', async function (id) {
await tfrpc.rpc.set('identity', id);
});
async function main() {
if (typeof(database) !== 'undefined') {
if (typeof database !== 'undefined') {
g_database = await database('ssb');
}
await app.setDocument(utf8Decode(await getFile('index.html')));
}
main();
main();

View File

@ -1,90 +1,94 @@
function textNode(text) {
const node = new commonmark.Node("text", undefined);
node.literal = text;
return node;
const node = new commonmark.Node('text', undefined);
node.literal = text;
return node;
}
function linkNode(text, link) {
const linkNode = new commonmark.Node("link", undefined);
linkNode.destination = `#q=${encodeURIComponent(link)}`;
linkNode.appendChild(textNode(text));
return linkNode;
const linkNode = new commonmark.Node('link', undefined);
if (link.startsWith('#')) {
linkNode.destination = `#q=${encodeURIComponent(link)}`;
} else {
linkNode.destination = link;
}
linkNode.appendChild(textNode(text));
return linkNode;
}
function splitMatches(text, regexp) {
// Regexp must be sticky.
regexp = new RegExp(regexp, "gm");
// Regexp must be sticky.
regexp = new RegExp(regexp, 'gm');
let i = 0;
const result = [];
let i = 0;
const result = [];
let match = regexp.exec(text);
while (match) {
const matchText = match[0];
let match = regexp.exec(text);
while (match) {
const matchText = match[0];
if (match.index > i) {
result.push([text.substring(i, match.index), false]);
}
if (match.index > i) {
result.push([text.substring(i, match.index), false]);
}
result.push([matchText, true]);
i = match.index + matchText.length;
result.push([matchText, true]);
i = match.index + matchText.length;
match = regexp.exec(text);
}
match = regexp.exec(text);
}
if (i < text.length) {
result.push([text.substring(i, text.length), false]);
}
if (i < text.length) {
result.push([text.substring(i, text.length), false]);
}
return result;
return result;
}
const regex = new RegExp("(?<!\w)#[\\w-]+");
const regex = new RegExp('(?:https?://[^ ]+[^ .,])|(?:(?<!\\w)#[\\w-]+)|(?:@[A-Za-z0-9+/]+=.ed25519)|(?:[%&][A-Za-z0-9+/]+=.sha256)');
function split(textNodes) {
const text = textNodes.map(n => n.literal).join("");
const parts = splitMatches(text, regex);
const text = textNodes.map((n) => n.literal).join('');
const parts = splitMatches(text, regex);
return parts.map(part => {
if (part[1]) {
return linkNode(part[0], part[0]);
} else {
return textNode(part[0]);
}
});
return parts.map((part) => {
if (part[1]) {
return linkNode(part[0], part[0]);
} else {
return textNode(part[0]);
}
});
}
export function transform(parsed) {
const walker = parsed.walker();
let event;
const walker = parsed.walker();
let event;
let nodes = [];
while ((event = walker.next())) {
const node = event.node;
if (event.entering && node.type === "text") {
nodes.push(node);
} else {
if (nodes.length > 0) {
split(nodes)
.reverse()
.forEach(newNode => {
nodes[0].insertAfter(newNode);
});
let nodes = [];
while ((event = walker.next())) {
const node = event.node;
if (event.entering && node.type === 'text') {
nodes.push(node);
} else {
if (nodes.length > 0) {
split(nodes)
.reverse()
.forEach((newNode) => {
nodes[0].insertAfter(newNode);
});
nodes.forEach(n => n.unlink());
nodes = [];
}
}
}
nodes.forEach((n) => n.unlink());
nodes = [];
}
}
}
if (nodes.length > 0) {
split(nodes)
.reverse()
.forEach(newNode => {
nodes[0].insertAfter(newNode);
});
nodes.forEach(n => n.unlink());
}
if (nodes.length > 0) {
split(nodes)
.reverse()
.forEach((newNode) => {
nodes[0].insertAfter(newNode);
});
nodes.forEach((n) => n.unlink());
}
return parsed;
return parsed;
}

View File

@ -1,112 +1,164 @@
import * as tfrpc from '/static/tfrpc.js';
import {html, render} from './lit-all.min.js';
import {styles} from './tf-styles.js';
let g_emojis;
function get_emojis() {
if (g_emojis) {
return Promise.resolve(g_emojis);
}
return fetch('emojis.json').then(function(result) {
return fetch('emojis.json').then(function (result) {
g_emojis = result.json();
return g_emojis;
});
}
export function picker(callback, anchor) {
get_emojis().then(function(json) {
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.position = 'absolute';
div.style.minWidth = 'min(16em, 90vw)';
div.style.width = 'min(16em, 90vw)';
div.style.maxWidth = 'min(16em, 90vw)';
div.style.maxHeight = '16em';
div.style.overflow = 'scroll';
div.style.fontWeight = 'bold';
div.style.fontSize = 'xx-large';
let input = document.createElement('input');
input.type = 'text';
input.style.display = 'block';
input.style.boxSizing = 'border-box';
input.style.width = '100%';
input.style.margin = '0';
input.style.position = 'relative';
div.appendChild(input);
let list = document.createElement('div');
div.appendChild(list);
div.addEventListener('mousedown', function(event) {
event.stopPropagation();
});
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);
}
function cleanup() {
console.log('emoji cleanup');
div.parentElement.removeChild(div);
window.removeEventListener('keydown', key_down);
console.log('removing click');
document.body.removeEventListener('mousedown', cleanup);
}
export async function picker(callback, anchor, author) {
let json = await get_emojis();
let recent = await get_recent(author);
function key_down(event) {
if (event.key == 'Escape') {
cleanup();
}
}
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.overflow = 'scroll';
div.style.fontWeight = 'bold';
div.style.fontSize = 'xx-large';
let input = document.createElement('input');
input.type = 'text';
input.style.display = 'block';
input.style.boxSizing = 'border-box';
input.style.width = '100%';
input.style.margin = '0';
input.style.position = 'relative';
div.appendChild(input);
let list = document.createElement('div');
div.appendChild(list);
div.addEventListener('mousedown', function (event) {
event.stopPropagation();
});
function chosen(event) {
console.log(event.srcElement.innerText);
callback(event.srcElement.innerText);
function key_down(event) {
if (event.key == 'Escape') {
cleanup();
}
}
function refresh() {
while (list.firstChild) {
list.removeChild(list.firstChild);
}
let search = input.value;
let any_at_all = false;
for (let row of Object.entries(json)) {
let header = document.createElement('div');
header.appendChild(document.createTextNode(row[0]));
list.appendChild(header);
let any = false;
for (let entry of Object.entries(row[1])) {
if (search &&
search.length &&
entry[0].indexOf(search) == -1) {
continue;
}
let emoji = document.createElement('span');
const k_size = '1.25em';
emoji.style.display = 'inline-block';
emoji.style.overflow = 'hidden';
emoji.style.cursor = 'pointer';
emoji.onclick = chosen;
emoji.title = entry[0];
emoji.appendChild(document.createTextNode(entry[1]));
list.appendChild(emoji);
any = true;
any_at_all = true;
}
if (!any) {
list.removeChild(header);
function chosen(event) {
console.log(event.srcElement.innerText);
callback(event.srcElement.innerText);
cleanup();
}
function refresh() {
while (list.firstChild) {
list.removeChild(list.firstChild);
}
let search = input.value.toLowerCase();
let any_at_all = false;
if (recent) {
let emoji_to_name = {};
for (let row of Object.values(json)) {
for (let entry of Object.entries(row)) {
emoji_to_name[entry[1]] = entry[0];
}
}
if (!any_at_all) {
list.appendChild(document.createTextNode('No matches found.'));
let header = document.createElement('div');
header.appendChild(document.createTextNode('Recent'));
list.appendChild(header);
let any = false;
for (let entry of recent) {
if (
search &&
search.length &&
(emoji_to_name[entry] || '').toLowerCase().indexOf(search) == -1
) {
continue;
}
let emoji = document.createElement('span');
const k_size = '1.25em';
emoji.style.display = 'inline-block';
emoji.style.overflow = 'hidden';
emoji.style.cursor = 'pointer';
emoji.onclick = chosen;
emoji.title = emoji_to_name[entry] || entry;
emoji.appendChild(document.createTextNode(entry));
list.appendChild(emoji);
any = true;
}
if (!any) {
list.removeChild(header);
}
}
refresh();
input.oninput = refresh;
document.body.appendChild(div);
div.style.position = 'fixed';
div.style.top = '50%';
div.style.left = '50%';
div.style.transform = 'translate(-50%, -50%)';
input.focus();
console.log('adding click');
document.body.addEventListener('mousedown', cleanup);
window.addEventListener('keydown', key_down);
});
}
for (let row of Object.entries(json)) {
let header = document.createElement('div');
header.appendChild(document.createTextNode(row[0]));
list.appendChild(header);
let any = false;
for (let entry of Object.entries(row[1])) {
if (
search &&
search.length &&
entry[0].toLowerCase().indexOf(search) == -1
) {
continue;
}
let emoji = document.createElement('span');
const k_size = '1.25em';
emoji.style.display = 'inline-block';
emoji.style.overflow = 'hidden';
emoji.style.cursor = 'pointer';
emoji.onclick = chosen;
emoji.title = entry[0];
emoji.appendChild(document.createTextNode(entry[1]));
list.appendChild(emoji);
any = true;
any_at_all = true;
}
if (!any) {
list.removeChild(header);
}
}
if (!any_at_all) {
list.appendChild(document.createTextNode('No matches found.'));
}
}
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);
}
render(modal, parent);
input.focus();
document.body.addEventListener('mousedown', cleanup);
window.addEventListener('keydown', key_down);
}

File diff suppressed because one or more lines are too long

View File

@ -1,8 +1,8 @@
<!DOCTYPE html>
<html style="color: #fff">
<!doctype html>
<html>
<head>
<title>Tilde Friends</title>
<base target="_top">
<base target="_top" />
<link rel="stylesheet" href="tribute.css" />
<style>
.tribute-container {
@ -10,13 +10,15 @@
}
</style>
</head>
<body>
<tf-app/>
<script>window.litDisableBundleWarning = true;</script>
<body style="margin: 0; padding: 0">
<tf-app></tf-app>
<tf-reactions-modal id="reactions_modal"></tf-reactions-modal>
<script>
window.litDisableBundleWarning = true;
</script>
<script src="filesaver.min.js"></script>
<script src="commonmark.min.js"></script>
<script src="commonmark-linkify.js" type="module"></script>
<script src="commonmark-hashtag.js" type="module"></script>
<script src="script.js" type="module"></script>
</body>
</html>
</html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,16 +1,17 @@
import {LitElement, html} from './lit-all.min.js';
import * as tfrpc from '/static/tfrpc.js';
import * as tf_id_picker from './tf-id-picker.js';
import * as tf_app from './tf-app.js';
import * as tf_message from './tf-message.js';
import * as tf_user from './tf-user.js';
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_tag from './tf-tag.js';
import * as tf_tab_query from './tf-tab-query.js';
import * as tf_tag from './tf-tag.js';

View File

@ -34,9 +34,13 @@ class TfElement extends LitElement {
this.users = {};
this.loaded = false;
this.tags = [];
tfrpc.rpc.getBroadcasts().then(b => { self.broadcasts = b || []; });
tfrpc.rpc.getConnections().then(c => { self.connections = c || []; });
tfrpc.rpc.getHash().then(hash => self.set_hash(hash));
tfrpc.rpc.getBroadcasts().then((b) => {
self.broadcasts = b || [];
});
tfrpc.rpc.getConnections().then((c) => {
self.connections = c || [];
});
tfrpc.rpc.getHash().then((hash) => self.set_hash(hash));
tfrpc.register(function hashChanged(hash) {
self.set_hash(hash);
});
@ -48,13 +52,15 @@ class TfElement extends LitElement {
self.broadcasts = value;
} else if (name === 'connections') {
self.connections = value;
} else if (name === 'identity') {
self.whoami = value;
}
});
this.initial_load();
}
async initial_load() {
let whoami = await tfrpc.rpc.localStorageGet('whoami');
let whoami = await tfrpc.rpc.getActiveIdentity();
let ids = (await tfrpc.rpc.getIdentities()) || [];
this.whoami = whoami ?? (ids.length ? ids[0] : undefined);
this.ids = ids;
@ -68,83 +74,13 @@ class TfElement extends LitElement {
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 contacts_internal(id, last_row_id, following, max_row_id) {
let result = Object.assign({}, following[id] || {});
result.following = result.following || {};
result.blocking = result.blocking || {};
let contacts = await tfrpc.rpc.query(
`
SELECT content FROM messages
WHERE author = ? AND
rowid > ? AND
rowid <= ? AND
json_extract(content, '$.type') = 'contact'
ORDER BY sequence
`,
[id, last_row_id, max_row_id]);
for (let row of contacts) {
let contact = JSON.parse(row.content);
if (contact.following === true) {
result.following[contact.contact] = true;
} else if (contact.following === false) {
delete result.following[contact.contact];
} else if (contact.blocking === true) {
result.blocking[contact.contact] = true;
} else if (contact.blocking === false) {
delete result.blocking[contact.contact];
}
}
following[id] = result;
return result;
}
async contact(id, last_row_id, following, max_row_id) {
return await this.contacts_internal(id, last_row_id, following, max_row_id);
}
async following_deep_internal(ids, depth, blocking, last_row_id, following, max_row_id) {
let contacts = await Promise.all([...new Set(ids)].map(x => this.contact(x, last_row_id, following, max_row_id)));
let result = {};
for (let i = 0; i < ids.length; i++) {
let id = ids[i];
let contact = contacts[i];
let all_blocking = Object.assign({}, contact.blocking, blocking);
let found = Object.keys(contact.following).filter(y => !all_blocking[y]);
let deeper = depth > 1 ? await this.following_deep_internal(found, depth - 1, all_blocking, last_row_id, following, max_row_id) : [];
result[id] = [id, ...found, ...deeper];
}
return [...new Set(Object.values(result).flat())];
}
async following_deep(ids, depth, blocking) {
const k_cache_version = 5;
let cache = await tfrpc.rpc.databaseGet('following');
cache = cache ? JSON.parse(cache) : {};
if (cache.version !== k_cache_version) {
cache = {
version: k_cache_version,
following: {},
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 result = await this.following_deep_internal(ids, depth, blocking, cache.last_row_id, cache.following, max_row_id);
cache.last_row_id = max_row_id;
let store = JSON.stringify(cache);
/* 2023-02-20: Exceeding message size. */
//if (store.length < 512 * 1024) {
await tfrpc.rpc.databaseSet('following', store);
//}
return [result, cache.following];
}
async fetch_about(ids, users) {
const k_cache_version = 1;
let cache = await tfrpc.rpc.databaseGet('about');
@ -156,9 +92,14 @@ class TfElement extends LitElement {
last_row_id: 0,
};
}
let max_row_id = (await tfrpc.rpc.query(`
let max_row_id = (
await tfrpc.rpc.query(
`
SELECT MAX(rowid) AS max_row_id FROM messages
`, []))[0].max_row_id;
`,
[]
)
)[0].max_row_id;
for (let id of Object.keys(cache.about)) {
if (ids.indexOf(id) == -1) {
delete cache.about[id];
@ -168,7 +109,7 @@ class TfElement extends LitElement {
let abouts = await tfrpc.rpc.query(
`
SELECT
messages.*
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
@ -179,7 +120,7 @@ class TfElement extends LitElement {
json_extract(messages.content, '$.type') = 'about'
UNION
SELECT
messages.*
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
@ -190,17 +131,21 @@ class TfElement extends LitElement {
ORDER BY messages.author, messages.sequence
`,
[
JSON.stringify(ids.filter(id => cache.about[id])),
JSON.stringify(ids.filter(id => !cache.about[id])),
JSON.stringify(ids.filter((id) => cache.about[id])),
JSON.stringify(ids.filter((id) => !cache.about[id])),
cache.last_row_id,
max_row_id,
]);
]
);
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);
cache.about[about.author] = Object.assign(
cache.about[about.author] || {},
content
);
}
}
cache.last_row_id = max_row_id;
@ -215,17 +160,16 @@ class TfElement extends LitElement {
async fetch_new_message(id) {
let messages = await tfrpc.rpc.query(
`
SELECT messages.*
SELECT messages.id, previous, author, sequence, timestamp, hash, json(content) AS content, signature
FROM messages
JOIN json_each(?) AS following ON messages.author = following.value
WHERE messages.id = ?
`,
[
JSON.stringify(this.following),
id,
]);
[JSON.stringify(this.following), id]
);
if (messages && messages.length) {
this.unread = [...this.unread, ...messages];
this.unread = this.unread.slice(this.unread.length - 1024);
}
}
@ -242,24 +186,21 @@ class TfElement extends LitElement {
}
async create_identity() {
if (confirm("Are you sure you want to create a new 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];
}
}
}
render_id_picker() {
return html`
<tf-id-picker id="picker" selected=${this.whoami} .ids=${this.ids} @change=${this._handle_whoami_changed}></tf-id-picker>
<button @click=${this.create_identity}>Create Identity</button>
`;
}
async load_recent_tags() {
let start = new Date();
this.tags = await tfrpc.rpc.query(`
this.tags = await tfrpc.rpc.query(
`
WITH
recent AS (SELECT id, content FROM messages
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
@ -271,16 +212,38 @@ class TfElement extends LitElement {
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]);
`,
[new Date() - 7 * 24 * 60 * 60 * 1000]
);
console.log('tags took', (new Date() - start) / 1000.0, 'seconds');
}
async load() {
let whoami = this.whoami;
let tags = this.load_recent_tags();
let [following, users] = await this.following_deep([whoami], 2, {});
users = await this.fetch_about(following.sort(), users);
this.following = following;
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});
}
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'
);
this.following = Object.keys(following);
this.users = users;
await tags;
console.log(`load finished ${whoami} => ${this.whoami}`);
@ -293,19 +256,54 @@ class TfElement extends LitElement {
let users = this.users;
if (this.tab === 'news') {
return html`
<tf-tab-news .following=${this.following} whoami=${this.whoami} .users=${this.users} hash=${this.hash} .unread=${this.unread} @refresh=${() => this.unread = []}></tf-tab-news>
<tf-tab-news
id="tf-tab-news"
.following=${this.following}
whoami=${this.whoami}
.users=${this.users}
hash=${this.hash}
.unread=${this.unread}
@refresh=${() => (this.unread = [])}
?loading=${this.loading}
></tf-tab-news>
`;
} else if (this.tab === 'connections') {
return html`
<tf-tab-connections .users=${this.users} .connections=${this.connections} .broadcasts=${this.broadcasts}></tf-tab-connections>
<tf-tab-connections
.users=${this.users}
.connections=${this.connections}
.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>
<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 .following=${this.following} whoami=${this.whoami} .users=${this.users} query=${this.hash?.startsWith('#q=') ? decodeURIComponent(this.hash.substring(3)) : null}></tf-tab-search>
<tf-tab-search
.following=${this.following}
whoami=${this.whoami}
.users=${this.users}
query=${this.hash?.startsWith('#q=')
? decodeURIComponent(this.hash.substring(3))
: 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>
`;
}
}
@ -318,6 +316,8 @@ class TfElement extends LitElement {
await tfrpc.rpc.setHash('#connections');
} else if (tab === 'mentions') {
await tfrpc.rpc.setHash('#mentions');
} else if (tab === 'query') {
await tfrpc.rpc.setHash('#sql=');
}
}
@ -325,34 +325,65 @@ class TfElement extends LitElement {
let self = this;
if (!this.loading && this.whoami && this.loaded !== this.whoami) {
console.log(`starting loading ${this.whoami} ${this.loaded}`);
this.loading = true;
this.load().finally(function() {
this.load().finally(function () {
self.loading = false;
});
}
const k_tabs = {
'📰': 'news',
'📡': 'connections',
'@': 'mentions',
'🔍': 'search',
'👩‍💻': 'query',
};
let tabs = html`
<div>
<input type="button" class="tab" value="News" ?disabled=${self.tab == 'news'} @click=${() => self.set_tab('news')}></input>
<input type="button" class="tab" value="Connections" ?disabled=${self.tab == 'connections'} @click=${() => self.set_tab('connections')}></input>
<input type="button" class="tab" value="Mentions" ?disabled=${self.tab == 'mentions'} @click=${() => self.set_tab('mentions')}></input>
<input type="button" class="tab" value="Search" ?disabled=${self.tab == 'search'} @click=${() => self.set_tab('search')}></input>
<div class="w3-bar w3-theme-l1">
${Object.entries(k_tabs).map(
([k, v]) => html`
<button
title=${v}
class="w3-bar-item w3-padding w3-hover-theme tab ${self.tab == v
? 'w3-theme-l2'
: 'w3-theme-l1'}"
@click=${() => self.set_tab(v)}
>
${k}
<span class=${self.tab == v ? '' : 'w3-hide-small'}
>${v.charAt(0).toUpperCase() + v.substring(1)}</span
>
</button>
`
)}
</div>
`;
let contents =
!this.loaded ?
this.loading ?
html`<div>Loading...</div>` :
html`<div>Select or create an identity.</div>` :
this.render_tab();
let contents = !this.loaded
? this.loading
? html`<div
class="w3-panel w3-theme-l5 w3-card-4 w3-padding-large w3-round-xlarge"
>
Loading...
</div>
${this.render_tab()}`
: html`<div>Select or create an identity.</div>`
: this.render_tab();
return html`
${this.render_id_picker()}
${tabs}
${this.tags.map(x => html`<tf-tag tag=${x.tag} count=${x.count}></tf-tag>`)}
${contents}
<div
style="width: 100vw; min-height: 100vh; height: 100%"
class="w3-theme-dark"
>
${tabs}
<div style="padding: 8px">
${this.tags.map(
(x) => html`<tf-tag tag=${x.tag} count=${x.count}></tf-tag>`
)}
${contents}
</div>
</div>
`;
}
}
customElements.define('tf-app', TfElement);
customElements.define('tf-app', TfElement);

View File

@ -1,4 +1,4 @@
import {LitElement, html, unsafeHTML} from './lit-all.min.js';
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';
@ -13,6 +13,7 @@ class TfComposeElement extends LitElement {
branch: {type: String},
apps: {type: Object},
drafts: {type: Object},
author: {type: String},
};
}
@ -25,6 +26,7 @@ class TfComposeElement extends LitElement {
this.branch = undefined;
this.apps = undefined;
this.drafts = {};
this.author = undefined;
}
process_text(text) {
@ -58,11 +60,13 @@ class TfComposeElement extends LitElement {
link: link,
};
}
draft.mentions[link].name = name.startsWith('@') ? name.substring(1) : name;
draft.mentions[link].name = name.startsWith('@')
? name.substring(1)
: name;
updated = true;
}
if (updated) {
this.requestUpdate();
setTimeout(() => this.notify(draft), 0);
}
return tfutils.markdown(text);
}
@ -70,36 +74,31 @@ class TfComposeElement extends LitElement {
input(event) {
let edit = this.renderRoot.getElementById('edit');
let preview = this.renderRoot.getElementById('preview');
preview.innerHTML = this.process_text(edit.value);
preview.innerHTML = this.process_text(edit.innerText);
let content_warning = this.renderRoot.getElementById('content_warning');
let content_warning_preview = this.renderRoot.getElementById('content_warning_preview');
if (content_warning && content_warning_preview) {
content_warning_preview.innerText = content_warning.value;
}
let draft = this.get_draft();
draft.text = edit.innerText;
draft.content_warning = content_warning?.value;
setTimeout(() => this.notify(draft), 0);
}
notify(draft) {
this.dispatchEvent(new CustomEvent('tf-draft', {
bubbles: true,
composed: true,
detail: {
id: this.branch,
draft: draft
},
}));
}
change() {
let draft = this.get_draft();
draft.text = this.renderRoot.getElementById('edit')?.value;
draft.content_warning = this.renderRoot.getElementById('content_warning')?.value;
this.notify(draft);
this.dispatchEvent(
new CustomEvent('tf-draft', {
bubbles: true,
composed: true,
detail: {
id: this.branch,
draft: draft,
},
})
);
}
convert_to_format(buffer, type, mime_type) {
return new Promise(function(resolve, reject) {
return new Promise(function (resolve, reject) {
let img = new Image();
img.onload = function() {
img.onload = function () {
let canvas = document.createElement('canvas');
let width_scale = Math.min(img.width, 1024) / img.width;
let height_scale = Math.min(img.height, 1024) / img.height;
@ -109,13 +108,17 @@ class TfComposeElement extends LitElement {
let context = canvas.getContext('2d');
context.drawImage(img, 0, 0, canvas.width, canvas.height);
let data_url = canvas.toDataURL(mime_type);
let result = atob(data_url.split(',')[1]).split('').map(x => x.charCodeAt(0));
let result = atob(data_url.split(',')[1])
.split('')
.map((x) => x.charCodeAt(0));
resolve(result);
};
img.onerror = function(event) {
img.onerror = function (event) {
reject(new Error('Failed to load image.'));
};
let raw = Array.from(new Uint8Array(buffer)).map(b => String.fromCharCode(b)).join('');
let raw = Array.from(new Uint8Array(buffer))
.map((b) => String.fromCharCode(b))
.join('');
let original = `data:${type};base64,${btoa(raw)}`;
img.src = original;
});
@ -131,7 +134,11 @@ class TfComposeElement extends LitElement {
let best_buffer;
let best_type;
for (let format of ['image/png', 'image/jpeg', 'image/webp']) {
let test_buffer = await self.convert_to_format(buffer, file.type, format);
let test_buffer = await self.convert_to_format(
buffer,
file.type,
format
);
if (!best_buffer || test_buffer.length < best_buffer.length) {
best_buffer = test_buffer;
best_type = format;
@ -154,10 +161,9 @@ class TfComposeElement extends LitElement {
size: buffer.length ?? buffer.byteLength,
};
let edit = self.renderRoot.getElementById('edit');
edit.value += `\n![${name}](${id})`;
self.change();
edit.innerText += `\n![${name}](${id})`;
self.input();
} catch(e) {
} catch (e) {
alert(e?.message);
}
}
@ -176,13 +182,13 @@ class TfComposeElement extends LitElement {
}
}
submit() {
async submit() {
let self = this;
let draft = this.get_draft();
let edit = this.renderRoot.getElementById('edit');
let message = {
type: 'post',
text: edit.value,
text: edit.innerText,
};
if (this.root || this.branch) {
message.root = this.root;
@ -195,43 +201,106 @@ class TfComposeElement extends LitElement {
message.contentWarning = draft.content_warning;
}
console.log('Would post:', message);
tfrpc.rpc.appendMessage(this.whoami, message).then(function() {
edit.value = '';
self.change();
if (draft.encrypt_to) {
let to = new Set(draft.encrypt_to);
to.add(this.whoami);
to = [...to];
message.recps = to;
console.log('message is now', message);
message = await tfrpc.rpc.encrypt(
this.whoami,
to,
JSON.stringify(message)
);
console.log('encrypted as', message);
}
try {
await tfrpc.rpc.appendMessage(this.whoami, message);
self.notify(undefined);
self.requestUpdate();
}).catch(function(error) {
} catch (error) {
alert(error.message);
});
}
}
discard() {
let edit = this.renderRoot.getElementById('edit');
edit.value = '';
this.change();
let preview = this.renderRoot.getElementById('preview');
preview.innerHTML = '';
this.notify(undefined);
}
attach() {
let self = this;
let edit = this.renderRoot.getElementById('edit');
let input = document.createElement('input');
input.type = 'file';
input.onchange = function(event) {
input.onchange = function (event) {
let file = event.target.files[0];
self.add_file(file);
};
input.click();
}
async autocomplete(text, callback) {
this.last_autocomplete = text;
let results = [];
try {
let rows = await tfrpc.rpc.query(
`
SELECT json(messages.content) AS content FROM messages_fts(?)
JOIN messages ON messages.rowid = messages_fts.rowid
WHERE json(messages.content) LIKE ?
ORDER BY timestamp DESC LIMIT 10
`,
['"' + text.replace('"', '""') + '"', `%![%${text}%](%)%`]
);
for (let row of rows) {
for (let match of row.content.matchAll(/!\[([^\]]*)\]\((&.*?)\)/g)) {
if (match[1].toLowerCase().indexOf(text.toLowerCase()) != -1) {
results.push({key: match[1], value: match[2]});
}
}
}
} finally {
if (this.last_autocomplete === text) {
callback(results);
}
}
}
firstUpdated() {
let values = Object.entries(this.users).map((x) => ({
key: x[1].name ?? x[0],
value: x[0],
}));
if (this.author) {
values = [].concat(
[
{
key: this.users[this.author]?.name,
value: this.author,
},
],
values
);
}
let tribute = new Tribute({
values: Object.entries(this.users).map(x => ({key: x[1].name, value: x[0]})),
selectTemplate: function(item) {
return `[@${item.original.key}](${item.original.value})`;
},
iframe: this.shadowRoot,
collection: [
{
values: values,
selectTemplate: function (item) {
return item
? `[@${item.original.key}](${item.original.value})`
: undefined;
},
},
{
trigger: '&',
values: this.autocomplete,
selectTemplate: function (item) {
return item
? `![${item.original.key}](${item.original.value})`
: undefined;
},
},
],
});
tribute.attach(this.renderRoot.getElementById('edit'));
}
@ -239,36 +308,59 @@ class TfComposeElement extends LitElement {
updated() {
super.updated();
let edit = this.renderRoot.getElementById('edit');
if (this.last_updated_text !== edit.value) {
if (this.last_updated_text !== edit.innerText) {
let preview = this.renderRoot.getElementById('preview');
preview.innerHTML = this.process_text(edit.value);
this.last_updated_text = edit.value;
preview.innerHTML = this.process_text(edit.innerText);
this.last_updated_text = edit.innerText;
}
let encrypt = this.renderRoot.getElementById('encrypt_to');
if (encrypt) {
let tribute = new Tribute({
iframe: this.shadowRoot,
values: Object.entries(this.users).map((x) => ({
key: x[1].name,
value: x[0],
})),
selectTemplate: function (item) {
return item.original.value;
},
});
tribute.attach(encrypt);
}
}
remove_mention(id) {
let draft = this.get_draft();
delete draft.mentions[id];
this.notify(draft);
this.requestUpdate();
setTimeout(() => this.notify(), 0);
}
render_mention(mention) {
let self = this;
return html`
<div style="display: flex; flex-direction: row">
<div style="align-self: center; margin: 0.5em">
<input type="button" value="🚮" title="Remove ${mention.name} mention" @click=${() => self.remove_mention(mention.link)}></input>
return html` <div style="display: flex; flex-direction: row">
<div style="align-self: center; margin: 0.5em">
<button
class="w3-button w3-theme-d1"
title="Remove ${mention.name} mention"
@click=${() => self.remove_mention(mention.link)}
>
🚮
</button>
</div>
<div style="display: flex; flex-direction: column">
<h3>${mention.name}</h3>
<div style="padding-left: 1em">
${Object.entries(mention)
.filter((x) => x[0] != 'name')
.map(
(x) =>
html`<div>
<span style="font-weight: bold">${x[0]}</span>: ${x[1]}
</div>`
)}
</div>
<div style="display: flex; flex-direction: column">
<h3>${mention.name}</h3>
<div style="padding-left: 1em">
${Object.entries(mention)
.filter(x => x[0] != 'name')
.map(x => html`<div><span style="font-weight: bold">${x[0]}</span>: ${x[1]}</div>`)}
</div>
</div>
</div>`;
</div>
</div>`;
}
render_attach_app() {
@ -301,14 +393,23 @@ class TfComposeElement extends LitElement {
if (this.apps) {
return html`
<div>
<select id="select">
${Object.keys(self.apps).map(app => html`<option value=${app}>${app}</option>`)}
<div class="w3-card-4 w3-margin w3-padding">
<select id="select" class="w3-select w3-theme-d1">
${Object.keys(self.apps).map(
(app) => html`<option value=${app}>${app}</option>`
)}
</select>
<input type="button" value="Attach" @click=${attach_selected_app}></input>
<input type="button" value="Cancel" @click=${() => this.apps = null}></input>
<button class="w3-button w3-theme-d1" @click=${attach_selected_app}>
Attach
</button>
<button
class="w3-button w3-theme-d1"
@click=${() => (this.apps = null)}
>
Cancel
</button>
</div>
`;
`;
}
}
@ -318,9 +419,16 @@ class TfComposeElement extends LitElement {
self.apps = await tfrpc.rpc.apps();
}
if (!this.apps) {
return html`<input type="button" value="Attach App" @click=${attach_app}></input>`;
return html`<button class="w3-button w3-theme-d1" @click=${attach_app}>
Attach App
</button>`;
} else {
return html`<input type="button" value="Discard App" @click=${() => this.apps = null}></input>`;
return html`<button
class="w3-button w3-theme-d1"
@click=${() => (this.apps = null)}
>
Discard App
</button>`;
}
}
@ -336,15 +444,17 @@ class TfComposeElement extends LitElement {
let draft = this.get_draft();
if (draft.content_warning !== undefined) {
return html`
<div>
<input type="checkbox" id="cw" @change=${() => self.set_content_warning(undefined)} checked></input>
<label for="cw">CW</label>
<input type="text" id="content_warning" @input=${this.input} @change=${this.change} value=${draft.content_warning}></input>
<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 {
return html`
<input type="checkbox" id="cw" @change=${() => self.set_content_warning('')}></input>
<input type="checkbox" class="w3-check w3-theme-d1" id="cw" @change=${() => self.set_content_warning('')}></input>
<label for="cw">CW</label>
`;
}
@ -354,31 +464,107 @@ class TfComposeElement extends LitElement {
return this.drafts[this.branch || ''] || {};
}
update_encrypt(event) {
let input = event.srcElement;
let matches = input.value.match(/@.*?\.ed25519/g);
if (matches) {
let draft = this.get_draft();
let to = [...new Set(matches.concat(draft.encrypt_to))];
this.set_encrypt(to);
input.value = '';
}
}
render_encrypt() {
let draft = this.get_draft();
if (draft.encrypt_to === undefined) {
return;
}
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>
<button class="w3-button w3-theme-d1" @click=${() => this.set_encrypt(undefined)}>🚮</button>
</div>
<ul>
${draft.encrypt_to.map(
(x) => html`
<li>
<tf-user id=${x} .users=${this.users}></tf-user>
<input type="button" class="w3-button w3-theme-d1" value="🚮" @click=${() => this.set_encrypt(draft.encrypt_to.filter((id) => id != x))}></input>
</li>`
)}
</ul>
`;
}
set_encrypt(encrypt) {
let draft = this.get_draft();
draft.encrypt_to = encrypt;
this.notify(draft);
this.requestUpdate();
}
render() {
let self = this;
let draft = self.get_draft();
let content_warning =
draft.content_warning !== undefined ?
html`<div id="content_warning_preview" class="content_warning">${draft.content_warning}</div>` :
undefined;
draft.content_warning !== undefined
? html`<div class="w3-panel w3-round-xlarge w3-theme-d2">
<p id="content_warning_preview">${draft.content_warning}</p>
</div>`
: undefined;
let encrypt =
draft.encrypt_to !== undefined
? undefined
: html`<button
class="w3-button w3-theme-d1"
@click=${() => this.set_encrypt([])}
>
🔐
</button>`;
let result = html`
<div style="display: flex; flex-direction: row; width: 100%">
<textarea id="edit" @input=${this.input} @change=${this.change} @paste=${this.paste} style="flex: 1 0 50%">${draft.text}</textarea>
<div style="flex: 1 0 50%">
${content_warning}
<div id="preview"></div>
<div
class="w3-card-4 w3-theme-d4 w3-padding-small"
style="box-sizing: border-box"
>
${this.render_encrypt()}
<div class="w3-container w3-padding-small">
<div class="w3-half">
<span
class="w3-input w3-theme-d1 w3-border"
style="resize: vertical; width: 100%; overflow: hidden; white-space: pre-wrap"
placeholder="Write a post here."
id="edit"
@input=${this.input}
@paste=${this.paste}
contenteditable="plaintext-only"
.innerText=${live(draft.text ?? '')}
></span>
</div>
<div class="w3-half w3-padding">
${content_warning}
<div id="preview"></div>
</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>
</div>
${Object.values(draft.mentions || {}).map(x => self.render_mention(x))}
${this.render_content_warning()}
${this.render_attach_app()}
<input type="button" value="Submit" @click=${this.submit}></input>
<input type="button" value="Attach" @click=${this.attach}></input>
${this.render_attach_app_button()}
<input type="button" value="Discard" @click=${this.discard}></input>
`;
return result;
}
}
customElements.define('tf-compose', TfComposeElement);
customElements.define('tf-compose', TfComposeElement);

View File

@ -1,37 +0,0 @@
import {LitElement, html} from './lit-all.min.js';
import * as tfrpc from '/static/tfrpc.js';
/*
** Provide a list of IDs, and this lets the user pick one.
*/
class TfIdentityPickerElement extends LitElement {
static get properties() {
return {
ids: {type: Array},
selected: {type: String},
};
}
constructor() {
super();
let self = this;
this.ids = [];
}
changed(event) {
this.selected = event.srcElement.value;
this.dispatchEvent(new Event('change', {
srcElement: this,
}));
}
render() {
return html`
<select @change=${this.changed} style="max-width: 100%">
${(this.ids ?? []).map(id => html`<option ?selected=${id == this.selected} value=${id}>${id}</option>`)}
</select>
`;
}
}
customElements.define('tf-id-picker', TfIdentityPickerElement);

View File

@ -1,4 +1,4 @@
import {LitElement, html, unsafeHTML} from './lit-all.min.js';
import {LitElement, html, render, unsafeHTML} from './lit-all.min.js';
import * as tfrpc from '/static/tfrpc.js';
import * as tfutils from './tf-utils.js';
import * as emojis from './emojis.js';
@ -11,10 +11,9 @@ class TfMessageElement extends LitElement {
message: {type: Object},
users: {type: Object},
drafts: {type: Object},
raw: {type: Boolean},
format: {type: String},
blog_data: {type: String},
expanded: {type: Object},
decrypted: {type: Object},
};
}
@ -27,18 +26,38 @@ class TfMessageElement extends LitElement {
this.message = {};
this.users = {};
this.drafts = {};
this.raw = false;
this.format = 'message';
this.expanded = {};
this.decrypted = undefined;
}
show_reply() {
let event = new CustomEvent('tf-draft', {bubbles: true, composed: true, detail: {id: this.message?.id, draft: ''}});
let event = new CustomEvent('tf-draft', {
bubbles: true,
composed: true,
detail: {
id: this.message?.id,
draft: {
encrypt_to: this.message?.decrypted?.recps,
},
},
});
this.dispatchEvent(event);
}
discard_reply() {
this.dispatchEvent(new CustomEvent('tf-draft', {bubbles: true, composed: true, detail: {id: this.id, draft: undefined}}));
this.dispatchEvent(
new CustomEvent('tf-draft', {
bubbles: true,
composed: true,
detail: {id: this.id, draft: undefined},
})
);
}
show_reactions() {
let modal = document.getElementById('reactions_modal');
modal.users = this.users;
modal.votes = this.message?.votes || [];
}
render_votes() {
@ -53,12 +72,21 @@ class TfMessageElement extends LitElement {
return expression;
}
}
return html`<div>${(this.message.votes || []).map(
vote => html`
<span title="${this.users[vote.author]?.name ?? vote.author} ${new Date(vote.timestamp)}">
${normalize_expression(vote.content.vote.expression)}
</span>
`)}</div>`;
if (this.message?.votes?.length) {
return html`<div class="w3-button" @click=${this.show_reactions}>
${(this.message.votes || []).map(
(vote) => html`
<span
title="${this.users[vote.author]?.name ?? vote.author} ${new Date(
vote.timestamp
)}"
>
${normalize_expression(vote.content.vote.expression)}
</span>
`
)}
</div>`;
}
}
render_raw() {
@ -72,30 +100,40 @@ class TfMessageElement extends LitElement {
content: this.message?.content,
signature: this.message?.signature,
};
return html`<div style="white-space: pre-wrap">${JSON.stringify(raw, null, 2)}</div>`;
return html`<div style="white-space: pre-wrap">
${JSON.stringify(raw, null, 2)}
</div>`;
}
vote(emoji) {
let reaction = emoji;
let message = this.message.id;
if (confirm('Are you sure you want to react with ' + reaction + ' to ' + message + '?')) {
tfrpc.rpc.appendMessage(
this.whoami,
{
if (
confirm(
'Are you sure you want to react with ' +
reaction +
' to ' +
message +
'?'
)
) {
tfrpc.rpc
.appendMessage(this.whoami, {
type: 'vote',
vote: {
link: message,
value: 1,
expression: reaction,
},
}).catch(function(error) {
})
.catch(function (error) {
alert(error?.message);
});
}
}
react(event) {
emojis.picker(x => this.vote(x));
emojis.picker((x) => this.vote(x), null, this.whoami);
}
show_image(link) {
@ -129,9 +167,12 @@ class TfMessageElement extends LitElement {
body_click(event) {
if (event.srcElement.tagName == 'IMG') {
this.show_image(event.srcElement.src);
} else if (event.srcElement.tagName == 'DIV' && event.srcElement.classList.contains('img_caption')) {
} else if (
event.srcElement.tagName == 'DIV' &&
event.srcElement.classList.contains('img_caption')
) {
let next = event.srcElement.nextSibling;
if (next.style.display == 'block') {
if (next.style.display != 'none') {
next.style.display = 'none';
} else {
next.style.display = 'block';
@ -140,50 +181,75 @@ class TfMessageElement extends LitElement {
}
render_mention(mention) {
if (!mention?.link || typeof(mention.link) != 'string') {
if (!mention?.link || typeof mention.link != 'string') {
return html` <pre>${JSON.stringify(mention)}</pre>`;
} else if (mention?.link?.startsWith('&') &&
mention?.type?.startsWith('image/')) {
} else if (
mention?.link?.startsWith('&') &&
mention?.type?.startsWith('image/')
) {
return html`
<img src=${'/' + mention.link + '/view'} style="max-width: 128px; max-height: 128px" title=${mention.name} @click=${() => this.show_image('/' + mention.link + '/view')}>
<img
src=${'/' + mention.link + '/view'}
style="max-width: 128px; max-height: 128px"
title=${mention.name}
@click=${() => this.show_image('/' + mention.link + '/view')}
/>
`;
} else if (mention.link?.startsWith('&') &&
mention.name?.startsWith('audio:')) {
} else if (
mention.link?.startsWith('&') &&
mention.name?.startsWith('audio:')
) {
return html`
<audio controls style="height: 32px">
<source src=${'/' + mention.link + '/view'}></source>
</audio>
`;
} else if (mention.link?.startsWith('&') &&
mention.name?.startsWith('video:')) {
} else if (
mention.link?.startsWith('&') &&
mention.name?.startsWith('video:')
) {
return html`
<video controls style="max-height: 240px; max-width: 128px">
<source src=${'/' + mention.link + '/view'}></source>
</video>
`;
} else if (mention.link?.startsWith('&') &&
mention?.type === 'application/tildefriends') {
} else if (
mention.link?.startsWith('&') &&
mention?.type === 'application/tildefriends'
) {
return html` <a href=${`/${mention.link}/`}>😎 ${mention.name}</a>`;
} else if (mention.link?.startsWith('%') || mention.link?.startsWith('@')) {
return html` <a href=${'#' + encodeURIComponent(mention.link)}>${mention.name}</a>`;
return html` <a href=${'#' + encodeURIComponent(mention.link)}
>${mention.name}</a
>`;
} else if (mention.link?.startsWith('#')) {
return html` <a href=${'#q=' + encodeURIComponent(mention.link)}>${mention.link}</a>`;
} else if (Object.keys(mention).length == 2 && mention.link && mention.name) {
return html` <a href=${'#q=' + encodeURIComponent(mention.link)}
>${mention.link}</a
>`;
} else if (
Object.keys(mention).length == 2 &&
mention.link &&
mention.name
) {
return html` <a href=${`/${mention.link}/view`}>${mention.name}</a>`;
} else {
return html` <pre style="white-space: pre-wrap">${JSON.stringify(mention, null, 2)}</pre>`;
return html` <pre style="white-space: pre-wrap">
${JSON.stringify(mention, null, 2)}</pre
>`;
}
}
render_mentions() {
let mentions = this.message?.content?.mentions || [];
mentions = mentions.filter(x => this.message?.content?.text?.indexOf(x.link) === -1);
mentions = mentions.filter(
(x) => this.message?.content?.text?.indexOf(x.link) === -1
);
if (mentions.length) {
let self = this;
return html`
<fieldset style="background-color: rgba(0, 0, 0, 0.1); padding: 0.5em; border: 1px solid black">
<fieldset style="padding: 0.5em; border: 1px solid black">
<legend>Mentions</legend>
${mentions.map(x => self.render_mention(x))}
${mentions.map((x) => self.render_mention(x))}
</fieldset>
`;
}
@ -194,36 +260,63 @@ class TfMessageElement extends LitElement {
return 0;
}
let total = message.child_messages.length;
for (let m of message.child_messages)
{
for (let m of message.child_messages) {
total += this.total_child_messages(m);
}
return total;
}
set_expanded(expanded, tag) {
this.dispatchEvent(new CustomEvent('tf-expand', {bubbles: true, composed: true, detail: {id: (this.message.id || '') + (tag || ''), expanded: expanded}}));
this.dispatchEvent(
new CustomEvent('tf-expand', {
bubbles: true,
composed: true,
detail: {id: (this.message.id || '') + (tag || ''), expanded: expanded},
})
);
}
toggle_expanded(tag) {
this.set_expanded(!this.expanded[(this.message.id || '') + (tag || '')], tag);
this.set_expanded(
!this.expanded[(this.message.id || '') + (tag || '')],
tag
);
}
render_children() {
let self = this;
if (this.message.child_messages?.length) {
if (!this.expanded[this.message.id]) {
return html`<input type="button" value=${this.total_child_messages(this.message) + ' More'} @click=${() => self.set_expanded(true)}></input>`;
return html`<button
class="w3-button w3-theme-d1"
@click=${() => self.set_expanded(true)}
>
+ ${this.total_child_messages(this.message) + ' More'}
</button>`;
} else {
return html`<input type="button" value="Collapse" @click=${() => self.set_expanded(false)}></input>${(this.message.child_messages || []).map(x => html`<tf-message .message=${x} whoami=${this.whoami} .users=${this.users} .drafts=${this.drafts} .expanded=${this.expanded}></tf-message>`)}`;
return html`<button
class="w3-button w3-theme-d1"
@click=${() => self.set_expanded(false)}
>
Collapse</button
>${(this.message.child_messages || []).map(
(x) =>
html`<tf-message
.message=${x}
whoami=${this.whoami}
.users=${this.users}
.drafts=${this.drafts}
.expanded=${this.expanded}
></tf-message>`
)}`;
}
}
}
render_channels() {
let content = this.message?.content;
if (this.decrypted?.type == 'post') {
content = this.decrypted;
if (this?.messsage?.decrypted?.type == 'post') {
content = this.message.decrypted;
}
let channels = [];
if (typeof content.channel === 'string') {
@ -231,61 +324,142 @@ class TfMessageElement extends LitElement {
}
if (Array.isArray(content.mentions)) {
for (let mention of content.mentions) {
if (typeof mention?.link === 'string' &&
mention.link.startsWith('#')) {
if (typeof mention?.link === 'string' && mention.link.startsWith('#')) {
channels.push(mention.link);
}
}
}
return channels.map(x => html`<tf-tag tag=${x}></tf-tag>`);
}
async try_decrypt(content) {
let result = await tfrpc.rpc.try_decrypt(this.whoami, content);
if (result) {
this.decrypted = JSON.parse(result);
} else {
this.decrypted = false;
}
return channels.map((x) => html`<tf-tag tag=${x}></tf-tag>`);
}
render() {
let content = this.message?.content;
if (this.decrypted?.type == 'post') {
content = this.decrypted;
if (this.message?.decrypted?.type == 'post') {
content = this.message.decrypted;
}
let class_background = this.message?.decrypted
? 'w3-pale-red'
: 'w3-theme-d4';
let self = this;
let raw_button = this.raw ?
html`<input type="button" value="Message" @click=${() => self.raw = false}></input>` :
html`<input type="button" value="Raw" @click=${() => self.raw = true}></input>`;
let raw_button;
switch (this.format) {
case 'raw':
if (content?.type == 'post' || content?.type == 'blog') {
raw_button = html`<button
class="w3-button w3-theme-d1"
@click=${() => (self.format = 'md')}
>
Markdown
</button>`;
} else {
raw_button = html`<button
class="w3-button w3-theme-d1"
@click=${() => (self.format = 'message')}
>
Message
</button>`;
}
break;
case 'md':
raw_button = html`<button
class="w3-button w3-theme-d1"
@click=${() => (self.format = 'message')}
>
Message
</button>`;
break;
case 'decrypted':
raw_button = html`<button
class="w3-button w3-theme-d1"
@click=${() => (self.format = 'raw')}
>
Raw
</button>`;
break;
default:
if (this.message.decrypted) {
raw_button = html`<button
class="w3-button w3-theme-d1"
@click=${() => (self.format = 'decrypted')}
>
Decrypted
</button>`;
} else {
raw_button = html`<button
class="w3-button w3-theme-d1"
@click=${() => (self.format = 'raw')}
>
Raw
</button>`;
}
break;
}
function small_frame(inner) {
let body;
return html`
<div style="border: 1px solid black; background-color: rgba(255, 255, 255, 0.1); margin-top: 8px; padding: 16px; display: inline-block; overflow-wrap: anywhere">
<div
class="w3-card-4 w3-theme-d4 w3-border-theme"
style="margin-top: 8px; padding: 16px; display: inline-block; overflow-wrap: anywhere"
>
<tf-user id=${self.message.author} .users=${self.users}></tf-user>
<span style="padding-right: 8px"><a tfarget="_top" href=${'#' + self.message.id}>%</a> ${new Date(self.message.timestamp).toLocaleString()}</span>
${raw_button}
${self.raw ? self.render_raw() : inner}
<span style="padding-right: 8px"
><a tfarget="_top" href=${'#' + self.message.id}>%</a> ${new Date(
self.message.timestamp
).toLocaleString()}</span
>
${raw_button} ${self.format == 'raw' ? self.render_raw() : inner}
${self.render_votes()}
${(self.message.child_messages || []).map(
(x) => html`
<tf-message
.message=${x}
whoami=${self.whoami}
.users=${self.users}
.drafts=${self.drafts}
.expanded=${self.expanded}
></tf-message>
`
)}
</div>
`;
}
if (this.message?.type === 'contact_group') {
return html`
<div style="border: 1px solid black; background-color: rgba(255, 255, 255, 0.1); margin-top: 8px; padding: 16px; overflow-wrap: anywhere">
${this.message.messages.map(x =>
html`<tf-message .message=${x} whoami=${this.whoami} .users=${this.users} .drafts=${this.drafts} .expanded=${this.expanded}></tf-message>`
)}
</div>`;
return html` <div
class="w3-card-4 w3-theme-d4 w3-border-theme"
style="margin-top: 8px; padding: 16px; overflow-wrap: anywhere"
>
${this.message.messages.map(
(x) =>
html`<tf-message
.message=${x}
whoami=${this.whoami}
.users=${this.users}
.drafts=${this.drafts}
.expanded=${this.expanded}
></tf-message>`
)}
</div>`;
} else if (this.message.placeholder) {
return html`
<div style="border: 1px solid black; background-color: rgba(255, 255, 255, 0.1); margin-top: 8px; padding: 16px; overflow-wrap: anywhere">
<a target="_top" href=${'#' + this.message.id}>${this.message.id}</a> (placeholder)
<div>${this.render_votes()}</div>
${(this.message.child_messages || []).map(x => html`
<tf-message .message=${x} whoami=${this.whoami} .users=${this.users} .drafts=${this.drafts} .expanded=${this.expanded}></tf-message>
`)}
</div>`;
} else if (typeof(content?.type === 'string')) {
return html` <div
class="w3-card-4 w3-theme-d4 w3-border-theme"
style="margin-top: 8px; padding: 16px; overflow-wrap: anywhere"
>
<a target="_top" href=${'#' + this.message.id}>${this.message.id}</a>
(placeholder)
<div>${this.render_votes()}</div>
${(this.message.child_messages || []).map(
(x) => html`
<tf-message
.message=${x}
whoami=${this.whoami}
.users=${this.users}
.drafts=${this.drafts}
.expanded=${this.expanded}
></tf-message>
`
)}
</div>`;
} else if (typeof (content?.type === 'string')) {
if (content.type == 'about') {
let name;
let image;
@ -295,7 +469,7 @@ class TfMessageElement extends LitElement {
}
if (content.image !== undefined) {
image = html`
<div><img src=${'/' + (typeof(content.image?.link) == 'string' ? content.image.link : content.image) + '/view'} style="width: 256px; height: auto"></img></div>
<div><img src=${'/' + (typeof content.image?.link == 'string' ? content.image.link : content.image) + '/view'} style="width: 256px; height: auto"></img></div>
`;
}
if (content.description !== undefined) {
@ -305,66 +479,98 @@ class TfMessageElement extends LitElement {
</div>
`;
}
let update = content.about == this.message.author ?
html`<div style="font-weight: bold">Updated profile.</div>` :
html`<div style="font-weight: bold">Updated profile for <tf-user id=${content.about} .users=${this.users}></tf-user>.</div>`;
return small_frame(html`
${update}
${name}
${image}
${description}
`);
let update =
content.about == this.message.author
? html`<div style="font-weight: bold">Updated profile.</div>`
: html`<div style="font-weight: bold">
Updated profile for
<tf-user id=${content.about} .users=${this.users}></tf-user>.
</div>`;
return small_frame(html` ${update} ${name} ${image} ${description} `);
} else if (content.type == 'contact') {
return html`
<div>
<tf-user id=${this.message.author} .users=${this.users}></tf-user>
is
${
content.blocking === true ? 'blocking' :
content.blocking === false ? 'no longer blocking' :
content.following === true ? 'following' :
content.following === false ? 'no longer following' :
'?'
}
<tf-user id=${this.message.content.contact} .users=${this.users}></tf-user>
${content.blocking === true
? 'blocking'
: content.blocking === false
? 'no longer blocking'
: content.following === true
? 'following'
: content.following === false
? 'no longer following'
: '?'}
<tf-user
id=${this.message.content.contact}
.users=${this.users}
></tf-user>
</div>
`;
} else if (content.type == 'post') {
let reply = (this.drafts[this.message?.id] !== undefined) ? html`
<tf-compose
whoami=${this.whoami}
.users=${this.users}
root=${this.message.content.root || this.message.id}
branch=${this.message.id}
.drafts=${this.drafts}
@tf-discard=${this.discard_reply}></tf-compose>
` : html`
<input type="button" value="Reply" @click=${this.show_reply}></input>
`;
let reply =
this.drafts[this.message?.id] !== undefined
? html`
<tf-compose
whoami=${this.whoami}
.users=${this.users}
root=${content.root || this.message.id}
branch=${this.message.id}
.drafts=${this.drafts}
@tf-discard=${this.discard_reply}
author=${this.message.author}
></tf-compose>
`
: html`
<button class="w3-button w3-theme-d1" @click=${this.show_reply}>
Reply
</button>
`;
let self = this;
let body = this.raw ?
this.render_raw() :
unsafeHTML(tfutils.markdown(content.text));
let body;
switch (this.format) {
case 'raw':
body = this.render_raw();
break;
case 'md':
body = html`<code
style="white-space: pre-wrap; overflow-wrap: anywhere"
>${content.text}</code
>`;
break;
case 'message':
body = unsafeHTML(tfutils.markdown(content.text));
break;
case 'decrypted':
body = html`<pre
style="white-space: pre-wrap; overflow-wrap: anywhere"
>
${JSON.stringify(content, null, 2)}</pre
>`;
break;
}
let content_warning = html`
<div class="content_warning" @click=${x => this.toggle_expanded(':cw')}>${content.contentWarning}</div>
`;
let content_html =
html`
${this.render_channels()}
<div @click=${this.body_click}>${body}</div>
${this.render_mentions()}
`;
let payload =
content.contentWarning ?
self.expanded[(this.message.id || '') + ':cw'] ?
html`
${content_warning}
${content_html}
` :
content_warning :
content_html;
let is_encrypted = this.decrypted ? html`<span style="align-self: center">🔓</span>` : undefined;
let style_background = this.decrypted ? 'rgba(255, 0, 0, 0.2)' : 'rgba(255, 255, 255, 0.1)';
<div
class="w3-panel w3-round-xlarge w3-theme-l4"
style="cursor: pointer"
@click=${(x) => this.toggle_expanded(':cw')}
>
<p>${content.contentWarning}</p>
</div>
`;
let content_html = html`
${this.render_channels()}
<div @click=${this.body_click}>${body}</div>
${this.render_mentions()}
`;
let payload = content.contentWarning
? self.expanded[(this.message.id || '') + ':cw']
? html` ${content_warning} ${content_html} `
: content_warning
: content_html;
let is_encrypted = this.message?.decrypted
? html`<span style="align-self: center">🔓</span>`
: undefined;
return html`
<style>
code {
@ -380,38 +586,97 @@ class TfMessageElement extends LitElement {
display: block;
}
</style>
<div style="border: 1px solid black; background-color: ${style_background}; margin-top: 8px; padding: 16px">
<div
class="w3-card-4 ${class_background} w3-border-theme"
style="margin-top: 8px; padding: 16px"
>
<div style="display: flex; flex-direction: row">
<tf-user id=${this.message.author} .users=${this.users}></tf-user>
${is_encrypted}
<span style="flex: 1"></span>
<span style="padding-right: 8px"><a target="_top" href=${'#' + self.message.id}>%</a> ${new Date(this.message.timestamp).toLocaleString()}</span>
<span style="padding-right: 8px"
><a target="_top" href=${'#' + self.message.id}>%</a>
${new Date(this.message.timestamp).toLocaleString()}</span
>
<span>${raw_button}</span>
</div>
${payload}
${this.render_votes()}
<div>
${payload} ${this.render_votes()}
<p>
${reply}
<input type="button" value="React" @click=${this.react}></input>
<button class="w3-button w3-theme-d1" @click=${this.react}>
React
</button>
</p>
${this.render_children()}
</div>
`;
} else if (content.type === 'issue') {
let is_encrypted = this.message?.decrypted
? html`<span style="align-self: center">🔓</span>`
: undefined;
return html`
<style>
code {
white-space: pre-wrap;
overflow-wrap: break-word;
}
div {
overflow-wrap: anywhere;
}
img {
max-width: 100%;
height: auto;
display: block;
}
</style>
<div
class="w3-card-4 ${class_background} w3-border-theme"
style="margin-top: 8px; padding: 16px"
>
<div style="display: flex; flex-direction: row">
<tf-user id=${this.message.author} .users=${this.users}></tf-user>
${is_encrypted}
<span style="flex: 1"></span>
<span style="padding-right: 8px"
><a target="_top" href=${'#' + self.message.id}>%</a>
${new Date(this.message.timestamp).toLocaleString()}</span
>
<span>${raw_button}</span>
</div>
${content.text} ${this.render_votes()}
<p>
<button class="w3-button w3-theme-d1" @click=${this.react}>
React
</button>
</p>
${this.render_children()}
</div>
`;
} else if (content.type === 'blog') {
let self = this;
tfrpc.rpc.get_blob(content.blog).then(function(data) {
tfrpc.rpc.get_blob(content.blog).then(function (data) {
self.blog_data = data;
});
let payload =
this.expanded[(this.message.id || '') + ':blog'] ?
html`<div>${this.blog_data ? unsafeHTML(tfutils.markdown(this.blog_data)) : 'Loading...'}</div>` :
undefined;
let body = this.raw ?
this.render_raw() :
html`
let payload = this.expanded[(this.message.id || '') + ':blog']
? html`<div>
${this.blog_data
? unsafeHTML(tfutils.markdown(this.blog_data))
: 'Loading...'}
</div>`
: undefined;
let body;
switch (this.format) {
case 'raw':
body = this.render_raw();
break;
case 'md':
body = content.summary;
break;
case 'message':
body = html`
<div
style="border: 1px solid #fff; border-radius: 1em; padding: 8px; margin: 4px; cursor: pointer"
@click=${x => self.toggle_expanded(':blog')}>
@click=${(x) => self.toggle_expanded(':blog')}>
<h2>${content.title}</h2>
<div style="display: flex; flex-direction: row">
<img src=/${content.thumbnail}/view></img>
@ -420,6 +685,26 @@ class TfMessageElement extends LitElement {
</div>
${payload}
`;
break;
}
let reply =
this.drafts[this.message?.id] !== undefined
? html`
<tf-compose
whoami=${this.whoami}
.users=${this.users}
root=${content.root || this.message.id}
branch=${this.message.id}
.drafts=${this.drafts}
@tf-discard=${this.discard_reply}
author=${this.message.author}
></tf-compose>
`
: html`
<button class="w3-button w3-theme-d1" @click=${this.show_reply}>
Reply
</button>
`;
return html`
<style>
code {
@ -435,46 +720,73 @@ class TfMessageElement extends LitElement {
display: block;
}
</style>
<div style="border: 1px solid black; background-color: rgba(255, 255, 255, 0.1); margin-top: 8px; padding: 16px">
<div
class="w3-card-4 w3-theme-d4 w3-border-theme"
style="margin-top: 8px; padding: 16px"
>
<div style="display: flex; flex-direction: row">
<tf-user id=${this.message.author} .users=${this.users}></tf-user>
<span style="flex: 1"></span>
<span style="padding-right: 8px"><a target="_top" href=${'#' + self.message.id}>%</a> ${new Date(this.message.timestamp).toLocaleString()}</span>
<span style="padding-right: 8px"
><a target="_top" href=${'#' + self.message.id}>%</a>
${new Date(this.message.timestamp).toLocaleString()}</span
>
<span>${raw_button}</span>
</div>
<div>${body}</div>
${this.render_mentions()}
${this.render_votes()}
<div>
${reply}
<button class="w3-button w3-theme-d1" @click=${this.react}>
React
</button>
</div>
${this.render_votes()} ${this.render_children()}
</div>
`;
} else if (content.type === 'pub') {
return small_frame(html`
<style>
span {
overflow-wrap: anywhere;
}
</style>
<span>
<div>
🍻 <tf-user .users=${this.users} id=${content.address.key}></tf-user>
</div>
<pre>${content.address.host}:${content.address.port}</pre>
</span>`);
return small_frame(
html` <style>
span {
overflow-wrap: anywhere;
}
</style>
<span>
<div>
🍻
<tf-user
.users=${this.users}
id=${content.address.key}
></tf-user>
</div>
<pre>${content.address.host}:${content.address.port}</pre>
</span>`
);
} else if (content.type === 'channel') {
return small_frame(html`
<div>
${content.subscribed ? 'subscribed to' : 'unsubscribed from'} <a href=${'#q=' + encodeURIComponent('#' + content.channel)}>#${content.channel}</a>
${content.subscribed ? 'subscribed to' : 'unsubscribed from'}
<a href=${'#q=' + encodeURIComponent('#' + content.channel)}
>#${content.channel}</a
>
</div>
`);
} else if (typeof(this.message.content) == 'string') {
if (this.decrypted) {
return small_frame(html`<span>🔓</span><pre>${JSON.stringify(this.decrypted, null, 2)}</pre>`);
} else if (this.decrypted === undefined) {
this.try_decrypt(content);
return small_frame(html`<span>🔐</span>`);
} else if (typeof this.message.content == 'string') {
if (this.message?.decrypted) {
if (this.format == 'decrypted') {
return small_frame(
html`<span>🔓</span>
<pre>${JSON.stringify(this.message.decrypted, null, 2)}</pre>`
);
} else {
return small_frame(
html`<span>🔓</span>
<div>${this.message.decrypted.type}</div>`
);
}
} else {
return small_frame(html`<span>🔒</span>`);
return small_frame(html`<span>🔒</span>`);
}
} else {
return small_frame(html`<div><b>type</b>: ${content.type}</div>`);
@ -485,4 +797,4 @@ class TfMessageElement extends LitElement {
}
}
customElements.define('tf-message', TfMessageElement);
customElements.define('tf-message', TfMessageElement);

View File

@ -61,7 +61,7 @@ class TfNewsElement extends LitElement {
message.parent_message = message.content.vote.link;
} else if (message.content.type == 'post') {
if (message.content.root) {
if (typeof(message.content.root) === 'string') {
if (typeof message.content.root === 'string') {
let m = ensure_message(message.content.root);
if (!m.child_messages) {
m.child_messages = [];
@ -89,8 +89,7 @@ class TfNewsElement extends LitElement {
for (let message of messages) {
try {
message.content = JSON.parse(message.content);
} catch {
}
} catch {}
if (!messages_by_id[message.id]) {
messages_by_id[message.id] = message;
link_message(message);
@ -100,8 +99,12 @@ class TfNewsElement extends LitElement {
message.parent_message = placeholder.parent_message;
message.child_messages = placeholder.child_messages;
message.votes = placeholder.votes;
if (placeholder.parent_message && messages_by_id[placeholder.parent_message]) {
let children = messages_by_id[placeholder.parent_message].child_messages;
if (
placeholder.parent_message &&
messages_by_id[placeholder.parent_message]
) {
let children =
messages_by_id[placeholder.parent_message].child_messages;
children.splice(children.indexOf(placeholder), 1);
children.push(message);
}
@ -116,7 +119,10 @@ class TfNewsElement extends LitElement {
let latest = 0;
for (let message of messages || []) {
if (message.latest_subtree_timestamp === undefined) {
message.latest_subtree_timestamp = Math.max(message.timestamp ?? 0, this.update_latest_subtree_timestamp(message.child_messages));
message.latest_subtree_timestamp = Math.max(
message.timestamp ?? 0,
this.update_latest_subtree_timestamp(message.child_messages)
);
}
latest = Math.max(latest, message.latest_subtree_timestamp);
}
@ -127,20 +133,22 @@ class TfNewsElement extends LitElement {
function recursive_sort(messages, top) {
if (messages) {
if (top) {
messages.sort((a, b) => b.latest_subtree_timestamp - a.latest_subtree_timestamp);
messages.sort(
(a, b) => b.latest_subtree_timestamp - a.latest_subtree_timestamp
);
} else {
messages.sort((a, b) => a.timestamp - b.timestamp);
}
for (let message of messages) {
recursive_sort(message.child_messages, false);
}
return messages.map(x => Object.assign({}, x));
return messages.map((x) => Object.assign({}, x));
} else {
return {};
}
}
let roots = Object.values(messages_by_id).filter(x => !x.parent_message);
let roots = Object.values(messages_by_id).filter((x) => !x.parent_message);
this.update_latest_subtree_timestamp(roots);
return recursive_sort(roots, true);
}
@ -167,10 +175,22 @@ class TfNewsElement extends LitElement {
load_and_render(messages) {
let messages_by_id = this.process_messages(messages);
let final_messages = this.group_following(this.finalize_messages(messages_by_id));
let final_messages = this.group_following(
this.finalize_messages(messages_by_id)
);
return html`
<div style="display: flex; flex-direction: column">
${final_messages.map(x => html`<tf-message .message=${x} whoami=${this.whoami} .users=${this.users} .drafts=${this.drafts} .expanded=${this.expanded} collapsed=true></tf-message>`)}
${final_messages.map(
(x) =>
html`<tf-message
.message=${x}
whoami=${this.whoami}
.users=${this.users}
.drafts=${this.drafts}
.expanded=${this.expanded}
collapsed="true"
></tf-message>`
)}
</div>
`;
}
@ -180,4 +200,4 @@ class TfNewsElement extends LitElement {
}
}
customElements.define('tf-news', TfNewsElement);
customElements.define('tf-news', TfNewsElement);

View File

@ -11,6 +11,9 @@ class TfProfileElement extends LitElement {
id: {type: String},
users: {type: Object},
size: {type: Number},
server_follows_me: {type: Boolean},
following: {type: Boolean},
blocking: {type: Boolean},
};
}
@ -24,14 +27,75 @@ class TfProfileElement extends LitElement {
this.id = null;
this.users = {};
this.size = 0;
this.server_follows_me = undefined;
}
async load() {
if (this.whoami !== this._follow_whoami) {
this._follow_whoami = this.whoami;
this.following = undefined;
this.blocking = undefined;
let result = 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') = ? AND
following IS NOT NULL
ORDER BY sequence DESC LIMIT 1
`,
[this.whoami, this.id]
);
this.following = result?.[0]?.following ?? false;
result = await tfrpc.rpc.query(
`
SELECT json_extract(content, '$.blocking') AS blocking
FROM messages WHERE author = ? AND
json_extract(content, '$.type') = 'contact' AND
json_extract(content, '$.contact') = ? AND
blocking IS NOT NULL
ORDER BY sequence DESC LIMIT 1
`,
[this.whoami, this.id]
);
this.blocking = result?.[0]?.blocking ?? false;
}
}
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) {
tfrpc.rpc.appendMessage(this.whoami,
Object.assign({
type: 'contact',
contact: this.id,
}, change)).catch(function(error) {
tfrpc.rpc
.appendMessage(
this.whoami,
Object.assign(
{
type: 'contact',
contact: this.id,
},
change
)
)
.catch(function (error) {
alert(error?.message);
});
}
@ -58,6 +122,7 @@ class TfProfileElement extends LitElement {
name: original.name,
description: original.description,
image: original.image,
publicWebHosting: original.publicWebHosting,
};
console.log(this.editing);
}
@ -73,11 +138,14 @@ class TfProfileElement extends LitElement {
message[key] = this.editing[key];
}
}
tfrpc.rpc.appendMessage(this.whoami, message).then(function() {
self.editing = null;
}).catch(function(error) {
alert(error?.message);
});
tfrpc.rpc
.appendMessage(this.whoami, message)
.then(function () {
self.editing = null;
})
.catch(function (error) {
alert(error?.message);
});
}
discard_edits() {
@ -88,27 +156,59 @@ class TfProfileElement extends LitElement {
let self = this;
let input = document.createElement('input');
input.type = 'file';
input.onchange = function(event) {
input.onchange = function (event) {
let file = event.target.files[0];
file.arrayBuffer().then(function(buffer) {
let bin = Array.from(new Uint8Array(buffer));
return tfrpc.rpc.store_blob(bin);
}).then(function(id) {
self.editing = Object.assign({}, self.editing, {image: id});
console.log(self.editing);
}).catch(function(e) {
alert(e.message);
});
file
.arrayBuffer()
.then(function (buffer) {
let bin = Array.from(new Uint8Array(buffer));
return tfrpc.rpc.store_blob(bin);
})
.then(function (id) {
self.editing = Object.assign({}, self.editing, {image: id});
console.log(self.editing);
})
.catch(function (e) {
alert(e.message);
});
};
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();
}
this.load();
let self = this;
let profile = this.users[this.id] || {};
tfrpc.rpc.query(
`SELECT SUM(LENGTH(content)) AS size FROM messages WHERE author = ?`,
[this.id]).then(function(result) {
tfrpc.rpc
.query(
`SELECT SUM(LENGTH(content)) AS size FROM messages WHERE author = ?`,
[this.id]
)
.then(function (result) {
self.size = result[0].size;
});
let edit;
@ -116,50 +216,90 @@ class TfProfileElement extends LitElement {
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`
<input type="button" value="Save Profile" @click=${this.save_edits}></input>
<input type="button" value="Discard" @click=${this.discard_edits}></input>
<button 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`<input type="button" value="Edit Profile" @click=${this.edit}></input>`;
edit = html`<button class="w3-button w3-theme-d1" @click=${this.edit}>
Edit Profile
</button>`;
}
}
if (this.id !== this.whoami &&
this.users[this.whoami]?.following) {
follow =
this.users[this.whoami].following[this.id] ?
html`<input type="button" value="Unfollow" @click=${this.unfollow}></input>` :
html`<input type="button" value="Follow" @click=${this.follow}></input>`;
if (this.id !== this.whoami && this.following !== undefined) {
follow = this.following
? html`<button class="w3-button w3-theme-d1" @click=${this.unfollow}>
Unfollow
</button>`
: html`<button class="w3-button w3-theme-d1" @click=${this.follow}>
Follow
</button>`;
}
if (this.id !== this.whoami &&
this.users[this.whoami]?.blocking) {
block =
this.users[this.whoami].blocking[this.id] ?
html`<input type="button" value="Unblock" @click=${this.unblock}></input>` :
html`<input type="button" value="Block" @click=${this.block}></input>`;
if (this.id !== this.whoami && this.blocking !== undefined) {
block = this.blocking
? html`<button class="w3-button w3-theme-d1" @click=${this.unblock}>
Unblock
</button>`
: html`<button class="w3-button w3-theme-d1" @click=${this.block}>
Block
</button>`;
}
let edit_profile = this.editing ? html`
<div style="flex: 1 0 50%">
<div>
<label for="name">Name:</label>
<input type="text" id="name" value=${this.editing.name} @input=${event => this.editing = Object.assign({}, this.editing, {name: event.srcElement.value})}></input>
</div>
<div>
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 id="description" @input=${event => this.editing = Object.assign({}, this.editing, {description: event.srcElement.value})}>${this.editing.description}</textarea>
<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>
<div>
<label for="public_web_hosting">Public Web Hosting:</label>
<input type="checkbox" id="public_web_hosting" value=${this.editing.public_web_hosting} @input=${event => this.editing = Object.assign({}, this.editing, {publicWebHosting: event.srcElement.checked})}></input>
</div>
<input type="button" value="Attach Image" @click=${this.attach_image}></input>
</div>` : null;
let image = typeof(profile.image) == 'string' ? profile.image : profile.image?.link;
</div>`
: null;
let image =
typeof profile.image == 'string' ? profile.image : profile.image?.link;
image = this.editing?.image ?? image;
let description = this.editing?.description ?? profile.description;
return html`<div style="border: 2px solid black; background-color: rgba(255, 255, 255, 0.2); padding: 16px">
<tf-user id=${this.id} .users=${this.users}></tf-user> (${tfutils.human_readable_size(this.size)})
<div style="display: flex; flex-direction: row">
<div class="w3-row">
<div class="w3-col s1 w3-container w3-right">
<button class="w3-button w3-theme-d1 w3-ripple" @click=${this.copy_id}>Copy</button>
</div>
<div class="w3-rest w3-container">
<input type="text" class="w3-theme-d1" style="width: 100%; vertical-align: middle" readonly value=${this.id}></input>
</div>
</div>
<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>
@ -167,10 +307,10 @@ class TfProfileElement extends LitElement {
</div>
</div>
<div>
Following ${Object.keys(profile.following || {}).length} identities.
Followed by ${Object.values(self.users).filter(x => (x.following || {})[self.id]).length} identities.
Blocking ${Object.keys(profile.blocking || {}).length} identities.
Blocked by ${Object.values(self.users).filter(x => (x.blocking || {})[self.id]).length} identities.
Following ${profile.following} identities.
Followed by ${profile.followed} identities.
Blocking ${profile.blocking} identities.
Blocked by ${profile.blocked} identities.
</div>
<div>
${edit}
@ -181,4 +321,4 @@ class TfProfileElement extends LitElement {
}
}
customElements.define('tf-profile', TfProfileElement);
customElements.define('tf-profile', TfProfileElement);

View File

@ -0,0 +1,68 @@
import {LitElement, html, unsafeHTML} from './lit-all.min.js';
import {styles} from './tf-styles.js';
class TfReactionsModalElement extends LitElement {
static get properties() {
return {
users: {type: Object},
votes: {type: Array},
};
}
static styles = styles;
constructor() {
super();
this.votes = [];
this.users = {};
}
clear() {
this.votes = [];
}
render() {
let self = this;
return this.votes?.length
? html` <div
class="w3-modal w3-animate-opacity"
style="display: block; box-sizing: border-box"
>
<div class="w3-modal-content w3-card-4 w3-theme-d1">
<div class="w3-container w3-padding">
<header class="w3-container">
<h2>Reactions</h2>
<span class="w3-button w3-display-topright" @click=${this.clear}
>&times;</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>
</div>
</div>`
: undefined;
}
}
customElements.define('tf-reactions-modal', TfReactionsModalElement);

View File

@ -1,48 +1,314 @@
import {css} from './lit-all.min.js';
export let styles = css`
a:link {
color: #bbf;
}
const tf = css`
img {
max-width: min(640px, 100%);
max-height: min(480px, auto);
}
a:visited {
color: #ddd;
}
.tab {
border: 0;
padding: 8px;
margin: 0px;
cursor: pointer;
}
a:hover {
color: #ddf;
}
.tab:disabled {
color: #088;
background-color: #fff;
}
img {
max-width: min(640px, 100%);
max-height: min(480px, auto);
}
.content_warning {
border: 1px solid #fff;
border-radius: 1em;
padding: 8px;
margin: 4px;
}
.tab {
border: 0;
padding: 8px;
margin: 0px;
cursor: pointer;
}
div.img_caption {
color: #888;
cursor: pointer;
}
.tab:disabled {
color: #088;
background-color: #fff;
}
div.img_caption::after {
content: ' ±';
}
.content_warning {
border: 1px solid #fff;
border-radius: 1em;
padding: 8px;
margin: 4px;
}
pre code {
display: block;
padding: 8px;
}
div.img_caption {
color: #888;
cursor: pointer;
}
blockquote {
border-left: 4px solid #fff;
padding: 8px;
padding-left: 12px;
}
`;
div.img_caption::after {
content: ' ±';
}
`;
// prettier-ignore
const w3 = css`
/* W3.CSS 4.15 December 2020 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-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}
/* 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-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}
`;
// 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}
.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}
.w3-theme {color:#fff !important; background-color:#4c6a92 !important}
.w3-text-theme {color:#4c6a92 !important}
.w3-border-theme {border-color:#4c6a92 !important}
.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}
`;
export let styles = [tf, w3, w3_2016_riverside];

View File

@ -1,38 +1,58 @@
import {LitElement, html} from './lit-all.min.js';
import * as tfrpc from '/static/tfrpc.js';
import {styles} from './tf-styles.js';
class TfTabConnectionsElement extends LitElement {
static get properties() {
return {
broadcasts: {type: Array},
identities: {type: Array},
my_identities: {type: Array},
connections: {type: Array},
stored_connections: {type: Array},
users: {type: Object},
server_identity: {type: String},
};
}
static styles = styles;
static k_broadcast_emojis = {
discovery: '🏓',
room: '🚪',
peer_exchange: '🕸',
};
constructor() {
super();
let self = this;
this.broadcasts = [];
this.identities = [];
this.my_identities = [];
this.connections = [];
this.stored_connections = [];
this.users = {};
tfrpc.rpc.getAllIdentities().then(function(identities) {
tfrpc.rpc.getIdentities().then(function (identities) {
self.my_identities = identities || [];
});
tfrpc.rpc.getAllIdentities().then(function (identities) {
self.identities = identities || [];
});
tfrpc.rpc.getStoredConnections().then(function(connections) {
tfrpc.rpc.getStoredConnections().then(function (connections) {
self.stored_connections = connections || [];
});
tfrpc.rpc.getServerIdentity().then(function (identity) {
self.server_identity = identity;
});
}
render_connection_summary(connection) {
if (connection.address && connection.port) {
return html`(<small>${connection.address}:${connection.port}</small>)`;
return html`<div>
<small>${connection.address}:${connection.port}</small>
</div>`;
} else if (connection.tunnel) {
return html`(room peer)`;
return html`<div>room peer</div>`;
} else {
return JSON.stringify(connection);
}
@ -40,13 +60,12 @@ class TfTabConnectionsElement extends LitElement {
render_room_peers(connection) {
let self = this;
let peers = this.broadcasts.filter(x => x.tunnel?.id == connection);
let peers = this.broadcasts.filter((x) => x.tunnel?.id == connection);
if (peers.length) {
return html`
<ul>
${peers.map(x => html`${self.render_room_peer(x)}`)}
</ul>
`;
let connections = this.connections.map((x) => x.id);
return html`${peers
.filter((x) => connections.indexOf(x.pubkey) == -1)
.map((x) => html`${self.render_room_peer(x)}`)}`;
}
}
@ -58,18 +77,31 @@ class TfTabConnectionsElement extends LitElement {
let self = this;
return html`
<li>
<input type="button" @click=${() => self._tunnel(connection.tunnel.id, connection.pubkey)} value="Connect"></input>
<tf-user id=${connection.pubkey} .users=${this.users}></tf-user>
<button
class="w3-button w3-theme-d1"
@click=${() => self._tunnel(connection.tunnel.id, connection.pubkey)}
>
Connect
</button>
<tf-user id=${connection.pubkey} .users=${this.users}></tf-user> 📡
</li>
`;
}
render_broadcast(connection) {
return html`
<li>
<input type="button" @click=${() => tfrpc.rpc.connect(connection)} value="Connect"></input>
<tf-user id=${connection.pubkey} .users=${this.users}></tf-user>
${this.render_connection_summary(connection)}
<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)}
</div>
</li>
`;
}
@ -79,44 +111,127 @@ class TfTabConnectionsElement extends LitElement {
this.stored_connections = (await tfrpc.rpc.getStoredConnections()) || [];
}
render_connection(connection) {
let requests = Object.values(
connection.requests.reduce(function (accumulator, value) {
let key = `${value.name}:${Math.sign(value.request_number)}`;
if (!accumulator[key]) {
accumulator[key] = Object.assign({count: 0}, value);
}
accumulator[key].count++;
return accumulator;
}, {})
);
return html`
<button
class="w3-button w3-theme-d1"
@click=${() => tfrpc.rpc.closeConnection(connection.id)}
>
Close
</button>
<tf-user id=${connection.id} .users=${this.users}></tf-user>
${connection.tunnel !== undefined
? '🚇'
: html`(${connection.host}:${connection.port})`}
<div>
${requests.map(
(x) => html`
<span class="w3-tag w3-small"
>${x.request_number > 0 ? '🟩' : '🟥'} ${x.name}
<span
class="w3-badge w3-white"
style=${x.count > 1 ? undefined : 'display: none'}
>${x.count}</span
></span
>
`
)}
</div>
<ul>
${this.connections
.filter((x) => x.tunnel === this.connections.indexOf(connection))
.map((x) => html`<li>${this.render_connection(x)}</li>`)}
${this.render_room_peers(connection.id)}
</ul>
`;
}
render() {
let self = this;
return html`
<h2>New Connection</h2>
<div style="display: flex; flex-direction: column">
<textarea id="code"></textarea>
<div class="w3-container" style="box-sizing: border-box">
<h2>New Connection</h2>
<textarea class="w3-input w3-theme-d1" id="code"></textarea>
<button
class="w3-button w3-theme-d1"
@click=${() =>
tfrpc.rpc.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))}
</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>
`
)}
</ul>
<h2>Stored Connections</h2>
<ul class="w3-ul w3-border">
${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>
</div>
</li>
`
)}
</ul>
<h2>Local Accounts</h2>
<ul class="w3-ul w3-border">
${this.identities.map(
(x) =>
html`<li class="w3-bar">
${x == this.server_identity
? html`<span class="w3-tag w3-medium w3-round w3-theme-l1"
>🖥 local server</span
>`
: undefined}
${this.my_identities.indexOf(x) != -1
? html`<span class="w3-tag w3-medium w3-round w3-theme-d1"
>😎 you</span
>`
: undefined}
<tf-user id=${x} .users=${this.users}></tf-user>
</li>`
)}
</ul>
</div>
<input type="button" @click=${() => tfrpc.rpc.connect(self.renderRoot.getElementById('code').value)} value="Connect"></input>
<h2>Broadcasts</h2>
<ul>
${this.broadcasts.filter(x => x.address).map(x => self.render_broadcast(x))}
</ul>
<h2>Connections</h2>
<ul>
${this.connections.map(x => html`
<li>
<input type="button" @click=${() => tfrpc.rpc.closeConnection(x)} value="Close"></input>
<tf-user id=${x} .users=${this.users}></tf-user>
${self.render_room_peers(x)}
</li>
`)}
</ul>
<h2>Stored Connections (WIP)</h2>
<ul>
${this.stored_connections.map(x => html`
<li>
<input type="button" @click=${() => self.forget_stored_connection(x)} value="Forget"></input>
<input type="button" @click=${() => tfrpc.rpc.connect(x)} value="Connect"></input>
${x.address}:${x.port} <tf-user id=${x.pubkey} .users=${self.users}></tf-user>
</li>
`)}
</ul>
<h2>Local Accounts</h2>
<ul>
${this.identities.map(x => html`<li><tf-user id=${x} .users=${this.users}></tf-user></li>`)}
</ul>
`;
}
}
customElements.define('tf-tab-connections', TfTabConnectionsElement);
customElements.define('tf-tab-connections', TfTabConnectionsElement);

View File

@ -27,15 +27,21 @@ class TfTabMentionsElement extends LitElement {
async load() {
console.log('Loading...', this.whoami);
let results = await tfrpc.rpc.query(`
SELECT messages.*
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]);
[
'"' + this.whoami.replace('"', '""') + '"',
JSON.stringify(this.following),
this.whoami,
]
);
console.log('Done.');
this.messages = results;
}
@ -58,8 +64,15 @@ class TfTabMentionsElement extends LitElement {
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>
<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);
customElements.define('tf-tab-mentions', TfTabMentionsElement);

View File

@ -33,61 +33,72 @@ class TfTabNewsFeedElement extends LitElement {
if (this.hash.startsWith('#@')) {
let r = await tfrpc.rpc.query(
`
WITH mine AS (SELECT messages.*
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.*
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
JOIN messages ON messages_refs.message = messages.id
UNION
SELECT * FROM mine
`,
[
this.hash.substring(1),
]);
[this.hash.substring(1)]
);
return r;
} else if (this.hash.startsWith('#%')) {
return await tfrpc.rpc.query(
`
SELECT messages.*
SELECT id, previous, author, sequence, timestamp, hash, json(content) AS content, signature
FROM messages
WHERE id = ?1
UNION
SELECT messages.*
SELECT 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),
]);
[this.hash.substring(1)]
);
} else {
return await tfrpc.rpc.query(
`
WITH news AS (SELECT messages.*
FROM messages
JOIN json_each(?) AS following ON messages.author = following.value
WHERE messages.timestamp > ?
ORDER BY messages.timestamp DESC)
SELECT messages.*
FROM news
JOIN messages_refs ON news.id = messages_refs.ref
JOIN messages ON messages_refs.message = messages.id
UNION
SELECT messages.*
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,
]);
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
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)));
}
}
@ -96,59 +107,105 @@ class TfTabNewsFeedElement extends LitElement {
this.start_time = last_start_time - 24 * 60 * 60 * 1000;
let more = await tfrpc.rpc.query(
`
WITH news AS (SELECT messages.*
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.*
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.*
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 = [...more, ...this.messages];
[JSON.stringify(this.following), this.start_time, last_start_time]
);
this.messages = await this.decrypt([...more, ...this.messages]);
}
async decrypt(messages) {
console.log('decrypt');
let result = [];
for (let message of messages) {
let content;
try {
content = JSON.parse(message?.content);
} catch {}
if (typeof content === 'string') {
let decrypted;
try {
decrypted = await tfrpc.rpc.try_decrypt(this.whoami, content);
} catch {}
if (decrypted) {
try {
message.decrypted = JSON.parse(decrypted);
} catch {
message.decrypted = decrypted;
}
}
}
result.push(message);
}
return result;
}
async add_messages(messages) {
this.messages = await this.decrypt([...messages, ...this.messages]);
}
render() {
if (!this.messages ||
if (
!this.messages ||
this._messages_hash !== this.hash ||
this._messages_following !== this.following) {
console.log(`loading messages for ${this.whoami}`);
this._messages_following !== this.following
) {
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(function(messages) {
self.messages = messages;
console.log(`loading mesages done for ${self.whoami}`);
}).catch(function(error) {
alert(JSON.stringify(error, null, 2));
});
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));
});
}
let more;
if (!this.hash.startsWith('#@') && !this.hash.startsWith('#%')) {
more = html`
<input type="button" value="Load More" @click=${this.load_more}></input>
<p>
<button class="w3-button w3-theme-d1" @click=${this.load_more}>
Load More
</button>
</p>
`;
}
return html`
<tf-news id="news" whoami=${this.whoami} .users=${this.users} .messages=${this.messages} .following=${this.following} .drafts=${this.drafts} .expanded=${this.expanded}></tf-news>
<tf-news
id="news"
whoami=${this.whoami}
.users=${this.users}
.messages=${this.messages}
.following=${this.following}
.drafts=${this.drafts}
.expanded=${this.expanded}
></tf-news>
${more}
`;
}
}
customElements.define('tf-tab-news-feed', TfTabNewsFeedElement);
customElements.define('tf-tab-news-feed', TfTabNewsFeedElement);

View File

@ -12,6 +12,7 @@ class TfTabNewsElement extends LitElement {
following: {type: Array},
drafts: {type: Object},
expanded: {type: Object},
loading: {type: Boolean},
};
}
@ -28,7 +29,7 @@ class TfTabNewsElement extends LitElement {
this.cache = {};
this.drafts = {};
this.expanded = {};
tfrpc.rpc.localStorageGet('drafts').then(function(d) {
tfrpc.rpc.localStorageGet('drafts').then(function (d) {
self.drafts = JSON.parse(d || '{}');
});
}
@ -48,7 +49,9 @@ class TfTabNewsElement extends LitElement {
let news = this.shadowRoot?.getElementById('news');
if (news) {
console.log('injecting messages', news.messages);
news.messages = Object.values(Object.fromEntries([...this.unread, ...news.messages].map(x => [x.id, x])));
news.add_messages(
Object.values(Object.fromEntries(this.unread.map((x) => [x.id, x])))
);
this.dispatchEvent(new CustomEvent('refresh'));
}
}
@ -62,11 +65,16 @@ class TfTabNewsElement extends LitElement {
let type = 'private';
try {
type = JSON.parse(message.content).type || type;
} catch {
}
} catch {}
counts[type] = (counts[type] || 0) + 1;
}
return 'Show New: ' + Object.keys(counts).sort().map(x => (counts[x].toString() + ' ' + x + 's')).join(', ');
return (
'↻ Show New: ' +
Object.keys(counts)
.sort()
.map((x) => counts[x].toString() + ' ' + x + 's')
.join(', ')
);
}
draft(event) {
@ -77,10 +85,7 @@ class TfTabNewsElement extends LitElement {
} else {
delete this.drafts[id];
}
/* Only trigger a re-render if we're creating a new draft or discarding an old one. */
if ((previous !== undefined) != (event.detail.draft !== undefined)) {
this.drafts = Object.assign({}, this.drafts);
}
this.drafts = Object.assign({}, this.drafts);
tfrpc.rpc.localStorageSet('drafts', JSON.stringify(this.drafts));
}
@ -96,24 +101,68 @@ class TfTabNewsElement extends LitElement {
}
on_keypress(event) {
if (event.target === document.body &&
event.key == '.') {
if (event.target === document.body && event.key == '.') {
this.show_more();
}
}
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('#@')
? html`<tf-profile
id=${this.hash.substring(1)}
whoami=${this.whoami}
.users=${this.users}
></tf-profile>`
: undefined;
let edit_profile;
if (
!this.loading &&
this.users[this.whoami]?.name === undefined &&
this.hash.substring(1) != this.whoami
) {
edit_profile = html` <div
class="w3-panel w3-padding w3-round w3-card-4 w3-theme-l3"
>
Follow your identity link above to edit your profile and set your
name.
</div>`;
}
return html`
<div><input type="button" value=${this.new_messages_text()} @click=${this.show_more}></input></div>
<a target="_top" href="#" ?hidden=${this.hash.length <= 1}>🏠Home</a>
<div>Welcome, <tf-user id=${this.whoami} .users=${this.users}></tf-user>!</div>
<div><tf-compose whoami=${this.whoami} .users=${this.users} .drafts=${this.drafts} @tf-draft=${this.draft}></tf-compose></div>
<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}
</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>
<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>
`;
}
}
customElements.define('tf-tab-news', TfTabNewsElement);
customElements.define('tf-tab-news', TfTabNewsElement);

136
apps/ssb/tf-tab-query.js Normal file
View File

@ -0,0 +1,136 @@
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);

View File

@ -5,6 +5,7 @@ import {styles} from './tf-styles.js';
class TfTabSearchElement extends LitElement {
static get properties() {
return {
drafts: {type: Object},
whoami: {type: String},
users: {type: Object},
following: {type: Array},
@ -22,28 +23,34 @@ class TfTabSearchElement extends LitElement {
this.users = {};
this.following = [];
this.expanded = {};
this.drafts = {};
tfrpc.rpc.localStorageGet('drafts').then(function (d) {
self.drafts = JSON.parse(d || '{}');
});
}
async search(query) {
console.log('Searching...', this.whoami, query);
let search = this.renderRoot.getElementById('search');
if (search ) {
if (search) {
search.value = query;
search.focus();
search.select();
}
await tfrpc.rpc.setHash('#q=' + encodeURIComponent(query));
let results = await tfrpc.rpc.query(`
SELECT messages.*
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)]);
['"' + query.replace('"', '""') + '"', JSON.stringify(this.following)]
);
console.log('Done.');
search = this.renderRoot.getElementById('search');
if (search ) {
if (search) {
search.value = query;
search.focus();
search.select();
@ -68,6 +75,18 @@ class TfTabSearchElement extends LitElement {
}
}
draft(event) {
let id = event.detail.id || '';
let previous = this.drafts[id];
if (event.detail.draft !== undefined) {
this.drafts[id] = event.detail.draft;
} else {
delete this.drafts[id];
}
this.drafts = Object.assign({}, this.drafts);
tfrpc.rpc.localStorageSet('drafts', JSON.stringify(this.drafts));
}
render() {
if (this.query !== this.last_query) {
this.last_query = this.query;
@ -75,13 +94,13 @@ class TfTabSearchElement extends LitElement {
}
let self = this;
return html`
<div style="display: flex; flex-direction: row">
<input type="text" id="search" value=${this.query} style="flex: 1" @keydown=${this.search_keydown}></input>
<input type="button" value="Search" @click=${(event) => self.search(self.renderRoot.getElementById('search').value)}></input>
<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>
<tf-news id="news" whoami=${this.whoami} .messages=${this.messages} .users=${this.users} .expanded=${this.expanded} @tf-expand=${this.on_expand}></tf-news>
<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>
`;
}
}
customElements.define('tf-tab-search', TfTabSearchElement);
customElements.define('tf-tab-search', TfTabSearchElement);

View File

@ -17,8 +17,12 @@ 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`<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
>`;
}
}
customElements.define('tf-tag', TfTagElement);
customElements.define('tf-tag', TfTagElement);

View File

@ -19,26 +19,33 @@ class TfUserElement extends LitElement {
}
render() {
let image = html`<span
class="w3-theme-light w3-circle"
style="display: inline-block; width: 2em; height: 2em; text-align: center; line-height: 2em"
>?</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>`;
name =
name !== undefined
? html`<a target="_top" href=${'#' + this.id}>${name}</a>`
: html`<a target="_top" href=${'#' + this.id}>${this.id}</a>`;
if (this.users[this.id]) {
let image = this.users[this.id].image;
image = typeof(image) == 'string' ? image : image?.link;
return html`
<div style="display: inline-block; font-weight: bold">
<img style="width: 2em; height: 2em; vertical-align: middle; border-radius: 50%" ?hidden=${image === undefined} src="${image ? '/' + image + '/view' : undefined}">
${name}
</div>`;
} else {
return html`
<div style="display: inline-block; font-weight: bold">
${name}
</div>`;
let image_link = this.users[this.id].image;
image_link =
typeof image_link == 'string' ? image_link : image_link?.link;
if (image_link !== undefined) {
image = html`<img
class="w3-circle"
style="width: 2em; height: 2em; vertical-align: middle"
src="/${image_link}/view"
/>`;
}
}
return html` <div style="display: inline-block; font-weight: bold">
${image} ${name}
</div>`;
}
}
customElements.define('tf-user', TfUserElement);
customElements.define('tf-user', TfUserElement);

View File

@ -1,21 +1,34 @@
import * as linkify from './commonmark-linkify.js';
import * as hashtagify from './commonmark-hashtag.js';
const k_code_classes = 'w3-theme-l4 w3-theme-border w3-round';
function image(node, entering) {
if (node.firstChild?.type === 'text' &&
node.firstChild.literal.startsWith('video:')) {
if (
node.firstChild?.type === 'text' &&
node.firstChild.literal.startsWith('video:')
) {
if (entering) {
this.lit('<video style="max-width: 100%; max-height: 480px" title="' + this.esc(node.firstChild?.literal) + '" controls>');
this.lit(
'<video style="max-width: 100%; max-height: 480px" title="' +
this.esc(node.firstChild?.literal) +
'" controls>'
);
this.lit('<source src="' + this.esc(node.destination) + '"></source>');
this.disableTags += 1;
} else {
this.disableTags -= 1;
this.lit('</video>');
}
} else if (node.firstChild?.type === 'text' &&
node.firstChild.literal.startsWith('audio:')) {
} else if (
node.firstChild?.type === 'text' &&
node.firstChild.literal.startsWith('audio:')
) {
if (entering) {
this.lit('<audio style="height: 32px; max-width: 100%" title="' + this.esc(node.firstChild?.literal) + '" controls>');
this.lit(
'<audio style="height: 32px; max-width: 100%" title="' +
this.esc(node.firstChild?.literal) +
'" controls>'
);
this.lit('<source src="' + this.esc(node.destination) + '"></source>');
this.disableTags += 1;
} else {
@ -25,7 +38,11 @@ function image(node, entering) {
} else {
if (entering) {
if (this.disableTags === 0) {
this.lit('<div class="img_caption">' + this.esc(node.firstChild?.literal || node.destination) + '</div>');
this.lit(
'<div class="img_caption">' +
this.esc(node.firstChild?.literal || node.destination) +
'</div>'
);
if (this.options.safe && potentiallyUnsafe(node.destination)) {
this.lit('<img src="" alt="');
} else {
@ -45,27 +62,52 @@ function image(node, entering) {
}
}
function code(node) {
let attrs = this.attrs(node);
attrs.push(['class', k_code_classes]);
this.tag('code', attrs);
this.out(node.literal);
this.tag('/code');
}
function attrs(node) {
let result = commonmark.HtmlRenderer.prototype.attrs.bind(this)(node);
if (node.type == 'block_quote') {
result.push(['class', 'w3-theme-d1']);
} else if (node.type == 'code_block') {
result.push(['class', k_code_classes]);
}
return result;
}
export function markdown(md) {
var reader = new commonmark.Parser({safe: true});
var writer = new commonmark.HtmlRenderer();
let reader = new commonmark.Parser({safe: true});
let writer = new commonmark.HtmlRenderer();
writer.image = image;
var parsed = reader.parse(md || '');
parsed = linkify.transform(parsed);
writer.code = code;
writer.attrs = attrs;
let parsed = reader.parse(md || '');
parsed = hashtagify.transform(parsed);
var walker = parsed.walker();
var event, node;
let walker = parsed.walker();
let event, node;
while ((event = walker.next())) {
node = event.node;
if (event.entering) {
if (node.type == 'link') {
if (node.destination.startsWith('@') &&
node.destination.endsWith('.ed25519')) {
if (
node.destination.startsWith('@') &&
node.destination.endsWith('.ed25519')
) {
node.destination = '#' + node.destination;
} else if (node.destination.startsWith('%') &&
node.destination.endsWith('.sha256')) {
} else if (
node.destination.startsWith('%') &&
node.destination.endsWith('.sha256')
) {
node.destination = '#' + node.destination;
} else if (node.destination.startsWith('&') &&
node.destination.endsWith('.sha256')) {
} else if (
node.destination.startsWith('&') &&
node.destination.endsWith('.sha256')
) {
node.destination = '/' + node.destination + '/view';
}
} else if (node.type == 'image') {
@ -90,4 +132,4 @@ export function human_readable_size(bytes) {
}
}
return `${Math.round(v * 10) / 10} ${u}`;
}
}

View File

@ -1,32 +1,32 @@
.tribute-container {
position: absolute;
top: 0;
left: 0;
height: auto;
overflow: auto;
display: block;
z-index: 999999;
position: absolute;
top: 0;
left: 0;
height: auto;
overflow: auto;
display: block;
z-index: 999999;
}
.tribute-container ul {
margin: 0;
margin-top: 2px;
padding: 0;
list-style: none;
background: #efefef;
margin: 0;
margin-top: 2px;
padding: 0;
list-style: none;
background: #efefef;
}
.tribute-container li {
padding: 5px 5px;
cursor: pointer;
padding: 5px 5px;
cursor: pointer;
}
.tribute-container li.highlight {
background: #ddd;
background: #ddd;
}
.tribute-container li span {
font-weight: bold;
font-weight: bold;
}
.tribute-container li.no-match {
cursor: default;
cursor: default;
}
.tribute-container .menu-highlighted {
font-weight: bold;
}
font-weight: bold;
}

View File

@ -482,16 +482,7 @@ class TributeRange {
}
getDocument() {
let iframe;
if (this.tribute.current.collection) {
iframe = this.tribute.current.collection.iframe;
}
if (!iframe) {
return document
}
return iframe.contentWindow.document
return document;
}
positionMenuAtCaret(scrollTo) {
@ -653,8 +644,8 @@ class TributeRange {
}
getWindowSelection() {
if (this.tribute.collection.iframe) {
return this.tribute.collection.iframe.contentWindow.getSelection()
if (this.tribute.collection[0].iframe?.getSelection) {
return this.tribute.collection[0].iframe.getSelection()
}
return window.getSelection()

Some files were not shown because too many files have changed in this diff Show More