254 Commits

Author SHA1 Message Date
94b7703ca9 build: Let's build 0.0.32.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 34m51s
2025-06-25 12:58:25 -04:00
a391dd1316 update: prettier
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-06-25 12:57:29 -04:00
b6ba5211b7 update: CodeMirror.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 37m36s
2025-06-23 21:34:27 -04:00
8e8e130045 docs: Ready 0.0.32 release notes.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 37m36s
2025-06-23 12:52:31 -04:00
1f40bc1a0f core: Fix the one place where we called a JS function and didn't check for jobs to run as a result. Fixes getting stuck in the intro as a non-admin.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 38m21s
2025-06-22 18:53:20 -04:00
5437212222 build: Fix android.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 38m48s
2025-06-18 20:25:49 -04:00
a8ab845cd2 docs: Minor usage cleanup.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-06-18 20:13:16 -04:00
8cee6dc98b docs: Fix copy+paste lies.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-06-18 19:58:25 -04:00
70c2b73414 welcome: Introducing hermietildefriends.svg.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-06-18 19:43:24 -04:00
98013c4422 welcome: Direct to the latest release, obviously.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-06-18 19:16:34 -04:00
e9e22b762d docs: Make this format a little prettier-friendly.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-06-18 19:08:47 -04:00
620db19936 docs: Auto-generate a usage.md from the command-line interface.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-06-18 19:00:13 -04:00
94a79dd62c ssb: How did I not have this index? Makes channel unread queries significantly faster.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 33m43s
2025-06-18 18:26:11 -04:00
b56c3efde0 prettier
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 33m8s
2025-06-18 12:37:41 -04:00
066827f8f1 ssb: Deconstruct and instrument the channels unread query.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-06-18 12:36:59 -04:00
c3b65d9cd8 update: CodeMirror.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-06-18 12:12:57 -04:00
a15b916b06 ssb: Only print broadcast failures once.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 32m33s
2025-06-16 22:26:23 -04:00
31d0a5c233 intro: Don't meddle with settings as non-admin. #129
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 32m57s
2025-06-16 12:20:09 -04:00
140179e80a intro: Fix grammar. #128
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-06-16 12:12:50 -04:00
53cba2d7e4 Move all but the deleting off of the writer when deleting blobs and messages. Trying to eliminate a period of unresponsiveness near launch.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 32m41s
2025-06-15 08:46:07 -04:00
e54312d3b8 ssb: Only admins are offered the option to enable peer exchange.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 32m49s
2025-06-14 20:27:19 -04:00
cadc27b7b5 ssb: Filter out invalid RPC flags.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 32m12s
2025-06-12 12:50:38 -04:00
388b829ec1 ssb: Allow unread status for the mentions tab.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-06-12 12:25:58 -04:00
67861f0f33 ssb: Add some options to encourage getting connected to the connections sidebar section.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 32m46s
2025-06-11 20:53:51 -04:00
a1f1eb34d5 ssb: sequence: Sequential 32-bit integer starting at 1.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 32m2s
2025-06-11 20:12:23 -04:00
2a6789063e ssb: Show/hide "Mark All Read" and the unread line more correctly.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 32m53s
2025-06-11 19:22:21 -04:00
cbf1273a55 ssb: Squeeze some blood from the following_perf stone.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-06-11 19:12:59 -04:00
8143a23ced format 2025-06-11 18:52:56 -04:00
3c17810747 welcome: Update the welcome page to be a bit more direct and relevant. #124
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 32m17s
2025-06-11 18:39:15 -04:00
bea7a2e9ed core: Use JS_NewTypedArray.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 32m29s
2025-06-11 12:56:27 -04:00
2f0a2ac6b0 core: Prefer JS_AtomToCString() over JS_AtomToString() + JS_ToCString().
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-06-11 12:46:52 -04:00
18908b6b56 core: Ignore websocket errors when navigating away from the page (ie, Abnormal closure when switching apps). #60
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 32m21s
2025-06-11 12:10:28 -04:00
b135a210cc core: Avoid trivial snprintfs.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 34m10s
2025-06-10 21:17:55 -04:00
3a2a829940 ssb: Disabling room support only disables the ability to tunnel through ourselves, not receive tunneled connections.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-06-10 20:36:59 -04:00
e56dd2dd2d core: Remove hitch tracking. Hasn't been an active problem, and the traces are sufficient to diagnose.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 32m36s
2025-06-10 12:44:56 -04:00
3f41a48bc7 ssb: Use message refs to get channel contents, not full-text search. Much faster for certain channels.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 32m43s
2025-06-09 12:16:09 -04:00
65ed53281a ssb: Shutdown hygiene. #108
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 36m20s
2025-06-07 18:00:05 -04:00
1121557a2e update: CodeMirror.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-06-07 17:29:45 -04:00
d4a7b86ee7 ssb: Revise private scanning slightly.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 34m58s
2025-06-07 15:10:37 -04:00
626c18b04e ssb: Fix some correctly identified private messages not showing up in the private message feed. 2025-06-07 15:04:08 -04:00
bfa97ed7c7 log: Disable stdout buffering. I want to see output sooner in journalctl.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 34m18s
2025-06-07 13:24:55 -04:00
deae4d5367 ssb: Obviously close the readers before the writer. 2025-06-07 13:19:46 -04:00
899605c860 ssb: Actually prevent the sqlite WAL file from growing out of control by truncating when it grows to about 64MB of pages.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 33m59s
2025-06-07 11:39:31 -04:00
dc9a279991 ssb: Free sqlite3_exec errors. 2025-06-07 10:36:44 -04:00
2a53892581 update: sqlite 3.50.1.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 34m58s
2025-06-07 08:04:34 -04:00
6bef0eb764 prettier
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 31m55s
2025-06-04 20:48:04 -04:00
462b40640c ssb: Don't show messages as unread when not in channels that allow unread status.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 32m29s
2025-06-04 19:56:02 -04:00
72e1b2025c core: Chasing shutdown issues some more. Logging. No more task promises once shutdown starts. #108
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-06-04 19:47:54 -04:00
fc7c4b1257 docs: Add a quick doc of how connecting Manyverse to tildefriends.net is supposed to work.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 32m3s
2025-06-04 18:45:18 -04:00
6c22c59056 room: Fix multiple issues with the core room app. Good gravy, it's like I don't want people to connect.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-06-04 18:24:31 -04:00
94c2b1184f prettier
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-06-04 18:01:39 -04:00
45231d703d ssb: Split recent votes into their own sidebar item as an experiment. #122 2025-06-04 18:00:46 -04:00
7882fcbe8f ssb: The red border is too much and stacks poorly. Let's try the same unread icon from the sidebar.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 31m51s
2025-06-04 12:24:04 -04:00
3bbc8c4d35 ssb: Consolidate the buttons around the compose UI. #122
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 33m53s
2025-06-02 21:47:35 -04:00
8ae10dc80b ssb: Add some more visibility that you're above the unread line. #122
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-06-02 21:24:03 -04:00
9b11c2c629 ssb: Expose a list of followed accounts on the profile page. #122
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-06-02 21:06:25 -04:00
e2a231fb4a ssb: Consolidated contact message groups a bit too much. 2025-06-02 12:14:06 -04:00
8a9502d1f2 ssb: More correct/thorough channel status.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 33m50s
2025-05-31 17:55:49 -04:00
534438df63 ssb: The message_refs table had loads of erroneous entries. This fixes that and adds channel/hashtag refs.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-05-31 17:42:17 -04:00
45a4feec96 ssb: Fix a use of undefined.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 33m36s
2025-05-31 16:52:37 -04:00
aa7a32395e ssb: Expose last successful connection time for stored connections.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-05-31 16:33:48 -04:00
ab9f57f044 ssb: Support expanding profile images the same as other images. #122
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-05-31 16:11:13 -04:00
4040d6aa08 ssb: Hide 'Mark as read' when we're not in a channel with a read status. #122
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 32m52s
2025-05-31 15:23:07 -04:00
1c96f5c35e ssb: Slight tweaks to 'This message is not currently available.' 2025-05-31 15:20:55 -04:00
4d3e42812d ssb: Condense follows/blocks more, and support replies to them. #122 2025-05-31 15:17:07 -04:00
f7b3711d4f ssb: Bury placeholder message blob ids in a context menu. #122
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 34m4s
2025-05-31 11:44:50 -04:00
2408e076ff ssb: Indicate connection status with some colors in the connections list in the sidebar. #122
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-05-31 11:28:09 -04:00
6f71ffb477 ssb: Treat most plaintext votes as 👍. #122
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 33m12s
2025-05-31 10:52:07 -04:00
214433f36a update: CodeMirror.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 32m41s
2025-05-29 12:39:31 -04:00
309b22732e update: sqlite 3.50.0.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-05-29 12:37:14 -04:00
6fe7687b2a docs: Update the screenshots.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 32m44s
2025-05-28 20:49:48 -04:00
a8cbf757ff build: Let's start work on 0.0.32. nix => 0.0.31.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 32m47s
2025-05-28 18:12:15 -04:00
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
125 changed files with 10497 additions and 3921 deletions

View File

@ -943,7 +943,9 @@ WARN_LOGFILE =
# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING # spaces. See also FILE_PATTERNS and EXTENSION_MAPPING
# Note: If this tag is empty the current directory is searched. # 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 # 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 # that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses
@ -1049,7 +1051,7 @@ EXAMPLE_RECURSIVE = NO
# that contain images that are to be included in the documentation (see the # that contain images that are to be included in the documentation (see the
# \image command). # \image command).
IMAGE_PATH = IMAGE_PATH = docs/images/
# The INPUT_FILTER tag can be used to specify a program that doxygen should # The INPUT_FILTER tag can be used to specify a program that doxygen should
# invoke to filter for each input file. Doxygen will invoke the filter program # invoke to filter for each input file. Doxygen will invoke the filter program
@ -2268,7 +2270,7 @@ GENERATE_AUTOGEN_DEF = NO
# database with symbols found by doxygen stored in tables. # database with symbols found by doxygen stored in tables.
# The default value is: NO. # 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 # 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 # 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. # The default directory is: sqlite3.
# This tag requires that the tag GENERATE_SQLITE3 is set to YES. # 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 # 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 # 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. # The default value is: YES.
# This tag requires that the tag GENERATE_SQLITE3 is set to 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 # Configuration options related to the Perl module output

View File

@ -16,14 +16,14 @@ MAKEFLAGS += --no-builtin-rules
## LD := Linker. ## LD := Linker.
## ANDROID_SDK := Path to the Android SDK. ## ANDROID_SDK := Path to the Android SDK.
VERSION_CODE := 34 VERSION_CODE := 38
VERSION_CODE_IOS := 11 VERSION_CODE_IOS := 14
VERSION_NUMBER := 0.0.29-wip VERSION_NUMBER := 0.0.32
VERSION_NAME := This program kills fascists. VERSION_NAME := This program kills fascists.
IPHONEOS_VERSION_MIN=14.0 IPHONEOS_VERSION_MIN=14.0
SQLITE_URL := https://www.sqlite.org/2025/sqlite-amalgamation-3490100.zip SQLITE_URL := https://www.sqlite.org/2025/sqlite-amalgamation-3500100.zip
BUNDLETOOL_URL := https://github.com/google/bundletool/releases/download/1.17.0/bundletool-all-1.17.0.jar 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_URL := https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage
APPIMAGETOOL_MD5 := e989fadfc4d685fd3d6aeeb9b525d74d out/appimagetool APPIMAGETOOL_MD5 := e989fadfc4d685fd3d6aeeb9b525d74d out/appimagetool
@ -45,6 +45,10 @@ export TZ=UTC
ifeq ($(UNAME_S),Darwin) ifeq ($(UNAME_S),Darwin)
BUILD_TYPES := debug release iosdebug iosrelease iossimdebug iossimrelease BUILD_TYPES := debug release iosdebug iosrelease iossimdebug iossimrelease
HAVE_ANDROID = 0
HAVE_LINUX_IOS = 0
HAVE_LINUX_MACOS = 0
HAVE_WIN = 0
else ifeq ($(UNAME_S),Linux) else ifeq ($(UNAME_S),Linux)
BUILD_TYPES := debug release BUILD_TYPES := debug release
HAVE_ANDROID = $(if $(shell which $(ANDROID_SDK)/platform-tools/adb),1) HAVE_ANDROID = $(if $(shell which $(ANDROID_SDK)/platform-tools/adb),1)
@ -62,6 +66,10 @@ LDFLAGS += \
-lnetwork \ -lnetwork \
-Wno-stringop-overflow -Wno-stringop-overflow
USE_SYSTEM_SSL := 1 USE_SYSTEM_SSL := 1
HAVE_ANDROID = 0
HAVE_LINUX_IOS = 0
HAVE_LINUX_MACOS = 0
HAVE_WIN = 0
else ifeq ($(UNAME_S),OpenBSD) else ifeq ($(UNAME_S),OpenBSD)
BUILD_TYPES := debug release BUILD_TYPES := debug release
CFLAGS += \ CFLAGS += \
@ -606,15 +614,16 @@ $(UV_OBJS): CFLAGS += \
-Ideps/libuv/include \ -Ideps/libuv/include \
-Ideps/libuv/src \ -Ideps/libuv/src \
-Wno-dangling-pointer \ -Wno-dangling-pointer \
-Wno-format-truncation \
-Wno-incompatible-pointer-types \ -Wno-incompatible-pointer-types \
-Wno-maybe-uninitialized \ -Wno-maybe-uninitialized \
-Wno-nonnull \
-Wno-sign-compare \ -Wno-sign-compare \
-Wno-unknown-attributes \ -Wno-unknown-attributes \
-Wno-unused-but-set-parameter \ -Wno-unused-but-set-parameter \
-Wno-unused-but-set-variable \ -Wno-unused-but-set-variable \
-Wno-unused-result \ -Wno-unused-result \
-Wno-unused-variable \ -Wno-unused-variable
-Wno-nonnull
$(filter out/win%,$(UV_OBJS)): \ $(filter out/win%,$(UV_OBJS)): \
CFLAGS += \ CFLAGS += \
-Wno-cast-function-type \ -Wno-cast-function-type \
@ -710,12 +719,12 @@ $(SQLITE_OBJS): CFLAGS += \
-DSQLITE_MAX_COMPOUND_SELECT=300 \ -DSQLITE_MAX_COMPOUND_SELECT=300 \
-DSQLITE_MAX_EXPR_DEPTH=40 \ -DSQLITE_MAX_EXPR_DEPTH=40 \
-DSQLITE_MAX_FUNCTION_ARG=8 \ -DSQLITE_MAX_FUNCTION_ARG=8 \
-DSQLITE_MAX_LENGTH=5242880 \ -DSQLITE_MAX_LENGTH=10485760 \
-DSQLITE_MAX_LIKE_PATTERN_LENGTH=50 \ -DSQLITE_MAX_LIKE_PATTERN_LENGTH=50 \
-DSQLITE_MAX_SQL_LENGTH=100000 \ -DSQLITE_MAX_SQL_LENGTH=100000 \
-DSQLITE_MAX_TRIGGER_DEPTH=10 \ -DSQLITE_MAX_TRIGGER_DEPTH=10 \
-DSQLITE_MAX_VARIABLE_NUMBER=100 \ -DSQLITE_MAX_VARIABLE_NUMBER=100 \
-DSQLITE_MAX_VDBE_OP=25000 \ -DSQLITE_MAX_VDBE_OP=50000 \
-DSQLITE_OMIT_DEPRECATED \ -DSQLITE_OMIT_DEPRECATED \
-DSQLITE_OMIT_DESERIALIZE \ -DSQLITE_OMIT_DESERIALIZE \
-DSQLITE_OMIT_LOAD_EXTENSION \ -DSQLITE_OMIT_LOAD_EXTENSION \
@ -734,7 +743,7 @@ $(SQLITE_OBJS): CFLAGS += \
QUICKJS_SOURCES := \ QUICKJS_SOURCES := \
deps/quickjs/cutils.c \ deps/quickjs/cutils.c \
deps/quickjs/libbf.c \ deps/quickjs/dtoa.c \
deps/quickjs/libregexp.c \ deps/quickjs/libregexp.c \
deps/quickjs/libunicode.c \ deps/quickjs/libunicode.c \
deps/quickjs/quickjs.c deps/quickjs/quickjs.c
@ -985,7 +994,8 @@ PACKAGE_DIRS := \
core \ core \
deps/codemirror \ deps/codemirror \
deps/prettier \ deps/prettier \
deps/lit 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 '.*'))) RAW_FILES := $(sort $(filter-out apps/blog% apps/issues% apps/welcome% apps/journal% %.map, $(shell find $(PACKAGE_DIRS) -type f -not -name '.*')))
@ -1006,7 +1016,7 @@ $(BUNDLETOOL):
@curl -q -L --create-dirs -o $@ $(BUNDLETOOL_URL) @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) 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 @mkdir -p out/aab/staging
@$(ANDROID_BUILD_TOOLS)/aapt2 link --proto-format -o out/aab/temporary.apk \ @$(ANDROID_BUILD_TOOLS)/aapt2 link --proto-format -o out/aab/temporary.apk \
-I $(ANDROID_PLATFORM)/android.jar \ -I $(ANDROID_PLATFORM)/android.jar \
@ -1026,14 +1036,11 @@ out/TildeFriends.aab: out/apk/classes.dex $(filter-out %debug%, $(ANDROID_TARGET
@cp out/apk/classes.dex out/aab/staging/dex/ @cp out/apk/classes.dex out/aab/staging/dex/
@rm -fv out/base.zip @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 @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 @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
@cp out/androidrelease-armv7a/tildefriends out/aab/staging/lib/armeabi-v7a/libtildefriends.so @$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip out/androidrelease/tildefriends -o out/aab/staging/lib/arm64-v8a/libtildefriends.so
@cp out/androidrelease-x86_64/tildefriends out/aab/staging/lib/x86_64/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
@cp out/androidrelease-x86/tildefriends out/aab/staging/lib/x86/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/aab/staging/lib/arm64-v8a/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
@$(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
@cp -r apps/ out/aab/staging/root/ @cp -r apps/ out/aab/staging/root/
@rm -rf out/aab/staging/root/apps/welcome* @rm -rf out/aab/staging/root/apps/welcome*
@cp -r core/ out/aab/staging/root/ @cp -r core/ out/aab/staging/root/
@ -1042,6 +1049,11 @@ out/TildeFriends.aab: out/apk/classes.dex $(filter-out %debug%, $(ANDROID_TARGET
@cp -r deps/codemirror/ out/aab/staging/root/deps/ @cp -r deps/codemirror/ out/aab/staging/root/deps/
@cd out/aab/staging/; zip -r ../base.zip *; cd ../../../ @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=$@ @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) $@ @$(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. aab: out/TildeFriends.aab ## Build an Android App Bundle.
@ -1153,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 -B out/zsign_build deps/zsign
@cmake --build out/zsign_build -- COLOR=0 VERBOSE=0 MAKESILENT=-s @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 $@) @mkdir -p $(dir $@)
@cp -v $(filter-out out/zsign%,$<) $@ @cp -v $(filter-out out/zsign%,$<) $@
@cp -v out/data.zip $(@D)/ @cp -v out/data.zip $(@D)/
@ -1465,6 +1483,18 @@ help: ## Display this help message.
.PHONY: help .PHONY: help
.DEFAULT_GOAL := help .DEFAULT_GOAL := help
docs: debug
docs: ## Build HTML docs. docs: ## Build HTML docs.
@echo '# CLI Usage\n' > docs/usage.md
@echo "## tildefriends -h" >> docs/usage.md
@echo '\n```' >> docs/usage.md
@out/debug/tildefriends -h >> docs/usage.md
@echo '```' >> docs/usage.md
@for command in $$(out/debug/tildefriends -h | grep -Po '[A-Za-z_]*(?= - )'); do
@ echo "\n## tildefriends $$command -h" >> docs/usage.md
@ echo '\n```' >> docs/usage.md
@ out/debug/tildefriends $$command -h >> docs/usage.md
@ echo '```' >> docs/usage.md
@done
@doxygen @doxygen
.PHONY: docs .PHONY: docs

View File

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

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} html{box-sizing:border-box}*,*:before,*:after{box-sizing:inherit}
/* Extract from normalize.css by Nicolas Gallagher and Jonathan Neal git.io/normalize */ /* 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} 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-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-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-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,.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-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-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-button:hover{color:#000!important;background-color:#ccc!important}
.w3-transparent,.w3-hover-none:hover{background-color:transparent!important} .w3-transparent,.w3-hover-none:hover{background-color:transparent!important}
.w3-hover-none:hover{box-shadow:none!important} .w3-hover-none:hover{box-shadow:none!important}
.w3-rtl{direction:rtl}.w3-ltr{direction:ltr}
/* Colors */ /* Colors */
.w3-amber,.w3-hover-amber:hover{color:#000!important;background-color:#ffc107!important} .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-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-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-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-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-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-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-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-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-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-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} html{box-sizing:border-box}*,*:before,*:after{box-sizing:inherit}
/* Extract from normalize.css by Nicolas Gallagher and Jonathan Neal git.io/normalize */ /* 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} 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-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-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-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,.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-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-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-button:hover{color:#000!important;background-color:#ccc!important}
.w3-transparent,.w3-hover-none:hover{background-color:transparent!important} .w3-transparent,.w3-hover-none:hover{background-color:transparent!important}
.w3-hover-none:hover{box-shadow:none!important} .w3-hover-none:hover{box-shadow:none!important}
.w3-rtl{direction:rtl}.w3-ltr{direction:ltr}
/* Colors */ /* Colors */
.w3-amber,.w3-hover-amber:hover{color:#000!important;background-color:#ffc107!important} .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-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-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-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-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-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-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-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-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-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-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} html{box-sizing:border-box}*,*:before,*:after{box-sizing:inherit}
/* Extract from normalize.css by Nicolas Gallagher and Jonathan Neal git.io/normalize */ /* 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} 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-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-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-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,.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-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-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-button:hover{color:#000!important;background-color:#ccc!important}
.w3-transparent,.w3-hover-none:hover{background-color:transparent!important} .w3-transparent,.w3-hover-none:hover{background-color:transparent!important}
.w3-hover-none:hover{box-shadow:none!important} .w3-hover-none:hover{box-shadow:none!important}
.w3-rtl{direction:rtl}.w3-ltr{direction:ltr}
/* Colors */ /* Colors */
.w3-amber,.w3-hover-amber:hover{color:#000!important;background-color:#ffc107!important} .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-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-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-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-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-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-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-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-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-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-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": "&eN6DNPpQUNhGvxneLuLPgsOXR6qyFZ7u+MAz0b4fa7k=.sha256"
}

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

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

286
apps/intro/index.html Normal file
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 mirror human social interaction.
</p>
<ul>
<li>
You can create your own account and post to your own feed on
your own device. This is all <b>local</b> with no external
communication. This puts you fully in control of your own words
and actions.
</li>
<li>
Before you can interact with others, you need to
<b>connect over the network</b>, either directly to your friends
(i.e., peer-to-peer between your phones on coffee shop Wi-Fi) or
to 🚪<i>rooms</i> and 🍻<i>pubs</i> (hint: search the web for
<i>#ssbroom</i>).
</li>
<li>
Who you choose to <b>follow</b> determines what you see, with
most people choosing to see messages from friends and friends of
those friends. If you encounter content you'd rather not see,
<b>block</b> the offending account to improve the experience for
you and your followers.
</li>
<li>
Your feed is an <b>immutable</b> log of your activity. Post with
care, because like your words in real life, posts can't be taken
back.
</li>
</ul>
</div>
<footer class="w3-center w3-xlarge w3-padding">
<a
class="w3-button w3-light-gray"
href="https://scuttlebutt.nz/"
target="_blank"
>See scuttlebutt.nz</a
>
<button class="w3-button w3-yellow proceed">Got It</button>
</footer>
</div>
</div>
<div class="slide w3-gray" style="width: 90%">
<div class="w3-card-4 w3-xlarge">
<header class="w3-container w3-blue w3-center">
<h1>~😎 Let's Talk Tilde Friends ~😎</h1>
</header>
<div class="w3-container w3-large w3-left-align">
<p>
Tilde Friends is an application platform that is an application of
its own.
</p>
<ul>
<li>
This intro is a Tilde Friends app. You can click <b>edit</b> at
the top to look under the hood and make changes.
</li>
<li>
It is already possible to make and share new applications using
only Tilde Friends and Secure Scuttlebutt without having to set
up development environments, configure web servers, register
domain names, or pay for hosting services.
</li>
<li>
But it's also set up so that you can't just break an app that
everybody is using or do malicious things with personal content.
There are <b>protections</b> in place like an operating system.
The intent is also for it to be <b>safe</b> to run strange apps
without worrying about adverse effects.
</li>
<li>
But this is all a big 🚧work in progress🚧 and
<b>experiment</b>. Let's see where it takes us.
</li>
</ul>
</div>
<footer class="w3-center w3-xlarge w3-padding">
<button class="w3-button w3-yellow proceed">Okay</button>
</footer>
</div>
</div>
<div class="slide w3-gray" style="width: 90%">
<div class="w3-card-4 w3-xlarge">
<header class="w3-container w3-blue w3-center">
<h1>🦀Let's Get this Tilde Friends Party Started🎉</h1>
</header>
<div class="w3-container w3-large w3-left-align">
<p>The button below will take you to the Secure Scuttlebutt app.</p>
<ul>
<li>
Remember:
<ol>
<li>You are in charge. This is all on your device.</li>
<li>
Make network connections to exchange messages with others.
</li>
<li>
Follow more accounts to see more content, and block those
posting content you'd rather not see.
</li>
<li>
Be respectful, and consider the consequences of what you
post.
</li>
<li>
This is all under active development. Exercise patience, and
report issues encountered.
</li>
</ol>
</li>
<li>
To see this tutorial again later, select <b>apps</b> -&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,5 +1,5 @@
{ {
"type": "tildefriends-app", "type": "tildefriends-app",
"emoji": "🚪", "emoji": "🚪",
"previous": "&HXCdDG8gGYXElTyEFbg85jqa6lDXNL2ENPIA9UoJNbI=.sha256" "previous": "&DJwkqNfYWtW9yBtJQMseEXm7l04Enpi+yAxZulLq9Vk=.sha256"
} }

View File

@ -2,8 +2,8 @@ async function main() {
print(core.url); print(core.url);
let host = core.url.match(/.*?\/\/([^:/]*)/)[1]; let host = core.url.match(/.*?\/\/([^:/]*)/)[1];
let port = await ssb.port(); let port = await ssb.port();
let id = (await ssb.getServerIdentity()).substring(1); let id = (await ssb.getServerIdentity()).substring(1).split('.')[0];
let room = `net:${host}:${port}~shs:${id}:SSB+Room+SK3TLYC2T86EHQCUHBUHASCASE18JBV24=`; let room = `net:${host}:${port}~shs:${id}:SSB+Room+PSK3TLYC2T86EHQCUHBUHASCASE18JBV24=`;
await app.setDocument(` await app.setDocument(`
<body style="color: #fff"> <body style="color: #fff">
<h1>Server</h1> <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", "type": "tildefriends-app",
"emoji": "🦀", "emoji": "🦀",
"previous": "&jAAzd36Nmpw0sRA1Dx9wLiIwGX+q//+S/Han+RLlEOw=.sha256" "previous": "&Rn4Eg5ev5qhrYRnwxPB0DiEwO7VdGMDGp7tL/W7bRZo=.sha256"
} }

View File

@ -106,6 +106,15 @@ tfrpc.register(async function sync() {
tfrpc.register(async function url() { tfrpc.register(async function url() {
return core.url; return core.url;
}); });
tfrpc.register(async function globalSettingsGet(key) {
return core.globalSettingsGet(key);
});
tfrpc.register(async function globalSettingsSet(key, value) {
return core.globalSettingsSet(key, value);
});
tfrpc.register(function isAdministrator() {
return core.user?.credentials?.permissions?.administration;
});
core.register('onBroadcastsChanged', async function () { core.register('onBroadcastsChanged', async function () {
await tfrpc.rpc.set('broadcasts', await ssb.getBroadcasts()); await tfrpc.rpc.set('broadcasts', await ssb.getBroadcasts());

View File

@ -14,23 +14,8 @@ function get_emojis() {
}); });
} }
async function get_recent(author) { export async function picker(callback, anchor, author, recent) {
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) {
let json = await get_emojis(); let json = await get_emojis();
let recent = await get_recent(author);
let div = document.createElement('div'); let div = document.createElement('div');
div.id = 'emoji_picker'; 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}, broadcasts: {type: Array},
connections: {type: Array}, connections: {type: Array},
loading: {type: Boolean}, loading: {type: Boolean},
loading_about: {type: Number},
loaded: {type: Boolean}, loaded: {type: Boolean},
following: {type: Array}, following: {type: Array},
users: {type: Object}, users: {type: Object},
@ -21,6 +22,7 @@ class TfElement extends LitElement {
guest: {type: Boolean}, guest: {type: Boolean},
url: {type: String}, url: {type: String},
private_messages: {type: Array}, private_messages: {type: Array},
recent_reactions: {type: Array},
}; };
} }
@ -36,11 +38,13 @@ class TfElement extends LitElement {
this.following = []; this.following = [];
this.users = {}; this.users = {};
this.loaded = false; this.loaded = false;
this.loading_about = 0;
this.channels = []; this.channels = [];
this.channels_unread = {}; this.channels_unread = {};
this.channels_latest = {}; this.channels_latest = {};
this.loading_latest = 0; this.loading_latest = 0;
this.loading_latest_scheduled = 0; this.loading_latest_scheduled = 0;
this.recent_reactions = [];
tfrpc.rpc.getBroadcasts().then((b) => { tfrpc.rpc.getBroadcasts().then((b) => {
self.broadcasts = b || []; self.broadcasts = b || [];
}); });
@ -124,7 +128,13 @@ class TfElement extends LitElement {
} }
next_channel(delta) { next_channel(delta) {
let channel_names = ['', '@', '🔐', ...this.channels.map((x) => '#' + x)]; let channel_names = [
'',
'@',
'🔐',
'👍',
...this.channels.map((x) => '#' + x),
];
let index = channel_names.indexOf(this.hash.substring(1)); let index = channel_names.indexOf(this.hash.substring(1));
index = index != -1 ? index + delta : 0; index = index != -1 ? index + delta : 0;
tfrpc.rpc.setHash( tfrpc.rpc.setHash(
@ -149,8 +159,9 @@ class TfElement extends LitElement {
} }
async fetch_about(following, users) { async fetch_about(following, users) {
this.loading_about++;
let ids = Object.keys(following).sort(); let ids = Object.keys(following).sort();
const k_cache_version = 1; const k_cache_version = 3;
let cache = await tfrpc.rpc.databaseGet('about'); let cache = await tfrpc.rpc.databaseGet('about');
let original_cache = cache; let original_cache = cache;
cache = cache ? JSON.parse(cache) : {}; cache = cache ? JSON.parse(cache) : {};
@ -158,81 +169,86 @@ class TfElement extends LitElement {
cache = { cache = {
version: k_cache_version, version: k_cache_version,
about: {}, about: {},
last_row_id: 0,
}; };
} }
let max_row_id = (
await tfrpc.rpc.query( let ids_out_of_date = ids.filter(
` (x) =>
SELECT MAX(rowid) AS max_row_id FROM messages (users[x]?.seq && !cache.about[x]?.seq) ||
`, (users[x]?.seq && users[x]?.seq > cache.about[x].seq)
[] );
)
)[0].max_row_id;
for (let id of Object.keys(cache.about)) { for (let id of Object.keys(cache.about)) {
if (ids.indexOf(id) == -1) { if (ids.indexOf(id) == -1) {
delete cache.about[id]; delete cache.about[id];
} else {
users[id] = Object.assign(cache.about[id], users[id] || {});
} }
} }
let abouts = await tfrpc.rpc.query( console.log(
` 'loading about for',
SELECT ids.length,
messages.author, json(messages.content) AS content, messages.sequence 'accounts',
FROM ids_out_of_date.length,
messages, 'out of date'
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,
]
); );
for (let about of abouts) { if (ids_out_of_date.length) {
let content = JSON.parse(about.content); try {
if (content.about === about.author) { let rows = await tfrpc.rpc.query(
delete content.type; `
delete content.about; SELECT all_abouts.author, json(json_group_object(all_abouts.key, all_abouts.value)) AS about
cache.about[about.author] = Object.assign( FROM (
cache.about[about.author] || {}, SELECT
content 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); let new_cache = JSON.stringify(cache);
if (new_cache !== original_cache) { if (new_cache != original_cache) {
let start_time = new Date(); let start_time = new Date();
tfrpc.rpc.databaseSet('about', new_cache).then(function () { tfrpc.rpc.databaseSet('about', new_cache).then(function () {
console.log('saving about took', (new Date() - start_time) / 1000); 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); return Object.assign({}, users);
} }
@ -292,11 +308,7 @@ class TfElement extends LitElement {
ranges.push([i, Math.min(i + k_chunk_size, latest), true]); ranges.push([i, Math.min(i + k_chunk_size, latest), true]);
} }
for (let i = cache.range[0]; i >= 0; i -= k_chunk_size) { for (let i = cache.range[0]; i >= 0; i -= k_chunk_size) {
ranges.push([ ranges.push([Math.max(i - k_chunk_size, 0), i, false]);
Math.max(i - k_chunk_size, 0),
Math.min(cache.range[0], i + k_chunk_size),
false,
]);
} }
} else { } else {
for (let i = 0; i < latest; i += k_chunk_size) { for (let i = 0; i < latest; i += k_chunk_size) {
@ -312,7 +324,7 @@ class TfElement extends LitElement {
messages.rowid > ?1 AND messages.rowid > ?1 AND
messages.rowid <= ?2 AND messages.rowid <= ?2 AND
json(messages.content) LIKE '"%' json(messages.content) LIKE '"%'
ORDER BY sequence DESC ORDER BY messages.rowid DESC
`, `,
[range[0], range[1]] [range[0], range[1]]
); );
@ -338,42 +350,93 @@ class TfElement extends LitElement {
return [cache.latest, cache.messages]; return [cache.latest, cache.messages];
} }
async query_timed(sql, args) {
let start = new Date();
let result = await tfrpc.rpc.query(sql, args);
let end = new Date();
console.log((end - start) / 1000, sql);
return result;
}
async load_channels_latest(following) { async load_channels_latest(following) {
let start_time = new Date(); let start_time = new Date();
let latest_private = this.get_latest_private(following); let latest_private = this.get_latest_private(following);
let channels = await tfrpc.rpc.query( const k_args = [
` JSON.stringify(this.channels),
SELECT channels.value AS channel, MAX(messages.rowid) AS rowid FROM messages JSON.stringify(following),
JOIN json_each(?1) AS channels ON messages.content ->> 'channel' = channels.value '"' + this.whoami.replace('"', '""') + '"',
JOIN json_each(?2) AS following ON messages.author = following.value this.whoami,
WHERE ];
messages.content ->> 'type' = 'post' AND let channels = (
messages.content ->> 'root' IS NULL AND await Promise.all([
messages.author != ?4 this.query_timed(
GROUP by channel `
UNION SELECT channels.value AS channel, MAX(messages.rowid) AS rowid FROM messages
SELECT '' AS channel, MAX(messages.rowid) AS rowid FROM messages JOIN json_each(?1) AS channels ON messages.content ->> 'channel' = channels.value
JOIN json_each(?2) AS following ON messages.author = following.value JOIN json_each(?2) AS following ON messages.author = following.value
WHERE WHERE
messages.content ->> 'type' = 'post' AND messages.content ->> 'type' = 'post' AND
messages.content ->> 'root' IS NULL AND messages.content ->> 'root' IS NULL AND
messages.author != ?4 messages.author != ?4
UNION GROUP by channel
SELECT '@' AS channel, MAX(messages.rowid) AS rowid FROM messages_fts(?3) `,
JOIN messages ON messages.rowid = messages_fts.rowid k_args
JOIN json_each(?2) AS following ON messages.author = following.value ),
WHERE messages.author != ?4 this.query_timed(
`, `
[ SELECT channels.value AS channel, MAX(messages.rowid) AS rowid FROM messages
JSON.stringify(this.channels), JOIN messages_refs ON messages.id = messages_refs.message
JSON.stringify(following), JOIN json_each(?1) AS channels ON messages_refs.ref = '#' || channels.value
'"' + this.whoami.replace('"', '""') + '"', JOIN json_each(?2) AS following ON messages.author = following.value
this.whoami, WHERE
] messages.content ->> 'type' = 'post' AND
); messages.content ->> 'root' IS NULL AND
this.channels_latest = Object.fromEntries( messages.author != ?4
channels.map((x) => [x.channel, x.rowid]) GROUP by channel
); `,
k_args
),
this.query_timed(
`
SELECT '' AS channel, MAX(messages.rowid) AS rowid FROM messages
JOIN json_each(?2) AS following ON messages.author = following.value
WHERE
messages.content ->> 'type' = 'post' AND
messages.content ->> 'root' IS NULL AND
messages.author != ?4
`,
k_args
),
this.query_timed(
`
SELECT '@' AS channel, MAX(messages.rowid) AS rowid FROM messages_fts(?3)
JOIN messages ON messages.rowid = messages_fts.rowid
JOIN json_each(?2) AS following ON messages.author = following.value
WHERE messages.author != ?4
`,
k_args
),
this.query_timed(
`
SELECT '👍' AS channel, MAX(messages.rowid) AS rowid FROM messages
JOIN json_each(?2) AS following ON messages.author = following.value
WHERE
messages.content ->> 'type' = 'vote' AND
messages.author != ?4
`,
k_args
),
])
).flat();
let latest = {};
for (let row of channels) {
if (!latest[row.channel]) {
latest[row.channel] = row.rowid;
} else {
latest[row.channel] = Math.max(row.rowid, latest[row.channel]);
}
}
this.channels_latest = latest;
console.log('channels took', (new Date() - start_time) / 1000.0); console.log('channels took', (new Date() - start_time) / 1000.0);
let self = this; let self = this;
start_time = new Date(); start_time = new Date();
@ -382,7 +445,6 @@ class TfElement extends LitElement {
'🔐': latest[0], '🔐': latest[0],
}); });
console.log('private took', (new Date() - start_time) / 1000.0); console.log('private took', (new Date() - start_time) / 1000.0);
console.log(latest);
self.private_messages = latest[1]; self.private_messages = latest[1];
}); });
} }
@ -412,43 +474,58 @@ class TfElement extends LitElement {
[JSON.stringify(Object.keys(users))] [JSON.stringify(Object.keys(users))]
); );
for (let row of info) { for (let row of info) {
users[row.author].seq = row.max_seq; users[row.author] = Object.assign(users[row.author], {
users[row.author].ts = row.max_ts; seq: row.max_sequence,
ts: row.max_ts,
});
} }
return users; 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() { async load() {
this.loading_latest = true; this.loading_latest = true;
try { try {
let start_time = new Date(); let start_time = new Date();
let whoami = this.whoami; let whoami = this.whoami;
let following = await tfrpc.rpc.following([whoami], 2); let following = await tfrpc.rpc.following([whoami], 2);
let old_users = this.users ?? {};
let users = {}; let users = {};
let by_count = []; let by_count = [];
for (let [id, v] of Object.entries(following)) { for (let [id, v] of Object.entries(following)) {
users[id] = { users[id] = Object.assign(
following: v.of, {
blocking: v.ob, following: v.of,
followed: v.if, blocking: v.ob,
blocked: v.ib, followed: v.if,
}; blocked: v.ib,
follow_depth: following[id]?.d,
},
old_users[id]
);
by_count.push({count: v.of, id: id}); by_count.push({count: v.of, id: id});
} }
let reactions = this.load_recent_reactions();
this.load_channels_latest(Object.keys(following)); this.load_channels_latest(Object.keys(following));
this.channels_unread = JSON.parse( this.channels_unread = JSON.parse(
(await tfrpc.rpc.databaseGet('unread')) ?? '{}' (await tfrpc.rpc.databaseGet('unread')) ?? '{}'
); );
this.following = Object.keys(following); this.following = Object.keys(following);
let about_start_time = new Date(); 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(); start_time = new Date();
users = await this.fetch_user_info(users); users = await this.fetch_user_info(users);
console.log( console.log(
@ -457,9 +534,22 @@ class TfElement extends LitElement {
'seconds' 'seconds'
); );
this.users = users; 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( console.log(
`load finished ${whoami} => ${this.whoami} in ${(new Date() - start_time) / 1000}` `load finished ${whoami} => ${this.whoami} in ${(new Date() - start_time) / 1000}`
); );
await reactions;
this.whoami = whoami; this.whoami = whoami;
this.loaded = whoami; this.loaded = whoami;
} finally { } finally {
@ -510,13 +600,14 @@ class TfElement extends LitElement {
whoami=${this.whoami} whoami=${this.whoami}
.users=${this.users} .users=${this.users}
hash=${this.hash} hash=${this.hash}
?loading=${this.loading} ?loading=${this.loading || this.loading_about != 0}
.channels=${this.channels} .channels=${this.channels}
.channels_latest=${this.channels_latest} .channels_latest=${this.channels_latest}
.channels_unread=${this.channels_unread} .channels_unread=${this.channels_unread}
@channelsetunread=${this.channel_set_unread} @channelsetunread=${this.channel_set_unread}
.connections=${this.connections} .connections=${this.connections}
.private_messages=${this.private_messages} .private_messages=${this.private_messages}
.recent_reactions=${this.recent_reactions}
></tf-tab-news> ></tf-tab-news>
`; `;
} else if (this.tab === 'connections') { } else if (this.tab === 'connections') {

View File

@ -255,10 +255,12 @@ class TfComposeElement extends LitElement {
let self = this; let self = this;
let input = document.createElement('input'); let input = document.createElement('input');
input.type = 'file'; input.type = 'file';
input.onchange = function (event) { input.addEventListener('change', function (event) {
input.parentNode.removeChild(input);
let file = event.target.files[0]; let file = event.target.files[0];
self.add_file(file); self.add_file(file);
}; });
document.body.appendChild(input);
input.click(); input.click();
} }
@ -357,7 +359,7 @@ class TfComposeElement extends LitElement {
remove_mention(id) { remove_mention(id) {
let draft = this.get_draft(); let draft = this.get_draft();
delete draft.mentions[id]; delete draft.mentions[id];
setTimeout(() => this.notify(), 0); setTimeout(() => this.notify(draft), 0);
} }
render_mention(mention) { render_mention(mention) {
@ -444,12 +446,15 @@ class TfComposeElement extends LitElement {
self.apps = await tfrpc.rpc.apps(); self.apps = await tfrpc.rpc.apps();
} }
if (!this.apps) { if (!this.apps) {
return html`<button class="w3-button w3-theme-d1" @click=${attach_app}> return html`<button
class="w3-button w3-bar-item w3-theme-d1"
@click=${attach_app}
>
Attach App Attach App
</button>`; </button>`;
} else { } else {
return html`<button return html`<button
class="w3-button w3-theme-d1" class="w3-button w3-bar-item w3-theme-d1"
@click=${() => (this.apps = null)} @click=${() => (this.apps = null)}
> >
Discard App Discard App
@ -470,18 +475,9 @@ class TfComposeElement extends LitElement {
if (draft.content_warning !== undefined) { if (draft.content_warning !== undefined) {
return html` return html`
<div class="w3-container w3-padding"> <div class="w3-container w3-padding">
<p>
<input type="checkbox" class="w3-check w3-theme-d1" id="cw" @change=${() => self.set_content_warning(undefined)} checked="checked"></input>
<label for="cw">CW</label>
</p>
<input type="text" class="w3-input w3-border w3-theme-d1" id="content_warning" placeholder="Enter a content warning here." @input=${self.input} value=${draft.content_warning}></input> <input type="text" class="w3-input w3-border w3-theme-d1" id="content_warning" placeholder="Enter a content warning here." @input=${self.input} value=${draft.content_warning}></input>
</div> </div>
`; `;
} else {
return html`
<input type="checkbox" class="w3-check w3-theme-d1" id="cw" @change=${() => self.set_content_warning('')}></input>
<label for="cw">CW</label>
`;
} }
} }
@ -522,7 +518,7 @@ class TfComposeElement extends LitElement {
return html` return html`
<div style="display: flex; flex-direction: row; width: 100%"> <div style="display: flex; flex-direction: row; width: 100%">
<label for="encrypt_to">🔐 To:</label> <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> <button class="w3-button w3-theme-d1" @click=${() => this.set_encrypt(undefined)}>🚮</button>
</div> </div>
<ul> <ul>
@ -544,6 +540,31 @@ class TfComposeElement extends LitElement {
this.requestUpdate(); this.requestUpdate();
} }
toggle_menu(event) {
event.srcElement.parentNode
.querySelector('.w3-dropdown-content')
.classList.toggle('w3-show');
}
connectedCallback() {
super.connectedCallback();
this._click_callback = this.document_click.bind(this);
document.body.addEventListener('mouseup', this._click_callback);
}
disconnectedCallback() {
super.disconnectedCallback();
document.body.removeEventListener('mouseup', this._click_callback);
}
document_click(event) {
let content = this.renderRoot.querySelector('.w3-dropdown-content');
let target = event.target;
if (content && !content.contains(target)) {
content.classList.remove('w3-show');
}
}
render() { render() {
let self = this; let self = this;
let draft = self.get_draft(); let draft = self.get_draft();
@ -557,7 +578,7 @@ class TfComposeElement extends LitElement {
draft.encrypt_to !== undefined draft.encrypt_to !== undefined
? undefined ? undefined
: html`<button : html`<button
class="w3-button w3-theme-d1" class="w3-button w3-bar-item w3-theme-d1"
@click=${() => this.set_encrypt([])} @click=${() => this.set_encrypt([])}
> >
🔐 🔐
@ -612,13 +633,43 @@ class TfComposeElement extends LitElement {
> >
Submit Submit
</button> </button>
<button class="w3-button w3-theme-d1" @click=${this.attach}> <div class="w3-dropdown-click">
Attach <button class="w3-button w3-theme-d1" @click=${this.toggle_menu}>
</button> ⚙️
${this.render_attach_app_button()} ${encrypt} </button>
<button class="w3-button w3-theme-d1" @click=${this.discard}> <div class="w3-dropdown-content w3-bar-block">
Discard ${this.get_draft().content_warning === undefined
</button> ? html`
<button
class="w3-button w3-bar-item w3-theme-d1"
@click=${() => self.set_content_warning('')}
>
Add Content Warning
</button>
`
: html`
<button
class="w3-button w3-bar-item w3-theme-d1"
@click=${() => self.set_content_warning(undefined)}
>
Remove Content Warning
</button>
`}
<button
class="w3-button w3-bar-item w3-theme-d1"
@click=${this.attach}
>
Attach
</button>
${this.render_attach_app_button()} ${encrypt}
<button
class="w3-button w3-bar-item w3-theme-d1"
@click=${this.discard}
>
Discard
</button>
</div>
</div>
</footer> </footer>
</div> </div>
`; `;

View File

@ -1,4 +1,11 @@
import {LitElement, html, repeat, render, unsafeHTML} from './lit-all.min.js'; import {
LitElement,
css,
html,
repeat,
render,
unsafeHTML,
} from './lit-all.min.js';
import * as tfrpc from '/static/tfrpc.js'; import * as tfrpc from '/static/tfrpc.js';
import * as tfutils from './tf-utils.js'; import * as tfutils from './tf-utils.js';
import * as emojis from './emojis.js'; import * as emojis from './emojis.js';
@ -16,6 +23,7 @@ class TfMessageElement extends LitElement {
expanded: {type: Object}, expanded: {type: Object},
channel: {type: String}, channel: {type: String},
channel_unread: {type: Number}, channel_unread: {type: Number},
recent_reactions: {type: Array},
}; };
} }
@ -31,6 +39,26 @@ class TfMessageElement extends LitElement {
this.format = 'message'; this.format = 'message';
this.expanded = {}; this.expanded = {};
this.channel_unread = -1; 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() { show_reply() {
@ -65,20 +93,27 @@ class TfMessageElement extends LitElement {
render_votes() { render_votes() {
function normalize_expression(expression) { function normalize_expression(expression) {
if (expression === 'Like' || !expression) { if (
return '👍'; expression === 'Unlike' ||
} else if (expression === 'Unlike') { expression === 'unlike' ||
expression == 'undig'
) {
return '👎'; return '👎';
} else if (expression === 'heart') { } else if (expression === 'heart') {
return '❤️'; return '❤️';
} else if (
(expression ?? '').split('').every((x) => x.charCodeAt(0) < 256)
) {
return '👍';
} else { } else {
return expression; return expression;
} }
} }
if (this.message?.votes?.length) { if (this.message?.votes?.length) {
return html` <div class="w3-container"> return html` <footer class="w3-container">
<div <div
class="w3-button w3-bar w3-padding-small" class="w3-button w3-bar"
style="padding: 0"
@click=${this.show_reactions} @click=${this.show_reactions}
> >
${(this.message.votes || []).map( ${(this.message.votes || []).map(
@ -93,7 +128,7 @@ class TfMessageElement extends LitElement {
` `
)} )}
</div> </div>
</div>`; </footer>`;
} }
} }
@ -136,7 +171,12 @@ class TfMessageElement extends LitElement {
} }
react(event) { 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) { show_image(link) {
@ -270,53 +310,69 @@ class TfMessageElement extends LitElement {
return total; return total;
} }
expanded_key() {
return this.message?.id || this.messages?.map((x) => x.id).join(':');
}
set_expanded(expanded, tag) { set_expanded(expanded, tag) {
let key = this.expanded_key();
this.dispatchEvent( this.dispatchEvent(
new CustomEvent('tf-expand', { new CustomEvent('tf-expand', {
bubbles: true, bubbles: true,
composed: true, composed: true,
detail: {id: (this.message.id || '') + (tag || ''), expanded: expanded}, detail: {id: key + (tag || ''), expanded: expanded},
}) })
); );
} }
toggle_expanded(tag) { toggle_expanded(tag) {
this.set_expanded( let key = this.expanded_key();
!this.expanded[(this.message.id || '') + (tag || '')], this.set_expanded(!this.expanded[key + (tag || '')], tag);
tag }
);
is_expanded(tag) {
let key = this.expanded_key();
return this.expanded[key + (tag || '')];
} }
render_children() { render_children() {
let self = this; let self = this;
if (this.message.child_messages?.length) { if (this.message.child_messages?.length) {
if (!this.expanded[this.message.id]) { if (!this.expanded[this.expanded_key()]) {
return html`<button return html`
class="w3-button w3-theme-d1" <button
@click=${() => self.set_expanded(true)} class="w3-button w3-theme-d1 w3-block w3-bar"
> style="box-sizing: border-box"
+ ${this.total_child_messages(this.message) + ' More'} @click=${() => self.set_expanded(true)}
</button>`; >
+ ${this.total_child_messages(this.message) + ' More'}
</button>
`;
} else { } else {
return html`<button return html` <div class="w3-container w3-margin-bottom">
class="w3-button w3-theme-d1" ${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)} @click=${() => self.set_expanded(false)}
> >
Collapse</button Collapse
>${repeat( </button>`;
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>`
)}`;
} }
} else { } else {
return undefined; return undefined;
@ -358,7 +414,7 @@ class TfMessageElement extends LitElement {
class_background() { class_background() {
return this.message?.decrypted return this.message?.decrypted
? 'w3-pale-red' ? 'w3-pale-red'
: this.message?.rowid >= this.channel_unread : this.allow_unread() && this.message?.rowid >= this.channel_unread
? 'w3-theme-d2' ? 'w3-theme-d2'
: 'w3-theme-d4'; : 'w3-theme-d4';
} }
@ -371,62 +427,74 @@ class TfMessageElement extends LitElement {
return content; 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 content = this.get_content();
let raw_button; let formats = [['message', 'Message']];
switch (this.format) { if (content?.type == 'post' || content?.type == 'blog') {
case 'raw': formats.push(['md', 'Markdown']);
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;
} }
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() { render_header() {
@ -438,16 +506,15 @@ class TfMessageElement extends LitElement {
return html` return html`
<header class="w3-bar"> <header class="w3-bar">
<span class="w3-bar-item"> <span class="w3-bar-item">
<tf-user id=${this.message.author} .users=${this.users}></tf-user> ${this.render_unread_icon()}<tf-user
id=${this.message.author}
.users=${this.users}
></tf-user>
</span> </span>
${is_encrypted} ${is_encrypted} ${this.render_menu()}
<span class="w3-bar-item w3-right">${this.render_raw_button()}</span> <div class="w3-bar-item w3-right" style="text-wrap: nowrap">
<span class="w3-bar-item w3-right" style="text-wrap: nowrap" ${new Date(this.message.timestamp).toLocaleString()}
><a target="_top" href=${'#' + encodeURIComponent(this.message.id)} </div>
>%</a
>
${new Date(this.message.timestamp).toLocaleString()}</span
>
</header> </header>
`; `;
} }
@ -495,6 +562,7 @@ class TfMessageElement extends LitElement {
.expanded=${self.expanded} .expanded=${self.expanded}
channel=${self.channel} channel=${self.channel}
channel_unread=${self.channel_unread} channel_unread=${self.channel_unread}
.recent_reactions=${self.recent_reactions}
></tf-message> ></tf-message>
` `
)} )}
@ -506,32 +574,76 @@ class TfMessageElement extends LitElement {
let reply = let reply =
this.drafts[this.message?.id] !== undefined this.drafts[this.message?.id] !== undefined
? html` ? html`
<tf-compose <div class="w3-section w3-container">
whoami=${this.whoami} <tf-compose
.users=${this.users} whoami=${this.whoami}
root=${content.root || this.message.id} .users=${this.users}
branch=${this.message.id} root=${content.root || this.message.id}
.drafts=${this.drafts} branch=${this.message.id}
@tf-discard=${this.discard_reply} .drafts=${this.drafts}
author=${this.message.author} @tf-discard=${this.discard_reply}
></tf-compose> author=${this.message.author}
.recent_reactions=${this.recent_reactions}
></tf-compose>
</div>
` `
: html` : undefined;
<button class="w3-button w3-theme-d1" @click=${this.show_reply}>
Reply
</button>
`;
return html` return html`
<div class="w3-section w3-container"> ${reply}
${reply} <footer>${this.render_children()}</footer>
<button class="w3-button w3-theme-d1" @click=${this.react}>
React
</button>
${this.render_children()}
</div>
`; `;
} }
content_group_by_author() {
let sorted = this.message.messages
.map((x) => [
x.author,
x.content.blocking !== undefined
? x.content.blocking
? 'is blocking'
: 'is no longer blocking'
: x.content.following !== undefined
? x.content.following
? 'is following'
: 'is no longer following'
: '',
x.content.contact,
x,
])
.sort();
let result = [];
let last;
let group;
for (let row of sorted) {
if (last && last[0] == row[0] && last[1] == row[1]) {
group.push(row[2]);
} else {
if (group) {
result.push({author: last[0], action: last[1], users: group});
}
last = row;
group = [row[2]];
}
}
if (group) {
result.push({author: last[0], action: last[1], users: group});
}
return result;
}
allow_unread() {
return (
this.channel == '@' ||
(!this.channel.startsWith('@') && !this.channel.startsWith('%'))
);
}
render_unread_icon() {
return this.allow_unread() && this.message?.rowid >= this.channel_unread
? html`✉️`
: undefined;
}
render() { render() {
let content = this.message?.content; let content = this.message?.content;
if (this.message?.decrypted?.type == 'post') { if (this.message?.decrypted?.type == 'post') {
@ -540,29 +652,90 @@ class TfMessageElement extends LitElement {
let class_background = this.class_background(); let class_background = this.class_background();
let self = this; let self = this;
if (this.message?.type === 'contact_group') { if (this.message?.type === 'contact_group') {
return this.render_frame( if (this.expanded[this.expanded_key()]) {
html` ${this.message.messages.map( return this.render_frame(html`
(x) => <div class="w3-padding">
html`<tf-message ${this.message.messages.map(
.message=${x} (x) =>
whoami=${this.whoami} html`<tf-message
.users=${this.users} .message=${x}
.drafts=${this.drafts} whoami=${this.whoami}
.expanded=${this.expanded} .users=${this.users}
channel=${this.channel} .drafts=${this.drafts}
channel_unread=${this.channel_unread} .expanded=${this.expanded}
></tf-message>` channel=${this.channel}
)}` channel_unread=${this.channel_unread}
); ></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>
`);
} else {
return this.render_frame(html`
<div class="w3-padding">
${this.content_group_by_author().map(
(x) => html`
<div>
<tf-user id=${x.author} .users=${this.users}></tf-user>
${x.action}
${x.users.map(
(y) => html`
<tf-user id=${y} .users=${this.users}></tf-user>
`
)}
</div>
`
)}
</div>
<button
class="w3-button w3-theme-d1 w3-block w3-bar"
style="box-sizing: border-box"
@click=${() => self.set_expanded(true)}
>
Expand
</button>
`);
}
} else if (this.message.placeholder) { } else if (this.message.placeholder) {
return this.render_frame( return this.render_frame(
html`<div class="w3-padding"> html`<div>
<p> <div class="w3-bar">
<a target="_top" href=${'#' + encodeURIComponent(this.message.id)} <a
>${this.message.id}</a class="w3-bar-item w3-panel w3-round-xlarge w3-theme-d1 w3-margin w3-button"
target="_top"
href=${'#' + encodeURIComponent(this.message?.id)}
> >
(placeholder) This message is not currently available.
</p> </a>
<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>
</div>
</div>
</div>
<div>${this.render_votes()}</div> <div>${this.render_votes()}</div>
${(this.message.child_messages || []).map( ${(this.message.child_messages || []).map(
(x) => html` (x) => html`
@ -589,7 +762,7 @@ class TfMessageElement extends LitElement {
} }
if (content.image !== undefined) { if (content.image !== undefined) {
image = html` image = html`
<div><img src=${'/' + (typeof content.image?.link == 'string' ? content.image.link : content.image) + '/view'} style="width: 256px; height: auto"></img></div> <div @click=${this.body_click}><img src=${'/' + (typeof content.image?.link == 'string' ? content.image.link : content.image) + '/view'} style="width: 256px; height: auto"></img></div>
`; `;
} }
if (content.description !== undefined) { if (content.description !== undefined) {
@ -612,25 +785,60 @@ class TfMessageElement extends LitElement {
</div> </div>
`); `);
} else if (content.type == 'contact') { } else if (content.type == 'contact') {
return html` return this.render_frame(html`
<div class="w3-padding"> <div class="w3-bar">
<tf-user id=${this.message.author} .users=${this.users}></tf-user> <div class="w3-bar-item">
is <tf-user id=${this.message.author} .users=${this.users}></tf-user>
${content.blocking === true is
? 'blocking' ${content.blocking === true
: content.blocking === false ? 'blocking'
? 'no longer blocking' : content.blocking === false
: content.following === true ? 'no longer blocking'
? 'following' : content.following === true
: content.following === false ? 'following'
? 'no longer following' : content.following === false
: '?'} ? 'no longer following'
<tf-user : '?'}
id=${this.message.content.contact} <tf-user
.users=${this.users} id=${this.message.content.contact}
></tf-user> .users=${this.users}
></tf-user>
</div>
<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}
</div>
</div>
${this.render_votes()} ${this.render_actions()}
</div> </div>
`; `);
} else if (content.type == 'post') { } else if (content.type == 'post') {
let self = this; let self = this;
let body; let body;
@ -653,11 +861,14 @@ class TfMessageElement extends LitElement {
} }
let content_warning = html` let content_warning = html`
<div <div
class="w3-panel w3-round-xlarge w3-theme-l4" class="w3-panel w3-round-xlarge w3-theme-l4 w3"
style="cursor: pointer" style="cursor: pointer"
@click=${(x) => this.toggle_expanded(':cw')} @click=${(x) => this.toggle_expanded(':cw')}
> >
<p>${content.contentWarning}</p> <p>${content.contentWarning}</p>
<p class="w3-small">
${this.is_expanded(':cw') ? 'Show less' : 'Show more'}
</p>
</div> </div>
`; `;
let content_html = html` let content_html = html`

View File

@ -13,6 +13,8 @@ class TfNewsElement extends LitElement {
expanded: {type: Object}, expanded: {type: Object},
channel: {type: String}, channel: {type: String},
channel_unread: {type: Number}, channel_unread: {type: Number},
recent_reactions: {type: Array},
hash: {type: String},
}; };
} }
@ -28,6 +30,7 @@ class TfNewsElement extends LitElement {
this.drafts = {}; this.drafts = {};
this.expanded = {}; this.expanded = {};
this.channel_unread = -1; this.channel_unread = -1;
this.recent_reactions = [];
} }
process_messages(messages) { process_messages(messages) {
@ -164,7 +167,10 @@ class TfNewsElement extends LitElement {
if (message?.content?.type === 'contact') { if (message?.content?.type === 'contact') {
group.push(message); group.push(message);
} else { } else {
if (group.length > 0) { if (group.length == 1) {
result.push(group[0]);
group = [];
} else if (group.length > 1) {
result.push({ result.push({
rowid: Math.max(...group.map((x) => x.rowid)), rowid: Math.max(...group.map((x) => x.rowid)),
type: 'contact_group', type: 'contact_group',
@ -175,7 +181,10 @@ class TfNewsElement extends LitElement {
result.push(message); result.push(message);
} }
} }
if (group.length > 0) { if (group.length == 1) {
result.push(group[0]);
group = [];
} else if (group.length > 1) {
result.push({ result.push({
rowid: Math.max(...group.map((x) => x.rowid)), rowid: Math.max(...group.map((x) => x.rowid)),
type: 'contact_group', type: 'contact_group',
@ -185,15 +194,21 @@ class TfNewsElement extends LitElement {
return result; return result;
} }
unread_allowed() {
return !this.hash?.startsWith('#%') && !this.hash?.startsWith('#@');
}
load_and_render(messages) { load_and_render(messages) {
let messages_by_id = this.process_messages(messages); let messages_by_id = this.process_messages(messages);
let final_messages = this.group_following( let final_messages = this.group_following(
this.finalize_messages(messages_by_id) this.finalize_messages(messages_by_id)
); );
let unread_rowid = -1; let unread_rowid = -1;
for (let message of final_messages) { if (this.unread_allowed()) {
if (message.rowid >= this.channel_unread) { for (let message of final_messages) {
unread_rowid = message.rowid; if (message.rowid >= this.channel_unread) {
unread_rowid = message.rowid;
}
} }
} }
return html` return html`
@ -211,6 +226,7 @@ class TfNewsElement extends LitElement {
collapsed="true" collapsed="true"
channel=${this.channel} channel=${this.channel}
channel_unread=${this.channel_unread} channel_unread=${this.channel_unread}
.recent_reactions=${this.recent_reactions}
></tf-message> ></tf-message>
${x.rowid == unread_rowid ${x.rowid == unread_rowid
? html`<div style="display: flex; flex-direction: row"> ? html`<div style="display: flex; flex-direction: row">

View File

@ -1,4 +1,4 @@
import {LitElement, html, unsafeHTML} from './lit-all.min.js'; import {LitElement, html, until, unsafeHTML} from './lit-all.min.js';
import * as tfrpc from '/static/tfrpc.js'; import * as tfrpc from '/static/tfrpc.js';
import * as tfutils from './tf-utils.js'; import * as tfutils from './tf-utils.js';
import {styles} from './tf-styles.js'; import {styles} from './tf-styles.js';
@ -11,6 +11,7 @@ class TfProfileElement extends LitElement {
id: {type: String}, id: {type: String},
users: {type: Object}, users: {type: Object},
size: {type: Number}, size: {type: Number},
sequence: {type: Number},
following: {type: Boolean}, following: {type: Boolean},
blocking: {type: Boolean}, blocking: {type: Boolean},
}; };
@ -26,6 +27,7 @@ class TfProfileElement extends LitElement {
this.id = null; this.id = null;
this.users = {}; this.users = {};
this.size = 0; this.size = 0;
this.sequence = 0;
} }
async load() { async load() {
@ -139,7 +141,8 @@ class TfProfileElement extends LitElement {
let self = this; let self = this;
let input = document.createElement('input'); let input = document.createElement('input');
input.type = 'file'; input.type = 'file';
input.onchange = function (event) { input.addEventListener('change', function (event) {
input.parentNode.removeChild(input);
let file = event.target.files[0]; let file = event.target.files[0];
file file
.arrayBuffer() .arrayBuffer()
@ -154,7 +157,8 @@ class TfProfileElement extends LitElement {
.catch(function (e) { .catch(function (e) {
alert(e.message); alert(e.message);
}); });
}; });
document.body.appendChild(input);
input.click(); input.click();
} }
@ -162,17 +166,86 @@ class TfProfileElement extends LitElement {
navigator.clipboard.writeText(this.id); navigator.clipboard.writeText(this.id);
} }
show_image(link) {
let div = document.createElement('div');
div.style.left = 0;
div.style.top = 0;
div.style.width = '100%';
div.style.height = '100%';
div.style.position = 'fixed';
div.style.background = '#000';
div.style.zIndex = 100;
div.style.display = 'grid';
let img = document.createElement('img');
img.src = link;
img.style.maxWidth = '100%';
img.style.maxHeight = '100%';
img.style.display = 'block';
img.style.margin = 'auto';
img.style.objectFit = 'contain';
img.style.width = '100%';
div.appendChild(img);
function image_close(event) {
document.body.removeChild(div);
window.removeEventListener('keydown', image_close);
}
div.onclick = image_close;
window.addEventListener('keydown', image_close);
document.body.appendChild(div);
}
body_click(event) {
if (event.srcElement.tagName == 'IMG') {
this.show_image(event.srcElement.src);
}
}
toggle_account_list(event) {
let content = event.srcElement.nextElementSibling;
if (content.classList.toggle('w3-hide')) {
event.srcElement.innerText = 'Show Followed Accounts';
} else {
event.srcElement.innerText = 'Hide Followed Accounts';
}
}
async load_follows() {
let accounts = await tfrpc.rpc.following([this.id], 1);
return html`
<div class="w3-container">
<button
class="w3-button w3-block w3-theme-d1"
@click=${this.toggle_account_list}
>
Show Followed Accounts
</button>
<div class="w3-hide w3-card">
<ul class="w3-ul w3-theme-d4 w3-border-theme">
${Object.keys(accounts).map(
(x) => html`
<li class="w3-border-theme">
<tf-user id=${x} .users=${this.users}></tf-user>
</li>
`
)}
</ul>
</div>
</div>
`;
}
render() { render() {
this.load(); this.load();
let self = this; let self = this;
let profile = this.users[this.id] || {}; let profile = this.users[this.id] || {};
tfrpc.rpc tfrpc.rpc
.query( .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] [this.id]
) )
.then(function (result) { .then(function (result) {
self.size = result[0].size; self.size = result[0].size;
self.sequence = result[0].sequence;
}); });
let edit; let edit;
let follow; let follow;
@ -237,15 +310,19 @@ class TfProfileElement extends LitElement {
</div> </div>
</div>` </div>`
: null; : null;
let image = let image = profile.image;
typeof profile.image == 'string' ? profile.image : profile.image?.link; if (typeof image == 'string' && !image.startsWith('&')) {
try {
image = JSON.parse(image)?.link;
} catch {}
}
image = this.editing?.image ?? image; image = this.editing?.image ?? image;
let description = this.editing?.description ?? profile.description; let description = this.editing?.description ?? profile.description;
return html`<div class="w3-card-4 w3-container w3-theme-d3" style="box-sizing: border-box"> return html`<div class="w3-card-4 w3-container w3-theme-d3" style="box-sizing: border-box">
<header class="w3-container"> <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> </header>
<div class="w3-container"> <div class="w3-container" @click=${this.body_click}>
<div class="w3-margin-bottom" style="display: flex; flex-direction: row"> <div class="w3-margin-bottom" style="display: flex; flex-direction: row">
<input type="text" class="w3-input w3-border w3-theme-d1" style="display: flex 1 1" readonly value=${this.id}></input> <input type="text" class="w3-input w3-border w3-theme-d1" style="display: flex 1 1" readonly value=${this.id}></input>
<button class="w3-button w3-theme-d1 w3-ripple" style="flex: 0 0 auto" @click=${this.copy_id}>Copy</button> <button class="w3-button w3-theme-d1 w3-ripple" style="flex: 0 0 auto" @click=${this.copy_id}>Copy</button>
@ -271,6 +348,7 @@ class TfProfileElement extends LitElement {
Blocked by ${profile.blocked} identities. Blocked by ${profile.blocked} identities.
</div> </div>
</div> </div>
${until(this.load_follows(), html`<p>Loading accounts followed...</p>`)}
<footer class="w3-container"> <footer class="w3-container">
<p> <p>
${edit} ${edit}

View File

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

View File

@ -48,7 +48,7 @@ const tf = css`
// prettier-ignore // prettier-ignore
const w3 = css` 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} html{box-sizing:border-box}*,*:before,*:after{box-sizing:inherit}
/* Extract from normalize.css by Nicolas Gallagher and Jonathan Neal git.io/normalize */ /* 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} 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-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,.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: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-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-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-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} .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:1205px){.w3-auto{max-width:95%}}
@media (max-width:600px){.w3-modal-content{margin:0 10px;width:auto!important}.w3-modal{padding-top:30px} @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-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%}} .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 (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-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-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-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,.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-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-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-transparent,.w3-hover-none:hover{background-color:transparent!important}
.w3-hover-none:hover{box-shadow:none!important} .w3-hover-none:hover{box-shadow:none!important}
/* Colors */ /* 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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-yellow,.w3-hover-pale-yellow:hover{color:#000!important;background-color:#ffffcc!important}

View File

@ -103,6 +103,23 @@ class TfTabConnectionsElement extends LitElement {
</div>`; </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) { render_broadcast(connection) {
let self = this; let self = this;
return html` return html`
@ -154,6 +171,16 @@ class TfTabConnectionsElement extends LitElement {
: undefined} : undefined}
${connection.flags.one_shot ? '🔃' : undefined} ${connection.flags.one_shot ? '🔃' : undefined}
<tf-user id=${connection.id} .users=${this.users}></tf-user> <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 ${connection.tunnel !== undefined
? '🚇' ? '🚇'
: html`(${connection.host}:${connection.port})`} : 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() { render() {
let self = this; let self = this;
return html` return html`
@ -220,27 +262,33 @@ class TfTabConnectionsElement extends LitElement {
> >
Connect Connect
</button> </button>
<h2>Broadcasts</h2> <h2
<ul class="w3-ul w3-border"> class="w3-button w3-block w3-theme-d1"
${this.broadcasts @click=${() => self.toggle_accordian('connections')}
.filter((x) => x.address) >
.filter( Connections (${this.valid_connections().length})
(x) => self.connections.map((c) => c.id).indexOf(x.pubkey) == -1 </h2>
) <ul class="w3-ul w3-border" id="connections">
.map((x) => self.render_broadcast(x))} ${this.valid_connections().map(
(x) => html` <li class="w3-bar">${this.render_connection(x)}</li> `
)}
</ul> </ul>
<h2>Connections</h2> <h2
<ul class="w3-ul w3-border"> class="w3-button w3-block w3-theme-d1"
${this.connections @click=${() => self.toggle_accordian('broadcasts')}
.filter((x) => x.tunnel === undefined) >
.map( Discovery (${this.valid_broadcasts().length})
(x) => html` </h2>
<li class="w3-bar">${this.render_connection(x)}</li> <ul class="w3-ul w3-border w3-hide" id="broadcasts">
` ${this.valid_broadcasts().map((x) => self.render_broadcast(x))}
)}
</ul> </ul>
<h2>Stored Connections</h2> <h2
<ul class="w3-ul w3-border"> 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( ${this.stored_connections.map(
(x) => html` (x) => html`
<li> <li>
@ -260,6 +308,12 @@ class TfTabConnectionsElement extends LitElement {
<div class="w3-bar-item"> <div class="w3-bar-item">
<tf-user id=${x.pubkey} .users=${self.users}></tf-user> <tf-user id=${x.pubkey} .users=${self.users}></tf-user>
<div><small>${x.address}:${x.port}</small></div> <div><small>${x.address}:${x.port}</small></div>
<div>
<small
>Last connection:
${new Date(x.last_success * 1000)}</small
>
</div>
</div> </div>
</div> </div>
${this.render_message(x)} ${this.render_message(x)}
@ -267,8 +321,13 @@ class TfTabConnectionsElement extends LitElement {
` `
)} )}
</ul> </ul>
<h2>Local Accounts</h2> <h2
<div class="w3-container"> 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( ${this.identities.map(
(x) => (x) =>
html`<div html`<div

View File

@ -18,6 +18,7 @@ class TfTabNewsFeedElement extends LitElement {
time_range: {type: Array}, time_range: {type: Array},
time_loading: {type: Array}, time_loading: {type: Array},
private_messages: {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.start_time = new Date().valueOf();
this.time_range = [0, 0]; this.time_range = [0, 0];
this.time_loading = undefined; this.time_loading = undefined;
this.recent_reactions = [];
this.loading = 0; this.loading = 0;
} }
@ -46,6 +48,63 @@ class TfTabNewsFeedElement extends LitElement {
: this.hash.substring(1); : 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) { async fetch_messages(start_time, end_time) {
this.time_loading = [start_time, end_time]; this.time_loading = [start_time, end_time];
let result; let result;
@ -107,7 +166,8 @@ class TfTabNewsFeedElement extends LitElement {
[this.hash.substring(1)] [this.hash.substring(1)]
); );
} else if (this.hash.startsWith('##')) { } else if (this.hash.startsWith('##')) {
result = await tfrpc.rpc.query( let t0 = new Date();
let initial_messages = await tfrpc.rpc.query(
` `
WITH WITH
all_news AS ( all_news AS (
@ -117,33 +177,28 @@ class TfTabNewsFeedElement extends LitElement {
WHERE messages.content ->> 'channel' = ?4 WHERE messages.content ->> 'channel' = ?4
UNION UNION
SELECT messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature SELECT messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
FROM messages_fts(?5) FROM messages_refs
JOIN messages ON messages.rowid = messages_fts.rowid JOIN messages ON messages.id = messages_refs.message
JOIN json_each(?1) AS following ON messages.author = following.value JOIN json_each(?1) AS following ON messages.author = following.value
JOIN json_tree(messages.content, '$.mentions') AS mention ON mention.value = '#' || ?4), WHERE messages_refs.ref = '#' || ?4
news AS (SELECT * FROM all_news )
WHERE (?2 IS NULL OR all_news.timestamp >= ?2) AND all_news.timestamp < ?3 SELECT TRUE AS is_primary, all_news.* FROM all_news
ORDER BY all_news.timestamp DESC LIMIT 20) WHERE (?2 IS NULL OR all_news.timestamp >= ?2) AND all_news.timestamp < ?3
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 ORDER BY all_news.timestamp DESC LIMIT 20
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), JSON.stringify(this.following),
start_time, start_time,
end_time, end_time,
this.hash.substring(2), this.hash.substring(2),
'"#' + 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 == '#🔐') { } else if (this.hash == '#🔐') {
result = await tfrpc.rpc.query( result = await tfrpc.rpc.query(
` `
@ -153,39 +208,54 @@ class TfTabNewsFeedElement extends LitElement {
WHERE WHERE
(?2 IS NULL OR (messages.timestamp >= ?2)) AND messages.timestamp < ?3 AND (?2 IS NULL OR (messages.timestamp >= ?2)) AND messages.timestamp < ?3 AND
json(messages.content) LIKE '"%' json(messages.content) LIKE '"%'
ORDER BY messages.sequence DESC LIMIT 20 ORDER BY messages.rowid DESC LIMIT 20
`, `,
[JSON.stringify(this.private_messages), start_time, end_time] [JSON.stringify(this.private_messages), start_time, end_time]
); );
result = (await this.decrypt(result)).filter((x) => x.decrypted); result = (await this.decrypt(result)).filter((x) => x.decrypted);
} else { } else if (this.hash == '#👍') {
result = await tfrpc.rpc.query( result = await tfrpc.rpc.query(
`
WITH votes AS (SELECT messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
FROM messages
JOIN json_each(?1) AS following ON messages.author = following.value
WHERE
messages.content ->> 'type' = 'vote' AND
(?2 IS NULL OR messages.timestamp >= ?2) AND messages.timestamp < ?3
ORDER BY timestamp DESC limit 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 votes
JOIN messages ON messages.id = votes.content ->> '$.vote.link'
UNION
SELECT TRUE AS is_primary, * FROM votes
`,
[JSON.stringify(this.following), start_time, end_time]
);
} else {
let t0 = new Date();
let initial_messages = await tfrpc.rpc.query(
` `
WITH WITH
all_news AS ( 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 SELECT messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
FROM messages FROM messages
JOIN json_each(?) AS following ON messages.author = following.value JOIN json_each(?) AS following ON messages.author = following.value
WHERE timestamp >= 0 AND timestamp < ?3), ),
news AS ( news AS (
SELECT * FROM all_news 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 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 SELECT TRUE AS is_primary, news.* FROM news
`, `,
[JSON.stringify(this.following), start_time, end_time] [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; this.time_loading = undefined;
return result; return result;
@ -205,13 +275,24 @@ class TfTabNewsFeedElement extends LitElement {
]; ];
} }
unread_allowed() {
return (
this.hash == '#@' ||
(!this.hash.startsWith('#%') && !this.hash.startsWith('#@'))
);
}
async load_more() { async load_more() {
this.loading++; this.loading++;
this.loading_canceled = false; this.loading_canceled = false;
try { try {
let more = []; let more = [];
let last_start_time = this.time_range[0]; 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( this.update_time_range_from_messages(
more.filter((x) => x.timestamp < last_start_time) more.filter((x) => x.timestamp < last_start_time)
); );
@ -311,7 +392,7 @@ class TfTabNewsFeedElement extends LitElement {
this.messages = this.merge_messages(this.messages, messages); this.messages = this.merge_messages(this.messages, messages);
this.time_loading = undefined; this.time_loading = undefined;
console.log( 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`
); );
} }
@ -350,9 +431,16 @@ class TfTabNewsFeedElement extends LitElement {
if (!this.hash.startsWith('#%')) { if (!this.hash.startsWith('#%')) {
more = html` more = html`
<p> <p>
<button class="w3-button w3-theme-d1" @click=${this.mark_all_read}> ${this.unread_allowed()
Mark All Read ? html`
</button> <button
class="w3-button w3-theme-d1"
@click=${this.mark_all_read}
>
Mark All Read
</button>
`
: undefined}
<button <button
?disabled=${this.loading} ?disabled=${this.loading}
class="w3-button w3-theme-d1" class="w3-button w3-theme-d1"
@ -384,9 +472,14 @@ class TfTabNewsFeedElement extends LitElement {
`; `;
} }
return cache(html` return cache(html`
<button class="w3-button w3-theme-d1" @click=${this.mark_all_read}> ${this.unread_allowed()
Mark All Read ? html`<button
</button> class="w3-button w3-theme-d1"
@click=${this.mark_all_read}
>
Mark All Read
</button>`
: undefined}
<tf-news <tf-news
id="news" id="news"
whoami=${this.whoami} whoami=${this.whoami}
@ -395,8 +488,10 @@ class TfTabNewsFeedElement extends LitElement {
.following=${this.following} .following=${this.following}
.drafts=${this.drafts} .drafts=${this.drafts}
.expanded=${this.expanded} .expanded=${this.expanded}
hash=${this.hash}
channel=${this.channel()} channel=${this.channel()}
channel_unread=${this.channels_unread?.[this.channel()]} channel_unread=${this.channels_unread?.[this.channel()]}
.recent_reactions=${this.recent_reactions}
></tf-news> ></tf-news>
${more} ${more}
`); `);

View File

@ -24,6 +24,8 @@ class TfTabNewsElement extends LitElement {
channels_latest: {type: Object}, channels_latest: {type: Object},
connections: {type: Array}, connections: {type: Array},
private_messages: {type: Array}, private_messages: {type: Array},
recent_reactions: {type: Array},
peer_exchange: {type: Boolean},
}; };
} }
@ -43,9 +45,11 @@ class TfTabNewsElement extends LitElement {
this.channels_latest = {}; this.channels_latest = {};
this.channels = []; this.channels = [];
this.connections = []; this.connections = [];
this.recent_reactions = [];
tfrpc.rpc.localStorageGet('drafts').then(function (d) { tfrpc.rpc.localStorageGet('drafts').then(function (d) {
self.drafts = JSON.parse(d || '{}'); self.drafts = JSON.parse(d || '{}');
}); });
this.check_peer_exchange();
} }
connectedCallback() { connectedCallback() {
@ -58,6 +62,14 @@ class TfTabNewsElement extends LitElement {
document.body.removeEventListener('keypress', this.on_keypress.bind(this)); document.body.removeEventListener('keypress', this.on_keypress.bind(this));
} }
async check_peer_exchange() {
if (await tfrpc.rpc.isAdministrator()) {
this.peer_exchange = await tfrpc.rpc.globalSettingsGet('peer_exchange');
} else {
this.peer_exchange = undefined;
}
}
load_latest() { load_latest() {
let news = this.shadowRoot?.getElementById('news'); let news = this.shadowRoot?.getElementById('news');
if (news) { if (news) {
@ -95,7 +107,13 @@ class TfTabNewsElement extends LitElement {
} }
unread_status(channel) { 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] &&
this.channels_latest[channel] > 0 && this.channels_latest[channel] > 0 &&
(this.channels_unread[channel] === undefined || (this.channels_unread[channel] === undefined ||
@ -156,6 +174,15 @@ class TfTabNewsElement extends LitElement {
.map((x) => x[0]); .map((x) => x[0]);
} }
refresh() {
tfrpc.rpc.sync();
}
async enable_peer_exchange() {
await tfrpc.rpc.globalSettingsSet('peer_exchange', true);
await this.check_peer_exchange();
}
render_sidebar() { render_sidebar() {
return html` return html`
<div <div
@ -194,6 +221,12 @@ class TfTabNewsElement extends LitElement {
style=${this.hash == '#@' ? 'font-weight: bold' : undefined} style=${this.hash == '#@' ? 'font-weight: bold' : undefined}
>${this.unread_status('@')}@mentions</a >${this.unread_status('@')}@mentions</a
> >
<a
href="#👍"
class="w3-bar-item w3-button"
style=${this.hash == '#👍' ? 'font-weight: bold' : undefined}
>${this.unread_status('👍')}👍votes</a
>
<a <a
href="#🔐" href="#🔐"
class="w3-bar-item w3-button" class="w3-bar-item w3-button"
@ -223,15 +256,44 @@ 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)?.length == 0
? html`
<button
class=${'w3-bar-item w3-button' +
(this.connections?.some((x) => x.flags.one_shot)
? ' w3-spin'
: '')}
@click=${this.refresh}
>
↻ Sync now
</button>
<button
class=${'w3-bar-item w3-button' +
(this.peer_exchange !== false ? ' w3-hide' : '')}
@click=${this.enable_peer_exchange}
>
Enable peer exchange
</button>
`
: undefined}
${this.connections ${this.connections
.filter((x) => x.id && !x.destroy_reason) .filter((x) => x.id)
.map( .map(
(x) => html` (x) => html`
<tf-user <tf-user
class="w3-bar-item" class="w3-bar-item"
style="max-width: 100%" style=${x.destroy_reason
? 'border-left: 4px solid red; border-right: 4px solid red'
: x.connected
? x.flags?.one_shot
? 'border-left: 4px solid blue; border-right: 4px solid blue'
: 'border-left: 4px solid green; border-right: 4px solid green'
: ''}
id=${x.id} id=${x.id}
fallback_name=${x.host}
.users=${this.users} .users=${this.users}
></tf-user> ></tf-user>
` `
@ -285,7 +347,7 @@ class TfTabNewsElement extends LitElement {
return cache(html` return cache(html`
${this.render_sidebar()} ${this.render_sidebar()}
<div <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" id="main"
class="w3-main" class="w3-main"
> >
@ -310,7 +372,7 @@ class TfTabNewsElement extends LitElement {
class="w3-button w3-hide-large" class="w3-button w3-hide-large"
@click=${this.show_sidebar} @click=${this.show_sidebar}
> >
&#9776; ${this.unread_status()}&#9776;
</div> </div>
Welcome, <tf-user id=${this.whoami} .users=${this.users}></tf-user>! Welcome, <tf-user id=${this.whoami} .users=${this.users}></tf-user>!
${edit_profile} ${edit_profile}
@ -339,6 +401,7 @@ class TfTabNewsElement extends LitElement {
.channels_unread=${this.channels_unread} .channels_unread=${this.channels_unread}
.channels_latest=${this.channels_latest} .channels_latest=${this.channels_latest}
.private_messages=${this.private_messages} .private_messages=${this.private_messages}
.recent_reactions=${this.recent_reactions}
></tf-tab-news-feed> ></tf-tab-news-feed>
</div> </div>
</div> </div>

View File

@ -6,6 +6,7 @@ class TfUserElement extends LitElement {
static get properties() { static get properties() {
return { return {
id: {type: String}, id: {type: String},
fallback_name: {type: String},
users: {type: Object}, users: {type: Object},
}; };
} }
@ -15,6 +16,7 @@ class TfUserElement extends LitElement {
constructor() { constructor() {
super(); super();
this.id = null; this.id = null;
this.fallback_name = null;
this.users = {}; this.users = {};
} }
@ -31,13 +33,16 @@ class TfUserElement extends LitElement {
>`; >`;
let name = this.users?.[this.id]?.name; let name = this.users?.[this.id]?.name;
name = html`<a target="_top" href=${'#' + this.id} name = html`<a target="_top" href=${'#' + this.id}
>${name !== undefined ? name : this.id}</a >${name ?? this.fallback_name ?? this.id}</a
>`; >`;
if (user) { if (user) {
let image_link = user.image; let image_link = user.image;
image_link = if (typeof image_link == 'string' && !image_link.startsWith('&')) {
typeof image_link == 'string' ? image_link : image_link?.link; try {
image_link = JSON.parse(image_link)?.link;
} catch {}
}
if (image_link !== undefined) { if (image_link !== undefined) {
image = html`<img image = html`<img
class=${'w3-theme-l4 ' + shape} class=${'w3-theme-l4 ' + shape}

View File

@ -1,5 +1,5 @@
{ {
"type": "tildefriends-app", "type": "tildefriends-app",
"emoji": "💾", "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() { async function get_biggest() {
return query(` 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() { async function main() {
await app.setDocument( await app.setDocument('<p style="color: #fff">Analyzing feeds...</p>');
'<p style="color: #fff">Finding the top 10 largest feeds...</p>' let most_follows = get_most_follows();
);
let most_follows = await get_most_follows();
let total = await get_total(); let total = await get_total();
let identities = await ssb.getAllIdentities(); let identities = await ssb.getAllIdentities();
let following1 = await ssb.following(identities, 1); let following1 = await ssb.following(identities, 1);
let following2 = await ssb.following(identities, 2); let following2 = await ssb.following(identities, 2);
let biggest = await get_biggest(); let biggest = await get_biggest();
most_follows = await most_follows;
let names = await get_names( let names = await get_names(
[].concat( [].concat(
biggest.map((x) => x.author), biggest.map((x) => x.author),
@ -94,7 +93,7 @@ async function main() {
} }
let html = `<body style="color: #000; background-color: #ddd">\n let html = `<body style="color: #000; background-color: #ddd">\n
<h1>Storage Summary</h1> <h1>Storage Summary</h1>
<h2>Top 10 Accounts by Size</h2> <h2>Top Accounts by Size</h2>
<ol>`; <ol>`;
for (let item of biggest) { for (let item of biggest) {
html += `<li> html += `<li>
@ -105,7 +104,7 @@ async function main() {
} }
html += ` html += `
</ol> </ol>
<h2>Top 10 Accounts by Follows</h2> <h2>Top Accounts by Follows</h2>
<ol>`; <ol>`;
for (let item of most_follows) { for (let item of most_follows) {
html += `<li> 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", "type": "tildefriends-app",
"emoji": "👋", "emoji": "👋",
"previous": "&wAb7J6E35xEXpiXsQ6t1RaWTGIvlatUnyH8ipF6pVic=.sha256" "previous": "&fY3YUKPuH/wqOgKPVNJu1vWEHCXf5fToL2qiVXMRmxc=.sha256"
} }

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 86 KiB

View File

@ -28,36 +28,25 @@
<b>😎 Tilde Friends</b> <b>😎 Tilde Friends</b>
</h1> </h1>
<h1 class="w3-xxlarge w3-text-green"> <h1 class="w3-xxlarge w3-text-green">
<b <b>a Secure Scuttlebutt decentralized social network client</b>
>the Secure Scuttlebutt decentralized social network client that's
<i>fancy🎩</i></b
>
</h1> </h1>
<p> <p>
In addition to participating in Secure Scuttlebutt, Tilde Friends is In addition to participating in Secure Scuttlebutt, Tilde Friends is
a platform for building, running, and sharing applications. a platform for building, running, and sharing applications.
</p> </p>
<p> <a
Available for lots of devices: class="w3-button w3-blue w3-padding-large"
<i class="fa-brands fa-linux w3-xlarge"></i> href="https://www.tildefriends.net/~core/ssb/"
<i class="fa-brands fa-android w3-xlarge"></i> >🦀 Try It</a
<i class="fa-brands fa-apple w3-xlarge"></i> >
<i class="fa fa-mobile-screen w3-xlarge"></i>
<i class="fa-brands fa-windows w3-xlarge"></i>
</p>
<a <a
class="w3-button w3-black w3-padding-large" class="w3-button w3-black w3-padding-large"
href="https://dev.tildefriends.net/cory/tildefriends/releases" href="https://dev.tildefriends.net/cory/tildefriends/releases/latest"
><i class="fa fa-download"></i> Download</a ><i class="fa fa-download"></i> Download</a
> >
<a <a
class="w3-button w3-black w3-padding-large" class="w3-button w3-black w3-padding-large"
href="https://www.tildefriends.net/~core/ssb/" href="https://dev.tildefriends.net/cory/tildefriends"
><i class="fa fa-link"></i> Try It</a
>
<a
class="w3-button w3-black w3-padding-large"
href="https://dev.tildefriends.net/"
><i class="fa fa-mug-hot"></i> Development</a ><i class="fa fa-mug-hot"></i> Development</a
> >
<a <a
@ -65,28 +54,11 @@
href="https://docs.tildefriends.net/" href="https://docs.tildefriends.net/"
><i class="fa fa-book"></i> Documentation</a ><i class="fa fa-book"></i> Documentation</a
> >
<p> <a
<a class="w3-button w3-black w3-padding-large"
class="w3-button w3-round-large w3-padding w3-blue-gray w3-margin-top" href="https://www.tildefriends.net/~cory/tildeblog/"
href="https://f-droid.org/en/packages/com.unprompted.tildefriends.fdroid/" ><i class="fa fa-solid fa-square-rss"></i> Blog</a
><img src="f-droid.svg" style="height: 2em; margin: 0" /> Get it >
on F-Droid</a
>
<a
class="w3-button w3-round-large w3-padding w3-blue-gray w3-margin-top"
href="https://dev.tildefriends.net/releases/tildefriends-x86_64.AppImage"
>
<img src="appimage.svg" style="height: 2em; margin: 0" />
Get Linux 64-bit AppImage
</a>
<a
class="w3-button w3-round-large w3-padding w3-blue-gray w3-margin-top"
href="https://play.google.com/store/apps/details?id=com.unprompted.tildefriends"
>
<img src="googleplay.svg" style="height: 2em; margin: 0" />
Get it on Google Play (Open Testing)
</a>
</p>
</div> </div>
<div class="w3-col l4 m6"> <div class="w3-col l4 m6">
<img src="tildefriends.png" class="w3-image w3-right w3-hide-small" /> <img src="tildefriends.png" class="w3-image w3-right w3-hide-small" />
@ -111,8 +83,100 @@
<a href="https://www.tildefriends.net/" <a href="https://www.tildefriends.net/"
>https://www.tildefriends.net/</a >https://www.tildefriends.net/</a
>. >.
<div class="w3-cell-row">
<div class="w3-container w3-cell">
<h3>Mobile</h3>
<p>
<a
class="w3-button w3-round-large w3-padding w3-blue-gray w3-margin-top"
href="https://f-droid.org/en/packages/com.unprompted.tildefriends.fdroid/"
><img src="f-droid.svg" style="height: 2em; margin: 0" />
Get it on F-Droid</a
>
<a
class="w3-button w3-round-large w3-padding w3-blue-gray w3-margin-top"
href="https://play.google.com/store/apps/details?id=com.unprompted.tildefriends"
>
<img
src="googleplay.svg"
style="height: 2em; margin: 0"
/>
Get it on Google Play (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>
<p>Just launch the app.</p>
</div>
<div class="w3-container w3-cell">
<h3>Web</h3>
<p>
<a
class="w3-button w3-round-large w3-blue w3-padding-large"
href="https://www.tildefriends.net/~core/ssb/"
>🦀 Try It</a
>
</p>
<p>
<a href="/login?return=/~core/intro"
>Register an account with tildefriends.net</a
>
to take it for a spin right away.
</p>
</div>
<div class="w3-container w3-cell">
<h3>Desktop</h3>
<p>
<a
class="w3-button w3-round-large w3-black w3-padding-large"
href="https://dev.tildefriends.net/cory/tildefriends/releases"
><i class="fa fa-download"></i> Download</a
>
<a
class="w3-button w3-round-large w3-padding w3-blue-gray"
href="https://dev.tildefriends.net/releases/tildefriends-x86_64.AppImage"
>
<img src="appimage.svg" style="height: 2em; margin: 0" />
Get Linux 64-bit AppImage
</a>
</p>
<p>
Tilde Friends is distributed as a single executable file (or
source that you can
<a href="http://dev.tildefriends.net">build yourself</a>)
and stores all of its data in a single
file(<code>db.sqlite</code>). You can generally download the
latest executable from
<a
href="https://dev.tildefriends.net/cory/tildefriends/releases"
>releases</a
>
for your platform, mark it as executable (<code
>chmod +x tildefriends*</code
>
on macOS and Linux), and run. Run with <code>-h</code> to
learn more.
</p>
<p>
Tilde Friends will run in the console and provide a web
interface at
<a href="http://localhost:12345/">http://localhost:12345/</a
>. You will have to register a username and password to sign
into your instance.
</p>
</div>
</div>
<p>
After a <a href="/~core/intro">brief introduction</a>, Tilde
Friends will take you to the Secure Scuttlebutt social network
app.
</p>
</li> </li>
<li>Create an account to identify yourself with that instance.</li>
<li> <li>
Describe yourself in your profile in the <b>ssb</b> app. Give Describe yourself in your profile in the <b>ssb</b> app. Give
yourself a name and an avatar if you like. yourself a name and an avatar if you like.
@ -146,11 +210,11 @@
<!-- SSB Section --> <!-- SSB Section -->
<div class="w3-light-grey"> <div class="w3-light-grey">
<div class="w3-row-padding w3-padding-64"> <div class="w3-row-padding w3-padding-64">
<div class="w3-col l4 m6 s4"> <div class="w3-col l4 m6 s4 w3-center">
<a href="https://scuttlebutt.nz/" <a href="https://scuttlebutt.nz/"
><img ><img
class="w3-image w3-round-large" class="w3-image"
src="ssb.png" src="hermietildefriends.svg"
alt="Secure Scuttlebutt" alt="Secure Scuttlebutt"
/></a> /></a>
</div> </div>

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: 50 KiB

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} html{box-sizing:border-box}*,*:before,*:after{box-sizing:inherit}
/* Extract from normalize.css by Nicolas Gallagher and Jonathan Neal git.io/normalize */ /* 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} 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-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-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-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,.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-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-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-button:hover{color:#000!important;background-color:#ccc!important}
.w3-transparent,.w3-hover-none:hover{background-color:transparent!important} .w3-transparent,.w3-hover-none:hover{background-color:transparent!important}
.w3-hover-none:hover{box-shadow:none!important} .w3-hover-none:hover{box-shadow:none!important}
.w3-rtl{direction:rtl}.w3-ltr{direction:ltr}
/* Colors */ /* Colors */
.w3-amber,.w3-hover-amber:hover{color:#000!important;background-color:#ffc107!important} .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-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-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-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-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-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-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-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-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-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-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'; import * as core from './core.js';
let g_next_id = 1;
let g_calls = {};
let gSessionIndex = 0; let gSessionIndex = 0;
/**
* TODOC
* @returns
*/
function makeSessionId() {
return 'session_' + (gSessionIndex++).toString();
}
/**
* TODOC
* @returns
*/
function App() { function App() {
this._on_output = null;
this._send_queue = []; this._send_queue = [];
this.calls = {};
this._next_call_id = 1;
return this; return this;
} }
/**
* TODOC
* @param {*} callback
*/
App.prototype.readOutput = function (callback) {
this._on_output = callback;
};
/**
* TODOC
* @param {*} api
* @returns
*/
App.prototype.makeFunction = function (api) { App.prototype.makeFunction = function (api) {
let self = this; let self = this;
let result = function () { let result = function () {
let id = g_next_id++; let id = self._next_call_id++;
while (!id || g_calls[id]) { while (!id || self.calls[id]) {
id = g_next_id++; id = self._next_call_id++;
} }
let promise = new Promise(function (resolve, reject) { let promise = new Promise(function (resolve, reject) {
g_calls[id] = {resolve: resolve, reject: reject}; self.calls[id] = {resolve: resolve, reject: reject};
}); });
let message = { let message = {
message: 'tfrpc', action: 'tfrpc',
method: api[0], method: api[0],
params: [...arguments], params: [...arguments],
id: id, id: id,
@ -59,10 +32,6 @@ App.prototype.makeFunction = function (api) {
return result; return result;
}; };
/**
* TODOC
* @param {*} message
*/
App.prototype.send = function (message) { App.prototype.send = function (message) {
if (this._send_queue) { if (this._send_queue) {
if (this._on_output) { 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) { exports.app_socket = async function socket(request, response) {
let process; let process;
let options = {}; let options = {};
@ -102,10 +66,16 @@ exports.app_socket = async function socket(request, response) {
try { try {
message = JSON.parse(event.data); message = JSON.parse(event.data);
} catch (error) { } catch (error) {
print('ERROR', error, event.data, event.data.length, event.opCode); print(
'WebSocket error:',
error,
event.data,
event.data.length,
event.opCode
);
return; return;
} }
if (message.action == 'hello') { if (!process && message.action == 'hello') {
let packageOwner; let packageOwner;
let packageName; let packageName;
let blobId; let blobId;
@ -122,7 +92,7 @@ exports.app_socket = async function socket(request, response) {
if (!blobId) { if (!blobId) {
response.send( response.send(
JSON.stringify({ JSON.stringify({
message: 'tfrpc', action: 'tfrpc',
method: 'error', method: 'error',
params: [message.path + ' not found'], params: [message.path + ' not found'],
id: -1, id: -1,
@ -163,7 +133,7 @@ exports.app_socket = async function socket(request, response) {
options.packageOwner = packageOwner; options.packageOwner = packageOwner;
options.packageName = packageName; options.packageName = packageName;
options.url = message.url; options.url = message.url;
let sessionId = makeSessionId(); let sessionId = 'session_' + (gSessionIndex++).toString();
if (blobId) { if (blobId) {
if (message.edit_only) { if (message.edit_only) {
response.send( response.send(
@ -175,9 +145,24 @@ exports.app_socket = async function socket(request, response) {
} }
} }
if (process) { 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); response.send(JSON.stringify(message), 0x1);
});
process.app.send(); process.app.send();
} }
@ -206,26 +191,13 @@ exports.app_socket = async function socket(request, response) {
if (process && process.timeout > 0) { if (process && process.timeout > 0) {
setTimeout(ping, process.timeout); 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 { } else {
if (process && process.eventHandlers['message']) { if (process) {
await core.invoke(process.eventHandlers['message'], [message]); 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) { } else if (event.opCode == 0x8) {

View File

@ -8,6 +8,7 @@ let gFiles = {};
let gApp = {files: {}, emoji: '📦'}; let gApp = {files: {}, emoji: '📦'};
let gEditor; let gEditor;
let gOriginalInput; let gOriginalInput;
let gUnloading;
let kErrorColor = '#dc322f'; let kErrorColor = '#dc322f';
let kDisconnectColor = '#f00'; let kDisconnectColor = '#f00';
@ -266,6 +267,7 @@ class TfNavigationElement extends LitElement {
<button <button
@click=${() => this.reset_permission(key)} @click=${() => this.reset_permission(key)}
class="w3-button w3-red" class="w3-button w3-red"
id=${'permission_reset:' + key}
> >
Reset Reset
</button> </button>
@ -275,6 +277,7 @@ class TfNavigationElement extends LitElement {
<button <button
@click=${() => (this.show_permissions = false)} @click=${() => (this.show_permissions = false)}
class="w3-button w3-blue" class="w3-button w3-blue"
id="permissions_close"
> >
Close Close
</button> </button>
@ -409,6 +412,7 @@ class TfFilesElement extends LitElement {
current: {type: String}, current: {type: String},
files: {type: Object}, files: {type: Object},
dropping: {type: Number}, dropping: {type: Number},
drop_target: {type: String},
}; };
} }
@ -447,6 +451,9 @@ class TfFilesElement extends LitElement {
if (!this.files[file].clean) { if (!this.files[file].clean) {
classes.push('dirty'); classes.push('dirty');
} }
if (this.drop_target == file) {
classes.push('drop');
}
return html`<div return html`<div
class="${classes.join(' ')}" class="${classes.join(' ')}"
@click=${(x) => this.file_click(file)} @click=${(x) => this.file_click(file)}
@ -463,11 +470,12 @@ class TfFilesElement extends LitElement {
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();
this.dropping = 0; this.dropping = 0;
this.drop_target = undefined;
for (let file of event.dataTransfer.files) { for (let file of event.dataTransfer.files) {
let buffer = await file.arrayBuffer(); let buffer = await file.arrayBuffer();
let text = new TextDecoder('latin1').decode(buffer); let text = new TextDecoder('latin1').decode(buffer);
gFiles[file.name] = { gFiles[file.name] = {
doc: new cm6.EditorState.create({ doc: cm6.EditorState.create({
doc: text, doc: text,
extensions: cm6.extensions, extensions: cm6.extensions,
}), }),
@ -486,6 +494,7 @@ class TfFilesElement extends LitElement {
*/ */
drag_enter(event) { drag_enter(event) {
this.dropping++; this.dropping++;
this.drop_target = event.srcElement.innerText.trim();
event.preventDefault(); event.preventDefault();
} }
@ -495,6 +504,13 @@ class TfFilesElement extends LitElement {
*/ */
drag_leave(event) { drag_leave(event) {
this.dropping--; this.dropping--;
if (this.dropping == 0) {
this.drop_target = undefined;
}
}
drag_over(event) {
event.preventDefault();
} }
/** /**
@ -521,6 +537,10 @@ class TfFilesElement extends LitElement {
background-color: #2aa198; background-color: #2aa198;
} }
div.file.drop {
border: 4px solid red;
}
div.file.dirty::after { div.file.dirty::after {
content: '*'; content: '*';
} }
@ -529,20 +549,12 @@ class TfFilesElement extends LitElement {
@drop=${this.drop} @drop=${this.drop}
@dragenter=${this.drag_enter} @dragenter=${this.drag_enter}
@dragleave=${this.drag_leave} @dragleave=${this.drag_leave}
@dragover=${this.drag_over}
> >
${Object.keys(this.files) ${Object.keys(this.files)
.sort() .sort()
.map((x) => self.render_file(x))} .map((x) => self.render_file(x))}
</div> </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 +1337,7 @@ function _receive_websocket_message(message) {
line.append(key, message.stats[key]); 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 api = k_api[message.method];
let id = message.id; let id = message.id;
let params = message.params; let params = message.params;
@ -1333,14 +1345,14 @@ function _receive_websocket_message(message) {
Promise.resolve(api.func(...params)) Promise.resolve(api.func(...params))
.then(function (result) { .then(function (result) {
send({ send({
message: 'tfrpc', action: 'tfrpc',
id: id, id: id,
result: result, result: result,
}); });
}) })
.catch(function (error) { .catch(function (error) {
send({ send({
message: 'tfrpc', action: 'tfrpc',
id: id, id: id,
error: error, error: error,
}); });
@ -1549,27 +1561,31 @@ function connectSocket(path) {
_receive_websocket_message(JSON.parse(event.data)); _receive_websocket_message(JSON.parse(event.data));
}; };
gSocket.onclose = function (event) { gSocket.onclose = function (event) {
const k_codes = { if (gUnloading) {
1000: 'Normal closure', setStatusMessage('⚪ Closing...', kStatusColor);
1001: 'Going away', } else {
1002: 'Protocol error', const k_codes = {
1003: 'Unsupported data', 1000: 'Normal closure',
1005: 'No status received', 1001: 'Going away',
1006: 'Abnormal closure', 1002: 'Protocol error',
1007: 'Invalid frame payload data', 1003: 'Unsupported data',
1008: 'Policy violation', 1005: 'No status received',
1009: 'Message too big', 1006: 'Abnormal closure',
1010: 'Missing extension', 1007: 'Invalid frame payload data',
1011: 'Internal error', 1008: 'Policy violation',
1012: 'Service restart', 1009: 'Message too big',
1013: 'Try again later', 1010: 'Missing extension',
1014: 'Bad gateway', 1011: 'Internal error',
1015: 'TLS handshake', 1012: 'Service restart',
}; 1013: 'Try again later',
setStatusMessage( 1014: 'Bad gateway',
'🔴 Closed: ' + (k_codes[event.code] || event.code), 1015: 'TLS handshake',
kDisconnectColor };
); setStatusMessage(
'🔴 Closed: ' + (k_codes[event.code] || event.code),
kDisconnectColor
);
}
}; };
} }
} }
@ -1843,6 +1859,9 @@ window.addEventListener('load', function () {
window.addEventListener('blur', blur); window.addEventListener('blur', blur);
window.addEventListener('message', message, false); window.addEventListener('message', message, false);
window.addEventListener('online', connectSocket); window.addEventListener('online', connectSocket);
window.addEventListener('beforeunload', function () {
gUnloading = true;
});
document.getElementById('name').value = window.location.pathname; document.getElementById('name').value = window.location.pathname;
document document
.getElementById('closeEditor') .getElementById('closeEditor')

View File

@ -3,12 +3,10 @@ import * as http from './http.js';
let gProcesses = {}; let gProcesses = {};
let gStatsTimer = false; let gStatsTimer = false;
let kPingInterval = 60 * 1000; let g_handler_index = 0;
const k_ping_interval = 60 * 1000;
/**
* TODOC
* @param {*} error
*/
function printError(error) { function printError(error) {
if (error.stackTrace) { if (error.stackTrace) {
print(error.fileName + ':' + error.lineNumber + ': ' + error.message); print(error.fileName + ':' + error.lineNumber + ': ' + error.message);
@ -21,12 +19,6 @@ function printError(error) {
} }
} }
/**
* TODOC
* @param {*} handlers
* @param {*} argv
* @returns
*/
function invoke(handlers, argv) { function invoke(handlers, argv) {
let promises = []; let promises = [];
if (handlers) { if (handlers) {
@ -47,12 +39,6 @@ function invoke(handlers, argv) {
return Promise.all(promises); return Promise.all(promises);
} }
/**
* TODOC
* @param {*} eventName
* @param {*} argv
* @returns
*/
function broadcastEvent(eventName, argv) { function broadcastEvent(eventName, argv) {
let promises = []; let promises = [];
for (let process of Object.values(gProcesses)) { for (let process of Object.values(gProcesses)) {
@ -63,11 +49,6 @@ function broadcastEvent(eventName, argv) {
return Promise.all(promises); return Promise.all(promises);
} }
/**
* TODOC
* @param {*} message
* @returns
*/
function broadcast(message) { function broadcast(message) {
let sender = this; let sender = this;
let promises = []; let promises = [];
@ -84,12 +65,6 @@ function broadcast(message) {
return Promise.all(promises); return Promise.all(promises);
} }
/**
* TODOC
* @param {String} eventName
* @param {*} argv
* @returns
*/
function broadcastAppEventToUser( function broadcastAppEventToUser(
user, user,
packageOwner, packageOwner,
@ -112,12 +87,6 @@ function broadcastAppEventToUser(
return Promise.all(promises); return Promise.all(promises);
} }
/**
* TODOC
* @param {*} caller
* @param {*} process
* @returns
*/
function getUser(caller, process) { function getUser(caller, process) {
return { return {
key: process.key, key: process.key,
@ -128,12 +97,6 @@ function getUser(caller, process) {
}; };
} }
/**
* TODOC
* @param {*} user
* @param {*} process
* @returns
*/
async function getApps(user, process) { async function getApps(user, process) {
if ( if (
process.credentials && process.credentials &&
@ -160,28 +123,13 @@ async function getApps(user, process) {
return {}; return {};
} }
/**
* TODOC
* @param {*} from
* @param {*} to
* @param {*} message
* @returns
*/
function postMessageInternal(from, to, message) { function postMessageInternal(from, to, message) {
if (to.eventHandlers['message']) { if (to.eventHandlers['message']) {
return invoke(to.eventHandlers['message'], [getUser(from, from), message]); return invoke(to.eventHandlers['message'], [getUser(from, from), message]);
} }
} }
/**
* TODOC
* @param {*} blobId
* @param {*} key
* @param {*} options
* @returns
*/
async function getProcessBlob(blobId, key, options) { async function getProcessBlob(blobId, key, options) {
// TODO(tasiaiso): break this down ?
let process = gProcesses[key]; let process = gProcesses[key];
if (!process && !(options && 'create' in options && !options.create)) { if (!process && !(options && 'create' in options && !options.create)) {
let resolveReady; let resolveReady;
@ -200,7 +148,7 @@ async function getProcessBlob(blobId, key, options) {
} }
process.lastActive = Date.now(); process.lastActive = Date.now();
process.lastPing = null; process.lastPing = null;
process.timeout = kPingInterval; process.timeout = k_ping_interval;
process.ready = new Promise(function (resolve, reject) { process.ready = new Promise(function (resolve, reject) {
resolveReady = resolve; resolveReady = resolve;
rejectReady = reject; rejectReady = reject;
@ -648,16 +596,24 @@ async function getProcessBlob(blobId, key, options) {
permissions: await imports.core.permissionsGranted(), permissions: await imports.core.permissionsGranted(),
}); });
}; };
process.resetPermission = async function resetPermission(permission) { process.client_api = {
let user = process?.credentials?.session?.name; createIdentity: function () {
await ssb.setUserPermission( return process.createIdentity();
user, },
options?.packageOwner, resetPermission: async function resetPermission(message) {
options?.packageName, let user = process?.credentials?.session?.name;
permission, await ssb.setUserPermission(
undefined user,
); options?.packageOwner,
return process.sendPermissions(); options?.packageName,
message.permission,
undefined
);
return process.sendPermissions();
},
setActiveIdentity: function setActiveIdentity(message) {
return process.setActiveIdentity(message.identity);
},
}; };
ssb.registerImports(imports, process); ssb.registerImports(imports, process);
process.task.setImports(imports); process.task.setImports(imports);
@ -723,9 +679,6 @@ ssb.addEventListener('connections', function () {
broadcastEvent('onConnectionsChanged', []); broadcastEvent('onConnectionsChanged', []);
}); });
/**
* TODOC
*/
async function loadSettings() { async function loadSettings() {
let data = {}; let data = {};
try { try {
@ -744,9 +697,6 @@ async function loadSettings() {
return data; return data;
} }
/**
* TODOC
*/
function sendStats() { function sendStats() {
let apps = Object.values(gProcesses) let apps = Object.values(gProcesses)
.filter((process) => process.app) .filter((process) => process.app)
@ -762,8 +712,6 @@ function sendStats() {
} }
} }
let g_handler_index = 0;
exports.callAppHandler = async function callAppHandler( exports.callAppHandler = async function callAppHandler(
response, response,
app_blob_id, 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} html{box-sizing:border-box}*,*:before,*:after{box-sizing:inherit}
/* Extract from normalize.css by Nicolas Gallagher and Jonathan Neal git.io/normalize */ /* 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} 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-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-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-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,.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-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-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-button:hover{color:#000!important;background-color:#ccc!important}
.w3-transparent,.w3-hover-none:hover{background-color:transparent!important} .w3-transparent,.w3-hover-none:hover{background-color:transparent!important}
.w3-hover-none:hover{box-shadow:none!important} .w3-hover-none:hover{box-shadow:none!important}
.w3-rtl{direction:rtl}.w3-ltr{direction:ltr}
/* Colors */ /* Colors */
.w3-amber,.w3-hover-amber:hover{color:#000!important;background-color:#ffc107!important} .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-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-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-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-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-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-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-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-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-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-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

@ -25,14 +25,14 @@
}: }:
pkgs.stdenv.mkDerivation rec { pkgs.stdenv.mkDerivation rec {
pname = "tildefriends"; pname = "tildefriends";
version = "0.0.28"; version = "0.0.31";
src = pkgs.fetchFromGitea { src = pkgs.fetchFromGitea {
domain = "dev.tildefriends.net"; domain = "dev.tildefriends.net";
owner = "cory"; owner = "cory";
repo = "tildefriends"; repo = "tildefriends";
rev = "v${version}"; rev = "v${version}";
hash = "sha256-vcLJCXgIrjC37t9oavK2QMRcMJljghuzKDYxQ4nyTcE="; hash = "sha256-c2ZKVNikI5jN5GQuvp7S53qqnRZniSrJMF1FUZdVNPI=";
fetchSubmodules = true; fetchSubmodules = true;
}; };

2
deps/c-ares vendored

File diff suppressed because one or more lines are too long

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

@ -30,9 +30,9 @@
} }
}, },
"node_modules/@codemirror/commands": { "node_modules/@codemirror/commands": {
"version": "6.8.0", "version": "6.8.1",
"resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.8.0.tgz", "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.8.1.tgz",
"integrity": "sha512-q8VPEFaEP4ikSlt6ZxjB3zW72+7osfAYW9i8Zu943uqbKuz6utc1+F170hyLUCUltXORjQXRyYQNfkckzA/bPQ==", "integrity": "sha512-KlGVYufHMQzxbdQONiLyGQDUW0itrLZwq3CcY7xpv9ZLRHqzkBSoteocBHtMCoY7/Ci4xhzSrToIeLg7FxHuaw==",
"dependencies": { "dependencies": {
"@codemirror/language": "^6.0.0", "@codemirror/language": "^6.0.0",
"@codemirror/state": "^6.4.0", "@codemirror/state": "^6.4.0",
@ -69,9 +69,9 @@
} }
}, },
"node_modules/@codemirror/lang-javascript": { "node_modules/@codemirror/lang-javascript": {
"version": "6.2.3", "version": "6.2.4",
"resolved": "https://registry.npmjs.org/@codemirror/lang-javascript/-/lang-javascript-6.2.3.tgz", "resolved": "https://registry.npmjs.org/@codemirror/lang-javascript/-/lang-javascript-6.2.4.tgz",
"integrity": "sha512-8PR3vIWg7pSu7ur8A07pGiYHgy3hHj+mRYRCSG8q+mPIrl0F02rgpGv+DsQTHRTc30rydOsf5PZ7yjKFg2Ackw==", "integrity": "sha512-0WVmhp1QOqZ4Rt6GlVGwKJN3KW7Xh4H2q8ZZNGZaP6lRdxXJzmjm4FqvmOojVj6khWJHIb9sp7U/72W7xQgqAA==",
"dependencies": { "dependencies": {
"@codemirror/autocomplete": "^6.0.0", "@codemirror/autocomplete": "^6.0.0",
"@codemirror/language": "^6.6.0", "@codemirror/language": "^6.6.0",
@ -83,18 +83,18 @@
} }
}, },
"node_modules/@codemirror/lang-json": { "node_modules/@codemirror/lang-json": {
"version": "6.0.1", "version": "6.0.2",
"resolved": "https://registry.npmjs.org/@codemirror/lang-json/-/lang-json-6.0.1.tgz", "resolved": "https://registry.npmjs.org/@codemirror/lang-json/-/lang-json-6.0.2.tgz",
"integrity": "sha512-+T1flHdgpqDDlJZ2Lkil/rLiRy684WMLc74xUnjJH48GQdfJo/pudlTRreZmKwzP8/tGdKf83wlbAdOCzlJOGQ==", "integrity": "sha512-x2OtO+AvwEHrEwR0FyyPtfDUiloG3rnVTSZV1W8UteaLL8/MajQd8DpvUb2YVzC+/T18aSDv0H9mu+xw0EStoQ==",
"dependencies": { "dependencies": {
"@codemirror/language": "^6.0.0", "@codemirror/language": "^6.0.0",
"@lezer/json": "^1.0.0" "@lezer/json": "^1.0.0"
} }
}, },
"node_modules/@codemirror/language": { "node_modules/@codemirror/language": {
"version": "6.10.8", "version": "6.11.1",
"resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.10.8.tgz", "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.11.1.tgz",
"integrity": "sha512-wcP8XPPhDH2vTqf181U8MbZnW+tDyPYy0UzVOa+oHORjyT+mhhom9vBd7dApJwoDz9Nb/a8kHjJIsuA/t8vNFw==", "integrity": "sha512-5kS1U7emOGV84vxC+ruBty5sUgcD0te6dyupyRVG2zaSjhTDM73LhVKUtVwiqSe6QwmEoA4SCiU8AKPFyumAWQ==",
"dependencies": { "dependencies": {
"@codemirror/state": "^6.0.0", "@codemirror/state": "^6.0.0",
"@codemirror/view": "^6.23.0", "@codemirror/view": "^6.23.0",
@ -105,9 +105,9 @@
} }
}, },
"node_modules/@codemirror/lint": { "node_modules/@codemirror/lint": {
"version": "6.8.4", "version": "6.8.5",
"resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.8.4.tgz", "resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.8.5.tgz",
"integrity": "sha512-u4q7PnZlJUojeRe8FJa/njJcMctISGgPQ4PnWsd9268R4ZTtU+tfFYmwkBvgcrK2+QQ8tYFVALVb5fVJykKc5A==", "integrity": "sha512-s3n3KisH7dx3vsoeGMxsbRAgKe4O1vbrnKBClm99PU0fWxmxsx5rR2PfqQgIt+2MMJBHbiJ5rfIdLYfB9NNvsA==",
"dependencies": { "dependencies": {
"@codemirror/state": "^6.0.0", "@codemirror/state": "^6.0.0",
"@codemirror/view": "^6.35.0", "@codemirror/view": "^6.35.0",
@ -115,9 +115,9 @@
} }
}, },
"node_modules/@codemirror/search": { "node_modules/@codemirror/search": {
"version": "6.5.10", "version": "6.5.11",
"resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.5.10.tgz", "resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.5.11.tgz",
"integrity": "sha512-RMdPdmsrUf53pb2VwflKGHEe1XVM07hI7vV2ntgw1dmqhimpatSJKva4VA9h4TLUDOD4EIF02201oZurpnEFsg==", "integrity": "sha512-KmWepDE6jUdL6n8cAAqIpRmLPBZ5ZKnicE8oGU/s3QrAVID+0VhLFrzUucVKHG5035/BSykhExDL/Xm7dHthiA==",
"dependencies": { "dependencies": {
"@codemirror/state": "^6.0.0", "@codemirror/state": "^6.0.0",
"@codemirror/view": "^6.0.0", "@codemirror/view": "^6.0.0",
@ -133,9 +133,9 @@
} }
}, },
"node_modules/@codemirror/theme-one-dark": { "node_modules/@codemirror/theme-one-dark": {
"version": "6.1.2", "version": "6.1.3",
"resolved": "https://registry.npmjs.org/@codemirror/theme-one-dark/-/theme-one-dark-6.1.2.tgz", "resolved": "https://registry.npmjs.org/@codemirror/theme-one-dark/-/theme-one-dark-6.1.3.tgz",
"integrity": "sha512-F+sH0X16j/qFLMAfbciKTxVOwkdAS336b7AXTKOZhy8BR3eH/RelsnLgLFINrpST63mmN2OuwUt0W2ndUgYwUA==", "integrity": "sha512-NzBdIvEJmx6fjeremiGp3t/okrLPYT0d9orIc7AFun8oZcRk58aejkqhv6spnz4MLAevrKNPMQYXEWMg4s+sKA==",
"dependencies": { "dependencies": {
"@codemirror/language": "^6.0.0", "@codemirror/language": "^6.0.0",
"@codemirror/state": "^6.0.0", "@codemirror/state": "^6.0.0",
@ -144,11 +144,12 @@
} }
}, },
"node_modules/@codemirror/view": { "node_modules/@codemirror/view": {
"version": "6.36.3", "version": "6.37.2",
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.36.3.tgz", "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.37.2.tgz",
"integrity": "sha512-N2bilM47QWC8Hnx0rMdDxO2x2ImJ1FvZWXubwKgjeoOrWwEiFrtpA7SFHcuZ+o2Ze2VzbkgbzWVj4+V18LVkeg==", "integrity": "sha512-XD3LdgQpxQs5jhOOZ2HRVT+Rj59O4Suc7g2ULvZ+Yi8eCkickrkZ5JFuoDhs2ST1mNI5zSsNYgR3NGa4OUrbnw==",
"dependencies": { "dependencies": {
"@codemirror/state": "^6.5.0", "@codemirror/state": "^6.5.0",
"crelt": "^1.0.6",
"style-mod": "^4.1.0", "style-mod": "^4.1.0",
"w3c-keyname": "^2.2.4" "w3c-keyname": "^2.2.4"
} }
@ -217,13 +218,13 @@
"integrity": "sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA==" "integrity": "sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA=="
}, },
"node_modules/@lezer/css": { "node_modules/@lezer/css": {
"version": "1.1.10", "version": "1.2.1",
"resolved": "https://registry.npmjs.org/@lezer/css/-/css-1.1.10.tgz", "resolved": "https://registry.npmjs.org/@lezer/css/-/css-1.2.1.tgz",
"integrity": "sha512-V5/89eDapjeAkWPBpWEfQjZ1Hag3aYUUJOL8213X0dFRuXJ4BXa5NKl9USzOnaLod4AOpmVCkduir2oKwZYZtg==", "integrity": "sha512-2F5tOqzKEKbCUNraIXc0f6HKeyKlmMWJnBB0i4XW6dJgssrZO/YlZ2pY5xgyqDleqqhiNJ3dQhbrV2aClZQMvg==",
"dependencies": { "dependencies": {
"@lezer/common": "^1.2.0", "@lezer/common": "^1.2.0",
"@lezer/highlight": "^1.0.0", "@lezer/highlight": "^1.0.0",
"@lezer/lr": "^1.0.0" "@lezer/lr": "^1.3.0"
} }
}, },
"node_modules/@lezer/highlight": { "node_modules/@lezer/highlight": {
@ -245,9 +246,9 @@
} }
}, },
"node_modules/@lezer/javascript": { "node_modules/@lezer/javascript": {
"version": "1.4.21", "version": "1.5.1",
"resolved": "https://registry.npmjs.org/@lezer/javascript/-/javascript-1.4.21.tgz", "resolved": "https://registry.npmjs.org/@lezer/javascript/-/javascript-1.5.1.tgz",
"integrity": "sha512-lL+1fcuxWYPURMM/oFZLEDm0XuLN128QPV+VuGtKpeaOGdcl9F2LYC3nh1S9LkPqx9M0mndZFdXCipNAZpzIkQ==", "integrity": "sha512-ATOImjeVJuvgm3JQ/bpo2Tmv55HSScE2MTPnKRMRIPx2cLhHGyX2VnqpHhtIV1tVzIjZDbcWQm+NCTF40ggZVw==",
"dependencies": { "dependencies": {
"@lezer/common": "^1.2.0", "@lezer/common": "^1.2.0",
"@lezer/highlight": "^1.1.3", "@lezer/highlight": "^1.1.3",
@ -323,9 +324,9 @@
} }
}, },
"node_modules/@rollup/pluginutils": { "node_modules/@rollup/pluginutils": {
"version": "5.1.4", "version": "5.2.0",
"resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.4.tgz", "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.2.0.tgz",
"integrity": "sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ==", "integrity": "sha512-qWJ2ZTbmumwiLFomfzTyt5Kng4hwPi9rwCYN4SHb6eaRU1KNO4ccxINHr/VhH4GgPlt1XfSTLX2LBTme8ne4Zw==",
"dependencies": { "dependencies": {
"@types/estree": "^1.0.0", "@types/estree": "^1.0.0",
"estree-walker": "^2.0.2", "estree-walker": "^2.0.2",
@ -344,9 +345,9 @@
} }
}, },
"node_modules/@rollup/rollup-android-arm-eabi": { "node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.34.8", "version": "4.44.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.34.8.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.44.0.tgz",
"integrity": "sha512-q217OSE8DTp8AFHuNHXo0Y86e1wtlfVrXiAlwkIvGRQv9zbc6mE3sjIVfwI8sYUyNxwOg0j/Vm1RKM04JcWLJw==", "integrity": "sha512-xEiEE5oDW6tK4jXCAyliuntGR+amEMO7HLtdSshVuhFnKTYoeYMyXQK7pLouAJJj5KHdwdn87bfHAR2nSdNAUA==",
"cpu": [ "cpu": [
"arm" "arm"
], ],
@ -356,9 +357,9 @@
] ]
}, },
"node_modules/@rollup/rollup-android-arm64": { "node_modules/@rollup/rollup-android-arm64": {
"version": "4.34.8", "version": "4.44.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.34.8.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.44.0.tgz",
"integrity": "sha512-Gigjz7mNWaOL9wCggvoK3jEIUUbGul656opstjaUSGC3eT0BM7PofdAJaBfPFWWkXNVAXbaQtC99OCg4sJv70Q==", "integrity": "sha512-uNSk/TgvMbskcHxXYHzqwiyBlJ/lGcv8DaUfcnNwict8ba9GTTNxfn3/FAoFZYgkaXXAdrAA+SLyKplyi349Jw==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -368,9 +369,9 @@
] ]
}, },
"node_modules/@rollup/rollup-darwin-arm64": { "node_modules/@rollup/rollup-darwin-arm64": {
"version": "4.34.8", "version": "4.44.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.34.8.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.44.0.tgz",
"integrity": "sha512-02rVdZ5tgdUNRxIUrFdcMBZQoaPMrxtwSb+/hOfBdqkatYHR3lZ2A2EGyHq2sGOd0Owk80oV3snlDASC24He3Q==", "integrity": "sha512-VGF3wy0Eq1gcEIkSCr8Ke03CWT+Pm2yveKLaDvq51pPpZza3JX/ClxXOCmTYYq3us5MvEuNRTaeyFThCKRQhOA==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -380,9 +381,9 @@
] ]
}, },
"node_modules/@rollup/rollup-darwin-x64": { "node_modules/@rollup/rollup-darwin-x64": {
"version": "4.34.8", "version": "4.44.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.34.8.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.44.0.tgz",
"integrity": "sha512-qIP/elwR/tq/dYRx3lgwK31jkZvMiD6qUtOycLhTzCvrjbZ3LjQnEM9rNhSGpbLXVJYQ3rq39A6Re0h9tU2ynw==", "integrity": "sha512-fBkyrDhwquRvrTxSGH/qqt3/T0w5Rg0L7ZIDypvBPc1/gzjJle6acCpZ36blwuwcKD/u6oCE/sRWlUAcxLWQbQ==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -392,9 +393,9 @@
] ]
}, },
"node_modules/@rollup/rollup-freebsd-arm64": { "node_modules/@rollup/rollup-freebsd-arm64": {
"version": "4.34.8", "version": "4.44.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.34.8.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.44.0.tgz",
"integrity": "sha512-IQNVXL9iY6NniYbTaOKdrlVP3XIqazBgJOVkddzJlqnCpRi/yAeSOa8PLcECFSQochzqApIOE1GHNu3pCz+BDA==", "integrity": "sha512-u5AZzdQJYJXByB8giQ+r4VyfZP+walV+xHWdaFx/1VxsOn6eWJhK2Vl2eElvDJFKQBo/hcYIBg/jaKS8ZmKeNQ==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -404,9 +405,9 @@
] ]
}, },
"node_modules/@rollup/rollup-freebsd-x64": { "node_modules/@rollup/rollup-freebsd-x64": {
"version": "4.34.8", "version": "4.44.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.34.8.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.44.0.tgz",
"integrity": "sha512-TYXcHghgnCqYFiE3FT5QwXtOZqDj5GmaFNTNt3jNC+vh22dc/ukG2cG+pi75QO4kACohZzidsq7yKTKwq/Jq7Q==", "integrity": "sha512-qC0kS48c/s3EtdArkimctY7h3nHicQeEUdjJzYVJYR3ct3kWSafmn6jkNCA8InbUdge6PVx6keqjk5lVGJf99g==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -416,9 +417,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-arm-gnueabihf": { "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
"version": "4.34.8", "version": "4.44.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.34.8.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.44.0.tgz",
"integrity": "sha512-A4iphFGNkWRd+5m3VIGuqHnG3MVnqKe7Al57u9mwgbyZ2/xF9Jio72MaY7xxh+Y87VAHmGQr73qoKL9HPbXj1g==", "integrity": "sha512-x+e/Z9H0RAWckn4V2OZZl6EmV0L2diuX3QB0uM1r6BvhUIv6xBPL5mrAX2E3e8N8rEHVPwFfz/ETUbV4oW9+lQ==",
"cpu": [ "cpu": [
"arm" "arm"
], ],
@ -428,9 +429,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-arm-musleabihf": { "node_modules/@rollup/rollup-linux-arm-musleabihf": {
"version": "4.34.8", "version": "4.44.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.34.8.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.44.0.tgz",
"integrity": "sha512-S0lqKLfTm5u+QTxlFiAnb2J/2dgQqRy/XvziPtDd1rKZFXHTyYLoVL58M/XFwDI01AQCDIevGLbQrMAtdyanpA==", "integrity": "sha512-1exwiBFf4PU/8HvI8s80icyCcnAIB86MCBdst51fwFmH5dyeoWVPVgmQPcKrMtBQ0W5pAs7jBCWuRXgEpRzSCg==",
"cpu": [ "cpu": [
"arm" "arm"
], ],
@ -440,9 +441,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-arm64-gnu": { "node_modules/@rollup/rollup-linux-arm64-gnu": {
"version": "4.34.8", "version": "4.44.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.34.8.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.44.0.tgz",
"integrity": "sha512-jpz9YOuPiSkL4G4pqKrus0pn9aYwpImGkosRKwNi+sJSkz+WU3anZe6hi73StLOQdfXYXC7hUfsQlTnjMd3s1A==", "integrity": "sha512-ZTR2mxBHb4tK4wGf9b8SYg0Y6KQPjGpR4UWwTFdnmjB4qRtoATZ5dWn3KsDwGa5Z2ZBOE7K52L36J9LueKBdOQ==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -452,9 +453,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-arm64-musl": { "node_modules/@rollup/rollup-linux-arm64-musl": {
"version": "4.34.8", "version": "4.44.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.34.8.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.44.0.tgz",
"integrity": "sha512-KdSfaROOUJXgTVxJNAZ3KwkRc5nggDk+06P6lgi1HLv1hskgvxHUKZ4xtwHkVYJ1Rep4GNo+uEfycCRRxht7+Q==", "integrity": "sha512-GFWfAhVhWGd4r6UxmnKRTBwP1qmModHtd5gkraeW2G490BpFOZkFtem8yuX2NyafIP/mGpRJgTJ2PwohQkUY/Q==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -464,9 +465,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-loongarch64-gnu": { "node_modules/@rollup/rollup-linux-loongarch64-gnu": {
"version": "4.34.8", "version": "4.44.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.34.8.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.44.0.tgz",
"integrity": "sha512-NyF4gcxwkMFRjgXBM6g2lkT58OWztZvw5KkV2K0qqSnUEqCVcqdh2jN4gQrTn/YUpAcNKyFHfoOZEer9nwo6uQ==", "integrity": "sha512-xw+FTGcov/ejdusVOqKgMGW3c4+AgqrfvzWEVXcNP6zq2ue+lsYUgJ+5Rtn/OTJf7e2CbgTFvzLW2j0YAtj0Gg==",
"cpu": [ "cpu": [
"loong64" "loong64"
], ],
@ -476,9 +477,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-powerpc64le-gnu": { "node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
"version": "4.34.8", "version": "4.44.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.34.8.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.44.0.tgz",
"integrity": "sha512-LMJc999GkhGvktHU85zNTDImZVUCJ1z/MbAJTnviiWmmjyckP5aQsHtcujMjpNdMZPT2rQEDBlJfubhs3jsMfw==", "integrity": "sha512-bKGibTr9IdF0zr21kMvkZT4K6NV+jjRnBoVMt2uNMG0BYWm3qOVmYnXKzx7UhwrviKnmK46IKMByMgvpdQlyJQ==",
"cpu": [ "cpu": [
"ppc64" "ppc64"
], ],
@ -488,9 +489,21 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-riscv64-gnu": { "node_modules/@rollup/rollup-linux-riscv64-gnu": {
"version": "4.34.8", "version": "4.44.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.34.8.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.44.0.tgz",
"integrity": "sha512-xAQCAHPj8nJq1PI3z8CIZzXuXCstquz7cIOL73HHdXiRcKk8Ywwqtx2wrIy23EcTn4aZ2fLJNBB8d0tQENPCmw==", "integrity": "sha512-vV3cL48U5kDaKZtXrti12YRa7TyxgKAIDoYdqSIOMOFBXqFj2XbChHAtXquEn2+n78ciFgr4KIqEbydEGPxXgA==",
"cpu": [
"riscv64"
],
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-riscv64-musl": {
"version": "4.44.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.44.0.tgz",
"integrity": "sha512-TDKO8KlHJuvTEdfw5YYFBjhFts2TR0VpZsnLLSYmB7AaohJhM8ctDSdDnUGq77hUh4m/djRafw+9zQpkOanE2Q==",
"cpu": [ "cpu": [
"riscv64" "riscv64"
], ],
@ -500,9 +513,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-s390x-gnu": { "node_modules/@rollup/rollup-linux-s390x-gnu": {
"version": "4.34.8", "version": "4.44.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.34.8.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.44.0.tgz",
"integrity": "sha512-DdePVk1NDEuc3fOe3dPPTb+rjMtuFw89gw6gVWxQFAuEqqSdDKnrwzZHrUYdac7A7dXl9Q2Vflxpme15gUWQFA==", "integrity": "sha512-8541GEyktXaw4lvnGp9m84KENcxInhAt6vPWJ9RodsB/iGjHoMB2Pp5MVBCiKIRxrxzJhGCxmNzdu+oDQ7kwRA==",
"cpu": [ "cpu": [
"s390x" "s390x"
], ],
@ -512,9 +525,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-x64-gnu": { "node_modules/@rollup/rollup-linux-x64-gnu": {
"version": "4.34.8", "version": "4.44.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.34.8.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.44.0.tgz",
"integrity": "sha512-8y7ED8gjxITUltTUEJLQdgpbPh1sUQ0kMTmufRF/Ns5tI9TNMNlhWtmPKKHCU0SilX+3MJkZ0zERYYGIVBYHIA==", "integrity": "sha512-iUVJc3c0o8l9Sa/qlDL2Z9UP92UZZW1+EmQ4xfjTc1akr0iUFZNfxrXJ/R1T90h/ILm9iXEY6+iPrmYB3pXKjw==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -524,9 +537,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-x64-musl": { "node_modules/@rollup/rollup-linux-x64-musl": {
"version": "4.34.8", "version": "4.44.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.34.8.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.44.0.tgz",
"integrity": "sha512-SCXcP0ZpGFIe7Ge+McxY5zKxiEI5ra+GT3QRxL0pMMtxPfpyLAKleZODi1zdRHkz5/BhueUrYtYVgubqe9JBNQ==", "integrity": "sha512-PQUobbhLTQT5yz/SPg116VJBgz+XOtXt8D1ck+sfJJhuEsMj2jSej5yTdp8CvWBSceu+WW+ibVL6dm0ptG5fcA==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -536,9 +549,9 @@
] ]
}, },
"node_modules/@rollup/rollup-win32-arm64-msvc": { "node_modules/@rollup/rollup-win32-arm64-msvc": {
"version": "4.34.8", "version": "4.44.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.34.8.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.44.0.tgz",
"integrity": "sha512-YHYsgzZgFJzTRbth4h7Or0m5O74Yda+hLin0irAIobkLQFRQd1qWmnoVfwmKm9TXIZVAD0nZ+GEb2ICicLyCnQ==", "integrity": "sha512-M0CpcHf8TWn+4oTxJfh7LQuTuaYeXGbk0eageVjQCKzYLsajWS/lFC94qlRqOlyC2KvRT90ZrfXULYmukeIy7w==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -548,9 +561,9 @@
] ]
}, },
"node_modules/@rollup/rollup-win32-ia32-msvc": { "node_modules/@rollup/rollup-win32-ia32-msvc": {
"version": "4.34.8", "version": "4.44.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.34.8.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.44.0.tgz",
"integrity": "sha512-r3NRQrXkHr4uWy5TOjTpTYojR9XmF0j/RYgKCef+Ag46FWUTltm5ziticv8LdNsDMehjJ543x/+TJAek/xBA2w==", "integrity": "sha512-3XJ0NQtMAXTWFW8FqZKcw3gOQwBtVWP/u8TpHP3CRPXD7Pd6s8lLdH3sHWh8vqKCyyiI8xW5ltJScQmBU9j7WA==",
"cpu": [ "cpu": [
"ia32" "ia32"
], ],
@ -560,9 +573,9 @@
] ]
}, },
"node_modules/@rollup/rollup-win32-x64-msvc": { "node_modules/@rollup/rollup-win32-x64-msvc": {
"version": "4.34.8", "version": "4.44.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.34.8.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.44.0.tgz",
"integrity": "sha512-U0FaE5O1BCpZSeE6gBl3c5ObhePQSfk9vDRToMmTkbhCOgW4jqvtS5LGyQ76L1fH8sM0keRp4uDTsbjiUyjk0g==", "integrity": "sha512-Q2Mgwt+D8hd5FIPUuPDsvPR7Bguza6yTkJxspDGkZj7tBRn2y4KSWYuIXpftFSjBra76TbKerCV7rgFPQrn+wQ==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -572,9 +585,9 @@
] ]
}, },
"node_modules/@types/estree": { "node_modules/@types/estree": {
"version": "1.0.6", "version": "1.0.8",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
"integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==" "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="
}, },
"node_modules/@types/resolve": { "node_modules/@types/resolve": {
"version": "1.20.2", "version": "1.20.2",
@ -582,9 +595,9 @@
"integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==" "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q=="
}, },
"node_modules/acorn": { "node_modules/acorn": {
"version": "8.14.0", "version": "8.15.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
"integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"dev": true, "dev": true,
"bin": { "bin": {
"acorn": "bin/acorn" "acorn": "bin/acorn"
@ -600,9 +613,9 @@
"dev": true "dev": true
}, },
"node_modules/codemirror": { "node_modules/codemirror": {
"version": "6.0.1", "version": "6.0.2",
"resolved": "https://registry.npmjs.org/codemirror/-/codemirror-6.0.1.tgz", "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-6.0.2.tgz",
"integrity": "sha512-J8j+nZ+CdWmIeFIGXEFbFPtpiYacFMDR8GlHK3IyHQJMCaVRfGx9NT+Hxivv1ckLWPvNdZqndbr/7lVhrf/Svg==", "integrity": "sha512-VhydHotNW5w1UGK0Qj96BwSk/Zqbp9WbnyK2W/eVMv4QyF41INRGpjUhFJY7/uDNuudSc33a/PKr4iDqRduvHw==",
"dependencies": { "dependencies": {
"@codemirror/autocomplete": "^6.0.0", "@codemirror/autocomplete": "^6.0.0",
"@codemirror/commands": "^6.0.0", "@codemirror/commands": "^6.0.0",
@ -733,11 +746,11 @@
} }
}, },
"node_modules/rollup": { "node_modules/rollup": {
"version": "4.34.8", "version": "4.44.0",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.34.8.tgz", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.44.0.tgz",
"integrity": "sha512-489gTVMzAYdiZHFVA/ig/iYFllCcWFHMvUHI1rpFmkoUtRlQxqh6/yiNqnYibjMZ2b/+FUQwldG+aLsEt6bglQ==", "integrity": "sha512-qHcdEzLCiktQIfwBq420pn2dP+30uzqYxv9ETm91wdt2R9AFcWfjNAmje4NWlnCIQ5RMTzVf0ZyisOKqHR6RwA==",
"dependencies": { "dependencies": {
"@types/estree": "1.0.6" "@types/estree": "1.0.8"
}, },
"bin": { "bin": {
"rollup": "dist/bin/rollup" "rollup": "dist/bin/rollup"
@ -747,25 +760,26 @@
"npm": ">=8.0.0" "npm": ">=8.0.0"
}, },
"optionalDependencies": { "optionalDependencies": {
"@rollup/rollup-android-arm-eabi": "4.34.8", "@rollup/rollup-android-arm-eabi": "4.44.0",
"@rollup/rollup-android-arm64": "4.34.8", "@rollup/rollup-android-arm64": "4.44.0",
"@rollup/rollup-darwin-arm64": "4.34.8", "@rollup/rollup-darwin-arm64": "4.44.0",
"@rollup/rollup-darwin-x64": "4.34.8", "@rollup/rollup-darwin-x64": "4.44.0",
"@rollup/rollup-freebsd-arm64": "4.34.8", "@rollup/rollup-freebsd-arm64": "4.44.0",
"@rollup/rollup-freebsd-x64": "4.34.8", "@rollup/rollup-freebsd-x64": "4.44.0",
"@rollup/rollup-linux-arm-gnueabihf": "4.34.8", "@rollup/rollup-linux-arm-gnueabihf": "4.44.0",
"@rollup/rollup-linux-arm-musleabihf": "4.34.8", "@rollup/rollup-linux-arm-musleabihf": "4.44.0",
"@rollup/rollup-linux-arm64-gnu": "4.34.8", "@rollup/rollup-linux-arm64-gnu": "4.44.0",
"@rollup/rollup-linux-arm64-musl": "4.34.8", "@rollup/rollup-linux-arm64-musl": "4.44.0",
"@rollup/rollup-linux-loongarch64-gnu": "4.34.8", "@rollup/rollup-linux-loongarch64-gnu": "4.44.0",
"@rollup/rollup-linux-powerpc64le-gnu": "4.34.8", "@rollup/rollup-linux-powerpc64le-gnu": "4.44.0",
"@rollup/rollup-linux-riscv64-gnu": "4.34.8", "@rollup/rollup-linux-riscv64-gnu": "4.44.0",
"@rollup/rollup-linux-s390x-gnu": "4.34.8", "@rollup/rollup-linux-riscv64-musl": "4.44.0",
"@rollup/rollup-linux-x64-gnu": "4.34.8", "@rollup/rollup-linux-s390x-gnu": "4.44.0",
"@rollup/rollup-linux-x64-musl": "4.34.8", "@rollup/rollup-linux-x64-gnu": "4.44.0",
"@rollup/rollup-win32-arm64-msvc": "4.34.8", "@rollup/rollup-linux-x64-musl": "4.44.0",
"@rollup/rollup-win32-ia32-msvc": "4.34.8", "@rollup/rollup-win32-arm64-msvc": "4.44.0",
"@rollup/rollup-win32-x64-msvc": "4.34.8", "@rollup/rollup-win32-ia32-msvc": "4.44.0",
"@rollup/rollup-win32-x64-msvc": "4.44.0",
"fsevents": "~2.3.2" "fsevents": "~2.3.2"
} }
}, },
@ -840,13 +854,13 @@
} }
}, },
"node_modules/terser": { "node_modules/terser": {
"version": "5.39.0", "version": "5.43.1",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.39.0.tgz", "resolved": "https://registry.npmjs.org/terser/-/terser-5.43.1.tgz",
"integrity": "sha512-LBAhFyLho16harJoWMg/nZsQYgTrg5jXOn2nCYjRUcZZEdE3qa2zb8QEDRUGVZBW4rlazf2fxkg8tztybTaqWw==", "integrity": "sha512-+6erLbBm0+LROX2sPXlUYx/ux5PyE9K/a92Wrt6oA+WDAoFTdpHE5tCYCI5PNzq2y8df4rA+QgHLJuR4jNymsg==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@jridgewell/source-map": "^0.3.3", "@jridgewell/source-map": "^0.3.3",
"acorn": "^8.8.2", "acorn": "^8.14.0",
"commander": "^2.20.0", "commander": "^2.20.0",
"source-map-support": "~0.5.20" "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

1024
deps/sqlite/shell.c vendored

File diff suppressed because it is too large Load Diff

4393
deps/sqlite/sqlite3.c vendored

File diff suppressed because it is too large Load Diff

146
deps/sqlite/sqlite3.h vendored
View File

@ -133,7 +133,7 @@ extern "C" {
** **
** Since [version 3.6.18] ([dateof:3.6.18]), ** Since [version 3.6.18] ([dateof:3.6.18]),
** SQLite source code has been stored in the ** SQLite source code has been stored in the
** <a href="http://www.fossil-scm.org/">Fossil configuration management ** <a href="http://fossil-scm.org/">Fossil configuration management
** system</a>. ^The SQLITE_SOURCE_ID macro evaluates to ** system</a>. ^The SQLITE_SOURCE_ID macro evaluates to
** a string which identifies a particular check-in of SQLite ** a string which identifies a particular check-in of SQLite
** within its configuration management system. ^The SQLITE_SOURCE_ID ** within its configuration management system. ^The SQLITE_SOURCE_ID
@ -146,9 +146,9 @@ extern "C" {
** [sqlite3_libversion_number()], [sqlite3_sourceid()], ** [sqlite3_libversion_number()], [sqlite3_sourceid()],
** [sqlite_version()] and [sqlite_source_id()]. ** [sqlite_version()] and [sqlite_source_id()].
*/ */
#define SQLITE_VERSION "3.49.1" #define SQLITE_VERSION "3.50.1"
#define SQLITE_VERSION_NUMBER 3049001 #define SQLITE_VERSION_NUMBER 3050001
#define SQLITE_SOURCE_ID "2025-02-18 13:38:58 873d4e274b4988d260ba8354a9718324a1c26187a4ab4c1cc0227c03d0f10e70" #define SQLITE_SOURCE_ID "2025-06-06 14:52:32 b77dc5e0f596d2140d9ac682b2893ff65d3a4140aa86067a3efebe29dc914c95"
/* /*
** CAPI3REF: Run-Time Library Version Numbers ** CAPI3REF: Run-Time Library Version Numbers
@ -1163,6 +1163,12 @@ struct sqlite3_io_methods {
** the value that M is to be set to. Before returning, the 32-bit signed ** the value that M is to be set to. Before returning, the 32-bit signed
** integer is overwritten with the previous value of M. ** integer is overwritten with the previous value of M.
** **
** <li>[[SQLITE_FCNTL_BLOCK_ON_CONNECT]]
** The [SQLITE_FCNTL_BLOCK_ON_CONNECT] opcode is used to configure the
** VFS to block when taking a SHARED lock to connect to a wal mode database.
** This is used to implement the functionality associated with
** SQLITE_SETLK_BLOCK_ON_CONNECT.
**
** <li>[[SQLITE_FCNTL_DATA_VERSION]] ** <li>[[SQLITE_FCNTL_DATA_VERSION]]
** The [SQLITE_FCNTL_DATA_VERSION] opcode is used to detect changes to ** The [SQLITE_FCNTL_DATA_VERSION] opcode is used to detect changes to
** a database file. The argument is a pointer to a 32-bit unsigned integer. ** a database file. The argument is a pointer to a 32-bit unsigned integer.
@ -1259,6 +1265,7 @@ struct sqlite3_io_methods {
#define SQLITE_FCNTL_CKSM_FILE 41 #define SQLITE_FCNTL_CKSM_FILE 41
#define SQLITE_FCNTL_RESET_CACHE 42 #define SQLITE_FCNTL_RESET_CACHE 42
#define SQLITE_FCNTL_NULL_IO 43 #define SQLITE_FCNTL_NULL_IO 43
#define SQLITE_FCNTL_BLOCK_ON_CONNECT 44
/* deprecated names */ /* deprecated names */
#define SQLITE_GET_LOCKPROXYFILE SQLITE_FCNTL_GET_LOCKPROXYFILE #define SQLITE_GET_LOCKPROXYFILE SQLITE_FCNTL_GET_LOCKPROXYFILE
@ -1989,13 +1996,16 @@ struct sqlite3_mem_methods {
** **
** [[SQLITE_CONFIG_LOOKASIDE]] <dt>SQLITE_CONFIG_LOOKASIDE</dt> ** [[SQLITE_CONFIG_LOOKASIDE]] <dt>SQLITE_CONFIG_LOOKASIDE</dt>
** <dd> ^(The SQLITE_CONFIG_LOOKASIDE option takes two arguments that determine ** <dd> ^(The SQLITE_CONFIG_LOOKASIDE option takes two arguments that determine
** the default size of lookaside memory on each [database connection]. ** the default size of [lookaside memory] on each [database connection].
** The first argument is the ** The first argument is the
** size of each lookaside buffer slot and the second is the number of ** size of each lookaside buffer slot ("sz") and the second is the number of
** slots allocated to each database connection.)^ ^(SQLITE_CONFIG_LOOKASIDE ** slots allocated to each database connection ("cnt").)^
** sets the <i>default</i> lookaside size. The [SQLITE_DBCONFIG_LOOKASIDE] ** ^(SQLITE_CONFIG_LOOKASIDE sets the <i>default</i> lookaside size.
** option to [sqlite3_db_config()] can be used to change the lookaside ** The [SQLITE_DBCONFIG_LOOKASIDE] option to [sqlite3_db_config()] can
** configuration on individual connections.)^ </dd> ** be used to change the lookaside configuration on individual connections.)^
** The [-DSQLITE_DEFAULT_LOOKASIDE] option can be used to change the
** default lookaside configuration at compile-time.
** </dd>
** **
** [[SQLITE_CONFIG_PCACHE2]] <dt>SQLITE_CONFIG_PCACHE2</dt> ** [[SQLITE_CONFIG_PCACHE2]] <dt>SQLITE_CONFIG_PCACHE2</dt>
** <dd> ^(The SQLITE_CONFIG_PCACHE2 option takes a single argument which is ** <dd> ^(The SQLITE_CONFIG_PCACHE2 option takes a single argument which is
@ -2232,31 +2242,50 @@ struct sqlite3_mem_methods {
** [[SQLITE_DBCONFIG_LOOKASIDE]] ** [[SQLITE_DBCONFIG_LOOKASIDE]]
** <dt>SQLITE_DBCONFIG_LOOKASIDE</dt> ** <dt>SQLITE_DBCONFIG_LOOKASIDE</dt>
** <dd> The SQLITE_DBCONFIG_LOOKASIDE option is used to adjust the ** <dd> The SQLITE_DBCONFIG_LOOKASIDE option is used to adjust the
** configuration of the lookaside memory allocator within a database ** configuration of the [lookaside memory allocator] within a database
** connection. ** connection.
** The arguments to the SQLITE_DBCONFIG_LOOKASIDE option are <i>not</i> ** The arguments to the SQLITE_DBCONFIG_LOOKASIDE option are <i>not</i>
** in the [DBCONFIG arguments|usual format]. ** in the [DBCONFIG arguments|usual format].
** The SQLITE_DBCONFIG_LOOKASIDE option takes three arguments, not two, ** The SQLITE_DBCONFIG_LOOKASIDE option takes three arguments, not two,
** so that a call to [sqlite3_db_config()] that uses SQLITE_DBCONFIG_LOOKASIDE ** so that a call to [sqlite3_db_config()] that uses SQLITE_DBCONFIG_LOOKASIDE
** should have a total of five parameters. ** should have a total of five parameters.
** ^The first argument (the third parameter to [sqlite3_db_config()] is a ** <ol>
** <li><p>The first argument ("buf") is a
** pointer to a memory buffer to use for lookaside memory. ** pointer to a memory buffer to use for lookaside memory.
** ^The first argument after the SQLITE_DBCONFIG_LOOKASIDE verb ** The first argument may be NULL in which case SQLite will allocate the
** may be NULL in which case SQLite will allocate the ** lookaside buffer itself using [sqlite3_malloc()].
** lookaside buffer itself using [sqlite3_malloc()]. ^The second argument is the ** <li><P>The second argument ("sz") is the
** size of each lookaside buffer slot. ^The third argument is the number of ** size of each lookaside buffer slot. Lookaside is disabled if "sz"
** slots. The size of the buffer in the first argument must be greater than ** is less than 8. The "sz" argument should be a multiple of 8 less than
** or equal to the product of the second and third arguments. The buffer ** 65536. If "sz" does not meet this constraint, it is reduced in size until
** must be aligned to an 8-byte boundary. ^If the second argument to ** it does.
** SQLITE_DBCONFIG_LOOKASIDE is not a multiple of 8, it is internally ** <li><p>The third argument ("cnt") is the number of slots. Lookaside is disabled
** rounded down to the next smaller multiple of 8. ^(The lookaside memory ** if "cnt"is less than 1. The "cnt" value will be reduced, if necessary, so
** that the product of "sz" and "cnt" does not exceed 2,147,418,112. The "cnt"
** parameter is usually chosen so that the product of "sz" and "cnt" is less
** than 1,000,000.
** </ol>
** <p>If the "buf" argument is not NULL, then it must
** point to a memory buffer with a size that is greater than
** or equal to the product of "sz" and "cnt".
** The buffer must be aligned to an 8-byte boundary.
** The lookaside memory
** configuration for a database connection can only be changed when that ** configuration for a database connection can only be changed when that
** connection is not currently using lookaside memory, or in other words ** connection is not currently using lookaside memory, or in other words
** when the "current value" returned by ** when the value returned by [SQLITE_DBSTATUS_LOOKASIDE_USED] is zero.
** [sqlite3_db_status](D,[SQLITE_DBSTATUS_LOOKASIDE_USED],...) is zero.
** Any attempt to change the lookaside memory configuration when lookaside ** Any attempt to change the lookaside memory configuration when lookaside
** memory is in use leaves the configuration unchanged and returns ** memory is in use leaves the configuration unchanged and returns
** [SQLITE_BUSY].)^</dd> ** [SQLITE_BUSY].
** If the "buf" argument is NULL and an attempt
** to allocate memory based on "sz" and "cnt" fails, then
** lookaside is silently disabled.
** <p>
** The [SQLITE_CONFIG_LOOKASIDE] configuration option can be used to set the
** default lookaside configuration at initialization. The
** [-DSQLITE_DEFAULT_LOOKASIDE] option can be used to set the default lookaside
** configuration at compile-time. Typical values for lookaside are 1200 for
** "sz" and 40 to 100 for "cnt".
** </dd>
** **
** [[SQLITE_DBCONFIG_ENABLE_FKEY]] ** [[SQLITE_DBCONFIG_ENABLE_FKEY]]
** <dt>SQLITE_DBCONFIG_ENABLE_FKEY</dt> ** <dt>SQLITE_DBCONFIG_ENABLE_FKEY</dt>
@ -2993,6 +3022,44 @@ SQLITE_API int sqlite3_busy_handler(sqlite3*,int(*)(void*,int),void*);
*/ */
SQLITE_API int sqlite3_busy_timeout(sqlite3*, int ms); SQLITE_API int sqlite3_busy_timeout(sqlite3*, int ms);
/*
** CAPI3REF: Set the Setlk Timeout
** METHOD: sqlite3
**
** This routine is only useful in SQLITE_ENABLE_SETLK_TIMEOUT builds. If
** the VFS supports blocking locks, it sets the timeout in ms used by
** eligible locks taken on wal mode databases by the specified database
** handle. In non-SQLITE_ENABLE_SETLK_TIMEOUT builds, or if the VFS does
** not support blocking locks, this function is a no-op.
**
** Passing 0 to this function disables blocking locks altogether. Passing
** -1 to this function requests that the VFS blocks for a long time -
** indefinitely if possible. The results of passing any other negative value
** are undefined.
**
** Internally, each SQLite database handle store two timeout values - the
** busy-timeout (used for rollback mode databases, or if the VFS does not
** support blocking locks) and the setlk-timeout (used for blocking locks
** on wal-mode databases). The sqlite3_busy_timeout() method sets both
** values, this function sets only the setlk-timeout value. Therefore,
** to configure separate busy-timeout and setlk-timeout values for a single
** database handle, call sqlite3_busy_timeout() followed by this function.
**
** Whenever the number of connections to a wal mode database falls from
** 1 to 0, the last connection takes an exclusive lock on the database,
** then checkpoints and deletes the wal file. While it is doing this, any
** new connection that tries to read from the database fails with an
** SQLITE_BUSY error. Or, if the SQLITE_SETLK_BLOCK_ON_CONNECT flag is
** passed to this API, the new connection blocks until the exclusive lock
** has been released.
*/
SQLITE_API int sqlite3_setlk_timeout(sqlite3*, int ms, int flags);
/*
** CAPI3REF: Flags for sqlite3_setlk_timeout()
*/
#define SQLITE_SETLK_BLOCK_ON_CONNECT 0x01
/* /*
** CAPI3REF: Convenience Routines For Running Queries ** CAPI3REF: Convenience Routines For Running Queries
** METHOD: sqlite3 ** METHOD: sqlite3
@ -5108,7 +5175,7 @@ SQLITE_API const void *sqlite3_column_decltype16(sqlite3_stmt*,int);
** other than [SQLITE_ROW] before any subsequent invocation of ** other than [SQLITE_ROW] before any subsequent invocation of
** sqlite3_step(). Failure to reset the prepared statement using ** sqlite3_step(). Failure to reset the prepared statement using
** [sqlite3_reset()] would result in an [SQLITE_MISUSE] return from ** [sqlite3_reset()] would result in an [SQLITE_MISUSE] return from
** sqlite3_step(). But after [version 3.6.23.1] ([dateof:3.6.23.1], ** sqlite3_step(). But after [version 3.6.23.1] ([dateof:3.6.23.1]),
** sqlite3_step() began ** sqlite3_step() began
** calling [sqlite3_reset()] automatically in this circumstance rather ** calling [sqlite3_reset()] automatically in this circumstance rather
** than returning [SQLITE_MISUSE]. This is not considered a compatibility ** than returning [SQLITE_MISUSE]. This is not considered a compatibility
@ -7004,6 +7071,8 @@ SQLITE_API int sqlite3_autovacuum_pages(
** **
** ^The second argument is a pointer to the function to invoke when a ** ^The second argument is a pointer to the function to invoke when a
** row is updated, inserted or deleted in a rowid table. ** row is updated, inserted or deleted in a rowid table.
** ^The update hook is disabled by invoking sqlite3_update_hook()
** with a NULL pointer as the second parameter.
** ^The first argument to the callback is a copy of the third argument ** ^The first argument to the callback is a copy of the third argument
** to sqlite3_update_hook(). ** to sqlite3_update_hook().
** ^The second callback argument is one of [SQLITE_INSERT], [SQLITE_DELETE], ** ^The second callback argument is one of [SQLITE_INSERT], [SQLITE_DELETE],
@ -11486,9 +11555,10 @@ SQLITE_API void sqlite3session_table_filter(
** is inserted while a session object is enabled, then later deleted while ** is inserted while a session object is enabled, then later deleted while
** the same session object is disabled, no INSERT record will appear in the ** the same session object is disabled, no INSERT record will appear in the
** changeset, even though the delete took place while the session was disabled. ** changeset, even though the delete took place while the session was disabled.
** Or, if one field of a row is updated while a session is disabled, and ** Or, if one field of a row is updated while a session is enabled, and
** another field of the same row is updated while the session is enabled, the ** then another field of the same row is updated while the session is disabled,
** resulting changeset will contain an UPDATE change that updates both fields. ** the resulting changeset will contain an UPDATE change that updates both
** fields.
*/ */
SQLITE_API int sqlite3session_changeset( SQLITE_API int sqlite3session_changeset(
sqlite3_session *pSession, /* Session object */ sqlite3_session *pSession, /* Session object */
@ -11560,8 +11630,9 @@ SQLITE_API sqlite3_int64 sqlite3session_changeset_size(sqlite3_session *pSession
** database zFrom the contents of the two compatible tables would be ** database zFrom the contents of the two compatible tables would be
** identical. ** identical.
** **
** It an error if database zFrom does not exist or does not contain the ** Unless the call to this function is a no-op as described above, it is an
** required compatible table. ** error if database zFrom does not exist or does not contain the required
** compatible table.
** **
** If the operation is successful, SQLITE_OK is returned. Otherwise, an SQLite ** If the operation is successful, SQLITE_OK is returned. Otherwise, an SQLite
** error code. In this case, if argument pzErrMsg is not NULL, *pzErrMsg ** error code. In this case, if argument pzErrMsg is not NULL, *pzErrMsg
@ -11696,7 +11767,7 @@ SQLITE_API int sqlite3changeset_start_v2(
** The following flags may passed via the 4th parameter to ** The following flags may passed via the 4th parameter to
** [sqlite3changeset_start_v2] and [sqlite3changeset_start_v2_strm]: ** [sqlite3changeset_start_v2] and [sqlite3changeset_start_v2_strm]:
** **
** <dt>SQLITE_CHANGESETAPPLY_INVERT <dd> ** <dt>SQLITE_CHANGESETSTART_INVERT <dd>
** Invert the changeset while iterating through it. This is equivalent to ** Invert the changeset while iterating through it. This is equivalent to
** inverting a changeset using sqlite3changeset_invert() before applying it. ** inverting a changeset using sqlite3changeset_invert() before applying it.
** It is an error to specify this flag with a patchset. ** It is an error to specify this flag with a patchset.
@ -12011,19 +12082,6 @@ SQLITE_API int sqlite3changeset_concat(
void **ppOut /* OUT: Buffer containing output changeset */ void **ppOut /* OUT: Buffer containing output changeset */
); );
/*
** CAPI3REF: Upgrade the Schema of a Changeset/Patchset
*/
SQLITE_API int sqlite3changeset_upgrade(
sqlite3 *db,
const char *zDb,
int nIn, const void *pIn, /* Input changeset */
int *pnOut, void **ppOut /* OUT: Inverse of input */
);
/* /*
** CAPI3REF: Changegroup Handle ** CAPI3REF: Changegroup Handle
** **

View File

@ -366,6 +366,8 @@ struct sqlite3_api_routines {
/* Version 3.44.0 and later */ /* Version 3.44.0 and later */
void *(*get_clientdata)(sqlite3*,const char*); void *(*get_clientdata)(sqlite3*,const char*);
int (*set_clientdata)(sqlite3*, const char*, void*, void(*)(void*)); int (*set_clientdata)(sqlite3*, const char*, void*, void(*)(void*));
/* Version 3.50.0 and later */
int (*setlk_timeout)(sqlite3*,int,int);
}; };
/* /*
@ -699,6 +701,8 @@ typedef int (*sqlite3_loadext_entry)(
/* Version 3.44.0 and later */ /* Version 3.44.0 and later */
#define sqlite3_get_clientdata sqlite3_api->get_clientdata #define sqlite3_get_clientdata sqlite3_api->get_clientdata
#define sqlite3_set_clientdata sqlite3_api->set_clientdata #define sqlite3_set_clientdata sqlite3_api->set_clientdata
/* Version 3.50.0 and later */
#define sqlite3_setlk_timeout sqlite3_api->setlk_timeout
#endif /* !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) */ #endif /* !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) */
#if !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) #if !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION)

View File

@ -0,0 +1,40 @@
# Connecting with Manyverse
Communication with [Manyverse](https://www.manyver.se/) should Just Work (tm).
This document is intended as a cheat sheet for the instances where it doesn't.
If your experience differs, please share so we can make things better.
## Connecting Manyverse to the tildefriends.net room
Open the `Connections` tab. This is from the desktop app, but mobile is similar.
![Manyverse connections tab](manyverse_connections_tab.png)
Open the `Connections Panel`.
![Manyverse connections panel](manyverse_connections_panel.png)
Use the `Add Connection` button at the bottom right to open the dialog to enter
a connections string to add a new connection.
![Manyverse connections panel](manyverse_paste_invite_code.png)
Copy the tildefriends.net room code from https://www.tildefriends.net/~cory/room/.
![Tilde Friends room code](tildefriends_room_app.png)
Paste.
On mobile especially, make sure the full text is pasted without modification.
![Manyverse invite code](manyverse_code.png)
Click `Done`, and you should be connected successfully. tildefriends.net is
all things: a room, a pub, and a client, so you should be able to start replicating
immediately as well as find other similarly connected people with whom to establish
further connections.
When logged into tildefriends.net, active connections it sees can be found on
the `Connections` tab: https://www.tildefriends.net/~core/ssb/#connections,
which may indicate errors if you find yourself disconnecting.

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 KiB

View File

@ -14,7 +14,7 @@
- upload to Apple with dist-ios on macos - upload to Apple with dist-ios on macos
- nix - nix
- june and december: update release version - june and december: update release version
- run `nix flake update` - run `nix --extra-experimental-features nix-command --extra-experimental-features flakes flake update`
- comment out the hash in default.nix - comment out the hash in default.nix
- update the version - update the version
- run `nix-build` - 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.

288
docs/usage.md Normal file
View File

@ -0,0 +1,288 @@
# CLI Usage
## tildefriends -h
```
Usage: out/debug/tildefriends command [command-options]
commands:
run - Run tildefriends (default).
sandbox - Run a sandboxed tildefriends sandbox process (used internally).
import - Import apps from file to the database.
export - Export apps from the database to file.
publish - Append a message to a feed.
private - Append a private post message to a feed.
create_invite - Create an invite.
get_sequence - Get the last sequence number for a feed.
get_identity - Get the server account identity.
get_profile - Get profile information for the given identity.
get_contacts - Get information about followed, blocked, and friend identities.
has_blob - Check whether a blob is in the blob store.
get_blob - Read a file from the blob store.
store_blob - Write a file to the blob store.
verify - Verify a feed.
test - Test SSB.
```
## tildefriends run -h
```
Usage: out/debug/tildefriends run [options]
Run tildefriends (default).
options:
-s, --script script Script to run (default: core/core.js).
-d, --db-path path SQLite database path (default: /home/cory/.local/share/tildefriends/db.sqlite).
-k, --ssb-network-key key SSB network key to use.
-n, --count count Number of instances to run.
-a, --args args Arguments of the format key=value,foo=bar,verbose=true (note: these are persisted to the database).
code_of_conduct (default: ""): Code of conduct presented at sign-in.
ssb_port (default: 8008): Port on which to listen for SSB secure handshake connections.
http_local_only (default: false): Whether to bind http(s) to the loopback address. Otherwise any.
http_port (default: 12345): Port on which to listen for HTTP connections.
https_port (default: 0): Port on which to listen for secure HTTP connections.
out_http_port_file (default: ""): File to which to write bound HTTP port.
blob_fetch_age_seconds (default: -1): Only blobs mentioned more recently than this age will be automatically fetched.
blob_expire_age_seconds (default: -1): Blobs older than this will be automatically deleted.
fetch_hosts (default: ""): Comma-separated list of host names to which HTTP fetch requests are allowed. None if empty.
http_redirect (default: ""): If connecting by HTTP and HTTPS is configured, Location header prefix (ie, "http://example.com")
index (default: "/~core/intro/"): Default path.
index_map (default: ""): Mappings from hostname to redirect path, one per line, as in: "www.tildefriends.net=/~core/index/"
peer_exchange (default: false): Enable discovery of, sharing of, and connecting to internet peer strangers, including announcing this instance.
replicator (default: true): Enable message and blob replication.
room (default: true): Enable peers to tunnel through this instance as a room.
room_name (default: "tilde friends tunnel"): Name of the room.
seeds_host (default: "seeds.tildefriends.net"): Hostname for seed connections.
account_registration (default: true): Allow registration of new accounts.
replication_hops (default: 2): Number of hops to replicate (1 = direct follows, 2 = follows of follows, etc.).
delete_stale_feeds (default: false): Periodically delete feeds that aren't visible from local accounts or related follows.
talk_to_strangers (default: true): Whether connections are accepted from accounts that aren't in the replication range or otherwise already known.
autologin (default: false): Whether mobile autologin is supported.
broadcast (default: true): Send network discovery broadcasts.
discovery (default: true): Receive network discovery broadcasts.
-o, --one-proc Run everything in one process (unsafely!).
-z, --zip path Zip archive from which to load files.
-v, --verbose Log raw messages.
-h, --help Show this usage information.
```
## tildefriends sandbox -h
```
Usage: out/debug/tildefriends sandbox [options]
Run a sandboxed tildefriends sandbox process (used internally).
options:
-h, --help Show this usage information.
-f, --fd File descriptor with which to communicate with parent process.
```
## tildefriends import -h
```
Usage: out/debug/tildefriends import [options] [paths...]
Import apps from file to the database.
options:
-u, --user user User into whose account apps will be imported (default: "import").
-d, --db-path db_path SQLite database path (default: /home/cory/.local/share/tildefriends/db.sqlite).
-h, --help Show this usage information.
```
## tildefriends export -h
```
Usage: out/debug/tildefriends export [options] [paths...]
Export apps from the database to file.
options:
-u, --user user User from whose account apps will be exported (default: "core").
-d, --db-path db_path SQLite database path (default: /home/cory/.local/share/tildefriends/db.sqlite).
-h, --help Show this usage information.
paths Paths of apps to export (example: /~core/ssb /~user/app).
```
## tildefriends publish -h
```
Usage: out/debug/tildefriends publish [options]
Append a message to a feed.
options:
-u, --user user User owning identity with which to publish.
-i, --id identity Identity with which to publish message.
-d, --db-path db_path SQLite database path (default: /home/cory/.local/share/tildefriends/db.sqlite).
-c, --content json JSON content of message to publish.
-h, --help Show this usage information.
```
## tildefriends private -h
```
Usage: out/debug/tildefriends private [options]
Append a private post message to a feed.
options:
-u, --user user User owning identity with which to publish (optional).
-i, --id identity Identity with which to publish message.
-r, --recipients recipients Recipient identities.
-d, --db-path db_path SQLite database path (default: /home/cory/.local/share/tildefriends/db.sqlite).
-t, --text text Private post text.
-h, --help Show this usage information.
```
## tildefriends create_invite -h
```
Usage: out/debug/tildefriends create_invite [options]
Create an invite.
options:
-d, --db-path db_path SQLite database path (default: /home/cory/.local/share/tildefriends/db.sqlite).
-i, --identity identity Account from which to get latest sequence number.
-a, --address address Address to which the recipient will connect.
-p, --port port Port to which the recipient will connect.
-u, --use_count count Number of times this invite may be used (default: 1).
-e, --expires seconds How long this invite is valid in seconds (-1 for indefinitely, default: 1 hour).
-h, --help Show this usage information.
```
## tildefriends get_sequence -h
```
Usage: out/debug/tildefriends get_sequence [options]
Get the last sequence number for a feed.
options:
-d, --db-path db_path SQLite database path (default: /home/cory/.local/share/tildefriends/db.sqlite).
-i, --identity identity Account from which to get latest sequence number.
-h, --help Show this usage information.
```
## tildefriends get_identity -h
```
Usage: out/debug/tildefriends get_identity [options]
Get the server account identity.
options:
-d, --db-path db_path SQLite database path (default: /home/cory/.local/share/tildefriends/db.sqlite).
-h, --help Show this usage information.
```
## tildefriends get_profile -h
```
Usage: out/debug/tildefriends get_profile [options]
Get profile information for the given identity.
options:
-d, --db-path db_path SQLite database path (default: /home/cory/.local/share/tildefriends/db.sqlite).
-i, --identity identity Account for which to get profile information.
-h, --help Show this usage information.
```
## tildefriends get_contacts -h
```
Usage: out/debug/tildefriends get_contacts [options]
Get information about followed, blocked, and friend identities.
options:
-d, --db-path db_path SQLite database path (default: /home/cory/.local/share/tildefriends/db.sqlite).
-i, --identity identity Account from which to get contact information.
-h, --help Show this usage information.
```
## tildefriends has_blob -h
```
Usage: out/debug/tildefriends has_blob [options]
Check whether a blob is in the blob store.
options:
-d, --db-path db_path SQLite database path (default: /home/cory/.local/share/tildefriends/db.sqlite).
-b, --blob_id blob_id ID of blob to query.
-h, --help Show this usage information.
```
## tildefriends get_blob -h
```
Usage: out/debug/tildefriends get_blob [options]
Read a file from the blob store.
options:
-d, --db-path db_path SQLite database path (default: /home/cory/.local/share/tildefriends/db.sqlite).
-b, --blob blob_id Blob identifier to retrieve.
-o, --output file_path Location to write the retrieved blob.
-h, --help Show this usage information.
```
## tildefriends store_blob -h
```
Usage: out/debug/tildefriends store_blob [options]
Write a file to the blob store.
options:
-d, --db-path db_path SQLite database path (default: /home/cory/.local/share/tildefriends/db.sqlite).
-f, --file file_path Path to file to add to the blob store.
-h, --help Show this usage information.
```
## tildefriends verify -h
```
Usage: out/debug/tildefriends verify [options]
Verify a feed.
options:
-i, --identity identity Identity to verify.
-s, --sequence sequence Sequence number to debug.
-d, --db-path db_path SQLite database path (default: /home/cory/.local/share/tildefriends/db.sqlite).
-h, --help Show this usage information.
```
## tildefriends test -h
```
Usage: out/debug/tildefriends test [options]
Test SSB.
options:
-t, --tests tests Comma-separated list of tests to run. (default: all)
-h, --help Show this usage information.
```

6
flake.lock generated
View File

@ -20,11 +20,11 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1739758141, "lastModified": 1748037224,
"narHash": "sha256-uq6A2L7o1/tR6VfmYhZWoVAwb3gTy7j4Jx30MIrH0rE=", "narHash": "sha256-92vihpZr6dwEMV6g98M5kHZIttrWahb9iRPBm1atcPk=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "c618e28f70257593de75a7044438efc1c1fc0791", "rev": "f09dede81861f3a83f7f06641ead34f02f37597f",
"type": "github" "type": "github"
}, },
"original": { "original": {

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

View File

@ -0,0 +1,14 @@
* Improve load times.
* Fix the messages_refs table, and make it usable for hashtags.
* Fix a circumstance where we would fail to call promise callbacks.
* Fix the private messages tab.
* Fix the room app.
* Expose followed accounts in a user's profile.
* Show connection status in the sidebar.
* Simplify placeholder messages.
* Only show "Mark as Read" when relevant.
* Treat profile images more like post images.
* Limit the WAL file size.
* Updates:
* CodeMirror
* sqlite 3.50.1

Binary file not shown.

Before

Width:  |  Height:  |  Size: 275 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 92 KiB

After

Width:  |  Height:  |  Size: 108 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": { "node_modules/prettier": {
"version": "3.5.2", "version": "3.6.1",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.2.tgz", "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.1.tgz",
"integrity": "sha512-lc6npv5PH7hVqozBR7lkBNOGXV9vMwROAPlumdBkX0wTbbzPu/U1hk5yL8p2pt4Xoc+2mkT8t/sow2YrV/M5qg==", "integrity": "sha512-5xGWRa90Sp2+x1dQtNpIpeOQpTDBs9cZDmA/qs2vDNN2i18PdapqY7CmBeyLlMuGqXJRIOPaCaVZTLNQRWUH/A==",
"bin": { "bin": {
"prettier": "bin/prettier.cjs" "prettier": "bin/prettier.cjs"
}, },

View File

View File

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

View File

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

View File

@ -107,7 +107,7 @@ static void _database_get_work(tf_ssb_t* ssb, void* user_data)
database_get_t* work = user_data; database_get_t* work = user_data;
sqlite3_stmt* statement; sqlite3_stmt* statement;
sqlite3* db = tf_ssb_acquire_db_reader(ssb); 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 && 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) 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; database_set_t* work = user_data;
sqlite3* db = tf_ssb_acquire_db_writer(ssb); sqlite3* db = tf_ssb_acquire_db_writer(ssb);
sqlite3_stmt* statement; 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 && 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) 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; sqlite3_stmt* statement;
if (!work->expected) 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 && 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) 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); 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 && 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 && 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; database_remove_t* work = user_data;
sqlite3_stmt* statement; sqlite3_stmt* statement;
sqlite3* db = tf_ssb_acquire_db_writer(ssb); 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 && 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) 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; database_get_all_t* work = user_data;
sqlite3_stmt* statement; sqlite3_stmt* statement;
sqlite3* db = tf_ssb_acquire_db_reader(ssb); 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) 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; database_get_like_t* work = user_data;
sqlite3_stmt* statement; sqlite3_stmt* statement;
sqlite3* db = tf_ssb_acquire_db_reader(ssb); 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) 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; databases_list_t* work = user_data;
sqlite3* db = tf_ssb_acquire_db_reader(ssb); sqlite3* db = tf_ssb_acquire_db_reader(ssb);
sqlite3_stmt* statement; 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) if (sqlite3_bind_text(statement, 1, work->pattern, -1, NULL) == SQLITE_OK)
{ {

View File

@ -698,7 +698,7 @@ static void _http_on_connection(uv_stream_t* stream, int status)
http->connections[http->connections_count++] = connection; 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)); tf_http_listener_t* listener = tf_malloc(sizeof(tf_http_listener_t));
*listener = (tf_http_listener_t) { *listener = (tf_http_listener_t) {
@ -716,25 +716,29 @@ int tf_http_listen(tf_http_t* http, int port, tf_tls_context_t* tls, tf_http_cle
if (r == 0) if (r == 0)
{ {
bool use_ipv6 = !tf_util_is_mobile();
#if defined(__HAIKU__) #if defined(__HAIKU__)
/* /*
** Binding to IPv6 here fails with an odd error, and the socket ** Binding to IPv6 here fails with an odd error, and the socket
** becomes unusable. Since we probably want localhost only ** becomes unusable. Since we probably want localhost only
** on this single-user OS, let's just assume IPv4. ** 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_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), .sin_port = ntohs(port),
}; };
#else struct sockaddr_in6 addr6 = {
struct sockaddr_in6 addr = {
.sin6_family = AF_INET6, .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), .sin6_port = ntohs(port),
}; };
#endif struct sockaddr* addr = use_ipv6 ? (struct sockaddr*)&addr6 : (struct sockaddr*)&addr4;
r = uv_tcp_bind(&listener->tcp, (struct sockaddr*)&addr, 0); r = uv_tcp_bind(&listener->tcp, addr, 0);
if (r) if (r)
{ {
tf_printf("%s:%d: uv_tcp_bind: %s\n", __FILE__, __LINE__, uv_strerror(r)); tf_printf("%s:%d: uv_tcp_bind: %s\n", __FILE__, __LINE__, uv_strerror(r));
@ -781,8 +785,25 @@ 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) static void _http_free_listener_on_close(uv_handle_t* handle)
{ {
tf_http_listener_t* listener = handle->data; 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; handle->data = NULL;
tf_free(listener); 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) void tf_http_destroy(tf_http_t* http)
@ -792,6 +813,11 @@ void tf_http_destroy(tf_http_t* http)
return; return;
} }
if (!http->is_shutting_down)
{
tf_printf("tf_http_destroy\n");
}
http->is_shutting_down = true; http->is_shutting_down = true;
http->is_in_destroy = true; http->is_in_destroy = true;
@ -808,6 +834,10 @@ void tf_http_destroy(tf_http_t* http)
listener->cleanup(listener->user_data); listener->cleanup(listener->user_data);
listener->cleanup = NULL; 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++) for (int i = 0; i < http->handlers_count; i++)
@ -825,20 +855,11 @@ void tf_http_destroy(tf_http_t* http)
http->user_data = NULL; http->user_data = NULL;
} }
if (http->connections_count == 0) if (http->connections_count == 0 && http->listeners_count == 0)
{ {
tf_free(http->connections); tf_free(http->connections);
http->connections = NULL; 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++) for (int i = 0; i < http->handlers_count; i++)
{ {
if (http->handlers[i].pattern) if (http->handlers[i].pattern)
@ -974,9 +995,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) 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. ** times to listen on multiple ports.
** @param http The HTTP instance. ** @param http The HTTP instance.
** @param port The port on which to listen, or 0 to assign a free port. ** @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 tls An optional TLS context to use for HTTPS requests.
** @param cleanup A function called when the HTTP instance is being cleaned up. ** @param cleanup A function called when the HTTP instance is being cleaned up.
** @param user_data User data passed to the cleanup callback. ** @param user_data User data passed to the cleanup callback.
** @return The port number on which the HTTP instance is now listening. ** @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. ** 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. ** Send a websocket message.
** @param request The HTTP request which was previously updated to a websocket ** @param request The HTTP request which was previously updated to a websocket
** session with tf_http_request_websocket_upgrade(). ** session with tf_http_request_websocket_upgrade().
** @param op_code Websocket op code.
** @param data The message data. ** @param data The message data.
** @param size The size of 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. ** Upgrade an HTTP request to a websocket session.

View File

@ -5,6 +5,7 @@
#include "log.h" #include "log.h"
#include "mem.h" #include "mem.h"
#include "ssb.db.h" #include "ssb.db.h"
#include "ssb.ebt.h"
#include "ssb.h" #include "ssb.h"
#include "task.h" #include "task.h"
#include "tls.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_decode(const char* data, int length);
const char* _form_data_get(const char** form_data, const char* key); const char* _form_data_get(const char** form_data, const char* key);
static JSClassID _httpd_class_id;
static JSClassID _httpd_request_class_id; static JSClassID _httpd_request_class_id;
typedef struct _http_user_data_t typedef struct _http_user_data_t
@ -75,7 +75,6 @@ static int _object_to_headers(JSContext* context, JSValue object, const char** h
JS_GetOwnPropertyNames(context, &ptab, &plen, object, JS_GPN_STRING_MASK); JS_GetOwnPropertyNames(context, &ptab, &plen, object, JS_GPN_STRING_MASK);
for (; count < (int)plen && count < headers_length / 2; ++count) for (; count < (int)plen && count < headers_length / 2; ++count)
{ {
JSValue key = JS_AtomToString(context, ptab[count].atom);
JSPropertyDescriptor desc; JSPropertyDescriptor desc;
JSValue key_value = JS_NULL; JSValue key_value = JS_NULL;
if (JS_GetOwnProperty(context, &desc, object, ptab[count].atom) == 1) if (JS_GetOwnProperty(context, &desc, object, ptab[count].atom) == 1)
@ -84,9 +83,8 @@ static int _object_to_headers(JSContext* context, JSValue object, const char** h
JS_FreeValue(context, desc.setter); JS_FreeValue(context, desc.setter);
JS_FreeValue(context, desc.getter); JS_FreeValue(context, desc.getter);
} }
headers[count * 2 + 0] = JS_ToCString(context, key); headers[count * 2 + 0] = JS_AtomToCString(context, ptab[count].atom);
headers[count * 2 + 1] = JS_ToCString(context, key_value); headers[count * 2 + 1] = JS_ToCString(context, key_value);
JS_FreeValue(context, key);
JS_FreeValue(context, key_value); JS_FreeValue(context, key_value);
} }
for (uint32_t i = 0; i < plen; ++i) for (uint32_t i = 0; i < plen; ++i)
@ -152,44 +150,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); tf_http_request_t* request = JS_GetOpaque(this_val, _httpd_request_class_id);
int opcode = 0x1; int opcode = 0x1;
JS_ToInt32(context, &opcode, argv[1]); JS_ToInt32(context, &opcode, argv[1]);
uint64_t length = 0; size_t length = 0;
size_t length_size = 0; const char* message = JS_ToCStringLen(context, &length, argv[0]);
const char* message = JS_ToCStringLen(context, &length_size, argv[0]); tf_http_request_websocket_send(request, opcode, message, length);
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);
JS_FreeCString(context, message); JS_FreeCString(context, message);
return JS_UNDEFINED; return JS_UNDEFINED;
} }
@ -572,12 +535,6 @@ static const char* _ext_to_content_type(const char* ext, bool use_fallback)
return use_fallback ? "application/binary" : NULL; 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) static void _httpd_request_finalizer(JSRuntime* runtime, JSValue value)
{ {
tf_http_request_t* request = JS_GetOpaque(value, _httpd_request_class_id); tf_http_request_t* request = JS_GetOpaque(value, _httpd_request_class_id);
@ -604,6 +561,50 @@ static void _httpd_endpoint_trace(tf_http_request_t* request)
tf_free(json); 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) static void _httpd_endpoint_mem(tf_http_request_t* request)
{ {
if (_httpd_redirect(request)) if (_httpd_redirect(request))
@ -637,25 +638,6 @@ static void _httpd_endpoint_mem(tf_http_request_t* request)
tf_free(response); tf_free(response);
} }
static void _httpd_endpoint_hitches(tf_http_request_t* request)
{
if (_httpd_redirect(request))
{
return;
}
tf_task_t* task = request->user_data;
char* response = tf_task_get_hitches(task);
const char* headers[] = {
"Content-Type",
"application/json; charset=utf-8",
"Access-Control-Allow-Origin",
"*",
};
tf_http_respond(request, 200, headers, tf_countof(headers) / 2, response, response ? strlen(response) : 0);
tf_free(response);
}
static const char* _after(const char* text, const char* prefix) static const char* _after(const char* text, const char* prefix)
{ {
if (!text || !prefix) if (!text || !prefix)
@ -947,7 +929,7 @@ static void _httpd_endpoint_app_blob_work(tf_ssb_t* ssb, void* user_data)
char* app_path = tf_malloc(path_length); char* app_path = tf_malloc(path_length);
snprintf(app_path, path_length, "path:%s", data->user_app->app); snprintf(app_path, path_length, "path:%s", data->user_app->app);
const char* value = tf_ssb_db_get_property(ssb, data->user_app->user, app_path); const char* value = tf_ssb_db_get_property(ssb, data->user_app->user, app_path);
snprintf(data->app_blob_id, sizeof(data->app_blob_id), "%s", value); tf_string_set(data->app_blob_id, sizeof(data->app_blob_id), value);
tf_free(app_path); tf_free(app_path);
tf_free((void*)value); tf_free((void*)value);
data->file = last_slash + 1; data->file = last_slash + 1;
@ -1113,6 +1095,7 @@ typedef struct _view_t
void* data; void* data;
size_t size; size_t size;
char etag[256]; char etag[256];
char notify_want_blob_id[k_blob_id_len];
bool not_modified; bool not_modified;
} view_t; } view_t;
@ -1145,7 +1128,7 @@ static void _httpd_endpoint_view_work(tf_ssb_t* ssb, void* user_data)
char* app_path = tf_malloc(app_path_length); char* app_path = tf_malloc(app_path_length);
snprintf(app_path, app_path_length, "path:%s", user_app->app); snprintf(app_path, app_path_length, "path:%s", user_app->app);
const char* value = tf_ssb_db_get_property(ssb, user_app->user, app_path); const char* value = tf_ssb_db_get_property(ssb, user_app->user, app_path);
snprintf(blob_id, sizeof(blob_id), "%s", value); tf_string_set(blob_id, sizeof(blob_id), value);
tf_free(app_path); tf_free(app_path);
tf_free((void*)value); tf_free((void*)value);
} }
@ -1167,7 +1150,13 @@ static void _httpd_endpoint_view_work(tf_ssb_t* ssb, void* user_data)
} }
else 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);
tf_string_set(view->notify_want_blob_id, sizeof(view->notify_want_blob_id), blob_id);
}
} }
} }
} }
@ -1210,6 +1199,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); const char* k_payload = tf_http_status_text(404);
tf_http_respond(view->request, 404, NULL, 0, k_payload, strlen(k_payload)); 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_free(view->form_data);
tf_http_request_unref(view->request); tf_http_request_unref(view->request);
tf_free(view); tf_free(view);
@ -1311,9 +1306,14 @@ static void _httpd_endpoint_save_work(tf_ssb_t* ssb, void* user_data)
tf_ssb_db_set_property(ssb, user_app->user, app_path, blob_id)) tf_ssb_db_set_property(ssb, user_app->user, app_path, blob_id))
{ {
tf_ssb_db_add_value_to_array_property(ssb, user_app->user, "apps", user_app->app); tf_ssb_db_add_value_to_array_property(ssb, user_app->user, "apps", user_app->app);
snprintf(save->blob_id, sizeof(save->blob_id), "%s", blob_id); tf_string_set(save->blob_id, sizeof(save->blob_id), blob_id);
save->response = 200; save->response = 200;
} }
else
{
tf_printf("Blob store or property set failed.\n");
save->response = 500;
}
JS_FreeCString(context, new_app_str); JS_FreeCString(context, new_app_str);
JS_FreeValue(context, new_app_json); JS_FreeValue(context, new_app_json);
@ -1330,7 +1330,7 @@ static void _httpd_endpoint_save_work(tf_ssb_t* ssb, void* user_data)
} }
else else
{ {
save->response = 401; save->response = 403;
} }
tf_free(user_app); tf_free(user_app);
} }
@ -1339,15 +1339,24 @@ static void _httpd_endpoint_save_work(tf_ssb_t* ssb, void* user_data)
char blob_id[k_blob_id_len] = { 0 }; char blob_id[k_blob_id_len] = { 0 };
if (tf_ssb_db_blob_store(ssb, request->body, request->content_length, blob_id, sizeof(blob_id), NULL)) if (tf_ssb_db_blob_store(ssb, request->body, request->content_length, blob_id, sizeof(blob_id), NULL))
{ {
snprintf(save->blob_id, sizeof(save->blob_id), "%s", blob_id); tf_string_set(save->blob_id, sizeof(save->blob_id), blob_id);
save->response = 200; save->response = 200;
} }
else
{
tf_printf("Blob store failed.\n");
save->response = 500;
}
} }
else else
{ {
save->response = 400; save->response = 400;
} }
} }
else
{
save->response = 401;
}
tf_free((void*)session); tf_free((void*)session);
JS_FreeCString(context, user_string); JS_FreeCString(context, user_string);
@ -1469,17 +1478,9 @@ static void _httpd_endpoint_delete(tf_http_request_t* request)
static void _httpd_endpoint_root_callback(const char* path, void* user_data) static void _httpd_endpoint_root_callback(const char* path, void* user_data)
{ {
tf_http_request_t* request = 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[] = { const char* headers[] = {
"Location", "Location",
url, path ? path : "/~core/apps/",
}; };
tf_http_respond(request, 303, headers, tf_countof(headers) / 2, NULL, 0); tf_http_respond(request, 303, headers, tf_countof(headers) / 2, NULL, 0);
tf_http_request_unref(request); tf_http_request_unref(request);
@ -1958,7 +1959,7 @@ static void _httpd_endpoint_login_work(tf_ssb_t* ssb, void* user_data)
const char* return_url = _form_data_get(form_data, "return"); const char* return_url = _form_data_get(form_data, "return");
if (return_url) if (return_url)
{ {
snprintf(login->location_header, sizeof(login->location_header), "%s", return_url); tf_string_set(login->location_header, sizeof(login->location_header), return_url);
} }
else else
{ {
@ -2063,7 +2064,7 @@ static void _httpd_endpoint_login_work(tf_ssb_t* ssb, void* user_data)
const char* return_url = _form_data_get(form_data, "return"); const char* return_url = _form_data_get(form_data, "return");
if (return_url) if (return_url)
{ {
snprintf(login->location_header, sizeof(login->location_header), "%s", return_url); tf_string_set(login->location_header, sizeof(login->location_header), return_url);
} }
else else
{ {
@ -2338,16 +2339,7 @@ static const char* _httpd_read_file(tf_task_t* task, const char* path)
void tf_httpd_register(JSContext* context) void tf_httpd_register(JSContext* context)
{ {
JS_NewClassID(&_httpd_class_id);
JS_NewClassID(&_httpd_request_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 = { JSClassDef request_def = {
.class_name = "Request", .class_name = "Request",
.finalizer = &_httpd_request_finalizer, .finalizer = &_httpd_request_finalizer,
@ -2358,22 +2350,29 @@ void tf_httpd_register(JSContext* context)
} }
JSValue global = JS_GetGlobalObject(context); 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_task_t* task = tf_task_get(context);
tf_ssb_t* ssb = tf_task_get_ssb(task); tf_ssb_t* ssb = tf_task_get_ssb(task);
uv_loop_t* loop = tf_task_get_loop(task); uv_loop_t* loop = tf_task_get_loop(task);
tf_http_t* http = tf_http_create(loop); tf_http_t* http = tf_http_create(loop);
tf_http_set_trace(http, tf_task_get_trace(task)); tf_http_set_trace(http, tf_task_get_trace(task));
JS_SetOpaque(httpd, http);
int64_t http_port = 0; int64_t http_port = 0;
int64_t https_port = 0; int64_t https_port = 0;
char out_http_port_file[512] = ""; char out_http_port_file[512] = "";
bool local_only = false;
sqlite3* db = tf_ssb_acquire_db_reader(ssb); 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, "http_port", &http_port);
tf_ssb_db_get_global_setting_int64(db, "https_port", &https_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_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); tf_ssb_release_db_reader(ssb, db);
if (https_port) if (https_port)
@ -2417,9 +2416,9 @@ void tf_httpd_register(JSContext* context)
tf_http_add_handler(http, "/robots.txt", _httpd_endpoint_robots_txt, NULL, NULL); tf_http_add_handler(http, "/robots.txt", _httpd_endpoint_robots_txt, NULL, NULL);
tf_http_add_handler(http, "/debug", _httpd_endpoint_debug, NULL, task); tf_http_add_handler(http, "/debug", _httpd_endpoint_debug, NULL, task);
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, "/mem", _httpd_endpoint_mem, NULL, task);
tf_http_add_handler(http, "/trace", _httpd_endpoint_trace, 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/logout", _httpd_endpoint_logout, NULL, task);
tf_http_add_handler(http, "/login/auto", _httpd_endpoint_login_auto, NULL, task); tf_http_add_handler(http, "/login/auto", _httpd_endpoint_login_auto, NULL, task);
@ -2427,15 +2426,11 @@ void tf_httpd_register(JSContext* context)
tf_http_add_handler(http, "/app/socket", _httpd_endpoint_app_socket, NULL, task); 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)); httpd_listener_t* listener = tf_malloc(sizeof(httpd_listener_t));
*listener = (httpd_listener_t) { 0 }; *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); 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)
@ -2468,11 +2463,17 @@ void tf_httpd_register(JSContext* context)
tf_tls_context_set_private_key(tls, private_key); tf_tls_context_set_private_key(tls, private_key);
httpd_listener_t* listener = tf_malloc(sizeof(httpd_listener_t)); httpd_listener_t* listener = tf_malloc(sizeof(httpd_listener_t));
*listener = (httpd_listener_t) { .tls = tls }; *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_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*)certificate);
tf_free((char*)private_key); tf_free((char*)private_key);
} }
} }
return http;
}
void tf_httpd_destroy(tf_http_t* http)
{
tf_http_destroy(http);
} }

View File

@ -11,7 +11,13 @@
** @{ ** @{
*/ */
#include "quickjs.h" /** A JS context. */
typedef struct JSContext JSContext;
/**
** An HTTP server instance.
*/
typedef struct _tf_http_t tf_http_t;
/** /**
** Register the HTTP script interface. Also registers a number of built-in ** Register the HTTP script interface. Also registers a number of built-in
@ -21,4 +27,16 @@
*/ */
void tf_httpd_register(JSContext* context); 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,12 +4,14 @@
#import <WebKit/WKWebView.h> #import <WebKit/WKWebView.h>
#import <WebKit/WKWebViewConfiguration.h> #import <WebKit/WKWebViewConfiguration.h>
#include "log.h"
#include <libgen.h> #include <libgen.h>
#include <string.h> #include <string.h>
void tf_run_thread_start(const char* zip_path); 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 (strong, nonatomic) WKWebView* web_view;
@property bool initial_load_complete; @property bool initial_load_complete;
@end @end
@ -19,19 +21,67 @@ static void _start_initial_load(WKWebView* web_view)
[web_view loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://localhost:12345/login/auto"]]]; [web_view loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://localhost:12345/login/auto"]]];
} }
@implementation ViewController : UIViewController @implementation ViewController : UINavigationController
- (void)viewDidLoad - (void)viewDidLoad
{ {
[super 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]; WKWebViewConfiguration* configuration = [[WKWebViewConfiguration alloc] init];
self.web_view = [[WKWebView alloc] initWithFrame:self.view.frame configuration:configuration]; self.web_view = [[WKWebView alloc] initWithFrame:self.view.frame configuration:configuration];
self.web_view.UIDelegate = self; self.web_view.UIDelegate = self;
self.web_view.navigationDelegate = self; self.web_view.navigationDelegate = self;
self.web_view.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth; self.web_view.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
self.web_view.translatesAutoresizingMaskIntoConstraints = false;
[self.view addSubview:self.web_view]; [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); _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 - (void)webView:(WKWebView*)webView didFinishNavigation:(WKNavigation*)navigation
{ {
self.initial_load_complete = true; self.initial_load_complete = true;

View File

@ -13,13 +13,13 @@
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>APPL</string> <string>APPL</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>0.0.29</string> <string>0.0.32</string>
<key>CFBundleSupportedPlatforms</key> <key>CFBundleSupportedPlatforms</key>
<array> <array>
<string>iPhoneOS</string> <string>iPhoneOS</string>
</array> </array>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>11</string> <string>14</string>
<key>DTPlatformName</key> <key>DTPlatformName</key>
<string>iphoneos</string> <string>iphoneos</string>
<key>LSRequiresIPhoneOS</key> <key>LSRequiresIPhoneOS</key>

View File

@ -148,6 +148,7 @@ static int _tf_command_private(const char* file, int argc, char* argv[]);
static int _tf_command_run(const char* file, int argc, char* argv[]); static int _tf_command_run(const char* file, int argc, char* argv[]);
static int _tf_command_sandbox(const char* file, int argc, char* argv[]); static int _tf_command_sandbox(const char* file, int argc, char* argv[]);
static int _tf_command_has_blob(const char* file, int argc, char* argv[]); static int _tf_command_has_blob(const char* file, int argc, char* argv[]);
static int _tf_command_get_blob(const char* file, int argc, char* argv[]);
static int _tf_command_store_blob(const char* file, int argc, char* argv[]); static int _tf_command_store_blob(const char* file, int argc, char* argv[]);
static int _tf_command_create_invite(const char* file, int argc, char* argv[]); static int _tf_command_create_invite(const char* file, int argc, char* argv[]);
static int _tf_command_get_sequence(const char* file, int argc, char* argv[]); static int _tf_command_get_sequence(const char* file, int argc, char* argv[]);
@ -168,8 +169,8 @@ typedef struct _command_t
const command_t k_commands[] = { const command_t k_commands[] = {
{ "run", _tf_command_run, "Run tildefriends (default)." }, { "run", _tf_command_run, "Run tildefriends (default)." },
{ "sandbox", _tf_command_sandbox, "Run a sandboxed tildefriends sandbox process (used internally)." }, { "sandbox", _tf_command_sandbox, "Run a sandboxed tildefriends sandbox process (used internally)." },
{ "import", _tf_command_import, "Import apps to SSB." }, { "import", _tf_command_import, "Import apps from file to the database." },
{ "export", _tf_command_export, "Export apps from SSB." }, { "export", _tf_command_export, "Export apps from the database to file." },
{ "publish", _tf_command_publish, "Append a message to a feed." }, { "publish", _tf_command_publish, "Append a message to a feed." },
{ "private", _tf_command_private, "Append a private post message to a feed." }, { "private", _tf_command_private, "Append a private post message to a feed." },
{ "create_invite", _tf_command_create_invite, "Create an invite." }, { "create_invite", _tf_command_create_invite, "Create an invite." },
@ -178,16 +179,31 @@ const command_t k_commands[] = {
{ "get_profile", _tf_command_get_profile, "Get profile information for the given identity." }, { "get_profile", _tf_command_get_profile, "Get profile information for the given identity." },
{ "get_contacts", _tf_command_get_contacts, "Get information about followed, blocked, and friend identities." }, { "get_contacts", _tf_command_get_contacts, "Get information about followed, blocked, and friend identities." },
{ "has_blob", _tf_command_has_blob, "Check whether a blob is in the blob store." }, { "has_blob", _tf_command_has_blob, "Check whether a blob is in the blob store." },
{ "get_blob", _tf_command_get_blob, "Read a file from the blob store." },
{ "store_blob", _tf_command_store_blob, "Write a file to the blob store." }, { "store_blob", _tf_command_store_blob, "Write a file to the blob store." },
{ "verify", _tf_command_verify, "Verify a feed." }, { "verify", _tf_command_verify, "Verify a feed." },
{ "test", _tf_command_test, "Test SSB." }, { "test", _tf_command_test, "Test SSB." },
}; };
static const char* _description(const char* name)
{
for (int i = 0; i < tf_countof(k_commands); i++)
{
if (strcmp(name, k_commands[i].name) == 0)
{
return k_commands[i].description;
}
}
return NULL;
}
static int _tf_command_test(const char* file, int argc, char* argv[]) static int _tf_command_test(const char* file, int argc, char* argv[])
{ {
#if !defined(__ANDROID__) #if !defined(__ANDROID__)
const char* default_db_path = _get_db_path();
tf_test_options_t test_options = { tf_test_options_t test_options = {
.exe_path = file, .exe_path = file,
.db_path = default_db_path,
}; };
bool show_usage = false; bool show_usage = false;
@ -195,10 +211,11 @@ static int _tf_command_test(const char* file, int argc, char* argv[])
{ {
static const struct option k_options[] = { static const struct option k_options[] = {
{ "tests", required_argument, NULL, 't' }, { "tests", required_argument, NULL, 't' },
{ "db-path", required_argument, NULL, 'd' },
{ "help", no_argument, NULL, 'h' }, { "help", no_argument, NULL, 'h' },
{ 0 }, { 0 },
}; };
int c = getopt_long(argc, argv, "t:h", k_options, NULL); int c = getopt_long(argc, argv, "t:d:h", k_options, NULL);
if (c == -1) if (c == -1)
{ {
break; break;
@ -214,6 +231,9 @@ static int _tf_command_test(const char* file, int argc, char* argv[])
case 't': case 't':
test_options.tests = optarg; test_options.tests = optarg;
break; break;
case 'd':
test_options.db_path = optarg;
break;
} }
} }
@ -225,14 +245,17 @@ static int _tf_command_test(const char* file, int argc, char* argv[])
if (show_usage) if (show_usage)
{ {
tf_printf("\n%s test [options]\n\n", file); tf_printf("\nUsage: %s test [options]\n\n", file);
tf_printf("options\n"); tf_printf("%s\n\n", _description("test"));
tf_printf("options:\n");
tf_printf(" -t, --tests tests Comma-separated list of tests to run. (default: all)\n"); tf_printf(" -t, --tests tests Comma-separated list of tests to run. (default: all)\n");
tf_printf(" -h, --help Show this usage information.\n"); tf_printf(" -h, --help Show this usage information.\n");
tf_free((void*)default_db_path);
return EXIT_FAILURE; return EXIT_FAILURE;
} }
tf_tests(&test_options); tf_tests(&test_options);
tf_free((void*)default_db_path);
return EXIT_SUCCESS; return EXIT_SUCCESS;
#else #else
return EXIT_FAILURE; return EXIT_FAILURE;
@ -278,7 +301,8 @@ static int _tf_command_import(const char* file, int argc, char* argv[])
if (show_usage) if (show_usage)
{ {
tf_printf("\n%s import [options] [paths...]\n\n", file); tf_printf("\nUsage: %s import [options] [paths...]\n\n", file);
tf_printf("%s\n\n", _description("import"));
tf_printf("options:\n"); tf_printf("options:\n");
tf_printf(" -u, --user user User into whose account apps will be imported (default: \"import\").\n"); tf_printf(" -u, --user user User into whose account apps will be imported (default: \"import\").\n");
tf_printf(" -d, --db-path db_path SQLite database path (default: %s).\n", default_db_path); tf_printf(" -d, --db-path db_path SQLite database path (default: %s).\n", default_db_path);
@ -347,7 +371,8 @@ static int _tf_command_export(const char* file, int argc, char* argv[])
if (show_usage) if (show_usage)
{ {
tf_printf("\n%s export [options] [paths...]\n\n", file); tf_printf("\nUsage: %s export [options] [paths...]\n\n", file);
tf_printf("%s\n\n", _description("export"));
tf_printf("options:\n"); tf_printf("options:\n");
tf_printf(" -u, --user user User from whose account apps will be exported (default: \"core\").\n"); tf_printf(" -u, --user user User from whose account apps will be exported (default: \"core\").\n");
tf_printf(" -d, --db-path db_path SQLite database path (default: %s).\n", default_db_path); tf_printf(" -d, --db-path db_path SQLite database path (default: %s).\n", default_db_path);
@ -461,9 +486,10 @@ static int _tf_command_publish(const char* file, int argc, char* argv[])
} }
} }
if (show_usage || !user || !identity || !content) if (show_usage || !identity || !content)
{ {
tf_printf("\n%s publish [options]\n\n", file); tf_printf("\nUsage: %s publish [options]\n\n", file);
tf_printf("%s\n\n", _description("publish"));
tf_printf("options:\n"); tf_printf("options:\n");
tf_printf(" -u, --user user User owning identity with which to publish.\n"); tf_printf(" -u, --user user User owning identity with which to publish.\n");
tf_printf(" -i, --id identity Identity with which to publish message.\n"); tf_printf(" -i, --id identity Identity with which to publish message.\n");
@ -480,10 +506,18 @@ static int _tf_command_publish(const char* file, int argc, char* argv[])
tf_ssb_t* ssb = tf_ssb_create(NULL, NULL, db_path, NULL); tf_ssb_t* ssb = tf_ssb_create(NULL, NULL, db_path, NULL);
tf_ssb_set_quiet(ssb, true); tf_ssb_set_quiet(ssb, true);
uint8_t private_key[512] = { 0 }; uint8_t private_key[512] = { 0 };
bool free_user = false;
if (!user)
{
user = tf_ssb_db_get_user_for_identity(ssb, identity);
free_user = true;
}
if (tf_ssb_db_identity_get_private_key(ssb, user, identity, private_key, sizeof(private_key))) if (tf_ssb_db_identity_get_private_key(ssb, user, identity, private_key, sizeof(private_key)))
{ {
JSContext* context = tf_ssb_get_context(ssb); JSContext* context = tf_ssb_get_context(ssb);
int64_t sequence = 0; int32_t sequence = 0;
char previous[k_id_base64_len] = { 0 }; char previous[k_id_base64_len] = { 0 };
tf_ssb_db_get_latest_message_by_author(ssb, identity, &sequence, previous, sizeof(previous)); tf_ssb_db_get_latest_message_by_author(ssb, identity, &sequence, previous, sizeof(previous));
JSValue content_value = JS_ParseJSON(context, content, strlen(content), NULL); JSValue content_value = JS_ParseJSON(context, content, strlen(content), NULL);
@ -509,6 +543,12 @@ static int _tf_command_publish(const char* file, int argc, char* argv[])
{ {
tf_printf("Did not find private key for identity %s belonging to %s.\n", identity, user); tf_printf("Did not find private key for identity %s belonging to %s.\n", identity, user);
} }
if (free_user)
{
tf_free((void*)user);
}
tf_ssb_destroy(ssb); tf_ssb_destroy(ssb);
tf_free((void*)default_db_path); tf_free((void*)default_db_path);
return result; return result;
@ -531,7 +571,7 @@ static int _tf_command_private(const char* file, int argc, char* argv[])
{ "id", required_argument, NULL, 'i' }, { "id", required_argument, NULL, 'i' },
{ "recipients", required_argument, NULL, 'r' }, { "recipients", required_argument, NULL, 'r' },
{ "db-path", required_argument, NULL, 'd' }, { "db-path", required_argument, NULL, 'd' },
{ "text", required_argument, NULL, 'c' }, { "text", required_argument, NULL, 't' },
{ "help", no_argument, NULL, 'h' }, { "help", no_argument, NULL, 'h' },
{ 0 }, { 0 },
}; };
@ -566,11 +606,12 @@ static int _tf_command_private(const char* file, int argc, char* argv[])
} }
} }
if (show_usage || !user || !identity || !recipients || !text) if (show_usage || !identity || !recipients || !text)
{ {
tf_printf("\n%s private [options]\n\n", file); tf_printf("\nUsage: %s private [options]\n\n", file);
tf_printf("%s\n\n", _description("private"));
tf_printf("options:\n"); tf_printf("options:\n");
tf_printf(" -u, --user user User owning identity with which to publish.\n"); tf_printf(" -u, --user user User owning identity with which to publish (optional).\n");
tf_printf(" -i, --id identity Identity with which to publish message.\n"); tf_printf(" -i, --id identity Identity with which to publish message.\n");
tf_printf(" -r, --recipients recipients Recipient identities.\n"); tf_printf(" -r, --recipients recipients Recipient identities.\n");
tf_printf(" -d, --db-path db_path SQLite database path (default: %s).\n", default_db_path); tf_printf(" -d, --db-path db_path SQLite database path (default: %s).\n", default_db_path);
@ -591,6 +632,13 @@ static int _tf_command_private(const char* file, int argc, char* argv[])
recipient_list[recipient_count++] = identity; recipient_list[recipient_count++] = identity;
bool free_user = false;
if (!user)
{
user = tf_ssb_db_get_user_for_identity(ssb, identity);
free_user = true;
}
if (tf_ssb_db_identity_get_private_key(ssb, user, identity, private_key, sizeof(private_key))) if (tf_ssb_db_identity_get_private_key(ssb, user, identity, private_key, sizeof(private_key)))
{ {
char* copy = tf_strdup(recipients); char* copy = tf_strdup(recipients);
@ -622,7 +670,7 @@ static int _tf_command_private(const char* file, int argc, char* argv[])
char* encrypted = tf_ssb_private_message_encrypt(private_key, recipient_list, recipient_count, message_str, strlen(message_str)); char* encrypted = tf_ssb_private_message_encrypt(private_key, recipient_list, recipient_count, message_str, strlen(message_str));
if (encrypted) if (encrypted)
{ {
int64_t sequence = 0; int32_t sequence = 0;
char previous[k_id_base64_len] = { 0 }; char previous[k_id_base64_len] = { 0 };
tf_ssb_db_get_latest_message_by_author(ssb, identity, &sequence, previous, sizeof(previous)); tf_ssb_db_get_latest_message_by_author(ssb, identity, &sequence, previous, sizeof(previous));
@ -642,6 +690,11 @@ static int _tf_command_private(const char* file, int argc, char* argv[])
{ {
tf_printf("Did not find private key for identity %s belonging to %s.\n", identity, user); tf_printf("Did not find private key for identity %s belonging to %s.\n", identity, user);
} }
if (free_user)
{
tf_free((void*)user);
}
done: done:
tf_ssb_destroy(ssb); tf_ssb_destroy(ssb);
tf_free((void*)default_db_path); tf_free((void*)default_db_path);
@ -687,7 +740,8 @@ static int _tf_command_store_blob(const char* file, int argc, char* argv[])
if (show_usage || !file_path) if (show_usage || !file_path)
{ {
tf_printf("\n%s store_blob [options]\n\n", file); tf_printf("\nUsage: %s store_blob [options]\n\n", file);
tf_printf("%s\n\n", _description("store_blob"));
tf_printf("options:\n"); tf_printf("options:\n");
tf_printf(" -d, --db-path db_path SQLite database path (default: %s).\n", default_db_path); tf_printf(" -d, --db-path db_path SQLite database path (default: %s).\n", default_db_path);
tf_printf(" -f, --file file_path Path to file to add to the blob store.\n"); tf_printf(" -f, --file file_path Path to file to add to the blob store.\n");
@ -745,6 +799,114 @@ static int _tf_command_store_blob(const char* file, int argc, char* argv[])
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }
static int _tf_command_get_blob(const char* file, int argc, char* argv[])
{
const char* default_db_path = _get_db_path();
const char* db_path = default_db_path;
const char* output = NULL;
const char* blob_id = NULL;
bool show_usage = false;
while (!show_usage)
{
static const struct option k_options[] = {
{ "db-path", required_argument, NULL, 'd' },
{ "blob", required_argument, NULL, 'b' },
{ "output", required_argument, NULL, 'o' },
{ "help", no_argument, NULL, 'h' },
{ 0 },
};
int c = getopt_long(argc, argv, "d:b:o:h", k_options, NULL);
if (c == -1)
{
break;
}
switch (c)
{
case '?':
case 'h':
default:
show_usage = true;
break;
case 'd':
db_path = optarg;
break;
case 'b':
blob_id = optarg;
break;
case 'o':
output = optarg;
break;
}
}
if (show_usage || !blob_id)
{
tf_printf("\nUsage: %s get_blob [options]\n\n", file);
tf_printf("%s\n\n", _description("get_blob"));
tf_printf("options:\n");
tf_printf(" -d, --db-path db_path SQLite database path (default: %s).\n", default_db_path);
tf_printf(" -b, --blob blob_id Blob identifier to retrieve.\n");
tf_printf(" -o, --output file_path Location to write the retrieved blob.\n");
tf_printf(" -h, --help Show this usage information.\n");
tf_free((void*)default_db_path);
return EXIT_FAILURE;
}
uint8_t* blob = NULL;
size_t size = 0;
_create_directories_for_file(db_path, 0700);
tf_ssb_t* ssb = tf_ssb_create(NULL, NULL, db_path, NULL);
tf_ssb_set_quiet(ssb, true);
bool fetched = tf_ssb_db_blob_get(ssb, blob_id, &blob, &size);
tf_ssb_destroy(ssb);
if (!fetched)
{
tf_printf("Failed to fetch blob: %s.\n", blob_id);
tf_free((void*)default_db_path);
return EXIT_FAILURE;
}
if (output)
{
FILE* blob_file = fopen(output, "wb");
if (!blob_file)
{
tf_printf("Failed to open %s: %s.\n", output, strerror(errno));
tf_free((void*)default_db_path);
tf_free(blob);
return EXIT_FAILURE;
}
if (fwrite(blob, 1, size, blob_file) != size)
{
tf_printf("Failed to write %s: %s\n", output, strerror(errno));
tf_free(blob);
tf_free((void*)default_db_path);
fclose(blob_file);
return EXIT_FAILURE;
}
fclose(blob_file);
}
else
{
if (fwrite(blob, 1, size, stdout) != size)
{
tf_free(blob);
tf_free((void*)default_db_path);
return EXIT_FAILURE;
}
}
tf_free(blob);
tf_free((void*)default_db_path);
return EXIT_SUCCESS;
}
static int _tf_command_has_blob(const char* file, int argc, char* argv[]) static int _tf_command_has_blob(const char* file, int argc, char* argv[])
{ {
const char* default_db_path = _get_db_path(); const char* default_db_path = _get_db_path();
@ -784,7 +946,8 @@ static int _tf_command_has_blob(const char* file, int argc, char* argv[])
if (show_usage || !blob_id) if (show_usage || !blob_id)
{ {
tf_printf("\n%s has_blob [options]\n\n", file); tf_printf("\nUsage: %s has_blob [options]\n\n", file);
tf_printf("%s\n\n", _description("has_blob"));
tf_printf("options:\n"); tf_printf("options:\n");
tf_printf(" -d, --db-path db_path SQLite database path (default: %s).\n", default_db_path); tf_printf(" -d, --db-path db_path SQLite database path (default: %s).\n", default_db_path);
tf_printf(" -b, --blob_id blob_id ID of blob to query.\n"); tf_printf(" -b, --blob_id blob_id ID of blob to query.\n");
@ -862,7 +1025,8 @@ static int _tf_command_create_invite(const char* file, int argc, char* argv[])
if (show_usage || !identity || !use_count || !expires || !host || !port) if (show_usage || !identity || !use_count || !expires || !host || !port)
{ {
tf_printf("\n%s get_sequence [options]\n\n", file); tf_printf("\nUsage: %s create_invite [options]\n\n", file);
tf_printf("%s\n\n", _description("create_invite"));
tf_printf("options:\n"); tf_printf("options:\n");
tf_printf(" -d, --db-path db_path SQLite database path (default: %s).\n", default_db_path); tf_printf(" -d, --db-path db_path SQLite database path (default: %s).\n", default_db_path);
tf_printf(" -i, --identity identity Account from which to get latest sequence number.\n"); tf_printf(" -i, --identity identity Account from which to get latest sequence number.\n");
@ -930,7 +1094,8 @@ static int _tf_command_get_sequence(const char* file, int argc, char* argv[])
if (show_usage || !identity) if (show_usage || !identity)
{ {
tf_printf("\n%s get_sequence [options]\n\n", file); tf_printf("\nUsage: %s get_sequence [options]\n\n", file);
tf_printf("%s\n\n", _description("get_sequence"));
tf_printf("options:\n"); tf_printf("options:\n");
tf_printf(" -d, --db-path db_path SQLite database path (default: %s).\n", default_db_path); tf_printf(" -d, --db-path db_path SQLite database path (default: %s).\n", default_db_path);
tf_printf(" -i, --identity identity Account from which to get latest sequence number.\n"); tf_printf(" -i, --identity identity Account from which to get latest sequence number.\n");
@ -941,9 +1106,9 @@ static int _tf_command_get_sequence(const char* file, int argc, char* argv[])
tf_ssb_t* ssb = tf_ssb_create(NULL, NULL, db_path, NULL); tf_ssb_t* ssb = tf_ssb_create(NULL, NULL, db_path, NULL);
tf_ssb_set_quiet(ssb, true); tf_ssb_set_quiet(ssb, true);
int64_t sequence = -1; int32_t sequence = -1;
int result = tf_ssb_db_get_latest_message_by_author(ssb, identity, &sequence, NULL, 0) ? EXIT_SUCCESS : EXIT_FAILURE; int result = tf_ssb_db_get_latest_message_by_author(ssb, identity, &sequence, NULL, 0) ? EXIT_SUCCESS : EXIT_FAILURE;
tf_printf("%" PRId64 "\n", sequence); tf_printf("%d\n", sequence);
tf_ssb_destroy(ssb); tf_ssb_destroy(ssb);
tf_free((void*)default_db_path); tf_free((void*)default_db_path);
return result; return result;
@ -983,7 +1148,8 @@ static int _tf_command_get_identity(const char* file, int argc, char* argv[])
if (show_usage) if (show_usage)
{ {
tf_printf("\n%s get_identity [options]\n\n", file); tf_printf("\nUsage: %s get_identity [options]\n\n", file);
tf_printf("%s\n\n", _description("get_identity"));
tf_printf("options:\n"); tf_printf("options:\n");
tf_printf(" -d, --db-path db_path SQLite database path (default: %s).\n", default_db_path); tf_printf(" -d, --db-path db_path SQLite database path (default: %s).\n", default_db_path);
tf_printf(" -h, --help Show this usage information.\n"); tf_printf(" -h, --help Show this usage information.\n");
@ -1040,7 +1206,8 @@ static int _tf_command_get_profile(const char* file, int argc, char* argv[])
if (show_usage || !identity) if (show_usage || !identity)
{ {
tf_printf("\n%s get_profile [options]\n\n", file); tf_printf("\nUsage: %s get_profile [options]\n\n", file);
tf_printf("%s\n\n", _description("get_profile"));
tf_printf("options:\n"); tf_printf("options:\n");
tf_printf(" -d, --db-path db_path SQLite database path (default: %s).\n", default_db_path); tf_printf(" -d, --db-path db_path SQLite database path (default: %s).\n", default_db_path);
tf_printf(" -i, --identity identity Account for which to get profile information.\n"); tf_printf(" -i, --identity identity Account for which to get profile information.\n");
@ -1099,7 +1266,8 @@ static int _tf_command_get_contacts(const char* file, int argc, char* argv[])
if (show_usage || !identity) if (show_usage || !identity)
{ {
tf_printf("\n%s get_contacts [options]\n\n", file); tf_printf("\nUsage: %s get_contacts [options]\n\n", file);
tf_printf("%s\n\n", _description("get_contacts"));
tf_printf("options:\n"); tf_printf("options:\n");
tf_printf(" -d, --db-path db_path SQLite database path (default: %s).\n", default_db_path); tf_printf(" -d, --db-path db_path SQLite database path (default: %s).\n", default_db_path);
tf_printf(" -i, --identity identity Account from which to get contact information.\n"); tf_printf(" -i, --identity identity Account from which to get contact information.\n");
@ -1161,17 +1329,19 @@ static int _tf_command_verify(const char* file, int argc, char* argv[])
const char* identity = NULL; const char* identity = NULL;
const char* default_db_path = _get_db_path(); const char* default_db_path = _get_db_path();
const char* db_path = default_db_path; const char* db_path = default_db_path;
int32_t sequence = 0;
bool show_usage = false; bool show_usage = false;
while (!show_usage) while (!show_usage)
{ {
static const struct option k_options[] = { static const struct option k_options[] = {
{ "id", required_argument, NULL, 'i' }, { "id", required_argument, NULL, 'i' },
{ "sequence", required_argument, NULL, 's' },
{ "db-path", required_argument, NULL, 'd' }, { "db-path", required_argument, NULL, 'd' },
{ "help", no_argument, NULL, 'h' }, { "help", no_argument, NULL, 'h' },
{ 0 }, { 0 },
}; };
int c = getopt_long(argc, argv, "i:d:h", k_options, NULL); int c = getopt_long(argc, argv, "i:s:d:h", k_options, NULL);
if (c == -1) if (c == -1)
{ {
break; break;
@ -1187,6 +1357,9 @@ static int _tf_command_verify(const char* file, int argc, char* argv[])
case 'i': case 'i':
identity = optarg; identity = optarg;
break; break;
case 's':
sequence = atoi(optarg);
break;
case 'd': case 'd':
db_path = optarg; db_path = optarg;
break; break;
@ -1195,18 +1368,49 @@ static int _tf_command_verify(const char* file, int argc, char* argv[])
if (show_usage) if (show_usage)
{ {
tf_printf("\n%s import [options] [paths...]\n\n", file); tf_printf("\nUsage: %s verify [options]\n\n", file);
tf_printf("%s\n\n", _description("verify"));
tf_printf("options:\n"); tf_printf("options:\n");
tf_printf(" -i, --identity identity Identity to verify.\n"); tf_printf(" -i, --identity identity Identity to verify.\n");
tf_printf(" -s, --sequence sequence Sequence number to debug.\n");
tf_printf(" -d, --db-path db_path SQLite database path (default: %s).\n", default_db_path); tf_printf(" -d, --db-path db_path SQLite database path (default: %s).\n", default_db_path);
tf_printf(" -h, --help Show this usage information.\n"); tf_printf(" -h, --help Show this usage information.\n");
tf_free((void*)default_db_path); tf_free((void*)default_db_path);
return EXIT_FAILURE; return EXIT_FAILURE;
} }
tf_printf("Verifying %s...\n", identity); bool verified = false;
tf_ssb_t* ssb = tf_ssb_create(NULL, NULL, db_path, NULL); tf_ssb_t* ssb = tf_ssb_create(NULL, NULL, db_path, NULL);
bool verified = tf_ssb_db_verify(ssb, identity); if (identity)
{
tf_printf("Verifying %s...\n", identity);
verified = tf_ssb_db_verify(ssb, identity, sequence, true);
}
else
{
sqlite3* db = tf_ssb_acquire_db_reader(ssb);
sqlite3_stmt* statement = NULL;
if (sqlite3_prepare_v2(db, "SELECT DISTINCT author FROM messages ORDER BY author", -1, &statement, NULL) == SQLITE_OK)
{
verified = true;
while (sqlite3_step(statement) == SQLITE_ROW)
{
const char* identity = (const char*)sqlite3_column_text(statement, 0);
tf_printf("Verifying %s...", identity);
if (tf_ssb_db_verify(ssb, identity, sequence, true))
{
tf_printf("success.\n");
}
else
{
tf_printf("fail.\n");
verified = false;
}
}
sqlite3_finalize(statement);
}
tf_ssb_release_db_reader(ssb, db);
}
tf_ssb_destroy(ssb); tf_ssb_destroy(ssb);
tf_free((void*)default_db_path); tf_free((void*)default_db_path);
return verified ? EXIT_SUCCESS : EXIT_FAILURE; return verified ? EXIT_SUCCESS : EXIT_FAILURE;
@ -1485,10 +1689,19 @@ static int _tf_command_run(const char* file, int argc, char* argv[])
} }
} }
for (int i = optind; i < argc; i++)
{
tf_printf("Unexpected argument: %s\n", argv[i]);
show_usage = true;
}
if (show_usage) if (show_usage)
{ {
tf_printf("\n%s run [options]\n\n", file); tf_printf("\nUsage: %s run [options]\n\n", file);
tf_printf("options\n"); #if !defined(__ANDROID__)
tf_printf("%s\n\n", _description("run"));
#endif
tf_printf("options:\n");
tf_printf(" -s, --script script Script to run (default: core/core.js).\n"); tf_printf(" -s, --script script Script to run (default: core/core.js).\n");
tf_printf(" -d, --db-path path SQLite database path (default: %s).\n", default_db_path); tf_printf(" -d, --db-path path SQLite database path (default: %s).\n", default_db_path);
tf_printf(" -k, --ssb-network-key key SSB network key to use.\n"); tf_printf(" -k, --ssb-network-key key SSB network key to use.\n");
@ -1573,6 +1786,9 @@ static int _tf_command_sandbox(const char* file, int argc, char* argv[])
if (show_usage) if (show_usage)
{ {
tf_printf("\nUsage: %s sandbox [options]\n\n", file); tf_printf("\nUsage: %s sandbox [options]\n\n", file);
#if !defined(__ANDROID__)
tf_printf("%s\n\n", _description("sandbox"));
#endif
tf_printf("options:\n"); tf_printf("options:\n");
tf_printf(" -h, --help Show this usage information.\n"); tf_printf(" -h, --help Show this usage information.\n");
tf_printf(" -f, --fd File descriptor with which to communicate with parent process.\n"); tf_printf(" -f, --fd File descriptor with which to communicate with parent process.\n");
@ -1842,6 +2058,7 @@ void tf_run_thread_start(const char* zip_path)
#else #else
int main(int argc, char* argv[]) int main(int argc, char* argv[])
{ {
setvbuf(stdout, NULL, _IONBF, 0);
_startup(argc, argv); _startup(argc, argv);
ares_library_init(0); ares_library_init(0);

View File

@ -22,8 +22,6 @@ static int64_t s_tls_malloc_size;
static int64_t s_js_malloc_size; static int64_t s_js_malloc_size;
static int64_t s_sqlite_malloc_size; static int64_t s_sqlite_malloc_size;
extern uint32_t fnv32a(const void* buffer, int length, uint32_t start);
static size_t _tf_mem_round_up(size_t size) static size_t _tf_mem_round_up(size_t size)
{ {
return (size + 7) & ~7; return (size + 7) & ~7;
@ -156,7 +154,7 @@ static void _tf_mem_summarize(void* ptr, size_t size, int frames_count, void* co
{ {
summary_t* summary = user_data; summary_t* summary = user_data;
tf_mem_allocation_t allocation = { tf_mem_allocation_t allocation = {
.stack_hash = fnv32a(frames, sizeof(void*) * frames_count, 0), .stack_hash = tf_util_fnv32a(frames, sizeof(void*) * frames_count, 0),
.count = 1, .count = 1,
.size = size, .size = size,
.frames_count = frames_count, .frames_count = frames_count,
@ -388,29 +386,17 @@ size_t tf_mem_get_uv_malloc_size()
return s_uv_malloc_size; return s_uv_malloc_size;
} }
#if defined(__OpenBSD__)
static void* _tf_tls_alloc(size_t size)
#else
static void* _tf_tls_alloc(size_t size, const char* file, int line) static void* _tf_tls_alloc(size_t size, const char* file, int line)
#endif
{ {
return _tf_alloc(&s_tls_malloc_size, size); return _tf_alloc(&s_tls_malloc_size, size);
} }
#if defined(__OpenBSD__)
static void* _tf_tls_realloc(void* ptr, size_t size)
#else
static void* _tf_tls_realloc(void* ptr, size_t size, const char* file, int line) static void* _tf_tls_realloc(void* ptr, size_t size, const char* file, int line)
#endif
{ {
return _tf_realloc(&s_tls_malloc_size, ptr, size); return _tf_realloc(&s_tls_malloc_size, ptr, size);
} }
#if defined(__OpenBSD__)
static void _tf_tls_free(void* ptr)
#else
static void _tf_tls_free(void* ptr, const char* file, int line) static void _tf_tls_free(void* ptr, const char* file, int line)
#endif
{ {
_tf_free(&s_tls_malloc_size, ptr); _tf_free(&s_tls_malloc_size, ptr);
} }

View File

@ -489,7 +489,7 @@ static JSValue _socket_connect(JSContext* context, JSValueConst this_val, int ar
const char* node = JS_ToCString(context, argv[0]); const char* node = JS_ToCString(context, argv[0]);
const char* port = JS_ToCString(context, argv[1]); const char* port = JS_ToCString(context, argv[1]);
snprintf(socket->_peerName, sizeof(socket->_peerName), "%s", node); tf_string_set(socket->_peerName, sizeof(socket->_peerName), node);
socket_resolve_data_t* data = tf_malloc(sizeof(socket_resolve_data_t)); socket_resolve_data_t* data = tf_malloc(sizeof(socket_resolve_data_t));
memset(data, 0, sizeof(*data)); memset(data, 0, sizeof(*data));
@ -1062,9 +1062,7 @@ static void _socket_onShutdown(uv_shutdown_t* request, int status)
} }
else else
{ {
char error[256]; tf_task_reject_promise(socket->_task, promise, JS_ThrowInternalError(tf_task_get_context(socket->_task), "uv_shutdown: %s", uv_strerror(status)));
snprintf(error, sizeof(error), "uv_shutdown: %s", uv_strerror(status));
tf_task_reject_promise(socket->_task, promise, JS_ThrowInternalError(tf_task_get_context(socket->_task), "%s", error));
} }
tf_free(request); tf_free(request);
} }

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