229 Commits

Author SHA1 Message Date
4a4bedfe2b docs: Fix the changelog version. Hmm.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 31m2s
2025-05-28 12:28:33 -04:00
051291f725 build: Let's build 0.0.31.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-05-28 12:27:37 -04:00
d2b338095f update: CodeMirror.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-05-28 12:26:42 -04:00
899827a8f2 ssb: Rename broadcasts to something not worse.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 31m41s
2025-05-27 21:09:26 -04:00
5fcbe3d6a9 docs: Prep for 0.0.31.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 33m31s
2025-05-27 12:44:33 -04:00
a0a40e6cb2 ssb: Implement blob file export from CLI. #121
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-05-27 12:30:24 -04:00
bb1190e3f8 intro: Ugg, CSS.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 32m8s
2025-05-22 12:00:33 -04:00
0a3baed1da intro: Wordsmith some more, and fiddle with style.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-05-22 11:49:58 -04:00
4931c489ed ssb: Dispatch blob wants immediately when added as the result of a web request.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 32m24s
2025-05-22 10:56:30 -04:00
996f9abaa2 ssb: Suppress the 'set your profile' banner if we're still loading abouts.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-05-22 10:44:41 -04:00
08c097e176 ssb: Fix a source of stale user information. 2025-05-22 10:26:59 -04:00
daa861a98b test: Test the intro flow. 2025-05-22 10:19:55 -04:00
a25d08fd76 intro: Skipped a step.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 30m35s
2025-05-22 07:04:57 -04:00
392d31cc53 format
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 30m10s
2025-05-21 22:16:52 -04:00
92926fa8df ssb: Chasing intermittent SQLITE_ERROR with multiple processes accessing the database. Switching to sqlite3_prepare_v2 for better errors, but maybe this is also the solution? 2025-05-21 22:05:33 -04:00
61ae9ae465 intro: Set an emoji, obviously.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 31m17s
2025-05-21 18:51:35 -04:00
89622697d5 intro: The default app is intro, and complete intro changes the default app to ssb.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-05-21 18:48:29 -04:00
17694f5646 update: CodeMirror.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-05-21 18:20:01 -04:00
5a1303149f intro: Add an intro app that aims to ease in completely new users. Needs some clean-up and to be hooked into the flow. #19
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-05-21 18:08:19 -04:00
8a0e190a86 ssb: Clean up outdated dynamic blob_wants_cache entries.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-05-21 17:44:00 -04:00
0d7dfd8c9e ssb: Get the 'encrypt to' input in line with everything else style-wise.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 34m14s
2025-05-18 07:55:10 -04:00
f979ff7050 ssb: Speculation on why I sometimes don't see names update.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 33m0s
2025-05-17 20:10:15 -04:00
e3fcdea362 ssb: Add entries to the blob wants cache when requesting a missing blob from the web. #120
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 34m7s
2025-05-17 12:46:47 -04:00
476fec2757 update: w3.css 5.02.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 37m55s
2025-05-14 22:19:53 -04:00
53c215399b ssb: Ugg, better safe than sorry.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 32m29s
2025-05-14 20:57:42 -04:00
2c330802da ssb: More account info fixes.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-05-14 20:56:13 -04:00
851d7046ea ssb: Fix failure to gather about information sometimes.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-05-14 20:39:06 -04:00
c0019d7246 format
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-05-14 20:33:02 -04:00
7688e4d3a8 ssb: I tried smarter data structures in my own code, but a better database index shows better performance for determining follows.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-05-14 20:13:14 -04:00
ef58749ce3 perf: Make -t following_perf respect the usual db path rules.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 33m33s
2025-05-12 12:41:13 -04:00
35941a7ddc perf: Add a quick test to measure ssb.following performance.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-05-12 12:32:41 -04:00
1f2664e5a8 ssb: Reduce redundant work in ssb.following.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 33m15s
2025-05-11 22:20:16 -04:00
35656a5c34 prettier 2025-05-11 21:54:53 -04:00
799f22e989 ssb: The updated fetch_abouts means that image JSON is now a string we need to handle in tf-user / tf-profile.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 33m16s
2025-05-11 21:42:48 -04:00
e226a37251 prettier
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 31m40s
2025-05-09 19:54:12 -04:00
8e3bc9d700 ssb: Now add back the about cache, and load times are getting good.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 31m43s
2025-05-09 13:25:05 -04:00
58c3e6c2ab build: Fix a -fanalyze issue in the tests.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 31m32s
2025-05-09 07:55:39 -04:00
0dc148bfea ssb: Don't forget the about info we know as we reload.
Some checks failed
Build Tilde Friends / Build-All (push) Failing after 4m53s
2025-05-09 07:35:52 -04:00
3eff1b08a9 ssb: Let about info load in its own time.
Some checks failed
Build Tilde Friends / Build-All (push) Failing after 4m45s
2025-05-09 07:27:35 -04:00
02d789471f ssb: Oops, filter on relevant accounts.
Some checks failed
Build Tilde Friends / Build-All (push) Failing after 4m33s
2025-05-08 12:58:57 -04:00
d367d47c4d ssb: Switch to a more brute force about-gathering approach. I think if I start with this and avoid querying all known accounts up-front, we will be plenty fast.
Some checks failed
Build Tilde Friends / Build-All (push) Failing after 5m12s
2025-05-08 12:39:26 -04:00
c93b8fc045 ssb: Show overall unread status on the hamburger menu.
Some checks failed
Build Tilde Friends / Build-All (push) Failing after 4m34s
2025-05-07 19:59:39 -04:00
eb9377e21d ssb: Add settings for broadcast and discovery.
Some checks failed
Build Tilde Friends / Build-All (push) Failing after 4m34s
2025-05-07 19:05:57 -04:00
a1764eee42 update: CodeMirror.
Some checks failed
Build Tilde Friends / Build-All (push) Failing after 5m16s
2025-05-07 18:37:23 -04:00
86ef74e20d test: More CLI tests, and make -u optional for publish, too.
Some checks failed
Build Tilde Friends / Build-All (push) Failing after 5m11s
2025-05-07 18:31:58 -04:00
4de53b9926 core: sqlite checkpoint tweaks. Switch the periodic checkpoint to passive mode. Truncate mode was painful on mobile. I'm seeing it succeed often enough that we don't grow indefinitely. Also fix the print.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-05-07 18:15:33 -04:00
99a195a3fd update: sqlite 3.49.2. 2025-05-07 18:00:30 -04:00
f1ced31f69 ssb: Faster loads by encouraging the right queries with CTEs in fetch_about.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-05-07 17:52:02 -04:00
b3cedf2baa ssb: Begin to add some CLI tests.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 33m19s
2025-05-06 12:49:57 -04:00
3bf19fabda ssb: Don't require -u for the private command. #119
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 33m44s
2025-05-05 21:51:38 -04:00
cf81ebe8ad ssb: emoji-fy other cases of some reactions.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-05-05 21:32:40 -04:00
278b5566a1 ssb: Remove some unintended whitespace at the bottom of messages.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-05-05 21:09:58 -04:00
e8c1390f09 ssb: Reply arrow that shows up better on my phone.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 32m57s
2025-05-01 12:38:11 -04:00
3c04abda45 ssb: More shutdown correctness. #108
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 32m26s
2025-04-30 12:35:40 -04:00
2597f99ccf ssb: Get recent reactions up front so that we don't need to delay showing the dialog for them.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 32m33s
2025-04-29 20:48:47 -04:00
9d3a07c1cf build: OpenBSD 7.7's SSL matches these signatures again.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 32m57s
2025-04-27 17:29:59 -04:00
bdfd8925b5 ssb: Remove some extra margin that slipped in.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 31m13s
2025-04-27 10:18:37 -04:00
1a4d1985f4 core: Revisit drag+drop into the editor. Works better but not as I intended. Use it just to update the screenshot in the welcome app.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-04-27 10:18:20 -04:00
6273f3ea53 ssb: Make the connections sidebar header also a link to the connections tab.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 33m18s
2025-04-26 18:53:08 -04:00
5bdc6fa471 update: CodeMirror.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-04-26 18:47:04 -04:00
3ba41291db update: QuickJS 2025-04-26.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-04-26 18:30:56 -04:00
0867811952 update: libuv 1.51.0.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 33m0s
2025-04-26 09:06:37 -04:00
8d961cd805 ssb: Indicate that content warnings can be expanded.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 32m38s
2025-04-24 12:35:36 -04:00
97cea7b40b build: Attempt to include native debug symbols in the Android .aab file.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 32m58s
2025-04-23 20:47:31 -04:00
4106834db8 build: nix flake update did this (but really it was nix --extra-experimental-features nix-command --extra-experimental-features flakes flake update). I don't know how any of this actually works.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 33m7s
2025-04-23 18:36:28 -04:00
a4a8f7cab2 build: Let's start work on 0.0.31.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-04-23 18:33:28 -04:00
9e209ee800 build: nix => 0.0.30. 2025-04-23 18:19:58 -04:00
ddfa84f040 docs: Changlog formatting.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 31m16s
2025-04-23 17:35:16 -04:00
6b3a6ec7c1 httpd: Don't overspecify the redirects.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 34m34s
2025-04-23 12:47:21 -04:00
4d037c02bf build: Let's build 0.0.30.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-04-23 12:29:42 -04:00
deaeab10d8 update: CodeMirror. 2025-04-23 12:29:02 -04:00
2a5375b1e7 core: Don't start new tasks as we're shutting down. #108
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 31m40s
2025-04-20 18:26:44 -04:00
e7a03e3283 ssb: Slightly faster channel query.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 32m3s
2025-04-20 17:43:53 -04:00
efb3a12dcc ssb: Make the reactions list dialog a bit more compact/concise.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 31m53s
2025-04-20 09:35:39 -04:00
3830d695d7 docs: Getting 0.0.30 ready.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 31m39s
2025-04-20 08:50:10 -04:00
f36620927b ssb: Chasing some failed blob stores. Bumping some sqlite limits seems to have actually helped?
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 33m30s
2025-04-19 21:40:15 -04:00
5423cbd628 import: Avoid log noise every launch when apps haven't actually changed. 2025-04-19 21:08:23 -04:00
abde709e54 prettier.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 30m7s
2025-04-16 19:27:24 -04:00
27f2d319ab storage: Faster. 2025-04-16 19:21:57 -04:00
66234b14bc test: Add some coverage of storing and resetting permissions by clicking the approve/deny/remember/reset buttons.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 30m32s
2025-04-16 12:32:34 -04:00
6a9167e565 ssb: More logging to help track down an issue I've seen on rare occasion when saving app changes from the editor.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 34m43s
2025-04-15 12:49:40 -04:00
3c60f8ca06 ssb: Whitespace around nested messages.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-04-15 12:42:13 -04:00
c26bf5c112 core: Setting and resetting user app permissions was all sorts of broken.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 32m21s
2025-04-14 12:41:35 -04:00
41cbde934a ssb: Layout the message expand/collapse buttons better.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 33m47s
2025-04-13 21:38:56 -04:00
946941d95e format
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-04-13 21:18:28 -04:00
50f0104239 ssb: Add size to messages_stats. Forces an already-necessary rebuild and provides information I'm keen on having readily available.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-04-13 21:16:26 -04:00
40fa7edadf ssb: Show message count when viewing an account profile.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-04-13 20:48:55 -04:00
d6926569c6 verify: Add an option to dump a specific message in the format that its signature validates as well as a hex representation of the bytes for good measure.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 31m2s
2025-04-13 13:28:48 -04:00
a8bba324ca ssb: Fix removing a mention discarding a draft.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 30m39s
2025-04-13 07:53:15 -04:00
5bba5776b3 core: Get rid of the httpd JS object. Potential progress on #108.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 30m37s
2025-04-12 09:43:55 -04:00
8104f6f228 ssb: Fix and test the messages_stats trigger.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 30m59s
2025-04-12 08:40:36 -04:00
3f4738e593 ssb: Fall back to hostnames for connections in the sidebar.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 30m36s
2025-04-12 07:05:37 -04:00
1516e17f5d update: Lit 3.3.0.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 31m33s
2025-04-11 19:32:26 -04:00
676d2702b7 ssb: Read back-pressure on a tunnel connection affects the parent connection. This was causing spurrious disconnects.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-04-11 19:29:10 -04:00
5d39548964 ssb: Why is this faster?
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 32m0s
2025-04-09 22:47:23 -04:00
67d458bd38 ssb: prettier.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-04-09 22:15:51 -04:00
d9684c7d62 ssb: Fix culling feeds again. 2025-04-09 22:15:01 -04:00
5a818d2119 ssb: More incremental profile loading? 2025-04-09 22:08:33 -04:00
6f96d4ce65 ssb: More different feed culling logic. 2025-04-09 20:45:23 -04:00
f72395756a ssb: If things time out because we're following a million accounts...recover ungracefully. 2025-04-09 20:02:16 -04:00
38d746b310 ssb: Dust off the verify command.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 31m53s
2025-04-09 19:32:38 -04:00
f7270987ea http: Trying to track down a rare 404 I get on save. 2025-04-09 19:08:28 -04:00
6f565c0f0a ssb: Move reply and react to the message % menu. 2025-04-09 19:02:02 -04:00
7f252e79b6 ssb: Faster channel loads. 2025-04-09 18:50:14 -04:00
ba2bb17638 ssb: Gather all the votes for messages seen. 2025-04-09 18:29:37 -04:00
bc7c658293 update: CoreMirror.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 31m12s
2025-04-09 12:28:56 -04:00
4d84e69bb5 update: libbacktrace. 2025-04-09 12:26:44 -04:00
03fac74908 update: c-ares 1.34.5. 2025-04-09 12:24:55 -04:00
5252ff1ecf update: OpenSSL 3.5.0. 2025-04-09 12:22:12 -04:00
20100d3fd4 ssb: Oops, that event was Firefox-specific.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 30m36s
2025-04-07 22:35:40 -04:00
dd3b2656ad ssb: Message % menu tweaks. Show on click. Hide when clicking off. Bury the message ID further.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-04-07 21:39:14 -04:00
657f25e22b ssb: Bring back the date. Whoops.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 29m37s
2025-04-05 22:05:26 -04:00
8be354fc49 ssb: It is troubling to me that querying like this is so much faster.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 29m37s
2025-04-05 18:40:00 -04:00
e574758340 ssb: Make a message % context menu with some new options to begin to declutter the message view itself.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 29m42s
2025-04-05 17:53:47 -04:00
40cf519492 http: I caught an http listener lingering on shutdown. Clean them up more correctly. Possible progress on #108.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 30m38s
2025-04-05 09:53:47 -04:00
0e4fda54e9 fdroid: Lean into SSB in the description.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 29m45s
2025-04-03 12:31:59 -04:00
868f91e1ef docs: Noticed some errors in the readme.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 32m38s
2025-04-02 20:01:26 -04:00
b9000c154f apps: Add a very wip web app.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 30m8s
2025-04-02 18:07:25 -04:00
894c72a82f ssb: Still chasing faster loads of the default news query.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 29m59s
2025-04-02 12:44:17 -04:00
c128cfc25c ssb: This makes the channels query sub-second for me.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 29m39s
2025-04-01 19:50:13 -04:00
36c88b463c buttfeed: Add PatchFox.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 30m41s
2025-04-01 08:13:54 -04:00
8a66e74074 ssb: Faster *and* correct. I think.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 29m43s
2025-03-30 13:18:16 -04:00
ea60b165da ssb: Had my refs backward.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 31m17s
2025-03-30 12:27:36 -04:00
1011e0026b ssb: Rearrange indexes for an overall faster load on mobile for me. Tidy up some things along the way.
Some checks are pending
Build Tilde Friends / Build-All (push) Has started running
2025-03-30 12:16:23 -04:00
9462521287 update: CodeMirror.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 29m53s
2025-03-27 12:37:13 -04:00
576022c41a build: 0.0.30-wip, let's gooooo.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 30m42s
2025-03-26 20:19:28 -04:00
70c38b7ea8 build: nix => 0.0.29.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-03-26 18:57:05 -04:00
36370f2dea build: Fix this dependendy test.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 28m50s
2025-03-26 18:48:49 -04:00
db9bf7f7bd build: Let's build 0.0.29.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 30m5s
2025-03-26 12:28:06 -04:00
fa7aef0c37 build: Fix make warnings on macos.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 30m9s
2025-03-25 19:31:25 -04:00
b135ea17f6 ssb: Let's make sure these are never null again.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 29m23s
2025-03-22 22:17:03 -04:00
4b1643bc47 ssb: Hook up a busy handler to give checkpointing a chance of working, and fix fundamental bugs with the messages_stats table. How did this ever work?
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 30m38s
2025-03-22 08:58:43 -04:00
240a8ce9c7 ssb: All initial population in transactions for correctness.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 30m29s
2025-03-21 20:52:27 -04:00
8928e8722b ssb: Notifying apps of every single message added isn't practical for big replications. Only notify on the completion of each chunk. Update the tests. The apps need some treatment, but refreshing works around any issues for now.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-03-21 20:45:49 -04:00
d692734e55 ssb: Prefer getting last message sequence from the messages_stats table if we don't need the message id. Trying to chip away at things I see in profiles.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 32m0s
2025-03-20 12:53:38 -04:00
50197198b4 update: Missed one instance of w3.css.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-03-20 12:41:20 -04:00
1ee1107c93 update: w3.css 5.0.1.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 32m59s
2025-03-19 20:51:51 -04:00
cf90533b6c ssb: Make these queries match the index more pedandically, if only for consistency.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-03-19 20:31:42 -04:00
f0211f621e ssb: That's just a weird formulation of this query. I saw it in a profile.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 31m13s
2025-03-19 19:09:29 -04:00
d9693af89b build: Some prep for v0.0.29. 2025-03-19 18:37:17 -04:00
13722232fb ssb: Don't let GC saturate the main thread during replication.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 30m45s
2025-03-19 07:47:10 -04:00
0bcb033349 ssb: This seems like it would explain replication stalls.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 30m18s
2025-03-19 06:46:34 -04:00
e92c439724 ssb: Commit messages in transactions for performance.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 30m39s
2025-03-18 12:49:47 -04:00
7f34b585d3 ssb: Don't count messages we're not intending to replicate toward progress.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 31m44s
2025-03-17 21:13:20 -04:00
d7e9fd918a ssb: Idea: ebt clocks shouldn't go backward.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-03-17 20:49:55 -04:00
9899c0c5e2 ssb: Try to keep these stats from going negative.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 30m12s
2025-03-17 12:29:30 -04:00
c50de0b0f0 ssb: Testing a theory that message sends are piling up. 2025-03-17 12:27:16 -04:00
bb7d2d7ae0 ssb: Update ebt received value on the fly.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 31m10s
2025-03-16 22:04:06 -04:00
862d172ca8 ssb: Pub blobs in the idle queue, too. I'm confused why things aren't moving like I'd expect. This seems more fair.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 31m10s
2025-03-16 19:30:35 -04:00
3671051d0e cli: Show usage and error on unexpected arguments.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 30m56s
2025-03-16 12:45:54 -04:00
223e20cbbc ssb: Fix the tests.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 32m27s
2025-03-16 11:52:11 -04:00
9af4312561 ssb: Attempt to show EBT replication progress in the connections tab, mainly to help track down bugs, which there are.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-03-16 11:39:28 -04:00
934e40240e build: Fix some haiku make warnings.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 31m22s
2025-03-15 17:46:25 +00:00
edb1980387 ssb: Address a -fanalyze issue. #114
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-03-15 13:44:02 -04:00
bb7b04013f ssb: Resolve a DB-related naming collision I caused better.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 31m45s
2025-03-12 20:49:48 -04:00
26a3007268 http: We can bind to localhost if we do it right, apparently.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-03-12 20:23:37 -04:00
5de2b09596 ssb: Obviously remove from blob_wants_cache when a blob is added.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 31m48s
2025-03-12 18:23:38 -04:00
3660577a23 ssb: I made blob_wants_view too slow. Let's make a real table updated by triggers.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-03-12 18:17:11 -04:00
98b4c7cf04 http: Disable binding to localhost on mobile until I can solve 'address not available'.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 31m49s
2025-03-11 21:36:55 -04:00
427a7b8d25 ssb: Request blobs referenced by messages, as an experiment. Recursively, too.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 32m25s
2025-03-11 19:02:48 -04:00
67b84830cd debug: Add an /ebt endpoint to examine the state of EBT clocks. Prevent invalid identities from getting in there, now that I see them.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 28m58s
2025-03-09 11:28:47 -04:00
973cd53266 security: Make mobile listen on localhost by default. I did not intend to leave it open.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 30m26s
2025-03-08 20:40:03 -05:00
1afdbe6932 build: Let's include speedscope again.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 31m3s
We're no longer trying to fit into a 5MB APK or anything.
2025-03-07 12:48:35 -05:00
942f582329 trace: Fix a crash and source of invalid json.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-03-07 12:37:02 -05:00
951a80389a update: CodeMirror.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 30m53s
2025-03-05 20:32:56 -05:00
b7ecfc9925 ios: Fix file upload. This is the last thing I would have expected. #109
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 30m36s
2025-03-05 19:48:39 -05:00
59e389d793 ios: Add rough back/forward/refresh buttons. #111
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 30m26s
2025-03-05 18:56:06 -05:00
2ec047cc00 build: Appease various doxygen versions.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 30m2s
2025-03-04 12:46:12 -05:00
ee33f54745 ssb: Don't send EBT clocks that weren't asked for. Fixes more replication-related warnings.
Some checks failed
Build Tilde Friends / Build-All (push) Failing after 2m36s
2025-03-04 12:19:52 -05:00
7a79534ca8 docs: Add some words about upgrading.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 28m44s
2025-03-02 21:21:04 -05:00
a74a9fc821 ssb: Clean up some muxrpc-related warnings.
Some checks are pending
Build Tilde Friends / Build-All (push) Waiting to run
2025-03-02 19:43:50 -05:00
e2c388b9db cleanup: Stray file again. 2025-03-02 19:29:27 -05:00
0f573ce09e ssb: Move identity info gathering to db.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 26m52s
2025-03-01 20:44:52 -05:00
bc70e41b7c ssb: prettier.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 30m51s
2025-02-27 20:17:24 -05:00
f500e14aa3 ssb: Make the connections tab a bit less scary.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 30m46s
2025-02-27 20:04:38 -05:00
8b47938238 perf: Make promise stack trace collection opt-in.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-02-27 19:41:21 -05:00
8912212d8e build: Fix 32-bit.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-02-27 19:10:48 -05:00
6a346bf940 http: Move websocket send to http, obvs. 2025-02-27 19:07:01 -05:00
b5bdae4611 js: prettier
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 29m58s
2025-02-27 15:00:37 -05:00
6590da5793 js: Just delete some placeholder comments around code that I am trying to delete. 2025-02-27 15:00:02 -05:00
eb2b426ec7 js: Clean up some oddness in old code as I struggle to replace it. 2025-02-27 14:28:07 -05:00
4864a0411f js: This RPC info does not need to be global. 2025-02-27 13:52:24 -05:00
68590cae33 http: Avoid a potential null dereference.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 30m0s
2025-02-27 11:25:38 -05:00
abf2bbaec2 js: Very minor trimming. 2025-02-27 10:01:59 -05:00
0da7e2722f js: Add a place to start moving imports to C. 2025-02-27 09:58:15 -05:00
60d4b06057 http: Prevent reentering tf_http_destroy. 2025-02-27 08:52:42 -05:00
4c3df34950 build: Let's start work on 0.0.29.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 30m9s
2025-02-26 20:04:17 -05:00
7737e60b52 build: ios => CFBundleVersion=10.
Some checks are pending
Build Tilde Friends / Build-All (push) Waiting to run
2025-02-26 19:44:00 -05:00
71e816bc13 build: nix => 0.0.28. 2025-02-26 19:43:20 -05:00
c74f90ef04 core: Fix stock apps not being loaded/updated.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 28m52s
2025-02-26 18:54:00 -05:00
26cb7e5a17 ios: Redid icon stuff.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-02-26 18:27:54 -05:00
2bad6672d8 ios: Generate Assets.car out of an overabundance of caution that F-Droid is going to complain about this as a binary file.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 30m10s
2025-02-26 12:43:42 -05:00
71c4011526 build: Let's prepare a 0.0.28 release.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 30m24s
2025-02-26 12:07:54 -05:00
5e81078f59 update: CodeMirror. 2025-02-26 12:05:07 -05:00
31b78e74df ssb: Following calculation fixes. It was not handled appropriately if an account was encountered multiple times with decreasing depths.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 31m3s
2025-02-25 21:52:15 -05:00
2ff689aab0 buttfeed: De-duplicate updates by link.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 29m14s
2025-02-24 12:16:31 -05:00
0a6f0ed3f7 ssb: Audit timer use that might cause unnecessary delays during shutdown.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 29m34s
2025-02-23 13:07:36 -05:00
bfec46673d ssb: Test an experiment that blobs are getting priority over messages during initial sync.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 30m16s
2025-02-22 11:57:57 -05:00
3a16614c72 docs: Remember to update the latest_release tag.
Some checks failed
Build Tilde Friends / Build-All (push) Failing after 2m42s
2025-02-22 11:23:36 -05:00
9c9efb845c ssb: Schedule the clock for reevaluation any time new messages are added. I think this will improve initial replication.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 28m13s
2025-02-21 22:30:14 -05:00
6192f1b94d room: Fix the room app port number multiple ways. 2025-02-21 22:05:28 -05:00
ce451b2449 cli: Fix the return value for get_profile.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 28m42s
2025-02-21 19:58:01 -05:00
8534e16469 docs: +sidebar.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 29m38s
2025-02-19 20:20:21 -05:00
f6cc6f2eae docs: Minor prep for 0.0.28.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-02-19 19:54:45 -05:00
0904425221 update: CodeMirror. 2025-02-19 19:04:59 -05:00
a729886522 update: sqlite 3.49.1. 2025-02-19 18:59:45 -05:00
e5dfedc7d1 ios: Drop min ios version to 14. Generate more of the Info.plist. We don't use non-exempt encryption.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 28m43s
2025-02-19 18:49:53 -05:00
f02423d084 ios: Now with more sed.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 29m45s
2025-02-18 21:46:04 -05:00
8f4b6e83eb ios: This fixes the build on macos and runs in the simulator.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-02-18 21:28:03 -05:00
3029919553 ios: Might as well generate the CFBundleShortVersionString from that other version.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 29m19s
2025-02-18 20:58:16 -05:00
ac67db0591 Merge pull request 'nix: fix build instructions fuckup' (#107) from tasiaiso/tildefriends:tasiaiso-fix-fuckup-holy-shit into main
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 29m1s
Reviewed-on: #107
2025-02-18 19:26:56 -05:00
1e08838f5b ios: I was able to get an iOS build to app store connect with this. Sanity lost.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-02-18 19:03:25 -05:00
d814f7ee77 nix: update flake lockfile, fix update procedure 2025-02-18 22:41:00 +01:00
7edfb9d386 docs: I forgot to do this last release.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 27m43s
2025-02-17 21:16:38 -05:00
6928d6caba cleanup: prettier.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 30m25s
2025-02-16 15:52:27 -05:00
1a626875cf build: Link warning on macos.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 31m15s
2025-02-16 15:13:45 -05:00
6eb3b64334 build: Fix android aab build.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 31m19s
2025-02-16 14:41:14 -05:00
09f3595e93 ssb: Use the right shape for your own profile image.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-02-16 14:37:45 -05:00
11e89622d4 security: Respect the autologin setting better.
Some checks failed
Build Tilde Friends / Build-All (push) Failing after 31m15s
2025-02-16 14:07:14 -05:00
0fa8acc264 ios: Untested, but attempt auto-login.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-02-16 13:42:07 -05:00
afc7c64ed8 android: Fix launch args.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-02-16 13:41:26 -05:00
c794c1b885 admin: Global settings can be specified on the command-line. Removed some previous, less thorough ways of configuring things. #102
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-02-16 13:37:25 -05:00
6247529799 build: Fix F-droid icon reference.
Some checks failed
Build Tilde Friends / Build-All (push) Failing after 31m15s
2025-02-16 11:49:45 -05:00
ad3eedc1fb android: Avoid the extra background on the icon, and set a different color for F-droid builds.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-02-16 11:43:27 -05:00
1cfac3cae6 ssb: Allow otherwise unrecognized incoming connections when not talking to strangers if they have a valid invite.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 31m17s
2025-02-16 08:45:08 -05:00
478bcd5d13 ssb: New incoming connections replace old ones for the same id.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-02-16 08:19:40 -05:00
6932da69b3 update: speedscope 1.22.2.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 31m8s
2025-02-15 21:08:46 -05:00
857f47bf55 ssb: Make the invite.use reponse flags match the docs exactly, and add some connection state paranoia.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-02-15 20:52:39 -05:00
373d742751 ssb: The docs show the response to invite.use() wrapped in {key: ..., value: ...}, so do that. 2025-02-15 08:19:23 -05:00
147 changed files with 4593 additions and 1886 deletions

1
.gitignore vendored
View File

@ -7,6 +7,7 @@ deps/openssl/
dist/
.flatpak-builder
.keys
**/.DS_Store
logs/
**/node_modules
out

View File

@ -943,7 +943,9 @@ WARN_LOGFILE =
# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING
# Note: If this tag is empty the current directory is searched.
INPUT = README.md docs/ src/
INPUT = README.md \
docs/ \
src/
# This tag can be used to specify the character encoding of the source files
# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses
@ -1680,7 +1682,7 @@ DISABLE_INDEX = NO
# The default value is: NO.
# This tag requires that the tag GENERATE_HTML is set to YES.
GENERATE_TREEVIEW = NO
GENERATE_TREEVIEW = YES
# When both GENERATE_TREEVIEW and DISABLE_INDEX are set to YES, then the
# FULL_SIDEBAR option determines if the side bar is limited to only the treeview
@ -2268,7 +2270,7 @@ GENERATE_AUTOGEN_DEF = NO
# database with symbols found by doxygen stored in tables.
# The default value is: NO.
GENERATE_SQLITE3 = NO
#GENERATE_SQLITE3 = NO
# The SQLITE3_OUTPUT tag is used to specify where the Sqlite3 database will be
# put. If a relative path is entered the value of OUTPUT_DIRECTORY will be put
@ -2276,7 +2278,7 @@ GENERATE_SQLITE3 = NO
# The default directory is: sqlite3.
# This tag requires that the tag GENERATE_SQLITE3 is set to YES.
SQLITE3_OUTPUT = sqlite3
#SQLITE3_OUTPUT = sqlite3
# The SQLITE3_OVERWRITE_DB tag is set to YES, the existing doxygen_sqlite3.db
# database file will be recreated with each doxygen run. If set to NO, doxygen
@ -2284,7 +2286,7 @@ SQLITE3_OUTPUT = sqlite3
# The default value is: YES.
# This tag requires that the tag GENERATE_SQLITE3 is set to YES.
SQLITE3_RECREATE_DB = YES
#SQLITE3_RECREATE_DB = YES
#---------------------------------------------------------------------------
# Configuration options related to the Perl module output

View File

@ -16,11 +16,14 @@ MAKEFLAGS += --no-builtin-rules
## LD := Linker.
## ANDROID_SDK := Path to the Android SDK.
VERSION_CODE := 33
VERSION_NUMBER := 0.0.28-wip
VERSION_CODE := 37
VERSION_CODE_IOS := 13
VERSION_NUMBER := 0.0.31
VERSION_NAME := This program kills fascists.
SQLITE_URL := https://www.sqlite.org/2025/sqlite-amalgamation-3490000.zip
IPHONEOS_VERSION_MIN=14.0
SQLITE_URL := https://www.sqlite.org/2025/sqlite-amalgamation-3490200.zip
BUNDLETOOL_URL := https://github.com/google/bundletool/releases/download/1.17.0/bundletool-all-1.17.0.jar
APPIMAGETOOL_URL := https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage
APPIMAGETOOL_MD5 := e989fadfc4d685fd3d6aeeb9b525d74d out/appimagetool
@ -42,6 +45,10 @@ export TZ=UTC
ifeq ($(UNAME_S),Darwin)
BUILD_TYPES := debug release iosdebug iosrelease iossimdebug iossimrelease
HAVE_ANDROID = 0
HAVE_LINUX_IOS = 0
HAVE_LINUX_MACOS = 0
HAVE_WIN = 0
else ifeq ($(UNAME_S),Linux)
BUILD_TYPES := debug release
HAVE_ANDROID = $(if $(shell which $(ANDROID_SDK)/platform-tools/adb),1)
@ -59,6 +66,10 @@ LDFLAGS += \
-lnetwork \
-Wno-stringop-overflow
USE_SYSTEM_SSL := 1
HAVE_ANDROID = 0
HAVE_LINUX_IOS = 0
HAVE_LINUX_MACOS = 0
HAVE_WIN = 0
else ifeq ($(UNAME_S),OpenBSD)
BUILD_TYPES := debug release
CFLAGS += \
@ -302,8 +313,8 @@ $(ANDROID_X86_64_TARGETS): LDFLAGS += -Lout/openssl/android/x86_64/usr/local/lib
$(NONMACOS_TARGETS): CFLAGS += -Wno-cast-function-type
$(MACOS_TARGETS): LDFLAGS += -Wl,-dead_strip
$(NONMACOS_TARGETS): LDFLAGS += -Wl,--gc-sections -Wl,--as-needed
$(IOS_TARGETS): CFLAGS += -miphoneos-version-min=9.0
$(IOS_TARGETS): LDFLAGS += -miphoneos-version-min=9.0
$(IOS_TARGETS): CFLAGS += -miphoneos-version-min=$(IPHONEOS_VERSION_MIN)
$(IOS_TARGETS): LDFLAGS += -miphoneos-version-min=$(IPHONEOS_VERSION_MIN)
ifeq ($(UNAME_S),Darwin)
$(IOS_TARGETS): CFLAGS += -Iout/openssl/ios/ios64-xcrun/usr/local/include
$(IOS_TARGETS): LDFLAGS += -Lout/openssl/ios/ios64-xcrun/usr/local/lib
@ -603,15 +614,16 @@ $(UV_OBJS): CFLAGS += \
-Ideps/libuv/include \
-Ideps/libuv/src \
-Wno-dangling-pointer \
-Wno-format-truncation \
-Wno-incompatible-pointer-types \
-Wno-maybe-uninitialized \
-Wno-nonnull \
-Wno-sign-compare \
-Wno-unknown-attributes \
-Wno-unused-but-set-parameter \
-Wno-unused-but-set-variable \
-Wno-unused-result \
-Wno-unused-variable \
-Wno-nonnull
-Wno-unused-variable
$(filter out/win%,$(UV_OBJS)): \
CFLAGS += \
-Wno-cast-function-type \
@ -707,12 +719,12 @@ $(SQLITE_OBJS): CFLAGS += \
-DSQLITE_MAX_COMPOUND_SELECT=300 \
-DSQLITE_MAX_EXPR_DEPTH=40 \
-DSQLITE_MAX_FUNCTION_ARG=8 \
-DSQLITE_MAX_LENGTH=5242880 \
-DSQLITE_MAX_LENGTH=10485760 \
-DSQLITE_MAX_LIKE_PATTERN_LENGTH=50 \
-DSQLITE_MAX_SQL_LENGTH=100000 \
-DSQLITE_MAX_TRIGGER_DEPTH=10 \
-DSQLITE_MAX_VARIABLE_NUMBER=100 \
-DSQLITE_MAX_VDBE_OP=25000 \
-DSQLITE_MAX_VDBE_OP=50000 \
-DSQLITE_OMIT_DEPRECATED \
-DSQLITE_OMIT_DESERIALIZE \
-DSQLITE_OMIT_LOAD_EXTENSION \
@ -731,7 +743,7 @@ $(SQLITE_OBJS): CFLAGS += \
QUICKJS_SOURCES := \
deps/quickjs/cutils.c \
deps/quickjs/libbf.c \
deps/quickjs/dtoa.c \
deps/quickjs/libregexp.c \
deps/quickjs/libunicode.c \
deps/quickjs/quickjs.c
@ -808,7 +820,7 @@ $(MINIUNZIP_OBJS): CFLAGS += \
LDFLAGS += \
-pthread \
-lm
$(HOST_TARGETS) $(IOS_TARGETS) $(IOSSIM_TARGETS) $(AARCH64_TARGETS) $(MACOS_TARGETS): LDFLAGS += \
$(HOST_TARGETS) $(IOS_TARGETS) $(IOSSIM_TARGETS) $(AARCH64_TARGETS) $(filter-out $(HOST_TARGETS),$(MACOS_TARGETS)): LDFLAGS += \
-lssl \
-lcrypto
ifneq ($(UNAME_S),Haiku)
@ -910,6 +922,17 @@ src/android/AndroidManifest.xml : $(firstword $(MAKEFILE_LIST))
-e 's/android:targetSdkVersion="[[:digit:]]*"/android:targetSdkVersion="$(ANDROID_TARGET_SDK_VERSION)"/' \
$@
src/ios/Info.plist : $(firstword $(MAKEFILE_LIST))
@echo "[ios_version] $@"
@cat $@ | \
tr '\n' '^' | \
sed -r \
-e 's@(<key>CFBundleShortVersionString</key>\^[[:space:]]*<string>)[0-9.]*(</string>)@\1$(VERSION_NUMBER:%-wip=%)\2@' \
-e 's@(<key>CFBundleVersion</key>\^[[:space:]]*<string>)[[:digit:]]+(</string>)@\1$(VERSION_CODE_IOS)\2@' \
-e 's@(<key>MinimumOSVersion</key>\^[[:space:]]*<string>)[0-9.]*(</string>)@\1$(IPHONEOS_VERSION_MIN)\2@' | \
tr '^' '\n' > \
$@.tmp && mv $@.tmp $@ || rm -f $@.tmp
##
## Android targets:
##
@ -927,29 +950,30 @@ out/res/layout_activity_main.xml.flat: src/android/res/layout/activity_main.xml
@echo "[aapt2] $@"
@$(ANDROID_BUILD_TOOLS)/aapt2 compile -o out/res/ src/android/res/layout/activity_main.xml
out/res/drawable_icon.xml.flat: src/android/res/drawable/icon.xml
out/res/drawable_%.xml.flat: src/android/res/drawable/%.xml
@mkdir -p $(dir $@)
@echo "[aapt2] $@"
@$(ANDROID_BUILD_TOOLS)/aapt2 compile -o out/res/ src/android/res/drawable/icon.xml
@$(ANDROID_BUILD_TOOLS)/aapt2 compile -o out/res/ $(<)
out/apk/res.apk out/gen/com/unprompted/tildefriends/R.java: out/res/layout_activity_main.xml.flat out/res/drawable_icon.xml.flat src/android/AndroidManifest.xml
out/apk/res.apk out/gen/com/unprompted/tildefriends/R.java: out/res/layout_activity_main.xml.flat out/res/drawable_icon.xml.flat out/res/drawable_logo.xml.flat src/android/AndroidManifest.xml
@echo [aapt2 link] res.apk
@mkdir -p out/apk/
@$(ANDROID_BUILD_TOOLS)/aapt2 link -I $(ANDROID_PLATFORM)/android.jar out/res/layout_activity_main.xml.flat out/res/drawable_icon.xml.flat \
@$(ANDROID_BUILD_TOOLS)/aapt2 link -I $(ANDROID_PLATFORM)/android.jar out/res/layout_activity_main.xml.flat out/res/drawable_icon.xml.flat out/res/drawable_logo.xml.flat \
--min-sdk-version $(ANDROID_MIN_SDK_VERSION) \
--target-sdk-version $(ANDROID_TARGET_SDK_VERSION) \
--manifest src/android/AndroidManifest.xml \
-o out/apk/res.apk \
--java out/gen/
out/apk/res.fdroid.apk out/gen_fdroid/com/unprompted/tildefriends/R.java: out/res/layout_activity_main.xml.flat out/res/drawable_icon.xml.flat src/android/AndroidManifest.xml
out/apk/res.fdroid.apk out/gen_fdroid/com/unprompted/tildefriends/R.java: out/res/layout_activity_main.xml.flat out/res/drawable_icon_fdroid.xml.flat out/res/drawable_logo.xml.flat src/android/AndroidManifest.xml
@echo [aapt2 link] res.fdroid.apk
@mkdir -p out/apk/
@$(ANDROID_BUILD_TOOLS)/aapt2 link -I $(ANDROID_PLATFORM)/android.jar out/res/layout_activity_main.xml.flat out/res/drawable_icon.xml.flat \
@sed -e 's@drawable/icon@drawable/icon_fdroid@' src/android/AndroidManifest.xml > out/apk/AndroidManifest.fdroid.xml
@$(ANDROID_BUILD_TOOLS)/aapt2 link -I $(ANDROID_PLATFORM)/android.jar out/res/layout_activity_main.xml.flat out/res/drawable_icon_fdroid.xml.flat out/res/drawable_logo.xml.flat \
--min-sdk-version $(ANDROID_MIN_SDK_VERSION) \
--target-sdk-version $(ANDROID_TARGET_SDK_VERSION) \
--rename-manifest-package com.unprompted.tildefriends.fdroid \
--manifest src/android/AndroidManifest.xml \
--manifest out/apk/AndroidManifest.fdroid.xml \
-o out/apk/res.fdroid.apk \
--java out/gen_fdroid/
@ -966,11 +990,12 @@ out/apk/classes.dex: $(CLASS_FILES)
@$(ANDROID_BUILD_TOOLS)/d8 --lib $(ANDROID_PLATFORM)/android.jar --output $(dir $@) out/classes/com/unprompted/tildefriends/*.class
PACKAGE_DIRS := \
apps/ \
core/ \
deps/codemirror/ \
deps/prettier/ \
deps/lit/
apps \
core \
deps/codemirror \
deps/prettier \
deps/lit \
deps/speedscope
RAW_FILES := $(sort $(filter-out apps/blog% apps/issues% apps/welcome% apps/journal% %.map, $(shell find $(PACKAGE_DIRS) -type f -not -name '.*')))
@ -991,7 +1016,7 @@ $(BUNDLETOOL):
@curl -q -L --create-dirs -o $@ $(BUNDLETOOL_URL)
out/TildeFriends.aab: out/apk/classes.dex $(filter-out %debug%, $(ANDROID_TARGETS)) $(RAW_FILES) out/apk/res.apk src/android/AndroidManifest.xml $(BUNDLETOOL)
@rm -rf out/aab/staging/
@rm -rf out/aab/staging/ out/aab/base.zip
@mkdir -p out/aab/staging
@$(ANDROID_BUILD_TOOLS)/aapt2 link --proto-format -o out/aab/temporary.apk \
-I $(ANDROID_PLATFORM)/android.jar \
@ -1000,6 +1025,7 @@ out/TildeFriends.aab: out/apk/classes.dex $(filter-out %debug%, $(ANDROID_TARGET
--manifest src/android/AndroidManifest.xml \
-R out/res/layout_activity_main.xml.flat \
-R out/res/drawable_icon.xml.flat \
-R out/res/drawable_logo.xml.flat \
--auto-add-overlay
@unzip out/aab/temporary.apk -d out/aab/staging/
@mkdir -p out/aab/staging/root/deps
@ -1010,14 +1036,11 @@ out/TildeFriends.aab: out/apk/classes.dex $(filter-out %debug%, $(ANDROID_TARGET
@cp out/apk/classes.dex out/aab/staging/dex/
@rm -fv out/base.zip
@mkdir -p out/aab/staging/lib/arm64-v8a out/aab/staging/lib/armeabi-v7a out/aab/staging/lib/x86_64 out/aab/staging/lib/x86
@cp out/androidrelease/tildefriends out/aab/staging/lib/arm64-v8a/libtildefriends.so
@cp out/androidrelease-armv7a/tildefriends out/aab/staging/lib/armeabi-v7a/libtildefriends.so
@cp out/androidrelease-x86_64/tildefriends out/aab/staging/lib/x86_64/libtildefriends.so
@cp out/androidrelease-x86/tildefriends out/aab/staging/lib/x86/libtildefriends.so
@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip out/aab/staging/lib/arm64-v8a/libtildefriends.so
@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip out/aab/staging/lib/armeabi-v7a/libtildefriends.so
@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip out/aab/staging/lib/x86_64/libtildefriends.so
@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip out/aab/staging/lib/x86/libtildefriends.so
@mkdir -p out/aab/staging/BUNDLE-METADATA/com.android.tools.build.debugsymbols/arm64-v8a out/aab/staging/BUNDLE-METADATA/com.android.tools.build.debugsymbols/armeabi-v7a out/aab/staging/BUNDLE-METADATA/com.android.tools.build.debugsymbols/x86_64 out/aab/staging/BUNDLE-METADATA/com.android.tools.build.debugsymbols/x86
@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip out/androidrelease/tildefriends -o out/aab/staging/lib/arm64-v8a/libtildefriends.so
@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip out/androidrelease-armv7a/tildefriends -o out/aab/staging/lib/armeabi-v7a/libtildefriends.so
@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip out/androidrelease-x86_64/tildefriends -o out/aab/staging/lib/x86_64/libtildefriends.so
@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip out/androidrelease-x86/tildefriends -o out/aab/staging/lib/x86/libtildefriends.so
@cp -r apps/ out/aab/staging/root/
@rm -rf out/aab/staging/root/apps/welcome*
@cp -r core/ out/aab/staging/root/
@ -1026,6 +1049,11 @@ out/TildeFriends.aab: out/apk/classes.dex $(filter-out %debug%, $(ANDROID_TARGET
@cp -r deps/codemirror/ out/aab/staging/root/deps/
@cd out/aab/staging/; zip -r ../base.zip *; cd ../../../
@java -jar $(BUNDLETOOL) build-bundle --overwrite --config=src/android/BundleConfig.json --modules=out/aab/base.zip --output=$@
@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip --only-keep-debug out/androidrelease/tildefriends -o out/aab/staging/BUNDLE-METADATA/com.android.tools.build.debugsymbols/arm64-v8a/libtildefriends.so.sym
@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip --only-keep-debug out/androidrelease-armv7a/tildefriends -o out/aab/staging/BUNDLE-METADATA/com.android.tools.build.debugsymbols/armeabi-v7a/libtildefriends.so.sym
@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip --only-keep-debug out/androidrelease-x86_64/tildefriends -o out/aab/staging/BUNDLE-METADATA/com.android.tools.build.debugsymbols/x86_64/libtildefriends.so.sym
@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip --only-keep-debug out/androidrelease-x86/tildefriends -o out/aab/staging/BUNDLE-METADATA/com.android.tools.build.debugsymbols/x86/libtildefriends.so.sym
@cd out/aab/staging; zip -u ../../../$@ BUNDLE-METADATA/com.android.tools.build.debugsymbols/arm64-v8a/libtildefriends.so.sym BUNDLE-METADATA/com.android.tools.build.debugsymbols/armeabi-v7a/libtildefriends.so.sym BUNDLE-METADATA/com.android.tools.build.debugsymbols/x86_64/libtildefriends.so.sym BUNDLE-METADATA/com.android.tools.build.debugsymbols/x86/libtildefriends.so.sym; cd ../../../
@$(ANDROID_BUILD_TOOLS)/apksigner sign -ks .keys/android.jks --ks-key-alias androidKey -ks-pass pass:android --min-sdk-version=$(ANDROID_MIN_SDK_VERSION) $@
aab: out/TildeFriends.aab ## Build an Android App Bundle.
@ -1137,7 +1165,13 @@ out/zsign_build/zsign: $(wildcard deps/zsign/*.cpp deps/zsign/*.h deps/zsign/*.t
@cmake -B out/zsign_build deps/zsign
@cmake --build out/zsign_build -- COLOR=0 VERBOSE=0 MAKESILENT=-s
out/tildefriends-%.app/tildefriends: out/%/tildefriends out/tildefriends-%.app/Info.plist out/tildefriends-%.app/tildefriends.png out/data.zip $(if $(HAVE_LINUX_IOS),out/zsign_build/zsign)
ifeq ($(HAVE_LINUX_IOS),1)
ZSIGN_DEP = out/zsign_build/zsign
else
ZSIGN_DEP =
endif
out/tildefriends-%.app/tildefriends: out/%/tildefriends out/tildefriends-%.app/Info.plist out/tildefriends-%.app/tildefriends.png out/data.zip $(ZSIGN_DEP)
@mkdir -p $(dir $@)
@cp -v $(filter-out out/zsign%,$<) $@
@cp -v out/data.zip $(@D)/
@ -1217,7 +1251,7 @@ $(LOCAL_DEPS):
CROSS_TOP=../../deps/ios_toolchain/target \
CROSS_SDK=iPhoneOS18.2.sdk \
CC=clang \
OPTIONS=-miphoneos-version-min=9.0 \
OPTIONS=-miphoneos-version-min=$(IPHONEOS_VERSION_MIN) \
tools/ssl-local
$(filter $(BUILD_DIR)/ios%,$(APP_OBJS)): | $(LOCAL_DEPS)
endif
@ -1264,7 +1298,7 @@ endif
ifeq ($(UNAME_S),Darwin)
IOS_DEPS := out/openssl/ios/ios64-xcrun/usr/local/lib/libssl.a
$(IOS_DEPS):
+@BUILD_PLATFORM=ios BUILD_TARGET=ios64-xcrun SSL_TARGET=ios64-xcrun OPTIONS="-fPIC -Wno-macro-redefined -miphoneos-version-min=9.0" tools/ssl-local
+@BUILD_PLATFORM=ios BUILD_TARGET=ios64-xcrun SSL_TARGET=ios64-xcrun OPTIONS="-fPIC -Wno-macro-redefined -miphoneos-version-min=$(IPHONEOS_VERSION_MIN)" tools/ssl-local
+@BUILD_PLATFORM=ios BUILD_TARGET=iossimulator-xcrun SSL_TARGET=iossimulator-xcrun OPTIONS="-fPIC -Wno-macro-redefined" tools/ssl-local
$(filter $(BUILD_DIR)/ios%,$(APP_OBJS)): | $(IOS_DEPS)
endif
@ -1398,6 +1432,17 @@ dist-test: dist ## Exercise some built distributable files, making sure they wor
@rm -rf tildefriends-$(VERSION_NUMBER)
.PHONY: dist-test
dist-ios: iosrelease-app
rm -rfv out/Payload out/tildefriends.ipa
mkdir -p out/Payload/tildefriends.app
cp -avR out/tildefriends-iosrelease.app/* out/Payload/tildefriends.app/
cp src/ios/tildefriends.png out/Payload/tildefriends.app/
xcrun -sdk iphoneos actool --compile out/Payload/tildefriends.app/ --platform iphoneos --minimum-deployment-target $(IPHONEOS_VERSION_MIN) --app-icon AppIcon src/ios/icons/Assets.xcassets src/ios/icons/*.png --output-partial-info-plist out/actool.plist
cp src/ios/distribution.mobileprovision out/Payload/tildefriends.app/embedded.mobileprovision
xcrun -sdk iphoneos codesign -f -s 'Apple Distribution' --entitlements src/ios/Entitlements.plist --generate-entitlement-der out/Payload/tildefriends.app
cd out; zip -r tildefriends.ipa Payload; cd ..
xcrun -sdk iphoneos altool --upload-app -f out/tildefriends.ipa -t ios -u $$(cat .keys/altool-user) -p $$(cat .keys/altool-password)
##
## Targets for tidying up:
##

View File

@ -38,7 +38,7 @@ dependencies in the right places.
### Requirements
System OpenSSL libraries are assumed to be available on Haiku and OpenSSL.
System OpenSSL libraries are assumed to be available on Haiku and OpenBSD.
On MacOS, Xcode's command-line tools are expected to be available.
@ -55,9 +55,8 @@ standard.
## Running
By default, running the built `out/debug/tildefriends` executable will start a
web server at <http://localhost:12345/>. It expects to be run with the
repository root as the current working directory. `tildefriends -h` lists
further options.
web server at <http://localhost:12345/>. `tildefriends -h` lists further
options.
The first user to create an account and log in will be granted administrative
privileges. Further administration can be done in the `admin` app at

View File

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

View File

@ -72,7 +72,7 @@ ${description.value}</textarea
</button>
</li>
`;
} else {
} else if (description.type != 'hidden') {
return html`
<li class="w3-row">
<label class="w3-quarter" for=${'gs_' + key} style="font-weight: bold">${title_case(key)}</label>

View File

@ -1,4 +1,4 @@
/* W3.CSS 4.15 December 2020 by Jan Egil and Borge Refsnes */
/* W3.CSS 5.02 March 31 2025 by Jan Egil and Borge Refsnes */
html{box-sizing:border-box}*,*:before,*:after{box-sizing:inherit}
/* Extract from normalize.css by Nicolas Gallagher and Jonathan Neal git.io/normalize */
html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}
@ -108,6 +108,8 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
.w3-round-small{border-radius:2px}.w3-round,.w3-round-medium{border-radius:4px}.w3-round-large{border-radius:8px}.w3-round-xlarge{border-radius:16px}.w3-round-xxlarge{border-radius:32px}
.w3-row-padding,.w3-row-padding>.w3-half,.w3-row-padding>.w3-third,.w3-row-padding>.w3-twothird,.w3-row-padding>.w3-threequarter,.w3-row-padding>.w3-quarter,.w3-row-padding>.w3-col{padding:0 8px}
.w3-container,.w3-panel{padding:0.01em 16px}.w3-panel{margin-top:16px;margin-bottom:16px}
.w3-grid{display:grid}.w3-grid-padding{display:grid;gap:16px}.w3-flex{display:flex}
.w3-text-center{text-align:center}.w3-text-bold,.w3-bold{font-weight:bold}.w3-text-italic,.w3-italic{font-style:italic}
.w3-code,.w3-codespan{font-family:Consolas,"courier new";font-size:16px}
.w3-code{width:auto;background-color:#fff;padding:8px 12px;border-left:4px solid #4CAF50;word-wrap:break-word}
.w3-codespan{color:crimson;background-color:#f1f1f1;padding-left:4px;padding-right:4px;font-size:110%}
@ -148,6 +150,7 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
.w3-button:hover{color:#000!important;background-color:#ccc!important}
.w3-transparent,.w3-hover-none:hover{background-color:transparent!important}
.w3-hover-none:hover{box-shadow:none!important}
.w3-rtl{direction:rtl}.w3-ltr{direction:ltr}
/* Colors */
.w3-amber,.w3-hover-amber:hover{color:#000!important;background-color:#ffc107!important}
.w3-aqua,.w3-hover-aqua:hover{color:#000!important;background-color:#00ffff!important}
@ -175,6 +178,19 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
.w3-grey,.w3-hover-grey:hover,.w3-gray,.w3-hover-gray:hover{color:#000!important;background-color:#9e9e9e!important}
.w3-light-grey,.w3-hover-light-grey:hover,.w3-light-gray,.w3-hover-light-gray:hover{color:#000!important;background-color:#f1f1f1!important}
.w3-dark-grey,.w3-hover-dark-grey:hover,.w3-dark-gray,.w3-hover-dark-gray:hover{color:#fff!important;background-color:#616161!important}
.w3-asphalt,.w3-hover-asphalt:hover{color:#fff!important;background-color:#343a40!important}
.w3-crimson,.w3-hover-crimson:hover{color:#fff!important;background-color:#a20025!important}
.w3-cobalt,w3-hover-cobalt:hover{color:#fff!important;background-color:#0050ef!important}
.w3-emerald,.w3-hover-emerald:hover{color:#fff!important;background-color:#008a00!important}
.w3-olive,.w3-hover-olive:hover{color:#fff!important;background-color:#6d8764!important}
.w3-paper,.w3-hover-paper:hover{color:#000!important;background-color:#f8f9fa!important}
.w3-sienna,.w3-hover-sienna:hover{color:#fff!important;background-color:#a0522d!important}
.w3-taupe,.w3-hover-taupe:hover{color:#fff!important;background-color:#87794e!important}
.w3-danger{color:#fff!important;background-color:#dd0000!important}
.w3-note{color:#000!important;background-color:#fff599!important}
.w3-info{color:#fff!important;background-color:#0a6fc2!important}
.w3-warning{color:#000!important;background-color:#ffb305!important}
.w3-success{color:#fff!important;background-color:#008a00!important}
.w3-pale-red,.w3-hover-pale-red:hover{color:#000!important;background-color:#ffdddd!important}
.w3-pale-green,.w3-hover-pale-green:hover{color:#000!important;background-color:#ddffdd!important}
.w3-pale-yellow,.w3-hover-pale-yellow:hover{color:#000!important;background-color:#ffffcc!important}
@ -232,4 +248,4 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
.w3-border-light-grey,.w3-hover-border-light-grey:hover,.w3-border-light-gray,.w3-hover-border-light-gray:hover{border-color:#f1f1f1!important}
.w3-border-dark-grey,.w3-hover-border-dark-grey:hover,.w3-border-dark-gray,.w3-hover-border-dark-gray:hover{border-color:#616161!important}
.w3-border-pale-red,.w3-hover-border-pale-red:hover{border-color:#ffe7e7!important}.w3-border-pale-green,.w3-hover-border-pale-green:hover{border-color:#e7ffe7!important}
.w3-border-pale-yellow,.w3-hover-border-pale-yellow:hover{border-color:#ffffcc!important}.w3-border-pale-blue,.w3-hover-border-pale-blue:hover{border-color:#e7ffff!important}
.w3-border-pale-yellow,.w3-hover-border-pale-yellow:hover{border-color:#ffffcc!important}.w3-border-pale-blue,.w3-hover-border-pale-blue:hover{border-color:#e7ffff!important}

View File

@ -1,4 +1,4 @@
/* W3.CSS 4.15 December 2020 by Jan Egil and Borge Refsnes */
/* W3.CSS 5.02 March 31 2025 by Jan Egil and Borge Refsnes */
html{box-sizing:border-box}*,*:before,*:after{box-sizing:inherit}
/* Extract from normalize.css by Nicolas Gallagher and Jonathan Neal git.io/normalize */
html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}
@ -108,6 +108,8 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
.w3-round-small{border-radius:2px}.w3-round,.w3-round-medium{border-radius:4px}.w3-round-large{border-radius:8px}.w3-round-xlarge{border-radius:16px}.w3-round-xxlarge{border-radius:32px}
.w3-row-padding,.w3-row-padding>.w3-half,.w3-row-padding>.w3-third,.w3-row-padding>.w3-twothird,.w3-row-padding>.w3-threequarter,.w3-row-padding>.w3-quarter,.w3-row-padding>.w3-col{padding:0 8px}
.w3-container,.w3-panel{padding:0.01em 16px}.w3-panel{margin-top:16px;margin-bottom:16px}
.w3-grid{display:grid}.w3-grid-padding{display:grid;gap:16px}.w3-flex{display:flex}
.w3-text-center{text-align:center}.w3-text-bold,.w3-bold{font-weight:bold}.w3-text-italic,.w3-italic{font-style:italic}
.w3-code,.w3-codespan{font-family:Consolas,"courier new";font-size:16px}
.w3-code{width:auto;background-color:#fff;padding:8px 12px;border-left:4px solid #4CAF50;word-wrap:break-word}
.w3-codespan{color:crimson;background-color:#f1f1f1;padding-left:4px;padding-right:4px;font-size:110%}
@ -148,6 +150,7 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
.w3-button:hover{color:#000!important;background-color:#ccc!important}
.w3-transparent,.w3-hover-none:hover{background-color:transparent!important}
.w3-hover-none:hover{box-shadow:none!important}
.w3-rtl{direction:rtl}.w3-ltr{direction:ltr}
/* Colors */
.w3-amber,.w3-hover-amber:hover{color:#000!important;background-color:#ffc107!important}
.w3-aqua,.w3-hover-aqua:hover{color:#000!important;background-color:#00ffff!important}
@ -175,6 +178,19 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
.w3-grey,.w3-hover-grey:hover,.w3-gray,.w3-hover-gray:hover{color:#000!important;background-color:#9e9e9e!important}
.w3-light-grey,.w3-hover-light-grey:hover,.w3-light-gray,.w3-hover-light-gray:hover{color:#000!important;background-color:#f1f1f1!important}
.w3-dark-grey,.w3-hover-dark-grey:hover,.w3-dark-gray,.w3-hover-dark-gray:hover{color:#fff!important;background-color:#616161!important}
.w3-asphalt,.w3-hover-asphalt:hover{color:#fff!important;background-color:#343a40!important}
.w3-crimson,.w3-hover-crimson:hover{color:#fff!important;background-color:#a20025!important}
.w3-cobalt,w3-hover-cobalt:hover{color:#fff!important;background-color:#0050ef!important}
.w3-emerald,.w3-hover-emerald:hover{color:#fff!important;background-color:#008a00!important}
.w3-olive,.w3-hover-olive:hover{color:#fff!important;background-color:#6d8764!important}
.w3-paper,.w3-hover-paper:hover{color:#000!important;background-color:#f8f9fa!important}
.w3-sienna,.w3-hover-sienna:hover{color:#fff!important;background-color:#a0522d!important}
.w3-taupe,.w3-hover-taupe:hover{color:#fff!important;background-color:#87794e!important}
.w3-danger{color:#fff!important;background-color:#dd0000!important}
.w3-note{color:#000!important;background-color:#fff599!important}
.w3-info{color:#fff!important;background-color:#0a6fc2!important}
.w3-warning{color:#000!important;background-color:#ffb305!important}
.w3-success{color:#fff!important;background-color:#008a00!important}
.w3-pale-red,.w3-hover-pale-red:hover{color:#000!important;background-color:#ffdddd!important}
.w3-pale-green,.w3-hover-pale-green:hover{color:#000!important;background-color:#ddffdd!important}
.w3-pale-yellow,.w3-hover-pale-yellow:hover{color:#000!important;background-color:#ffffcc!important}
@ -232,4 +248,4 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
.w3-border-light-grey,.w3-hover-border-light-grey:hover,.w3-border-light-gray,.w3-hover-border-light-gray:hover{border-color:#f1f1f1!important}
.w3-border-dark-grey,.w3-hover-border-dark-grey:hover,.w3-border-dark-gray,.w3-hover-border-dark-gray:hover{border-color:#616161!important}
.w3-border-pale-red,.w3-hover-border-pale-red:hover{border-color:#ffe7e7!important}.w3-border-pale-green,.w3-hover-border-pale-green:hover{border-color:#e7ffe7!important}
.w3-border-pale-yellow,.w3-hover-border-pale-yellow:hover{border-color:#ffffcc!important}.w3-border-pale-blue,.w3-hover-border-pale-blue:hover{border-color:#e7ffff!important}
.w3-border-pale-yellow,.w3-hover-border-pale-yellow:hover{border-color:#ffffcc!important}.w3-border-pale-blue,.w3-hover-border-pale-blue:hover{border-color:#e7ffff!important}

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 @@
/* W3.CSS 4.15 December 2020 by Jan Egil and Borge Refsnes */
/* W3.CSS 5.02 March 31 2025 by Jan Egil and Borge Refsnes */
html{box-sizing:border-box}*,*:before,*:after{box-sizing:inherit}
/* Extract from normalize.css by Nicolas Gallagher and Jonathan Neal git.io/normalize */
html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}
@ -108,6 +108,8 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
.w3-round-small{border-radius:2px}.w3-round,.w3-round-medium{border-radius:4px}.w3-round-large{border-radius:8px}.w3-round-xlarge{border-radius:16px}.w3-round-xxlarge{border-radius:32px}
.w3-row-padding,.w3-row-padding>.w3-half,.w3-row-padding>.w3-third,.w3-row-padding>.w3-twothird,.w3-row-padding>.w3-threequarter,.w3-row-padding>.w3-quarter,.w3-row-padding>.w3-col{padding:0 8px}
.w3-container,.w3-panel{padding:0.01em 16px}.w3-panel{margin-top:16px;margin-bottom:16px}
.w3-grid{display:grid}.w3-grid-padding{display:grid;gap:16px}.w3-flex{display:flex}
.w3-text-center{text-align:center}.w3-text-bold,.w3-bold{font-weight:bold}.w3-text-italic,.w3-italic{font-style:italic}
.w3-code,.w3-codespan{font-family:Consolas,"courier new";font-size:16px}
.w3-code{width:auto;background-color:#fff;padding:8px 12px;border-left:4px solid #4CAF50;word-wrap:break-word}
.w3-codespan{color:crimson;background-color:#f1f1f1;padding-left:4px;padding-right:4px;font-size:110%}
@ -148,6 +150,7 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
.w3-button:hover{color:#000!important;background-color:#ccc!important}
.w3-transparent,.w3-hover-none:hover{background-color:transparent!important}
.w3-hover-none:hover{box-shadow:none!important}
.w3-rtl{direction:rtl}.w3-ltr{direction:ltr}
/* Colors */
.w3-amber,.w3-hover-amber:hover{color:#000!important;background-color:#ffc107!important}
.w3-aqua,.w3-hover-aqua:hover{color:#000!important;background-color:#00ffff!important}
@ -175,6 +178,19 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
.w3-grey,.w3-hover-grey:hover,.w3-gray,.w3-hover-gray:hover{color:#000!important;background-color:#9e9e9e!important}
.w3-light-grey,.w3-hover-light-grey:hover,.w3-light-gray,.w3-hover-light-gray:hover{color:#000!important;background-color:#f1f1f1!important}
.w3-dark-grey,.w3-hover-dark-grey:hover,.w3-dark-gray,.w3-hover-dark-gray:hover{color:#fff!important;background-color:#616161!important}
.w3-asphalt,.w3-hover-asphalt:hover{color:#fff!important;background-color:#343a40!important}
.w3-crimson,.w3-hover-crimson:hover{color:#fff!important;background-color:#a20025!important}
.w3-cobalt,w3-hover-cobalt:hover{color:#fff!important;background-color:#0050ef!important}
.w3-emerald,.w3-hover-emerald:hover{color:#fff!important;background-color:#008a00!important}
.w3-olive,.w3-hover-olive:hover{color:#fff!important;background-color:#6d8764!important}
.w3-paper,.w3-hover-paper:hover{color:#000!important;background-color:#f8f9fa!important}
.w3-sienna,.w3-hover-sienna:hover{color:#fff!important;background-color:#a0522d!important}
.w3-taupe,.w3-hover-taupe:hover{color:#fff!important;background-color:#87794e!important}
.w3-danger{color:#fff!important;background-color:#dd0000!important}
.w3-note{color:#000!important;background-color:#fff599!important}
.w3-info{color:#fff!important;background-color:#0a6fc2!important}
.w3-warning{color:#000!important;background-color:#ffb305!important}
.w3-success{color:#fff!important;background-color:#008a00!important}
.w3-pale-red,.w3-hover-pale-red:hover{color:#000!important;background-color:#ffdddd!important}
.w3-pale-green,.w3-hover-pale-green:hover{color:#000!important;background-color:#ddffdd!important}
.w3-pale-yellow,.w3-hover-pale-yellow:hover{color:#000!important;background-color:#ffffcc!important}
@ -232,4 +248,4 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
.w3-border-light-grey,.w3-hover-border-light-grey:hover,.w3-border-light-gray,.w3-hover-border-light-gray:hover{border-color:#f1f1f1!important}
.w3-border-dark-grey,.w3-hover-border-dark-grey:hover,.w3-border-dark-gray,.w3-hover-border-dark-gray:hover{border-color:#616161!important}
.w3-border-pale-red,.w3-hover-border-pale-red:hover{border-color:#ffe7e7!important}.w3-border-pale-green,.w3-hover-border-pale-green:hover{border-color:#e7ffe7!important}
.w3-border-pale-yellow,.w3-hover-border-pale-yellow:hover{border-color:#ffffcc!important}.w3-border-pale-blue,.w3-hover-border-pale-blue:hover{border-color:#e7ffff!important}
.w3-border-pale-yellow,.w3-hover-border-pale-yellow:hover{border-color:#ffffcc!important}.w3-border-pale-blue,.w3-hover-border-pale-blue:hover{border-color:#e7ffff!important}

5
apps/intro.json Normal file
View File

@ -0,0 +1,5 @@
{
"type": "tildefriends-app",
"emoji": "💡",
"previous": "&ckE7T/dt9f1xV8jNSgXVcXYkAzMhU9689MRQbhOi9Wo=.sha256"
}

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

@ -0,0 +1,13 @@
import * as tfrpc from '/tfrpc.js';
async function main() {
await app.setDocument(utf8Decode(getFile('index.html')));
}
tfrpc.register(async function complete() {
if ((await core.globalSettingsGet('index')) == '/~core/intro/') {
return await core.globalSettingsSet('index', '/~core/ssb/');
}
});
main();

286
apps/intro/index.html Normal file
View File

@ -0,0 +1,286 @@
<!doctype html>
<html style="height: 100%; margin: 0; padding: 0; box-sizing: border-box">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" type="text/css" href="w3.css" />
<style>
.slide {
display: none;
margin-left: auto;
margin-right: auto;
}
.dot {
width: 1em;
height: 1em;
cursor: pointer;
}
.w3-left,
.w3-right {
cursor: pointer;
}
</style>
</head>
<body
style="
width: 100%;
height: 100%;
max-width: 100%;
max-height: 100%;
margin: 0;
padding: 0;
flex-direction: column;
align-items: center;
"
class="w3-flex w3-dark-gray w3-center"
>
<div
style="
flex: 1 1 auto;
overflow: auto;
contain: content;
padding-top: 16px;
padding-bottom: 16px;
"
>
<div class="slide">
<div
class="w3-content w3-xlarge w3-card-4 w3-blue w3-panel w3-padding-32 w3-round-xlarge"
style="margin: 32px"
>
<div>
<div>Welcome to</div>
<div>~😎 Tilde Friends.</div>
</div>
<footer>
<button class="w3-button w3-yellow proceed">Next</button>
</footer>
</div>
</div>
<div class="slide w3-card-4 w3-gray" style="width: 90%">
<header class="w3-container w3-blue w3-xlarge">
<h1>This brief tutorial will introduce:</h1>
</header>
<ul class="w3-large w3-left-align">
<li><b>Secure Scuttlebutt</b>, a decentralized social network.</li>
<li>
<b>Tilde Friends</b>, the application platform that you are using
right now.
</li>
<li>
<b>How to get started</b> if you want to get gossiping right away.
</li>
</ul>
<footer class="w3-center w3-xlarge w3-padding">
<button class="w3-button w3-yellow proceed">Onward</button>
</footer>
</div>
<div class="slide w3-gray" style="width: 90%">
<div class="w3-card-4 w3-xlarge">
<header class="w3-container w3-blue">
<h1>💻Secure Scuttlebutt in a Nutshell🦀</h1>
</header>
<div class="w3-container w3-large w3-left-align">
<p>
Secure Scuttlebutt is a social network whose technical operation
attempts to mirrors human social interaction.
</p>
<ul>
<li>
You can create your own account and post to your own feed on
your own device. This is all <b>local</b> with no external
communication. This puts you fully in control of your own words
and actions.
</li>
<li>
Before you can interact with others, you need to
<b>connect over the network</b>, either directly to your friends
(i.e., peer-to-peer between your phones on coffee shop Wi-Fi) or
to 🚪<i>rooms</i> and 🍻<i>pubs</i> (hint: search the web for
<i>#ssbroom</i>).
</li>
<li>
Who you choose to <b>follow</b> determines what you see, with
most people choosing to see messages from friends and friends of
those friends. If you encounter content you'd rather not see,
<b>block</b> the offending account to improve the experience for
you and your followers.
</li>
<li>
Your feed is an <b>immutable</b> log of your activity. Post with
care, because like your words in real life, posts can't be taken
back.
</li>
</ul>
</div>
<footer class="w3-center w3-xlarge w3-padding">
<a
class="w3-button w3-light-gray"
href="https://scuttlebutt.nz/"
target="_blank"
>See scuttlebutt.nz</a
>
<button class="w3-button w3-yellow proceed">Got It</button>
</footer>
</div>
</div>
<div class="slide w3-gray" style="width: 90%">
<div class="w3-card-4 w3-xlarge">
<header class="w3-container w3-blue w3-center">
<h1>~😎 Let's Talk Tilde Friends ~😎</h1>
</header>
<div class="w3-container w3-large w3-left-align">
<p>
Tilde Friends is an application platform that is an application of
its own.
</p>
<ul>
<li>
This intro is a Tilde Friends app. You can click <b>edit</b> at
the top to look under the hood and make changes.
</li>
<li>
It is already possible to make and share new applications using
only Tilde Friends and Secure Scuttlebutt without having to set
up development environments, configure web servers, register
domain names, or pay for hosting services.
</li>
<li>
But it's also set up so that you can't just break an app that
everybody is using or do malicious things with personal content.
There are <b>protections</b> in place like an operating system.
The intent is also for it to be <b>safe</b> to run strange apps
without worrying about adverse effects.
</li>
<li>
But this is all a big 🚧work in progress🚧 and
<b>experiment</b>. Let's see where it takes us.
</li>
</ul>
</div>
<footer class="w3-center w3-xlarge w3-padding">
<button class="w3-button w3-yellow proceed">Okay</button>
</footer>
</div>
</div>
<div class="slide w3-gray" style="width: 90%">
<div class="w3-card-4 w3-xlarge">
<header class="w3-container w3-blue w3-center">
<h1>🦀Let's Get this Tilde Friends Party Started🎉</h1>
</header>
<div class="w3-container w3-large w3-left-align">
<p>The button below will take you to the Secure Scuttlebutt app.</p>
<ul>
<li>
Remember:
<ol>
<li>You are in charge. This is all on your device.</li>
<li>
Make network connections to exchange messages with others.
</li>
<li>
Follow more accounts to see more content, and block those
posting content you'd rather not see.
</li>
<li>
Be respectful, and consider the consequences of what you
post.
</li>
<li>
This is all under active development. Exercise patience, and
report issues encountered.
</li>
</ol>
</li>
<li>
To see this tutorial again later, select <b>apps</b> -&gt;
<b>Core Apps</b> -&gt; <b>intro</b>.
</li>
</ul>
</div>
<footer class="w3-center w3-xlarge w3-padding">
<button class="w3-button w3-yellow" id="complete">Let's Go!</button>
</footer>
</div>
</div>
</div>
<div
class="w3-text-white w3-xlarge w3-center w3-flex"
style="
width: 100%;
flex: 0 1;
flex-direction: row;
align-items: center;
gap: 8px;
"
>
<div class="w3-jumbo" id="left" style="flex: 1 0; cursor: pointer">
&#10094;
</div>
<span class="w3-badge dot w3-border w3-hover-yellow"></span>
<span class="w3-badge dot w3-border w3-hover-yellow"></span>
<span class="w3-badge dot w3-border w3-hover-yellow"></span>
<span class="w3-badge dot w3-border w3-hover-yellow"></span>
<span class="w3-badge dot w3-border w3-hover-yellow"></span>
<div class="w3-jumbo" style="flex: 1 0; cursor: pointer" id="right">
&#10095;
</div>
</div>
<script type="module">
import * as tfrpc from '/static/tfrpc.js';
let index = 0;
function set(i) {
show(i - index);
}
function show(delta) {
let slides = [...document.getElementsByClassName('slide')];
let dots = [...document.getElementsByClassName('dot')];
index = (index + delta + slides.length) % slides.length;
for (let slide of slides) {
slide.style.display =
slides.indexOf(slide) == index ? 'block' : 'none';
}
for (let dot of dots) {
if (dots.indexOf(dot) == index) {
dot.classList.add('w3-white');
} else {
dot.classList.remove('w3-white');
}
}
document.getElementById('left').style.visibility =
index == 0 ? 'hidden' : 'visible';
document.getElementById('right').style.visibility =
index == slides.length - 1 ? 'hidden' : 'visible';
}
let dots = [...document.getElementsByClassName('dot')];
for (let dot of dots) {
dot.onclick = () => set(dots.indexOf(dot));
}
for (let button of document.getElementsByClassName('proceed')) {
button.onclick = () => show(1);
}
document.getElementById('left').onclick = () => show(-1);
document.getElementById('right').onclick = () => show(1);
document.getElementById('complete').onclick = function () {
console.log('completing');
tfrpc.rpc.complete().finally(function () {
console.log('completed');
let a = document.createElement('a');
a.href = '/~core/ssb/';
a.target = '_top';
document.body.appendChild(a);
a.click();
});
};
window.addEventListener('keyup', function (event) {
if (event.key == 'ArrowLeft') {
show(-1);
} else if (event.key == 'ArrowRight') {
show(1);
}
});
show(0);
</script>
</body>
</html>

251
apps/intro/w3.css Normal file
View File

@ -0,0 +1,251 @@
/* W3.CSS 5.02 March 31 2025 by Jan Egil and Borge Refsnes */
html{box-sizing:border-box}*,*:before,*:after{box-sizing:inherit}
/* Extract from normalize.css by Nicolas Gallagher and Jonathan Neal git.io/normalize */
html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}
article,aside,details,figcaption,figure,footer,header,main,menu,nav,section{display:block}summary{display:list-item}
audio,canvas,progress,video{display:inline-block}progress{vertical-align:baseline}
audio:not([controls]){display:none;height:0}[hidden],template{display:none}
a{background-color:transparent}a:active,a:hover{outline-width:0}
abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}
b,strong{font-weight:bolder}dfn{font-style:italic}mark{background:#ff0;color:#000}
small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}
sub{bottom:-0.25em}sup{top:-0.5em}figure{margin:1em 40px}img{border-style:none}
code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}hr{box-sizing:content-box;height:0;overflow:visible}
button,input,select,textarea,optgroup{font:inherit;margin:0}optgroup{font-weight:bold}
button,input{overflow:visible}button,select{text-transform:none}
button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button}
button::-moz-focus-inner,[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner{border-style:none;padding:0}
button:-moz-focusring,[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring{outline:1px dotted ButtonText}
fieldset{border:1px solid #c0c0c0;margin:0 2px;padding:.35em .625em .75em}
legend{color:inherit;display:table;max-width:100%;padding:0;white-space:normal}textarea{overflow:auto}
[type=checkbox],[type=radio]{padding:0}
[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}
[type=search]{-webkit-appearance:textfield;outline-offset:-2px}
[type=search]::-webkit-search-decoration{-webkit-appearance:none}
::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}
/* End extract */
html,body{font-family:Verdana,sans-serif;font-size:15px;line-height:1.5}html{overflow-x:hidden}
h1{font-size:36px}h2{font-size:30px}h3{font-size:24px}h4{font-size:20px}h5{font-size:18px}h6{font-size:16px}
.w3-serif{font-family:serif}.w3-sans-serif{font-family:sans-serif}.w3-cursive{font-family:cursive}.w3-monospace{font-family:monospace}
h1,h2,h3,h4,h5,h6{font-family:"Segoe UI",Arial,sans-serif;font-weight:400;margin:10px 0}.w3-wide{letter-spacing:4px}
hr{border:0;border-top:1px solid #eee;margin:20px 0}
.w3-image{max-width:100%;height:auto}img{vertical-align:middle}a{color:inherit}
.w3-table,.w3-table-all{border-collapse:collapse;border-spacing:0;width:100%;display:table}.w3-table-all{border:1px solid #ccc}
.w3-bordered tr,.w3-table-all tr{border-bottom:1px solid #ddd}.w3-striped tbody tr:nth-child(even){background-color:#f1f1f1}
.w3-table-all tr:nth-child(odd){background-color:#fff}.w3-table-all tr:nth-child(even){background-color:#f1f1f1}
.w3-hoverable tbody tr:hover,.w3-ul.w3-hoverable li:hover{background-color:#ccc}.w3-centered tr th,.w3-centered tr td{text-align:center}
.w3-table td,.w3-table th,.w3-table-all td,.w3-table-all th{padding:8px 8px;display:table-cell;text-align:left;vertical-align:top}
.w3-table th:first-child,.w3-table td:first-child,.w3-table-all th:first-child,.w3-table-all td:first-child{padding-left:16px}
.w3-btn,.w3-button{border:none;display:inline-block;padding:8px 16px;vertical-align:middle;overflow:hidden;text-decoration:none;color:inherit;background-color:inherit;text-align:center;cursor:pointer;white-space:nowrap}
.w3-btn:hover{box-shadow:0 8px 16px 0 rgba(0,0,0,0.2),0 6px 20px 0 rgba(0,0,0,0.19)}
.w3-btn,.w3-button{-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}
.w3-disabled,.w3-btn:disabled,.w3-button:disabled{cursor:not-allowed;opacity:0.3}.w3-disabled *,:disabled *{pointer-events:none}
.w3-btn.w3-disabled:hover,.w3-btn:disabled:hover{box-shadow:none}
.w3-badge,.w3-tag{background-color:#000;color:#fff;display:inline-block;padding-left:8px;padding-right:8px;text-align:center}.w3-badge{border-radius:50%}
.w3-ul{list-style-type:none;padding:0;margin:0}.w3-ul li{padding:8px 16px;border-bottom:1px solid #ddd}.w3-ul li:last-child{border-bottom:none}
.w3-tooltip,.w3-display-container{position:relative}.w3-tooltip .w3-text{display:none}.w3-tooltip:hover .w3-text{display:inline-block}
.w3-ripple:active{opacity:0.5}.w3-ripple{transition:opacity 0s}
.w3-input{padding:8px;display:block;border:none;border-bottom:1px solid #ccc;width:100%}
.w3-select{padding:9px 0;width:100%;border:none;border-bottom:1px solid #ccc}
.w3-dropdown-click,.w3-dropdown-hover{position:relative;display:inline-block;cursor:pointer}
.w3-dropdown-hover:hover .w3-dropdown-content{display:block}
.w3-dropdown-hover:first-child,.w3-dropdown-click:hover{background-color:#ccc;color:#000}
.w3-dropdown-hover:hover > .w3-button:first-child,.w3-dropdown-click:hover > .w3-button:first-child{background-color:#ccc;color:#000}
.w3-dropdown-content{cursor:auto;color:#000;background-color:#fff;display:none;position:absolute;min-width:160px;margin:0;padding:0;z-index:1}
.w3-check,.w3-radio{width:24px;height:24px;position:relative;top:6px}
.w3-sidebar{height:100%;width:200px;background-color:#fff;position:fixed!important;z-index:1;overflow:auto}
.w3-bar-block .w3-dropdown-hover,.w3-bar-block .w3-dropdown-click{width:100%}
.w3-bar-block .w3-dropdown-hover .w3-dropdown-content,.w3-bar-block .w3-dropdown-click .w3-dropdown-content{min-width:100%}
.w3-bar-block .w3-dropdown-hover .w3-button,.w3-bar-block .w3-dropdown-click .w3-button{width:100%;text-align:left;padding:8px 16px}
.w3-main,#main{transition:margin-left .4s}
.w3-modal{z-index:3;display:none;padding-top:100px;position:fixed;left:0;top:0;width:100%;height:100%;overflow:auto;background-color:rgb(0,0,0);background-color:rgba(0,0,0,0.4)}
.w3-modal-content{margin:auto;background-color:#fff;position:relative;padding:0;outline:0;width:600px}
.w3-bar{width:100%;overflow:hidden}.w3-center .w3-bar{display:inline-block;width:auto}
.w3-bar .w3-bar-item{padding:8px 16px;float:left;width:auto;border:none;display:block;outline:0}
.w3-bar .w3-dropdown-hover,.w3-bar .w3-dropdown-click{position:static;float:left}
.w3-bar .w3-button{white-space:normal}
.w3-bar-block .w3-bar-item{width:100%;display:block;padding:8px 16px;text-align:left;border:none;white-space:normal;float:none;outline:0}
.w3-bar-block.w3-center .w3-bar-item{text-align:center}.w3-block{display:block;width:100%}
.w3-responsive{display:block;overflow-x:auto}
.w3-container:after,.w3-container:before,.w3-panel:after,.w3-panel:before,.w3-row:after,.w3-row:before,.w3-row-padding:after,.w3-row-padding:before,
.w3-cell-row:before,.w3-cell-row:after,.w3-clear:after,.w3-clear:before,.w3-bar:before,.w3-bar:after{content:"";display:table;clear:both}
.w3-col,.w3-half,.w3-third,.w3-twothird,.w3-threequarter,.w3-quarter{float:left;width:100%}
.w3-col.s1{width:8.33333%}.w3-col.s2{width:16.66666%}.w3-col.s3{width:24.99999%}.w3-col.s4{width:33.33333%}
.w3-col.s5{width:41.66666%}.w3-col.s6{width:49.99999%}.w3-col.s7{width:58.33333%}.w3-col.s8{width:66.66666%}
.w3-col.s9{width:74.99999%}.w3-col.s10{width:83.33333%}.w3-col.s11{width:91.66666%}.w3-col.s12{width:99.99999%}
@media (min-width:601px){.w3-col.m1{width:8.33333%}.w3-col.m2{width:16.66666%}.w3-col.m3,.w3-quarter{width:24.99999%}.w3-col.m4,.w3-third{width:33.33333%}
.w3-col.m5{width:41.66666%}.w3-col.m6,.w3-half{width:49.99999%}.w3-col.m7{width:58.33333%}.w3-col.m8,.w3-twothird{width:66.66666%}
.w3-col.m9,.w3-threequarter{width:74.99999%}.w3-col.m10{width:83.33333%}.w3-col.m11{width:91.66666%}.w3-col.m12{width:99.99999%}}
@media (min-width:993px){.w3-col.l1{width:8.33333%}.w3-col.l2{width:16.66666%}.w3-col.l3{width:24.99999%}.w3-col.l4{width:33.33333%}
.w3-col.l5{width:41.66666%}.w3-col.l6{width:49.99999%}.w3-col.l7{width:58.33333%}.w3-col.l8{width:66.66666%}
.w3-col.l9{width:74.99999%}.w3-col.l10{width:83.33333%}.w3-col.l11{width:91.66666%}.w3-col.l12{width:99.99999%}}
.w3-rest{overflow:hidden}.w3-stretch{margin-left:-16px;margin-right:-16px}
.w3-content,.w3-auto{margin-left:auto;margin-right:auto}.w3-content{max-width:980px}.w3-auto{max-width:1140px}
.w3-cell-row{display:table;width:100%}.w3-cell{display:table-cell}
.w3-cell-top{vertical-align:top}.w3-cell-middle{vertical-align:middle}.w3-cell-bottom{vertical-align:bottom}
.w3-hide{display:none!important}.w3-show-block,.w3-show{display:block!important}.w3-show-inline-block{display:inline-block!important}
@media (max-width:1205px){.w3-auto{max-width:95%}}
@media (max-width:600px){.w3-modal-content{margin:0 10px;width:auto!important}.w3-modal{padding-top:30px}
.w3-dropdown-hover.w3-mobile .w3-dropdown-content,.w3-dropdown-click.w3-mobile .w3-dropdown-content{position:relative}
.w3-hide-small{display:none!important}.w3-mobile{display:block;width:100%!important}.w3-bar-item.w3-mobile,.w3-dropdown-hover.w3-mobile,.w3-dropdown-click.w3-mobile{text-align:center}
.w3-dropdown-hover.w3-mobile,.w3-dropdown-hover.w3-mobile .w3-btn,.w3-dropdown-hover.w3-mobile .w3-button,.w3-dropdown-click.w3-mobile,.w3-dropdown-click.w3-mobile .w3-btn,.w3-dropdown-click.w3-mobile .w3-button{width:100%}}
@media (max-width:768px){.w3-modal-content{width:500px}.w3-modal{padding-top:50px}}
@media (min-width:993px){.w3-modal-content{width:900px}.w3-hide-large{display:none!important}.w3-sidebar.w3-collapse{display:block!important}}
@media (max-width:992px) and (min-width:601px){.w3-hide-medium{display:none!important}}
@media (max-width:992px){.w3-sidebar.w3-collapse{display:none}.w3-main{margin-left:0!important;margin-right:0!important}.w3-auto{max-width:100%}}
.w3-top,.w3-bottom{position:fixed;width:100%;z-index:1}.w3-top{top:0}.w3-bottom{bottom:0}
.w3-overlay{position:fixed;display:none;width:100%;height:100%;top:0;left:0;right:0;bottom:0;background-color:rgba(0,0,0,0.5);z-index:2}
.w3-display-topleft{position:absolute;left:0;top:0}.w3-display-topright{position:absolute;right:0;top:0}
.w3-display-bottomleft{position:absolute;left:0;bottom:0}.w3-display-bottomright{position:absolute;right:0;bottom:0}
.w3-display-middle{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);-ms-transform:translate(-50%,-50%)}
.w3-display-left{position:absolute;top:50%;left:0%;transform:translate(0%,-50%);-ms-transform:translate(-0%,-50%)}
.w3-display-right{position:absolute;top:50%;right:0%;transform:translate(0%,-50%);-ms-transform:translate(0%,-50%)}
.w3-display-topmiddle{position:absolute;left:50%;top:0;transform:translate(-50%,0%);-ms-transform:translate(-50%,0%)}
.w3-display-bottommiddle{position:absolute;left:50%;bottom:0;transform:translate(-50%,0%);-ms-transform:translate(-50%,0%)}
.w3-display-container:hover .w3-display-hover{display:block}.w3-display-container:hover span.w3-display-hover{display:inline-block}.w3-display-hover{display:none}
.w3-display-position{position:absolute}
.w3-circle{border-radius:50%}
.w3-round-small{border-radius:2px}.w3-round,.w3-round-medium{border-radius:4px}.w3-round-large{border-radius:8px}.w3-round-xlarge{border-radius:16px}.w3-round-xxlarge{border-radius:32px}
.w3-row-padding,.w3-row-padding>.w3-half,.w3-row-padding>.w3-third,.w3-row-padding>.w3-twothird,.w3-row-padding>.w3-threequarter,.w3-row-padding>.w3-quarter,.w3-row-padding>.w3-col{padding:0 8px}
.w3-container,.w3-panel{padding:0.01em 16px}.w3-panel{margin-top:16px;margin-bottom:16px}
.w3-grid{display:grid}.w3-grid-padding{display:grid;gap:16px}.w3-flex{display:flex}
.w3-text-center{text-align:center}.w3-text-bold,.w3-bold{font-weight:bold}.w3-text-italic,.w3-italic{font-style:italic}
.w3-code,.w3-codespan{font-family:Consolas,"courier new";font-size:16px}
.w3-code{width:auto;background-color:#fff;padding:8px 12px;border-left:4px solid #4CAF50;word-wrap:break-word}
.w3-codespan{color:crimson;background-color:#f1f1f1;padding-left:4px;padding-right:4px;font-size:110%}
.w3-card,.w3-card-2{box-shadow:0 2px 5px 0 rgba(0,0,0,0.16),0 2px 10px 0 rgba(0,0,0,0.12)}
.w3-card-4,.w3-hover-shadow:hover{box-shadow:0 4px 10px 0 rgba(0,0,0,0.2),0 4px 20px 0 rgba(0,0,0,0.19)}
.w3-spin{animation:w3-spin 2s infinite linear}@keyframes w3-spin{0%{transform:rotate(0deg)}100%{transform:rotate(359deg)}}
.w3-animate-fading{animation:fading 10s infinite}@keyframes fading{0%{opacity:0}50%{opacity:1}100%{opacity:0}}
.w3-animate-opacity{animation:opac 0.8s}@keyframes opac{from{opacity:0} to{opacity:1}}
.w3-animate-top{position:relative;animation:animatetop 0.4s}@keyframes animatetop{from{top:-300px;opacity:0} to{top:0;opacity:1}}
.w3-animate-left{position:relative;animation:animateleft 0.4s}@keyframes animateleft{from{left:-300px;opacity:0} to{left:0;opacity:1}}
.w3-animate-right{position:relative;animation:animateright 0.4s}@keyframes animateright{from{right:-300px;opacity:0} to{right:0;opacity:1}}
.w3-animate-bottom{position:relative;animation:animatebottom 0.4s}@keyframes animatebottom{from{bottom:-300px;opacity:0} to{bottom:0;opacity:1}}
.w3-animate-zoom {animation:animatezoom 0.6s}@keyframes animatezoom{from{transform:scale(0)} to{transform:scale(1)}}
.w3-animate-input{transition:width 0.4s ease-in-out}.w3-animate-input:focus{width:100%!important}
.w3-opacity,.w3-hover-opacity:hover{opacity:0.60}.w3-opacity-off,.w3-hover-opacity-off:hover{opacity:1}
.w3-opacity-max{opacity:0.25}.w3-opacity-min{opacity:0.75}
.w3-greyscale-max,.w3-grayscale-max,.w3-hover-greyscale:hover,.w3-hover-grayscale:hover{filter:grayscale(100%)}
.w3-greyscale,.w3-grayscale{filter:grayscale(75%)}.w3-greyscale-min,.w3-grayscale-min{filter:grayscale(50%)}
.w3-sepia{filter:sepia(75%)}.w3-sepia-max,.w3-hover-sepia:hover{filter:sepia(100%)}.w3-sepia-min{filter:sepia(50%)}
.w3-tiny{font-size:10px!important}.w3-small{font-size:12px!important}.w3-medium{font-size:15px!important}.w3-large{font-size:18px!important}
.w3-xlarge{font-size:24px!important}.w3-xxlarge{font-size:36px!important}.w3-xxxlarge{font-size:48px!important}.w3-jumbo{font-size:64px!important}
.w3-left-align{text-align:left!important}.w3-right-align{text-align:right!important}.w3-justify{text-align:justify!important}.w3-center{text-align:center!important}
.w3-border-0{border:0!important}.w3-border{border:1px solid #ccc!important}
.w3-border-top{border-top:1px solid #ccc!important}.w3-border-bottom{border-bottom:1px solid #ccc!important}
.w3-border-left{border-left:1px solid #ccc!important}.w3-border-right{border-right:1px solid #ccc!important}
.w3-topbar{border-top:6px solid #ccc!important}.w3-bottombar{border-bottom:6px solid #ccc!important}
.w3-leftbar{border-left:6px solid #ccc!important}.w3-rightbar{border-right:6px solid #ccc!important}
.w3-section,.w3-code{margin-top:16px!important;margin-bottom:16px!important}
.w3-margin{margin:16px!important}.w3-margin-top{margin-top:16px!important}.w3-margin-bottom{margin-bottom:16px!important}
.w3-margin-left{margin-left:16px!important}.w3-margin-right{margin-right:16px!important}
.w3-padding-small{padding:4px 8px!important}.w3-padding{padding:8px 16px!important}.w3-padding-large{padding:12px 24px!important}
.w3-padding-16{padding-top:16px!important;padding-bottom:16px!important}.w3-padding-24{padding-top:24px!important;padding-bottom:24px!important}
.w3-padding-32{padding-top:32px!important;padding-bottom:32px!important}.w3-padding-48{padding-top:48px!important;padding-bottom:48px!important}
.w3-padding-64{padding-top:64px!important;padding-bottom:64px!important}
.w3-padding-top-64{padding-top:64px!important}.w3-padding-top-48{padding-top:48px!important}
.w3-padding-top-32{padding-top:32px!important}.w3-padding-top-24{padding-top:24px!important}
.w3-left{float:left!important}.w3-right{float:right!important}
.w3-button:hover{color:#000!important;background-color:#ccc!important}
.w3-transparent,.w3-hover-none:hover{background-color:transparent!important}
.w3-hover-none:hover{box-shadow:none!important}
.w3-rtl{direction:rtl}.w3-ltr{direction:ltr}
/* Colors */
.w3-amber,.w3-hover-amber:hover{color:#000!important;background-color:#ffc107!important}
.w3-aqua,.w3-hover-aqua:hover{color:#000!important;background-color:#00ffff!important}
.w3-blue,.w3-hover-blue:hover{color:#fff!important;background-color:#2196F3!important}
.w3-light-blue,.w3-hover-light-blue:hover{color:#000!important;background-color:#87CEEB!important}
.w3-brown,.w3-hover-brown:hover{color:#fff!important;background-color:#795548!important}
.w3-cyan,.w3-hover-cyan:hover{color:#000!important;background-color:#00bcd4!important}
.w3-blue-grey,.w3-hover-blue-grey:hover,.w3-blue-gray,.w3-hover-blue-gray:hover{color:#fff!important;background-color:#607d8b!important}
.w3-green,.w3-hover-green:hover{color:#fff!important;background-color:#4CAF50!important}
.w3-light-green,.w3-hover-light-green:hover{color:#000!important;background-color:#8bc34a!important}
.w3-indigo,.w3-hover-indigo:hover{color:#fff!important;background-color:#3f51b5!important}
.w3-khaki,.w3-hover-khaki:hover{color:#000!important;background-color:#f0e68c!important}
.w3-lime,.w3-hover-lime:hover{color:#000!important;background-color:#cddc39!important}
.w3-orange,.w3-hover-orange:hover{color:#000!important;background-color:#ff9800!important}
.w3-deep-orange,.w3-hover-deep-orange:hover{color:#fff!important;background-color:#ff5722!important}
.w3-pink,.w3-hover-pink:hover{color:#fff!important;background-color:#e91e63!important}
.w3-purple,.w3-hover-purple:hover{color:#fff!important;background-color:#9c27b0!important}
.w3-deep-purple,.w3-hover-deep-purple:hover{color:#fff!important;background-color:#673ab7!important}
.w3-red,.w3-hover-red:hover{color:#fff!important;background-color:#f44336!important}
.w3-sand,.w3-hover-sand:hover{color:#000!important;background-color:#fdf5e6!important}
.w3-teal,.w3-hover-teal:hover{color:#fff!important;background-color:#009688!important}
.w3-yellow,.w3-hover-yellow:hover{color:#000!important;background-color:#ffeb3b!important}
.w3-white,.w3-hover-white:hover{color:#000!important;background-color:#fff!important}
.w3-black,.w3-hover-black:hover{color:#fff!important;background-color:#000!important}
.w3-grey,.w3-hover-grey:hover,.w3-gray,.w3-hover-gray:hover{color:#000!important;background-color:#9e9e9e!important}
.w3-light-grey,.w3-hover-light-grey:hover,.w3-light-gray,.w3-hover-light-gray:hover{color:#000!important;background-color:#f1f1f1!important}
.w3-dark-grey,.w3-hover-dark-grey:hover,.w3-dark-gray,.w3-hover-dark-gray:hover{color:#fff!important;background-color:#616161!important}
.w3-asphalt,.w3-hover-asphalt:hover{color:#fff!important;background-color:#343a40!important}
.w3-crimson,.w3-hover-crimson:hover{color:#fff!important;background-color:#a20025!important}
.w3-cobalt,w3-hover-cobalt:hover{color:#fff!important;background-color:#0050ef!important}
.w3-emerald,.w3-hover-emerald:hover{color:#fff!important;background-color:#008a00!important}
.w3-olive,.w3-hover-olive:hover{color:#fff!important;background-color:#6d8764!important}
.w3-paper,.w3-hover-paper:hover{color:#000!important;background-color:#f8f9fa!important}
.w3-sienna,.w3-hover-sienna:hover{color:#fff!important;background-color:#a0522d!important}
.w3-taupe,.w3-hover-taupe:hover{color:#fff!important;background-color:#87794e!important}
.w3-danger{color:#fff!important;background-color:#dd0000!important}
.w3-note{color:#000!important;background-color:#fff599!important}
.w3-info{color:#fff!important;background-color:#0a6fc2!important}
.w3-warning{color:#000!important;background-color:#ffb305!important}
.w3-success{color:#fff!important;background-color:#008a00!important}
.w3-pale-red,.w3-hover-pale-red:hover{color:#000!important;background-color:#ffdddd!important}
.w3-pale-green,.w3-hover-pale-green:hover{color:#000!important;background-color:#ddffdd!important}
.w3-pale-yellow,.w3-hover-pale-yellow:hover{color:#000!important;background-color:#ffffcc!important}
.w3-pale-blue,.w3-hover-pale-blue:hover{color:#000!important;background-color:#ddffff!important}
.w3-text-amber,.w3-hover-text-amber:hover{color:#ffc107!important}
.w3-text-aqua,.w3-hover-text-aqua:hover{color:#00ffff!important}
.w3-text-blue,.w3-hover-text-blue:hover{color:#2196F3!important}
.w3-text-light-blue,.w3-hover-text-light-blue:hover{color:#87CEEB!important}
.w3-text-brown,.w3-hover-text-brown:hover{color:#795548!important}
.w3-text-cyan,.w3-hover-text-cyan:hover{color:#00bcd4!important}
.w3-text-blue-grey,.w3-hover-text-blue-grey:hover,.w3-text-blue-gray,.w3-hover-text-blue-gray:hover{color:#607d8b!important}
.w3-text-green,.w3-hover-text-green:hover{color:#4CAF50!important}
.w3-text-light-green,.w3-hover-text-light-green:hover{color:#8bc34a!important}
.w3-text-indigo,.w3-hover-text-indigo:hover{color:#3f51b5!important}
.w3-text-khaki,.w3-hover-text-khaki:hover{color:#b4aa50!important}
.w3-text-lime,.w3-hover-text-lime:hover{color:#cddc39!important}
.w3-text-orange,.w3-hover-text-orange:hover{color:#ff9800!important}
.w3-text-deep-orange,.w3-hover-text-deep-orange:hover{color:#ff5722!important}
.w3-text-pink,.w3-hover-text-pink:hover{color:#e91e63!important}
.w3-text-purple,.w3-hover-text-purple:hover{color:#9c27b0!important}
.w3-text-deep-purple,.w3-hover-text-deep-purple:hover{color:#673ab7!important}
.w3-text-red,.w3-hover-text-red:hover{color:#f44336!important}
.w3-text-sand,.w3-hover-text-sand:hover{color:#fdf5e6!important}
.w3-text-teal,.w3-hover-text-teal:hover{color:#009688!important}
.w3-text-yellow,.w3-hover-text-yellow:hover{color:#d2be0e!important}
.w3-text-white,.w3-hover-text-white:hover{color:#fff!important}
.w3-text-black,.w3-hover-text-black:hover{color:#000!important}
.w3-text-grey,.w3-hover-text-grey:hover,.w3-text-gray,.w3-hover-text-gray:hover{color:#757575!important}
.w3-text-light-grey,.w3-hover-text-light-grey:hover,.w3-text-light-gray,.w3-hover-text-light-gray:hover{color:#f1f1f1!important}
.w3-text-dark-grey,.w3-hover-text-dark-grey:hover,.w3-text-dark-gray,.w3-hover-text-dark-gray:hover{color:#3a3a3a!important}
.w3-border-amber,.w3-hover-border-amber:hover{border-color:#ffc107!important}
.w3-border-aqua,.w3-hover-border-aqua:hover{border-color:#00ffff!important}
.w3-border-blue,.w3-hover-border-blue:hover{border-color:#2196F3!important}
.w3-border-light-blue,.w3-hover-border-light-blue:hover{border-color:#87CEEB!important}
.w3-border-brown,.w3-hover-border-brown:hover{border-color:#795548!important}
.w3-border-cyan,.w3-hover-border-cyan:hover{border-color:#00bcd4!important}
.w3-border-blue-grey,.w3-hover-border-blue-grey:hover,.w3-border-blue-gray,.w3-hover-border-blue-gray:hover{border-color:#607d8b!important}
.w3-border-green,.w3-hover-border-green:hover{border-color:#4CAF50!important}
.w3-border-light-green,.w3-hover-border-light-green:hover{border-color:#8bc34a!important}
.w3-border-indigo,.w3-hover-border-indigo:hover{border-color:#3f51b5!important}
.w3-border-khaki,.w3-hover-border-khaki:hover{border-color:#f0e68c!important}
.w3-border-lime,.w3-hover-border-lime:hover{border-color:#cddc39!important}
.w3-border-orange,.w3-hover-border-orange:hover{border-color:#ff9800!important}
.w3-border-deep-orange,.w3-hover-border-deep-orange:hover{border-color:#ff5722!important}
.w3-border-pink,.w3-hover-border-pink:hover{border-color:#e91e63!important}
.w3-border-purple,.w3-hover-border-purple:hover{border-color:#9c27b0!important}
.w3-border-deep-purple,.w3-hover-border-deep-purple:hover{border-color:#673ab7!important}
.w3-border-red,.w3-hover-border-red:hover{border-color:#f44336!important}
.w3-border-sand,.w3-hover-border-sand:hover{border-color:#fdf5e6!important}
.w3-border-teal,.w3-hover-border-teal:hover{border-color:#009688!important}
.w3-border-yellow,.w3-hover-border-yellow:hover{border-color:#ffeb3b!important}
.w3-border-white,.w3-hover-border-white:hover{border-color:#fff!important}
.w3-border-black,.w3-hover-border-black:hover{border-color:#000!important}
.w3-border-grey,.w3-hover-border-grey:hover,.w3-border-gray,.w3-hover-border-gray:hover{border-color:#9e9e9e!important}
.w3-border-light-grey,.w3-hover-border-light-grey:hover,.w3-border-light-gray,.w3-hover-border-light-gray:hover{border-color:#f1f1f1!important}
.w3-border-dark-grey,.w3-hover-border-dark-grey:hover,.w3-border-dark-gray,.w3-hover-border-dark-gray:hover{border-color:#616161!important}
.w3-border-pale-red,.w3-hover-border-pale-red:hover{border-color:#ffe7e7!important}.w3-border-pale-green,.w3-hover-border-pale-green:hover{border-color:#e7ffe7!important}
.w3-border-pale-yellow,.w3-hover-border-pale-yellow:hover{border-color:#ffffcc!important}.w3-border-pale-blue,.w3-hover-border-pale-blue:hover{border-color:#e7ffff!important}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

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,7 +1,9 @@
async function main() {
let host = core.url.match(/.*\/\/(.*?)\//)[1];
print(core.url);
let host = core.url.match(/.*?\/\/([^:/]*)/)[1];
let port = await ssb.port();
let id = (await ssb.getServerIdentity()).substring(1);
let room = `net:${host}:${ssb.port}~shs:${id}:SSB+Room+SK3TLYC2T86EHQCUHBUHASCASE18JBV24=`;
let room = `net:${host}:${port}~shs:${id}:SSB+Room+SK3TLYC2T86EHQCUHBUHASCASE18JBV24=`;
await app.setDocument(`
<body style="color: #fff">
<h1>Server</h1>

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,5 +1,5 @@
{
"type": "tildefriends-app",
"emoji": "🦀",
"previous": "&weo9lucKMSz3xzM6l3knRepNnyRIzKlaJnP9NDJAk+g=.sha256"
"previous": "&0Lxm4IgS3mpvSccP3bg7wNPACtLKMTbie51ea/vJbeg=.sha256"
}

View File

@ -14,23 +14,8 @@ function get_emojis() {
});
}
async function get_recent(author) {
let recent = await tfrpc.rpc.query(
`
SELECT DISTINCT content ->> '$.vote.expression' AS value
FROM messages
WHERE author = ? AND
content ->> '$.type' = 'vote'
ORDER BY timestamp DESC LIMIT 10
`,
[author]
);
return recent.map((x) => x.value);
}
export async function picker(callback, anchor, author) {
export async function picker(callback, anchor, author, recent) {
let json = await get_emojis();
let recent = await get_recent(author);
let div = document.createElement('div');
div.id = 'emoji_picker';

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -11,6 +11,7 @@ class TfElement extends LitElement {
broadcasts: {type: Array},
connections: {type: Array},
loading: {type: Boolean},
loading_about: {type: Number},
loaded: {type: Boolean},
following: {type: Array},
users: {type: Object},
@ -21,6 +22,7 @@ class TfElement extends LitElement {
guest: {type: Boolean},
url: {type: String},
private_messages: {type: Array},
recent_reactions: {type: Array},
};
}
@ -36,11 +38,13 @@ class TfElement extends LitElement {
this.following = [];
this.users = {};
this.loaded = false;
this.loading_about = 0;
this.channels = [];
this.channels_unread = {};
this.channels_latest = {};
this.loading_latest = 0;
this.loading_latest_scheduled = 0;
this.recent_reactions = [];
tfrpc.rpc.getBroadcasts().then((b) => {
self.broadcasts = b || [];
});
@ -149,8 +153,9 @@ class TfElement extends LitElement {
}
async fetch_about(following, users) {
this.loading_about++;
let ids = Object.keys(following).sort();
const k_cache_version = 1;
const k_cache_version = 3;
let cache = await tfrpc.rpc.databaseGet('about');
let original_cache = cache;
cache = cache ? JSON.parse(cache) : {};
@ -158,81 +163,86 @@ class TfElement extends LitElement {
cache = {
version: k_cache_version,
about: {},
last_row_id: 0,
};
}
let max_row_id = (
await tfrpc.rpc.query(
`
SELECT MAX(rowid) AS max_row_id FROM messages
`,
[]
)
)[0].max_row_id;
let ids_out_of_date = ids.filter(
(x) =>
(users[x]?.seq && !cache.about[x]?.seq) ||
(users[x]?.seq && users[x]?.seq > cache.about[x].seq)
);
for (let id of Object.keys(cache.about)) {
if (ids.indexOf(id) == -1) {
delete cache.about[id];
} else {
users[id] = Object.assign(cache.about[id], users[id] || {});
}
}
let abouts = await tfrpc.rpc.query(
`
SELECT
messages.author, json(messages.content) AS content, messages.sequence
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.author, json(messages.content) AS content, messages.sequence
FROM
messages,
json_each(?2) AS following
WHERE
messages.author = following.value AND
messages.rowid <= ?4 AND
json_extract(messages.content, '$.type') = 'about'
ORDER BY messages.author, messages.sequence
`,
[
JSON.stringify(ids.filter((id) => cache.about[id])),
JSON.stringify(ids.filter((id) => !cache.about[id])),
cache.last_row_id,
max_row_id,
]
console.log(
'loading about for',
ids.length,
'accounts',
ids_out_of_date.length,
'out of date'
);
for (let about of abouts) {
let content = JSON.parse(about.content);
if (content.about === about.author) {
delete content.type;
delete content.about;
cache.about[about.author] = Object.assign(
cache.about[about.author] || {},
content
if (ids_out_of_date.length) {
try {
let rows = await tfrpc.rpc.query(
`
SELECT all_abouts.author, json(json_group_object(all_abouts.key, all_abouts.value)) AS about
FROM (
SELECT
messages.author,
fields.key,
RANK() OVER (PARTITION BY messages.author, fields.key ORDER BY messages.sequence DESC) AS rank,
fields.value
FROM messages JOIN json_each(messages.content) AS fields
WHERE
messages.content ->> '$.type' = 'about' AND
messages.content ->> '$.about' = messages.author AND
NOT fields.key IN ('about', 'type')) all_abouts
JOIN json_each(?) AS following ON all_abouts.author = following.value
WHERE rank = 1
GROUP BY all_abouts.author
`,
[JSON.stringify(ids_out_of_date)]
);
users = users || {};
for (let row of rows) {
users[row.author] = Object.assign(
users[row.author] || {},
JSON.parse(row.about)
);
cache.about[row.author] = Object.assign(
{seq: users[row.author].seq},
JSON.parse(row.about)
);
}
} catch (e) {
console.log(e);
}
}
cache.last_row_id = max_row_id;
for (let id of ids_out_of_date) {
if (!cache.about[id]?.seq) {
cache.about[id] = Object.assign(cache.about[id] ?? {}, {
seq: users[id]?.seq ?? 0,
});
}
}
this.loading_about--;
let new_cache = JSON.stringify(cache);
if (new_cache !== original_cache) {
if (new_cache != original_cache) {
let start_time = new Date();
tfrpc.rpc.databaseSet('about', new_cache).then(function () {
console.log('saving about took', (new Date() - start_time) / 1000);
});
}
users = users || {};
for (let id of Object.keys(cache.about)) {
users[id] = Object.assign(
{follow_depth: following[id]?.d},
users[id] || {},
cache.about[id]
);
}
return Object.assign({}, users);
}
@ -382,7 +392,6 @@ class TfElement extends LitElement {
'🔐': latest[0],
});
console.log('private took', (new Date() - start_time) / 1000.0);
console.log(latest);
self.private_messages = latest[1];
});
}
@ -412,43 +421,58 @@ class TfElement extends LitElement {
[JSON.stringify(Object.keys(users))]
);
for (let row of info) {
users[row.author].seq = row.max_seq;
users[row.author].ts = row.max_ts;
users[row.author] = Object.assign(users[row.author], {
seq: row.max_sequence,
ts: row.max_ts,
});
}
return users;
}
async load_recent_reactions() {
this.recent_reactions = (
await tfrpc.rpc.query(
`
SELECT DISTINCT content ->> '$.vote.expression' AS value
FROM messages
WHERE author = ? AND
content ->> '$.type' = 'vote'
ORDER BY timestamp DESC LIMIT 10
`,
[this.whoami]
)
).map((x) => x.value);
}
async load() {
this.loading_latest = true;
try {
let start_time = new Date();
let whoami = this.whoami;
let following = await tfrpc.rpc.following([whoami], 2);
let old_users = this.users ?? {};
let users = {};
let by_count = [];
for (let [id, v] of Object.entries(following)) {
users[id] = {
following: v.of,
blocking: v.ob,
followed: v.if,
blocked: v.ib,
};
users[id] = Object.assign(
{
following: v.of,
blocking: v.ob,
followed: v.if,
blocked: v.ib,
follow_depth: following[id]?.d,
},
old_users[id]
);
by_count.push({count: v.of, id: id});
}
let reactions = this.load_recent_reactions();
this.load_channels_latest(Object.keys(following));
this.channels_unread = JSON.parse(
(await tfrpc.rpc.databaseGet('unread')) ?? '{}'
);
this.following = Object.keys(following);
let about_start_time = new Date();
users = await this.fetch_about(following, users);
console.log(
'about took',
(new Date() - about_start_time) / 1000.0,
'seconds for',
Object.keys(users).length,
'users'
);
start_time = new Date();
users = await this.fetch_user_info(users);
console.log(
@ -457,9 +481,22 @@ class TfElement extends LitElement {
'seconds'
);
this.users = users;
let self = this;
this.fetch_about(following, users).then(function (result) {
self.users = result;
console.log(
'about took',
(new Date() - about_start_time) / 1000.0,
'seconds for',
Object.keys(users).length,
'users'
);
});
console.log(
`load finished ${whoami} => ${this.whoami} in ${(new Date() - start_time) / 1000}`
);
await reactions;
this.whoami = whoami;
this.loaded = whoami;
} finally {
@ -510,13 +547,14 @@ class TfElement extends LitElement {
whoami=${this.whoami}
.users=${this.users}
hash=${this.hash}
?loading=${this.loading}
?loading=${this.loading || this.loading_about != 0}
.channels=${this.channels}
.channels_latest=${this.channels_latest}
.channels_unread=${this.channels_unread}
@channelsetunread=${this.channel_set_unread}
.connections=${this.connections}
.private_messages=${this.private_messages}
.recent_reactions=${this.recent_reactions}
></tf-tab-news>
`;
} else if (this.tab === 'connections') {

View File

@ -255,10 +255,12 @@ class TfComposeElement extends LitElement {
let self = this;
let input = document.createElement('input');
input.type = 'file';
input.onchange = function (event) {
input.addEventListener('change', function (event) {
input.parentNode.removeChild(input);
let file = event.target.files[0];
self.add_file(file);
};
});
document.body.appendChild(input);
input.click();
}
@ -357,7 +359,7 @@ class TfComposeElement extends LitElement {
remove_mention(id) {
let draft = this.get_draft();
delete draft.mentions[id];
setTimeout(() => this.notify(), 0);
setTimeout(() => this.notify(draft), 0);
}
render_mention(mention) {
@ -522,7 +524,7 @@ class TfComposeElement extends LitElement {
return html`
<div style="display: flex; flex-direction: row; width: 100%">
<label for="encrypt_to">🔐 To:</label>
<input type="text" id="encrypt_to" style="display: flex; flex: 1 1" @input=${this.update_encrypt}></input>
<input type="text" id="encrypt_to" class="w3-input w3-theme-d1 w3-border" style="display: flex; flex: 1 1" @input=${this.update_encrypt}></input>
<button class="w3-button w3-theme-d1" @click=${() => this.set_encrypt(undefined)}>🚮</button>
</div>
<ul>

View File

@ -16,6 +16,7 @@ class TfMessageElement extends LitElement {
expanded: {type: Object},
channel: {type: String},
channel_unread: {type: Number},
recent_reactions: {type: Array},
};
}
@ -31,6 +32,26 @@ class TfMessageElement extends LitElement {
this.format = 'message';
this.expanded = {};
this.channel_unread = -1;
this.recent_reactions = [];
}
connectedCallback() {
super.connectedCallback();
this._click_callback = this.document_click.bind(this);
document.body.addEventListener('mouseup', this._click_callback);
}
disconnectedCallback() {
super.disconnectedCallback();
document.body.removeEventListener('mouseup', this._click_callback);
}
document_click(event) {
let content = this.renderRoot.querySelector('.w3-dropdown-content');
let target = event.target;
if (content && !content.contains(target)) {
content.classList.remove('w3-show');
}
}
show_reply() {
@ -65,9 +86,9 @@ class TfMessageElement extends LitElement {
render_votes() {
function normalize_expression(expression) {
if (expression === 'Like' || !expression) {
if (expression === 'Like' || expression === 'like' || !expression) {
return '👍';
} else if (expression === 'Unlike') {
} else if (expression === 'Unlike' || expression === 'unlike') {
return '👎';
} else if (expression === 'heart') {
return '❤️';
@ -76,9 +97,10 @@ class TfMessageElement extends LitElement {
}
}
if (this.message?.votes?.length) {
return html` <div class="w3-container">
return html` <footer class="w3-container">
<div
class="w3-button w3-bar w3-padding-small"
class="w3-button w3-bar"
style="padding: 0"
@click=${this.show_reactions}
>
${(this.message.votes || []).map(
@ -93,7 +115,7 @@ class TfMessageElement extends LitElement {
`
)}
</div>
</div>`;
</footer>`;
}
}
@ -136,7 +158,12 @@ class TfMessageElement extends LitElement {
}
react(event) {
emojis.picker((x) => this.vote(x), null, this.whoami);
emojis.picker(
(x) => this.vote(x),
null,
this.whoami,
this.recent_reactions
);
}
show_image(link) {
@ -287,36 +314,48 @@ class TfMessageElement extends LitElement {
);
}
is_expanded(tag) {
return this.expanded[(this.message.id || '') + (tag || '')];
}
render_children() {
let self = this;
if (this.message.child_messages?.length) {
if (!this.expanded[this.message.id]) {
return html`<button
class="w3-button w3-theme-d1"
@click=${() => self.set_expanded(true)}
>
+ ${this.total_child_messages(this.message) + ' More'}
</button>`;
return html`
<button
class="w3-button w3-theme-d1 w3-block w3-bar"
style="box-sizing: border-box"
@click=${() => self.set_expanded(true)}
>
+ ${this.total_child_messages(this.message) + ' More'}
</button>
`;
} else {
return html`<button
class="w3-button w3-theme-d1"
return html` <div class="w3-container w3-margin-bottom">
${repeat(
this.message.child_messages || [],
(x) => x.id,
(x) =>
html`<tf-message
.message=${x}
whoami=${this.whoami}
.users=${this.users}
.drafts=${this.drafts}
.expanded=${this.expanded}
channel=${this.channel}
channel_unread=${this.channel_unread}
.recent_reactions=${this.recent_reactions}
></tf-message>`
)}
</div>
<button
class="w3-button w3-theme-d1 w3-block w3-bar"
style="box-sizing: border-box"
@click=${() => self.set_expanded(false)}
>
Collapse</button
>${repeat(
this.message.child_messages || [],
(x) => x.id,
(x) =>
html`<tf-message
.message=${x}
whoami=${this.whoami}
.users=${this.users}
.drafts=${this.drafts}
.expanded=${this.expanded}
channel=${this.channel}
channel_unread=${this.channel_unread}
></tf-message>`
)}`;
Collapse
</button>`;
}
} else {
return undefined;
@ -371,62 +410,74 @@ class TfMessageElement extends LitElement {
return content;
}
render_raw_button() {
copy_id(event) {
navigator.clipboard.writeText(this.message?.id);
}
toggle_menu(event) {
event.srcElement.parentNode
.querySelector('.w3-dropdown-content')
.classList.toggle('w3-show');
}
render_menu() {
let content = this.get_content();
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=${() => (this.format = 'md')}
>
Markdown
</button>`;
} else {
raw_button = html`<button
class="w3-button w3-theme-d1"
@click=${() => (this.format = 'message')}
>
Message
</button>`;
}
break;
case 'md':
raw_button = html`<button
class="w3-button w3-theme-d1"
@click=${() => (this.format = 'message')}
>
Message
</button>`;
break;
case 'decrypted':
raw_button = html`<button
class="w3-button w3-theme-d1"
@click=${() => (this.format = 'raw')}
>
Raw
</button>`;
break;
default:
if (this.message.decrypted) {
raw_button = html`<button
class="w3-button w3-theme-d1"
@click=${() => (this.format = 'decrypted')}
>
Decrypted
</button>`;
} else {
raw_button = html`<button
class="w3-button w3-theme-d1"
@click=${() => (this.format = 'raw')}
>
Raw
</button>`;
}
break;
let formats = [['message', 'Message']];
if (content?.type == 'post' || content?.type == 'blog') {
formats.push(['md', 'Markdown']);
}
return raw_button;
if (this.message?.decrypted) {
formats.push(['decrypted', 'Decrypted']);
}
formats.push(['raw', 'Raw']);
return html`
<div class="w3-bar-item w3-right">
<button class="w3-button w3-theme-d1" @click=${this.toggle_menu}>
%
</button>
<div
class="w3-dropdown-content w3-bar-block w3-card-4 w3-theme-l1"
style="right: 48px"
>
<a
target="_top"
class="w3-button w3-bar-item"
href=${'#' + encodeURIComponent(this.message?.id)}
>View Message</a
>
<button
class="w3-button w3-bar-item w3-border-bottom"
@click=${this.copy_id}
>
Copy ID
</button>
${this.drafts[this.message?.id] === undefined
? html`
<button class="w3-button w3-bar-item" @click=${this.show_reply}>
↩️ Reply
</button>
`
: undefined}
<button
class="w3-button w3-bar-item w3-border-bottom"
@click=${this.react}
>
👍 React
</button>
${formats.map(
([format, name]) => html`
<button
class="w3-button w3-bar-item"
style=${format == this.format ? 'font-weight: bold' : ''}
@click=${() => (this.format = format)}
>
${name}
</button>
`
)}
</div>
</div>
`;
}
render_header() {
@ -440,14 +491,10 @@ class TfMessageElement extends LitElement {
<span class="w3-bar-item">
<tf-user id=${this.message.author} .users=${this.users}></tf-user>
</span>
${is_encrypted}
<span class="w3-bar-item w3-right">${this.render_raw_button()}</span>
<span class="w3-bar-item w3-right" style="text-wrap: nowrap"
><a target="_top" href=${'#' + encodeURIComponent(this.message.id)}
>%</a
>
${new Date(this.message.timestamp).toLocaleString()}</span
>
${is_encrypted} ${this.render_menu()}
<div class="w3-bar-item w3-right" style="text-wrap: nowrap">
${new Date(this.message.timestamp).toLocaleString()}
</div>
</header>
`;
}
@ -495,6 +542,7 @@ class TfMessageElement extends LitElement {
.expanded=${self.expanded}
channel=${self.channel}
channel_unread=${self.channel_unread}
.recent_reactions=${self.recent_reactions}
></tf-message>
`
)}
@ -506,29 +554,23 @@ class TfMessageElement extends LitElement {
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>
<div class="w3-section w3-container">
<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}
.recent_reactions=${this.recent_reactions}
></tf-compose>
</div>
`
: html`
<button class="w3-button w3-theme-d1" @click=${this.show_reply}>
Reply
</button>
`;
: undefined;
return html`
<div class="w3-section w3-container">
${reply}
<button class="w3-button w3-theme-d1" @click=${this.react}>
React
</button>
${this.render_children()}
</div>
${reply}
<footer>${this.render_children()}</footer>
`;
}
@ -653,11 +695,14 @@ class TfMessageElement extends LitElement {
}
let content_warning = html`
<div
class="w3-panel w3-round-xlarge w3-theme-l4"
class="w3-panel w3-round-xlarge w3-theme-l4 w3"
style="cursor: pointer"
@click=${(x) => this.toggle_expanded(':cw')}
>
<p>${content.contentWarning}</p>
<p class="w3-small">
${this.is_expanded(':cw') ? 'Show less' : 'Show more'}
</p>
</div>
`;
let content_html = html`

View File

@ -13,6 +13,7 @@ class TfNewsElement extends LitElement {
expanded: {type: Object},
channel: {type: String},
channel_unread: {type: Number},
recent_reactions: {type: Array},
};
}
@ -28,6 +29,7 @@ class TfNewsElement extends LitElement {
this.drafts = {};
this.expanded = {};
this.channel_unread = -1;
this.recent_reactions = [];
}
process_messages(messages) {
@ -211,6 +213,7 @@ class TfNewsElement extends LitElement {
collapsed="true"
channel=${this.channel}
channel_unread=${this.channel_unread}
.recent_reactions=${this.recent_reactions}
></tf-message>
${x.rowid == unread_rowid
? html`<div style="display: flex; flex-direction: row">

View File

@ -11,6 +11,7 @@ class TfProfileElement extends LitElement {
id: {type: String},
users: {type: Object},
size: {type: Number},
sequence: {type: Number},
following: {type: Boolean},
blocking: {type: Boolean},
};
@ -26,6 +27,7 @@ class TfProfileElement extends LitElement {
this.id = null;
this.users = {};
this.size = 0;
this.sequence = 0;
}
async load() {
@ -139,7 +141,8 @@ class TfProfileElement extends LitElement {
let self = this;
let input = document.createElement('input');
input.type = 'file';
input.onchange = function (event) {
input.addEventListener('change', function (event) {
input.parentNode.removeChild(input);
let file = event.target.files[0];
file
.arrayBuffer()
@ -154,7 +157,8 @@ class TfProfileElement extends LitElement {
.catch(function (e) {
alert(e.message);
});
};
});
document.body.appendChild(input);
input.click();
}
@ -168,11 +172,12 @@ class TfProfileElement extends LitElement {
let profile = this.users[this.id] || {};
tfrpc.rpc
.query(
`SELECT SUM(LENGTH(content)) AS size FROM messages WHERE author = ?`,
`SELECT SUM(LENGTH(content)) AS size, MAX(sequence) AS sequence FROM messages WHERE author = ?`,
[this.id]
)
.then(function (result) {
self.size = result[0].size;
self.sequence = result[0].sequence;
});
let edit;
let follow;
@ -237,13 +242,17 @@ class TfProfileElement extends LitElement {
</div>
</div>`
: null;
let image =
typeof profile.image == 'string' ? profile.image : profile.image?.link;
let image = profile.image;
if (typeof image == 'string' && !image.startsWith('&')) {
try {
image = JSON.parse(image)?.link;
} catch {}
}
image = this.editing?.image ?? image;
let description = this.editing?.description ?? profile.description;
return html`<div class="w3-card-4 w3-container w3-theme-d3" style="box-sizing: border-box">
<header class="w3-container">
<p><tf-user id=${this.id} .users=${this.users}></tf-user> (${tfutils.human_readable_size(this.size)})</p>
<p><tf-user id=${this.id} .users=${this.users}></tf-user> (${tfutils.human_readable_size(this.size)} in ${this.sequence} messages)</p>
</header>
<div class="w3-container">
<div class="w3-margin-bottom" style="display: flex; flex-direction: row">

View File

@ -41,23 +41,26 @@ class TfReactionsModalElement extends LitElement {
>
</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>
`
)}
${this.votes
.sort((x, y) => y.timestamp - x.timestamp)
.map(
(x) => html`
<li style="display: flex; flex-direction: row; gap: 4px">
<span style="flex-basis: 3em"
>${x?.content?.vote?.expression}</span
>
<tf-user
style="flex: 1 1"
id=${x.author}
.users=${this.users}
></tf-user>
<span
style="flex-shrink: 1; white-space: nowrap; overflow: hidden; text-overflow: ellipsis"
>${new Date(x?.timestamp).toLocaleString()}</span
>
</li>
`
)}
</ul>
<footer class="w3-container w3-padding">
<button class="w3-button" @click=${this.clear}>Close</button>

View File

@ -48,7 +48,7 @@ const tf = css`
// prettier-ignore
const w3 = css`
/* W3.CSS 4.15 December 2020 by Jan Egil and Borge Refsnes */
/* W3.CSS 5.01 March 14 2025 by Jan Egil and Borge Refsnes */
html{box-sizing:border-box}*,*:before,*:after{box-sizing:inherit}
/* Extract from normalize.css by Nicolas Gallagher and Jonathan Neal git.io/normalize */
html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}
@ -88,7 +88,7 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
.w3-table th:first-child,.w3-table td:first-child,.w3-table-all th:first-child,.w3-table-all td:first-child{padding-left:16px}
.w3-btn,.w3-button{border:none;display:inline-block;padding:8px 16px;vertical-align:middle;overflow:hidden;text-decoration:none;color:inherit;background-color:inherit;text-align:center;cursor:pointer;white-space:nowrap}
.w3-btn:hover{box-shadow:0 8px 16px 0 rgba(0,0,0,0.2),0 6px 20px 0 rgba(0,0,0,0.19)}
.w3-btn,.w3-button{-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}
.w3-btn,.w3-button{-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}
.w3-disabled,.w3-btn:disabled,.w3-button:disabled{cursor:not-allowed;opacity:0.3}.w3-disabled *,:disabled *{pointer-events:none}
.w3-btn.w3-disabled:hover,.w3-btn:disabled:hover{box-shadow:none}
.w3-badge,.w3-tag{background-color:#000;color:#fff;display:inline-block;padding-left:8px;padding-right:8px;text-align:center}.w3-badge{border-radius:50%}
@ -136,7 +136,7 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
.w3-hide{display:none!important}.w3-show-block,.w3-show{display:block!important}.w3-show-inline-block{display:inline-block!important}
@media (max-width:1205px){.w3-auto{max-width:95%}}
@media (max-width:600px){.w3-modal-content{margin:0 10px;width:auto!important}.w3-modal{padding-top:30px}
.w3-dropdown-hover.w3-mobile .w3-dropdown-content,.w3-dropdown-click.w3-mobile .w3-dropdown-content{position:relative}
.w3-dropdown-hover.w3-mobile .w3-dropdown-content,.w3-dropdown-click.w3-mobile .w3-dropdown-content{position:relative}
.w3-hide-small{display:none!important}.w3-mobile{display:block;width:100%!important}.w3-bar-item.w3-mobile,.w3-dropdown-hover.w3-mobile,.w3-dropdown-click.w3-mobile{text-align:center}
.w3-dropdown-hover.w3-mobile,.w3-dropdown-hover.w3-mobile .w3-btn,.w3-dropdown-hover.w3-mobile .w3-button,.w3-dropdown-click.w3-mobile,.w3-dropdown-click.w3-mobile .w3-btn,.w3-dropdown-click.w3-mobile .w3-button{width:100%}}
@media (max-width:768px){.w3-modal-content{width:500px}.w3-modal{padding-top:50px}}
@ -158,6 +158,10 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
.w3-round-small{border-radius:2px}.w3-round,.w3-round-medium{border-radius:4px}.w3-round-large{border-radius:8px}.w3-round-xlarge{border-radius:16px}.w3-round-xxlarge{border-radius:32px}
.w3-row-padding,.w3-row-padding>.w3-half,.w3-row-padding>.w3-third,.w3-row-padding>.w3-twothird,.w3-row-padding>.w3-threequarter,.w3-row-padding>.w3-quarter,.w3-row-padding>.w3-col{padding:0 8px}
.w3-container,.w3-panel{padding:0.01em 16px}.w3-panel{margin-top:16px;margin-bottom:16px}
.w3-grid{display:grid}.w3-grid-padding{display:grid;gap:16px}.w3-flex{display:flex}
.w3-text-center{text-align:center}.w3-text-bold,.w3-bold{font-weight:bold}.w3-text-italic,.w3-italic{font-style:italic}
.w3-code,.w3-codespan{font-family:Consolas,"courier new";font-size:16px}
.w3-code{width:auto;background-color:#fff;padding:8px 12px;border-left:4px solid #4CAF50;word-wrap:break-word}
.w3-codespan{color:crimson;background-color:#f1f1f1;padding-left:4px;padding-right:4px;font-size:110%}
@ -199,9 +203,9 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
.w3-transparent,.w3-hover-none:hover{background-color:transparent!important}
.w3-hover-none:hover{box-shadow:none!important}
/* Colors */
.w3-amber,.w3-hover-amber:hover{color:#000!important;background-color:#ffc107!important}
.w3-amber,.w3-hover-amber:hover,.w3-warning{color:#000!important;background-color:#ffc107!important}
.w3-aqua,.w3-hover-aqua:hover{color:#000!important;background-color:#00ffff!important}
.w3-blue,.w3-hover-blue:hover{color:#fff!important;background-color:#2196F3!important}
.w3-blue,.w3-hover-blue:hover,.w3-info,.w3-primary{color:#fff!important;background-color:#2196F3!important}
.w3-light-blue,.w3-hover-light-blue:hover{color:#000!important;background-color:#87CEEB!important}
.w3-brown,.w3-hover-brown:hover{color:#fff!important;background-color:#795548!important}
.w3-cyan,.w3-hover-cyan:hover{color:#000!important;background-color:#00bcd4!important}
@ -216,15 +220,24 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
.w3-pink,.w3-hover-pink:hover{color:#fff!important;background-color:#e91e63!important}
.w3-purple,.w3-hover-purple:hover{color:#fff!important;background-color:#9c27b0!important}
.w3-deep-purple,.w3-hover-deep-purple:hover{color:#fff!important;background-color:#673ab7!important}
.w3-red,.w3-hover-red:hover{color:#fff!important;background-color:#f44336!important}
.w3-red,.w3-hover-red:hover,.w3-danger{color:#fff!important;background-color:#f44336!important}
.w3-sand,.w3-hover-sand:hover{color:#000!important;background-color:#fdf5e6!important}
.w3-teal,.w3-hover-teal:hover{color:#fff!important;background-color:#009688!important}
.w3-yellow,.w3-hover-yellow:hover{color:#000!important;background-color:#ffeb3b!important}
.w3-yellow,.w3-hover-yellow:hover,.w3-note{color:#000!important;background-color:#ffeb3b!important}
.w3-white,.w3-hover-white:hover{color:#000!important;background-color:#fff!important}
.w3-black,.w3-hover-black:hover{color:#fff!important;background-color:#000!important}
.w3-grey,.w3-hover-grey:hover,.w3-gray,.w3-hover-gray:hover{color:#000!important;background-color:#9e9e9e!important}
.w3-grey,.w3-hover-grey:hover,.w3-gray,.w3-hover-gray:hover,.w3-secondary{color:#000!important;background-color:#9e9e9e!important}
.w3-light-grey,.w3-hover-light-grey:hover,.w3-light-gray,.w3-hover-light-gray:hover{color:#000!important;background-color:#f1f1f1!important}
.w3-dark-grey,.w3-hover-dark-grey:hover,.w3-dark-gray,.w3-hover-dark-gray:hover{color:#fff!important;background-color:#616161!important}
.w3-asphalt,.w3-hover-asphalt:hover{color:#fff!important;background-color:#343a40!important}.w3-crimson,.w3-hover-crimson:hover{color:#fff!important;background-color:#a20025!important}
.w3-cobalt,w3-hover-cobalt:hover{color:#fff!important;background-color:#0050ef!important}
.w3-emerald,.w3-hover-emerald:hover,.w3-success{color:#fff!important;background-color:#008a00!important}
.w3-olive,.w3-hover-olive:hover{color:#fff!important;background-color:#6d8764!important}
.w3-paper,.w3-hover-paper:hover{color:#000!important;background-color:#f8f9fa!important}.w3-sienna,.w3-hover-sienna:hover{color:#fff!important;background-color:#a0522d!important}
.w3-taupe,.w3-hover-taupe:hover{color:#fff!important;background-color:#87794e!important}
.w3-pale-red,.w3-hover-pale-red:hover{color:#000!important;background-color:#ffdddd!important}
.w3-pale-green,.w3-hover-pale-green:hover{color:#000!important;background-color:#ddffdd!important}
.w3-pale-yellow,.w3-hover-pale-yellow:hover{color:#000!important;background-color:#ffffcc!important}

View File

@ -103,6 +103,23 @@ class TfTabConnectionsElement extends LitElement {
</div>`;
}
render_progress(name, value, max) {
if (max && value != max) {
return html`
<div class="w3-theme-d1 w3-small">
<div
class="w3-container w3-theme-l1"
style="width: ${Math.floor(
(100.0 * value) / max
)}%; text-wrap: nowrap"
>
${name} ${value} / ${max} (${Math.round((100.0 * value) / max)}%)
</div>
</div>
`;
}
}
render_broadcast(connection) {
let self = this;
return html`
@ -154,6 +171,16 @@ class TfTabConnectionsElement extends LitElement {
: undefined}
${connection.flags.one_shot ? '🔃' : undefined}
<tf-user id=${connection.id} .users=${this.users}></tf-user>
${this.render_progress(
'recv',
connection.progress.in.total - connection.progress.in.current,
connection.progress.in.total
)}
${this.render_progress(
'send',
connection.progress.out.total - connection.progress.out.current,
connection.progress.out.total
)}
${connection.tunnel !== undefined
? '🚇'
: html`(${connection.host}:${connection.port})`}
@ -206,6 +233,21 @@ class TfTabConnectionsElement extends LitElement {
});
}
toggle_accordian(id) {
let element = this.renderRoot.getElementById(id);
element.classList.toggle('w3-hide');
}
valid_connections() {
return this.connections.filter((x) => x.tunnel === undefined);
}
valid_broadcasts() {
return this.broadcasts
.filter((x) => x.address)
.filter((x) => this.connections.map((c) => c.id).indexOf(x.pubkey) == -1);
}
render() {
let self = this;
return html`
@ -220,27 +262,33 @@ class TfTabConnectionsElement extends LitElement {
>
Connect
</button>
<h2>Broadcasts</h2>
<ul class="w3-ul w3-border">
${this.broadcasts
.filter((x) => x.address)
.filter(
(x) => self.connections.map((c) => c.id).indexOf(x.pubkey) == -1
)
.map((x) => self.render_broadcast(x))}
<h2
class="w3-button w3-block w3-theme-d1"
@click=${() => self.toggle_accordian('connections')}
>
Connections (${this.valid_connections().length})
</h2>
<ul class="w3-ul w3-border" id="connections">
${this.valid_connections().map(
(x) => html` <li class="w3-bar">${this.render_connection(x)}</li> `
)}
</ul>
<h2>Connections</h2>
<ul class="w3-ul w3-border">
${this.connections
.filter((x) => x.tunnel === undefined)
.map(
(x) => html`
<li class="w3-bar">${this.render_connection(x)}</li>
`
)}
<h2
class="w3-button w3-block w3-theme-d1"
@click=${() => self.toggle_accordian('broadcasts')}
>
Discovery (${this.valid_broadcasts().length})
</h2>
<ul class="w3-ul w3-border w3-hide" id="broadcasts">
${this.valid_broadcasts().map((x) => self.render_broadcast(x))}
</ul>
<h2>Stored Connections</h2>
<ul class="w3-ul w3-border">
<h2
class="w3-button w3-block w3-theme-d1"
@click=${() => self.toggle_accordian('stored_connections')}
>
Stored Connections (${this.stored_connections.length})
</h2>
<ul class="w3-ul w3-border w3-hide" id="stored_connections">
${this.stored_connections.map(
(x) => html`
<li>
@ -267,8 +315,13 @@ class TfTabConnectionsElement extends LitElement {
`
)}
</ul>
<h2>Local Accounts</h2>
<div class="w3-container">
<h2
class="w3-button w3-block w3-theme-d1"
@click=${() => self.toggle_accordian('local_accounts')}
>
Local Accounts (${this.identities.length})
</h2>
<div class="w3-container w3-hide" id="local_accounts">
${this.identities.map(
(x) =>
html`<div

View File

@ -18,6 +18,7 @@ class TfTabNewsFeedElement extends LitElement {
time_range: {type: Array},
time_loading: {type: Array},
private_messages: {type: Array},
recent_reactions: {type: Array},
};
}
@ -37,6 +38,7 @@ class TfTabNewsFeedElement extends LitElement {
this.start_time = new Date().valueOf();
this.time_range = [0, 0];
this.time_loading = undefined;
this.recent_reactions = [];
this.loading = 0;
}
@ -46,6 +48,63 @@ class TfTabNewsFeedElement extends LitElement {
: this.hash.substring(1);
}
async _fetch_related_messages(messages) {
let refs = await tfrpc.rpc.query(
`
WITH
news AS (
SELECT value AS id FROM json_each(?)
)
SELECT refs_out.ref AS ref FROM messages_refs refs_out JOIN news ON refs_out.message = news.id
UNION
SELECT refs_in.message AS ref FROM messages_refs refs_in JOIN news ON refs_in.ref = news.id
`,
[JSON.stringify(messages.map((x) => x.id))]
);
let related_messages = await tfrpc.rpc.query(
`
SELECT FALSE AS is_primary, messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
FROM messages
JOIN json_each(?2) refs ON messages.id = refs.value
JOIN json_each(?1) AS following ON messages.author = following.value
`,
[JSON.stringify(this.following), JSON.stringify(refs.map((x) => x.ref))]
);
let combined = [].concat(messages, related_messages);
let refs2 = await tfrpc.rpc.query(
`
WITH
news AS (
SELECT value AS id FROM json_each(?)
)
SELECT refs_out.ref AS ref FROM messages_refs refs_out JOIN news ON refs_out.message = news.id
UNION
SELECT refs_in.message AS ref FROM messages_refs refs_in JOIN news ON refs_in.ref = news.id
`,
[JSON.stringify(combined.map((x) => x.id))]
);
let t0 = new Date();
let result = [].concat(
combined,
await tfrpc.rpc.query(
`
SELECT FALSE AS is_primary, messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
FROM json_each(?2) refs
JOIN messages ON messages.id = refs.value
JOIN json_each(?1) following ON messages.author = following.value
WHERE messages.content ->> 'type' != 'post'
`,
[
JSON.stringify(this.following),
JSON.stringify(refs2.map((x) => x.ref)),
]
)
);
let t1 = new Date();
console.log((t1 - t0) / 1000);
return result;
}
async fetch_messages(start_time, end_time) {
this.time_loading = [start_time, end_time];
let result;
@ -107,7 +166,8 @@ class TfTabNewsFeedElement extends LitElement {
[this.hash.substring(1)]
);
} else if (this.hash.startsWith('##')) {
result = await tfrpc.rpc.query(
let t0 = new Date();
let initial_messages = await tfrpc.rpc.query(
`
WITH
all_news AS (
@ -120,21 +180,11 @@ class TfTabNewsFeedElement extends LitElement {
FROM messages_fts(?5)
JOIN messages ON messages.rowid = messages_fts.rowid
JOIN json_each(?1) AS following ON messages.author = following.value
JOIN json_tree(messages.content, '$.mentions') AS mention ON mention.value = '#' || ?4),
news AS (SELECT * FROM all_news
WHERE (?2 IS NULL OR all_news.timestamp >= ?2) AND all_news.timestamp < ?3
ORDER BY all_news.timestamp DESC LIMIT 20)
SELECT FALSE AS is_primary, messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
FROM news
JOIN messages_refs ON news.id = messages_refs.ref
JOIN messages ON messages_refs.message = messages.id
UNION
SELECT FALSE AS is_primary, messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
FROM news
JOIN messages_refs ON news.id = messages_refs.message
JOIN messages ON messages_refs.ref = messages.id
UNION
SELECT TRUE AS is_primary, news.* FROM news
JOIN json_tree(messages.content, '$.mentions') AS mention ON mention.value = '#' || ?4
)
SELECT TRUE AS is_primary, all_news.* FROM all_news
WHERE (?2 IS NULL OR all_news.timestamp >= ?2) AND all_news.timestamp < ?3
ORDER BY all_news.timestamp DESC LIMIT 20
`,
[
JSON.stringify(this.following),
@ -144,6 +194,12 @@ class TfTabNewsFeedElement extends LitElement {
'"#' + this.hash.substring(2).replace('"', '""') + '"',
]
);
let t1 = new Date();
result = await this._fetch_related_messages(initial_messages);
let t2 = new Date();
console.log(
`load of ${result.length} rows took ${(t2 - t0) / 1000} (${(t1 - t0) / 1000} to find ${initial_messages.length} initial messages, ${(t2 - t1) / 1000} to find ${result.length} total messages) following=${this.following.length} st=${start_time} et=${end_time}`
);
} else if (this.hash == '#🔐') {
result = await tfrpc.rpc.query(
`
@ -159,33 +215,30 @@ class TfTabNewsFeedElement extends LitElement {
);
result = (await this.decrypt(result)).filter((x) => x.decrypted);
} else {
result = await tfrpc.rpc.query(
let t0 = new Date();
let initial_messages = await tfrpc.rpc.query(
`
WITH
all_news AS (
SELECT messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
FROM messages
JOIN json_each(?) AS following ON messages.author = following.value
WHERE timestamp >= 0 AND timestamp < ?3),
),
news AS (
SELECT * FROM all_news
WHERE (?2 IS NULL OR all_news.timestamp >= ?2) AND all_news.timestamp < ?3
WHERE all_news.timestamp < ?3 AND (?2 IS NULL OR all_news.timestamp >= ?2)
ORDER BY timestamp DESC LIMIT 20
)
SELECT FALSE AS is_primary, messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
FROM news
JOIN messages_refs ON news.id = messages_refs.ref
JOIN messages ON messages_refs.message = messages.id
UNION
SELECT FALSE AS is_primary, messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
FROM news
JOIN messages_refs ON news.id = messages_refs.message
JOIN messages ON messages_refs.ref = messages.id
UNION
SELECT TRUE AS is_primary, news.* FROM news
`,
[JSON.stringify(this.following), start_time, end_time]
);
let t1 = new Date();
result = await this._fetch_related_messages(initial_messages);
let t2 = new Date();
console.log(
`load of ${result.length} rows took ${(t2 - t0) / 1000} (${(t1 - t0) / 1000} to find ${initial_messages.length} initial messages, ${(t2 - t1) / 1000} to find ${result.length} total messages) following=${this.following.length} st=${start_time} et=${end_time}`
);
}
this.time_loading = undefined;
return result;
@ -211,7 +264,11 @@ class TfTabNewsFeedElement extends LitElement {
try {
let more = [];
let last_start_time = this.time_range[0];
more = await this.fetch_messages(null, last_start_time);
try {
more = await this.fetch_messages(null, last_start_time);
} catch (e) {
console.log(e);
}
this.update_time_range_from_messages(
more.filter((x) => x.timestamp < last_start_time)
);
@ -311,7 +368,7 @@ class TfTabNewsFeedElement extends LitElement {
this.messages = this.merge_messages(this.messages, messages);
this.time_loading = undefined;
console.log(
`loading messages done for ${self.whoami} in ${(new Date() - start_time) / 1000}s`
`loading ${messages.length} messages done for ${self.whoami} in ${(new Date() - start_time) / 1000}s`
);
}
@ -397,6 +454,7 @@ class TfTabNewsFeedElement extends LitElement {
.expanded=${this.expanded}
channel=${this.channel()}
channel_unread=${this.channels_unread?.[this.channel()]}
.recent_reactions=${this.recent_reactions}
></tf-news>
${more}
`);

View File

@ -24,6 +24,7 @@ class TfTabNewsElement extends LitElement {
channels_latest: {type: Object},
connections: {type: Array},
private_messages: {type: Array},
recent_reactions: {type: Array},
};
}
@ -43,6 +44,7 @@ class TfTabNewsElement extends LitElement {
this.channels_latest = {};
this.channels = [];
this.connections = [];
this.recent_reactions = [];
tfrpc.rpc.localStorageGet('drafts').then(function (d) {
self.drafts = JSON.parse(d || '{}');
});
@ -95,7 +97,13 @@ class TfTabNewsElement extends LitElement {
}
unread_status(channel) {
if (
if (channel === undefined) {
if (
Object.keys(this.channels_unread).some((x) => this.unread_status(x))
) {
return '✉️ ';
}
} else if (
this.channels_latest[channel] &&
this.channels_latest[channel] > 0 &&
(this.channels_unread[channel] === undefined ||
@ -223,7 +231,9 @@ class TfTabNewsElement extends LitElement {
`
)}
<h4 class="w3-bar-item w3-theme-d2">Connections</h4>
<a class="w3-bar-item w3-theme-d2 w3-button" href="#connections">
<h4 style="margin: 0">Connections</h4>
</a>
${this.connections
.filter((x) => x.id && !x.destroy_reason)
.map(
@ -232,6 +242,7 @@ class TfTabNewsElement extends LitElement {
class="w3-bar-item"
style="max-width: 100%"
id=${x.id}
fallback_name=${x.host}
.users=${this.users}
></tf-user>
`
@ -285,7 +296,7 @@ class TfTabNewsElement extends LitElement {
return cache(html`
${this.render_sidebar()}
<div
style="margin-left: 2in; padding: 0px; top: 0; max-height: 100%; overflow: auto"
style="margin-left: 2in; padding: 0px; top: 0; max-height: 100%; overflow: auto; contain: layout"
id="main"
class="w3-main"
>
@ -310,7 +321,7 @@ class TfTabNewsElement extends LitElement {
class="w3-button w3-hide-large"
@click=${this.show_sidebar}
>
&#9776;
${this.unread_status()}&#9776;
</div>
Welcome, <tf-user id=${this.whoami} .users=${this.users}></tf-user>!
${edit_profile}
@ -339,6 +350,7 @@ class TfTabNewsElement extends LitElement {
.channels_unread=${this.channels_unread}
.channels_latest=${this.channels_latest}
.private_messages=${this.private_messages}
.recent_reactions=${this.recent_reactions}
></tf-tab-news-feed>
</div>
</div>

View File

@ -6,6 +6,7 @@ class TfUserElement extends LitElement {
static get properties() {
return {
id: {type: String},
fallback_name: {type: String},
users: {type: Object},
};
}
@ -15,13 +16,16 @@ class TfUserElement extends LitElement {
constructor() {
super();
this.id = null;
this.fallback_name = null;
this.users = {};
}
render() {
let user = this.users[this.id];
let shape =
!user?.follow_depth || user.follow_depth >= 2 ? 'w3-circle' : 'w3-round';
user?.follow_depth === undefined || user.follow_depth >= 2
? 'w3-circle'
: 'w3-round';
let image = html`<span
class=${'w3-theme-l4 ' + shape}
style="display: inline-block; width: 2em; height: 2em; text-align: center; line-height: 2em"
@ -29,13 +33,16 @@ class TfUserElement extends LitElement {
>`;
let name = this.users?.[this.id]?.name;
name = html`<a target="_top" href=${'#' + this.id}
>${name !== undefined ? name : this.id}</a
>${name ?? this.fallback_name ?? this.id}</a
>`;
if (user) {
let image_link = user.image;
image_link =
typeof image_link == 'string' ? image_link : image_link?.link;
if (typeof image_link == 'string' && !image_link.startsWith('&')) {
try {
image_link = JSON.parse(image_link)?.link;
} catch {}
}
if (image_link !== undefined) {
image = html`<img
class=${'w3-theme-l4 ' + shape}

View File

@ -1,5 +1,5 @@
{
"type": "tildefriends-app",
"emoji": "💾",
"previous": "&mvGTlWKFR5QM/3nb4fJ2WQq0n/gNKvBmhGDkAvb8ki8=.sha256"
"previous": "&tzZFIe7Y54O4sx1QtAPdemkXh+p5qHXSG/dlS7NP6OQ=.sha256"
}

View File

@ -8,7 +8,7 @@ async function query(sql, args) {
async function get_biggest() {
return query(`
select author, sum(length(content)) as size from messages group by author order by size desc limit 10;
select author, size from messages_stats group by author order by size desc limit 10;
`);
}
@ -62,15 +62,14 @@ function nice_size(bytes) {
}
async function main() {
await app.setDocument(
'<p style="color: #fff">Finding the top 10 largest feeds...</p>'
);
let most_follows = await get_most_follows();
await app.setDocument('<p style="color: #fff">Analyzing feeds...</p>');
let most_follows = get_most_follows();
let total = await get_total();
let identities = await ssb.getAllIdentities();
let following1 = await ssb.following(identities, 1);
let following2 = await ssb.following(identities, 2);
let biggest = await get_biggest();
most_follows = await most_follows;
let names = await get_names(
[].concat(
biggest.map((x) => x.author),
@ -94,7 +93,7 @@ async function main() {
}
let html = `<body style="color: #000; background-color: #ddd">\n
<h1>Storage Summary</h1>
<h2>Top 10 Accounts by Size</h2>
<h2>Top Accounts by Size</h2>
<ol>`;
for (let item of biggest) {
html += `<li>
@ -105,7 +104,7 @@ async function main() {
}
html += `
</ol>
<h2>Top 10 Accounts by Follows</h2>
<h2>Top Accounts by Follows</h2>
<ol>`;
for (let item of most_follows) {
html += `<li>

5
apps/web.json Normal file
View File

@ -0,0 +1,5 @@
{
"type": "tildefriends-app",
"emoji": "🕸",
"previous": "&n7hu5b8/TsfiG6FDlCRG5nPCrIdCr96+xpIJ/aQT/uM=.sha256"
}

100
apps/web/app.js Normal file
View File

@ -0,0 +1,100 @@
let g_hash;
async function query(sql, params) {
let results = [];
await ssb.sqlAsync(sql, params, function (row) {
results.push(row);
});
return results;
}
async function resolve(id) {
try {
let blob = await ssb.blobGet(id);
if (blob) {
let json;
try {
json = JSON.parse(utf8Decode(blob));
} catch {
return {id: utf8Decode(blob)};
}
if (json?.links) {
for (let [key, value] of Object.entries(json.links)) {
json.links[key] = await resolve(value);
}
return json;
} else {
return 'huh?' + json;
}
} else {
return `missing<${id}>`;
}
} catch (e) {
return id + ': ' + e.message;
}
}
async function get_names(identities) {
return Object.fromEntries(
(
await query(
`
SELECT author, name FROM (
SELECT
messages.author,
RANK() OVER (PARTITION BY messages.author ORDER BY messages.sequence DESC) AS author_rank,
messages.content ->> 'name' AS name
FROM messages
JOIN json_each(?) AS identities ON identities.value = messages.author
WHERE
json_extract(messages.content, '$.type') = 'about' AND
content ->> 'about' = messages.author AND name IS NOT NULL)
WHERE author_rank = 1
`,
[JSON.stringify(identities)]
)
).map((x) => [x.author, x.name])
);
}
async function render(hash) {
g_hash = hash;
if (!hash) {
let sites = await query(
`
SELECT site.author, site.id
FROM messages site
WHERE site.content ->> 'type' = 'web-init'
`,
[]
);
let names = await get_names(sites.map((x) => x.author));
if (hash === g_hash) {
await app.setDocument(
`<ul style="background-color: #ddd">${sites.map((x) => `<li><a target="_top" href="#${encodeURIComponent(x.id)}">${names[x.author] ?? x.author} - ${x.id}</a></li>`).join('\n')}</ul>`
);
}
} else {
let site_id =
hash.charAt(0) == '#'
? decodeURIComponent(hash.substring(1))
: decodeURIComponent(hash);
await app.setDocument(`<html style="margin: 0; padding: 0; width: 100vw; height: 100vh; margin: 0; padding: 0">
<body style="display: flex; flex-direction: column; width: 100vw; height: 100vh">
<iframe src="${encodeURIComponent(site_id)}/index.html" style="flex: 1 1; border: 0; background-color: #fff"></iframe>
</body>
</html>`);
}
}
core.register('message', async function message_handler(message) {
if (message.event == 'hashChange') {
await render(message.hash);
}
});
async function main() {
render(null);
}
main();

63
apps/web/handler.js Normal file
View File

@ -0,0 +1,63 @@
async function query(sql, params) {
let results = [];
await ssb.sqlAsync(sql, params, function (row) {
results.push(row);
});
return results;
}
function guess_content_type(name) {
if (name.endsWith('.html')) {
return 'text/html; charset=UTF-8';
} else if (name.endsWith('.js') || name.endsWith('.mjs')) {
return 'text/javascript; charset=UTF-8';
} else if (name.endsWith('.css')) {
return 'text/stylesheet; charset=UTF-8';
} else {
return 'application/binary';
}
}
async function main() {
let path = request.path.replaceAll(/(%[0-9a-fA-F]{2})/g, (x) =>
String.fromCharCode(parseInt(x.substring(1), 16))
);
let match = path.match(/^(%.{44}\.sha256)(?:\/)?(.*)$/);
let content_type = guess_content_type(request.path);
let root = await query(
`
SELECT root.content ->> 'root' AS root
FROM messages site
JOIN messages root
ON site.id = ? AND root.author = site.author AND root.content ->> 'site' = site.id
ORDER BY root.sequence DESC LIMIT 1
`,
[match[1]]
);
let root_id = root[0]['root'];
let last_id = root_id;
let blob = await ssb.blobGet(root_id);
try {
for (let part of match[2]?.split('/')) {
let dir = JSON.parse(utf8Decode(blob));
last_id = dir?.links[part];
blob = await ssb.blobGet(dir?.links[part]);
content_type = guess_content_type(part);
}
} catch {}
respond({
status_code: 200,
data: blob ? utf8Decode(blob) : `${last_id} not found`,
content_type: content_type,
});
}
main().catch(function (e) {
respond({
status_code: 200,
data: `${e.message}\n${e.stack}`,
content_type: 'text/plain',
});
});

View File

@ -1,5 +1,5 @@
{
"type": "tildefriends-app",
"emoji": "👋",
"previous": "&wAb7J6E35xEXpiXsQ6t1RaWTGIvlatUnyH8ipF6pVic=.sha256"
"previous": "&1o8MrBHfH42NnO+ruajwCmW/DUCb+IT1qtnAZI/agyo=.sha256"
}

View File

@ -45,6 +45,11 @@
<i class="fa fa-mobile-screen w3-xlarge"></i>
<i class="fa-brands fa-windows w3-xlarge"></i>
</p>
<a
class="w3-button w3-blue w3-padding-large"
href="https://www.tildefriends.net/~core/ssb/"
>🦀 Try It</a
>
<a
class="w3-button w3-black w3-padding-large"
href="https://dev.tildefriends.net/cory/tildefriends/releases"
@ -52,12 +57,7 @@
>
<a
class="w3-button w3-black w3-padding-large"
href="https://www.tildefriends.net/~core/ssb/"
><i class="fa fa-link"></i> Try It</a
>
<a
class="w3-button w3-black w3-padding-large"
href="https://dev.tildefriends.net/"
href="https://dev.tildefriends.net/cory/tildefriends"
><i class="fa fa-mug-hot"></i> Development</a
>
<a
@ -65,6 +65,11 @@
href="https://docs.tildefriends.net/"
><i class="fa fa-book"></i> Documentation</a
>
<a
class="w3-button w3-black w3-padding-large"
href="https://www.tildefriends.net/~cory/tildeblog/"
><i class="fa fa-solid fa-square-rss"></i> Blog</a
>
<p>
<a
class="w3-button w3-round-large w3-padding w3-blue-gray w3-margin-top"
@ -86,6 +91,13 @@
<img src="googleplay.svg" style="height: 2em; margin: 0" />
Get it on Google Play (Open Testing)
</a>
<a
class="w3-button w3-round-large w3-padding w3-blue-gray w3-margin-top"
href="https://testflight.apple.com/join/tXxgtSpE"
>
<img src="ios.svg" style="height: 2em; margin: 0" />
Get it on iOS (TestFlight)
</a>
</p>
</div>
<div class="w3-col l4 m6">

3
apps/welcome/ios.svg Normal file
View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="814" height="1000">
<path d="M788.1 340.9c-5.8 4.5-108.2 62.2-108.2 190.5 0 148.4 130.3 200.9 134.2 202.2-.6 3.2-20.7 71.9-68.7 141.9-42.8 61.6-87.5 123.1-155.5 123.1s-85.5-39.5-164-39.5c-76.5 0-103.7 40.8-165.9 40.8s-105.6-57-155.5-127C46.7 790.7 0 663 0 541.8c0-194.4 126.4-297.5 250.8-297.5 66.1 0 121.2 43.4 162.7 43.4 39.5 0 101.1-46 176.3-46 28.5 0 130.9 2.6 198.3 99.2zm-234-181.5c31.1-36.9 53.1-88.1 53.1-139.3 0-7.1-.6-14.3-1.9-20.1-50.6 1.9-110.8 33.7-147.1 75.8-28.5 32.4-55.1 83.6-55.1 135.5 0 7.8 1.3 15.6 1.9 18.1 3.2.6 8.4 1.3 13.6 1.3 45.4 0 102.5-30.4 135.5-71.3z"/>
</svg>

After

Width:  |  Height:  |  Size: 660 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 141 KiB

After

Width:  |  Height:  |  Size: 141 KiB

View File

@ -1,4 +1,4 @@
/* W3.CSS 4.15 December 2020 by Jan Egil and Borge Refsnes */
/* W3.CSS 5.02 March 31 2025 by Jan Egil and Borge Refsnes */
html{box-sizing:border-box}*,*:before,*:after{box-sizing:inherit}
/* Extract from normalize.css by Nicolas Gallagher and Jonathan Neal git.io/normalize */
html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}
@ -108,6 +108,8 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
.w3-round-small{border-radius:2px}.w3-round,.w3-round-medium{border-radius:4px}.w3-round-large{border-radius:8px}.w3-round-xlarge{border-radius:16px}.w3-round-xxlarge{border-radius:32px}
.w3-row-padding,.w3-row-padding>.w3-half,.w3-row-padding>.w3-third,.w3-row-padding>.w3-twothird,.w3-row-padding>.w3-threequarter,.w3-row-padding>.w3-quarter,.w3-row-padding>.w3-col{padding:0 8px}
.w3-container,.w3-panel{padding:0.01em 16px}.w3-panel{margin-top:16px;margin-bottom:16px}
.w3-grid{display:grid}.w3-grid-padding{display:grid;gap:16px}.w3-flex{display:flex}
.w3-text-center{text-align:center}.w3-text-bold,.w3-bold{font-weight:bold}.w3-text-italic,.w3-italic{font-style:italic}
.w3-code,.w3-codespan{font-family:Consolas,"courier new";font-size:16px}
.w3-code{width:auto;background-color:#fff;padding:8px 12px;border-left:4px solid #4CAF50;word-wrap:break-word}
.w3-codespan{color:crimson;background-color:#f1f1f1;padding-left:4px;padding-right:4px;font-size:110%}
@ -148,6 +150,7 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
.w3-button:hover{color:#000!important;background-color:#ccc!important}
.w3-transparent,.w3-hover-none:hover{background-color:transparent!important}
.w3-hover-none:hover{box-shadow:none!important}
.w3-rtl{direction:rtl}.w3-ltr{direction:ltr}
/* Colors */
.w3-amber,.w3-hover-amber:hover{color:#000!important;background-color:#ffc107!important}
.w3-aqua,.w3-hover-aqua:hover{color:#000!important;background-color:#00ffff!important}
@ -175,6 +178,19 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
.w3-grey,.w3-hover-grey:hover,.w3-gray,.w3-hover-gray:hover{color:#000!important;background-color:#9e9e9e!important}
.w3-light-grey,.w3-hover-light-grey:hover,.w3-light-gray,.w3-hover-light-gray:hover{color:#000!important;background-color:#f1f1f1!important}
.w3-dark-grey,.w3-hover-dark-grey:hover,.w3-dark-gray,.w3-hover-dark-gray:hover{color:#fff!important;background-color:#616161!important}
.w3-asphalt,.w3-hover-asphalt:hover{color:#fff!important;background-color:#343a40!important}
.w3-crimson,.w3-hover-crimson:hover{color:#fff!important;background-color:#a20025!important}
.w3-cobalt,w3-hover-cobalt:hover{color:#fff!important;background-color:#0050ef!important}
.w3-emerald,.w3-hover-emerald:hover{color:#fff!important;background-color:#008a00!important}
.w3-olive,.w3-hover-olive:hover{color:#fff!important;background-color:#6d8764!important}
.w3-paper,.w3-hover-paper:hover{color:#000!important;background-color:#f8f9fa!important}
.w3-sienna,.w3-hover-sienna:hover{color:#fff!important;background-color:#a0522d!important}
.w3-taupe,.w3-hover-taupe:hover{color:#fff!important;background-color:#87794e!important}
.w3-danger{color:#fff!important;background-color:#dd0000!important}
.w3-note{color:#000!important;background-color:#fff599!important}
.w3-info{color:#fff!important;background-color:#0a6fc2!important}
.w3-warning{color:#000!important;background-color:#ffb305!important}
.w3-success{color:#fff!important;background-color:#008a00!important}
.w3-pale-red,.w3-hover-pale-red:hover{color:#000!important;background-color:#ffdddd!important}
.w3-pale-green,.w3-hover-pale-green:hover{color:#000!important;background-color:#ddffdd!important}
.w3-pale-yellow,.w3-hover-pale-yellow:hover{color:#000!important;background-color:#ffffcc!important}
@ -232,4 +248,4 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
.w3-border-light-grey,.w3-hover-border-light-grey:hover,.w3-border-light-gray,.w3-hover-border-light-gray:hover{border-color:#f1f1f1!important}
.w3-border-dark-grey,.w3-hover-border-dark-grey:hover,.w3-border-dark-gray,.w3-hover-border-dark-gray:hover{border-color:#616161!important}
.w3-border-pale-red,.w3-hover-border-pale-red:hover{border-color:#ffe7e7!important}.w3-border-pale-green,.w3-hover-border-pale-green:hover{border-color:#e7ffe7!important}
.w3-border-pale-yellow,.w3-hover-border-pale-yellow:hover{border-color:#ffffcc!important}.w3-border-pale-blue,.w3-hover-border-pale-blue:hover{border-color:#e7ffff!important}
.w3-border-pale-yellow,.w3-hover-border-pale-yellow:hover{border-color:#ffffcc!important}.w3-border-pale-blue,.w3-hover-border-pale-blue:hover{border-color:#e7ffff!important}

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,53 +1,26 @@
import * as core from './core.js';
let g_next_id = 1;
let g_calls = {};
let gSessionIndex = 0;
/**
* TODOC
* @returns
*/
function makeSessionId() {
return 'session_' + (gSessionIndex++).toString();
}
/**
* TODOC
* @returns
*/
function App() {
this._on_output = null;
this._send_queue = [];
this.calls = {};
this._next_call_id = 1;
return this;
}
/**
* TODOC
* @param {*} callback
*/
App.prototype.readOutput = function (callback) {
this._on_output = callback;
};
/**
* TODOC
* @param {*} api
* @returns
*/
App.prototype.makeFunction = function (api) {
let self = this;
let result = function () {
let id = g_next_id++;
while (!id || g_calls[id]) {
id = g_next_id++;
let id = self._next_call_id++;
while (!id || self.calls[id]) {
id = self._next_call_id++;
}
let promise = new Promise(function (resolve, reject) {
g_calls[id] = {resolve: resolve, reject: reject};
self.calls[id] = {resolve: resolve, reject: reject};
});
let message = {
message: 'tfrpc',
action: 'tfrpc',
method: api[0],
params: [...arguments],
id: id,
@ -59,10 +32,6 @@ App.prototype.makeFunction = function (api) {
return result;
};
/**
* TODOC
* @param {*} message
*/
App.prototype.send = function (message) {
if (this._send_queue) {
if (this._on_output) {
@ -77,11 +46,6 @@ App.prototype.send = function (message) {
}
};
/**
* TODOC
* @param {*} request
* @param {*} response
*/
exports.app_socket = async function socket(request, response) {
let process;
let options = {};
@ -102,10 +66,16 @@ exports.app_socket = async function socket(request, response) {
try {
message = JSON.parse(event.data);
} catch (error) {
print('ERROR', error, event.data, event.data.length, event.opCode);
print(
'WebSocket error:',
error,
event.data,
event.data.length,
event.opCode
);
return;
}
if (message.action == 'hello') {
if (!process && message.action == 'hello') {
let packageOwner;
let packageName;
let blobId;
@ -122,7 +92,7 @@ exports.app_socket = async function socket(request, response) {
if (!blobId) {
response.send(
JSON.stringify({
message: 'tfrpc',
action: 'tfrpc',
method: 'error',
params: [message.path + ' not found'],
id: -1,
@ -163,7 +133,7 @@ exports.app_socket = async function socket(request, response) {
options.packageOwner = packageOwner;
options.packageName = packageName;
options.url = message.url;
let sessionId = makeSessionId();
let sessionId = 'session_' + (gSessionIndex++).toString();
if (blobId) {
if (message.edit_only) {
response.send(
@ -175,9 +145,24 @@ exports.app_socket = async function socket(request, response) {
}
}
if (process) {
process.app.readOutput(function (message) {
process.client_api.tfrpc = function (message) {
if (message.id) {
let calls = process?.app?.calls;
if (calls) {
let call = calls[message.id];
if (call) {
if (message.error !== undefined) {
call.reject(message.error);
} else {
call.resolve(message.result);
}
delete calls[message.id];
}
}
}
};
process.app._on_output = (message) =>
response.send(JSON.stringify(message), 0x1);
});
process.app.send();
}
@ -206,26 +191,13 @@ exports.app_socket = async function socket(request, response) {
if (process && process.timeout > 0) {
setTimeout(ping, process.timeout);
}
} else if (message.action == 'resetPermission') {
if (process) {
process.resetPermission(message.permission);
}
} else if (message.action == 'setActiveIdentity') {
process.setActiveIdentity(message.identity);
} else if (message.action == 'createIdentity') {
await process.createIdentity();
} else if (message.message == 'tfrpc') {
if (message.id && g_calls[message.id]) {
if (message.error !== undefined) {
g_calls[message.id].reject(message.error);
} else {
g_calls[message.id].resolve(message.result);
}
delete g_calls[message.id];
}
} else {
if (process && process.eventHandlers['message']) {
await core.invoke(process.eventHandlers['message'], [message]);
if (process) {
if (process.client_api[message.action]) {
process.client_api[message.action](message);
} else if (process.eventHandlers['message']) {
await core.invoke(process.eventHandlers['message'], [message]);
}
}
}
} else if (event.opCode == 0x8) {

View File

@ -266,6 +266,7 @@ class TfNavigationElement extends LitElement {
<button
@click=${() => this.reset_permission(key)}
class="w3-button w3-red"
id=${'permission_reset:' + key}
>
Reset
</button>
@ -275,6 +276,7 @@ class TfNavigationElement extends LitElement {
<button
@click=${() => (this.show_permissions = false)}
class="w3-button w3-blue"
id="permissions_close"
>
Close
</button>
@ -409,6 +411,7 @@ class TfFilesElement extends LitElement {
current: {type: String},
files: {type: Object},
dropping: {type: Number},
drop_target: {type: String},
};
}
@ -447,6 +450,9 @@ class TfFilesElement extends LitElement {
if (!this.files[file].clean) {
classes.push('dirty');
}
if (this.drop_target == file) {
classes.push('drop');
}
return html`<div
class="${classes.join(' ')}"
@click=${(x) => this.file_click(file)}
@ -463,11 +469,12 @@ class TfFilesElement extends LitElement {
event.preventDefault();
event.stopPropagation();
this.dropping = 0;
this.drop_target = undefined;
for (let file of event.dataTransfer.files) {
let buffer = await file.arrayBuffer();
let text = new TextDecoder('latin1').decode(buffer);
gFiles[file.name] = {
doc: new cm6.EditorState.create({
doc: cm6.EditorState.create({
doc: text,
extensions: cm6.extensions,
}),
@ -486,6 +493,7 @@ class TfFilesElement extends LitElement {
*/
drag_enter(event) {
this.dropping++;
this.drop_target = event.srcElement.innerText.trim();
event.preventDefault();
}
@ -495,6 +503,13 @@ class TfFilesElement extends LitElement {
*/
drag_leave(event) {
this.dropping--;
if (this.dropping == 0) {
this.drop_target = undefined;
}
}
drag_over(event) {
event.preventDefault();
}
/**
@ -521,6 +536,10 @@ class TfFilesElement extends LitElement {
background-color: #2aa198;
}
div.file.drop {
border: 4px solid red;
}
div.file.dirty::after {
content: '*';
}
@ -529,20 +548,12 @@ class TfFilesElement extends LitElement {
@drop=${this.drop}
@dragenter=${this.drag_enter}
@dragleave=${this.drag_leave}
@dragover=${this.drag_over}
>
${Object.keys(this.files)
.sort()
.map((x) => self.render_file(x))}
</div>
<div
?hidden=${this.dropping == 0}
@drop=${this.drop}
@dragenter=${this.drag_enter}
@dragleave=${this.drag_leave}
style="text-align: center; vertical-align: middle; outline: 16px solid red; margin: -8px; background-color: rgba(255, 0, 0, 0.5); position: absolute; left: 16px; top: 16px; width: calc(100% - 16px); height: calc(100% - 16px); z-index: 1000"
>
Drop File(s)
</div>
`;
}
}
@ -1325,7 +1336,7 @@ function _receive_websocket_message(message) {
line.append(key, message.stats[key]);
}
}
} else if (message && message.message === 'tfrpc' && message.method) {
} else if (message && message.action === 'tfrpc' && message.method) {
let api = k_api[message.method];
let id = message.id;
let params = message.params;
@ -1333,14 +1344,14 @@ function _receive_websocket_message(message) {
Promise.resolve(api.func(...params))
.then(function (result) {
send({
message: 'tfrpc',
action: 'tfrpc',
id: id,
result: result,
});
})
.catch(function (error) {
send({
message: 'tfrpc',
action: 'tfrpc',
id: id,
error: error,
});

View File

@ -3,31 +3,22 @@ import * as http from './http.js';
let gProcesses = {};
let gStatsTimer = false;
let kPingInterval = 60 * 1000;
let g_handler_index = 0;
/**
* TODOC
* @param {*} out
* @param {*} error
*/
function printError(out, error) {
const k_ping_interval = 60 * 1000;
function printError(error) {
if (error.stackTrace) {
out.print(error.fileName + ':' + error.lineNumber + ': ' + error.message);
out.print(error.stackTrace);
print(error.fileName + ':' + error.lineNumber + ': ' + error.message);
print(error.stackTrace);
} else {
for (let [k, v] of Object.entries(error)) {
out.print(k, v);
print(k, v);
}
out.print(error.toString());
print(error.toString());
}
}
/**
* TODOC
* @param {*} handlers
* @param {*} argv
* @returns
*/
function invoke(handlers, argv) {
let promises = [];
if (handlers) {
@ -48,12 +39,6 @@ function invoke(handlers, argv) {
return Promise.all(promises);
}
/**
* TODOC
* @param {*} eventName
* @param {*} argv
* @returns
*/
function broadcastEvent(eventName, argv) {
let promises = [];
for (let process of Object.values(gProcesses)) {
@ -64,11 +49,6 @@ function broadcastEvent(eventName, argv) {
return Promise.all(promises);
}
/**
* TODOC
* @param {*} message
* @returns
*/
function broadcast(message) {
let sender = this;
let promises = [];
@ -85,12 +65,6 @@ function broadcast(message) {
return Promise.all(promises);
}
/**
* TODOC
* @param {String} eventName
* @param {*} argv
* @returns
*/
function broadcastAppEventToUser(
user,
packageOwner,
@ -113,12 +87,6 @@ function broadcastAppEventToUser(
return Promise.all(promises);
}
/**
* TODOC
* @param {*} caller
* @param {*} process
* @returns
*/
function getUser(caller, process) {
return {
key: process.key,
@ -129,12 +97,6 @@ function getUser(caller, process) {
};
}
/**
* TODOC
* @param {*} user
* @param {*} process
* @returns
*/
async function getApps(user, process) {
if (
process.credentials &&
@ -161,28 +123,13 @@ async function getApps(user, process) {
return {};
}
/**
* TODOC
* @param {*} from
* @param {*} to
* @param {*} message
* @returns
*/
function postMessageInternal(from, to, message) {
if (to.eventHandlers['message']) {
return invoke(to.eventHandlers['message'], [getUser(from, from), message]);
}
}
/**
* TODOC
* @param {*} blobId
* @param {*} key
* @param {*} options
* @returns
*/
async function getProcessBlob(blobId, key, options) {
// TODO(tasiaiso): break this down ?
let process = gProcesses[key];
if (!process && !(options && 'create' in options && !options.create)) {
let resolveReady;
@ -201,7 +148,7 @@ async function getProcessBlob(blobId, key, options) {
}
process.lastActive = Date.now();
process.lastPing = null;
process.timeout = kPingInterval;
process.timeout = k_ping_interval;
process.ready = new Promise(function (resolve, reject) {
resolveReady = resolve;
rejectReady = reject;
@ -461,16 +408,15 @@ async function getProcessBlob(blobId, key, options) {
if (process.app) {
process.app.makeFunction(['error'])(error);
} else {
printError({print: print}, error);
printError(error);
}
} catch (e) {
printError({print: print}, error);
printError(error);
}
};
imports.ssb = Object.fromEntries(
Object.keys(ssb).map((key) => [key, ssb[key].bind(ssb)])
);
imports.ssb.port = tildefriends.ssb_port;
imports.ssb.createIdentity = () => process.createIdentity();
imports.ssb.addIdentity = function (id) {
if (
@ -650,17 +596,26 @@ async function getProcessBlob(blobId, key, options) {
permissions: await imports.core.permissionsGranted(),
});
};
process.resetPermission = async function resetPermission(permission) {
let user = process?.credentials?.session?.name;
await ssb.setUserPermission(
user,
options?.packageOwner,
options?.packageName,
permission,
undefined
);
return process.sendPermissions();
process.client_api = {
createIdentity: function () {
return process.createIdentity();
},
resetPermission: async function resetPermission(message) {
let user = process?.credentials?.session?.name;
await ssb.setUserPermission(
user,
options?.packageOwner,
options?.packageName,
message.permission,
undefined
);
return process.sendPermissions();
},
setActiveIdentity: function setActiveIdentity(message) {
return process.setActiveIdentity(message.identity);
},
};
ssb.registerImports(imports, process);
process.task.setImports(imports);
process.task.activate();
let source = await ssb.blobGet(blobId);
@ -687,7 +642,7 @@ async function getProcessBlob(blobId, key, options) {
);
}
} catch (e) {
printError({print: print}, e);
printError(e);
}
broadcastEvent('onSessionBegin', [getUser(process, process)]);
if (process.app) {
@ -701,14 +656,10 @@ async function getProcessBlob(blobId, key, options) {
sendStats();
}
} catch (error) {
if (process.app) {
if (process?.task?.onError) {
process.task.onError(error);
} else {
printError({print: print}, error);
}
if (process?.app && process?.task?.onError) {
process.task.onError(error);
} else {
printError({print: print}, error);
printError(error);
}
rejectReady(error);
}
@ -728,9 +679,6 @@ ssb.addEventListener('connections', function () {
broadcastEvent('onConnectionsChanged', []);
});
/**
* TODOC
*/
async function loadSettings() {
let data = {};
try {
@ -749,9 +697,6 @@ async function loadSettings() {
return data;
}
/**
* TODOC
*/
function sendStats() {
let apps = Object.values(gProcesses)
.filter((process) => process.app)
@ -767,8 +712,6 @@ function sendStats() {
}
}
let g_handler_index = 0;
exports.callAppHandler = async function callAppHandler(
response,
app_blob_id,

View File

@ -1,4 +1,4 @@
/* W3.CSS 4.15 December 2020 by Jan Egil and Borge Refsnes */
/* W3.CSS 5.02 March 31 2025 by Jan Egil and Borge Refsnes */
html{box-sizing:border-box}*,*:before,*:after{box-sizing:inherit}
/* Extract from normalize.css by Nicolas Gallagher and Jonathan Neal git.io/normalize */
html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}
@ -108,6 +108,8 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
.w3-round-small{border-radius:2px}.w3-round,.w3-round-medium{border-radius:4px}.w3-round-large{border-radius:8px}.w3-round-xlarge{border-radius:16px}.w3-round-xxlarge{border-radius:32px}
.w3-row-padding,.w3-row-padding>.w3-half,.w3-row-padding>.w3-third,.w3-row-padding>.w3-twothird,.w3-row-padding>.w3-threequarter,.w3-row-padding>.w3-quarter,.w3-row-padding>.w3-col{padding:0 8px}
.w3-container,.w3-panel{padding:0.01em 16px}.w3-panel{margin-top:16px;margin-bottom:16px}
.w3-grid{display:grid}.w3-grid-padding{display:grid;gap:16px}.w3-flex{display:flex}
.w3-text-center{text-align:center}.w3-text-bold,.w3-bold{font-weight:bold}.w3-text-italic,.w3-italic{font-style:italic}
.w3-code,.w3-codespan{font-family:Consolas,"courier new";font-size:16px}
.w3-code{width:auto;background-color:#fff;padding:8px 12px;border-left:4px solid #4CAF50;word-wrap:break-word}
.w3-codespan{color:crimson;background-color:#f1f1f1;padding-left:4px;padding-right:4px;font-size:110%}
@ -148,6 +150,7 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
.w3-button:hover{color:#000!important;background-color:#ccc!important}
.w3-transparent,.w3-hover-none:hover{background-color:transparent!important}
.w3-hover-none:hover{box-shadow:none!important}
.w3-rtl{direction:rtl}.w3-ltr{direction:ltr}
/* Colors */
.w3-amber,.w3-hover-amber:hover{color:#000!important;background-color:#ffc107!important}
.w3-aqua,.w3-hover-aqua:hover{color:#000!important;background-color:#00ffff!important}
@ -175,6 +178,19 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
.w3-grey,.w3-hover-grey:hover,.w3-gray,.w3-hover-gray:hover{color:#000!important;background-color:#9e9e9e!important}
.w3-light-grey,.w3-hover-light-grey:hover,.w3-light-gray,.w3-hover-light-gray:hover{color:#000!important;background-color:#f1f1f1!important}
.w3-dark-grey,.w3-hover-dark-grey:hover,.w3-dark-gray,.w3-hover-dark-gray:hover{color:#fff!important;background-color:#616161!important}
.w3-asphalt,.w3-hover-asphalt:hover{color:#fff!important;background-color:#343a40!important}
.w3-crimson,.w3-hover-crimson:hover{color:#fff!important;background-color:#a20025!important}
.w3-cobalt,w3-hover-cobalt:hover{color:#fff!important;background-color:#0050ef!important}
.w3-emerald,.w3-hover-emerald:hover{color:#fff!important;background-color:#008a00!important}
.w3-olive,.w3-hover-olive:hover{color:#fff!important;background-color:#6d8764!important}
.w3-paper,.w3-hover-paper:hover{color:#000!important;background-color:#f8f9fa!important}
.w3-sienna,.w3-hover-sienna:hover{color:#fff!important;background-color:#a0522d!important}
.w3-taupe,.w3-hover-taupe:hover{color:#fff!important;background-color:#87794e!important}
.w3-danger{color:#fff!important;background-color:#dd0000!important}
.w3-note{color:#000!important;background-color:#fff599!important}
.w3-info{color:#fff!important;background-color:#0a6fc2!important}
.w3-warning{color:#000!important;background-color:#ffb305!important}
.w3-success{color:#fff!important;background-color:#008a00!important}
.w3-pale-red,.w3-hover-pale-red:hover{color:#000!important;background-color:#ffdddd!important}
.w3-pale-green,.w3-hover-pale-green:hover{color:#000!important;background-color:#ddffdd!important}
.w3-pale-yellow,.w3-hover-pale-yellow:hover{color:#000!important;background-color:#ffffcc!important}
@ -232,4 +248,4 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
.w3-border-light-grey,.w3-hover-border-light-grey:hover,.w3-border-light-gray,.w3-hover-border-light-gray:hover{border-color:#f1f1f1!important}
.w3-border-dark-grey,.w3-hover-border-dark-grey:hover,.w3-border-dark-gray,.w3-hover-border-dark-gray:hover{border-color:#616161!important}
.w3-border-pale-red,.w3-hover-border-pale-red:hover{border-color:#ffe7e7!important}.w3-border-pale-green,.w3-hover-border-pale-green:hover{border-color:#e7ffe7!important}
.w3-border-pale-yellow,.w3-hover-border-pale-yellow:hover{border-color:#ffffcc!important}.w3-border-pale-blue,.w3-hover-border-pale-blue:hover{border-color:#e7ffff!important}
.w3-border-pale-yellow,.w3-hover-border-pale-yellow:hover{border-color:#ffffcc!important}.w3-border-pale-blue,.w3-hover-border-pale-blue:hover{border-color:#e7ffff!important}

View File

@ -1,4 +1,8 @@
# How to upgrade to a newer version
# - On the june and december release, you'll have to update nixpkgs to the current branch
# Change `nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.11";`
# to the latest release (see https://nixos.org/)
# - Run `$ nix flake update`
# - Comment `src.hash`
# - Change `version`
# - Run `$ nix build`
@ -21,14 +25,14 @@
}:
pkgs.stdenv.mkDerivation rec {
pname = "tildefriends";
version = "0.0.27.1";
version = "0.0.30";
src = pkgs.fetchFromGitea {
domain = "dev.tildefriends.net";
owner = "cory";
repo = "tildefriends";
rev = "v${version}";
hash = "sha256-3t1m9ZomQF3DteWyALJWrnCq0EAROEK8shKXh6Ao38c=";
hash = "sha256-t5yvouzSL2j/ge1VHLqzIZ+Avqj4iEDt7L+yrHoTZAQ=";
fetchSubmodules = true;
};

2
deps/c-ares vendored

File diff suppressed because one or more lines are too long

243
deps/codemirror_src/package-lock.json generated vendored
View File

@ -30,9 +30,9 @@
}
},
"node_modules/@codemirror/commands": {
"version": "6.8.0",
"resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.8.0.tgz",
"integrity": "sha512-q8VPEFaEP4ikSlt6ZxjB3zW72+7osfAYW9i8Zu943uqbKuz6utc1+F170hyLUCUltXORjQXRyYQNfkckzA/bPQ==",
"version": "6.8.1",
"resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.8.1.tgz",
"integrity": "sha512-KlGVYufHMQzxbdQONiLyGQDUW0itrLZwq3CcY7xpv9ZLRHqzkBSoteocBHtMCoY7/Ci4xhzSrToIeLg7FxHuaw==",
"dependencies": {
"@codemirror/language": "^6.0.0",
"@codemirror/state": "^6.4.0",
@ -69,9 +69,9 @@
}
},
"node_modules/@codemirror/lang-javascript": {
"version": "6.2.3",
"resolved": "https://registry.npmjs.org/@codemirror/lang-javascript/-/lang-javascript-6.2.3.tgz",
"integrity": "sha512-8PR3vIWg7pSu7ur8A07pGiYHgy3hHj+mRYRCSG8q+mPIrl0F02rgpGv+DsQTHRTc30rydOsf5PZ7yjKFg2Ackw==",
"version": "6.2.4",
"resolved": "https://registry.npmjs.org/@codemirror/lang-javascript/-/lang-javascript-6.2.4.tgz",
"integrity": "sha512-0WVmhp1QOqZ4Rt6GlVGwKJN3KW7Xh4H2q8ZZNGZaP6lRdxXJzmjm4FqvmOojVj6khWJHIb9sp7U/72W7xQgqAA==",
"dependencies": {
"@codemirror/autocomplete": "^6.0.0",
"@codemirror/language": "^6.6.0",
@ -92,9 +92,9 @@
}
},
"node_modules/@codemirror/language": {
"version": "6.10.8",
"resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.10.8.tgz",
"integrity": "sha512-wcP8XPPhDH2vTqf181U8MbZnW+tDyPYy0UzVOa+oHORjyT+mhhom9vBd7dApJwoDz9Nb/a8kHjJIsuA/t8vNFw==",
"version": "6.11.0",
"resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.11.0.tgz",
"integrity": "sha512-A7+f++LodNNc1wGgoRDTt78cOwWm9KVezApgjOMp1W4hM0898nsqBXwF+sbePE7ZRcjN7Sa1Z5m2oN27XkmEjQ==",
"dependencies": {
"@codemirror/state": "^6.0.0",
"@codemirror/view": "^6.23.0",
@ -105,9 +105,9 @@
}
},
"node_modules/@codemirror/lint": {
"version": "6.8.4",
"resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.8.4.tgz",
"integrity": "sha512-u4q7PnZlJUojeRe8FJa/njJcMctISGgPQ4PnWsd9268R4ZTtU+tfFYmwkBvgcrK2+QQ8tYFVALVb5fVJykKc5A==",
"version": "6.8.5",
"resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.8.5.tgz",
"integrity": "sha512-s3n3KisH7dx3vsoeGMxsbRAgKe4O1vbrnKBClm99PU0fWxmxsx5rR2PfqQgIt+2MMJBHbiJ5rfIdLYfB9NNvsA==",
"dependencies": {
"@codemirror/state": "^6.0.0",
"@codemirror/view": "^6.35.0",
@ -115,9 +115,9 @@
}
},
"node_modules/@codemirror/search": {
"version": "6.5.9",
"resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.5.9.tgz",
"integrity": "sha512-7DdQ9aaZMMxuWB1u6IIFWWuK9NocVZwvo4nG8QjJTS6oZGvteoLSiXw3EbVZVlO08Ri2ltO89JVInMpfcJxhtg==",
"version": "6.5.11",
"resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.5.11.tgz",
"integrity": "sha512-KmWepDE6jUdL6n8cAAqIpRmLPBZ5ZKnicE8oGU/s3QrAVID+0VhLFrzUucVKHG5035/BSykhExDL/Xm7dHthiA==",
"dependencies": {
"@codemirror/state": "^6.0.0",
"@codemirror/view": "^6.0.0",
@ -144,9 +144,9 @@
}
},
"node_modules/@codemirror/view": {
"version": "6.36.2",
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.36.2.tgz",
"integrity": "sha512-DZ6ONbs8qdJK0fdN7AB82CgI6tYXf4HWk1wSVa0+9bhVznCuuvhQtX8bFBoy3dv8rZSQqUd8GvhVAcielcidrA==",
"version": "6.36.8",
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.36.8.tgz",
"integrity": "sha512-yoRo4f+FdnD01fFt4XpfpMCcCAo9QvZOtbrXExn4SqzH32YC6LgzqxfLZw/r6Ge65xyY03mK/UfUqrVw1gFiFg==",
"dependencies": {
"@codemirror/state": "^6.5.0",
"style-mod": "^4.1.0",
@ -217,13 +217,13 @@
"integrity": "sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA=="
},
"node_modules/@lezer/css": {
"version": "1.1.10",
"resolved": "https://registry.npmjs.org/@lezer/css/-/css-1.1.10.tgz",
"integrity": "sha512-V5/89eDapjeAkWPBpWEfQjZ1Hag3aYUUJOL8213X0dFRuXJ4BXa5NKl9USzOnaLod4AOpmVCkduir2oKwZYZtg==",
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@lezer/css/-/css-1.2.1.tgz",
"integrity": "sha512-2F5tOqzKEKbCUNraIXc0f6HKeyKlmMWJnBB0i4XW6dJgssrZO/YlZ2pY5xgyqDleqqhiNJ3dQhbrV2aClZQMvg==",
"dependencies": {
"@lezer/common": "^1.2.0",
"@lezer/highlight": "^1.0.0",
"@lezer/lr": "^1.0.0"
"@lezer/lr": "^1.3.0"
}
},
"node_modules/@lezer/highlight": {
@ -245,9 +245,9 @@
}
},
"node_modules/@lezer/javascript": {
"version": "1.4.21",
"resolved": "https://registry.npmjs.org/@lezer/javascript/-/javascript-1.4.21.tgz",
"integrity": "sha512-lL+1fcuxWYPURMM/oFZLEDm0XuLN128QPV+VuGtKpeaOGdcl9F2LYC3nh1S9LkPqx9M0mndZFdXCipNAZpzIkQ==",
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/@lezer/javascript/-/javascript-1.5.1.tgz",
"integrity": "sha512-ATOImjeVJuvgm3JQ/bpo2Tmv55HSScE2MTPnKRMRIPx2cLhHGyX2VnqpHhtIV1tVzIjZDbcWQm+NCTF40ggZVw==",
"dependencies": {
"@lezer/common": "^1.2.0",
"@lezer/highlight": "^1.1.3",
@ -344,9 +344,9 @@
}
},
"node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.34.6",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.34.6.tgz",
"integrity": "sha512-+GcCXtOQoWuC7hhX1P00LqjjIiS/iOouHXhMdiDSnq/1DGTox4SpUvO52Xm+div6+106r+TcvOeo/cxvyEyTgg==",
"version": "4.41.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.41.1.tgz",
"integrity": "sha512-NELNvyEWZ6R9QMkiytB4/L4zSEaBC03KIXEghptLGLZWJ6VPrL63ooZQCOnlx36aQPGhzuOMwDerC1Eb2VmrLw==",
"cpu": [
"arm"
],
@ -356,9 +356,9 @@
]
},
"node_modules/@rollup/rollup-android-arm64": {
"version": "4.34.6",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.34.6.tgz",
"integrity": "sha512-E8+2qCIjciYUnCa1AiVF1BkRgqIGW9KzJeesQqVfyRITGQN+dFuoivO0hnro1DjT74wXLRZ7QF8MIbz+luGaJA==",
"version": "4.41.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.41.1.tgz",
"integrity": "sha512-DXdQe1BJ6TK47ukAoZLehRHhfKnKg9BjnQYUu9gzhI8Mwa1d2fzxA1aw2JixHVl403bwp1+/o/NhhHtxWJBgEA==",
"cpu": [
"arm64"
],
@ -368,9 +368,9 @@
]
},
"node_modules/@rollup/rollup-darwin-arm64": {
"version": "4.34.6",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.34.6.tgz",
"integrity": "sha512-z9Ib+OzqN3DZEjX7PDQMHEhtF+t6Mi2z/ueChQPLS/qUMKY7Ybn5A2ggFoKRNRh1q1T03YTQfBTQCJZiepESAg==",
"version": "4.41.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.41.1.tgz",
"integrity": "sha512-5afxvwszzdulsU2w8JKWwY8/sJOLPzf0e1bFuvcW5h9zsEg+RQAojdW0ux2zyYAz7R8HvvzKCjLNJhVq965U7w==",
"cpu": [
"arm64"
],
@ -380,9 +380,9 @@
]
},
"node_modules/@rollup/rollup-darwin-x64": {
"version": "4.34.6",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.34.6.tgz",
"integrity": "sha512-PShKVY4u0FDAR7jskyFIYVyHEPCPnIQY8s5OcXkdU8mz3Y7eXDJPdyM/ZWjkYdR2m0izD9HHWA8sGcXn+Qrsyg==",
"version": "4.41.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.41.1.tgz",
"integrity": "sha512-egpJACny8QOdHNNMZKf8xY0Is6gIMz+tuqXlusxquWu3F833DcMwmGM7WlvCO9sB3OsPjdC4U0wHw5FabzCGZg==",
"cpu": [
"x64"
],
@ -392,9 +392,9 @@
]
},
"node_modules/@rollup/rollup-freebsd-arm64": {
"version": "4.34.6",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.34.6.tgz",
"integrity": "sha512-YSwyOqlDAdKqs0iKuqvRHLN4SrD2TiswfoLfvYXseKbL47ht1grQpq46MSiQAx6rQEN8o8URtpXARCpqabqxGQ==",
"version": "4.41.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.41.1.tgz",
"integrity": "sha512-DBVMZH5vbjgRk3r0OzgjS38z+atlupJ7xfKIDJdZZL6sM6wjfDNo64aowcLPKIx7LMQi8vybB56uh1Ftck/Atg==",
"cpu": [
"arm64"
],
@ -404,9 +404,9 @@
]
},
"node_modules/@rollup/rollup-freebsd-x64": {
"version": "4.34.6",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.34.6.tgz",
"integrity": "sha512-HEP4CgPAY1RxXwwL5sPFv6BBM3tVeLnshF03HMhJYCNc6kvSqBgTMmsEjb72RkZBAWIqiPUyF1JpEBv5XT9wKQ==",
"version": "4.41.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.41.1.tgz",
"integrity": "sha512-3FkydeohozEskBxNWEIbPfOE0aqQgB6ttTkJ159uWOFn42VLyfAiyD9UK5mhu+ItWzft60DycIN1Xdgiy8o/SA==",
"cpu": [
"x64"
],
@ -416,9 +416,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
"version": "4.34.6",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.34.6.tgz",
"integrity": "sha512-88fSzjC5xeH9S2Vg3rPgXJULkHcLYMkh8faix8DX4h4TIAL65ekwuQMA/g2CXq8W+NJC43V6fUpYZNjaX3+IIg==",
"version": "4.41.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.41.1.tgz",
"integrity": "sha512-wC53ZNDgt0pqx5xCAgNunkTzFE8GTgdZ9EwYGVcg+jEjJdZGtq9xPjDnFgfFozQI/Xm1mh+D9YlYtl+ueswNEg==",
"cpu": [
"arm"
],
@ -428,9 +428,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
"version": "4.34.6",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.34.6.tgz",
"integrity": "sha512-wM4ztnutBqYFyvNeR7Av+reWI/enK9tDOTKNF+6Kk2Q96k9bwhDDOlnCUNRPvromlVXo04riSliMBs/Z7RteEg==",
"version": "4.41.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.41.1.tgz",
"integrity": "sha512-jwKCca1gbZkZLhLRtsrka5N8sFAaxrGz/7wRJ8Wwvq3jug7toO21vWlViihG85ei7uJTpzbXZRcORotE+xyrLA==",
"cpu": [
"arm"
],
@ -440,9 +440,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-gnu": {
"version": "4.34.6",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.34.6.tgz",
"integrity": "sha512-9RyprECbRa9zEjXLtvvshhw4CMrRa3K+0wcp3KME0zmBe1ILmvcVHnypZ/aIDXpRyfhSYSuN4EPdCCj5Du8FIA==",
"version": "4.41.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.41.1.tgz",
"integrity": "sha512-g0UBcNknsmmNQ8V2d/zD2P7WWfJKU0F1nu0k5pW4rvdb+BIqMm8ToluW/eeRmxCared5dD76lS04uL4UaNgpNA==",
"cpu": [
"arm64"
],
@ -452,9 +452,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-musl": {
"version": "4.34.6",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.34.6.tgz",
"integrity": "sha512-qTmklhCTyaJSB05S+iSovfo++EwnIEZxHkzv5dep4qoszUMX5Ca4WM4zAVUMbfdviLgCSQOu5oU8YoGk1s6M9Q==",
"version": "4.41.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.41.1.tgz",
"integrity": "sha512-XZpeGB5TKEZWzIrj7sXr+BEaSgo/ma/kCgrZgL0oo5qdB1JlTzIYQKel/RmhT6vMAvOdM2teYlAaOGJpJ9lahg==",
"cpu": [
"arm64"
],
@ -464,9 +464,9 @@
]
},
"node_modules/@rollup/rollup-linux-loongarch64-gnu": {
"version": "4.34.6",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.34.6.tgz",
"integrity": "sha512-4Qmkaps9yqmpjY5pvpkfOerYgKNUGzQpFxV6rnS7c/JfYbDSU0y6WpbbredB5cCpLFGJEqYX40WUmxMkwhWCjw==",
"version": "4.41.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.41.1.tgz",
"integrity": "sha512-bkCfDJ4qzWfFRCNt5RVV4DOw6KEgFTUZi2r2RuYhGWC8WhCA8lCAJhDeAmrM/fdiAH54m0mA0Vk2FGRPyzI+tw==",
"cpu": [
"loong64"
],
@ -476,9 +476,9 @@
]
},
"node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
"version": "4.34.6",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.34.6.tgz",
"integrity": "sha512-Zsrtux3PuaxuBTX/zHdLaFmcofWGzaWW1scwLU3ZbW/X+hSsFbz9wDIp6XvnT7pzYRl9MezWqEqKy7ssmDEnuQ==",
"version": "4.41.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.41.1.tgz",
"integrity": "sha512-3mr3Xm+gvMX+/8EKogIZSIEF0WUu0HL9di+YWlJpO8CQBnoLAEL/roTCxuLncEdgcfJcvA4UMOf+2dnjl4Ut1A==",
"cpu": [
"ppc64"
],
@ -488,9 +488,21 @@
]
},
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
"version": "4.34.6",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.34.6.tgz",
"integrity": "sha512-aK+Zp+CRM55iPrlyKiU3/zyhgzWBxLVrw2mwiQSYJRobCURb781+XstzvA8Gkjg/hbdQFuDw44aUOxVQFycrAg==",
"version": "4.41.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.41.1.tgz",
"integrity": "sha512-3rwCIh6MQ1LGrvKJitQjZFuQnT2wxfU+ivhNBzmxXTXPllewOF7JR1s2vMX/tWtUYFgphygxjqMl76q4aMotGw==",
"cpu": [
"riscv64"
],
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-riscv64-musl": {
"version": "4.41.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.41.1.tgz",
"integrity": "sha512-LdIUOb3gvfmpkgFZuccNa2uYiqtgZAz3PTzjuM5bH3nvuy9ty6RGc/Q0+HDFrHrizJGVpjnTZ1yS5TNNjFlklw==",
"cpu": [
"riscv64"
],
@ -500,9 +512,9 @@
]
},
"node_modules/@rollup/rollup-linux-s390x-gnu": {
"version": "4.34.6",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.34.6.tgz",
"integrity": "sha512-WoKLVrY9ogmaYPXwTH326+ErlCIgMmsoRSx6bO+l68YgJnlOXhygDYSZe/qbUJCSiCiZAQ+tKm88NcWuUXqOzw==",
"version": "4.41.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.41.1.tgz",
"integrity": "sha512-oIE6M8WC9ma6xYqjvPhzZYk6NbobIURvP/lEbh7FWplcMO6gn7MM2yHKA1eC/GvYwzNKK/1LYgqzdkZ8YFxR8g==",
"cpu": [
"s390x"
],
@ -512,9 +524,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-gnu": {
"version": "4.34.6",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.34.6.tgz",
"integrity": "sha512-Sht4aFvmA4ToHd2vFzwMFaQCiYm2lDFho5rPcvPBT5pCdC+GwHG6CMch4GQfmWTQ1SwRKS0dhDYb54khSrjDWw==",
"version": "4.41.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.41.1.tgz",
"integrity": "sha512-cWBOvayNvA+SyeQMp79BHPK8ws6sHSsYnK5zDcsC3Hsxr1dgTABKjMnMslPq1DvZIp6uO7kIWhiGwaTdR4Og9A==",
"cpu": [
"x64"
],
@ -524,9 +536,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-musl": {
"version": "4.34.6",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.34.6.tgz",
"integrity": "sha512-zmmpOQh8vXc2QITsnCiODCDGXFC8LMi64+/oPpPx5qz3pqv0s6x46ps4xoycfUiVZps5PFn1gksZzo4RGTKT+A==",
"version": "4.41.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.41.1.tgz",
"integrity": "sha512-y5CbN44M+pUCdGDlZFzGGBSKCA4A/J2ZH4edTYSSxFg7ce1Xt3GtydbVKWLlzL+INfFIZAEg1ZV6hh9+QQf9YQ==",
"cpu": [
"x64"
],
@ -536,9 +548,9 @@
]
},
"node_modules/@rollup/rollup-win32-arm64-msvc": {
"version": "4.34.6",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.34.6.tgz",
"integrity": "sha512-3/q1qUsO/tLqGBaD4uXsB6coVGB3usxw3qyeVb59aArCgedSF66MPdgRStUd7vbZOsko/CgVaY5fo2vkvPLWiA==",
"version": "4.41.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.41.1.tgz",
"integrity": "sha512-lZkCxIrjlJlMt1dLO/FbpZbzt6J/A8p4DnqzSa4PWqPEUUUnzXLeki/iyPLfV0BmHItlYgHUqJe+3KiyydmiNQ==",
"cpu": [
"arm64"
],
@ -548,9 +560,9 @@
]
},
"node_modules/@rollup/rollup-win32-ia32-msvc": {
"version": "4.34.6",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.34.6.tgz",
"integrity": "sha512-oLHxuyywc6efdKVTxvc0135zPrRdtYVjtVD5GUm55I3ODxhU/PwkQFD97z16Xzxa1Fz0AEe4W/2hzRtd+IfpOA==",
"version": "4.41.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.41.1.tgz",
"integrity": "sha512-+psFT9+pIh2iuGsxFYYa/LhS5MFKmuivRsx9iPJWNSGbh2XVEjk90fmpUEjCnILPEPJnikAU6SFDiEUyOv90Pg==",
"cpu": [
"ia32"
],
@ -560,9 +572,9 @@
]
},
"node_modules/@rollup/rollup-win32-x64-msvc": {
"version": "4.34.6",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.34.6.tgz",
"integrity": "sha512-0PVwmgzZ8+TZ9oGBmdZoQVXflbvuwzN/HRclujpl4N/q3i+y0lqLw8n1bXA8ru3sApDjlmONaNAuYr38y1Kr9w==",
"version": "4.41.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.41.1.tgz",
"integrity": "sha512-Wq2zpapRYLfi4aKxf2Xff0tN+7slj2d4R87WEzqw7ZLsVvO5zwYCIuEGSZYiK41+GlwUo1HiR+GdkLEJnCKTCw==",
"cpu": [
"x64"
],
@ -572,9 +584,9 @@
]
},
"node_modules/@types/estree": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz",
"integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw=="
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz",
"integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ=="
},
"node_modules/@types/resolve": {
"version": "1.20.2",
@ -582,9 +594,9 @@
"integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q=="
},
"node_modules/acorn": {
"version": "8.14.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz",
"integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==",
"version": "8.14.1",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz",
"integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==",
"dev": true,
"bin": {
"acorn": "bin/acorn"
@ -733,11 +745,11 @@
}
},
"node_modules/rollup": {
"version": "4.34.6",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.34.6.tgz",
"integrity": "sha512-wc2cBWqJgkU3Iz5oztRkQbfVkbxoz5EhnCGOrnJvnLnQ7O0WhQUYyv18qQI79O8L7DdHrrlJNeCHd4VGpnaXKQ==",
"version": "4.41.1",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.41.1.tgz",
"integrity": "sha512-cPmwD3FnFv8rKMBc1MxWCwVQFxwf1JEmSX3iQXrRVVG15zerAIXRjMFVWnd5Q5QvgKF7Aj+5ykXFhUl+QGnyOw==",
"dependencies": {
"@types/estree": "1.0.6"
"@types/estree": "1.0.7"
},
"bin": {
"rollup": "dist/bin/rollup"
@ -747,25 +759,26 @@
"npm": ">=8.0.0"
},
"optionalDependencies": {
"@rollup/rollup-android-arm-eabi": "4.34.6",
"@rollup/rollup-android-arm64": "4.34.6",
"@rollup/rollup-darwin-arm64": "4.34.6",
"@rollup/rollup-darwin-x64": "4.34.6",
"@rollup/rollup-freebsd-arm64": "4.34.6",
"@rollup/rollup-freebsd-x64": "4.34.6",
"@rollup/rollup-linux-arm-gnueabihf": "4.34.6",
"@rollup/rollup-linux-arm-musleabihf": "4.34.6",
"@rollup/rollup-linux-arm64-gnu": "4.34.6",
"@rollup/rollup-linux-arm64-musl": "4.34.6",
"@rollup/rollup-linux-loongarch64-gnu": "4.34.6",
"@rollup/rollup-linux-powerpc64le-gnu": "4.34.6",
"@rollup/rollup-linux-riscv64-gnu": "4.34.6",
"@rollup/rollup-linux-s390x-gnu": "4.34.6",
"@rollup/rollup-linux-x64-gnu": "4.34.6",
"@rollup/rollup-linux-x64-musl": "4.34.6",
"@rollup/rollup-win32-arm64-msvc": "4.34.6",
"@rollup/rollup-win32-ia32-msvc": "4.34.6",
"@rollup/rollup-win32-x64-msvc": "4.34.6",
"@rollup/rollup-android-arm-eabi": "4.41.1",
"@rollup/rollup-android-arm64": "4.41.1",
"@rollup/rollup-darwin-arm64": "4.41.1",
"@rollup/rollup-darwin-x64": "4.41.1",
"@rollup/rollup-freebsd-arm64": "4.41.1",
"@rollup/rollup-freebsd-x64": "4.41.1",
"@rollup/rollup-linux-arm-gnueabihf": "4.41.1",
"@rollup/rollup-linux-arm-musleabihf": "4.41.1",
"@rollup/rollup-linux-arm64-gnu": "4.41.1",
"@rollup/rollup-linux-arm64-musl": "4.41.1",
"@rollup/rollup-linux-loongarch64-gnu": "4.41.1",
"@rollup/rollup-linux-powerpc64le-gnu": "4.41.1",
"@rollup/rollup-linux-riscv64-gnu": "4.41.1",
"@rollup/rollup-linux-riscv64-musl": "4.41.1",
"@rollup/rollup-linux-s390x-gnu": "4.41.1",
"@rollup/rollup-linux-x64-gnu": "4.41.1",
"@rollup/rollup-linux-x64-musl": "4.41.1",
"@rollup/rollup-win32-arm64-msvc": "4.41.1",
"@rollup/rollup-win32-ia32-msvc": "4.41.1",
"@rollup/rollup-win32-x64-msvc": "4.41.1",
"fsevents": "~2.3.2"
}
},
@ -840,13 +853,13 @@
}
},
"node_modules/terser": {
"version": "5.38.2",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.38.2.tgz",
"integrity": "sha512-w8CXxxbFA5zfNsR/i8HZq5bvn18AK0O9jj7hyo1YqkovLxEFa0uP0LCVGZRqiRaKRFxXhELBp8SteeAjEnfeJg==",
"version": "5.40.0",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.40.0.tgz",
"integrity": "sha512-cfeKl/jjwSR5ar7d0FGmave9hFGJT8obyo0z+CrQOylLDbk7X81nPU6vq9VORa5jU30SkDnT2FXjLbR8HLP+xA==",
"dev": true,
"dependencies": {
"@jridgewell/source-map": "^0.3.3",
"acorn": "^8.8.2",
"acorn": "^8.14.0",
"commander": "^2.20.0",
"source-map-support": "~0.5.20"
},

2
deps/libuv vendored

Submodule deps/libuv updated: 8fb9cb9194...5152db2cbf

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

2
deps/quickjs vendored

View File

@ -11,7 +11,7 @@
<link rel="icon" type="image/x-icon" href="favicon-FOKUP5Y5.ico">
</head>
<body>
<script src="speedscope-CAEVGCWN.js"></script>
<script src="speedscope-VHEG2FVF.js"></script>

View File

@ -1,3 +1,3 @@
speedscope@1.22.0
Thu Jan 16 16:49:47 PST 2025
bd7b44a0a7d63375ee6ea0a0d1b96e65a456642f
speedscope@1.22.2
Sat Feb 15 13:02:38 PST 2025
1c254dcb3e2b4f6d921340d20e972d9d27b788f4

File diff suppressed because one or more lines are too long

71
deps/sqlite/shell.c vendored
View File

@ -6267,8 +6267,7 @@ int sqlite3_ieee_init(
** step HIDDEN
** );
**
** The virtual table also has a rowid, logically equivalent to n+1 where
** "n" is the ascending integer in the aforesaid production definition.
** The virtual table also has a rowid which is an alias for the value.
**
** Function arguments in queries against this virtual table are translated
** into equality constraints against successive hidden columns. In other
@ -6323,6 +6322,7 @@ SQLITE_EXTENSION_INIT1
#include <assert.h>
#include <string.h>
#include <limits.h>
#include <math.h>
#ifndef SQLITE_OMIT_VIRTUALTABLE
/*
@ -6483,6 +6483,7 @@ static int seriesConnect(
int rc;
/* Column numbers */
#define SERIES_COLUMN_ROWID (-1)
#define SERIES_COLUMN_VALUE 0
#define SERIES_COLUMN_START 1
#define SERIES_COLUMN_STOP 2
@ -6570,13 +6571,11 @@ static int seriesColumn(
#endif
/*
** Return the rowid for the current row, logically equivalent to n+1 where
** "n" is the ascending integer in the aforesaid production definition.
** The rowid is the same as the value.
*/
static int seriesRowid(sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid){
series_cursor *pCur = (series_cursor*)cur;
sqlite3_uint64 n = pCur->ss.uSeqIndexNow;
*pRowid = (sqlite3_int64)((n<LARGEST_UINT64)? n+1 : 0);
*pRowid = pCur->ss.iValueNow;
return SQLITE_OK;
}
@ -6689,25 +6688,52 @@ static int seriesFilter(
** constraints on the "value" column.
*/
if( idxNum & 0x0080 ){
iMin = iMax = sqlite3_value_int64(argv[i++]);
if( sqlite3_value_numeric_type(argv[i])==SQLITE_FLOAT ){
double r = sqlite3_value_double(argv[i++]);
if( r==ceil(r) ){
iMin = iMax = (sqlite3_int64)r;
}else{
returnNoRows = 1;
}
}else{
iMin = iMax = sqlite3_value_int64(argv[i++]);
}
}else{
if( idxNum & 0x0300 ){
iMin = sqlite3_value_int64(argv[i++]);
if( idxNum & 0x0200 ){
if( iMin==LARGEST_INT64 ){
returnNoRows = 1;
if( sqlite3_value_numeric_type(argv[i])==SQLITE_FLOAT ){
double r = sqlite3_value_double(argv[i++]);
if( idxNum & 0x0200 && r==ceil(r) ){
iMin = (sqlite3_int64)ceil(r+1.0);
}else{
iMin++;
iMin = (sqlite3_int64)ceil(r);
}
}else{
iMin = sqlite3_value_int64(argv[i++]);
if( idxNum & 0x0200 ){
if( iMin==LARGEST_INT64 ){
returnNoRows = 1;
}else{
iMin++;
}
}
}
}
if( idxNum & 0x3000 ){
iMax = sqlite3_value_int64(argv[i++]);
if( idxNum & 0x2000 ){
if( iMax==SMALLEST_INT64 ){
returnNoRows = 1;
if( sqlite3_value_numeric_type(argv[i])==SQLITE_FLOAT ){
double r = sqlite3_value_double(argv[i++]);
if( (idxNum & 0x2000)!=0 && r==floor(r) ){
iMax = (sqlite3_int64)(r-1.0);
}else{
iMax--;
iMax = (sqlite3_int64)floor(r);
}
}else{
iMax = sqlite3_value_int64(argv[i++]);
if( idxNum & 0x2000 ){
if( iMax==SMALLEST_INT64 ){
returnNoRows = 1;
}else{
iMax--;
}
}
}
}
@ -6726,8 +6752,7 @@ static int seriesFilter(
pCur->ss.iBase += ((d+szStep-1)/szStep)*szStep;
}
if( pCur->ss.iTerm>iMax ){
sqlite3_uint64 d = pCur->ss.iTerm - iMax;
pCur->ss.iTerm -= ((d+szStep-1)/szStep)*szStep;
pCur->ss.iTerm = iMax;
}
}else{
sqlite3_int64 szStep = -pCur->ss.iStep;
@ -6737,8 +6762,7 @@ static int seriesFilter(
pCur->ss.iBase -= ((d+szStep-1)/szStep)*szStep;
}
if( pCur->ss.iTerm<iMin ){
sqlite3_uint64 d = iMin - pCur->ss.iTerm;
pCur->ss.iTerm += ((d+szStep-1)/szStep)*szStep;
pCur->ss.iTerm = iMin;
}
}
}
@ -6866,7 +6890,10 @@ static int seriesBestIndex(
continue;
}
if( pConstraint->iColumn<SERIES_COLUMN_START ){
if( pConstraint->iColumn==SERIES_COLUMN_VALUE && pConstraint->usable ){
if( (pConstraint->iColumn==SERIES_COLUMN_VALUE ||
pConstraint->iColumn==SERIES_COLUMN_ROWID)
&& pConstraint->usable
){
switch( op ){
case SQLITE_INDEX_CONSTRAINT_EQ:
case SQLITE_INDEX_CONSTRAINT_IS: {

131
deps/sqlite/sqlite3.c vendored
View File

@ -1,6 +1,6 @@
/******************************************************************************
** This file is an amalgamation of many separate C source files from SQLite
** version 3.49.0. By combining all the individual C code files into this
** version 3.49.2. By combining all the individual C code files into this
** single large file, the entire code can be compiled as a single translation
** unit. This allows many compilers to do optimizations that would not be
** possible if the files were compiled separately. Performance improvements
@ -18,7 +18,7 @@
** separate file. This file contains only code for the core SQLite library.
**
** The content in this amalgamation comes from Fossil check-in
** 4a7dd425dc2a0e5082a9049c9b4a9d4f199a with changes in files:
** 17144570b0d96ae63cd6f3edca39e27ebd74 with changes in files:
**
**
*/
@ -465,9 +465,9 @@ extern "C" {
** [sqlite3_libversion_number()], [sqlite3_sourceid()],
** [sqlite_version()] and [sqlite_source_id()].
*/
#define SQLITE_VERSION "3.49.0"
#define SQLITE_VERSION_NUMBER 3049000
#define SQLITE_SOURCE_ID "2025-02-06 11:55:18 4a7dd425dc2a0e5082a9049c9b4a9d4f199a71583d014c24b4cfe276c5a77cde"
#define SQLITE_VERSION "3.49.2"
#define SQLITE_VERSION_NUMBER 3049002
#define SQLITE_SOURCE_ID "2025-05-07 10:39:52 17144570b0d96ae63cd6f3edca39e27ebd74925252bbaf6723bcb2f6b4861fb1"
/*
** CAPI3REF: Run-Time Library Version Numbers
@ -19064,6 +19064,7 @@ struct Index {
unsigned bLowQual:1; /* sqlite_stat1 says this is a low-quality index */
unsigned bNoQuery:1; /* Do not use this index to optimize queries */
unsigned bAscKeyBug:1; /* True if the bba7b69f9849b5bf bug applies */
unsigned bIdxRowid:1; /* One or more of the index keys is the ROWID */
unsigned bHasVCol:1; /* Index references one or more VIRTUAL columns */
unsigned bHasExpr:1; /* Index contains an expression, either a literal
** expression, or a reference to a VIRTUAL column */
@ -97241,6 +97242,7 @@ case OP_MakeRecord: {
zHdr += sqlite3PutVarint(zHdr, serial_type);
if( pRec->n ){
assert( pRec->z!=0 );
assert( pRec->z!=(const char*)sqlite3CtypeMap );
memcpy(zPayload, pRec->z, pRec->n);
zPayload += pRec->n;
}
@ -115468,11 +115470,11 @@ SQLITE_PRIVATE void sqlite3ExprIfTrue(Parse *pParse, Expr *pExpr, int dest, int
assert( TK_ISNULL==OP_IsNull ); testcase( op==TK_ISNULL );
assert( TK_NOTNULL==OP_NotNull ); testcase( op==TK_NOTNULL );
r1 = sqlite3ExprCodeTemp(pParse, pExpr->pLeft, &regFree1);
sqlite3VdbeTypeofColumn(v, r1);
assert( regFree1==0 || regFree1==r1 );
if( regFree1 ) sqlite3VdbeTypeofColumn(v, r1);
sqlite3VdbeAddOp2(v, op, r1, dest);
VdbeCoverageIf(v, op==TK_ISNULL);
VdbeCoverageIf(v, op==TK_NOTNULL);
testcase( regFree1==0 );
break;
}
case TK_BETWEEN: {
@ -115643,11 +115645,11 @@ SQLITE_PRIVATE void sqlite3ExprIfFalse(Parse *pParse, Expr *pExpr, int dest, int
case TK_ISNULL:
case TK_NOTNULL: {
r1 = sqlite3ExprCodeTemp(pParse, pExpr->pLeft, &regFree1);
sqlite3VdbeTypeofColumn(v, r1);
assert( regFree1==0 || regFree1==r1 );
if( regFree1 ) sqlite3VdbeTypeofColumn(v, r1);
sqlite3VdbeAddOp2(v, op, r1, dest);
testcase( op==TK_ISNULL ); VdbeCoverageIf(v, op==TK_ISNULL);
testcase( op==TK_NOTNULL ); VdbeCoverageIf(v, op==TK_NOTNULL);
testcase( regFree1==0 );
break;
}
case TK_BETWEEN: {
@ -126336,6 +126338,7 @@ SQLITE_PRIVATE void sqlite3CreateIndex(
assert( j<=0x7fff );
if( j<0 ){
j = pTab->iPKey;
pIndex->bIdxRowid = 1;
}else{
if( pTab->aCol[j].notNull==0 ){
pIndex->uniqNotNull = 0;
@ -131067,7 +131070,7 @@ static void concatFuncCore(
for(i=0; i<argc; i++){
n += sqlite3_value_bytes(argv[i]);
}
n += (argc-1)*nSep;
n += (argc-1)*(i64)nSep;
z = sqlite3_malloc64(n+1);
if( z==0 ){
sqlite3_result_error_nomem(context);
@ -139132,48 +139135,48 @@ static const char *const pragCName[] = {
/* 13 */ "pk",
/* 14 */ "hidden",
/* table_info reuses 8 */
/* 15 */ "schema", /* Used by: table_list */
/* 16 */ "name",
/* 15 */ "name", /* Used by: function_list */
/* 16 */ "builtin",
/* 17 */ "type",
/* 18 */ "ncol",
/* 19 */ "wr",
/* 20 */ "strict",
/* 21 */ "seqno", /* Used by: index_xinfo */
/* 22 */ "cid",
/* 23 */ "name",
/* 24 */ "desc",
/* 25 */ "coll",
/* 26 */ "key",
/* 27 */ "name", /* Used by: function_list */
/* 28 */ "builtin",
/* 29 */ "type",
/* 30 */ "enc",
/* 31 */ "narg",
/* 32 */ "flags",
/* 33 */ "tbl", /* Used by: stats */
/* 34 */ "idx",
/* 35 */ "wdth",
/* 36 */ "hght",
/* 37 */ "flgs",
/* 38 */ "seq", /* Used by: index_list */
/* 39 */ "name",
/* 40 */ "unique",
/* 41 */ "origin",
/* 42 */ "partial",
/* 18 */ "enc",
/* 19 */ "narg",
/* 20 */ "flags",
/* 21 */ "schema", /* Used by: table_list */
/* 22 */ "name",
/* 23 */ "type",
/* 24 */ "ncol",
/* 25 */ "wr",
/* 26 */ "strict",
/* 27 */ "seqno", /* Used by: index_xinfo */
/* 28 */ "cid",
/* 29 */ "name",
/* 30 */ "desc",
/* 31 */ "coll",
/* 32 */ "key",
/* 33 */ "seq", /* Used by: index_list */
/* 34 */ "name",
/* 35 */ "unique",
/* 36 */ "origin",
/* 37 */ "partial",
/* 38 */ "tbl", /* Used by: stats */
/* 39 */ "idx",
/* 40 */ "wdth",
/* 41 */ "hght",
/* 42 */ "flgs",
/* 43 */ "table", /* Used by: foreign_key_check */
/* 44 */ "rowid",
/* 45 */ "parent",
/* 46 */ "fkid",
/* index_info reuses 21 */
/* 47 */ "seq", /* Used by: database_list */
/* 48 */ "name",
/* 49 */ "file",
/* 50 */ "busy", /* Used by: wal_checkpoint */
/* 51 */ "log",
/* 52 */ "checkpointed",
/* collation_list reuses 38 */
/* 47 */ "busy", /* Used by: wal_checkpoint */
/* 48 */ "log",
/* 49 */ "checkpointed",
/* 50 */ "seq", /* Used by: database_list */
/* 51 */ "name",
/* 52 */ "file",
/* index_info reuses 27 */
/* 53 */ "database", /* Used by: lock_status */
/* 54 */ "status",
/* collation_list reuses 33 */
/* 55 */ "cache_size", /* Used by: default_cache_size */
/* module_list pragma_list reuses 9 */
/* 56 */ "timeout", /* Used by: busy_timeout */
@ -139266,7 +139269,7 @@ static const PragmaName aPragmaName[] = {
{/* zName: */ "collation_list",
/* ePragTyp: */ PragTyp_COLLATION_LIST,
/* ePragFlg: */ PragFlg_Result0,
/* ColNames: */ 38, 2,
/* ColNames: */ 33, 2,
/* iArg: */ 0 },
#endif
#if !defined(SQLITE_OMIT_COMPILEOPTION_DIAGS)
@ -139301,7 +139304,7 @@ static const PragmaName aPragmaName[] = {
{/* zName: */ "database_list",
/* ePragTyp: */ PragTyp_DATABASE_LIST,
/* ePragFlg: */ PragFlg_Result0,
/* ColNames: */ 47, 3,
/* ColNames: */ 50, 3,
/* iArg: */ 0 },
#endif
#if !defined(SQLITE_OMIT_PAGER_PRAGMAS) && !defined(SQLITE_OMIT_DEPRECATED)
@ -139381,7 +139384,7 @@ static const PragmaName aPragmaName[] = {
{/* zName: */ "function_list",
/* ePragTyp: */ PragTyp_FUNCTION_LIST,
/* ePragFlg: */ PragFlg_Result0,
/* ColNames: */ 27, 6,
/* ColNames: */ 15, 6,
/* iArg: */ 0 },
#endif
#endif
@ -139410,17 +139413,17 @@ static const PragmaName aPragmaName[] = {
{/* zName: */ "index_info",
/* ePragTyp: */ PragTyp_INDEX_INFO,
/* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result1|PragFlg_SchemaOpt,
/* ColNames: */ 21, 3,
/* ColNames: */ 27, 3,
/* iArg: */ 0 },
{/* zName: */ "index_list",
/* ePragTyp: */ PragTyp_INDEX_LIST,
/* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result1|PragFlg_SchemaOpt,
/* ColNames: */ 38, 5,
/* ColNames: */ 33, 5,
/* iArg: */ 0 },
{/* zName: */ "index_xinfo",
/* ePragTyp: */ PragTyp_INDEX_INFO,
/* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result1|PragFlg_SchemaOpt,
/* ColNames: */ 21, 6,
/* ColNames: */ 27, 6,
/* iArg: */ 1 },
#endif
#if !defined(SQLITE_OMIT_INTEGRITY_CHECK)
@ -139599,7 +139602,7 @@ static const PragmaName aPragmaName[] = {
{/* zName: */ "stats",
/* ePragTyp: */ PragTyp_STATS,
/* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result0|PragFlg_SchemaReq,
/* ColNames: */ 33, 5,
/* ColNames: */ 38, 5,
/* iArg: */ 0 },
#endif
#if !defined(SQLITE_OMIT_PAGER_PRAGMAS)
@ -139618,7 +139621,7 @@ static const PragmaName aPragmaName[] = {
{/* zName: */ "table_list",
/* ePragTyp: */ PragTyp_TABLE_LIST,
/* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result1,
/* ColNames: */ 15, 6,
/* ColNames: */ 21, 6,
/* iArg: */ 0 },
{/* zName: */ "table_xinfo",
/* ePragTyp: */ PragTyp_TABLE_INFO,
@ -139695,7 +139698,7 @@ static const PragmaName aPragmaName[] = {
{/* zName: */ "wal_checkpoint",
/* ePragTyp: */ PragTyp_WAL_CHECKPOINT,
/* ePragFlg: */ PragFlg_NeedSchema,
/* ColNames: */ 50, 3,
/* ColNames: */ 47, 3,
/* iArg: */ 0 },
#endif
#if !defined(SQLITE_OMIT_FLAG_PRAGMAS)
@ -147073,6 +147076,7 @@ static int multiSelect(
multi_select_end:
pDest->iSdst = dest.iSdst;
pDest->nSdst = dest.nSdst;
pDest->iSDParm2 = dest.iSDParm2;
if( pDelete ){
sqlite3ParserAddCleanup(pParse, sqlite3SelectDeleteGeneric, pDelete);
}
@ -151027,6 +151031,7 @@ static void agginfoFree(sqlite3 *db, void *pArg){
** * There is no WHERE or GROUP BY or HAVING clauses on the subqueries
** * The outer query is a simple count(*) with no WHERE clause or other
** extraneous syntax.
** * None of the subqueries are DISTINCT (forumpost/a860f5fb2e 2025-03-10)
**
** Return TRUE if the optimization is undertaken.
*/
@ -151059,7 +151064,11 @@ static int countOfViewOptimization(Parse *pParse, Select *p){
if( pSub->op!=TK_ALL && pSub->pPrior ) return 0; /* Must be UNION ALL */
if( pSub->pWhere ) return 0; /* No WHERE clause */
if( pSub->pLimit ) return 0; /* No LIMIT clause */
if( pSub->selFlags & SF_Aggregate ) return 0; /* Not an aggregate */
if( pSub->selFlags & (SF_Aggregate|SF_Distinct) ){
testcase( pSub->selFlags & SF_Aggregate );
testcase( pSub->selFlags & SF_Distinct );
return 0; /* Not an aggregate nor DISTINCT */
}
assert( pSub->pHaving==0 ); /* Due to the previous */
pSub = pSub->pPrior; /* Repeat over compound */
}while( pSub );
@ -166881,7 +166890,7 @@ static int whereLoopAddBtreeIndex(
if( (pNew->wsFlags & WHERE_TOP_LIMIT)==0
&& pNew->u.btree.nEq<pProbe->nColumn
&& (pNew->u.btree.nEq<pProbe->nKeyCol ||
pProbe->idxType!=SQLITE_IDXTYPE_PRIMARYKEY)
(pProbe->idxType!=SQLITE_IDXTYPE_PRIMARYKEY && !pProbe->bIdxRowid))
){
if( pNew->u.btree.nEq>3 ){
sqlite3ProgressCheck(pParse);
@ -182305,7 +182314,7 @@ SQLITE_API int sqlite3_config(int op, ...){
static int setupLookaside(sqlite3 *db, void *pBuf, int sz, int cnt){
#ifndef SQLITE_OMIT_LOOKASIDE
void *pStart;
sqlite3_int64 szAlloc = sz*(sqlite3_int64)cnt;
sqlite3_int64 szAlloc;
int nBig; /* Number of full-size slots */
int nSm; /* Number smaller LOOKASIDE_SMALL-byte slots */
@ -182324,7 +182333,9 @@ static int setupLookaside(sqlite3 *db, void *pBuf, int sz, int cnt){
*/
sz = ROUNDDOWN8(sz); /* IMP: R-33038-09382 */
if( sz<=(int)sizeof(LookasideSlot*) ) sz = 0;
if( sz>65528 ) sz = 65528;
if( cnt<0 ) cnt = 0;
szAlloc = (i64)sz*(i64)cnt;
if( sz==0 || cnt==0 ){
sz = 0;
pStart = 0;
@ -182339,10 +182350,10 @@ static int setupLookaside(sqlite3 *db, void *pBuf, int sz, int cnt){
#ifndef SQLITE_OMIT_TWOSIZE_LOOKASIDE
if( sz>=LOOKASIDE_SMALL*3 ){
nBig = szAlloc/(3*LOOKASIDE_SMALL+sz);
nSm = (szAlloc - sz*nBig)/LOOKASIDE_SMALL;
nSm = (szAlloc - (i64)sz*(i64)nBig)/LOOKASIDE_SMALL;
}else if( sz>=LOOKASIDE_SMALL*2 ){
nBig = szAlloc/(LOOKASIDE_SMALL+sz);
nSm = (szAlloc - sz*nBig)/LOOKASIDE_SMALL;
nSm = (szAlloc - (i64)sz*(i64)nBig)/LOOKASIDE_SMALL;
}else
#endif /* SQLITE_OMIT_TWOSIZE_LOOKASIDE */
if( sz>0 ){
@ -255872,7 +255883,7 @@ static void fts5SourceIdFunc(
){
assert( nArg==0 );
UNUSED_PARAM2(nArg, apUnused);
sqlite3_result_text(pCtx, "fts5: 2025-02-06 11:55:18 4a7dd425dc2a0e5082a9049c9b4a9d4f199a71583d014c24b4cfe276c5a77cde", -1, SQLITE_TRANSIENT);
sqlite3_result_text(pCtx, "fts5: 2025-05-07 10:39:52 17144570b0d96ae63cd6f3edca39e27ebd74925252bbaf6723bcb2f6b4861fb1", -1, SQLITE_TRANSIENT);
}
/*

View File

@ -146,9 +146,9 @@ extern "C" {
** [sqlite3_libversion_number()], [sqlite3_sourceid()],
** [sqlite_version()] and [sqlite_source_id()].
*/
#define SQLITE_VERSION "3.49.0"
#define SQLITE_VERSION_NUMBER 3049000
#define SQLITE_SOURCE_ID "2025-02-06 11:55:18 4a7dd425dc2a0e5082a9049c9b4a9d4f199a71583d014c24b4cfe276c5a77cde"
#define SQLITE_VERSION "3.49.2"
#define SQLITE_VERSION_NUMBER 3049002
#define SQLITE_SOURCE_ID "2025-05-07 10:39:52 17144570b0d96ae63cd6f3edca39e27ebd74925252bbaf6723bcb2f6b4861fb1"
/*
** CAPI3REF: Run-Time Library Version Numbers

View File

@ -4,12 +4,17 @@
- run the tests
- format + prettier
- update metadata/en-US/changelogs
- git tag
- git tag v1.2.3
- git tag -f latest_release
- push
- make dist
- make a release on gitea
- upload the artifacts
- upload the AppImage and zsyncmake
- upload to Google
- upload to Apple with dist-ios on macos
- nix
- june and december: update release version
- run `nix flake update`
- comment out the hash in default.nix
- update the version
- run `nix-build`

18
docs/upgrading.md Normal file
View File

@ -0,0 +1,18 @@
# Upgrading
Tilde Friends can be upgraded simply by running a new executable against an
existing database.
Tilde Friends writes all data to a `db.sqlite` file, either in
`~/.local/share/tildefriends/` or in the working directory where it is run,
depending on the platform and whether each one already exists. Run with
`tildefriends run -d DB_PATH` to specify the path to the database explicitly.
This file can be copied and moved across machines as needed like any [sqlite3
database](https://www.sqlite.org/onefile.html).
Schema changes and compatibility breaks have been rare, by design. In general,
upgrading is not expected to require any manual intervention and likely does
not involve any automatic migration, either. Downgrading is not well-supported
but will probably just work excepting rare changes that will be called out in
the changelog.

14
flake.lock generated
View File

@ -5,11 +5,11 @@
"systems": "systems"
},
"locked": {
"lastModified": 1710146030,
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
"lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github"
},
"original": {
@ -20,16 +20,16 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1717281328,
"narHash": "sha256-evZPzpf59oNcDUXxh2GHcxHkTEG4fjae2ytWP85jXRo=",
"lastModified": 1745279238,
"narHash": "sha256-AQ7M9wTa/Pa/kK5pcGTgX/DGqMHyzsyINfN7ktsI7Fo=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "b3b2b28c1daa04fe2ae47c21bb76fd226eac4ca1",
"rev": "9684b53175fc6c09581e94cc85f05ab77464c7e3",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-24.05",
"ref": "nixos-24.11",
"repo": "nixpkgs",
"type": "github"
}

View File

@ -2,7 +2,7 @@
description = "Tilde Friends is a platform for making, running, and sharing web applications.";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.05";
nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.11";
flake-utils.url = "github:numtide/flake-utils";
};

View File

@ -0,0 +1,15 @@
* Allow specifying all global settings from the command-line (CLI usage changed).
* Replication improvements.
* An iOS build is on TestFlight.
* macOS targets are debug and release like everywhere else.
* Running from a subdirectory is fine.
* Patchwork compatibility improvements.
* Invite fixes.
* Follow/block UI fixes.
* Mobile automatically logs in.
* Updates:
* CodeMirror
* OpenSSL 3.4.1
* libbacktrace
* speedscope 1.22.2
* sqlite 3.49.1

View File

@ -0,0 +1,11 @@
* The connections tab now shows replication progress.
* Replication performance and thoroughness improvements.
* Bind only to localhost on mobile, configurable.
* Request blobs referenced by referenced blobs, and improve performance of that
query.
* Fix file upload on iOS.
* Add rough back/forward/refresh buttons on iOS.
* Other crash fixes and performance improvements.
* Updates:
* CodeMirror
* w3.css

View File

@ -0,0 +1,12 @@
* Faster loads.
* Replication fixes.
* Shutdown fixes.
* Consolidated message actions into a % menu.
* Fixed and tested handling of user permissions.
* Add a very work in progress "web" app.
* Updates:
* CodeMirror
* Lit 3.3.0
* OpenSSL 3.5.0
* c-ares 1.34.5
* libbacktrace

View File

@ -0,0 +1,13 @@
* Faster loads.
* Minor UI tweaks.
* Added an intro app as part of the initial flow for first-time users.
* Fixed more shutdown issues.
* Fixed a longstanding potential database issue.
* Added a blob export command.
* Refresh blob wants for blobs that are requested over the web.
* Updates:
* CodeMirror
* QuickJS 2025-04-26
* libuv 1.51.0
* sqlite 3.49.2
* w3.css 5.02

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 92 KiB

After

Width:  |  Height:  |  Size: 101 KiB

View File

@ -1 +1 @@
A tool for making and sharing
A Secure Scuttlebutt decentralized social network client

6
package-lock.json generated
View File

@ -11,9 +11,9 @@
}
},
"node_modules/prettier": {
"version": "3.5.0",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.0.tgz",
"integrity": "sha512-quyMrVt6svPS7CjQ9gKb3GLEX/rl3BCL2oa/QkNcXv4YNVBC9olt3s+H7ukto06q7B1Qz46PbrKLO34PR6vXcA==",
"version": "3.5.3",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz",
"integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==",
"bin": {
"prettier": "bin/prettier.cjs"
},

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.unprompted.tildefriends"
android:versionCode="33"
android:versionName="0.0.28-wip">
android:versionCode="37"
android:versionName="0.0.31">
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.INTERNET"/>
<application

View File

@ -347,7 +347,6 @@ public class TildeFriendsActivity extends Activity {
} catch (NumberFormatException e) {
e.printStackTrace();
} catch (java.io.FileNotFoundException e) {
Log.w("tildefriends", "Port file does not yet exist.");
} catch (java.io.IOException e) {
e.printStackTrace();
}

View File

@ -1,55 +1,16 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="65dp"
android:height="65dp"
android:viewportWidth="61"
android:viewportHeight="65">
<path
android:pathData="M6,0h49a8,8 45,0 1,8 8v49a8,8 135,0 1,-8 8H6a8,8 45,0 1,-8 -8V8a8,8 135,0 1,8 -8Z"
android:strokeWidth=".712717"
android:fillColor="#0af"
android:fillAlpha="1"/>
<path
android:pathData="m1.6762,36.6891v-4.0039q2.0703,-2.3438 5.4297,-2.3438 1.1719,0 2.4609,0.3516 1.2891,0.332 3.6719,1.3477 1.3477,0.5664 2.0117,0.7422 0.6836,0.1758 1.3672,0.1758 1.2695,0 2.6172,-0.7617 1.3672,-0.7617 2.4219,-1.9141v4.1406q-1.25,1.1719 -2.5391,1.6992 -1.2695,0.5273 -2.8711,0.5273 -1.1719,0 -2.2461,-0.2734 -1.0547,-0.2734 -3.3789,-1.3086 -2.3047,-1.0352 -3.8477,-1.0352 -1.25,0 -2.3633,0.5469 -1.0938,0.5273 -2.7344,2.1094z"
android:fillColor="#000000"/>
<path
android:pathData="M42.4653,32.2273m-16.7723,0a16.7723,16.7723 0,1 1,33.5446 0a16.7723,16.7723 0,1 1,-33.5446 0"
android:fillColor="#fcea2b"/>
<path
android:pathData="M49.2697,34.097c2.8899,0 5.2344,-2.0871 5.2344,-4.6591 0,-1.2871 0.3267,-2.5742 -0.6213,-3.4164 -0.9473,-0.843 -3.1685,-1.2426 -4.6131,-1.2426 -1.7188,0 -3.7504,0.1043 -4.7043,1.2426 -0.6519,0.7766 -0.5302,2.3722 -0.5302,3.4164 0,2.572 2.343,4.6591 5.2344,4.6591zM34.9819,34.097c2.8899,0 5.2351,-2.0871 5.2351,-4.6591 0,-1.2871 0.326,-2.5742 -0.6213,-3.4164 -0.948,-0.843 -3.1685,-1.2426 -4.6138,-1.2426 -1.7181,0 -3.7497,0.1043 -4.7043,1.2426 -0.6512,0.7766 -0.5302,2.3722 -0.5302,3.4164 0,2.572 2.343,4.6591 5.2344,4.6591z"
android:fillColor="#3f3f3f"/>
<path
android:pathData="M42.3829,32.2681m-16.7723,0a16.7723,16.7723 0,1 1,33.5446 0a16.7723,16.7723 0,1 1,-33.5446 0"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#000"
android:strokeLineCap="round"/>
<path
android:pathData="M49.5403,38.6897c-4.794,2.5705 -10.242,2.6675 -14.3148,0M29.983,28.1903s-0.695,6.2349 5.0025,5.774c1.9106,-0.1546 5.7004,-0.474 5.7369,-6.0832 0.0036,-0.509 -0.0051,-1.1668 -0.5907,-1.9179 -0.7766,-0.9969 -2.6048,-1.4373 -7.2522,-1.037 0,0 -2.5129,-0.0729 -2.8965,3.264z"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#000"
android:strokeLineCap="round"/>
<path
android:pathData="m30.0341,27.8016 l-0.3158,-2.459 2.7951,-0.3843M54.6733,28.1903s0.695,6.2349 -5.0025,5.774c-1.9106,-0.1546 -5.7004,-0.474 -5.7376,-6.0832 -0.0029,-0.509 0.0058,-1.1668 0.5914,-1.9179 0.7766,-0.9969 2.6048,-1.4373 7.2522,-1.037 0,0 2.5129,-0.0729 2.8965,3.264z"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#000"
android:strokeLineCap="round"/>
<path
android:pathData="M39.1874,25.2383s3.0073,1.8479 6.3129,0M40.6685,28.813s1.6058,-2.7353 3.3078,0M54.6172,27.803l0.3158,-2.4582 -2.7951,-0.385"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#000"
android:strokeLineCap="round"/>
<path
android:pathData="M40.974,27.8716s1.309,-2.7346 2.6974,0"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#000"
android:strokeLineCap="round"/>
</vector>
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:gravity="center"
android:width="256dp"
android:height="256dp">
<shape>
<solid
android:color="#0af"/>
</shape>
</item>
<item android:drawable="@drawable/logo"
android:width="65dp"
android:height="65dp"
android:gravity="center"/>
</layer-list>

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:gravity="center"
android:width="256dp"
android:height="256dp">
<shape>
<solid
android:color="#0fa"/>
</shape>
</item>
<item android:drawable="@drawable/logo"
android:width="65dp"
android:height="65dp"
android:gravity="center"/>
</layer-list>

View File

@ -0,0 +1,51 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="65dp"
android:height="65dp"
android:viewportWidth="65"
android:viewportHeight="65">
<path
android:pathData="m1.6762,36.6891v-4.0039q2.0703,-2.3438 5.4297,-2.3438 1.1719,0 2.4609,0.3516 1.2891,0.332 3.6719,1.3477 1.3477,0.5664 2.0117,0.7422 0.6836,0.1758 1.3672,0.1758 1.2695,0 2.6172,-0.7617 1.3672,-0.7617 2.4219,-1.9141v4.1406q-1.25,1.1719 -2.5391,1.6992 -1.2695,0.5273 -2.8711,0.5273 -1.1719,0 -2.2461,-0.2734 -1.0547,-0.2734 -3.3789,-1.3086 -2.3047,-1.0352 -3.8477,-1.0352 -1.25,0 -2.3633,0.5469 -1.0938,0.5273 -2.7344,2.1094z"
android:fillColor="#000000"/>
<path
android:pathData="M42.4653,32.2273m-16.7723,0a16.7723,16.7723 0,1 1,33.5446 0a16.7723,16.7723 0,1 1,-33.5446 0"
android:fillColor="#fcea2b"/>
<path
android:pathData="M49.2697,34.097c2.8899,0 5.2344,-2.0871 5.2344,-4.6591 0,-1.2871 0.3267,-2.5742 -0.6213,-3.4164 -0.9473,-0.843 -3.1685,-1.2426 -4.6131,-1.2426 -1.7188,0 -3.7504,0.1043 -4.7043,1.2426 -0.6519,0.7766 -0.5302,2.3722 -0.5302,3.4164 0,2.572 2.343,4.6591 5.2344,4.6591zM34.9819,34.097c2.8899,0 5.2351,-2.0871 5.2351,-4.6591 0,-1.2871 0.326,-2.5742 -0.6213,-3.4164 -0.948,-0.843 -3.1685,-1.2426 -4.6138,-1.2426 -1.7181,0 -3.7497,0.1043 -4.7043,1.2426 -0.6512,0.7766 -0.5302,2.3722 -0.5302,3.4164 0,2.572 2.343,4.6591 5.2344,4.6591z"
android:fillColor="#3f3f3f"/>
<path
android:pathData="M42.3829,32.2681m-16.7723,0a16.7723,16.7723 0,1 1,33.5446 0a16.7723,16.7723 0,1 1,-33.5446 0"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#000"
android:strokeLineCap="round"/>
<path
android:pathData="M49.5403,38.6897c-4.794,2.5705 -10.242,2.6675 -14.3148,0M29.983,28.1903s-0.695,6.2349 5.0025,5.774c1.9106,-0.1546 5.7004,-0.474 5.7369,-6.0832 0.0036,-0.509 -0.0051,-1.1668 -0.5907,-1.9179 -0.7766,-0.9969 -2.6048,-1.4373 -7.2522,-1.037 0,0 -2.5129,-0.0729 -2.8965,3.264z"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#000"
android:strokeLineCap="round"/>
<path
android:pathData="m30.0341,27.8016 l-0.3158,-2.459 2.7951,-0.3843M54.6733,28.1903s0.695,6.2349 -5.0025,5.774c-1.9106,-0.1546 -5.7004,-0.474 -5.7376,-6.0832 -0.0029,-0.509 0.0058,-1.1668 0.5914,-1.9179 0.7766,-0.9969 2.6048,-1.4373 7.2522,-1.037 0,0 2.5129,-0.0729 2.8965,3.264z"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#000"
android:strokeLineCap="round"/>
<path
android:pathData="M39.1874,25.2383s3.0073,1.8479 6.3129,0M40.6685,28.813s1.6058,-2.7353 3.3078,0M54.6172,27.803l0.3158,-2.4582 -2.7951,-0.385"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#000"
android:strokeLineCap="round"/>
<path
android:pathData="M40.974,27.8716s1.309,-2.7346 2.6974,0"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#000"
android:strokeLineCap="round"/>
</vector>

19
src/api.js.c Normal file
View File

@ -0,0 +1,19 @@
#include "api.js.h"
#include "log.h"
#include <quickjs.h>
static JSValue _tf_api_register_imports(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
{
return JS_UNDEFINED;
}
void tf_api_register(JSContext* context)
{
JSValue global = JS_GetGlobalObject(context);
JSValue ssb = JS_GetPropertyStr(context, global, "ssb");
JS_SetPropertyStr(context, ssb, "registerImports", JS_NewCFunction(context, _tf_api_register_imports, "registerImports", 2));
JS_FreeValue(context, ssb);
JS_FreeValue(context, global);
}

18
src/api.js.h Normal file
View File

@ -0,0 +1,18 @@
#pragma once
/**
** \defgroup api_js JS API
** Functions that are ultimately exposed to apps.
** @{
*/
/** A JS context. */
typedef struct JSContext JSContext;
/**
** Register JS API functions.
** @param context The JS context.
*/
void tf_api_register(JSContext* context);
/** @} */

View File

@ -107,7 +107,7 @@ static void _database_get_work(tf_ssb_t* ssb, void* user_data)
database_get_t* work = user_data;
sqlite3_stmt* statement;
sqlite3* db = tf_ssb_acquire_db_reader(ssb);
if (sqlite3_prepare(db, "SELECT value FROM properties WHERE id = ? AND key = ?", -1, &statement, NULL) == SQLITE_OK)
if (sqlite3_prepare_v2(db, "SELECT value FROM properties WHERE id = ? AND key = ?", -1, &statement, NULL) == SQLITE_OK)
{
if (sqlite3_bind_text(statement, 1, work->id, -1, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 2, work->key, work->key_length, NULL) == SQLITE_OK &&
sqlite3_step(statement) == SQLITE_ROW)
@ -185,7 +185,7 @@ static void _database_set_work(tf_ssb_t* ssb, void* user_data)
database_set_t* work = user_data;
sqlite3* db = tf_ssb_acquire_db_writer(ssb);
sqlite3_stmt* statement;
if (sqlite3_prepare(db, "INSERT OR REPLACE INTO properties (id, key, value) VALUES (?1, ?2, ?3)", -1, &statement, NULL) == SQLITE_OK)
if (sqlite3_prepare_v2(db, "INSERT OR REPLACE INTO properties (id, key, value) VALUES (?1, ?2, ?3)", -1, &statement, NULL) == SQLITE_OK)
{
if (sqlite3_bind_text(statement, 1, work->id, -1, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 2, work->key, work->key_length, NULL) == SQLITE_OK &&
sqlite3_bind_text(statement, 3, work->value, work->value_length, NULL) == SQLITE_OK && sqlite3_step(statement) == SQLITE_DONE)
@ -265,7 +265,7 @@ static void _database_exchange_work(tf_ssb_t* ssb, void* user_data)
sqlite3_stmt* statement;
if (!work->expected)
{
if (sqlite3_prepare(db, "INSERT INTO properties (id, key, value) VALUES (?1, ?2, ?3) ON CONFLICT DO NOTHING", -1, &statement, NULL) == SQLITE_OK)
if (sqlite3_prepare_v2(db, "INSERT INTO properties (id, key, value) VALUES (?1, ?2, ?3) ON CONFLICT DO NOTHING", -1, &statement, NULL) == SQLITE_OK)
{
if (sqlite3_bind_text(statement, 1, work->id, -1, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 2, work->key, work->key_length, NULL) == SQLITE_OK &&
sqlite3_bind_text(statement, 3, work->value, work->value_length, NULL) == SQLITE_OK && sqlite3_step(statement) == SQLITE_DONE)
@ -275,7 +275,7 @@ static void _database_exchange_work(tf_ssb_t* ssb, void* user_data)
sqlite3_finalize(statement);
}
}
else if (sqlite3_prepare(db, "UPDATE properties SET value = ?1 WHERE id = ?2 AND key = ?3 AND value = ?4", -1, &statement, NULL) == SQLITE_OK)
else if (sqlite3_prepare_v2(db, "UPDATE properties SET value = ?1 WHERE id = ?2 AND key = ?3 AND value = ?4", -1, &statement, NULL) == SQLITE_OK)
{
if (sqlite3_bind_text(statement, 1, work->value, work->value_length, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 2, work->id, -1, NULL) == SQLITE_OK &&
sqlite3_bind_text(statement, 3, work->key, work->key_length, NULL) == SQLITE_OK &&
@ -339,7 +339,7 @@ static void _database_remove_work(tf_ssb_t* ssb, void* user_data)
database_remove_t* work = user_data;
sqlite3_stmt* statement;
sqlite3* db = tf_ssb_acquire_db_writer(ssb);
if (sqlite3_prepare(db, "DELETE FROM properties WHERE id = ?1 AND key = ?2", -1, &statement, NULL) == SQLITE_OK)
if (sqlite3_prepare_v2(db, "DELETE FROM properties WHERE id = ?1 AND key = ?2", -1, &statement, NULL) == SQLITE_OK)
{
if (sqlite3_bind_text(statement, 1, work->id, -1, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 2, work->key, work->key_length, NULL) == SQLITE_OK &&
sqlite3_step(statement) == SQLITE_OK)
@ -401,7 +401,7 @@ static void _database_get_all_work(tf_ssb_t* ssb, void* user_data)
database_get_all_t* work = user_data;
sqlite3_stmt* statement;
sqlite3* db = tf_ssb_acquire_db_reader(ssb);
if (sqlite3_prepare(db, "SELECT key FROM properties WHERE id = ?", -1, &statement, NULL) == SQLITE_OK)
if (sqlite3_prepare_v2(db, "SELECT key FROM properties WHERE id = ?", -1, &statement, NULL) == SQLITE_OK)
{
if (sqlite3_bind_text(statement, 1, work->id, -1, NULL) == SQLITE_OK)
{
@ -487,7 +487,7 @@ static void _database_get_like_work(tf_ssb_t* ssb, void* user_data)
database_get_like_t* work = user_data;
sqlite3_stmt* statement;
sqlite3* db = tf_ssb_acquire_db_reader(ssb);
if (sqlite3_prepare(db, "SELECT key, value FROM properties WHERE id = ? AND KEY LIKE ?", -1, &statement, NULL) == SQLITE_OK)
if (sqlite3_prepare_v2(db, "SELECT key, value FROM properties WHERE id = ? AND KEY LIKE ?", -1, &statement, NULL) == SQLITE_OK)
{
if (sqlite3_bind_text(statement, 1, work->id, -1, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 2, work->pattern, -1, NULL) == SQLITE_OK)
{
@ -566,7 +566,7 @@ static void _databases_list_work(tf_ssb_t* ssb, void* user_data)
databases_list_t* work = user_data;
sqlite3* db = tf_ssb_acquire_db_reader(ssb);
sqlite3_stmt* statement;
if (sqlite3_prepare(db, "SELECT DISTINCT id FROM properties WHERE id LIKE ?", -1, &statement, NULL) == SQLITE_OK)
if (sqlite3_prepare_v2(db, "SELECT DISTINCT id FROM properties WHERE id LIKE ?", -1, &statement, NULL) == SQLITE_OK)
{
if (sqlite3_bind_text(statement, 1, work->pattern, -1, NULL) == SQLITE_OK)
{

View File

@ -84,6 +84,7 @@ typedef struct _tf_http_listener_t
typedef struct _tf_http_t
{
bool is_shutting_down;
bool is_in_destroy;
tf_http_listener_t** listeners;
int listeners_count;
@ -395,7 +396,7 @@ static void _http_add_body_bytes(tf_http_connection_t* connection, const void* d
if (fin)
{
if (connection->request->on_message)
if (connection->request && connection->request->on_message)
{
tf_trace_begin(connection->http->trace, connection->trace_name ? connection->trace_name : "websocket");
connection->request->on_message(connection->request, connection->fragment_length ? connection->fragment_op_code : op_code,
@ -697,7 +698,7 @@ static void _http_on_connection(uv_stream_t* stream, int status)
http->connections[http->connections_count++] = connection;
}
int tf_http_listen(tf_http_t* http, int port, tf_tls_context_t* tls, tf_http_cleanup_t* cleanup, void* user_data)
int tf_http_listen(tf_http_t* http, int port, bool local_only, tf_tls_context_t* tls, tf_http_cleanup_t* cleanup, void* user_data)
{
tf_http_listener_t* listener = tf_malloc(sizeof(tf_http_listener_t));
*listener = (tf_http_listener_t) {
@ -715,25 +716,29 @@ int tf_http_listen(tf_http_t* http, int port, tf_tls_context_t* tls, tf_http_cle
if (r == 0)
{
bool use_ipv6 = !tf_util_is_mobile();
#if defined(__HAIKU__)
/*
** Binding to IPv6 here fails with an odd error, and the socket
** becomes unusable. Since we probably want localhost only
** on this single-user OS, let's just assume IPv4.
*/
struct sockaddr_in addr = {
use_ipv6 = false;
#endif
struct sockaddr_in addr4 = {
.sin_family = AF_INET,
.sin_addr = { .s_addr = INADDR_ANY },
.sin_addr = { .s_addr = local_only ? htonl(INADDR_LOOPBACK) : htonl(INADDR_ANY) },
.sin_port = ntohs(port),
};
#else
struct sockaddr_in6 addr = {
struct sockaddr_in6 addr6 = {
.sin6_family = AF_INET6,
.sin6_addr = IN6ADDR_ANY_INIT,
.sin6_addr = local_only ? (struct in6_addr)IN6ADDR_LOOPBACK_INIT : (struct in6_addr)IN6ADDR_ANY_INIT,
.sin6_port = ntohs(port),
};
#endif
r = uv_tcp_bind(&listener->tcp, (struct sockaddr*)&addr, 0);
struct sockaddr* addr = use_ipv6 ? (struct sockaddr*)&addr6 : (struct sockaddr*)&addr4;
r = uv_tcp_bind(&listener->tcp, addr, 0);
if (r)
{
tf_printf("%s:%d: uv_tcp_bind: %s\n", __FILE__, __LINE__, uv_strerror(r));
@ -780,13 +785,36 @@ void tf_http_add_handler(tf_http_t* http, const char* pattern, tf_http_callback_
static void _http_free_listener_on_close(uv_handle_t* handle)
{
tf_http_listener_t* listener = handle->data;
tf_http_t* http = listener->http;
for (int i = 0; i < http->listeners_count; i++)
{
if (http->listeners[i] == listener)
{
http->listeners[i] = http->listeners[http->listeners_count - 1];
http->listeners_count--;
break;
}
}
handle->data = NULL;
tf_free(listener);
if (!http->listeners_count)
{
tf_free(http->listeners);
http->listeners = NULL;
tf_http_destroy(http);
}
}
void tf_http_destroy(tf_http_t* http)
{
if (http->is_in_destroy)
{
return;
}
http->is_shutting_down = true;
http->is_in_destroy = true;
for (int i = 0; i < http->connections_count; i++)
{
@ -801,6 +829,10 @@ void tf_http_destroy(tf_http_t* http)
listener->cleanup(listener->user_data);
listener->cleanup = NULL;
}
if (listener->tcp.data && !uv_is_closing((uv_handle_t*)&listener->tcp))
{
uv_close((uv_handle_t*)&listener->tcp, _http_free_listener_on_close);
}
}
for (int i = 0; i < http->handlers_count; i++)
@ -818,20 +850,11 @@ void tf_http_destroy(tf_http_t* http)
http->user_data = NULL;
}
if (http->connections_count == 0)
if (http->connections_count == 0 && http->listeners_count == 0)
{
tf_free(http->connections);
http->connections = NULL;
for (int i = 0; i < http->listeners_count; i++)
{
tf_http_listener_t* listener = http->listeners[i];
uv_close((uv_handle_t*)&listener->tcp, _http_free_listener_on_close);
}
tf_free(http->listeners);
http->listeners = NULL;
http->listeners_count = 0;
for (int i = 0; i < http->handlers_count; i++)
{
if (http->handlers[i].pattern)
@ -845,6 +868,10 @@ void tf_http_destroy(tf_http_t* http)
tf_free(http);
}
else
{
http->is_in_destroy = false;
}
}
const char* tf_http_status_text(int status)
@ -963,9 +990,42 @@ static void _http_write(tf_http_connection_t* connection, const void* data, size
}
}
void tf_http_request_send(tf_http_request_t* request, const void* data, size_t size)
void tf_http_request_websocket_send(tf_http_request_t* request, int op_code, const void* data, size_t size)
{
_http_write(request->connection, data, size);
uint8_t* copy = tf_malloc(size + 16);
bool fin = true;
size_t header = 1;
copy[0] = (fin ? (1 << 7) : 0) | (op_code & 0xf);
if (size < 126)
{
copy[1] = size;
header += 1;
}
else if (size < (1 << 16))
{
copy[1] = 126;
copy[2] = (size >> 8) & 0xff;
copy[3] = (size >> 0) & 0xff;
header += 3;
}
else
{
uint32_t high = ((uint64_t)size >> 32) & 0xffffffff;
uint32_t low = (size >> 0) & 0xffffffff;
copy[1] = 127;
copy[2] = (high >> 24) & 0xff;
copy[3] = (high >> 16) & 0xff;
copy[4] = (high >> 8) & 0xff;
copy[5] = (high >> 0) & 0xff;
copy[6] = (low >> 24) & 0xff;
copy[7] = (low >> 16) & 0xff;
copy[8] = (low >> 8) & 0xff;
copy[9] = (low >> 0) & 0xff;
header += 9;
}
memcpy(copy + header, data, size);
_http_write(request->connection, copy, header + size);
tf_free(copy);
}
void tf_http_respond(tf_http_request_t* request, int status, const char** headers, int headers_count, const void* body, size_t content_length)

View File

@ -116,12 +116,13 @@ void tf_http_set_trace(tf_http_t* http, tf_trace_t* trace);
** times to listen on multiple ports.
** @param http The HTTP instance.
** @param port The port on which to listen, or 0 to assign a free port.
** @param local_only Only access connections on localhost, otherwise any address.
** @param tls An optional TLS context to use for HTTPS requests.
** @param cleanup A function called when the HTTP instance is being cleaned up.
** @param user_data User data passed to the cleanup callback.
** @return The port number on which the HTTP instance is now listening.
*/
int tf_http_listen(tf_http_t* http, int port, tf_tls_context_t* tls, tf_http_cleanup_t* cleanup, void* user_data);
int tf_http_listen(tf_http_t* http, int port, bool local_only, tf_tls_context_t* tls, tf_http_cleanup_t* cleanup, void* user_data);
/**
** Add an HTTP request handler.
@ -209,10 +210,11 @@ const char* tf_http_get_cookie(const char* cookie_header, const char* name);
** Send a websocket message.
** @param request The HTTP request which was previously updated to a websocket
** session with tf_http_request_websocket_upgrade().
** @param op_code Websocket op code.
** @param data The message data.
** @param size The size of data.
*/
void tf_http_request_send(tf_http_request_t* request, const void* data, size_t size);
void tf_http_request_websocket_send(tf_http_request_t* request, int op_code, const void* data, size_t size);
/**
** Upgrade an HTTP request to a websocket session.

View File

@ -5,6 +5,7 @@
#include "log.h"
#include "mem.h"
#include "ssb.db.h"
#include "ssb.ebt.h"
#include "ssb.h"
#include "task.h"
#include "tls.h"
@ -46,7 +47,6 @@ static const char* _make_set_session_cookie_header(tf_http_request_t* request, c
const char** _form_data_decode(const char* data, int length);
const char* _form_data_get(const char** form_data, const char* key);
static JSClassID _httpd_class_id;
static JSClassID _httpd_request_class_id;
typedef struct _http_user_data_t
@ -152,44 +152,9 @@ static JSValue _httpd_response_send(JSContext* context, JSValueConst this_val, i
tf_http_request_t* request = JS_GetOpaque(this_val, _httpd_request_class_id);
int opcode = 0x1;
JS_ToInt32(context, &opcode, argv[1]);
uint64_t length = 0;
size_t length_size = 0;
const char* message = JS_ToCStringLen(context, &length_size, argv[0]);
length = length_size;
uint8_t* copy = tf_malloc(length + 16);
bool fin = true;
size_t header = 1;
copy[0] = (fin ? (1 << 7) : 0) | (opcode & 0xf);
if (length < 126)
{
copy[1] = length;
header += 1;
}
else if (length < (1 << 16))
{
copy[1] = 126;
copy[2] = (length >> 8) & 0xff;
copy[3] = (length >> 0) & 0xff;
header += 3;
}
else
{
uint32_t high = (length >> 32) & 0xffffffff;
uint32_t low = (length >> 0) & 0xffffffff;
copy[1] = 127;
copy[2] = (high >> 24) & 0xff;
copy[3] = (high >> 16) & 0xff;
copy[4] = (high >> 8) & 0xff;
copy[5] = (high >> 0) & 0xff;
copy[6] = (low >> 24) & 0xff;
copy[7] = (low >> 16) & 0xff;
copy[8] = (low >> 8) & 0xff;
copy[9] = (low >> 0) & 0xff;
header += 9;
}
memcpy(copy + header, message, length);
tf_http_request_send(request, copy, header + length);
tf_free(copy);
size_t length = 0;
const char* message = JS_ToCStringLen(context, &length, argv[0]);
tf_http_request_websocket_send(request, opcode, message, length);
JS_FreeCString(context, message);
return JS_UNDEFINED;
}
@ -572,12 +537,6 @@ static const char* _ext_to_content_type(const char* ext, bool use_fallback)
return use_fallback ? "application/binary" : NULL;
}
static void _httpd_finalizer(JSRuntime* runtime, JSValue value)
{
tf_http_t* http = JS_GetOpaque(value, _httpd_class_id);
tf_http_destroy(http);
}
static void _httpd_request_finalizer(JSRuntime* runtime, JSValue value)
{
tf_http_request_t* request = JS_GetOpaque(value, _httpd_request_class_id);
@ -604,6 +563,50 @@ static void _httpd_endpoint_trace(tf_http_request_t* request)
tf_free(json);
}
static void _httpd_endpoint_ebt(tf_http_request_t* request)
{
if (_httpd_redirect(request))
{
return;
}
tf_task_t* task = request->user_data;
tf_ssb_t* ssb = tf_task_get_ssb(task);
JSContext* context = tf_ssb_get_context(ssb);
JSValue object = JS_NewObject(context);
tf_ssb_connection_t* connections[256];
int connection_count = tf_ssb_get_connections(ssb, connections, tf_countof(connections));
for (int i = 0; i < connection_count; i++)
{
char id[k_id_base64_len];
tf_ssb_connection_get_id(connections[i], id, sizeof(id));
char key[256];
JSValue clock = JS_NewObject(context);
snprintf(key, sizeof(key), "%d:%s", i, id);
tf_ssb_ebt_t* ebt = tf_ssb_connection_get_ebt(connections[i]);
tf_ssb_ebt_debug_clock(ebt, context, clock);
JS_SetPropertyStr(context, object, key, clock);
}
JSValue json_value = JS_JSONStringify(context, object, JS_NULL, JS_NewInt32(context, 2));
const char* json = JS_ToCString(context, json_value);
JS_FreeValue(context, json_value);
const char* headers[] = {
"Content-Type",
"application/json; charset=utf-8",
"Access-Control-Allow-Origin",
"*",
};
tf_http_respond(request, 200, headers, tf_countof(headers) / 2, json, json ? strlen(json) : 0);
JS_FreeCString(context, json);
JS_FreeValue(context, object);
}
static void _httpd_endpoint_mem(tf_http_request_t* request)
{
if (_httpd_redirect(request))
@ -1113,6 +1116,7 @@ typedef struct _view_t
void* data;
size_t size;
char etag[256];
char notify_want_blob_id[k_blob_id_len];
bool not_modified;
} view_t;
@ -1167,7 +1171,13 @@ static void _httpd_endpoint_view_work(tf_ssb_t* ssb, void* user_data)
}
else
{
tf_ssb_db_blob_get(ssb, blob_id, (uint8_t**)&view->data, &view->size);
if (!tf_ssb_db_blob_get(ssb, blob_id, (uint8_t**)&view->data, &view->size))
{
sqlite3* db = tf_ssb_acquire_db_writer(ssb);
tf_ssb_db_add_blob_wants(db, blob_id);
tf_ssb_release_db_writer(ssb, db);
snprintf(view->notify_want_blob_id, sizeof(view->notify_want_blob_id), "%s", blob_id);
}
}
}
}
@ -1210,6 +1220,12 @@ static void _httpd_endpoint_view_after_work(tf_ssb_t* ssb, int status, void* use
const char* k_payload = tf_http_status_text(404);
tf_http_respond(view->request, 404, NULL, 0, k_payload, strlen(k_payload));
}
if (*view->notify_want_blob_id)
{
tf_ssb_notify_blob_want_added(ssb, view->notify_want_blob_id);
}
tf_free(view->form_data);
tf_http_request_unref(view->request);
tf_free(view);
@ -1314,6 +1330,11 @@ static void _httpd_endpoint_save_work(tf_ssb_t* ssb, void* user_data)
snprintf(save->blob_id, sizeof(save->blob_id), "%s", blob_id);
save->response = 200;
}
else
{
tf_printf("Blob store or property set failed.\n");
save->response = 500;
}
JS_FreeCString(context, new_app_str);
JS_FreeValue(context, new_app_json);
@ -1330,7 +1351,7 @@ static void _httpd_endpoint_save_work(tf_ssb_t* ssb, void* user_data)
}
else
{
save->response = 401;
save->response = 403;
}
tf_free(user_app);
}
@ -1342,12 +1363,21 @@ static void _httpd_endpoint_save_work(tf_ssb_t* ssb, void* user_data)
snprintf(save->blob_id, sizeof(save->blob_id), "%s", blob_id);
save->response = 200;
}
else
{
tf_printf("Blob store failed.\n");
save->response = 500;
}
}
else
{
save->response = 400;
}
}
else
{
save->response = 401;
}
tf_free((void*)session);
JS_FreeCString(context, user_string);
@ -1469,17 +1499,9 @@ static void _httpd_endpoint_delete(tf_http_request_t* request)
static void _httpd_endpoint_root_callback(const char* path, void* user_data)
{
tf_http_request_t* request = user_data;
const char* host = tf_http_request_get_header(request, "x-forwarded-host");
if (!host)
{
host = tf_http_request_get_header(request, "host");
}
char url[1024];
snprintf(url, sizeof(url), "%s%s%s", request->is_tls ? "https://" : "http://", host, path ? path : "/~core/apps/");
const char* headers[] = {
"Location",
url,
path ? path : "/~core/apps/",
};
tf_http_respond(request, 303, headers, tf_countof(headers) / 2, NULL, 0);
tf_http_request_unref(request);
@ -2166,56 +2188,48 @@ static void _httpd_endpoint_logout(tf_http_request_t* request)
tf_http_respond(request, 303, headers, tf_countof(headers) / 2, NULL, 0);
}
static bool _task_arg_is_mobile(tf_task_t* task)
{
bool result = false;
JSContext* context = tf_task_get_context(task);
JSValue global = JS_GetGlobalObject(context);
JSValue tildefriends = JS_GetPropertyStr(context, global, "tildefriends");
JSValue args = JS_GetPropertyStr(context, tildefriends, "args");
JSValue mobile = JS_GetPropertyStr(context, args, "mobile");
result = JS_ToBool(context, mobile) != 0;
JS_FreeValue(context, mobile);
JS_FreeValue(context, args);
JS_FreeValue(context, tildefriends);
JS_FreeValue(context, global);
return result;
}
typedef struct _auto_login_t
{
tf_http_request_t* request;
bool autologin;
const char* users;
} auto_login_t;
static void _httpd_auto_login_work(tf_ssb_t* ssb, void* user_data)
{
auto_login_t* request = user_data;
request->users = tf_ssb_db_get_property(ssb, "auth", "users");
if (request->users && strcmp(request->users, "[]") == 0)
{
tf_free((void*)request->users);
request->users = NULL;
}
sqlite3* db = tf_ssb_acquire_db_reader(ssb);
tf_ssb_db_get_global_setting_bool(db, "autologin", &request->autologin);
tf_ssb_release_db_reader(ssb, db);
if (!request->users)
if (request->autologin)
{
JSMallocFunctions funcs = { 0 };
tf_get_js_malloc_functions(&funcs);
JSRuntime* runtime = JS_NewRuntime2(&funcs, NULL);
JSContext* context = JS_NewContext(runtime);
static const char* k_account_name = "mobile";
sqlite3* db = tf_ssb_acquire_db_writer(ssb);
bool registered = tf_ssb_db_register_account(tf_ssb_get_loop(ssb), db, context, k_account_name, k_account_name);
tf_ssb_release_db_writer(ssb, db);
if (registered)
{
_make_administrator_if_first(ssb, context, k_account_name, true);
}
JS_FreeContext(context);
JS_FreeRuntime(runtime);
request->users = tf_ssb_db_get_property(ssb, "auth", "users");
if (request->users && strcmp(request->users, "[]") == 0)
{
tf_free((void*)request->users);
request->users = NULL;
}
if (!request->users)
{
JSMallocFunctions funcs = { 0 };
tf_get_js_malloc_functions(&funcs);
JSRuntime* runtime = JS_NewRuntime2(&funcs, NULL);
JSContext* context = JS_NewContext(runtime);
static const char* k_account_name = "mobile";
sqlite3* db = tf_ssb_acquire_db_writer(ssb);
bool registered = tf_ssb_db_register_account(tf_ssb_get_loop(ssb), db, context, k_account_name, k_account_name);
tf_ssb_release_db_writer(ssb, db);
if (registered)
{
_make_administrator_if_first(ssb, context, k_account_name, true);
}
JS_FreeContext(context);
JS_FreeRuntime(runtime);
request->users = tf_ssb_db_get_property(ssb, "auth", "users");
}
}
}
@ -2224,36 +2238,44 @@ static void _httpd_auto_login_after_work(tf_ssb_t* ssb, int status, void* user_d
auto_login_t* work = user_data;
JSContext* context = tf_ssb_get_context(ssb);
const char* session_token = NULL;
if (work->users)
if (!work->autologin)
{
JSValue json = JS_ParseJSON(context, work->users, strlen(work->users), NULL);
JSValue user = JS_GetPropertyUint32(context, json, 0);
const char* user_string = JS_ToCString(context, user);
session_token = _make_session_jwt(context, ssb, user_string);
JS_FreeCString(context, user_string);
JS_FreeValue(context, user);
JS_FreeValue(context, json);
}
if (session_token)
{
const char* cookie = _make_set_session_cookie_header(work->request, session_token);
tf_free((void*)session_token);
const char* headers[] = {
"Set-Cookie",
cookie,
"Location",
"/",
};
tf_http_respond(work->request, 303, headers, tf_countof(headers) / 2, NULL, 0);
tf_free((void*)cookie);
const char* k_payload = tf_http_status_text(404);
tf_http_respond(work->request, 404, NULL, 0, k_payload, strlen(k_payload));
}
else
{
const char* headers[] = {
"Location",
"/",
};
tf_http_respond(work->request, 303, headers, tf_countof(headers) / 2, NULL, 0);
if (work->users)
{
JSValue json = JS_ParseJSON(context, work->users, strlen(work->users), NULL);
JSValue user = JS_GetPropertyUint32(context, json, 0);
const char* user_string = JS_ToCString(context, user);
session_token = _make_session_jwt(context, ssb, user_string);
JS_FreeCString(context, user_string);
JS_FreeValue(context, user);
JS_FreeValue(context, json);
}
if (session_token)
{
const char* cookie = _make_set_session_cookie_header(work->request, session_token);
tf_free((void*)session_token);
const char* headers[] = {
"Set-Cookie",
cookie,
"Location",
"/",
};
tf_http_respond(work->request, 303, headers, tf_countof(headers) / 2, NULL, 0);
tf_free((void*)cookie);
}
else
{
const char* headers[] = {
"Location",
"/",
};
tf_http_respond(work->request, 303, headers, tf_countof(headers) / 2, NULL, 0);
}
}
tf_http_request_unref(work->request);
tf_free((void*)work->users);
@ -2263,20 +2285,12 @@ static void _httpd_auto_login_after_work(tf_ssb_t* ssb, int status, void* user_d
static void _httpd_endpoint_login_auto(tf_http_request_t* request)
{
tf_task_t* task = request->user_data;
if (tf_util_is_mobile() || _task_arg_is_mobile(task))
{
tf_http_request_ref(request);
tf_ssb_t* ssb = tf_task_get_ssb(task);
tf_http_request_ref(request);
tf_ssb_t* ssb = tf_task_get_ssb(task);
auto_login_t* work = tf_malloc(sizeof(auto_login_t));
*work = (auto_login_t) { .request = request };
tf_ssb_run_work(ssb, _httpd_auto_login_work, _httpd_auto_login_after_work, work);
}
else
{
const char* k_payload = tf_http_status_text(404);
tf_http_respond(request, 404, NULL, 0, k_payload, strlen(k_payload));
}
auto_login_t* work = tf_malloc(sizeof(auto_login_t));
*work = (auto_login_t) { .request = request };
tf_ssb_run_work(ssb, _httpd_auto_login_work, _httpd_auto_login_after_work, work);
}
static void _httpd_endpoint_app_socket(tf_http_request_t* request)
@ -2319,35 +2333,6 @@ static void _httpd_endpoint_app_socket(tf_http_request_t* request)
JS_FreeValue(context, global);
}
static int _tf_httpd_get_tildefriends_int(JSContext* context, const char* arg)
{
JSValue global = JS_GetGlobalObject(context);
JSValue tildefriends = JS_GetPropertyStr(context, global, "tildefriends");
JSValue arg_value = JS_GetPropertyStr(context, tildefriends, arg);
int value = 0;
JS_ToInt32(context, &value, arg_value);
JS_FreeValue(context, arg_value);
JS_FreeValue(context, tildefriends);
JS_FreeValue(context, global);
return value;
}
static const char* _tf_httpd_get_tildefriends_arg_string(JSContext* context, const char* arg)
{
JSValue global = JS_GetGlobalObject(context);
JSValue tildefriends = JS_GetPropertyStr(context, global, "tildefriends");
JSValue args = JS_GetPropertyStr(context, tildefriends, "args");
JSValue arg_value = JS_GetPropertyStr(context, args, arg);
const char* value = !JS_IsUndefined(arg_value) ? JS_ToCString(context, arg_value) : NULL;
const char* result = value ? tf_strdup(value) : NULL;
JS_FreeCString(context, value);
JS_FreeValue(context, arg_value);
JS_FreeValue(context, args);
JS_FreeValue(context, tildefriends);
JS_FreeValue(context, global);
return result;
}
static void _httpd_free_user_data(void* user_data)
{
tf_free(user_data);
@ -2375,16 +2360,7 @@ static const char* _httpd_read_file(tf_task_t* task, const char* path)
void tf_httpd_register(JSContext* context)
{
JS_NewClassID(&_httpd_class_id);
JS_NewClassID(&_httpd_request_class_id);
JSClassDef httpd_def = {
.class_name = "Httpd",
.finalizer = &_httpd_finalizer,
};
if (JS_NewClass(JS_GetRuntime(context), _httpd_class_id, &httpd_def) != 0)
{
fprintf(stderr, "Failed to register Httpd.\n");
}
JSClassDef request_def = {
.class_name = "Request",
.finalizer = &_httpd_request_finalizer,
@ -2394,19 +2370,31 @@ void tf_httpd_register(JSContext* context)
fprintf(stderr, "Failed to register Request.\n");
}
int http_port = _tf_httpd_get_tildefriends_int(context, "http_port");
int https_port = _tf_httpd_get_tildefriends_int(context, "https_port");
const char* out_http_port_file = _tf_httpd_get_tildefriends_arg_string(context, "out_http_port_file");
JSValue global = JS_GetGlobalObject(context);
JSValue httpd = JS_NewObjectClass(context, _httpd_class_id);
JSValue httpd = JS_NewObject(context);
JS_SetPropertyStr(context, httpd, "auth_query", JS_NewCFunction(context, _httpd_auth_query, "auth_query", 1));
JS_SetPropertyStr(context, global, "httpd", httpd);
JS_FreeValue(context, global);
}
tf_http_t* tf_httpd_create(JSContext* context)
{
tf_task_t* task = tf_task_get(context);
tf_ssb_t* ssb = tf_task_get_ssb(task);
uv_loop_t* loop = tf_task_get_loop(task);
tf_http_t* http = tf_http_create(loop);
tf_http_set_trace(http, tf_task_get_trace(task));
JS_SetOpaque(httpd, http);
int64_t http_port = 0;
int64_t https_port = 0;
char out_http_port_file[512] = "";
bool local_only = false;
sqlite3* db = tf_ssb_acquire_db_reader(ssb);
tf_ssb_db_get_global_setting_int64(db, "http_port", &http_port);
tf_ssb_db_get_global_setting_int64(db, "https_port", &https_port);
tf_ssb_db_get_global_setting_string(db, "out_http_port_file", out_http_port_file, sizeof(out_http_port_file));
tf_ssb_db_get_global_setting_bool(db, "http_local_only", &local_only);
tf_ssb_release_db_reader(ssb, db);
if (https_port)
{
@ -2452,6 +2440,7 @@ void tf_httpd_register(JSContext* context)
tf_http_add_handler(http, "/hitches", _httpd_endpoint_hitches, NULL, task);
tf_http_add_handler(http, "/mem", _httpd_endpoint_mem, NULL, task);
tf_http_add_handler(http, "/trace", _httpd_endpoint_trace, NULL, task);
tf_http_add_handler(http, "/ebt", _httpd_endpoint_ebt, NULL, task);
tf_http_add_handler(http, "/login/logout", _httpd_endpoint_logout, NULL, task);
tf_http_add_handler(http, "/login/auto", _httpd_endpoint_login_auto, NULL, task);
@ -2459,18 +2448,14 @@ void tf_httpd_register(JSContext* context)
tf_http_add_handler(http, "/app/socket", _httpd_endpoint_app_socket, NULL, task);
JS_SetPropertyStr(context, httpd, "auth_query", JS_NewCFunction(context, _httpd_auth_query, "auth_query", 1));
JS_SetPropertyStr(context, global, "httpd", httpd);
JS_FreeValue(context, global);
if (http_port > 0 || out_http_port_file)
if (http_port > 0 || *out_http_port_file)
{
httpd_listener_t* listener = tf_malloc(sizeof(httpd_listener_t));
*listener = (httpd_listener_t) { 0 };
int assigned_port = tf_http_listen(http, http_port, NULL, _httpd_listener_cleanup, listener);
int assigned_port = tf_http_listen(http, http_port, local_only, NULL, _httpd_listener_cleanup, listener);
tf_printf(CYAN "~😎 Tilde Friends" RESET " " YELLOW VERSION_NUMBER RESET " is now up at " MAGENTA "http://127.0.0.1:%d/" RESET ".\n", assigned_port);
if (out_http_port_file)
if (*out_http_port_file)
{
const char* actual_http_port_file = tf_task_get_path_with_root(task, out_http_port_file);
FILE* file = fopen(actual_http_port_file, "wb");
@ -2500,13 +2485,17 @@ void tf_httpd_register(JSContext* context)
tf_tls_context_set_private_key(tls, private_key);
httpd_listener_t* listener = tf_malloc(sizeof(httpd_listener_t));
*listener = (httpd_listener_t) { .tls = tls };
int assigned_port = tf_http_listen(http, https_port, tls, _httpd_listener_cleanup, listener);
int assigned_port = tf_http_listen(http, https_port, local_only, tls, _httpd_listener_cleanup, listener);
tf_printf(CYAN "~😎 Tilde Friends" RESET " " YELLOW VERSION_NUMBER RESET " is now up at " MAGENTA "https://127.0.0.1:%d/" RESET ".\n", assigned_port);
}
tf_free((char*)certificate);
tf_free((char*)private_key);
}
}
tf_free((void*)out_http_port_file);
return http;
}
void tf_httpd_destroy(tf_http_t* http)
{
tf_http_destroy(http);
}

View File

@ -13,6 +13,11 @@
#include "quickjs.h"
/**
** An HTTP server instance.
*/
typedef struct _tf_http_t tf_http_t;
/**
** Register the HTTP script interface. Also registers a number of built-in
** request handlers. An ongoing project is to move the JS request handlers
@ -21,4 +26,16 @@
*/
void tf_httpd_register(JSContext* context);
/**
** Create the HTTP server instance.
** @param context The JS context.
*/
tf_http_t* tf_httpd_create(JSContext* context);
/**
** Destroy the HTTP server instance.
** @param http The HTTP server instance.
*/
void tf_httpd_destroy(tf_http_t* http);
/** @} */

View File

@ -4,34 +4,84 @@
#import <WebKit/WKWebView.h>
#import <WebKit/WKWebViewConfiguration.h>
#include "log.h"
#include <libgen.h>
#include <string.h>
void tf_run_thread_start(const char* zip_path);
@interface ViewController : UIViewController <WKUIDelegate, WKNavigationDelegate>
@interface ViewController : UINavigationController <WKUIDelegate, WKNavigationDelegate>
@property (strong, nonatomic) WKWebView* web_view;
@property bool initial_load_complete;
@end
static void _start_initial_load(WKWebView* web_view)
{
[web_view loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://localhost:12345/"]]];
[web_view loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://localhost:12345/login/auto"]]];
}
@implementation ViewController : UIViewController
@implementation ViewController : UINavigationController
- (void)viewDidLoad
{
[super viewDidLoad];
[self setToolbarHidden:false animated:false];
self.toolbar.items = @[
[[UIBarButtonItem alloc] initWithTitle:@"Back" style:UIBarButtonItemStylePlain target:self action:@selector(goBack)],
[[UIBarButtonItem alloc] initWithTitle:@"Forward" style:UIBarButtonItemStylePlain target:self action:@selector(goForward)],
[[UIBarButtonItem alloc] initWithTitle:@"Refresh" style:UIBarButtonItemStylePlain target:self action:@selector(reload)]
];
WKWebViewConfiguration* configuration = [[WKWebViewConfiguration alloc] init];
self.web_view = [[WKWebView alloc] initWithFrame:self.view.frame configuration:configuration];
self.web_view.UIDelegate = self;
self.web_view.navigationDelegate = self;
self.web_view.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
self.web_view.translatesAutoresizingMaskIntoConstraints = false;
[self.view addSubview:self.web_view];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.web_view attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view
attribute:NSLayoutAttributeTop
multiplier:1.0
constant:0]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.web_view attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self.view
attribute:NSLayoutAttributeBottom
multiplier:1.0
constant:-75]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.web_view attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self.view
attribute:NSLayoutAttributeLeft
multiplier:1.0
constant:0]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.web_view attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:self.view
attribute:NSLayoutAttributeRight
multiplier:1.0
constant:0]];
_start_initial_load(self.web_view);
}
- (void)goBack
{
if (self.web_view.canGoBack)
{
[self.web_view goBack];
}
}
- (void)goForward
{
if (self.web_view.canGoForward)
{
[self.web_view goForward];
}
}
- (void)reload
{
[self.web_view reload];
}
- (void)webView:(WKWebView*)webView didFinishNavigation:(WKNavigation*)navigation
{
self.initial_load_complete = true;

View File

@ -7,6 +7,6 @@
<key>com.apple.developer.team-identifier</key>
<string>EDVXQ27EB5</string>
<key>get-task-allow</key>
<true/>
<false/>
</dict>
</plist>

View File

@ -2,34 +2,86 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleName</key>
<string>tildefriends</string>
<key>CFBundleSupportedPlatforms</key>
<array>
<string>iPhoneOS</string>
</array>
<key>CFBundleDisplayName</key>
<string>Tilde Friends</string>
<key>CFBundleExecutable</key>
<string>tildefriends</string>
<key>CFBundleIdentifier</key>
<string>com.unprompted.tildefriends</string>
<key>CFBundleResourceSpecification</key>
<string>ResourceRules.plist</string>
<key>CFBundleName</key>
<string>tildefriends</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>0.0.31</string>
<key>CFBundleSupportedPlatforms</key>
<array>
<string>iPhoneOS</string>
</array>
<key>CFBundleVersion</key>
<string>13</string>
<key>DTPlatformName</key>
<string>iphoneos</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>CFBundleDisplayName</key>
<string>Tilde Friends</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleIconName</key>
<string>AppIcon</string>
<key>CFBundleIconFile</key>
<string>tildefriends.png</string>
<key>MinimumOSVersion</key>
<string>14.0</string>
<key>UIDeviceFamily</key>
<array>
<integer>1</integer>
<integer>2</integer>
</array>
<key>UILaunchScreen</key>
<dict>
<key>UIImageName</key>
<string>tildefriends.png</string>
</dict>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>arm64</string>
</array>
<key>BuildMachineOSBuild</key>
<string>24D70</string>
<key>DTPlatformBuild</key>
<string>22C146</string>
<key>DTPlatformVersion</key>
<string>18.2</string>
<key>DTSDKBuild</key>
<string>22C146</string>
<key>DTSDKName</key>
<string>iphoneos18.2</string>
<key>DTXcode</key>
<string>1620</string>
<key>DTXcodeBuild</key>
<string>16C5032a</string>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~iphone</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>CFBundleIcons</key>
<dict>
<key>CFBundlePrimaryIcon</key>
<dict>
<key>CFBundleIconFiles</key>
<array>
<string>AppIcon60x60</string>
</array>
<key>CFBundleIconName</key>
<string>AppIcon</string>
</dict>
</dict>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
</dict>
</plist>

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

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