285 Commits

Author SHA1 Message Date
f42811d3d4 build: Let's release 0.0.26. 2024-12-23 11:12:26 -05:00
c3b1832cfb update: CodeMirror. 2024-12-23 11:08:36 -05:00
eb6753afe1 ssb: prettier. 2024-12-23 11:08:27 -05:00
5051cecb84 ssb: Make the emoji picker behave a little better, still. 2024-12-23 10:28:12 -05:00
cd03ede358 ssb: Work in progress trying to get the emoji picker in line with some of the other modal dialogs. 2024-12-22 14:45:16 -05:00
6563f8c738 ssb: Avoid unqualified emojis. I noticed the red heart was missing ever since I reprocessed the list. 2024-12-22 14:19:28 -05:00
e5279b4827 ssb: Show unread status on all message types. 2024-12-22 13:41:03 -05:00
79ff505963 ssb: Fix various channel / unread status / show new messages bugs. 2024-12-22 13:16:56 -05:00
8a67eba5fc ssb: Add a store_blob command. 2024-12-22 10:41:58 -05:00
6609a5f340 core: Length of undefined is 0. It's fine. Quiet some errors. 2024-12-18 20:54:13 -05:00
d9972cb349 tests: Work around an intermittent -t=auto failure. The 'Edit Profile' click is getting lost as things rapidly update? I haven't ever seen it as a human clicking. 2024-12-18 20:09:50 -05:00
28d2539432 ssb: A first pass at showing private messages next to channels. 2024-12-18 20:03:53 -05:00
f28386b71f ssb: Off by one on the unread line. 2024-12-18 12:46:02 -05:00
53717076f5 ssb: Fix some unread marker issues. 2024-12-18 12:43:25 -05:00
a9aa928629 tests: Prefer tf_printf. 2024-12-17 20:41:27 -05:00
8df121148d update: c-ares 1.34.4. 2024-12-15 08:33:38 -05:00
5e23c32ae8 build: Fix a potential null dereference? 2024-12-15 07:53:24 -05:00
9c0f6481c0 ssb: Try to go easier on the main thread, still. 2024-12-14 21:36:33 -05:00
68ae45dd58 ssb: Prevent -t=bench from stalling. 2024-12-11 20:53:25 -05:00
3091747438 ssb: prettier. 2024-12-11 20:35:32 -05:00
2f266b8dd4 ssb: Attempt to request more feeds as more contact messages come in. 2024-12-11 20:26:28 -05:00
ee20b87ee2 ssb: Alt+up/down to cycle through channels. 2024-12-11 12:53:04 -05:00
83e025d0bb update: CodeMirror. 2024-12-11 12:41:42 -05:00
5115c6e217 ssb: Fix an instance of channels being stuck unread. 2024-12-10 21:09:55 -05:00
76f6a94de5 ssb: Fix replication hops usage. Thanks @Cashew. 2024-12-10 19:18:01 -05:00
954830be18 ssb: Allow encrypting/decrypting with the server identity as an admin. 2024-12-10 12:43:07 -05:00
ea70299a45 update: sqlite 3.47.2. 2024-12-08 16:47:21 -05:00
88da071ed6 ssb: We can load more messages by author, now. 2024-12-08 09:40:02 -05:00
1dbf162a71 ssb: Bring back the updating date while loading. 2024-12-07 14:58:01 -05:00
1c0964753b ssb: Correctness around loading messages by time range. 2024-12-07 14:25:19 -05:00
daa1c7f577 ssb: Don't miss contact messages that aren't followed by non-follow messages. 2024-12-07 14:08:53 -05:00
854416ceb2 ssb: Make the depth arg to ssb.following() match the docs. 2024-12-07 11:28:33 -05:00
2230351e3e ssb: Show the load more button for mentions. 2024-12-07 10:38:34 -05:00
7da3244da2 ssb: prettier. 2024-12-05 20:47:02 -05:00
bfeb0c2988 update: prettier. 2024-12-05 20:46:23 -05:00
d4e75c1dec ssb: Move mentions into the channels sidebar. 2024-12-05 20:45:20 -05:00
405bddcde0 ssb: Make the tab bar stay on top of the content. Weird, the random things that were showing up on top. 2024-12-04 21:23:17 -05:00
8a27c45ab1 ssb: An experiment in including hashtag mentions in channel content. Also sort the channel list like I thought I already did. 2024-12-04 20:50:46 -05:00
10b15896b3 ssb: Fix the loading cancel button. 2024-12-04 20:28:57 -05:00
0e97bbe37c android: Fix some crashes, callstacks, and warnings I'm seeing in the logs. 2024-12-04 20:05:50 -05:00
e0d7e90894 ssb: Add an overlay for the sidebar so that it can be closed by tapping back on the content. 2024-12-03 19:40:08 -05:00
5d13f6aab6 wiki: Back to latest commonmark built as mjs. 2024-12-02 18:45:23 -05:00
1ebfbbe89e wiki: Go back to the last version that worked. 2024-12-02 17:57:08 -05:00
91ad43fdfc ssb: A more plausibly correct way to load new messages correctly. 2024-12-01 18:20:57 -05:00
6fe6fc180d ssb: New theme, better load, remove debug prints. 2024-12-01 16:27:59 -05:00
d84d0bec38 ssb: This index help channel status load faster. 2024-12-01 16:26:40 -05:00
7e7b1c6ee1 ssb: Make #hashtags direct to channels. 2024-12-01 15:32:35 -05:00
effb354d1b ssb: Working toward a more sensible unread indication and user interface for setting read/unread. 2024-12-01 12:56:31 -05:00
ba7d1ad35f core: This case is not a good cause to crash. 2024-12-01 09:48:15 -05:00
3ca2b19502 ssb: Canceling loads, more mobile-friendly sidebar, and respond to channel subscription changes. 2024-11-30 17:49:36 -05:00
8e0d91dcf5 security: Setting global settings requires approval. 2024-11-30 16:58:48 -05:00
cd2c2587ae ssb: Merge in the new very work in progress channels interface. 2024-11-30 15:05:14 -05:00
53044696ba ssb: Just request blobs for all references from about messages for now. Much faster than narrowing down to the most recent images. 2024-11-29 10:28:16 -05:00
6d6927213f Revert "ssb: Try harder to replicate profile images, even if they were set before our blob replication cutoff."
This reverts commit 7f4e2617ee.
2024-11-29 08:54:54 -05:00
be1b5bce4f test: Simplify my selection helper syntax a bit. 2024-11-28 19:58:51 -05:00
4b4fd0735b test: Make -t auto a bit more resilient by hoisting all the retries into one place that makes sense to me. 2024-11-28 17:51:22 -05:00
c565b2a31f bot: Make sure release messages get through. 2024-11-28 11:11:25 -05:00
55f2261905 prettier: Update the copy of prettier used in the editor. 2024-11-28 11:00:59 -05:00
51912f2b83 ssb: Update emojis.json, and add a script to regenerate it. 2024-11-28 09:16:07 -05:00
7f4e2617ee ssb: Try harder to replicate profile images, even if they were set before our blob replication cutoff. 2024-11-27 21:42:54 -05:00
960a385202 update: CodeMirror. 2024-11-27 15:20:32 -05:00
21f48d3485 build: Let's start work on 0.0.26. 2024-11-27 12:24:42 -05:00
7f9605e55f nix: Update for 0.0.25. 2024-11-27 12:23:52 -05:00
cc409dc3f7 build: Let's build 0.0.25. 2024-11-27 12:10:17 -05:00
af6091760c ssb+docs: prettier. 2024-11-27 12:07:00 -05:00
e1d93c003c docs: Update docs from wiki. 2024-11-27 10:13:16 -05:00
ff9dd2dd03 haiku: Disable a bit of a test that is giving me an SQLITE_PROTOCOL error only on Haiku. 2024-11-27 15:05:23 -05:00
7a306bb3d2 build: Fix a regex warning. 2024-11-27 14:36:50 -05:00
7ffc148358 build: I wanted to get the binary out of the makefile to appease F-Droid, and one thing lead to another. 2024-11-27 09:28:14 -05:00
50fef2edfa build: Fix on OpenBSD. TIL awk. 2024-11-27 09:06:02 -05:00
aa40084010 build: Redid this thing in sed to make it work on more platforms. 2024-11-26 22:55:01 -05:00
740d788c7c storage: Show accounts with the most follows, for help pruning accounts. 2024-11-26 16:25:15 -05:00
4c2fa2c1b3 storage: Show totals, too. 2024-11-26 16:05:28 -05:00
4350c7b7a9 storage: Add a little app to show something about feed sizes. 2024-11-26 15:59:02 -05:00
595f14d98d docs: Update some docs links to the gitea wiki and generally refresh the README.md slightly. 2024-11-26 11:42:33 -05:00
2e95d6ea63 docs: Add the Tilde Friends gitea wiki as a git submodule to replace the docs directory. Maybe I will succeed at doing something with it if it is more web-facing. 2024-11-26 11:30:57 -05:00
0da6abeb98 ssb: We can trace request names these days. 2024-11-26 11:14:30 -05:00
e4e050e8e7 ssb: Fix some message link encoding. 2024-11-26 08:42:51 -05:00
5bc082b75e build: Prepare a changelog for the next release. 2024-11-25 21:12:00 -05:00
beedbd7646 build: Attempt to self-document the makefile. 2024-11-25 21:11:36 -05:00
507b069ffe cleanup: prettier. 2024-11-25 20:05:40 -05:00
71444b0427 ssb: Shutdown fixes. 2024-11-25 17:14:16 -05:00
a08bba438e update: sqlite 3.47.1. 2024-11-25 13:16:20 -05:00
df1e6711af ssb: Add a setting to periodically clean up un-followed feeds. 2024-11-25 12:53:28 -05:00
f6d4e934e3 ssb: Adjust the follow/hops policies. Replication defaults to 2 hops, counted in the same way as the docs, and is configurable. 2024-11-25 11:20:01 -05:00
d5bd4c6735 test: Use -t=auto to update some screenshots. 2024-11-25 09:53:11 -05:00
eb12ba6ed2 test: Use -t=auto to generate some screenshots, detect -t=auto failure more reliably, exercise setting the initial profile, and fix various bugs that fell out. 2024-11-25 09:38:49 -05:00
6e83c08535 ssb: Add an index that helps me calculate feed size about 8x faster. 2024-11-23 17:50:32 -05:00
b6bfdec48d ssb: Move the refresh/sync button to the navigation bar as an experiment. 2024-11-23 16:49:33 -05:00
f9ec796291 bot: Give more information about new issues. 2024-11-23 13:50:23 -05:00
3beb1d0683 update: CodeMirror. 2024-11-20 20:26:30 -05:00
8836c7f0ca cleanup: prettier + format. 2024-11-20 20:24:58 -05:00
ef5ce1d6e1 web: Show the little graphs if the Tilde Friends verison thingy is expanded. I want to be able to optionally see these on mobile. 2024-11-20 20:06:33 -05:00
0ea1213139 ssb: Use the same refresh character in two places. 2024-11-20 19:46:41 -05:00
51fe372f60 ssb: Stick the stylesheet on document.body. No more fonts changing when various dialogs show up. 2024-11-20 19:44:27 -05:00
eb8f9f8936 web: Add some meta tags to make it show up better in search engines / embeds maybe. 2024-11-20 19:24:13 -05:00
afc1524874 bot: Scrape my changes better from gitea RSS. 2024-11-18 22:58:51 -05:00
fbb975625c update: speedscope 1.21.0. 2024-11-17 19:07:27 -05:00
53e75d8209 cleanup: Consolidate countof macros. 2024-11-13 20:22:42 -05:00
5bdf970c10 ssb: Don't list broadcasts for identities to which we are already connected. 2024-11-13 19:23:04 -05:00
50089f72c6 ssb: We can show what state a connection is in. 2024-11-13 19:15:59 -05:00
62e15e0208 update: CodeMirror. 2024-11-13 19:03:01 -05:00
3d8b02a7f3 ssb+issues+core: prettier 2024-11-13 18:58:09 -05:00
20701d9cf1 ssb: Missed a few connection failures for context. 2024-11-13 18:44:14 -05:00
fa94442eb2 ssb: Populate more connection errors with context. 2024-11-13 18:35:17 -05:00
68ff77e172 ssb: Hook up connect error messages more thoroughly. 2024-11-13 18:20:14 -05:00
102e9be3a8 update: c-ares 1.34.3. 2024-11-13 17:54:10 -05:00
92bf01a183 ssb+issues: Fix missing dependencies of my commonmarkjs extensions. 2024-11-12 21:47:15 -05:00
559504ae29 security: Use commonmarkjs with {safe: true} as intended. 2024-11-12 20:43:03 -05:00
9b00b41a1e ssb: Connection result preliminary hookup to ui, and fix some fallout. 2024-11-11 22:24:54 -05:00
b1f6ad17e1 ssb: Pass around reasons for failing to connect. This will help get that information to the ui when I finish hooking it up. 2024-11-11 22:12:41 -05:00
e7979fe9db test: Add more retries until selenium cooperates. 2024-11-11 21:06:04 -05:00
7a276adbbc ssb: Size blob ID buffers appropriately. 2024-11-11 21:05:29 -05:00
db4997fdc4 bot: Remove the header. 2024-11-11 08:19:46 -05:00
44ebb841f0 bot: Fix empty buttfeed posts, and use requested RSS feed for Habitat. 2024-11-10 19:30:36 -05:00
09ae4e2096 ssb: Handle the inevitable %25 in a document hash better? 2024-11-09 18:04:58 -05:00
0b46efe4ea test: Hack around an intermitted -t=auto failure. 2024-11-09 15:09:08 -05:00
f1dda43e66 ui: Click off the identity menu to close it. 2024-11-09 14:52:54 -05:00
ce483138d7 ssb: Fighting with profile CSS. 2024-11-09 14:41:40 -05:00
73cc39226d bot: Some fixes to get SecureScuttlebuttFeed running. 2024-11-09 09:24:13 -05:00
57257f63dd bot: Add a little script to post about recent development activity from a handful of RSS feeds I've gathered. 2024-11-09 09:01:34 -05:00
88b25790e8 ssb: Remove some pointless logging. 2024-11-06 20:56:10 -05:00
e01defc4aa update: CodeMirror. 2024-11-06 20:49:03 -05:00
cb50c43e93 build: We all cope in our own ways. 2024-11-06 20:42:49 -05:00
5908d15f91 js: Move default global settings to C. 2024-11-06 20:29:48 -05:00
f66cfaec12 http: Fix some caching issues. 2024-11-06 12:41:54 -05:00
259f92c53b http: Populate query and headers for handler.js like we used to. 2024-11-04 21:46:38 -05:00
a84f850e91 http: Bring back handler.js support, mostly. Partly in C, this time. 2024-11-03 21:09:57 -05:00
5a765e6f07 js: Also unused. 2024-11-03 07:44:31 -05:00
791889c659 js: Remove some unused code. 2024-11-03 07:38:52 -05:00
5da63faf1f http+js: Move app blob handling from JS to C. handler.js support has been temporarily removed. 2024-11-02 21:37:14 -04:00
30d108fc35 http: URL pattern matcher fixes. 2024-11-02 20:10:55 -04:00
a09fefab5e http: Add a more expressive but still nowhere near regex URL pattern matcher. 2024-11-02 19:22:04 -04:00
f74ca1c236 test: Remove some debug prints, whoops. 2024-11-02 16:32:05 -04:00
30e027092b test: Cover more ways to request apps and files. 2024-11-02 15:43:03 -04:00
fd4ac7c9b9 test: Test some expectes results from http requests to various paths. 2024-11-02 14:11:54 -04:00
4482049b94 log: Show the version number in the welcome banner. 2024-11-02 08:45:47 -04:00
5839380437 update: CodeMirror to latest. 2024-11-02 08:45:47 -04:00
2152470fdc update: libbacktrace to latest. 2024-11-02 08:45:47 -04:00
93b2a81495 test: Fix -t=publish on haiku. 2024-11-01 18:55:27 -04:00
e139e952c0 ssb: Fix some spacing in the permissions dialog. 2024-11-01 18:18:16 -04:00
cf1c57ccb8 build: Let's start work on 0.0.25. 2024-11-01 18:01:10 -04:00
f7a2138488 nix: Update version to 0.0.24. 2024-10-30 19:40:12 -04:00
9614d03bef ssb: Fix a timer leak I observed trying to wrap up 0.0.24. 2024-10-30 19:32:24 -04:00
32a335c676 test: Retry harder. 2024-10-30 19:32:05 -04:00
06e27fc1e0 docs: Update the 0.0.24 changelog. 2024-10-30 19:31:33 -04:00
1f40e8dcd9 build: Let's build 0.0.24. 2024-10-30 12:56:20 -04:00
77ff8cef1f db: Fix the db app, and show a message instead of an ugly error when you're not signed in. 2024-10-29 20:19:50 -04:00
ef844fbccb build: Oh, you can generate a .flatpak file, if that's your thing. 2024-10-27 18:50:07 -04:00
070dc5a4c0 build: flatpak filesystem access tweaks. 2024-10-27 14:37:37 -04:00
177ef1cdcc build: A flatpak experiment. I still don't get it. 2024-10-27 14:31:11 -04:00
4b1ebf02e1 js: Remove a stale /save reference, and exercise re-saving an app in the test. 2024-10-27 14:05:20 -04:00
863e50203e js: Move /save to C. 2024-10-27 13:42:56 -04:00
01b8c209de core: Testing a theory to encourage clean shutdowns. 2024-10-25 15:34:43 -04:00
30e92f2bc1 js: Fix typo in /delete. 2024-10-25 15:34:01 -04:00
02accabb4a js: Oh yeah, administrators can delete core apps still. 2024-10-25 15:20:54 -04:00
fa00a41fe0 js: Move app delete to C. 2024-10-25 13:58:06 -04:00
2e66666bdf ssb: Indicate which connections are one-shot / sync now connections. 2024-10-25 12:53:45 -04:00
4fe3c9a751 test: Verify that deleting apps actually does something. 2024-10-25 12:34:22 -04:00
0a35e14590 js: Fix database.getall(). 2024-10-23 21:50:34 -04:00
e979c176e3 test: Exercise nominally creating and deleting an app. 2024-10-23 18:48:42 -04:00
a0d9c3dc29 js: Move the global 404 response to C. 2024-10-23 18:27:36 -04:00
efcb68351c docs: Reminding myself how to make a release. 2024-10-23 18:06:56 -04:00
94e8bf2e1c test: Add some nominal testing for the new publish command. 2024-10-23 15:57:44 -04:00
82d1a294a6 ssb: prettier. 2024-10-23 15:38:49 -04:00
de20274589 ssb: Add a publish command that can be used to publish messages from the command-line. 2024-10-23 15:38:07 -04:00
2f193e64c8 ssb: Show muxrpc command names when possible in verbose logging. 2024-10-23 15:37:28 -04:00
86751362cb ssb: Indicate which muxrpc sends failed, and use that to fix some replication nonsense and log noise. 2024-10-23 14:13:55 -04:00
4118323631 build: Fix some cflags not making it to libsodium. Disable the libsodium -flto warning a more different way. 2024-10-23 12:02:41 -04:00
0d134f7f10 update: CodeMirror to latest. 2024-10-23 11:34:30 -04:00
409724cfcd update: OpenSSL 3.4.0. 2024-10-23 11:32:45 -04:00
799a33be40 update: libbacktrace to latest. 2024-10-23 11:20:23 -04:00
2fa9c66495 update: libuv 1.49.2. 2024-10-23 11:05:15 -04:00
ad818a8e7e update: sqlite 3.47.0. 2024-10-23 11:01:42 -04:00
581f72b3f8 ssb: Disallow rich text paste on Firefox. Didn't realize it doesn't support contenteditable='plaintext-only'. 2024-10-17 12:41:50 -04:00
1dd7e4347c js: Kill setGlobalSettings. 2024-10-16 21:02:48 -04:00
36cc9398c7 js: Move storePermission to C. 2024-10-16 20:36:53 -04:00
68817feeec js: Kill getSessionProcessBlob. 2024-10-16 19:50:31 -04:00
97661e2ca2 http: Fix some headers. 2024-10-16 19:26:26 -04:00
72def5ae6d js: Move /view to C. 2024-10-16 19:16:45 -04:00
e638b155a1 js: Kill gGlobalSettings. Decouples things a bit. 2024-10-16 18:11:08 -04:00
32db18b0d6 ssb: Close the reactions list dialog by clicking off of it. 2024-10-16 12:35:10 -04:00
b653a5250d build: Appease gcc 14. 2024-10-15 12:41:47 -04:00
30329f7cad ssb: No duplicate connections, even with tunnels. This is confusing. 2024-10-14 12:44:21 -04:00
29a1478c86 ssb: No duplicate tunnels. 2024-10-13 18:13:31 -04:00
c882bf31ec docs: grammar 2024-10-13 14:58:21 -04:00
17ccb8f083 update: libuv 1.49.1. 2024-10-13 14:46:24 -04:00
0e7d2a8b0e ssb: The identity app now lets you switch out the server identity if you are an administrator. 2024-10-13 14:40:14 -04:00
3743543ef8 welcome: prettier 2024-10-13 14:19:55 -04:00
700dd7b45a build: Appease OpenBSD. 2024-10-11 19:04:49 -04:00
c2eb73fd8a update: c-ares 1.34.1. 2024-10-11 18:25:09 -04:00
e1f4f7f95b ssb: Don't stretch profile images when fitting into a circle. I swear I did this ages ago. 2024-10-10 21:41:34 -04:00
37401409c6 ios: Include data in the app. How did this ever work? 2024-10-10 21:32:35 -04:00
b282631cd5 ios: Mobile provision junk. 2024-10-10 20:43:13 -04:00
9618d3b3f3 welcome: Link to the open testing Google Play build. 2024-10-09 12:20:37 -04:00
c9f997d121 update: commonmark 0.31.2. 2024-10-08 20:40:29 -04:00
f1dee2a089 ssb: Why would I not log the host of failed DNS requests? 2024-10-08 20:16:04 -04:00
8273277c91 editor: Fix in-browser prettification of html files. 2024-10-08 20:15:04 -04:00
9758844da3 editor: Fix visible whitespace toggle. 2024-10-08 19:56:58 -04:00
e41c7fbbc7 welcome: prettier 2024-10-08 19:41:55 -04:00
24db8a5a49 welcome: Slight wording updates. 2024-10-08 19:28:23 -04:00
36e82b9873 ssb: Sync now connects to room members one level deep. 2024-10-08 19:10:33 -04:00
8a32f2b8b1 Latest CodeMirror. 2024-10-08 12:44:49 -04:00
277830bc3c format: Sort includes. Yes, please. 2024-10-08 12:19:44 -04:00
a8fa969114 Lit 3.2.1. 2024-10-08 12:18:37 -04:00
c3f3dced68 docs: I figured out how to disable the gitea release zips lacking submodules (DISABLE_DOWNLOAD_SOURCE_ARCHIVES), so remove the caveat about them from the docs. 2024-10-06 12:16:30 -04:00
85fce59c0c ssb: Sync on demand fixes. Avoid keeping message streams live in this mode. 2024-10-06 11:50:49 -04:00
8a6147d512 ssb: Beginnings of a "sync now" mode for mobile. 2024-10-06 11:14:37 -04:00
e799b256b2 ssb: Even more muxrpc activity status fixes. 2024-10-05 21:00:50 -04:00
b222dc0ca8 Merge branch 'main' of https://dev.tildefriends.net/cory/tildefriends 2024-10-05 20:44:17 -04:00
c52c6b04ca ssb: muxrpc activity status fixes. 2024-10-05 20:44:01 -04:00
b95eed46bb ssb: Fix a request leak in tunnel.connect. 2024-10-04 22:05:17 -04:00
7c36a543da ssb: Fix a leaked request and a shutdown error. 2024-10-04 12:39:39 -04:00
90e000c18e ssb: Fix activity indication of muxrpc requests expiring. 2024-10-03 12:41:45 -04:00
1bb9d737d8 ssb: Show activity for each muxrpc request. 2024-10-02 20:43:51 -04:00
9a5db2ec51 appimage: Put update information in the appimage. 2024-10-02 20:03:44 -04:00
dbed29a044 Update prettier. And run it some more. 2024-10-02 18:49:17 -04:00
681859531c muxrpc: Simplifying comparing RPC names. This has just always bugged me. 2024-10-02 18:46:12 -04:00
8e1ad6b16a js: Unused external. 2024-10-02 18:12:28 -04:00
5448f1ba2d Update CodeMirror. 2024-10-02 18:00:40 -04:00
e43da4e1a3 welcome: Better OpenSSL link. 2024-10-02 17:56:36 -04:00
eaa9da49cc welcome: F-Droid/AppImage links. 2024-10-02 17:55:01 -04:00
40873b529c build: Suppress a warning in libuv on arm. 2024-09-30 12:37:41 -04:00
8cc4c19d73 Prettier and generated files I've missed. 2024-09-30 12:15:27 -04:00
bb9c18faf1 Some missing log newlines. 2024-09-30 12:13:57 -04:00
fabdfb76b9 android: readParcelable compatibility. 2024-09-29 08:18:46 -04:00
bce263a928 android: Use FileObserver, which is actually compatible with api level 24 which we claim to support. 2024-09-29 00:17:38 -04:00
195920e476 android: Avoid a ClosedWatchServiceException. 2024-09-28 23:25:28 -04:00
a821d895c5 docs: Give working advice on how to get the tree and dependencies. 2024-09-28 07:11:47 -04:00
ab1b6ec27d build: Add a dependency off appimagetool?? 2024-09-27 22:10:01 -04:00
6dc099809f build: This creates a working AppImage. 2024-09-27 21:19:18 -04:00
03c8b75994 Let's start work on 0.0.24. 2024-09-25 20:26:57 -04:00
38887452ad nix => 0.0.23. 2024-09-25 20:20:14 -04:00
7512edad59 build: I forgot to build the .xz as part of dist. 2024-09-25 20:02:45 -04:00
944c895bcd Generated 0.0.23 files. Oops. 2024-09-25 19:55:12 -04:00
e7d87ee8e2 I think that worked. Let's build 0.0.23. 2024-09-25 19:49:52 -04:00
cfdbd10635 ci: Oh, we can not require fuse? 2024-09-25 19:45:05 -04:00
d3a2d8733f ci: Maybe we don't need to manually load it? 2024-09-25 19:37:17 -04:00
a7e623d817 ci: apt install fuse3? 2024-09-25 19:35:47 -04:00
3f0c37cea4 ci: modprobe harder. 2024-09-25 19:33:30 -04:00
2c96a6d22a ci: modprobe fuse? Disable things to get this going faster until it works. 2024-09-25 19:30:33 -04:00
57b4214a72 AppImages require FUSE to run. 2024-09-25 19:10:13 -04:00
433b3d39d9 ci: Build the appimage, for real.nofoolin. 2024-09-25 18:50:06 -04:00
26441ed45c Let's try to artifact the appimage. 2024-09-25 12:49:32 -04:00
92cd38c2a0 Change notes. 2024-09-25 12:33:06 -04:00
3b5a06794f libuv 1.49.0. 2024-09-25 12:11:17 -04:00
d104409272 Prevent votes from overflowing. 2024-09-23 12:43:48 -04:00
e5f58c2898 Produce user info for the server identity for admin users. 2024-09-19 12:22:38 -04:00
f83863ef01 This doc was ancient, so paste some of the latest from the wiki in. It's something. 2024-09-18 20:16:35 -04:00
837f069cf5 Update CodeMirror. 2024-09-18 20:16:35 -04:00
9f057dc29a Add F-Droid and c-ares to the welcome page. 2024-09-18 20:01:36 -04:00
c4904f176c Distinguish the server identity in the identity app. 2024-09-18 19:09:44 -04:00
d3a5aba703 A brave new world where admin users can use the server identity. 2024-09-17 12:47:28 -04:00
9e283e427c Fix viewing apps by blob ID URL. 2024-09-16 12:45:06 -04:00
133ba31d66 c-ares 1.33.1. 2024-09-15 08:57:22 -04:00
241a65a92a sus. Disable a warning in c-ares during ltcg with gcc 13 on Haiku. 2024-09-15 12:52:28 -04:00
0b54795bab Merge branch 'main' of https://dev.tildefriends.net/cory/tildefriends 2024-09-11 20:28:06 -04:00
6208193de5 Fix plumbing for replies on the search tab. 2024-09-11 20:25:55 -04:00
c53321532f Update CodeMirror. 2024-09-11 20:18:57 -04:00
34f25e3e06 How did I not have an index on type? Wow. 2024-09-11 19:53:07 -04:00
c46244366e I don't know what GHES is, but it says to use this version, so sure. 2024-09-11 19:52:43 -04:00
6518af04fc It finally built. Now let's upload some artifacts. 2024-09-11 19:33:37 -04:00
bf137ff1f7 Seriously? More volume mount config. 2024-09-11 19:00:30 -04:00
1877955b62 Syntax? 2024-09-11 18:54:39 -04:00
50d0875de2 Mount volumes across another container? 2024-09-11 18:49:06 -04:00
bf151e6b7d I am confused. 2024-09-11 18:41:55 -04:00
82893402d0 Maybe we can sign here? 2024-09-10 21:54:36 -04:00
8049102787 More silent OpenSSL build for mingw. 2024-09-10 21:19:15 -04:00
f42cc3d9fd Find the Android SDKs you just installed. 2024-09-10 21:01:20 -04:00
5f9a5208db Appease the sdkmanager version number whatever. 2024-09-10 20:34:25 -04:00
6df506d238 Spelling. 2024-09-10 20:25:51 -04:00
2bd3354256 yaml better?? 2024-09-10 20:22:06 -04:00
b55aaa1d18 Maybe CI android?? 2024-09-10 20:20:40 -04:00
34e19505bd No longer need this test. 2024-09-09 15:57:43 -04:00
6e06ec0904 Clean up connections that don't handshake in time. 2024-09-09 15:25:10 -04:00
a5814074fe OpenSSL 3.3.2. 2024-09-04 20:24:32 -04:00
d7479df5a2 Latest CodeMirror. 2024-09-04 20:11:28 -04:00
34508aa0ae dist slightly more in parallel. Exclude dotfiles from data.zip. 2024-09-04 20:07:26 -04:00
ae096b2c9c Try harder to make webview localStorage work on different versions. I suspect that's what is about. 2024-09-04 12:50:12 -04:00
95d036e34a Build an AppImage. Why not? 2024-08-28 20:55:52 -04:00
4af5e8ec42 I guess prettier says this, now. 2024-08-28 20:24:26 -04:00
2a5f71bd5d This now lives in the fdroiddata repository. 2024-08-28 19:59:49 -04:00
97fb63dda1 Actually 0.0.23-wip. 2024-08-28 19:59:34 -04:00
87d42e3b3b 0.0.23-wip again. Let's gooooo. 2024-08-28 19:49:36 -04:00
0394129a4c nix => 0.0.22 again. 2024-08-28 19:42:31 -04:00
150 changed files with 15704 additions and 7267 deletions
.clang-format.fdroid.yml
.gitea/workflows
.gitignore.gitmodulesGNUmakefileREADME.md
apps
core
default.nix
deps
docs
docs
metadata/en-US
package-lock.json
src
tools

@ -14,7 +14,7 @@ IndentWidth: 4
MaxEmptyLinesToKeep: 1
ObjCBlockIndentWidth: 4
ObjCBreakBeforeNestedBlockParam: false
SortIncludes: false
SortIncludes: true
TabWidth: 4
UseTab: Always
...

@ -1,40 +0,0 @@
Categories:
- Internet
License: MIT
AutoName: tildefriends
AuthorName: Cory McWilliams
AuthorEmail: cory@tildefriends.net
RepoType: git
Repo: https://dev.tildefriends.net/cory/tildefriends.git
Builds:
- versionName: 0.0.21-wip
versionCode: 22
commit: 09b6a00731d45fa160b23a2c44be6def98d92d6a
subdir: src/android
submodules: true
sudo:
- apt-get update
- apt-get install -y ant make zip
androidupdate:
- no
scandelete:
- deps/libuv/docs/src/static/diagrams.key/Index.zip
- deps/openssl_src/cloudflare-quiche/*
- deps/openssl_src/fuzz/*
- deps/openssl_src/gost-engine/*
- deps/openssl_src/test/*
- deps/openssl_src/tlslite-ng/*
prebuild:
- sdkmanager "platforms;android-34" "build-tools;34.0.0"
build:
- mkdir bin/
- ANDROID_SDK=$$SDK$$ ANDROID_NDK=$$NDK$$ ANDROID_NDK_ROOT=$$NDK$$ make -C ../../ -j`nproc` fdroid
ndk: r26d
AutoUpdateMode: Version ^v[0-9\.]+$
UpdateCheckMode: Tags
CurrentVersion: 0.0.21-wip
CurrentVersionCode: 22

@ -5,11 +5,34 @@ on: [push]
jobs:
Build-All:
runs-on: ubuntu-latest
container:
valid_volumes: ['/opt/keys']
volumes:
- /opt/keys:/opt/keys
steps:
- name: check out code
uses: actions/checkout@v4
with:
submodules: true
- run: sudo apt update && sudo apt install -y doxygen graphviz mingw-w64
- run: make all -j`nproc` docs
- run: ln -s /opt/keys .keys
- name: Setup JDK
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
- name: Setup Android SDK
uses: android-actions/setup-android@v3
with:
packages: 'tools platform-tools build-tools;34.0.0 platforms;android-34 ndk;26.3.11579264'
- run: sudo apt update && sudo apt install -y doxygen graphviz mingw-w64 libgpgme11
- run: ANDROID_SDK=$HOME/.android/sdk make -j`nproc` all docs
- run: docker build .
- uses: actions/upload-artifact@v3
with:
path: out/TildeFriends-release.fdroid.apk
- uses: actions/upload-artifact@v3
with:
path: out/winrelease/tildefriends.exe
- uses: actions/upload-artifact@v3
with:
path: out/tildefriends-x86_64.AppImage

1
.gitignore vendored

@ -4,6 +4,7 @@ db.*
deps/ios_toolchain/
deps/openssl/
dist/
.flatpak-builder
.keys
logs/
**/node_modules

3
.gitmodules vendored

@ -26,3 +26,6 @@
[submodule "deps/c-ares"]
path = deps/c-ares
url = https://github.com/c-ares/c-ares.git
[submodule "docs"]
path = docs
url = https://dev.tildefriends.net/cory/tildefriends.wiki.git

@ -3,12 +3,27 @@
MAKEFLAGS += --warn-undefined-variables
MAKEFLAGS += --no-builtin-rules
VERSION_CODE := 26
VERSION_NUMBER := 0.0.22
VERSION_NAME := Get born soon.
## == Tilde Friends build. ==
##
## This is a list of all supported build targets.
##
## Consider passing -j$(nproc) or adding it to your $MAKEFLAGS to build in
## parallel (faster).
##
## Useful variables to override:
## CC := Compiler.
## AS := Assembler.
## LD := Linker.
## ANDROID_SDK := Path to the Android SDK.
SQLITE_URL := https://www.sqlite.org/2024/sqlite-amalgamation-3460100.zip
VERSION_CODE := 31
VERSION_NUMBER := 0.0.26
VERSION_NAME := This program kills fascists.
SQLITE_URL := https://www.sqlite.org/2024/sqlite-amalgamation-3470200.zip
BUNDLETOOL_URL := https://github.com/google/bundletool/releases/download/1.17.0/bundletool-all-1.17.0.jar
APPIMAGETOOL_URL := https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage
APPIMAGETOOL_MD5 := e989fadfc4d685fd3d6aeeb9b525d74d out/appimagetool
PROJECT = tildefriends
BUILD_DIR ?= out
@ -35,7 +50,8 @@ BUILD_TYPES := debug release
CFLAGS += -Dstatic_assert=_Static_assert
LDFLAGS += \
-lbsd \
-lnetwork
-lnetwork \
-Wno-stringop-overflow
else ifeq ($(UNAME_S),OpenBSD)
BUILD_TYPES := debug release
CFLAGS += \
@ -63,6 +79,7 @@ CFLAGS += \
-g
LDFLAGS += \
-Wno-attributes \
-Wno-aggressive-loop-optimizations \
-flto=auto
ANDROID_MIN_SDK_VERSION := 24
@ -159,7 +176,9 @@ NONANDROID_RELEASE_TARGETS := $(filter-out $(ANDROID_ARM64_TARGETS),$(RELEASE_TA
NONANDROID_TARGETS := $(filter-out $(ANDROID_TARGETS),$(ALL_TARGETS))
NONMACOS_TARGETS := $(filter-out $(MACOS_TARGETS) $(IOS_TARGETS) $(IOSSIM_TARGETS),$(ALL_TARGETS))
DEADSTRIP_TARGETS := $(filter-out $(ANDROID_TARGETS),$(NONMACOS_TARGETS))
ifneq ($(UNAME_S),OpenBSD)
$(NONMACOS_TARGETS): LDFLAGS += -static-libgcc
endif
$(NONANDROID_TARGETS): CFLAGS += -fno-omit-frame-pointer
$(filter-out $(WINDOWS_TARGETS),$(ALL_TARGETS)): LDFLAGS += -rdynamic
@ -219,12 +238,15 @@ $(ANDROID_X86_64_TARGETS): CFLAGS += -Ideps/openssl/android/x86_64/usr/local/inc
$(ANDROID_X86_64_TARGETS): LDFLAGS += -Ldeps/openssl/android/x86_64/usr/local/lib
$(NONMACOS_TARGETS): CFLAGS += -Wno-cast-function-type
$(DEADSTRIP_TARGETS): LDFLAGS += -Wl,--gc-sections
$(IOS_TARGETS): CFLAGS += -mios-version-min=9.0 -Ideps/openssl/ios/ios64-xcrun/usr/local/include
$(IOS_TARGETS): LDFLAGS += -Ldeps/openssl/ios/ios64-xcrun/usr/local/lib
$(IOS_TARGETS): CFLAGS += -miphoneos-version-min=9.0 -Ideps/openssl/ios/ios64-xcrun/usr/local/include
$(IOS_TARGETS): LDFLAGS += -miphoneos-version-min=9.0 -Ldeps/openssl/ios/ios64-xcrun/usr/local/lib
$(IOSSIM_TARGETS): CFLAGS += -Ideps/openssl/ios/iossimulator-xcrun/usr/local/include
$(IOSSIM_TARGETS): LDFLAGS += -Ldeps/openssl/ios/iossimulator-xcrun/usr/local/lib
ifeq ($(UNAME_M),x86_64)
ifeq ($(UNAME_S),Linux)
all: appimage
endif
ifneq ($(UNAME_S),Haiku)
out/debug/tildefriends: CFLAGS += -fsanitize=address -fsanitize=undefined -fno-common
out/debug/tildefriends: LDFLAGS += -fsanitize=address -fsanitize=undefined
@ -272,98 +294,101 @@ $(filter-out $(BUILD_DIR)/android% $(BUILD_DIR)/macos% $(BUILD_DIR)/ios%,$(APP_O
endif
ARES_SOURCES := \
deps/c-ares/src/lib/ares_platform.c \
deps/c-ares/src/lib/record/ares_dns_mapping.c \
deps/c-ares/src/lib/record/ares_dns_parse.c \
deps/c-ares/src/lib/record/ares_dns_write.c \
deps/c-ares/src/lib/record/ares_dns_name.c \
deps/c-ares/src/lib/record/ares_dns_record.c \
deps/c-ares/src/lib/record/ares_dns_multistring.c \
deps/c-ares/src/lib/ares_destroy.c \
deps/c-ares/src/lib/ares_data.c \
deps/c-ares/src/lib/ares_sysconfig.c \
deps/c-ares/src/lib/ares_addrinfo2hostent.c \
deps/c-ares/src/lib/ares_addrinfo_localhost.c \
deps/c-ares/src/lib/ares_android.c \
deps/c-ares/src/lib/ares_cancel.c \
deps/c-ares/src/lib/ares_metrics.c \
deps/c-ares/src/lib/ares_getnameinfo.c \
deps/c-ares/src/lib/legacy/ares_parse_txt_reply.c \
deps/c-ares/src/lib/legacy/ares_parse_naptr_reply.c \
deps/c-ares/src/lib/legacy/ares_create_query.c \
deps/c-ares/src/lib/legacy/ares_parse_mx_reply.c \
deps/c-ares/src/lib/legacy/ares_parse_srv_reply.c \
deps/c-ares/src/lib/legacy/ares_parse_ptr_reply.c \
deps/c-ares/src/lib/legacy/ares_parse_caa_reply.c \
deps/c-ares/src/lib/legacy/ares_parse_aaaa_reply.c \
deps/c-ares/src/lib/legacy/ares_expand_name.c \
deps/c-ares/src/lib/legacy/ares_parse_uri_reply.c \
deps/c-ares/src/lib/legacy/ares_parse_a_reply.c \
deps/c-ares/src/lib/legacy/ares_expand_string.c \
deps/c-ares/src/lib/legacy/ares_fds.c \
deps/c-ares/src/lib/legacy/ares_parse_ns_reply.c \
deps/c-ares/src/lib/legacy/ares_parse_soa_reply.c \
deps/c-ares/src/lib/legacy/ares_getsock.c \
deps/c-ares/src/lib/windows_port.c \
deps/c-ares/src/lib/ares_qcache.c \
deps/c-ares/src/lib/ares_update_servers.c \
deps/c-ares/src/lib/ares_process.c \
deps/c-ares/src/lib/ares_close_sockets.c \
deps/c-ares/src/lib/ares_conn.c \
deps/c-ares/src/lib/ares_cookie.c \
deps/c-ares/src/lib/ares_data.c \
deps/c-ares/src/lib/ares_destroy.c \
deps/c-ares/src/lib/ares_free_hostent.c \
deps/c-ares/src/lib/ares_free_string.c \
deps/c-ares/src/lib/ares_freeaddrinfo.c \
deps/c-ares/src/lib/ares_getaddrinfo.c \
deps/c-ares/src/lib/ares_getenv.c \
deps/c-ares/src/lib/ares_gethostbyaddr.c \
deps/c-ares/src/lib/ares_gethostbyname.c \
deps/c-ares/src/lib/ares_getnameinfo.c \
deps/c-ares/src/lib/ares_hosts_file.c \
deps/c-ares/src/lib/ares_init.c \
deps/c-ares/src/lib/ares_library_init.c \
deps/c-ares/src/lib/ares_metrics.c \
deps/c-ares/src/lib/ares_options.c \
deps/c-ares/src/lib/ares_parse_into_addrinfo.c \
deps/c-ares/src/lib/ares_process.c \
deps/c-ares/src/lib/ares_qcache.c \
deps/c-ares/src/lib/ares_query.c \
deps/c-ares/src/lib/ares_search.c \
deps/c-ares/src/lib/ares_send.c \
deps/c-ares/src/lib/dsa/ares__slist.c \
deps/c-ares/src/lib/dsa/ares__htable.c \
deps/c-ares/src/lib/dsa/ares__llist.c \
deps/c-ares/src/lib/dsa/ares__htable_szvp.c \
deps/c-ares/src/lib/dsa/ares__htable_asvp.c \
deps/c-ares/src/lib/dsa/ares__htable_vpvp.c \
deps/c-ares/src/lib/dsa/ares__htable_strvp.c \
deps/c-ares/src/lib/dsa/ares__array.c \
deps/c-ares/src/lib/ares__socket.c \
deps/c-ares/src/lib/event/ares_event_poll.c \
deps/c-ares/src/lib/event/ares_event_thread.c \
deps/c-ares/src/lib/event/ares_event_select.c \
deps/c-ares/src/lib/event/ares_event_kqueue.c \
deps/c-ares/src/lib/ares_set_socket_functions.c \
deps/c-ares/src/lib/ares_socket.c \
deps/c-ares/src/lib/ares_sortaddrinfo.c \
deps/c-ares/src/lib/ares_strerror.c \
deps/c-ares/src/lib/ares_sysconfig.c \
deps/c-ares/src/lib/ares_sysconfig_files.c \
deps/c-ares/src/lib/ares_sysconfig_mac.c \
deps/c-ares/src/lib/ares_sysconfig_win.c \
deps/c-ares/src/lib/ares_update_servers.c \
deps/c-ares/src/lib/ares_version.c \
deps/c-ares/src/lib/dsa/ares_array.c \
deps/c-ares/src/lib/dsa/ares_htable.c \
deps/c-ares/src/lib/dsa/ares_htable_asvp.c \
deps/c-ares/src/lib/dsa/ares_htable_dict.c \
deps/c-ares/src/lib/dsa/ares_htable_strvp.c \
deps/c-ares/src/lib/dsa/ares_htable_szvp.c \
deps/c-ares/src/lib/dsa/ares_htable_vpvp.c \
deps/c-ares/src/lib/dsa/ares_llist.c \
deps/c-ares/src/lib/dsa/ares_slist.c \
deps/c-ares/src/lib/event/ares_event_configchg.c \
deps/c-ares/src/lib/event/ares_event_epoll.c \
deps/c-ares/src/lib/event/ares_event_kqueue.c \
deps/c-ares/src/lib/event/ares_event_poll.c \
deps/c-ares/src/lib/event/ares_event_select.c \
deps/c-ares/src/lib/event/ares_event_thread.c \
deps/c-ares/src/lib/event/ares_event_wake_pipe.c \
deps/c-ares/src/lib/event/ares_event_win32.c \
deps/c-ares/src/lib/ares_search.c \
deps/c-ares/src/lib/ares__parse_into_addrinfo.c \
deps/c-ares/src/lib/ares__hosts_file.c \
deps/c-ares/src/lib/ares_getaddrinfo.c \
deps/c-ares/src/lib/ares__addrinfo2hostent.c \
deps/c-ares/src/lib/ares_freeaddrinfo.c \
deps/c-ares/src/lib/ares_strerror.c \
deps/c-ares/src/lib/ares_version.c \
deps/c-ares/src/lib/ares_gethostbyaddr.c \
deps/c-ares/src/lib/ares__addrinfo_localhost.c \
deps/c-ares/src/lib/ares_free_hostent.c \
deps/c-ares/src/lib/ares__close_sockets.c \
deps/c-ares/src/lib/ares_free_string.c \
deps/c-ares/src/lib/ares_init.c \
deps/c-ares/src/lib/ares_options.c \
deps/c-ares/src/lib/str/ares_strcasecmp.c \
deps/c-ares/src/lib/str/ares__buf.c \
deps/c-ares/src/lib/str/ares_strsplit.c \
deps/c-ares/src/lib/str/ares_str.c \
deps/c-ares/src/lib/ares_sysconfig_mac.c \
deps/c-ares/src/lib/ares__sortaddrinfo.c \
deps/c-ares/src/lib/ares_sysconfig_files.c \
deps/c-ares/src/lib/util/ares__iface_ips.c \
deps/c-ares/src/lib/util/ares__timeval.c \
deps/c-ares/src/lib/util/ares_math.c \
deps/c-ares/src/lib/util/ares_rand.c \
deps/c-ares/src/lib/util/ares__threads.c \
deps/c-ares/src/lib/ares_query.c \
deps/c-ares/src/lib/ares_cookie.c \
deps/c-ares/src/lib/inet_net_pton.c \
deps/c-ares/src/lib/inet_ntop.c \
deps/c-ares/src/lib/ares_library_init.c \
deps/c-ares/src/lib/ares_android.c \
deps/c-ares/src/lib/ares_sysconfig_win.c \
deps/c-ares/src/lib/legacy/ares_create_query.c \
deps/c-ares/src/lib/legacy/ares_expand_name.c \
deps/c-ares/src/lib/legacy/ares_expand_string.c \
deps/c-ares/src/lib/legacy/ares_fds.c \
deps/c-ares/src/lib/legacy/ares_getsock.c \
deps/c-ares/src/lib/legacy/ares_parse_a_reply.c \
deps/c-ares/src/lib/legacy/ares_parse_aaaa_reply.c \
deps/c-ares/src/lib/legacy/ares_parse_caa_reply.c \
deps/c-ares/src/lib/legacy/ares_parse_mx_reply.c \
deps/c-ares/src/lib/legacy/ares_parse_naptr_reply.c \
deps/c-ares/src/lib/legacy/ares_parse_ns_reply.c \
deps/c-ares/src/lib/legacy/ares_parse_ptr_reply.c \
deps/c-ares/src/lib/legacy/ares_parse_soa_reply.c \
deps/c-ares/src/lib/legacy/ares_parse_srv_reply.c \
deps/c-ares/src/lib/legacy/ares_parse_txt_reply.c \
deps/c-ares/src/lib/legacy/ares_parse_uri_reply.c \
deps/c-ares/src/lib/record/ares_dns_mapping.c \
deps/c-ares/src/lib/record/ares_dns_multistring.c \
deps/c-ares/src/lib/record/ares_dns_name.c \
deps/c-ares/src/lib/record/ares_dns_parse.c \
deps/c-ares/src/lib/record/ares_dns_record.c \
deps/c-ares/src/lib/record/ares_dns_write.c \
deps/c-ares/src/lib/str/ares_buf.c \
deps/c-ares/src/lib/str/ares_str.c \
deps/c-ares/src/lib/str/ares_strsplit.c \
deps/c-ares/src/lib/util/ares_iface_ips.c \
deps/c-ares/src/lib/util/ares_math.c \
deps/c-ares/src/lib/util/ares_rand.c \
deps/c-ares/src/lib/util/ares_threads.c \
deps/c-ares/src/lib/util/ares_timeval.c \
deps/c-ares/src/lib/util/ares_uri.c \
deps/c-ares/src/lib/windows_port.c \
deps/c-ares/src/lib/ares_timeout.c
ARES_OBJS := $(call get_objs,ARES_SOURCES)
$(ARES_OBJS): CFLAGS += \
-Ideps/c-ares/include \
-Ideps/c-ares/src/lib \
-Ideps/c-ares/src/lib/include \
-Ideps/c-ares_config/ \
-D_GNU_SOURCE \
-Wno-unused-function \
@ -497,12 +522,17 @@ $(UV_OBJS): CFLAGS += \
-Wno-incompatible-pointer-types \
-Wno-maybe-uninitialized \
-Wno-sign-compare \
-Wno-unknown-attributes \
-Wno-unused-but-set-parameter \
-Wno-unused-but-set-variable \
-Wno-unused-result \
-Wno-unused-variable
-Wno-unused-variable \
-Wno-nonnull
$(UV_OBJS): CFLAGS += -fno-lto
$(filter out/win%,$(UV_OBJS)): CFLAGS += -Wno-cast-function-type
$(filter out/win%,$(UV_OBJS)): \
CFLAGS += \
-Wno-cast-function-type \
-Wno-missing-braces
ifeq ($(UNAME_S),Linux)
$(UV_OBJS): CFLAGS += \
-D_GNU_SOURCE
@ -578,7 +608,6 @@ ifneq ($(UNAME_S),OpenBSD)
$(filter-out $(BUILD_DIR)/win%,$(SODIUM_OBJS)): CFLAGS += \
-DHAVE_ALLOCA_H
endif
$(SODIUM_OBJS): CFLAGS := $(filter-out -flto,$(CFLAGS))
SQLITE_SOURCES := deps/sqlite/sqlite3.c
SQLITE_OBJS := $(call get_objs,SQLITE_SOURCES)
@ -731,11 +760,28 @@ $(IOS_TARGETS) $(IOSSIM_TARGETS): LDFLAGS += \
-framework UIKit \
-framework WebKit
unix: debug release
win: windebug winrelease
all: $(BUILD_TYPES)
##
## Common targets:
##
debug: ## Build a debug executable for the current platform.
release: ## Build a release executable for the current platform.
all: $(BUILD_TYPES) ## Build all targets that appear possible to build on this machine.
unix: debug release ## Build all UNIX targets.
win: windebug winrelease ## Build all Windows targets.
.PHONY: all win unix
##
## Windows targets:
##
windebug: ## Build a debug win32 executable.
winrelease: ## Build a release win32 executable.
##
## MacOS targets:
##
macosdebug: ## Build a MacOS debug executable.
macosrelease: ## Build a MacOS release executable.
ALL_APP_OBJS := \
$(APP_OBJS) \
$(ARES_OBJS) \
@ -791,7 +837,18 @@ src/android/AndroidManifest.xml : $(firstword $(MAKEFILE_LIST))
-e 's/android:targetSdkVersion="[[:digit:]]*"/android:targetSdkVersion="$(ANDROID_TARGET_SDK_VERSION)"/' \
$@
# Android support.
##
## Android targets:
##
androiddebug: ## Build a debug 64-bit ARM Android APK.
androidrelease: ## Build a release 64-bit ARM Android APK.
androiddebug-armv7a: ## Build a debug 32-bit ARM Android APK.
androidrelease-armv7a: ## Build a release 32-bit ARM Android APK.
androiddebug-x86: ## Build a debug x86 Android APK.
androidrelease-x86: ## Build a release x86 Android APK.
androiddebug-x86_64: ## Build a debug x86_64 Android APK.
androidrelease-x86_64: ## Build a release x86_64 Android APK.
out/res/layout_activity_main.xml.flat: src/android/res/layout/activity_main.xml
@mkdir -p $(dir $@)
@echo "[aapt2] $@"
@ -842,7 +899,7 @@ PACKAGE_DIRS := \
deps/prettier/ \
deps/lit/
RAW_FILES := $(sort $(filter-out apps/blog% apps/issues% apps/welcome% apps/journal% %.map, $(shell find $(PACKAGE_DIRS) -type f)))
RAW_FILES := $(sort $(filter-out apps/blog% apps/issues% apps/welcome% apps/journal% %.map, $(shell find $(PACKAGE_DIRS) -type f -not -name '.*')))
out/apk/TildeFriends-arm-debug.unsigned.apk: BUILD_TYPE := debug
out/apk/TildeFriends-arm-release.unsigned.apk: BUILD_TYPE := release
@ -898,7 +955,7 @@ out/TildeFriends.aab: out/apk/classes.dex $(filter-out %debug%, $(ANDROID_TARGET
@java -jar $(BUNDLETOOL) build-bundle --overwrite --config=src/android/BundleConfig.json --modules=out/aab/base.zip --output=$@
@jarsigner -keystore .keys/android.jks $@ androidKey -storepass android
aab: out/TildeFriends.aab
aab: out/TildeFriends.aab ## Build an Android App Bundle.
.PHONY: aab
out/TildeFriends.apks: out/TildeFriends.aab $(BUNDLETOOL)
@ -965,20 +1022,32 @@ out/%.zopfli.apk: out/%.apk
$(ANDROID_BUILD_TOOLS)/zipalign -f -z 4 $< $@.zopfli
@$(ANDROID_BUILD_TOOLS)/apksigner sign --ks .keys/android.jks --ks-key-alias androidKey --ks-pass pass:android --key-pass pass:android --min-sdk-version $(ANDROID_MIN_SDK_VERSION) --out $@ $@.zopfli
release-apk: out/TildeFriends-arm-release.zopfli.apk out/TildeFriends-x86-release.zopfli.apk
release-apk: out/TildeFriends-arm-release.zopfli.apk out/TildeFriends-x86-release.zopfli.apk ## Build an Android release APK.
.PHONY: release-apk
apkgo: out/TildeFriends-arm-debug.apk
fdroid: out/apk/TildeFriends-release.fdroid.unsigned.apk ## Build Android APK for distribution on F-Droid.
.PHONY: fdroid
apkgo: out/TildeFriends-arm-debug.apk ## Build, install, and run a debug Android APK.
@adb install -r $<
@adb shell am start com.unprompted.tildefriends/.TildeFriendsActivity
.PHONY: apkgo
releaseapkgo: out/TildeFriends-arm-release.apk
releaseapkgo: out/TildeFriends-arm-release.apk ## Build, install, and run a release Android APK.
@adb install -r $<
@adb shell am start com.unprompted.tildefriends/.TildeFriendsActivity
.PHONY: releaseapkgo
# iOS Support
apklog: ## Display Android log output.
@adb logcat *:S tildefriends
.PHONY: apklog
##
## iPhoneOS targets:
##
iosdebug: ## Build a debug iPhoneOS executable.
iosrelease: ## Build a release iPhoneOS executable.
out/%.app/Info.plist: src/ios/Info.plist
@mkdir -p $(dir $@)
@cp -v $< $@
@ -992,6 +1061,7 @@ out/data.zip: $(RAW_FILES)
out/tildefriends-%.app/tildefriends: out/%/tildefriends out/tildefriends-%.app/Info.plist out/tildefriends-%.app/tildefriends.png out/data.zip
@mkdir -p $(dir $@)
@cp -v $< $@
@cp -v out/data.zip $(@D)/
ifeq ($(HAVE_LINUX_IOS),1)
@zsign -q -k .keys/apple.p12 -f -m src/ios/embedded.mobileprovision $(realpath $(dir $@))
endif
@ -1014,39 +1084,23 @@ out/%/tildefriends.standalone.exe: out/%/tildefriends.exe out/data.zip
@cat $< out/data.zip > $@
@chmod +x $@
iossimdebug-app: out/tildefriends-iossimdebug.app/tildefriends
iossimrelease-app: out/tildefriends-iossimrelease.app/tildefriends
iosdebug-app: out/tildefriends-iosdebug.app/tildefriends
iosrelease-app: out/tildefriends-iosrelease.app/tildefriends
iossimdebug-app: out/tildefriends-iossimdebug.app/tildefriends ## Build a debug iOS Simulator .app directory.
iossimrelease-app: out/tildefriends-iossimrelease.app/tildefriends ## Build a release iOS Simulator .app directory.
iosdebug-app: out/tildefriends-iosdebug.app/tildefriends ## Build a debug iOS .app directory.
iosrelease-app: out/tildefriends-iosrelease.app/tildefriends ## Build a release iOS .app directory.
iosdebug-ipa: out/tildefriends-debug.ipa
iosrelease-ipa: out/tildefriends-release.ipa
iosdebug-ipa: out/tildefriends-debug.ipa ## Build a debug iOS .ipa.
iosrelease-ipa: out/tildefriends-release.ipa ## Build a release iOS .ipa.
.PHONY: iossimdebug-app iossimrelease-app iosdebug-app iosrelease-app
ios%go: out/tildefriends-ios%.app/tildefriends
ideviceinstaller -i $(realpath $(dir $<))
iossimdebuggo: out/tildefriends-iossimdebug.app/tildefriends
iossimdebuggo: out/tildefriends-iossimdebug.app/tildefriends ## Build, install, and run an iOS debug build.
xcrun simctl install booted out/tildefriends-iossimdebug.app/
xcrun simctl launch booted com.unprompted.tildefriends
.PHONY: iossimdebuggo
apklog:
@adb logcat *:S tildefriends
.PHONY: apklog
fetchdeps:
@echo "[fetch] sqlite"
@test -f out/deps/sqlite.zip && test "$$(cat out/deps/sqlite.txt 2>/dev/null)" = $(SQLITE_URL) || (mkdir -p out/deps/ && curl -q $(SQLITE_URL) -o out/deps/sqlite.zip)
@test -d deps/sqlite/ && test "$$(cat out/deps/sqlite.txt 2>/dev/null)" = $(SQLITE_URL) || (mkdir -p deps/sqlite/ && unzip -qDjo -d deps/sqlite/ out/deps/sqlite.zip)
@echo -n $(SQLITE_URL) > out/deps/sqlite.txt
@echo "[fetch] prettier"
@test -f deps/prettier/standalone.mjs || curl -q --create-dirs -O --output-dir deps/prettier/ https://cdn.jsdelivr.net/npm/prettier@3.2.5/standalone.mjs
@test -f deps/prettier/html.mjs || curl -q --create-dirs -O --output-dir deps/prettier/ https://cdn.jsdelivr.net/npm/prettier@3.2.5/plugins/html.mjs
@test -f deps/prettier/babel.mjs || curl -q --create-dirs -O --output-dir deps/prettier/ https://cdn.jsdelivr.net/npm/prettier@3.2.5/plugins/babel.mjs
@test -f deps/prettier/estree.mjs || curl -q --create-dirs -O --output-dir deps/prettier/ https://cdn.jsdelivr.net/npm/prettier@3.2.5/plugins/estree.mjs
.PHONY: fetchdeps
ANDROID_DEPS := deps/openssl/android/arm64-v8a/usr/local/lib/libssl.a
$(ANDROID_DEPS):
+@ANDROID_NDK_ROOT=$(ANDROID_NDK) tools/ssl-android
@ -1066,14 +1120,62 @@ $(IOS_DEPS):
$(filter $(BUILD_DIR)/ios%,$(APP_OBJS)): | $(IOS_DEPS)
endif
clean:
rm -rf $(BUILD_DIR)
.PHONY: clean
##
## Linux package targets:
##
dist: release-apk iosrelease-ipa aab $(if $(HAVE_WIN), out/winrelease/tildefriends.standalone.exe) out/TildeFriends-release.fdroid.apk
@echo [archive] dist/tildefriends-$(VERSION_NUMBER).tar.xz
out/tildefriends-x86_64.AppImage: out/release/tildefriends out/data.zip
@echo "[appimage] $$@"
@rm -rf out/tildefriends.AppDir
@mkdir -p out/tildefriends.AppDir/usr/bin
@mkdir -p out/tildefriends.AppDir/usr/share/applications
@mkdir -p out/tildefriends.AppDir/usr/share/icons/hicolor/scalable/apps
@mkdir -p out/tildefriends.AppDir/usr/share/tildefriends
@echo $(APPIMAGETOOL_MD5) > out/appimagetool.md5
@test -x out/appimagetool || curl -q -L -o out/appimagetool $(APPIMAGETOOL_URL) && md5sum -c out/appimagetool.md5 && chmod +x out/appimagetool
@echo "[Desktop Entry]\nName=tildefriends\nExec=/usr/bin/tildefriends\nIcon=/usr/share/icons/hicolor/scalable/apps/tildefriends\nType=Application\nCategories=Network" > out/tildefriends.AppDir/tildefriends.desktop
@cp src/ios/tildefriends.svg out/tildefriends.AppDir/usr/share/icons/hicolor/scalable/apps/
@cp src/ios/tildefriends.svg out/tildefriends.AppDir/
@cp out/release/tildefriends out/tildefriends.AppDir/usr/bin/
@cp out/data.zip out/tildefriends.AppDir/usr/share/tildefriends/data.zip
@echo "#!/bin/sh\n\$${APPDIR}/usr/bin/tildefriends run -z \$$APPDIR/usr/share/tildefriends/data.zip" > out/tildefriends.AppDir/AppRun
@chmod +x out/tildefriends.AppDir/AppRun
@cd out; ./appimagetool --appimage-extract; cd ..
@cd out; unset SOURCE_DATE_EPOCH; PATH=$$PATH:squashfs-root/usr/bin ARCH=x86_64 squashfs-root/usr/bin/appimagetool -u 'zsync|https://dev.tildefriends.net/releases/tildefriends-x86_64.AppImage.zsync' tildefriends.AppDir tildefriends-x86_64.AppImage; cd ..
appimage: out/tildefriends-x86_64.AppImage ## Build an AppImage.
.PHONY: appimage
flatpak: out/ ## Build a flatpak.
flatpak-builder --force-clean --user --install-deps-from=flathub --install --repo=out/flatpak-repo out/flatpak src/com.unprompted.tildefriends.yml
flatpak build-bundle out/flatpak-repo out/tildefriends.flatpak com.unprompted.tildefriends
.PHONY: flatpak
##
## Targets for release management:
##
fetchdeps: ## Update various external sources that live in the tree that can't be pulled in as git submodules.
@echo "[fetch] sqlite"
@test -f out/deps/sqlite.zip && test "$$(cat out/deps/sqlite.txt 2>/dev/null)" = $(SQLITE_URL) || (mkdir -p out/deps/ && curl -q $(SQLITE_URL) -o out/deps/sqlite.zip)
@test -d deps/sqlite/ && test "$$(cat out/deps/sqlite.txt 2>/dev/null)" = $(SQLITE_URL) || (mkdir -p deps/sqlite/ && unzip -qDjo -d deps/sqlite/ out/deps/sqlite.zip)
@echo -n $(SQLITE_URL) > out/deps/sqlite.txt
@echo "[fetch] prettier"
@test -f deps/prettier/standalone.mjs || curl -q --create-dirs -O --output-dir deps/prettier/ https://cdn.jsdelivr.net/npm/prettier@3.2.5/standalone.mjs
@test -f deps/prettier/html.mjs || curl -q --create-dirs -O --output-dir deps/prettier/ https://cdn.jsdelivr.net/npm/prettier@3.2.5/plugins/html.mjs
@test -f deps/prettier/babel.mjs || curl -q --create-dirs -O --output-dir deps/prettier/ https://cdn.jsdelivr.net/npm/prettier@3.2.5/plugins/babel.mjs
@test -f deps/prettier/estree.mjs || curl -q --create-dirs -O --output-dir deps/prettier/ https://cdn.jsdelivr.net/npm/prettier@3.2.5/plugins/estree.mjs
.PHONY: fetchdeps
shots: ## Copy generated screenshots from `tildefriends test -t=auto` into place in the metadata/ directory.
@echo [shots] $(wildcard out/screenshot*.png)
@cp -f out/screenshot*.png metadata/en-US/images/phoneScreenshots/
.PHONY: shots
tarball: ## Build an all-inclusive source tarball (.tar.xz).
@echo [archive] out/tildefriends-$(VERSION_NUMBER).tar.xz
@rm -rf out/tildefriends-$(VERSION_NUMBER)
@mkdir -p dist/ out/tildefriends-$(VERSION_NUMBER)
@mkdir -p out/tildefriends-$(VERSION_NUMBER)
@git ls-files --recurse-submodules | tar -c -T- | tar -x -C out/tildefriends-$(VERSION_NUMBER)
@tar \
--exclude=apps/welcome* \
@ -1090,9 +1192,16 @@ dist: release-apk iosrelease-ipa aab $(if $(HAVE_WIN), out/winrelease/tildefrien
--exclude=deps/sqlite/shell.c \
--exclude=deps/zlib/contrib/vstudio \
--exclude=deps/zlib/doc \
-caf dist/tildefriends-$(VERSION_NUMBER).tar.xz \
-caf out/tildefriends-$(VERSION_NUMBER).tar.xz \
-C out/ \
tildefriends-$(VERSION_NUMBER)
.PHONY: tarball
dist: ## Build versions of all distributables for release.
dist: release-apk iosrelease-ipa aab $(if $(HAVE_WIN), out/winrelease/tildefriends.standalone.exe) out/TildeFriends-release.fdroid.apk appimage tarball
@mkdir -p dist/
@echo "[cp] tildefriends-$(VERSION_NUMBER).tar.xz"
@cp out/tildefriends-$(VERSION_NUMBER).tar.xz dist/tildefriends-$(VERSION_NUMBER).tar.xz
@echo "[cp] TildeFriends-x86-$(VERSION_NUMBER).apk"
@cp out/TildeFriends-x86-release.zopfli.apk dist/TildeFriends-x86-$(VERSION_NUMBER).apk
@echo "[cp] TildeFriends-arm-$(VERSION_NUMBER).apk"
@ -1101,28 +1210,61 @@ dist: release-apk iosrelease-ipa aab $(if $(HAVE_WIN), out/winrelease/tildefrien
@cp out/tildefriends-release.ipa dist/TildeFriends-$(VERSION_NUMBER).ipa
@test $(HAVE_WIN) && echo "[cp] tildefriends-$(VERSION_NUMBER).exe"
@test $(HAVE_WIN) && cp out/winrelease/tildefriends.standalone.exe dist/tildefriends-$(VERSION_NUMBER).exe
@echo "[cp] TildeFriends-$(VERSION_NUMBER).aab"
@cp out/TildeFriends.aab dist/TildeFriends-$(VERSION_NUMBER).aab
@echo "[cp] TildeFriends-$(VERSION_NUMBER).fdroid.apk"
@cp out/TildeFriends-release.fdroid.apk dist/TildeFriends-$(VERSION_NUMBER).fdroid.apk
@echo "[cp] TildeFriends-x86_64-$(VERSION_NUMBER).AppImage"
@cp out/tildefriends-x86_64.AppImage dist/TildeFriends-x86_64-$(VERSION_NUMBER).AppImage
.PHONY: dist
dist-test: dist
dist-test: dist ## Exercise some built distributable files, making sure they work as intended.
@tar -xf tildefriends-$(VERSION_NUMBER).tar.xz
@$(MAKE) -C tildefriends-$(VERSION_NUMBER)/ debug release
@docker build tildefriends-$(VERSION_NUMBER)/
@rm -rf tildefriends-$(VERSION_NUMBER)
.PHONY: dist-test
format:
##
## Targets for tidying up:
##
format: ## Standardize formatting of C source.
@clang-format -i $(wildcard src/*.c src/*.h src/*.m)
.PHONY: format
prettier:
prettier: ## Standardize formatting of JavaScript and Markdown source.
@npm run prettier
.PHONY: prettier
docs:
clean: ## Clean all generated files from the out/ directory.
rm -rf $(BUILD_DIR)
.PHONY: clean
##
## Documentation:
##
help: ## Display this help message.
@awk \
-F: \
-vG=$$(tput setaf 2) \
-vO=$$(tput setaf 3) \
-vB=$$(tput setaf 4) \
-vM=$$(tput setaf 5) \
-vC=$$(tput setaf 6) \
-vR=$$(tput sgr0) ' \
/^## ==.*==$$/ { sub(/^## ?/, ""); printf "%s%s%s\n", C, $$0, R } \
/^##.*:=.*/ { sub(/^## ?/, ""); sub(/:=/, ":"); printf " %s%-20s%s %s%s%s\n", M, $$1, R, O, $$2, R } \
/^##/ { sub(/^## ?/, ""); print $$0 } \
/^[[:alnum:]-]+:.*##/ { \
sub(/:.*##\s?/, ":"); \
printf " %s%-20s%s %s%s%s\n", G, $$1, R, O, $$2, R \
} \
' < $(filter-out %.d,$(MAKEFILE_LIST))
@echo "" # Blank line.
.PHONY: help
.DEFAULT_GOAL := help
docs: ## Build HTML docs.
@doxygen
.PHONY: docs
fdroid: out/apk/TildeFriends-release.fdroid.unsigned.apk
.PHONY: fdroid

@ -14,25 +14,53 @@ Scuttlebutt, as well as a platform for writing and running web applications.
3. Make creating and sharing web applications accessible to anyone with a
browser.
## Getting the Source
Tilde Friends uses git submodules, so either:
```
git clone --recurse-submodules https://dev.tildefriends.net/cory/tildefriends.git
```
or:
```
git clone https://dev.tildefriends.net/cory/tildefriends.git
cd tildefriends
git submodule update --init --recursive
```
The `.tar.xz` source releases are all-inclusive.
## Building
Builds on Linux (x86_64 and aarch64), MacOS, OpenBSD, and Haiku. Builds for
all of those host platforms plus mingw64, iOS, and android.
Builds on Linux (x86_64 and aarch64), MacOS, OpenBSD, and Haiku. It's possible
to build for Android, iOS, and Windows on Linux, if you have the right
dependencies in the right places.
1. Requires openssl (`libssl-dev`, in debian-speak). All other dependencies
are kept up to date in the tree.
2. To build, run `make debug` or `make release`. An executable will be
generated in a subdirectory of `out/`.
3. It's possible to build for Android, iOS, and Windows on Linux, if you have
the right dependencies in the right places. `make windebug winrelease
iosdebug-ipa iosrelease-ipa release-apk`.
4. To build in docker, `docker build .`.
5. `make format` will normalize formatting to the coding standard.
### Requirements
On Linux only, system OpenSSL libraries (`libssl-dev`, in debian-speak) are
assumed to be available.
On MacOS, Xcode's command-line tools are expected to be available.
### Build Commands
Run `make` with no arguments to see available build targets and options. `make
debug` is a good place to start.
To build in docker, `docker build .`.
`make format` and `make prettier` will normalize formatting to the coding
standard.
## Running
By default, running the built `tildefriends` executable will start a web server
at <http://localhost:12345/>. `tildefriends -h` lists further options.
By default, running the built `out/debug/tildefriends` executable will start a
web server at <http://localhost:12345/>. It expects to be run with the
repository root as the current working directory. `tildefriends -h` lists
further options.
The first user to create an account and log in will be granted administrative
privileges. Further administration can be done at
@ -41,7 +69,7 @@ privileges. Further administration can be done at
## Documentation
Docs are a work in progress:
<https://www.tildefriends.net/~cory/wiki/#test-wiki/tf-app-quick-reference>.
<https://dev.tildefriends.net/cory/tildefriends/wiki>.
## License

@ -28,7 +28,10 @@ function global_settings_set(key, value) {
}
function title_case(name) {
return name.split('_').map(x => x.charAt(0).toUpperCase() + x.substring(1)).join(' ');
return name
.split('_')
.map((x) => x.charAt(0).toUpperCase() + x.substring(1))
.join(' ');
}
window.addEventListener('load', function () {

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

@ -21,7 +21,7 @@ function* treeify(prefix, o) {
function markdown(md) {
let parsed = new commonmark.Parser().parse(md ?? '*undocumented*');
return new commonmark.HtmlRenderer().render(parsed);
return new commonmark.HtmlRenderer({safe: true}).render(parsed);
}
function document(api) {

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

@ -52,8 +52,8 @@ export async function get_blog_message(id) {
}
export function markdown(md) {
let reader = new commonmark.Parser({safe: true});
let writer = new commonmark.HtmlRenderer();
let reader = new commonmark.Parser();
let writer = new commonmark.HtmlRenderer({safe: true});
let parsed = reader.parse(md || '');
let walker = parsed.walker();
let event, node;

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

@ -1,4 +1,5 @@
{
"type": "tildefriends-app",
"emoji": "💽"
"emoji": "💽",
"previous": "&uQzkIe/Aj8yNhLKe3hEq+5fEJsGwIUx8NVBjJKwoV2U=.sha256"
}

@ -51,6 +51,19 @@ async function key_list(db) {
app.setDocument(doc);
}
function load() {
if (core.user?.credentials?.session) {
database_list();
} else {
app.setDocument(`<!DOCTYPE html>
<html>
<body style="background: #888">
<h1>Must be signed in to examine databases.</h1>
</body>
</html>`);
}
}
core.register('message', async function (message) {
if (message.event == 'hashChange') {
let hash = message.hash.substring(1);
@ -62,9 +75,9 @@ core.register('message', async function (message) {
} else if (hash.length) {
key_list(await database(hash.split(':').slice(1).join(':')));
} else {
database_list();
load();
}
}
});
database_list();
load();

@ -1,5 +1,5 @@
{
"type": "tildefriends-app",
"emoji": "🪪",
"previous": "&de7q4A59auHP/34bXgeNH05JZoxsGr5TjwXPvehWH30=.sha256"
"previous": "&5kw/2PgcySwOYCmAkjHTR2xTkIx3i7UjQmtQ8MfgWw8=.sha256"
}

@ -1,5 +1,7 @@
import * as tfrpc from '/tfrpc.js';
const is_admin = core.user?.credentials?.permissions?.administration;
tfrpc.register(async function get_private_key(id) {
return bip39Words(await ssb.getPrivateKey(id));
});
@ -15,9 +17,13 @@ tfrpc.register(async function delete_id(id) {
tfrpc.register(async function reload() {
await main();
});
tfrpc.register(async function make_server(id) {
return await ssb.swapWithServerIdentity(id);
});
async function main() {
let ids = await ssb.getIdentities();
let server_id = await ssb.getServerIdentity();
await app.setDocument(
`
<head>
@ -98,6 +104,16 @@ async function main() {
alert('Error deleting ID: ' + e);
}
}
handler.make_server = async function make_server(event) {
let id = event.srcElement.dataset.id;
try {
if (confirm('Are you sure you want to make "' + id + '" the server identity?\\n\\nFor it to take effect, you will need to both sign in again and restart Tilde Friends.')) {
await tfrpc.rpc.make_server(id);
}
} catch (e) {
alert('Error making server ID: ' + e);
}
}
</script>
<header class="w3-theme w3-padding"><h1>SSB Identity Management</h1></header>
<div class="w3-card-4 w3-margin">
@ -116,14 +132,15 @@ async function main() {
<div class="w3-card-4 w3-margin">
<header class="w3-container w3-theme-l2"><h2>Identities</h2></header>
<ul class="w3-ul">` +
ids
(ids ?? [])
.map(
(
id
) => `<li style="overflow: hidden; text-wrap: nowrap; text-overflow: ellipsis">
<button onclick="handler.export_id(event)" data-id="${id}" class="w3-button w3-theme">Export Identity</button>
<button onclick="handler.delete_id(event)" data-id="${id}" class="w3-button w3-theme">Delete Identity</button>
${id}
${is_admin && id != server_id ? `<button onclick="handler.make_server(event)" data-id="${id}" class="w3-button w3-theme">Make Server Identity</button>` : ''}
${id}${id == server_id ? ' <div class="w3-tag w3-theme-l4 w3-round">🖥 local server</div>' : ''}
</li>`
)
.join('\n') +

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

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

@ -1,5 +1,11 @@
import * as linkify from './commonmark-linkify.js';
var reUnsafeProtocol = /^javascript:|vbscript:|file:|data:/i;
var reSafeDataProtocol = /^data:image\/(?:png|gif|jpeg|webp)/i;
var potentiallyUnsafe = function (url) {
return reUnsafeProtocol.test(url) && !reSafeDataProtocol.test(url);
};
function image(node, entering) {
if (
node.firstChild?.type === 'text' &&
@ -61,8 +67,8 @@ function image(node, entering) {
}
export function markdown(md) {
var reader = new commonmark.Parser({safe: true});
var writer = new commonmark.HtmlRenderer();
var reader = new commonmark.Parser();
var writer = new commonmark.HtmlRenderer({safe: true});
writer.image = image;
var parsed = reader.parse(md || '');
parsed = linkify.transform(parsed);

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

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

@ -18,8 +18,8 @@ class TfJournalEntryElement extends LitElement {
}
markdown(md) {
var reader = new commonmark.Parser({safe: true});
var writer = new commonmark.HtmlRenderer();
var reader = new commonmark.Parser();
var writer = new commonmark.HtmlRenderer({safe: true});
var parsed = reader.parse(md || '');
return writer.render(parsed);
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -1,5 +1,5 @@
{
"type": "tildefriends-app",
"emoji": "🐌",
"previous": "&xsmsLytB3VvoHphiFHZGGEvrCfTEVGXrGwobGTIYFPQ=.sha256"
"previous": "&q/1uGp0jMvsYGW7Gj8E33kf6UFo/uNYDXg3zo1sVKQg=.sha256"
}

@ -103,6 +103,9 @@ tfrpc.register(async function encrypt(id, recipients, content) {
tfrpc.register(async function getActiveIdentity() {
return await ssb.getActiveIdentity();
});
tfrpc.register(async function sync() {
return await ssb.sync();
});
core.register('onBroadcastsChanged', async function () {
await tfrpc.rpc.set('broadcasts', await ssb.getBroadcasts());
});

@ -7,7 +7,7 @@ function textNode(text) {
function linkNode(text, link) {
const linkNode = new commonmark.Node('link', undefined);
if (link.startsWith('#')) {
linkNode.destination = `#q=${encodeURIComponent(link)}`;
linkNode.destination = `#${encodeURIComponent('#' + link)}`;
} else {
linkNode.destination = link;
}

File diff suppressed because one or more lines are too long

@ -37,10 +37,12 @@ export async function picker(callback, anchor, author) {
div.style.color = '#000';
div.style.background = '#fff';
div.style.border = '1px solid #000';
div.style.display = 'block';
div.style.display = 'flex';
div.style.overflow = 'scroll';
div.style.fontWeight = 'bold';
div.style.fontSize = 'xx-large';
div.style.flex = '1 1';
div.style.flexDirection = 'column';
let input = document.createElement('input');
input.type = 'text';
input.style.display = 'block';
@ -50,6 +52,7 @@ export async function picker(callback, anchor, author) {
input.style.position = 'relative';
div.appendChild(input);
let list = document.createElement('div');
list.style.overflow = 'scroll';
div.appendChild(list);
div.addEventListener('mousedown', function (event) {
event.stopPropagation();
@ -142,21 +145,40 @@ export async function picker(callback, anchor, author) {
}
refresh();
input.oninput = refresh;
let modal = html`
<style>
${styles}
</style>
<div class="w3-modal" style="display: block">
<div class="w3-modal-content w3-card-4">${div}</div>
</div>
`;
let parent = document.createElement('div');
document.body.appendChild(parent);
function cleanup() {
parent.parentElement.removeChild(parent);
window.removeEventListener('keydown', key_down);
document.body.removeEventListener('mousedown', cleanup);
}
let modal = html`
<style>
${styles}
</style>
<div
class="w3-modal"
style="display: block; box-sizing: border-box; z-index: 10"
>
<div class="w3-modal-content w3-card-4">
<div
class="w3-content w3-theme-d1"
style="display: flex; flex-direction: column; max-height: 50vh"
>
<header class="w3-container" style="flex: 0 0">
<h1>Choose a Reaction</h1>
<span class="w3-button w3-display-topright" @click=${cleanup}
>&times;</span
>
</header>
${div}
<footer class="w3-container w3-padding" style="flex: 0 0">
<button class="w3-button" @click=${cleanup}>Close</button>
</footer>
</div>
</div>
</div>
`;
document.body.appendChild(parent);
render(modal, parent);
input.focus();
document.body.addEventListener('mousedown', cleanup);

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -8,10 +8,16 @@ import * as tf_compose from './tf-compose.js';
import * as tf_news from './tf-news.js';
import * as tf_profile from './tf-profile.js';
import * as tf_reactions_modal from './tf-reactions-modal.js';
import * as tf_tab_mentions from './tf-tab-mentions.js';
import * as tf_tab_news from './tf-tab-news.js';
import * as tf_tab_news_feed from './tf-tab-news-feed.js';
import * as tf_tab_search from './tf-tab-search.js';
import * as tf_tab_connections from './tf-tab-connections.js';
import * as tf_tab_query from './tf-tab-query.js';
import * as tf_tag from './tf-tag.js';
import * as tf_styles from './tf-styles.js';
window.addEventListener('load', function () {
let style = document.createElement('style');
style.innerText = tf_styles.styles;
document.body.appendChild(style);
});

@ -16,7 +16,9 @@ class TfElement extends LitElement {
following: {type: Array},
users: {type: Object},
ids: {type: Array},
tags: {type: Array},
channels: {type: Array},
channels_unread: {type: Object},
channels_latest: {type: Object},
};
}
@ -33,7 +35,11 @@ class TfElement extends LitElement {
this.following = [];
this.users = {};
this.loaded = false;
this.tags = [];
this.channels = [];
this.channels_unread = {};
this.channels_latest = {};
this.loading_channels_latest = 0;
this.loading_channels_latest_scheduled = 0;
tfrpc.rpc.getBroadcasts().then((b) => {
self.broadcasts = b || [];
});
@ -64,16 +70,76 @@ class TfElement extends LitElement {
let ids = (await tfrpc.rpc.getIdentities()) || [];
this.whoami = whoami ?? (ids.length ? ids[0] : undefined);
this.ids = ids;
await this.load_channels();
}
async load_channels() {
let channels = await tfrpc.rpc.query(
`
SELECT
content ->> 'channel' AS channel,
content ->> 'subscribed' AS subscribed
FROM
messages
WHERE
author = ? AND
content ->> 'type' = 'channel'
ORDER BY sequence
`,
[this.whoami]
);
let channel_map = {};
for (let row of channels) {
if (row.subscribed) {
channel_map[row.channel] = true;
} else {
delete channel_map[row.channel];
}
}
this.channels = Object.keys(channel_map).sort();
}
connectedCallback() {
super.connectedCallback();
this._keydown = this.keydown.bind(this);
window.addEventListener('keydown', this._keydown);
}
disconnectedCallback() {
super.disconnectedCallback();
window.removeEventListener('keydown', this._keydown);
}
keydown(event) {
if (event.altKey && event.key == 'ArrowUp') {
this.next_channel(1);
event.preventDefault();
} else if (event.altKey && event.key == 'ArrowDown') {
this.next_channel(-1);
event.preventDefault();
}
}
next_channel(delta) {
let channel_names = ['', '@'].concat(this.channels);
let index = channel_names.indexOf(this.hash.substring(1));
if (index != -1) {
index += delta;
this.set_hash(
'#' +
encodeURIComponent(
channel_names[(index + channel_names.length) % channel_names.length]
)
);
}
}
set_hash(hash) {
this.hash = hash || '#';
this.hash = decodeURIComponent(hash || '#');
if (this.hash.startsWith('#q=')) {
this.tab = 'search';
} else if (this.hash === '#connections') {
this.tab = 'connections';
} else if (this.hash === '#mentions') {
this.tab = 'mentions';
} else if (this.hash.startsWith('#sql=')) {
this.tab = 'query';
} else {
@ -167,10 +233,19 @@ class TfElement extends LitElement {
`,
[JSON.stringify(this.following), id]
);
for (let message of messages) {
if (message.author == this.whoami) {
let content = JSON.parse(message.content);
if (content?.type == 'channel') {
this.load_channels();
}
}
}
if (messages && messages.length) {
this.unread = [...this.unread, ...messages];
this.unread = this.unread.slice(this.unread.length - 1024);
}
this.schedule_load_channels_latest();
}
async _handle_whoami_changed(event) {
@ -195,32 +270,94 @@ class TfElement extends LitElement {
}
}
async load_recent_tags() {
let start = new Date();
this.tags = await tfrpc.rpc.query(
`
WITH
recent AS (SELECT id, json(content) AS content FROM messages
WHERE messages.timestamp > ? AND json_extract(content, '$.type') = 'post'
ORDER BY timestamp DESC LIMIT 1024),
recent_channels AS (SELECT recent.id, '#' || json_extract(content, '$.channel') AS tag
FROM recent
WHERE json_extract(content, '$.channel') IS NOT NULL),
recent_mentions AS (SELECT recent.id, json_extract(mention.value, '$.link') AS tag
FROM recent, json_each(recent.content, '$.mentions') AS mention
WHERE json_valid(mention.value) AND tag LIKE '#%'),
combined AS (SELECT id, tag FROM recent_channels UNION ALL SELECT id, tag FROM recent_mentions),
by_message AS (SELECT DISTINCT id, tag FROM combined)
SELECT tag, COUNT(*) AS count FROM by_message GROUP BY tag ORDER BY count DESC LIMIT 10
`,
[new Date() - 7 * 24 * 60 * 60 * 1000]
);
console.log('tags took', (new Date() - start) / 1000.0, 'seconds');
async get_latest_private(following) {
let latest = (
await tfrpc.rpc.query('SELECT MAX(rowid) AS latest FROM messages')
)[0].latest;
const k_chunk_count = 256;
while (latest - k_chunk_count >= 0) {
let messages = await tfrpc.rpc.query(
`
SELECT messages.rowid, messages.id, previous, author, sequence, timestamp, hash, json(content) AS content, signature
FROM messages
JOIN json_each(?1) AS following ON messages.author = following.value
WHERE
messages.rowid > ?2 AND
messages.rowid <= ?3 AND
json(messages.content) LIKE '"%'
ORDER BY sequence DESC
`,
[JSON.stringify(following), latest - k_chunk_count, latest]
);
messages = (await this.decrypt(messages)).filter((x) => x.decrypted);
if (messages.length) {
return Math.max(...messages.map((x) => x.rowid));
}
latest -= k_chunk_count;
}
return -1;
}
async load_channels_latest(following) {
this.loading_channels_latest++;
try {
let start_time = new Date();
let latest_private = this.get_latest_private(following);
let channels = await tfrpc.rpc.query(
`
SELECT channels.value AS channel, MAX(messages.rowid) AS rowid FROM messages
JOIN json_each(?1) AS channels ON messages.content ->> 'channel' = channels.value
JOIN json_each(?2) AS following ON messages.author = following.value
WHERE messages.content ->> 'type' = 'post' AND messages.content ->> 'root' IS NULL
GROUP by channel
UNION
SELECT '' AS channel, MAX(messages.rowid) AS rowid FROM messages
JOIN json_each(?2) AS following ON messages.author = following.value
UNION
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
`,
[
JSON.stringify(this.channels),
JSON.stringify(following),
'"' + this.whoami.replace('"', '""') + '"',
]
);
this.channels_latest = Object.fromEntries(
channels.map((x) => [x.channel, x.rowid])
);
console.log('latest', this.channels_latest);
console.log('unread', this.channels_unread);
console.log('channels took', (new Date() - start_time) / 1000.0);
let self = this;
latest_private.then(function (latest) {
self.channels_latest = Object.assign({}, self.channels_latest, {
'🔐': latest,
});
console.log('private took', (new Date() - start_time) / 1000.0);
});
} finally {
this.loading_channels_latest--;
}
}
_schedule_load_channels_latest_timer() {
--this.loading_channels_latest_scheduled;
this.schedule_load_channels_latest();
}
schedule_load_channels_latest() {
if (!this.loading_channels_latest) {
this.load_channels_latest(this.following);
} else if (!this.loading_channels_latest_scheduled) {
this.loading_channels_latest_scheduled++;
setTimeout(this._schedule_load_channels_latest_timer, 5000);
}
}
async load() {
let whoami = this.whoami;
let tags = this.load_recent_tags();
let following = await tfrpc.rpc.following([whoami], 2);
let users = {};
let by_count = [];
@ -233,7 +370,10 @@ class TfElement extends LitElement {
};
by_count.push({count: v.of, id: id});
}
console.log(by_count.sort((x, y) => y.count - x.count).slice(0, 20));
let channels_latest = this.load_channels_latest(Object.keys(following));
this.channels_unread = JSON.parse(
(await tfrpc.rpc.databaseGet('unread')) ?? '{}'
);
let start_time = new Date();
users = await this.fetch_about(Object.keys(following).sort(), users);
console.log(
@ -243,14 +383,47 @@ class TfElement extends LitElement {
Object.keys(users).length,
'users'
);
start_time = new Date();
await channels_latest;
this.following = Object.keys(following);
this.users = users;
await tags;
console.log(`load finished ${whoami} => ${this.whoami}`);
this.whoami = whoami;
this.loaded = whoami;
}
channel_set_unread(event) {
this.channels_unread[event.detail.channel ?? ''] = event.detail.unread;
this.channels_unread = Object.assign({}, this.channels_unread);
tfrpc.rpc.databaseSet('unread', JSON.stringify(this.channels_unread));
}
async decrypt(messages) {
let whoami = this.whoami;
return Promise.all(
messages.map(async function (message) {
let content;
try {
content = JSON.parse(message?.content);
} catch {}
if (typeof content === 'string') {
let decrypted;
try {
decrypted = await tfrpc.rpc.try_decrypt(whoami, content);
} catch {}
if (decrypted) {
try {
message.decrypted = JSON.parse(decrypted);
} catch {
message.decrypted = decrypted;
}
}
}
return message;
})
);
}
render_tab() {
let following = this.following;
let users = this.users;
@ -265,6 +438,10 @@ class TfElement extends LitElement {
.unread=${this.unread}
@refresh=${() => (this.unread = [])}
?loading=${this.loading}
.channels=${this.channels}
.channels_latest=${this.channels_latest}
.channels_unread=${this.channels_unread}
@channelsetunread=${this.channel_set_unread}
></tf-tab-news>
`;
} else if (this.tab === 'connections') {
@ -275,14 +452,6 @@ class TfElement extends LitElement {
.broadcasts=${this.broadcasts}
></tf-tab-connections>
`;
} else if (this.tab === 'mentions') {
return html`
<tf-tab-mentions
.following=${this.following}
whoami=${this.whoami}
.users="${this.users}}"
></tf-tab-mentions>
`;
} else if (this.tab === 'search') {
return html`
<tf-tab-search
@ -314,13 +483,15 @@ class TfElement extends LitElement {
await tfrpc.rpc.setHash('#');
} else if (tab === 'connections') {
await tfrpc.rpc.setHash('#connections');
} else if (tab === 'mentions') {
await tfrpc.rpc.setHash('#mentions');
} else if (tab === 'query') {
await tfrpc.rpc.setHash('#sql=');
}
}
refresh() {
tfrpc.rpc.sync();
}
render() {
let self = this;
@ -334,13 +505,21 @@ class TfElement extends LitElement {
const k_tabs = {
'📰': 'news',
'📡': 'connections',
'@': 'mentions',
'🔍': 'search',
'👩‍💻': 'query',
};
let tabs = html`
<div class="w3-bar w3-theme-l1">
<div
class="w3-bar w3-theme-l1"
style="position: sticky; top: 0; z-index: 10"
>
<button
class="w3-bar-item w3-button w3-circle w3-ripple"
@click=${this.refresh}
>
</button>
${Object.entries(k_tabs).map(
([k, v]) => html`
<button
@ -374,13 +553,7 @@ class TfElement extends LitElement {
style="width: 100vw; min-height: 100vh; height: 100%"
class="w3-theme-dark"
>
${tabs}
<div style="padding: 8px">
${this.tags.map(
(x) => html`<tf-tag tag=${x.tag} count=${x.count}></tf-tag>`
)}
${contents}
</div>
${tabs} ${contents}
</div>
`;
}

@ -14,6 +14,7 @@ class TfComposeElement extends LitElement {
apps: {type: Object},
drafts: {type: Object},
author: {type: String},
channel: {type: String},
};
}
@ -180,6 +181,13 @@ class TfComposeElement extends LitElement {
break;
}
}
event.preventDefault();
document.execCommand(
'insertText',
false,
event.clipboardData.getData('text/plain')
);
}
async submit() {
@ -189,6 +197,7 @@ class TfComposeElement extends LitElement {
let message = {
type: 'post',
text: edit.innerText,
channel: this.channel,
};
if (this.root || this.branch) {
message.root = this.root;
@ -528,6 +537,9 @@ class TfComposeElement extends LitElement {
class="w3-card-4 w3-theme-d4 w3-padding-small"
style="box-sizing: border-box"
>
${this.channel !== undefined
? html`<p>To #${this.channel}:</p>`
: undefined}
${this.render_encrypt()}
<div class="w3-container w3-padding-small">
<div class="w3-half">

@ -14,6 +14,8 @@ class TfMessageElement extends LitElement {
format: {type: String},
blog_data: {type: String},
expanded: {type: Object},
channel: {type: String},
channel_unread: {type: Number},
};
}
@ -28,6 +30,7 @@ class TfMessageElement extends LitElement {
this.drafts = {};
this.format = 'message';
this.expanded = {};
this.channel_unread = -1;
}
show_reply() {
@ -73,18 +76,23 @@ class TfMessageElement extends LitElement {
}
}
if (this.message?.votes?.length) {
return html`<div class="w3-button" @click=${this.show_reactions}>
${(this.message.votes || []).map(
(vote) => html`
<span
title="${this.users[vote.author]?.name ?? vote.author} ${new Date(
vote.timestamp
)}"
>
${normalize_expression(vote.content.vote.expression)}
</span>
`
)}
return html` <div class="w3-container">
<div
class="w3-button w3-bar w3-padding-small"
@click=${this.show_reactions}
>
${(this.message.votes || []).map(
(vote) => html`
<span
class="w3-bar-item w3-padding-small"
title="${this.users[vote.author]?.name ??
vote.author} ${new Date(vote.timestamp)}"
>
${normalize_expression(vote.content.vote.expression)}
</span>
`
)}
</div>
</div>`;
}
}
@ -223,7 +231,7 @@ class TfMessageElement extends LitElement {
>${mention.name}</a
>`;
} else if (mention.link?.startsWith('#')) {
return html` <a href=${'#q=' + encodeURIComponent(mention.link)}
return html` <a href=${'#' + encodeURIComponent('#' + mention.link)}
>${mention.link}</a
>`;
} else if (
@ -307,12 +315,27 @@ ${JSON.stringify(mention, null, 2)}</pre
.users=${this.users}
.drafts=${this.drafts}
.expanded=${this.expanded}
channel=${this.channel}
channel_unread=${this.channel_unread}
></tf-message>`
)}`;
}
}
}
mark_unread() {
this.dispatchEvent(
new CustomEvent('channelsetunread', {
bubbles: true,
composed: true,
detail: {
channel: this.channel,
unread: this.message.rowid,
},
})
);
}
render_channels() {
let content = this.message?.content;
if (this?.messsage?.decrypted?.type == 'post') {
@ -339,7 +362,9 @@ ${JSON.stringify(mention, null, 2)}</pre
}
let class_background = this.message?.decrypted
? 'w3-pale-red'
: 'w3-theme-d4';
: this.message?.rowid >= this.channel_unread
? 'w3-theme-d2'
: 'w3-theme-d4';
let self = this;
let raw_button;
switch (this.format) {
@ -398,14 +423,15 @@ ${JSON.stringify(mention, null, 2)}</pre
let body;
return html`
<div
class="w3-card-4 w3-theme-d4 w3-border-theme"
class="w3-card-4 ${class_background} w3-border-theme"
style="margin-top: 8px; padding: 16px; display: inline-block; overflow-wrap: anywhere"
>
<tf-user id=${self.message.author} .users=${self.users}></tf-user>
<span style="padding-right: 8px"
><a tfarget="_top" href=${'#' + self.message.id}>%</a> ${new Date(
self.message.timestamp
).toLocaleString()}</span
><a tfarget="_top" href=${'#' + encodeURIComponent(self.message.id)}
>%</a
>
${new Date(self.message.timestamp).toLocaleString()}</span
>
${raw_button} ${self.format == 'raw' ? self.render_raw() : inner}
${self.render_votes()}
@ -417,6 +443,8 @@ ${JSON.stringify(mention, null, 2)}</pre
.users=${self.users}
.drafts=${self.drafts}
.expanded=${self.expanded}
channel=${self.channel}
channel_unread=${self.channel_unread}
></tf-message>
`
)}
@ -425,7 +453,7 @@ ${JSON.stringify(mention, null, 2)}</pre
}
if (this.message?.type === 'contact_group') {
return html` <div
class="w3-card-4 w3-theme-d4 w3-border-theme"
class="w3-card-4 ${class_background} w3-border-theme"
style="margin-top: 8px; padding: 16px; overflow-wrap: anywhere"
>
${this.message.messages.map(
@ -436,15 +464,19 @@ ${JSON.stringify(mention, null, 2)}</pre
.users=${this.users}
.drafts=${this.drafts}
.expanded=${this.expanded}
channel=${this.channel}
channel_unread=${this.channel_unread}
></tf-message>`
)}
</div>`;
} else if (this.message.placeholder) {
return html` <div
class="w3-card-4 w3-theme-d4 w3-border-theme"
class="w3-card-4 ${class_background} w3-border-theme"
style="margin-top: 8px; padding: 16px; overflow-wrap: anywhere"
>
<a target="_top" href=${'#' + this.message.id}>${this.message.id}</a>
<a target="_top" href=${'#' + encodeURIComponent(this.message.id)}
>${this.message.id}</a
>
(placeholder)
<div>${this.render_votes()}</div>
${(this.message.child_messages || []).map(
@ -455,6 +487,8 @@ ${JSON.stringify(mention, null, 2)}</pre
.users=${this.users}
.drafts=${this.drafts}
.expanded=${this.expanded}
channel=${this.channel}
channel_unread=${this.channel_unread}
></tf-message>
`
)}
@ -595,7 +629,11 @@ ${JSON.stringify(content, null, 2)}</pre
${is_encrypted}
<span style="flex: 1"></span>
<span style="padding-right: 8px"
><a target="_top" href=${'#' + self.message.id}>%</a>
><a
target="_top"
href=${'#' + encodeURIComponent(self.message.id)}
>%</a
>
${new Date(this.message.timestamp).toLocaleString()}</span
>
<span>${raw_button}</span>
@ -606,6 +644,16 @@ ${JSON.stringify(content, null, 2)}</pre
<button class="w3-button w3-theme-d1" @click=${this.react}>
React
</button>
${!content.root && this.message.rowid < this.channel_unread
? html`
<button
class="w3-button w3-theme-d1"
@click=${this.mark_unread}
>
Mark Unread
</button>
`
: undefined}
</p>
${this.render_children()}
</div>
@ -638,7 +686,11 @@ ${JSON.stringify(content, null, 2)}</pre
${is_encrypted}
<span style="flex: 1"></span>
<span style="padding-right: 8px"
><a target="_top" href=${'#' + self.message.id}>%</a>
><a
target="_top"
href=${'#' + encodeURIComponent(self.message.id)}
>%</a
>
${new Date(this.message.timestamp).toLocaleString()}</span
>
<span>${raw_button}</span>
@ -721,14 +773,18 @@ ${JSON.stringify(content, null, 2)}</pre
}
</style>
<div
class="w3-card-4 w3-theme-d4 w3-border-theme"
class="w3-card-4 ${class_background} w3-border-theme"
style="margin-top: 8px; padding: 16px"
>
<div style="display: flex; flex-direction: row">
<tf-user id=${this.message.author} .users=${this.users}></tf-user>
<span style="flex: 1"></span>
<span style="padding-right: 8px"
><a target="_top" href=${'#' + self.message.id}>%</a>
><a
target="_top"
href=${'#' + encodeURIComponent(self.message.id)}
>%</a
>
${new Date(this.message.timestamp).toLocaleString()}</span
>
<span>${raw_button}</span>
@ -767,7 +823,7 @@ ${JSON.stringify(content, null, 2)}</pre
return small_frame(html`
<div>
${content.subscribed ? 'subscribed to' : 'unsubscribed from'}
<a href=${'#q=' + encodeURIComponent('#' + content.channel)}
<a href=${'#' + encodeURIComponent('#' + content.channel)}
>#${content.channel}</a
>
</div>

@ -11,6 +11,8 @@ class TfNewsElement extends LitElement {
following: {type: Array},
drafts: {type: Object},
expanded: {type: Object},
channel: {type: String},
channel_unread: {type: Number},
};
}
@ -25,6 +27,7 @@ class TfNewsElement extends LitElement {
this.following = [];
this.drafts = {};
this.expanded = {};
this.channel_unread = -1;
}
process_messages(messages) {
@ -33,12 +36,13 @@ class TfNewsElement extends LitElement {
console.log('processing', messages.length, 'messages');
function ensure_message(id) {
function ensure_message(id, rowid) {
let found = messages_by_id[id];
if (found) {
return found;
} else {
let added = {
rowid: rowid,
id: id,
placeholder: true,
content: '"placeholder"',
@ -53,7 +57,7 @@ class TfNewsElement extends LitElement {
function link_message(message) {
if (message.content.type === 'vote') {
let parent = ensure_message(message.content.vote.link);
let parent = ensure_message(message.content.vote.link, message.rowid);
if (!parent.votes) {
parent.votes = [];
}
@ -62,14 +66,14 @@ class TfNewsElement extends LitElement {
} else if (message.content.type == 'post') {
if (message.content.root) {
if (typeof message.content.root === 'string') {
let m = ensure_message(message.content.root);
let m = ensure_message(message.content.root, message.rowid);
if (!m.child_messages) {
m.child_messages = [];
}
m.child_messages.push(message);
message.parent_message = message.content.root;
} else {
let m = ensure_message(message.content.root[0]);
let m = ensure_message(message.content.root[0], message.rowid);
if (!m.child_messages) {
m.child_messages = [];
}
@ -162,6 +166,7 @@ class TfNewsElement extends LitElement {
} else {
if (group.length > 0) {
result.push({
rowid: Math.max(...group.map((x) => x.rowid)),
type: 'contact_group',
messages: group,
});
@ -170,6 +175,13 @@ class TfNewsElement extends LitElement {
result.push(message);
}
}
if (group.length > 0) {
result.push({
rowid: Math.max(...group.map((x) => x.rowid)),
type: 'contact_group',
messages: group,
});
}
return result;
}
@ -178,18 +190,38 @@ class TfNewsElement extends LitElement {
let final_messages = this.group_following(
this.finalize_messages(messages_by_id)
);
let unread_rowid = -1;
for (let message of final_messages) {
if (message.rowid >= this.channel_unread) {
unread_rowid = message.rowid;
}
}
return html`
<div style="display: flex; flex-direction: column">
<div>
${final_messages.map(
(x) =>
html`<tf-message
(x) => html`
<tf-message
.message=${x}
whoami=${this.whoami}
.users=${this.users}
.drafts=${this.drafts}
.expanded=${this.expanded}
collapsed="true"
></tf-message>`
channel=${this.channel}
channel_unread=${this.channel_unread}
></tf-message>
${x.rowid == unread_rowid && x != final_messages[0]
? html`<div style="display: flex; flex-direction: row">
<div
style="border-bottom: 1px solid #f00; flex: 1; align-self: center; height: 1px"
></div>
<div style="color: #f00; padding: 8px">unread</div>
<div
style="border-bottom: 1px solid #f00; flex: 1; align-self: center; height: 1px"
></div>
</div>`
: undefined}
`
)}
</div>
`;

@ -233,7 +233,11 @@ class TfProfileElement extends LitElement {
</button>`;
}
edit = html`
<button class="w3-button w3-theme-d1" @click=${this.save_edits}>
<button
id="save_profile"
class="w3-button w3-theme-d1"
@click=${this.save_edits}
>
Save Profile
</button>
<button class="w3-button w3-theme-d1" @click=${this.discard_edits}>
@ -242,7 +246,11 @@ class TfProfileElement extends LitElement {
${server_follow}
`;
} else {
edit = html`<button class="w3-button w3-theme-d1" @click=${this.edit}>
edit = html`<button
id="edit_profile"
class="w3-button w3-theme-d1"
@click=${this.edit}
>
Edit Profile
</button>`;
}
@ -289,16 +297,10 @@ class TfProfileElement extends LitElement {
typeof profile.image == 'string' ? profile.image : profile.image?.link;
image = this.editing?.image ?? image;
let description = this.editing?.description ?? profile.description;
return html`<div style="border: 2px solid black; background-color: rgba(255, 255, 255, 0.2); padding: 16px">
<tf-user id=${this.id} .users=${this.users}></tf-user> (${tfutils.human_readable_size(this.size)})
<div class="w3-row">
<div class="w3-col s1 w3-container w3-right">
<button class="w3-button w3-theme-d1 w3-ripple" @click=${this.copy_id}>Copy</button>
</div>
<div class="w3-rest w3-container">
<input type="text" class="w3-theme-d1" style="width: 100%; vertical-align: middle" readonly value=${this.id}></input>
</div>
</div>
return html`<div class="w3-container" style="box-sizing: border-box; border: 2px solid black; background-color: rgba(255, 255, 255, 0.2)">
<p><tf-user id=${this.id} .users=${this.users}></tf-user> (${tfutils.human_readable_size(this.size)})
<input type="text" class="w3-input w3-border w3-theme-d1" readonly value=${this.id}></input>
<button class="w3-button w3-theme-d1 w3-ripple" @click=${this.copy_id}>Copy</button>
<div style="display: flex; flex-direction: row; gap: 1em">
${edit_profile}
<div style="flex: 1 0 50%">
@ -312,11 +314,11 @@ class TfProfileElement extends LitElement {
Blocking ${profile.blocking} identities.
Blocked by ${profile.blocked} identities.
</div>
<div>
<p>
${edit}
${follow}
${block}
</div>
</p>
</div>`;
}
}

@ -26,9 +26,13 @@ class TfReactionsModalElement extends LitElement {
return this.votes?.length
? html` <div
class="w3-modal w3-animate-opacity"
style="display: block; box-sizing: border-box"
style="display: block; box-sizing: border-box; z-index: 10"
@click=${this.clear}
>
<div class="w3-modal-content w3-card-4 w3-theme-d1">
<div
class="w3-modal-content w3-card-4 w3-theme-d1"
onclick="event.stopPropagation()"
>
<div class="w3-container w3-padding">
<header class="w3-container">
<h2>Reactions</h2>

@ -286,29 +286,29 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
`;
// prettier-ignore
const w3_2016_riverside = css`
.w3-theme-l5 {color:#000 !important; background-color:#f4f6f9 !important}
.w3-theme-l4 {color:#000 !important; background-color:#d9e1ec !important}
.w3-theme-l3 {color:#000 !important; background-color:#b4c3d8 !important}
.w3-theme-l2 {color:#fff !important; background-color:#8ea6c5 !important}
.w3-theme-l1 {color:#fff !important; background-color:#6888b1 !important}
.w3-theme-d1 {color:#fff !important; background-color:#456185 !important}
.w3-theme-d2 {color:#fff !important; background-color:#3d5676 !important}
.w3-theme-d3 {color:#fff !important; background-color:#354b68 !important}
.w3-theme-d4 {color:#fff !important; background-color:#2e4059 !important}
.w3-theme-d5 {color:#fff !important; background-color:#26364a !important}
const w3_2016_snorkel_blue = css`
.w3-theme-l5 {color:#000 !important; background-color:#e9f5ff !important}
.w3-theme-l4 {color:#000 !important; background-color:#b5dffd !important}
.w3-theme-l3 {color:#000 !important; background-color:#6bc0fc !important}
.w3-theme-l2 {color:#fff !important; background-color:#21a0fa !important}
.w3-theme-l1 {color:#fff !important; background-color:#0479cc !important}
.w3-theme-d1 {color:#fff !important; background-color:#024575 !important}
.w3-theme-d2 {color:#fff !important; background-color:#023e68 !important}
.w3-theme-d3 {color:#fff !important; background-color:#02365b !important}
.w3-theme-d4 {color:#fff !important; background-color:#022e4e !important}
.w3-theme-d5 {color:#fff !important; background-color:#012641 !important}
.w3-theme-light {color:#000 !important; background-color:#f4f6f9 !important}
.w3-theme-dark {color:#fff !important; background-color:#26364a !important}
.w3-theme-action {color:#fff !important; background-color:#26364a !important}
.w3-theme-light {color:#000 !important; background-color:#e9f5ff !important}
.w3-theme-dark {color:#fff !important; background-color:#012641 !important}
.w3-theme-action {color:#fff !important; background-color:#012641 !important}
.w3-theme {color:#fff !important; background-color:#4c6a92 !important}
.w3-text-theme {color:#4c6a92 !important}
.w3-border-theme {border-color:#4c6a92 !important}
.w3-theme {color:#fff !important; background-color:#034f84 !important}
.w3-text-theme {color:#034f84 !important}
.w3-border-theme {border-color:#034f84 !important}
.w3-hover-theme:hover {color:#fff !important; background-color:#4c6a92 !important}
.w3-hover-text-theme:hover {color:#4c6a92 !important}
.w3-hover-border-theme:hover {border-color:#4c6a92 !important}
.w3-hover-theme:hover {color:#fff !important; background-color:#034f84 !important}
.w3-hover-text-theme:hover {color:#034f84 !important}
.w3-hover-border-theme:hover {border-color:#034f84 !important}
`;
export let styles = [tf, w3, w3_2016_riverside];
export let styles = [tf, w3, w3_2016_snorkel_blue];

@ -12,6 +12,9 @@ class TfTabConnectionsElement extends LitElement {
stored_connections: {type: Array},
users: {type: Object},
server_identity: {type: String},
connect_attempt: {type: Object},
connect_message: {type: String},
connect_success: {type: Boolean},
};
}
@ -88,20 +91,36 @@ class TfTabConnectionsElement extends LitElement {
`;
}
render_message(connection) {
return html`<div
?hidden=${this.connect_message === undefined ||
this.connect_attempt != connection}
style="cursor: pointer"
class=${'w3-panel ' + (this.connect_success ? 'w3-green' : 'w3-red')}
@click=${() => (this.connect_attempt = undefined)}
>
<p>${this.connect_message}</p>
</div>`;
}
render_broadcast(connection) {
let self = this;
return html`
<li class="w3-bar" style="overflow: hidden; overflow-wrap: nowrap">
<button
class="w3-bar-item w3-button w3-theme-d1"
@click=${() => tfrpc.rpc.connect(connection)}
>
Connect
</button>
<div class="w3-bar-item">
${TfTabConnectionsElement.k_broadcast_emojis[connection.origin]}
<tf-user id=${connection.pubkey} .users=${this.users}></tf-user>
${this.render_connection_summary(connection)}
<li>
<div class="w3-bar" style="overflow: hidden; overflow-wrap: nowrap">
<button
class="w3-bar-item w3-button w3-theme-d1"
@click=${() => self.connect(connection)}
>
Connect
</button>
<div class="w3-bar-item">
${TfTabConnectionsElement.k_broadcast_emojis[connection.origin]}
<tf-user id=${connection.pubkey} .users=${this.users}></tf-user>
${this.render_connection_summary(connection)}
</div>
</div>
${this.render_message(connection)}
</li>
`;
}
@ -129,6 +148,7 @@ class TfTabConnectionsElement extends LitElement {
>
Close
</button>
${connection.flags.one_shot ? '🔃' : undefined}
<tf-user id=${connection.id} .users=${this.users}></tf-user>
${connection.tunnel !== undefined
? '🚇'
@ -136,7 +156,9 @@ class TfTabConnectionsElement extends LitElement {
<div>
${requests.map(
(x) => html`
<span class="w3-tag w3-small"
<span
class=${'w3-tag w3-small ' +
(x.active ? 'w3-theme-l3' : 'w3-theme-d3')}
>${x.request_number > 0 ? '🟩' : '🟥'} ${x.name}
<span
class="w3-badge w3-white"
@ -156,16 +178,38 @@ class TfTabConnectionsElement extends LitElement {
`;
}
connect(address) {
let self = this;
self.connect_attempt = address;
self.connect_message = undefined;
self.connect_success = false;
tfrpc.rpc
.connect(address)
.then(function () {
if (self.connect_attempt == address) {
self.connect_message = 'Connected.';
self.connect_success = true;
}
})
.catch(function (error) {
if (self.connect_attempt == address) {
self.connect_message = 'Error: ' + error;
self.connect_success = false;
}
});
}
render() {
let self = this;
return html`
<div class="w3-container" style="box-sizing: border-box">
<h2>New Connection</h2>
<textarea class="w3-input w3-theme-d1" id="code"></textarea>
${this.render_message(this.renderRoot.getElementById('code')?.value)}
<button
class="w3-button w3-theme-d1"
@click=${() =>
tfrpc.rpc.connect(self.renderRoot.getElementById('code').value)}
self.connect(self.renderRoot.getElementById('code')?.value)}
>
Connect
</button>
@ -173,6 +217,9 @@ class TfTabConnectionsElement extends LitElement {
<ul class="w3-ul w3-border">
${this.broadcasts
.filter((x) => x.address)
.filter(
(x) => self.connections.map((c) => c.id).indexOf(x.pubkey) == -1
)
.map((x) => self.render_broadcast(x))}
</ul>
<h2>Connections</h2>
@ -189,23 +236,26 @@ class TfTabConnectionsElement extends LitElement {
<ul class="w3-ul w3-border">
${this.stored_connections.map(
(x) => html`
<li class="w3-bar">
<button
class="w3-bar-item w3-button w3-theme-d1"
@click=${() => self.forget_stored_connection(x)}
>
Forget
</button>
<button
class="w3-bar-item w3-button w3-theme-d1"
@click=${() => tfrpc.rpc.connect(x)}
>
Connect
</button>
<div class="w3-bar-item">
<tf-user id=${x.pubkey} .users=${self.users}></tf-user>
<div><small>${x.address}:${x.port}</small></div>
<li>
<div class="w3-bar">
<button
class="w3-bar-item w3-button w3-theme-d1"
@click=${() => self.forget_stored_connection(x)}
>
Forget
</button>
<button
class="w3-bar-item w3-button w3-theme-d1"
@click=${() => this.connect(x)}
>
Connect
</button>
<div class="w3-bar-item">
<tf-user id=${x.pubkey} .users=${self.users}></tf-user>
<div><small>${x.address}:${x.port}</small></div>
</div>
</div>
${this.render_message(x)}
</li>
`
)}

@ -1,78 +0,0 @@
import {LitElement, html, unsafeHTML} from './lit-all.min.js';
import * as tfrpc from '/static/tfrpc.js';
import {styles} from './tf-styles.js';
class TfTabMentionsElement extends LitElement {
static get properties() {
return {
whoami: {type: String},
users: {type: Object},
following: {type: Array},
expanded: {type: Object},
messages: {type: Array},
};
}
static styles = styles;
constructor() {
super();
let self = this;
this.whoami = null;
this.users = {};
this.following = [];
this.expanded = {};
this.messages = [];
}
async load() {
console.log('Loading...', this.whoami);
let results = await tfrpc.rpc.query(
`
SELECT messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
FROM messages_fts(?)
JOIN messages ON messages.rowid = messages_fts.rowid
JOIN json_each(?) AS following ON messages.author = following.value
WHERE messages.author != ?
ORDER BY timestamp DESC limit 20
`,
[
'"' + this.whoami.replace('"', '""') + '"',
JSON.stringify(this.following),
this.whoami,
]
);
console.log('Done.');
this.messages = results;
}
on_expand(event) {
if (event.detail.expanded) {
let expand = {};
expand[event.detail.id] = true;
this.expanded = Object.assign({}, this.expanded, expand);
} else {
delete this.expanded[event.detail.id];
this.expanded = Object.assign({}, this.expanded);
}
}
render() {
let self = this;
if (!this.loading) {
this.loading = true;
this.load();
}
return html`
<tf-news
id="news"
whoami=${this.whoami}
.messages=${this.messages}
.users=${this.users}
.expanded=${this.expanded}
@tf-expand=${this.on_expand}
></tf-news>
`;
}
}
customElements.define('tf-tab-mentions', TfTabMentionsElement);

@ -12,6 +12,11 @@ class TfTabNewsFeedElement extends LitElement {
messages: {type: Array},
drafts: {type: Object},
expanded: {type: Object},
channels_unread: {type: Object},
channels_latest: {type: Object},
loading: {type: Number},
time_range: {type: Array},
time_loading: {type: Array},
};
}
@ -26,30 +31,67 @@ class TfTabNewsFeedElement extends LitElement {
this.following = [];
this.drafts = {};
this.expanded = {};
this.start_time = new Date().valueOf() - 24 * 60 * 60 * 1000;
this.channels_unread = {};
this.channels_latest = {};
this.start_time = new Date().valueOf();
this.time_range = [0, 0];
this.time_loading = undefined;
this.loading = 0;
}
async fetch_messages() {
if (this.hash.startsWith('#@')) {
let r = await tfrpc.rpc.query(
channel() {
return this.hash.startsWith('##')
? this.hash.substring(2)
: this.hash.substring(1);
}
async fetch_messages(start_time, end_time) {
this.time_loading = [start_time, end_time];
let result;
if (this.hash == '#@') {
result = await tfrpc.rpc.query(
`
WITH mine AS (SELECT id, previous, author, sequence, timestamp, hash, json(content) AS content, 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(?1)
JOIN messages ON messages.rowid = messages_fts.rowid
JOIN json_each(?2) AS following ON messages.author = following.value
WHERE
messages.author != ?1 AND
messages.timestamp >= ?3 AND
messages.timestamp < ?4
ORDER BY timestamp DESC limit 20
`,
[
'"' + this.whoami.replace('"', '""') + '"',
JSON.stringify(this.following),
start_time,
end_time,
]
);
} else if (this.hash.startsWith('#@')) {
result = await tfrpc.rpc.query(
`
WITH mine AS (SELECT rowid, id, previous, author, sequence, timestamp, hash, json(content) AS content, signature
FROM messages
WHERE messages.author = ?
ORDER BY sequence DESC
LIMIT 20)
SELECT messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
ORDER BY sequence DESC)
SELECT messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
FROM mine
JOIN messages_refs ON mine.id = messages_refs.ref
JOIN messages ON messages_refs.message = messages.id
WHERE
mine.timestamp >= ?2 AND
mine.timestamp < ?3
UNION
SELECT * FROM mine
WHERE
mine.timestamp >= ?2 AND
mine.timestamp < ?3
`,
[this.hash.substring(1)]
[this.hash.substring(1), start_time, end_time]
);
return r;
} else if (this.hash.startsWith('#%')) {
return await tfrpc.rpc.query(
result = await tfrpc.rpc.query(
`
SELECT id, previous, author, sequence, timestamp, hash, json(content) AS content, signature
FROM messages
@ -62,6 +104,68 @@ class TfTabNewsFeedElement extends LitElement {
`,
[this.hash.substring(1)]
);
} else if (this.hash.startsWith('##')) {
let promises = [];
const k_following_limit = 256;
for (let i = 0; i < this.following.length; i += k_following_limit) {
promises.push(
tfrpc.rpc.query(
`
WITH news AS (SELECT messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
FROM messages
JOIN json_each(?) AS following ON messages.author = following.value
WHERE
messages.timestamp >= ? AND
messages.timestamp < ? AND
messages.content ->> 'channel' = ?
ORDER BY messages.timestamp DESC)
SELECT 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 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 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)
JOIN messages ON messages.rowid = messages_fts.rowid
JOIN json_each(?1) AS following ON messages.author = following.value
JOIN json_tree(messages.content, '$.mentions') AS mention ON mention.value = '#' || ?4
WHERE
messages.timestamp >= ?2 AND
messages.timestamp < ?3
UNION
SELECT news.* FROM news
`,
[
JSON.stringify(this.following.slice(i, i + k_following_limit)),
start_time,
end_time,
this.hash.substring(2),
'"#' + this.hash.substring(2).replace('"', '""') + '"',
]
)
);
}
result = [].concat(...(await Promise.all(promises)));
} else if (this.hash == '#🔐') {
result = await tfrpc.rpc.query(
`
SELECT messages.rowid, messages.id, previous, author, sequence, timestamp, hash, json(content) AS content, signature
FROM messages
JOIN json_each(?1) AS following ON messages.author = following.value
WHERE
messages.timestamp >= ?2 AND
messages.timestamp < ?3 AND
json(messages.content) LIKE '"%'
ORDER BY sequence DESC
`,
[JSON.stringify(this.following), start_time, end_time]
);
result = (await this.decrypt(result)).filter((x) => x.decrypted);
} else {
let promises = [];
const k_following_limit = 256;
@ -69,17 +173,17 @@ class TfTabNewsFeedElement extends LitElement {
promises.push(
tfrpc.rpc.query(
`
WITH news AS (SELECT messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
WITH news AS (SELECT messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
FROM messages
JOIN json_each(?) AS following ON messages.author = following.value
WHERE messages.timestamp > ? AND messages.timestamp < ?
WHERE messages.timestamp >= ? AND messages.timestamp < ?
ORDER BY messages.timestamp DESC)
SELECT messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
SELECT 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 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 news
JOIN messages_refs ON news.id = messages_refs.message
JOIN messages ON messages_refs.ref = messages.id
@ -88,50 +192,58 @@ class TfTabNewsFeedElement extends LitElement {
`,
[
JSON.stringify(this.following.slice(i, i + k_following_limit)),
this.start_time,
/*
** Don't show messages more than a day into the future to prevent
** messages with far-future timestamps from staying at the top forever.
*/
new Date().valueOf() + 24 * 60 * 60 * 1000,
start_time,
end_time,
]
)
);
}
return [].concat(...(await Promise.all(promises)));
result = [].concat(...(await Promise.all(promises)));
}
this.time_loading = undefined;
return result;
}
update_time_range_from_messages(messages) {
this.time_range = [
messages.reduce(
(accumulator, current) => Math.min(accumulator, current.timestamp),
this.time_range[0]
),
messages.reduce(
(accumulator, current) => Math.max(accumulator, current.timestamp),
this.time_range[1]
),
];
}
async load_more() {
let last_start_time = this.start_time;
this.start_time = last_start_time - 24 * 60 * 60 * 1000;
let more = await tfrpc.rpc.query(
`
WITH news AS (SELECT messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
FROM messages
JOIN json_each(?) AS following ON messages.author = following.value
WHERE messages.timestamp > ?
AND messages.timestamp <= ?
ORDER BY messages.timestamp DESC)
SELECT messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
FROM news
JOIN messages_refs ON news.id = messages_refs.ref
JOIN messages ON messages_refs.message = messages.id
UNION
SELECT messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
FROM news
JOIN messages_refs ON news.id = messages_refs.message
JOIN messages ON messages_refs.ref = messages.id
UNION
SELECT news.* FROM news
`,
[JSON.stringify(this.following), this.start_time, last_start_time]
);
this.messages = await this.decrypt([...more, ...this.messages]);
this.loading++;
this.loading_canceled = false;
try {
let more = [];
while (!more.length && !this.loading_canceled) {
let last_start_time = this.start_time;
this.start_time = last_start_time - 7 * 24 * 60 * 60 * 1000;
more = await this.fetch_messages(this.start_time, last_start_time);
this.update_time_range_from_messages(
more.filter(
(x) =>
x.timestamp >= this.start_time && x.timestamp < last_start_time
)
);
}
this.messages = await this.decrypt([...more, ...this.messages]);
} finally {
this.loading--;
}
}
cancel_load() {
this.loading_canceled = true;
}
async decrypt(messages) {
console.log('decrypt');
let result = [];
for (let message of messages) {
let content;
@ -156,8 +268,94 @@ class TfTabNewsFeedElement extends LitElement {
return result;
}
async add_messages(messages) {
this.messages = await this.decrypt([...messages, ...this.messages]);
async load_latest() {
this.loading++;
let now = new Date().valueOf();
let end_time = now + 24 * 60 * 60 * 1000;
let messages = [];
try {
messages = await this.fetch_messages(
this.time_range[1] - 24 * 60 * 60 * 1000,
end_time
);
messages = await this.decrypt(messages);
this.update_time_range_from_messages(
messages.filter(
(x) => x.timestamp >= this.time_range[1] && x.timestamp < end_time
)
);
} finally {
this.loading--;
}
this.messages = Object.values(
Object.fromEntries([...this.messages, ...messages].map((x) => [x.id, x]))
);
console.log('done loading latest messages.');
}
async load_messages() {
let self = this;
this.loading++;
let messages = [];
try {
this.messages = [];
this._messages_hash = this.hash;
this._messages_following = this.following;
let now = new Date().valueOf();
let start_time = now - 24 * 60 * 60 * 1000;
this.start_time = start_time;
this.time_range = [this.start_time, now + 24 * 60 * 60 * 1000];
messages = await this.fetch_messages(
this.time_range[0],
this.time_range[1]
);
this.update_time_range_from_messages(
messages.filter(
(x) =>
x.timestamp >= this.time_range[0] &&
x.timestamp < this.time_range[1]
)
);
messages = await this.decrypt(messages);
if (!messages.length) {
let more = [];
while (!more.length && start_time >= 0) {
let last_start_time = start_time;
start_time = last_start_time - 7 * 24 * 60 * 60 * 1000;
more = await this.fetch_messages(start_time, last_start_time);
this.update_time_range_from_messages(
more.filter(
(x) => x.timestamp >= start_time && x.timestamp < last_start_time
)
);
}
messages = await this.decrypt([...more, ...this.messages]);
}
} finally {
this.loading--;
}
this.messages = messages;
this.time_loading = undefined;
console.log(`loading messages done for ${self.whoami}`);
}
mark_all_read() {
let newest = this.messages.reduce(
(accumulator, current) => Math.max(accumulator, current.rowid),
this.channels_latest[this.channel()] ?? -1
);
if (newest >= 0) {
this.dispatchEvent(
new CustomEvent('channelsetunread', {
bubbles: true,
composed: true,
detail: {
channel: this.channel(),
unread: newest + 1,
},
})
);
}
}
render() {
@ -169,31 +367,49 @@ class TfTabNewsFeedElement extends LitElement {
console.log(
`loading messages for ${this.whoami} (following ${this.following.length})`
);
let self = this;
this.messages = [];
this._messages_hash = this.hash;
this._messages_following = this.following;
this.fetch_messages()
.then(this.decrypt.bind(this))
.then(function (messages) {
self.messages = messages;
console.log(`loading mesages done for ${self.whoami}`);
})
.catch(function (error) {
alert(JSON.stringify(error, null, 2));
});
this.load_messages();
}
let more;
if (!this.hash.startsWith('#@') && !this.hash.startsWith('#%')) {
if (!this.hash.startsWith('#%')) {
more = html`
<p>
<button class="w3-button w3-theme-d1" @click=${this.load_more}>
<button class="w3-button w3-theme-d1" @click=${this.mark_all_read}>
Mark All Read
</button>
<button
?disabled=${this.loading}
class="w3-button w3-theme-d1"
@click=${this.load_more}
>
Load More
</button>
<button
class=${'w3-button w3-theme-d1' + (this.loading ? '' : ' w3-hide')}
@click=${this.cancel_load}
>
Cancel
</button>
<span
>Showing
${new Date(
this.time_loading
? Math.min(this.time_loading[0], this.time_range[0])
: this.time_range[0]
).toLocaleDateString()}
-
${new Date(
this.time_loading
? Math.max(this.time_loading[1], this.time_range[1])
: this.time_range[1]
).toLocaleDateString()}.</span
>
</p>
`;
}
return html`
<button class="w3-button w3-theme-d1" @click=${this.mark_all_read}>
Mark All Read
</button>
<tf-news
id="news"
whoami=${this.whoami}
@ -202,6 +418,8 @@ class TfTabNewsFeedElement extends LitElement {
.following=${this.following}
.drafts=${this.drafts}
.expanded=${this.expanded}
channel=${this.channel()}
channel_unread=${this.channels_unread?.[this.channel()]}
></tf-news>
${more}
`;

@ -13,6 +13,9 @@ class TfTabNewsElement extends LitElement {
drafts: {type: Object},
expanded: {type: Object},
loading: {type: Boolean},
channels: {type: Array},
channels_unread: {type: Object},
channels_latest: {type: Object},
};
}
@ -29,6 +32,9 @@ class TfTabNewsElement extends LitElement {
this.cache = {};
this.drafts = {};
this.expanded = {};
this.channels_unread = {};
this.channels_latest = {};
this.channels = [];
tfrpc.rpc.localStorageGet('drafts').then(function (d) {
self.drafts = JSON.parse(d || '{}');
});
@ -48,10 +54,7 @@ class TfTabNewsElement extends LitElement {
let unread = this.unread;
let news = this.shadowRoot?.getElementById('news');
if (news) {
console.log('injecting messages', news.messages);
news.add_messages(
Object.values(Object.fromEntries(this.unread.map((x) => [x.id, x])))
);
news.load_latest();
this.dispatchEvent(new CustomEvent('refresh'));
}
}
@ -106,14 +109,57 @@ class TfTabNewsElement extends LitElement {
}
}
unread_status(channel) {
if (
this.channels_latest[channel] &&
(this.channels_unread[channel] === undefined ||
this.channels_unread[channel] <= this.channels_latest[channel])
) {
return '🔵';
}
}
show_sidebar() {
this.renderRoot.getElementById('sidebar').style.display = 'block';
this.renderRoot.getElementById('sidebar_overlay').style.display = 'block';
}
hide_sidebar() {
this.renderRoot.getElementById('sidebar').style.display = 'none';
this.renderRoot.getElementById('sidebar_overlay').style.display = 'none';
}
async channel_toggle_subscribed() {
let channel = this.hash.substring(2);
let subscribed = this.channels.indexOf(channel) != -1;
subscribed = !subscribed;
await tfrpc.rpc.appendMessage(this.whoami, {
type: 'channel',
channel: channel,
subscribed: subscribed,
});
if (subscribed) {
this.channels = [].concat([channel], this.channels).sort();
} else {
this.channels = this.channels.filter((x) => x != channel);
}
}
channel() {
return this.hash.startsWith('##') ? this.hash.substring(2) : undefined;
}
render() {
let profile = this.hash.startsWith('#@')
? html`<tf-profile
id=${this.hash.substring(1)}
whoami=${this.whoami}
.users=${this.users}
></tf-profile>`
: undefined;
let profile =
this.hash.startsWith('#@') && this.hash != '#@'
? html`<tf-profile
class="tf-profile"
id=${this.hash.substring(1)}
whoami=${this.whoami}
.users=${this.users}
></tf-profile>`
: undefined;
let edit_profile;
if (
!this.loading &&
@ -128,39 +174,118 @@ class TfTabNewsElement extends LitElement {
</div>`;
}
return html`
<p class="w3-bar">
<button
class="w3-bar-item w3-button w3-theme-d1"
@click=${this.show_more}
<div
class="w3-sidebar w3-bar-block w3-theme-d1 w3-collapse w3-animate-left"
style="width: 2in; left: 0; z-index: 5"
id="sidebar"
>
<div
class="w3-right w3-button w3-hide-large"
@click=${this.hide_sidebar}
>
${this.new_messages_text()}
</button>
</p>
<div class="w3-bar">
Welcome, <tf-user id=${this.whoami} .users=${this.users}></tf-user>!
${edit_profile}
&times;
</div>
${this.hash.startsWith('##') &&
this.channels.indexOf(this.hash.substring(2)) == -1
? html`
<div class="w3-bar-item w3-theme-d2">Viewing</div>
<a
href="#"
class="w3-bar-item w3-button"
style="font-weight: bold"
>${this.hash.substring(2)}</a
>
`
: undefined}
<div class="w3-bar-item w3-theme-d2">Channels</div>
<a
href="#"
class="w3-bar-item w3-button"
style=${this.hash == '#' ? 'font-weight: bold' : undefined}
>general ${this.unread_status('')}</a
>
<a
href="#@"
class="w3-bar-item w3-button"
style=${this.hash == '#@' ? 'font-weight: bold' : undefined}
>@mentions ${this.unread_status('@')}</a
>
<a
href="#🔐"
class="w3-bar-item w3-button"
style=${this.hash == '#🔐' ? 'font-weight: bold' : undefined}
>🔐private ${this.unread_status('🔐')}</a
>
${this.channels.map(
(x) => html`
<a
href=${'#' + encodeURIComponent('#' + x)}
class="w3-bar-item w3-button"
style=${this.hash == '##' + x ? 'font-weight: bold' : undefined}
>#${x} ${this.unread_status(x)}</a
>
`
)}
</div>
<div>
<tf-compose
id="tf-compose"
<div
class="w3-overlay"
id="sidebar_overlay"
@click=${this.hide_sidebar}
></div>
<div style="margin-left: 2in; padding: 8px" id="main" class="w3-main">
<div
id="show_sidebar"
class="w3-left w3-button w3-hide-large"
@click=${this.show_sidebar}
>
&#9776;
</div>
<p>
<button class="w3-button w3-theme-d1" @click=${this.show_more}>
${this.new_messages_text()}
</button>
${this.hash.startsWith('##')
? html`
<button
class="w3-button w3-theme-d1"
@click=${this.channel_toggle_subscribed}
>
${this.channels.indexOf(this.hash.substring(2)) != -1
? 'Unsubscribe from #'
: 'Subscribe to #'}${this.hash.substring(2)}
</button>
`
: undefined}
</p>
<div class="w3-bar">
Welcome, <tf-user id=${this.whoami} .users=${this.users}></tf-user>!
${edit_profile}
</div>
<div>
<tf-compose
id="tf-compose"
whoami=${this.whoami}
.users=${this.users}
.drafts=${this.drafts}
@tf-draft=${this.draft}
.channel=${this.channel()}
></tf-compose>
</div>
${profile}
<tf-tab-news-feed
id="news"
whoami=${this.whoami}
.users=${this.users}
.following=${this.following}
hash=${this.hash}
.drafts=${this.drafts}
.expanded=${this.expanded}
@tf-draft=${this.draft}
></tf-compose>
@tf-expand=${this.on_expand}
.channels_unread=${this.channels_unread}
.channels_latest=${this.channels_latest}
></tf-tab-news-feed>
</div>
${profile}
<tf-tab-news-feed
id="news"
whoami=${this.whoami}
.users=${this.users}
.following=${this.following}
hash=${this.hash}
.drafts=${this.drafts}
.expanded=${this.expanded}
@tf-draft=${this.draft}
@tf-expand=${this.on_expand}
></tf-tab-news-feed>
`;
}
}

@ -5,6 +5,7 @@ import {styles} from './tf-styles.js';
class TfTabSearchElement extends LitElement {
static get properties() {
return {
drafts: {type: Object},
whoami: {type: String},
users: {type: Object},
following: {type: Array},
@ -22,6 +23,10 @@ class TfTabSearchElement extends LitElement {
this.users = {};
this.following = [];
this.expanded = {};
this.drafts = {};
tfrpc.rpc.localStorageGet('drafts').then(function (d) {
self.drafts = JSON.parse(d || '{}');
});
}
async search(query) {
@ -70,6 +75,18 @@ class TfTabSearchElement extends LitElement {
}
}
draft(event) {
let id = event.detail.id || '';
let previous = this.drafts[id];
if (event.detail.draft !== undefined) {
this.drafts[id] = event.detail.draft;
} else {
delete this.drafts[id];
}
this.drafts = Object.assign({}, this.drafts);
tfrpc.rpc.localStorageSet('drafts', JSON.stringify(this.drafts));
}
render() {
if (this.query !== this.last_query) {
this.last_query = this.query;
@ -81,7 +98,7 @@ class TfTabSearchElement extends LitElement {
<input type="text" class="w3-input w3-theme-d1" id="search" value=${this.query} style="flex: 1" @keydown=${this.search_keydown}></input>
<button class="w3-button w3-theme-d1" @click=${(event) => self.search(self.renderRoot.getElementById('search').value)}>Search</button>
</div>
<tf-news id="news" whoami=${this.whoami} .messages=${this.messages} .users=${this.users} .expanded=${this.expanded} @tf-expand=${this.on_expand}></tf-news>
<tf-news id="news" whoami=${this.whoami} .messages=${this.messages} .users=${this.users} .expanded=${this.expanded} .drafts=${this.drafts} @tf-expand=${this.on_expand} @tf-draft=${this.draft}></tf-news>
`;
}
}

@ -18,7 +18,7 @@ class TfTagElement extends LitElement {
render() {
let number = this.count ? html` (${this.count})` : undefined;
return html`<a
href="#q=${this.tag}"
href=${'#' + encodeURIComponent(this.tag)}
style="display: inline-block; margin: 3px; border: 1px solid black; background-color: #444; padding: 4px; border-radius: 3px"
>${this.tag}${number}</a
>`;

@ -37,7 +37,7 @@ class TfUserElement extends LitElement {
if (image_link !== undefined) {
image = html`<img
class="w3-circle"
style="width: 2em; height: 2em; vertical-align: middle"
style="width: 2em; height: 2em; vertical-align: middle; object-fit: cover"
src="/${image_link}/view"
/>`;
}

@ -2,6 +2,12 @@ import * as hashtagify from './commonmark-hashtag.js';
const k_code_classes = 'w3-theme-l4 w3-theme-border w3-round';
var reUnsafeProtocol = /^javascript:|vbscript:|file:|data:/i;
var reSafeDataProtocol = /^data:image\/(?:png|gif|jpeg|webp)/i;
var potentiallyUnsafe = function (url) {
return reUnsafeProtocol.test(url) && !reSafeDataProtocol.test(url);
};
function image(node, entering) {
if (
node.firstChild?.type === 'text' &&
@ -81,8 +87,8 @@ function attrs(node) {
}
export function markdown(md) {
let reader = new commonmark.Parser({safe: true});
let writer = new commonmark.HtmlRenderer();
let reader = new commonmark.Parser();
let writer = new commonmark.HtmlRenderer({safe: true});
writer.image = image;
writer.code = code;
writer.attrs = attrs;

5
apps/storage.json Normal file

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

127
apps/storage/app.js Normal file

@ -0,0 +1,127 @@
async function query(sql, args) {
let rows = [];
await ssb.sqlAsync(sql, args ?? [], function (row) {
rows.push(row);
});
return rows;
}
async function get_biggest() {
return query(`
select author, sum(length(content)) as size from messages group by author order by size desc limit 10;
`);
}
async function get_total() {
return (
await query(`
select sum(length(content)) as size, count(distinct author) as count from messages;
`)
)[0];
}
async function get_names(identities) {
return query(
`
SELECT author, name FROM (
SELECT
messages.author,
RANK() OVER (PARTITION BY messages.author ORDER BY messages.sequence DESC) AS author_rank,
messages.content ->> 'name' AS name
FROM messages
JOIN json_each(?) AS identities ON identities.value = messages.author
WHERE
json_extract(messages.content, '$.type') = 'about' AND
content ->> 'about' = messages.author AND name IS NOT NULL)
WHERE author_rank = 1
`,
[JSON.stringify(identities)]
);
}
async function get_most_follows() {
return query(`
select author, count(*) as count
from messages
where content ->> 'type' = 'contact' and content ->> 'following' = true
group by author
order by count desc
limit 10;
`);
}
function nice_size(bytes) {
let value = bytes;
let index = 0;
let units = ['B', 'kB', 'MB', 'GB'];
while (value > 1024 && index < units.length - 1) {
value /= 1024;
index++;
}
return `${Math.round(value * 10) / 10} ${units[index]}`;
}
async function main() {
await app.setDocument(
'<p style="color: #fff">Finding the top 10 largest feeds...</p>'
);
let most_follows = await get_most_follows();
let total = await get_total();
let identities = await ssb.getAllIdentities();
let following1 = await ssb.following(identities, 1);
let following2 = await ssb.following(identities, 2);
let biggest = await get_biggest();
let names = await get_names(
[].concat(
biggest.map((x) => x.author),
most_follows.map((x) => x.author)
)
);
names = Object.fromEntries(names.map((x) => [x.author, x.name]));
for (let item of biggest) {
item.name = names[item.author];
item.following =
identities.indexOf(item.author) != -1
? 0
: following1[item.author] !== undefined
? 1
: following2[item.author] !== undefined
? 2
: undefined;
}
for (let item of most_follows) {
item.name = names[item.author];
}
let html = `<body style="color: #000; background-color: #ddd">\n
<h1>Storage Summary</h1>
<h2>Top 10 Accounts by Size</h2>
<ol>`;
for (let item of biggest) {
html += `<li>
<span style="color: #888">${nice_size(item.size)}</span>
<a target="_top" href="/~core/ssb/#${encodeURI(item.author)}">${item.name ?? item.author}</a>
</li>
\n`;
}
html += `
</ol>
<h2>Top 10 Accounts by Follows</h2>
<ol>`;
for (let item of most_follows) {
html += `<li>
<span style="color: #888">${item.count}</span>
${following2[item.author] ? '✅' : '🚫'}
<a target="_top" href="/~core/ssb/#${encodeURI(item.author)}">${item.name ?? item.author}</a>
</li>
\n`;
}
html += `
</ol>
<p>Total <span style="color: #888">${nice_size(total.size)}</span> in ${total.count} accounts.</p>
`;
await app.setDocument(html);
}
main().catch(function (e) {
print(e);
});

4
apps/test.json Normal file

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

3
apps/test/app.js Normal file

@ -0,0 +1,3 @@
app.setDocument(
'<p style="color: #fff">Maybe one day this app will run tests, but for now there is nothing to see here.</p>'
);

1
apps/test/hello.txt Normal file

@ -0,0 +1 @@
Hello, world!

@ -1,5 +1,5 @@
{
"type": "tildefriends-app",
"emoji": "👋",
"previous": "&W5aJp2DgOW5rQ0AOIC9Ut3DpsahPrO6PjkJ1PQbNRdM=.sha256"
"previous": "&7gFmLW5zSMhmxWWY1+jeRcHdullgujSqGJg94lVgr1k=.sha256"
}

78
apps/welcome/appimage.svg Normal file

@ -0,0 +1,78 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" width="48px" height="48px" id="svg3832" version="1.1" inkscape:version="0.47 r22583" sodipodi:docname="appimage-assistant_alt3.svg">
<defs id="defs3834">
<linearGradient inkscape:collect="always" xlink:href="#linearGradient3308-4-6-931-761-0" id="linearGradient2975" gradientUnits="userSpaceOnUse" x1="24.3125" y1="22.96875" x2="24.3125" y2="41.03125"/>
<linearGradient id="linearGradient3308-4-6-931-761-0">
<stop id="stop2919-2" style="stop-color:#ffffff;stop-opacity:1" offset="0"/>
<stop id="stop2921-76" style="stop-color:#ffffff;stop-opacity:0" offset="1"/>
</linearGradient>
<linearGradient inkscape:collect="always" xlink:href="#linearGradient4222" id="linearGradient2979" gradientUnits="userSpaceOnUse" gradientTransform="matrix(0,0.3704967,-0.3617496,0,33.508315,6.1670925)" x1="7.6485429" y1="26.437023" x2="41.861729" y2="26.437023"/>
<linearGradient id="linearGradient4222">
<stop id="stop4224" style="stop-color:#ffffff;stop-opacity:1" offset="0"/>
<stop id="stop4226" style="stop-color:#ffffff;stop-opacity:0" offset="1"/>
</linearGradient>
<linearGradient inkscape:collect="always" xlink:href="#linearGradient3308-4-6-931-761" id="linearGradient2982" gradientUnits="userSpaceOnUse" gradientTransform="translate(0,0.9999987)" x1="23.99999" y1="4.999989" x2="23.99999" y2="43"/>
<linearGradient id="linearGradient3308-4-6-931-761">
<stop id="stop2919" style="stop-color:#ffffff;stop-opacity:1" offset="0"/>
<stop id="stop2921" style="stop-color:#ffffff;stop-opacity:0" offset="1"/>
</linearGradient>
<radialGradient inkscape:collect="always" xlink:href="#linearGradient3575" id="radialGradient2985" gradientUnits="userSpaceOnUse" gradientTransform="matrix(0,1.0262008,-1.6561124,9.4072203e-4,-56.097482,-45.332325)" cx="48.42384" cy="-48.027504" fx="48.42384" fy="-48.027504" r="38.212933"/>
<linearGradient id="linearGradient3575">
<stop id="stop3577" style="stop-color:#fafafa;stop-opacity:1" offset="0"/>
<stop id="stop3579" style="stop-color:#e6e6e6;stop-opacity:1" offset="1"/>
</linearGradient>
<radialGradient inkscape:collect="always" xlink:href="#linearGradient3993" id="radialGradient2990" gradientUnits="userSpaceOnUse" gradientTransform="matrix(0,2.0478765,-2.7410544,-8.6412258e-8,47.161382,-8.837436)" cx="9.3330879" cy="8.4497671" fx="9.3330879" fy="8.4497671" r="19.99999"/>
<linearGradient id="linearGradient3993">
<stop offset="0" style="stop-color:#a3c0d0;stop-opacity:1" id="stop3995"/>
<stop offset="1" style="stop-color:#427da1;stop-opacity:1" id="stop4001"/>
</linearGradient>
<linearGradient inkscape:collect="always" xlink:href="#linearGradient2508" id="linearGradient2992" gradientUnits="userSpaceOnUse" gradientTransform="translate(0,0.9674382)" x1="14.048676" y1="44.137306" x2="14.048676" y2="4.0000005"/>
<linearGradient id="linearGradient2508">
<stop offset="0" style="stop-color:#2e4a5a;stop-opacity:1" id="stop2510"/>
<stop offset="1" style="stop-color:#6e8796;stop-opacity:1" id="stop2512"/>
</linearGradient>
<radialGradient cx="4.9929786" cy="43.5" r="2.5" fx="4.9929786" fy="43.5" id="radialGradient2873-966-168" xlink:href="#linearGradient3688-166-749" gradientUnits="userSpaceOnUse" gradientTransform="matrix(2.003784,0,0,1.4,27.98813,-17.4)"/>
<linearGradient id="linearGradient3688-166-749">
<stop id="stop2883" style="stop-color:#181818;stop-opacity:1" offset="0"/>
<stop id="stop2885" style="stop-color:#181818;stop-opacity:0" offset="1"/>
</linearGradient>
<radialGradient cx="4.9929786" cy="43.5" r="2.5" fx="4.9929786" fy="43.5" id="radialGradient2875-742-326" xlink:href="#linearGradient3688-464-309" gradientUnits="userSpaceOnUse" gradientTransform="matrix(2.003784,0,0,1.4,-20.01187,-104.4)"/>
<linearGradient id="linearGradient3688-464-309">
<stop id="stop2889" style="stop-color:#181818;stop-opacity:1" offset="0"/>
<stop id="stop2891" style="stop-color:#181818;stop-opacity:0" offset="1"/>
</linearGradient>
<linearGradient x1="25.058096" y1="47.027729" x2="25.058096" y2="39.999443" id="linearGradient2877-634-617" xlink:href="#linearGradient3702-501-757" gradientUnits="userSpaceOnUse"/>
<linearGradient id="linearGradient3702-501-757">
<stop id="stop2895" style="stop-color:#181818;stop-opacity:0" offset="0"/>
<stop id="stop2897" style="stop-color:#181818;stop-opacity:1" offset="0.5"/>
<stop id="stop2899" style="stop-color:#181818;stop-opacity:0" offset="1"/>
</linearGradient>
</defs>
<sodipodi:namedview id="base" pagecolor="#ffffff" bordercolor="#666666" borderopacity="1.0" inkscape:pageopacity="0.0" inkscape:pageshadow="2" inkscape:zoom="7" inkscape:cx="24" inkscape:cy="24" inkscape:current-layer="layer1" showgrid="true" inkscape:grid-bbox="true" inkscape:document-units="px" inkscape:window-width="603" inkscape:window-height="484" inkscape:window-x="417" inkscape:window-y="162" inkscape:window-maximized="0"/>
<metadata id="metadata3837">
<rdf:RDF>
<cc:Work rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
<dc:title/>
</cc:Work>
</rdf:RDF>
</metadata>
<g id="layer1" inkscape:label="Layer 1" inkscape:groupmode="layer">
<g style="display:inline" id="g2036" transform="matrix(1.1,0,0,0.4444449,-2.4000022,25.11107)">
<g style="opacity:0.4" id="g3712" transform="matrix(1.052632,0,0,1.285713,-1.263158,-13.42854)">
<rect style="fill:url(#radialGradient2873-966-168);fill-opacity:1;stroke:none" id="rect2801" y="40" x="38" height="7" width="5"/>
<rect style="fill:url(#radialGradient2875-742-326);fill-opacity:1;stroke:none" id="rect3696" transform="scale(-1,-1)" y="-47" x="-10" height="7" width="5"/>
<rect style="fill:url(#linearGradient2877-634-617);fill-opacity:1;stroke:none" id="rect3700" y="40" x="10" height="7.0000005" width="28"/>
</g>
</g>
<rect style="fill:url(#radialGradient2990);fill-opacity:1;stroke:url(#linearGradient2992);stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" id="rect5505" y="5.4674392" x="4.5" ry="2.2322156" rx="2.2322156" height="39" width="39"/>
<path style="opacity:0.05;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.00178742;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" id="path4294-1" d="m 21,6.9687498 a 2.0165107,2.0165107 0 0 0 -2.03125,2.03125 l 0,3.9687502 -1.15625,0 a 2.0165107,2.0165107 0 0 0 -1.5,3.375 l 5.0625,5.75 c -0.06312,0.110777 -0.178724,0.246032 -0.21875,0.34375 -0.195898,0.478256 -0.25,0.83653 -0.25,1.21875 l 0,0.125 L 20.8125,23.6875 C 20.534322,23.409323 20.213169,23.162739 19.71875,22.96875 19.47154,22.87176 19.185456,22.791748 18.75,22.8125 c -0.435456,0.02075 -1.054055,0.210302 -1.46875,0.625 L 15.75,24.96875 c -0.414689,0.414689 -0.604245,1.033294 -0.625,1.46875 -0.02075,0.435456 0.05925,0.721537 0.15625,0.96875 C 15.475241,27.900677 15.721817,28.221821 16,28.5 l 0.09375,0.09375 -0.125,0 c -0.382218,0 -0.740493,0.0541 -1.21875,0.25 -0.239128,0.09795 -0.538285,0.214988 -0.84375,0.53125 -0.305465,0.316262 -0.625,0.914788 -0.625,1.53125 l 0,2.1875 c 0,0.616465 0.319536,1.214989 0.625,1.53125 0.305464,0.316261 0.604622,0.433301 0.84375,0.53125 0.478256,0.195898 0.83653,0.25 1.21875,0.25 l 0.125,0 L 16,35.5 c -0.278175,0.278176 -0.52476,0.599329 -0.71875,1.09375 -0.09699,0.24721 -0.177003,0.533292 -0.15625,0.96875 0.02075,0.435458 0.210304,1.054058 0.625,1.46875 l 1.53125,1.53125 c 0.414691,0.414697 1.033292,0.604245 1.46875,0.625 0.435458,0.02076 0.721537,-0.05926 0.96875,-0.15625 0.494425,-0.19399 0.81557,-0.440568 1.09375,-0.71875 l 0.09375,-0.09375 0,0.125 c 0,0.38222 0.0541,0.740495 0.25,1.21875 0.09795,0.239127 0.214989,0.538285 0.53125,0.84375 0.316261,0.305465 0.914783,0.625 1.53125,0.625 l 2.1875,0 c 0.616466,0 1.214989,-0.319534 1.53125,-0.625 0.316261,-0.305466 0.433302,-0.604622 0.53125,-0.84375 0.195896,-0.478255 0.25,-0.836532 0.25,-1.21875 l 0,-0.125 0.09375,0.09375 c 0.278176,0.278175 0.599329,0.52476 1.09375,0.71875 0.24721,0.09699 0.533292,0.177003 0.96875,0.15625 0.435458,-0.02075 1.054058,-0.210304 1.46875,-0.625 L 32.875,39.03125 C 33.289697,38.616559 33.479245,37.997958 33.5,37.5625 33.52076,37.127042 33.44074,36.840963 33.34375,36.59375 33.14976,36.099325 32.903182,35.77818 32.625,35.5 l -0.09375,-0.09375 0.125,0 c 0.38222,0 0.740494,-0.0541 1.21875,-0.25 0.239128,-0.09795 0.538286,-0.214988 0.84375,-0.53125 0.305464,-0.316262 0.625,-0.914787 0.625,-1.53125 l 0,-2.1875 c 0,-0.61646 -0.319535,-1.214987 -0.625,-1.53125 -0.305465,-0.316263 -0.604621,-0.433301 -0.84375,-0.53125 -0.478257,-0.195898 -0.836532,-0.25 -1.21875,-0.25 l -0.125,0 L 32.625,28.5 c 0.278177,-0.278177 0.52476,-0.599329 0.71875,-1.09375 C 33.44074,27.15904 33.520753,26.872957 33.5,26.4375 33.47925,26.002043 33.289697,25.383443 32.875,24.96875 L 31.34375,23.4375 c -0.414688,-0.414694 -1.03329,-0.604245 -1.46875,-0.625 -0.43546,-0.02076 -0.721537,0.05925 -0.96875,0.15625 -0.494426,0.193991 -0.815572,0.44057 -1.09375,0.71875 l -0.09375,0.09375 0,-0.125 c 0,-0.382218 -0.0541,-0.740493 -0.25,-1.21875 -0.09112,-0.22245 -0.228127,-0.500183 -0.5,-0.78125 l 4.71875,-5.3125 a 2.0165107,2.0165107 0 0 0 -1.5,-3.375 l -1.15625,0 0,-3.9687502 A 2.0165107,2.0165107 0 0 0 27,6.9687498 l -6,0 z M 24.3125,31.25 c 0.427097,0 0.75,0.322904 0.75,0.75 0,0.427096 -0.322903,0.75 -0.75,0.75 -0.427094,0 -0.75,-0.322906 -0.75,-0.75 0,-0.427094 0.322906,-0.75 0.75,-0.75 z"/>
<path style="opacity:0.05;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.00178742;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" id="path4294" d="m 20.90625,8.0312498 a 0.96385067,0.96385067 0 0 0 -0.875,0.96875 l 0,5.0312502 -2.21875,0 A 0.96385067,0.96385067 0 0 0 17.09375,15.625 l 5.78125,6.53125 c -0.158814,0.0616 -0.341836,0.0951 -0.4375,0.1875 -0.169161,0.163386 -0.252971,0.323419 -0.3125,0.46875 -0.119058,0.290663 -0.15625,0.566746 -0.15625,0.84375 l 0,1.65625 C 21.718163,25.40233 21.485871,25.509772 21.25,25.625 l -1.1875,-1.1875 c -0.199651,-0.19965 -0.421433,-0.352095 -0.71875,-0.46875 -0.148659,-0.05833 -0.329673,-0.104846 -0.5625,-0.09375 -0.232827,0.0111 -0.53583,0.09833 -0.75,0.3125 L 16.5,25.71875 c -0.214168,0.214168 -0.301403,0.517173 -0.3125,0.75 -0.0111,0.232827 0.03542,0.41384 0.09375,0.5625 0.116655,0.297321 0.269096,0.519099 0.46875,0.71875 l 1.1875,1.1875 c -0.115228,0.235871 -0.222668,0.468163 -0.3125,0.71875 l -1.65625,0 c -0.277003,0 -0.553087,0.03719 -0.84375,0.15625 -0.145332,0.05953 -0.305363,0.143338 -0.46875,0.3125 -0.163387,0.169162 -0.3125,0.46403 -0.3125,0.78125 l 0,2.1875 c 0,0.317221 0.149114,0.612089 0.3125,0.78125 0.163386,0.169161 0.323419,0.252971 0.46875,0.3125 0.290663,0.119058 0.566746,0.15625 0.84375,0.15625 l 1.65625,0 c 0.08983,0.250587 0.197272,0.482879 0.3125,0.71875 L 16.75,36.25 c -0.199649,0.19965 -0.352095,0.421432 -0.46875,0.71875 -0.05833,0.148659 -0.104846,0.329672 -0.09375,0.5625 0.0111,0.232828 0.09833,0.535831 0.3125,0.75 l 1.53125,1.53125 c 0.214168,0.214172 0.517172,0.301403 0.75,0.3125 0.232828,0.0111 0.41384,-0.03542 0.5625,-0.09375 0.29732,-0.116655 0.519098,-0.269096 0.71875,-0.46875 L 21.25,38.375 c 0.235871,0.115228 0.468164,0.222668 0.71875,0.3125 l 0,1.65625 c 0,0.277003 0.03719,0.553087 0.15625,0.84375 0.05953,0.145331 0.143339,0.305364 0.3125,0.46875 0.169161,0.163386 0.464028,0.3125 0.78125,0.3125 l 2.1875,0 c 0.317221,0 0.612089,-0.149113 0.78125,-0.3125 0.169161,-0.163387 0.252971,-0.323419 0.3125,-0.46875 0.119057,-0.290663 0.15625,-0.566748 0.15625,-0.84375 l 0,-1.65625 c 0.250586,-0.08983 0.482879,-0.197272 0.71875,-0.3125 l 1.1875,1.1875 c 0.19965,0.199649 0.421432,0.352095 0.71875,0.46875 0.148659,0.05833 0.329672,0.104846 0.5625,0.09375 0.232828,-0.0111 0.535831,-0.09833 0.75,-0.3125 L 32.125,38.28125 c 0.214172,-0.214168 0.301403,-0.517172 0.3125,-0.75 0.0111,-0.232828 -0.03542,-0.41384 -0.09375,-0.5625 C 32.227095,36.67143 32.074654,36.449652 31.875,36.25 L 30.6875,35.0625 C 30.802728,34.82663 30.910168,34.594337 31,34.34375 l 1.65625,0 c 0.277004,0 0.553087,-0.03719 0.84375,-0.15625 0.145332,-0.05953 0.305364,-0.143339 0.46875,-0.3125 0.163386,-0.169161 0.3125,-0.46403 0.3125,-0.78125 l 0,-2.1875 c 0,-0.317219 -0.149114,-0.612088 -0.3125,-0.78125 C 33.805364,29.955838 33.645332,29.872029 33.5,29.8125 33.209336,29.693442 32.933253,29.65625 32.65625,29.65625 l -1.65625,0 C 30.91017,29.405663 30.802728,29.17337 30.6875,28.9375 L 31.875,27.75 c 0.19965,-0.19965 0.352095,-0.421432 0.46875,-0.71875 0.05833,-0.148659 0.104846,-0.329672 0.09375,-0.5625 -0.0111,-0.232828 -0.09833,-0.535831 -0.3125,-0.75 L 30.59375,24.1875 c -0.214167,-0.21417 -0.517171,-0.301403 -0.75,-0.3125 -0.232829,-0.0111 -0.41384,0.03542 -0.5625,0.09375 -0.29732,0.116656 -0.519099,0.269097 -0.71875,0.46875 L 27.375,25.625 c -0.235871,-0.115228 -0.468163,-0.222668 -0.71875,-0.3125 l 0,-1.65625 c 0,-0.277003 -0.03719,-0.553087 -0.15625,-0.84375 -0.05953,-0.145332 -0.143338,-0.305363 -0.3125,-0.46875 -0.169162,-0.163387 -0.46403,-0.3125 -0.78125,-0.3125 l -0.15625,0 5.65625,-6.40625 A 0.96385067,0.96385067 0 0 0 30.1875,14.03125 l -2.21875,0 0,-5.0312502 A 0.96385067,0.96385067 0 0 0 27,8.0312498 l -6,0 a 0.96385067,0.96385067 0 0 0 -0.09375,0 z M 24.3125,30.1875 c 1.002113,0 1.8125,0.810388 1.8125,1.8125 0,1.002112 -0.810387,1.8125 -1.8125,1.8125 C 23.31039,33.8125 22.5,33.002111 22.5,32 c 0,-1.002111 0.81039,-1.8125 1.8125,-1.8125 z"/>
<path style="fill:url(#radialGradient2985);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.00178742;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" id="path2317" d="M 21,8.9999996 21,15 17.8125,15 24,22 30.1875,15 27,15 l 0,-6.0000004 -6,0 z M 23.21875,23 c -0.172892,0 -0.28125,0.294922 -0.28125,0.65625 l 0,2.28125 C 22.24145,26.095996 21.585954,26.379869 21,26.75 l -1.625,-1.625 c -0.255498,-0.255497 -0.533998,-0.372253 -0.65625,-0.25 l -1.53125,1.53125 c -0.122254,0.122254 -0.0055,0.400753 0.25,0.65625 l 1.625,1.625 c -0.37013,0.585953 -0.654003,1.24145 -0.8125,1.9375 l -2.28125,0 c -0.361328,0 -0.65625,0.108357 -0.65625,0.28125 l 0,2.1875 c 0,0.172892 0.294922,0.28125 0.65625,0.28125 l 2.28125,0 c 0.158497,0.69605 0.44237,1.351546 0.8125,1.9375 l -1.625,1.625 c -0.255497,0.255498 -0.372254,0.533997 -0.25,0.65625 l 1.53125,1.53125 c 0.122252,0.122254 0.400752,0.0055 0.65625,-0.25 L 21,37.25 c 0.585954,0.37013 1.24145,0.654002 1.9375,0.8125 l 0,2.28125 C 22.9375,40.705077 23.045858,41 23.21875,41 l 2.1875,0 c 0.172893,0 0.28125,-0.294924 0.28125,-0.65625 l 0,-2.28125 c 0.69605,-0.158498 1.351546,-0.44237 1.9375,-0.8125 l 1.625,1.625 c 0.255498,0.255497 0.533997,0.372254 0.65625,0.25 l 1.53125,-1.53125 c 0.122254,-0.122252 0.0055,-0.400752 -0.25,-0.65625 l -1.625,-1.625 c 0.370129,-0.585954 0.654003,-1.24145 0.8125,-1.9375 l 2.28125,0 c 0.361329,0 0.65625,-0.108358 0.65625,-0.28125 l 0,-2.1875 c 0,-0.172893 -0.294921,-0.28125 -0.65625,-0.28125 l -2.28125,0 c -0.158497,-0.69605 -0.442371,-1.351547 -0.8125,-1.9375 l 1.625,-1.625 c 0.255497,-0.255497 0.372254,-0.533997 0.25,-0.65625 L 29.90625,24.875 C 29.783997,24.752745 29.505498,24.8695 29.25,25.125 l -1.625,1.625 c -0.585954,-0.370131 -1.24145,-0.654004 -1.9375,-0.8125 l 0,-2.28125 C 25.6875,23.294922 25.579143,23 25.40625,23 l -2.1875,0 z m 1.09375,6.21875 c 1.528616,0 2.78125,1.252635 2.78125,2.78125 0,1.528615 -1.252634,2.78125 -2.78125,2.78125 -1.528614,0 -2.78125,-1.252635 -2.78125,-2.78125 0,-1.528615 1.252636,-2.78125 2.78125,-2.78125 z"/>
<rect style="opacity:0.4;fill:none;stroke:url(#linearGradient2982);stroke-width:0.99999976;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" id="rect6741" y="6.4999886" x="5.4999981" ry="1.365193" rx="1.365193" height="37.000011" width="36.999985"/>
<path style="fill:none;stroke:url(#linearGradient2979);stroke-width:0.99829447;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible" id="path2777" d="M 28.926376,15.466668 24,21.177578 18.963089,15.5 21.5,15.5 l 0,-6.0000004 5,0 0,6.0000004 2.426376,-0.03333 z"/>
<path style="fill:none;stroke:url(#linearGradient2975);stroke-width:1;stroke-opacity:1;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" id="path4243" d="m 23.4375,23.46875 c -0.01166,0.05381 -0.03125,0.100205 -0.03125,0.1875 l 0,2.28125 a 0.48185467,0.48185467 0 0 1 -0.375,0.46875 c -0.638467,0.145384 -1.238423,0.407111 -1.78125,0.75 a 0.48185467,0.48185467 0 0 1 -0.59375,-0.0625 l -1.625,-1.625 C 18.9779,25.4154 18.9477,25.40242 18.90625,25.375 l -1.21875,1.21875 c 0.02742,0.04145 0.0404,0.07165 0.09375,0.125 l 1.625,1.625 a 0.48185467,0.48185467 0 0 1 0.0625,0.59375 c -0.342888,0.542826 -0.604615,1.142782 -0.75,1.78125 a 0.48185467,0.48185467 0 0 1 -0.46875,0.375 l -2.28125,0 c -0.08729,0 -0.133695,0.01959 -0.1875,0.03125 l 0,1.75 c 0.05381,0.01166 0.100205,0.03125 0.1875,0.03125 l 2.28125,0 a 0.48185467,0.48185467 0 0 1 0.46875,0.375 c 0.145385,0.638468 0.407112,1.238423 0.75,1.78125 a 0.48185467,0.48185467 0 0 1 -0.0625,0.59375 l -1.625,1.625 c -0.05335,0.05335 -0.06633,0.08355 -0.09375,0.125 l 1.21875,1.21875 c 0.04145,-0.02742 0.07165,-0.0404 0.125,-0.09375 l 1.625,-1.625 A 0.48185467,0.48185467 0 0 1 21.25,36.84375 c 0.542827,0.342888 1.142781,0.604614 1.78125,0.75 a 0.48185467,0.48185467 0 0 1 0.375,0.46875 l 0,2.28125 c 0,0.08729 0.01959,0.133695 0.03125,0.1875 l 1.75,0 c 0.01166,-0.0538 0.03125,-0.100206 0.03125,-0.1875 l 0,-2.28125 a 0.48185467,0.48185467 0 0 1 0.375,-0.46875 c 0.638469,-0.145386 1.238423,-0.407112 1.78125,-0.75 a 0.48185467,0.48185467 0 0 1 0.59375,0.0625 l 1.625,1.625 c 0.05335,0.05335 0.08355,0.06633 0.125,0.09375 l 1.21875,-1.21875 c -0.02742,-0.04145 -0.0404,-0.07165 -0.09375,-0.125 l -1.625,-1.625 a 0.48185467,0.48185467 0 0 1 -0.0625,-0.59375 c 0.342888,-0.542828 0.604615,-1.142783 0.75,-1.78125 a 0.48185467,0.48185467 0 0 1 0.46875,-0.375 l 2.28125,0 c 0.08729,0 0.133695,-0.01959 0.1875,-0.03125 l 0,-1.75 c -0.0538,-0.01166 -0.100204,-0.03125 -0.1875,-0.03125 l -2.28125,0 a 0.48185467,0.48185467 0 0 1 -0.46875,-0.375 c -0.145385,-0.638467 -0.407113,-1.238424 -0.75,-1.78125 a 0.48185467,0.48185467 0 0 1 0.0625,-0.59375 l 1.625,-1.625 c 0.05335,-0.05335 0.06633,-0.08355 0.09375,-0.125 L 29.71875,25.375 c -0.04145,0.02742 -0.07165,0.0404 -0.125,0.09375 l -1.625,1.625 a 0.48185467,0.48185467 0 0 1 -0.59375,0.0625 c -0.542827,-0.342889 -1.142783,-0.604616 -1.78125,-0.75 a 0.48185467,0.48185467 0 0 1 -0.375,-0.46875 l 0,-2.28125 c 0,-0.0873 -0.01959,-0.133695 -0.03125,-0.1875 l -1.75,0 z m 0.875,5.28125 c 1.791829,0 3.25,1.458172 3.25,3.25 0,1.791828 -1.458171,3.25 -3.25,3.25 -1.791827,0 -3.25,-1.458172 -3.25,-3.25 0,-1.791828 1.458173,-3.25 3.25,-3.25 z"/>
</g>
</svg>

After

(image error) Size: 19 KiB

75
apps/welcome/f-droid.svg Normal file

@ -0,0 +1,75 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" width="48" height="48" viewBox="0 0 48.000001 48.000001" id="svg4230" version="1.1" inkscape:version="0.91 r13725" sodipodi:docname="fdroid-logo.svg">
<defs id="defs4232">
<linearGradient inkscape:collect="always" id="linearGradient5212">
<stop style="stop-color:#ffffff;stop-opacity:0.09803922" offset="0" id="stop5214"/>
<stop style="stop-color:#ffffff;stop-opacity:0" offset="1" id="stop5216"/>
</linearGradient>
<radialGradient inkscape:collect="always" xlink:href="#linearGradient5212" id="radialGradient5220" cx="-98.23381" cy="3.4695871" fx="-98.23381" fy="3.4695871" r="22.671185" gradientTransform="matrix(0,1.9747624,-2.117225,3.9784049e-8,8.677247,1199.588)" gradientUnits="userSpaceOnUse"/>
<filter inkscape:collect="always" style="color-interpolation-filters:sRGB" id="filter4175" x="-0.023846937" width="1.0476939" y="-0.02415504" height="1.0483101">
<feGaussianBlur inkscape:collect="always" stdDeviation="0.45053152" id="feGaussianBlur4177"/>
</filter>
</defs>
<sodipodi:namedview id="base" pagecolor="#ffffff" bordercolor="#666666" borderopacity="1.0" inkscape:pageopacity="0.0" inkscape:pageshadow="2" inkscape:zoom="11.313708" inkscape:cx="6.4184057" inkscape:cy="25.737489" inkscape:document-units="px" inkscape:current-layer="layer1" showgrid="true" units="px" inkscape:window-width="1920" inkscape:window-height="1009" inkscape:window-x="0" inkscape:window-y="34" inkscape:window-maximized="1" gridtolerance="10000"/>
<metadata id="metadata4235">
<rdf:RDF>
<cc:Work rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
<dc:title/>
<cc:license rdf:resource="http://creativecommons.org/licenses/by-sa/3.0/"/>
</cc:Work>
<cc:License rdf:about="http://creativecommons.org/licenses/by-sa/3.0/">
<cc:permits rdf:resource="http://creativecommons.org/ns#Reproduction"/>
<cc:permits rdf:resource="http://creativecommons.org/ns#Distribution"/>
<cc:requires rdf:resource="http://creativecommons.org/ns#Notice"/>
<cc:requires rdf:resource="http://creativecommons.org/ns#Attribution"/>
<cc:permits rdf:resource="http://creativecommons.org/ns#DerivativeWorks"/>
<cc:requires rdf:resource="http://creativecommons.org/ns#ShareAlike"/>
</cc:License>
</rdf:RDF>
</metadata>
<g inkscape:label="Layer 1" inkscape:groupmode="layer" id="layer1" transform="translate(0,-1004.3622)">
<path style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#263238;fill-opacity:0.4;fill-rule:evenodd;stroke:none;stroke-width:2.5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter4175);color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" d="m 2.613462,1006.3488 a 1.250125,1.250125 0 0 0 -1.01172,2.0293 l 3.60351,4.6641 c -0.12699,0.3331 -0.20312,0.6915 -0.20312,1.0703 l 0,4 0,2.8652 0,0.1348 c 0,1.662 1.338,3 3,3 l 32,0 c 1.662,0 3,-1.338 3,-3 l 0,-4 0,-2.8652 0,-0.1348 c 0,-0.3803 -0.0771,-0.74 -0.20508,-1.0742 l 3.60156,-4.6602 a 1.250125,1.250125 0 0 0 -1.04882,-2.0273 1.250125,1.250125 0 0 0 -0.92969,0.498 l -3.43164,4.4414 c -0.31022,-0.1079 -0.63841,-0.1777 -0.98633,-0.1777 l -32,0 c -0.34857,0 -0.67757,0.069 -0.98828,0.1777 l -3.4336,-4.4414 a 1.250125,1.250125 0 0 0 -0.96679,-0.5 z m 5.38867,18.7637 c -0.20775,0 -0.40983,0.021 -0.60547,0.061 -1.36951,0.2761 -2.39453,1.4698 -2.39453,2.9101 l 0,0.029 0,19.7793 0,0.029 0,0.1914 c 0,1.662 1.338,3 3,3 l 32,0 c 1.662,0 3,-1.338 3,-3 l 0,-20 0,-0.029 c 0,-1.4403 -1.02502,-2.634 -2.39453,-2.9101 -0.19565,-0.039 -0.39772,-0.061 -0.60547,-0.061 l -32,0 z" id="path4192" inkscape:connector-curvature="0"/>
<g id="g5012">
<g id="g4179" transform="matrix(-1,0,0,1,47.999779,0)">
<path style="fill:#8ab000;fill-opacity:1;fill-rule:evenodd;stroke:#769616;stroke-width:2.5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" d="m 2.5889342,1006.8622 4.25,5.5" id="path4181" inkscape:connector-curvature="0" sodipodi:nodetypes="cc"/>
<path sodipodi:nodetypes="cccccc" inkscape:connector-curvature="0" id="path4183" d="m 2.6113281,1005.6094 c -0.4534623,0.012 -0.7616975,0.189 -0.9807462,0.4486 2.0269314,2.4089 2.368401,2.7916 5.1354735,6.2214 1.0195329,1.319 2.0816026,0.6373 1.0620696,-0.6817 l -4.25,-5.5 c -0.2289894,-0.3056 -0.5850813,-0.478 -0.9667969,-0.4883 z" style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:0.29803923;fill-rule:evenodd;stroke:none;stroke-width:2.5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"/>
<path sodipodi:nodetypes="ccccc" inkscape:connector-curvature="0" id="path4185" d="m 1.6220992,1006.0705 c -0.1238933,0.1479 -0.561176,0.8046 -0.02249,1.5562 l 4.25,5.5 c 1.0195329,1.319 1.1498748,-0.6123 1.1498748,-0.6123 0,0 -3.7344514,-4.51 -5.3773848,-6.4439 z" style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#263238;fill-opacity:0.2;fill-rule:evenodd;stroke:none;stroke-width:2.5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"/>
<path sodipodi:nodetypes="cscccc" inkscape:connector-curvature="0" id="path4187" d="m 2.3378905,1005.8443 c -0.438175,0 -0.959862,0.1416 -0.8242183,0.7986 0.103561,0.5016 4.6608262,6.0744 4.6608262,6.0744 1.0195329,1.319 2.4934721,0.6763 1.4739391,-0.6425 l -4.234375,-5.4727 c -0.2602394,-0.29 -0.6085188,-0.7436 -1.076172,-0.7578 z" style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#8ab000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:2.5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"/>
</g>
<g id="g4955">
<path sodipodi:nodetypes="cc" inkscape:connector-curvature="0" id="path4945" d="m 2.5889342,1006.8622 4.25,5.5" style="fill:#8ab000;fill-opacity:1;fill-rule:evenodd;stroke:#769616;stroke-width:2.5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"/>
<path style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:0.29803923;fill-rule:evenodd;stroke:none;stroke-width:2.5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" d="m 2.6113281,1005.6094 c -0.4534623,0.012 -0.7616975,0.189 -0.9807462,0.4486 2.0269314,2.4089 2.368401,2.7916 5.1354735,6.2214 1.0195329,1.319 2.0816026,0.6373 1.0620696,-0.6817 l -4.25,-5.5 c -0.2289894,-0.3056 -0.5850813,-0.478 -0.9667969,-0.4883 z" id="path4947" inkscape:connector-curvature="0" sodipodi:nodetypes="cccccc"/>
<path style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#263238;fill-opacity:0.2;fill-rule:evenodd;stroke:none;stroke-width:2.5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" d="m 1.6220992,1006.0705 c -0.1238933,0.1479 -0.561176,0.8046 -0.02249,1.5562 l 4.25,5.5 c 1.0195329,1.319 1.1498748,-0.6123 1.1498748,-0.6123 0,0 -3.7344514,-4.51 -5.3773848,-6.4439 z" id="path4951" inkscape:connector-curvature="0" sodipodi:nodetypes="ccccc"/>
<path style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#8ab000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:2.5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" d="m 2.3378905,1005.8443 c -0.438175,0 -0.959862,0.1416 -0.8242183,0.7986 0.103561,0.5016 4.6608262,6.0744 4.6608262,6.0744 1.0195329,1.319 2.4934721,0.6763 1.4739391,-0.6425 l -4.234375,-5.4727 c -0.2602394,-0.29 -0.6085188,-0.7436 -1.076172,-0.7578 z" id="path4925" inkscape:connector-curvature="0" sodipodi:nodetypes="cscccc"/>
</g>
<g transform="translate(42,0)" id="g4967">
<rect style="opacity:1;fill:#aeea00;fill-opacity:1;stroke:none;stroke-width:3;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:3;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" id="rect4144" width="38" height="13" x="-37" y="1010.3622" rx="3" ry="3"/>
<rect ry="3" rx="3" y="1013.3622" x="-37" height="10" width="38" id="rect4961" style="opacity:1;fill:#263238;fill-opacity:0.2;stroke:none;stroke-width:3;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:3;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"/>
<rect ry="3" rx="3" y="1010.3622" x="-37" height="10" width="38" id="rect4963" style="opacity:1;fill:#ffffff;fill-opacity:0.29803923;stroke:none;stroke-width:3;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:3;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"/>
<rect ry="2.5384617" rx="3" y="1011.3622" x="-37" height="11" width="38" id="rect4965" style="opacity:1;fill:#aeea00;fill-opacity:1;stroke:none;stroke-width:3;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:3;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"/>
</g>
<g id="g4979">
<rect style="opacity:1;fill:#1976d2;fill-opacity:1;stroke:none;stroke-width:3;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:3;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" id="rect4146" width="38" height="26" x="5" y="1024.3622" rx="3" ry="3"/>
<rect ry="3" rx="3" y="1037.3622" x="5" height="13" width="38" id="rect4973" style="opacity:1;fill:#263238;fill-opacity:0.2;stroke:none;stroke-width:3;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:3;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"/>
<rect ry="3" rx="3" y="1024.3622" x="5" height="13" width="38" id="rect4975" style="opacity:1;fill:#ffffff;fill-opacity:0.2;stroke:none;stroke-width:3;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:3;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"/>
<rect ry="2.7692308" rx="3" y="1025.3622" x="5" height="24" width="38" id="rect4977" style="opacity:1;fill:#1976d2;fill-opacity:1;stroke:none;stroke-width:3;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:3;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"/>
</g>
<g transform="translate(0,1013.3622)" id="g4211">
<path style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#0d47a1;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" d="m 24,17.75 c -2.880662,0 -5.319789,1.984685 -6.033203,4.650391 l 3.212891,0 C 21.734004,21.415044 22.774798,20.75 24,20.75 c 1.812692,0 3.25,1.437308 3.25,3.25 0,1.812693 -1.437308,3.25 -3.25,3.25 -1.307381,0 -2.411251,-0.75269 -2.929688,-1.849609 l -3.154296,0 C 18.558263,28.166146 21.04791,30.25 24,30.25 c 3.434013,0 6.25,-2.815987 6.25,-6.25 0,-3.434012 -2.815987,-6.25 -6.25,-6.25 z" id="path4161" inkscape:connector-curvature="0"/>
<circle style="opacity:1;fill:none;fill-opacity:0.40392157;stroke:#0d47a1;stroke-width:1.89999998;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" id="path4209" cx="24" cy="24" r="9.5500002"/>
</g>
<g id="g4989" transform="translate(0,0.50001738)">
<ellipse cy="1016.4872" cx="14.375" id="circle4985" style="opacity:1;fill:#263238;fill-opacity:0.2;stroke:none;stroke-width:1.89999998;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.69721117" rx="3.375" ry="3.875"/>
<circle style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1.89999998;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.69721117" id="path4859" cx="14.375" cy="1016.9872" r="3.375"/>
</g>
<g transform="translate(19.5,0.50001738)" id="g4171">
<ellipse ry="3.875" rx="3.375" style="opacity:1;fill:#263238;fill-opacity:0.2;stroke:none;stroke-width:1.89999998;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.69721117" id="ellipse4175" cx="14.375" cy="1016.4872"/>
<circle r="3.375" cy="1016.9872" cx="14.375" id="circle4177" style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1.89999998;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.69721117"/>
</g>
</g>
<path inkscape:connector-curvature="0" id="path5128" d="m 2.613462,1005.5987 a 1.250125,1.250125 0 0 0 -1.01172,2.0293 l 3.60351,4.6641 c -0.12699,0.3331 -0.20312,0.6915 -0.20312,1.0703 l 0,4 0,2.8652 0,0.1348 c 0,1.662 1.338,3 3,3 l 32,0 c 1.662,0 3,-1.338 3,-3 l 0,-4 0,-2.8652 0,-0.1348 c 0,-0.3803 -0.0771,-0.74 -0.20508,-1.0742 l 3.60156,-4.6602 a 1.250125,1.250125 0 0 0 -1.04882,-2.0273 1.250125,1.250125 0 0 0 -0.92969,0.498 l -3.43164,4.4414 c -0.31022,-0.1079 -0.63841,-0.1777 -0.98633,-0.1777 l -32,0 c -0.34857,0 -0.67757,0.069 -0.98828,0.1777 l -3.4336,-4.4414 a 1.250125,1.250125 0 0 0 -0.96679,-0.5 z m 5.38867,18.7637 c -0.20775,0 -0.40983,0.021 -0.60547,0.061 -1.36951,0.2761 -2.39453,1.4698 -2.39453,2.9101 l 0,0.029 0,19.7793 0,0.029 0,0.1914 c 0,1.662 1.338,3 3,3 l 32,0 c 1.662,0 3,-1.338 3,-3 l 0,-20 0,-0.029 c 0,-1.4403 -1.02502,-2.634 -2.39453,-2.9101 -0.19565,-0.039 -0.39772,-0.061 -0.60547,-0.061 l -32,0 z" style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:url(#radialGradient5220);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:2.5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"/>
</g>
</svg>

After

(image error) Size: 21 KiB

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg height="800px" width="800px" version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
viewBox="0 0 511.999 511.999" xml:space="preserve">
<g>
<path style="fill:#32BBFF;" d="M382.369,175.623C322.891,142.356,227.427,88.937,79.355,6.028
C69.372-0.565,57.886-1.429,47.962,1.93l254.05,254.05L382.369,175.623z"/>
<path style="fill:#32BBFF;" d="M47.962,1.93c-1.86,0.63-3.67,1.39-5.401,2.308C31.602,10.166,23.549,21.573,23.549,36v439.96
c0,14.427,8.052,25.834,19.012,31.761c1.728,0.917,3.537,1.68,5.395,2.314L302.012,255.98L47.962,1.93z"/>
<path style="fill:#32BBFF;" d="M302.012,255.98L47.956,510.035c9.927,3.384,21.413,2.586,31.399-4.103
c143.598-80.41,237.986-133.196,298.152-166.746c1.675-0.941,3.316-1.861,4.938-2.772L302.012,255.98z"/>
</g>
<path style="fill:#2C9FD9;" d="M23.549,255.98v219.98c0,14.427,8.052,25.834,19.012,31.761c1.728,0.917,3.537,1.68,5.395,2.314
L302.012,255.98H23.549z"/>
<path style="fill:#29CC5E;" d="M79.355,6.028C67.5-1.8,53.52-1.577,42.561,4.239l255.595,255.596l84.212-84.212
C322.891,142.356,227.427,88.937,79.355,6.028z"/>
<path style="fill:#D93F21;" d="M298.158,252.126L42.561,507.721c10.96,5.815,24.939,6.151,36.794-1.789
c143.598-80.41,237.986-133.196,298.152-166.746c1.675-0.941,3.316-1.861,4.938-2.772L298.158,252.126z"/>
<path style="fill:#FFD500;" d="M488.45,255.98c0-12.19-6.151-24.492-18.342-31.314c0,0-22.799-12.721-92.682-51.809l-83.123,83.123
l83.204,83.205c69.116-38.807,92.6-51.892,92.6-51.892C482.299,280.472,488.45,268.17,488.45,255.98z"/>
<path style="fill:#FFAA00;" d="M470.108,287.294c12.191-6.822,18.342-19.124,18.342-31.314H294.303l83.204,83.205
C446.624,300.379,470.108,287.294,470.108,287.294z"/>
</svg>

After

(image error) Size: 1.8 KiB

@ -66,8 +66,30 @@
<a
class="w3-button w3-black w3-padding-large"
href="https://dev.tildefriends.net/"
><i class="fa fa-mug-hot"></i> Code</a
><i class="fa fa-mug-hot"></i> Development</a
>
<p>
<a
class="w3-button w3-round-large w3-padding w3-blue-gray w3-margin-top"
href="https://f-droid.org/en/packages/com.unprompted.tildefriends.fdroid/"
><img src="f-droid.svg" style="height: 2em; margin: 0" /> Get it
on F-Droid</a
>
<a
class="w3-button w3-round-large w3-padding w3-blue-gray w3-margin-top"
href="https://dev.tildefriends.net/releases/tildefriends-x86_64.AppImage"
>
<img src="appimage.svg" style="height: 2em; margin: 0" />
Get Linux 64-bit AppImage
</a>
<a
class="w3-button w3-round-large w3-padding w3-blue-gray w3-margin-top"
href="https://play.google.com/store/apps/details?id=com.unprompted.tildefriends"
>
<img src="googleplay.svg" style="height: 2em; margin: 0" />
Get it on Google Play (Open Testing)
</a>
</p>
</div>
<div class="w3-col l4 m6">
<img src="tildefriends.png" class="w3-image w3-right w3-hide-small" />
@ -88,36 +110,31 @@
<a href="https://dev.tildefriends.net/cory/tildefriends/releases"
>Download</a
>
Tilde Friends and run your own instance, or use
Tilde Friends or use
<a href="https://www.tildefriends.net/"
>https://www.tildefriends.net/</a
>.
</li>
<li>
Create an account to identify yourself with that instance by
username and password.
</li>
<li>
Create an SSB identity in the <b>ssb</b> app. This will generate a
keypair used to identify yourself to other users and sign your
messages so that they can be verified as from you.
</li>
<li>Create an account to identify yourself with that instance.</li>
<li>
Describe yourself in your profile in the <b>ssb</b> app. Give
yourself a name and an avatar if you like.
</li>
<li>
Connect to others. You will automatically discover peers on the
same instance and same network if there are any. Or use
<a href="https://github.com/staltz/ssb-room/blob/master/FAQ.md"
>rooms</a
>
and pubs to reach more distant users.
<a href="https://www.tildefriends.net/~cory/room/"
>tildefriends.net itself</a
>
operates as a room, so you can connect and see who else is online
and establish a connection.
Connect to others.
<ul>
<li>Automatically discover peers on the same network.</li>
<li>
Manually connect to rooms and pubs, including
<a href="https://www.tildefriends.net/~cory/room/"
>tildefriends.net itself</a
>.
</li>
<li>
Enable <b>Peer Exchange</b> in the <b>admin</b> to discover
internet peers.
</li>
</ul>
</li>
<li>Follow people to grow your network.</li>
<li>
@ -206,8 +223,13 @@
<!-- Technlology Section -->
<div class="w3-container w3-padding-64 w3-light-grey w3-center">
<h1 class="w3-jumbo"><b>Trusted Technology</b></h1>
<p>Tilde Friends is built using boring, trusted tech.</p>
<h1 class="w3-jumbo"><b>Boring Technology</b></h1>
<p>
Tilde Friends is built using boring, trusted tech. Unless a better
reason presents itself, it strives to use only simple and widely adopted
dependencies in order to keep it easy to build for all sorts of
platforms and maintainable for a very long time.
</p>
<p>
Though of course for building Tilde Friends apps, you are free to use
whatever fits.
@ -244,7 +266,7 @@
<i class="fa fa-lock w3-text-purple w3-jumbo"></i>
<p>libsodium</p>
</a>
<a href="https://www.openssl.org/" class="w3-col s3">
<a href="https://github.com/openssl/openssl/releases" class="w3-col s3">
<i class="fa fa-shield-halved w3-text-green w3-jumbo"></i>
<p>OpenSSL</p>
</a>
@ -270,6 +292,13 @@
<i class="fa fa-fire w3-text-cyan w3-jumbo"></i>
<p>Lit</p>
</a>
<a href="https://github.com/c-ares/c-ares" class="w3-col s3">
<i class="fa fa-book-atlas w3-text-purple w3-jumbo"></i>
<p>c-ares</p>
</a>
</div>
<div class="w3-row" style="margin-top: 64px">
<a href="https://www.gnu.org/software/make/" class="w3-col s3">
<i class="fa fa-hammer w3-text-teal w3-jumbo"></i>
<p>GNU Make</p>

@ -1,5 +1,5 @@
{
"type": "tildefriends-app",
"emoji": "📝",
"previous": "&DaYqKHRBKhjFGaOzbKZ1+/pLspJeEkDJYTF2B50tH6k=.sha256"
"previous": "&4UHlsfQJvSh7L3D86uFtr7KUKCMRVBBTFxRIMqIc5as=.sha256"
}

File diff suppressed because one or more lines are too long

@ -2,8 +2,8 @@ import * as utils from './utils.js';
import * as commonmark from './commonmark.min.js';
function markdown(md) {
let reader = new commonmark.Parser({safe: true});
let writer = new commonmark.HtmlRenderer();
let reader = new commonmark.Parser();
let writer = new commonmark.HtmlRenderer({safe: true});
let parsed = reader.parse(md || '');
let walker = parsed.walker();
let event;

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -20,8 +20,8 @@ class TfWikiDocElement extends LitElement {
}
markdown(md) {
let reader = new commonmark.Parser({safe: true});
let writer = new commonmark.HtmlRenderer();
let reader = new commonmark.Parser();
let writer = new commonmark.HtmlRenderer({safe: true});
let parsed = reader.parse(md || '');
let walker = parsed.walker();
let event;

@ -10,7 +10,7 @@ let gSessionIndex = 0;
* @returns
*/
function makeSessionId() {
return (gSessionIndex++).toString();
return 'session_' + (gSessionIndex++).toString();
}
/**
@ -172,11 +172,7 @@ async function socket(request, response, client) {
0x1
);
} else {
process = await core.getSessionProcessBlob(
blobId,
sessionId,
options
);
process = await core.getProcessBlob(blobId, sessionId, options);
}
}
if (process) {

@ -56,7 +56,7 @@ class TfNavigationElement extends LitElement {
status: {type: Object},
spark_lines: {type: Object},
version: {type: Object},
show_version: {type: Boolean},
show_expanded: {type: Boolean},
identity: {type: String},
identities: {type: Array},
names: {type: Object},
@ -105,7 +105,6 @@ class TfNavigationElement extends LitElement {
let spark_line = document.createElement('tf-sparkline');
spark_line.title = key;
spark_line.classList.add('w3-bar-item');
spark_line.classList.add('w3-hide-small');
spark_line.style.paddingRight = '0';
if (options) {
if (options.max) {
@ -162,6 +161,10 @@ class TfNavigationElement extends LitElement {
class="w3-dropdown-content w3-bar-block w3-card-4"
style="max-width: 100%; right: 0"
>
<div
style="position: fixed; left: 0; right: 0; top: 0; bottom: 0; background-color: rgba(0, 0, 0, 0.25); z-index: -100"
@click=${self.toggle_id_dropdown}
></div>
<button
class="w3-bar-item w3-button w3-border"
@click=${() => (window.location.href = '/~core/identity')}
@ -169,6 +172,7 @@ class TfNavigationElement extends LitElement {
Manage Identities...
</button>
<button
id="edit_profile"
class="w3-bar-item w3-button w3-border"
@click=${self.edit_profile}
>
@ -311,13 +315,13 @@ class TfNavigationElement extends LitElement {
<span
class="w3-bar-item"
style="cursor: pointer"
@click=${() => (this.show_version = !this.show_version)}
@click=${() => (this.show_expanded = !this.show_expanded)}
>😎</span
>
<span
class="w3-bar-item"
style=${'white-space: nowrap' +
(this.show_version ? '' : '; display: none')}
(this.show_expanded ? '' : '; display: none')}
title=${this.version?.name +
' ' +
Object.entries(this.version || {})
@ -372,9 +376,11 @@ class TfNavigationElement extends LitElement {
</div>
`
: undefined}
${Object.keys(this.spark_lines)
.sort()
.map((x) => this.spark_lines[x])}
<span class=${this.show_expanded ? '' : 'w3-hide-small'}>
${Object.keys(this.spark_lines)
.sort()
.map((x) => this.spark_lines[x])}
</span>
${this.render_identity()}
</div>
${this.status?.is_error
@ -382,8 +388,8 @@ class TfNavigationElement extends LitElement {
<link type="text/css" rel="stylesheet" href="/static/w3.css" />
<div class="w3-model w3-animate-top" style="position: absolute; left: 50%; transform: translate(-50%); z-index: 1">
<dijv class="w3-modal-content w3-card-4" style="display: block; padding: 1em">
<span @click=${self.clear_error} class="w3-button w3-display-topright">&times;</span>
<div style="color: ${this.status.color ?? kErrorColor}"><b>ERROR:</b><p style="white-space: pre">${this.status.message}</p></div>
<span id="close_error" @click=${self.clear_error} class="w3-button w3-display-topright">&times;</span>
<div style="color: ${this.status.color ?? kErrorColor}"><b>ERROR:</b><p id="error" style="white-space: pre">${this.status.message}</p></div>
</div>
</div>
`
@ -1174,7 +1180,7 @@ function api_requestPermission(permission, id) {
let div = document.createElement('div');
div.appendChild(
document.createTextNode('This app is requesting the following permission:')
document.createTextNode('This app is requesting the following permission: ')
);
let span = document.createElement('span');
span.style = 'font-weight: bold';
@ -1190,6 +1196,7 @@ function api_requestPermission(permission, id) {
check.classList.add('w3-check');
check.classList.add('w3-blue');
div.appendChild(check);
div.appendChild(document.createTextNode(' '));
let label = document.createElement('label');
label.htmlFor = check.id;
label.appendChild(document.createTextNode('Remember this decision.'));
@ -1782,10 +1789,11 @@ async function sourcePretty() {
let prettier = (await import('/prettier/standalone.mjs')).default;
let babel = (await import('/prettier/babel.mjs')).default;
let estree = (await import('/prettier/estree.mjs')).default;
let prettier_html = (await import('/prettier/html.mjs')).default;
let source = gEditor.state.doc.toString();
let formatted = await prettier.format(source, {
parser: 'babel',
plugins: [babel, estree],
parser: gCurrentFile?.toLowerCase()?.endsWith('.html') ? 'html' : 'babel',
plugins: [babel, estree, prettier_html],
trailingComma: 'es5',
useTabs: true,
semi: true,
@ -1820,8 +1828,8 @@ function toggleVisibleWhitespace() {
.cm-highlightTab {
background-image: unset !important;
}
.cm-highlightSpace:before {
content: unset !important;
.cm-highlightSpace {
background-image: unset !important;
}
`;
window.localStorage.setItem('visible_whitespace', '1');

@ -4,92 +4,6 @@ import * as http from './http.js';
let gProcesses = {};
let gStatsTimer = false;
const k_content_security_policy =
'sandbox allow-downloads allow-top-navigation-by-user-activation';
const k_global_settings = {
index: {
type: 'string',
default_value: '/~core/apps/',
description: 'Default path.',
},
index_map: {
type: 'textarea',
default_value: undefined,
description:
'Mappings from hostname to redirect path, one per line, as in: "www.tildefriends.net=/~core/index/"',
},
room: {
type: 'boolean',
default_value: true,
description: 'Enable peers to tunnel through this instance as a room.',
},
room_name: {
type: 'string',
default_value: 'tilde friends tunnel',
description: 'Name of the room.',
},
replicator: {
type: 'boolean',
default_value: true,
description: 'Enable message and blob replication.',
},
code_of_conduct: {
type: 'textarea',
default_value: undefined,
description: 'Code of conduct presented at sign-in.',
},
http_redirect: {
type: 'string',
default_value: undefined,
description:
'If connecting by HTTP and HTTPS is configured, Location header prefix (ie, "https://example.com")',
},
fetch_hosts: {
type: 'string',
default_value: undefined,
description:
'Comma-separated list of host names to which HTTP fetch requests are allowed. None if empty.',
},
blob_fetch_age_seconds: {
type: 'integer',
default_value:
platform() == 'android' || platform() == 'iphone'
? 0.5 * 365 * 24 * 60 * 60
: undefined,
description:
'Only blobs mentioned more recently than this age will be automatically fetched.',
},
blob_expire_age_seconds: {
type: 'integer',
default_value:
platform() == 'android' || platform() == 'iphone'
? 1.0 * 365 * 24 * 60 * 60
: undefined,
description: 'Blobs older than this will be automatically deleted.',
},
seeds_host: {
type: 'string',
default_value: 'seeds.tildefriends.net',
description: 'Hostname for seed connections.',
},
peer_exchange: {
type: 'boolean',
default_value: false,
description: 'Enable discovery of, sharing of, and connecting to internet peer strangers, including announcing this instance.',
},
account_registration: {
type: 'boolean',
default_value: true,
description: 'Allow registration of new accounts.',
},
};
let gGlobalSettings = {
index: '/~core/apps/',
};
let kPingInterval = 60 * 1000;
/**
@ -261,23 +175,6 @@ function postMessageInternal(from, to, message) {
}
}
/**
* TODOC
* @param {*} blobId
* @param {*} session
* @param {*} options
* @returns
*/
async function getSessionProcessBlob(blobId, session, options) {
let actualOptions = {timeout: kPingInterval};
if (options) {
for (let i in options) {
actualOptions[i] = options[i];
}
}
return getProcessBlob(blobId, 'session_' + session, actualOptions);
}
/**
* TODOC
* @param {*} blobId
@ -305,7 +202,7 @@ async function getProcessBlob(blobId, key, options) {
}
process.lastActive = Date.now();
process.lastPing = null;
process.timeout = options.timeout;
process.timeout = kPingInterval;
process.ready = new Promise(function (resolve, reject) {
resolveReady = resolve;
rejectReady = reject;
@ -344,59 +241,59 @@ async function getProcessBlob(blobId, key, options) {
return [];
}
},
permissionsGranted: function () {
permissionsGranted: async function () {
let user = process?.credentials?.session?.name;
let settings = await loadSettings();
if (
user &&
options?.packageOwner &&
options?.packageName &&
gGlobalSettings.userPermissions &&
gGlobalSettings.userPermissions[user] &&
gGlobalSettings.userPermissions[user][options.packageOwner]
settings.userPermissions &&
settings.userPermissions[user] &&
settings.userPermissions[user][options.packageOwner]
) {
return gGlobalSettings.userPermissions[user][
options.packageOwner
][options.packageName];
return settings.userPermissions[user][options.packageOwner][
options.packageName
];
}
},
allPermissionsGranted: function () {
allPermissionsGranted: async function () {
let user = process?.credentials?.session?.name;
let settings = await loadSettings();
if (
user &&
options?.packageOwner &&
options?.packageName &&
gGlobalSettings.userPermissions &&
gGlobalSettings.userPermissions[user]
settings.userPermissions &&
settings.userPermissions[user]
) {
return gGlobalSettings.userPermissions[user];
return settings.userPermissions[user];
}
},
permissionsForUser: function (user) {
return (
(gGlobalSettings?.permissions
? gGlobalSettings.permissions[user]
: []) ?? []
);
permissionsForUser: async function (user) {
let settings = await loadSettings();
return settings?.permissions?.[user] ?? [];
},
apps: (user) => getApps(user, process),
getSockets: getSockets,
permissionTest: function (permission) {
permissionTest: async function (permission) {
let user = process?.credentials?.session?.name;
let settings = await loadSettings();
if (!user || !options?.packageOwner || !options?.packageName) {
return;
} else if (
gGlobalSettings.userPermissions &&
gGlobalSettings.userPermissions[user] &&
gGlobalSettings.userPermissions[user][options.packageOwner] &&
gGlobalSettings.userPermissions[user][options.packageOwner][
settings.userPermissions &&
settings.userPermissions[user] &&
settings.userPermissions[user][options.packageOwner] &&
settings.userPermissions[user][options.packageOwner][
options.packageName
] &&
gGlobalSettings.userPermissions[user][options.packageOwner][
settings.userPermissions[user][options.packageOwner][
options.packageName
][permission] !== undefined
) {
if (
gGlobalSettings.userPermissions[user][options.packageOwner][
settings.userPermissions[user][options.packageOwner][
options.packageName
][permission]
) {
@ -407,9 +304,9 @@ async function getProcessBlob(blobId, key, options) {
} else if (process.app) {
return process.app
.makeFunction(['requestPermission'])(permission)
.then(function (value) {
.then(async function (value) {
if (value == 'allow') {
storePermission(
await ssb.setUserPermission(
user,
options.packageOwner,
options.packageName,
@ -421,7 +318,7 @@ async function getProcessBlob(blobId, key, options) {
} else if (value == 'allow once') {
return true;
} else if (value == 'deny') {
storePermission(
await ssb.setUserPermission(
user,
options.packageOwner,
options.packageName,
@ -508,23 +405,25 @@ async function getProcessBlob(blobId, key, options) {
}
};
if (process.credentials?.permissions?.administration) {
imports.core.globalSettingsDescriptions = function () {
let settings = Object.assign({}, k_global_settings);
for (let [key, value] of Object.entries(gGlobalSettings)) {
imports.core.globalSettingsDescriptions = async function () {
let settings = Object.assign({}, defaultGlobalSettings());
for (let [key, value] of Object.entries(await loadSettings())) {
if (settings[key]) {
settings[key].value = value;
}
}
return settings;
};
imports.core.globalSettingsGet = function (key) {
return gGlobalSettings[key];
imports.core.globalSettingsGet = async function (key) {
let settings = await loadSettings();
return settings?.[key];
};
imports.core.globalSettingsSet = async function (key, value) {
await imports.core.permissionTest('set_global_setting');
print('Setting', key, value);
await loadSettings();
gGlobalSettings[key] = value;
setGlobalSettings(gGlobalSettings);
let settings = await loadSettings();
settings[key] = value;
await new Database('core').set('settings', JSON.stringify(settings));
print('Done.');
};
imports.core.deleteUser = async function (user) {
@ -691,11 +590,24 @@ async function getProcessBlob(blobId, key, options) {
);
}
};
imports.ssb.swapWithServerIdentity = function (id) {
if (
process.credentials &&
process.credentials.session &&
process.credentials.session.name
) {
return ssb.swapWithServerIdentity(
process.credentials.session.name,
id
);
}
};
imports.ssb.addEventListener = undefined;
imports.ssb.removeEventListener = undefined;
imports.ssb.getIdentityInfo = undefined;
imports.fetch = function (url, options) {
return http.fetch(url, options, gGlobalSettings.fetch_hosts);
imports.fetch = async function (url, options) {
let settings = await loadSettings();
return http.fetch(url, options, settings?.fetch_hosts);
};
if (
@ -722,12 +634,12 @@ async function getProcessBlob(blobId, key, options) {
Object.keys(db).map((x) => [x, db[x].bind(db)])
);
};
imports.databases = function () {
imports.databases = async function () {
return [].concat(
databases.list(
await databases.list(
':shared:' + process.credentials.session.name + ':%'
),
databases.list(process.credentials.session.name + ':%')
await databases.list(process.credentials.session.name + ':%')
);
};
}
@ -746,22 +658,22 @@ async function getProcessBlob(blobId, key, options) {
);
};
}
process.sendPermissions = function sendPermissions() {
process.sendPermissions = async function sendPermissions() {
process.app.send({
action: 'permissions',
permissions: imports.core.permissionsGranted(),
permissions: await imports.core.permissionsGranted(),
});
};
process.resetPermission = function resetPermission(permission) {
process.resetPermission = async function resetPermission(permission) {
let user = process?.credentials?.session?.name;
storePermission(
await ssb.setUserPermission(
user,
options?.packageOwner,
options?.packageName,
permission,
undefined
);
process.sendPermissions();
return process.sendPermissions();
};
process.task.setImports(imports);
process.task.activate();
@ -794,7 +706,7 @@ async function getProcessBlob(blobId, key, options) {
broadcastEvent('onSessionBegin', [getUser(process, process)]);
if (process.app) {
process.app.send({action: 'ready', version: version()});
process.sendPermissions();
await process.sendPermissions();
}
await process.task.execute({name: appSourceName, source: appSource});
resolveReady(process);
@ -818,374 +730,6 @@ async function getProcessBlob(blobId, key, options) {
return process;
}
/**
* TODOC
* @param {*} settings
* @returns
*/
async function setGlobalSettings(settings) {
gGlobalSettings = settings;
try {
return await new Database('core').set('settings', JSON.stringify(settings));
} catch (error) {
print('Error storing settings:', error);
}
}
/**
* TODOC
* @param {*} response
* @param {*} data
* @param {*} type
* @param {*} headers
* @param {*} status_code
*/
function sendData(response, data, type, headers, status_code) {
if (data) {
response.writeHead(
status_code ?? 200,
Object.assign(
{
'Content-Type':
type ||
httpd.mime_type_from_magic_bytes(data) ||
'application/binary',
'Content-Length': data.byteLength,
},
headers || {}
)
);
response.end(data);
} else {
response.writeHead(
status_code ?? 404,
Object.assign(
{
'Content-Type': 'text/plain; charset=utf-8',
'Content-Length': 'File not found'.length,
},
headers || {}
)
);
response.end('File not found');
}
}
let g_handler_index = 0;
/**
* TODOC
* @param {*} response
* @param {*} handler_blob_id
* @param {*} path
* @param {*} query
* @param {*} headers
* @param {*} packageOwner
* @param {*} packageName
* @returns
*/
async function useAppHandler(
response,
handler_blob_id,
path,
query,
headers,
packageOwner,
packageName
) {
print('useAppHandler', packageOwner, packageName);
let do_resolve;
let promise = new Promise(async function (resolve, reject) {
do_resolve = resolve;
});
let process;
let result;
try {
process = await getProcessBlob(
handler_blob_id,
'handler_' + g_handler_index++,
{
script: 'handler.js',
imports: {
request: {
path: path,
query: query,
},
respond: do_resolve,
},
credentials: await httpd.auth_query(headers),
packageOwner: packageOwner,
packageName: packageName,
}
);
await process.ready;
result = await promise;
} finally {
if (process?.task) {
await process.task.kill();
}
}
return result;
}
/**
* TODOC
* @param {*} request
* @param {*} response
* @param {*} blobId
* @param {*} uri
* @returns
*/
async function blobHandler(request, response, blobId, uri) {
if (!uri) {
response.writeHead(303, {
Location:
(request.client.tls ? 'https://' : 'http://') +
(request.headers['x-forwarded-host'] ?? request.headers.host) +
blobId +
'/',
'Content-Length': '0',
});
response.end();
return;
}
let process;
if (uri == '/view') {
let data;
let match;
let query = form.decodeForm(request.query);
let headers = {
'Content-Security-Policy': k_content_security_policy,
};
if (query.filename && query.filename.match(/^[A-Za-z0-9\.-]*$/)) {
headers['Content-Disposition'] = `attachment; filename=${query.filename}`;
}
if ((match = /^\/\~(\w+)\/(\w+)$/.exec(blobId))) {
let id = await new Database(match[1]).get('path:' + match[2]);
if (id) {
if (request.headers['if-none-match'] === '"' + id + '"') {
headers['Content-Length'] = '0';
response.writeHead(304, headers);
response.end();
} else {
data = await ssb.blobGet(id);
if (match[3]) {
let appObject = JSON.parse(data);
data = appObject.files[match[3]];
}
sendData(
response,
data,
undefined,
Object.assign({etag: '"' + id + '"'}, headers)
);
}
} else {
if (request.headers['if-none-match'] === '"' + blobId + '"') {
headers['Content-Length'] = '0';
response.writeHead(304, headers);
response.end();
} else {
sendData(
response,
data,
undefined,
Object.assign({etag: '"' + blobId + '"'}, headers)
);
}
}
} else {
if (request.headers['if-none-match'] === '"' + blobId + '"') {
headers['Content-Length'] = '0';
response.writeHead(304, headers);
response.end();
} else {
data = await ssb.blobGet(blobId);
sendData(
response,
data,
undefined,
Object.assign({etag: '"' + blobId + '"'}, headers)
);
}
}
} else if (uri == '/save') {
let match;
if ((match = /^\/\~(\w+)\/(\w+)$/.exec(blobId))) {
let user = match[1];
let appName = match[2];
let credentials = await httpd.auth_query(request.headers);
if (
credentials &&
credentials.session &&
(credentials.session.name == user ||
(credentials.permissions.administration && user == 'core'))
) {
let database = new Database(user);
let app_object = JSON.parse(utf8Decode(request.body));
let previous_id = await database.get('path:' + appName);
if (previous_id) {
try {
let previous_object = JSON.parse(
utf8Decode(await ssb.blobGet(previous_id))
);
delete previous_object.previous;
delete app_object.previous;
if (JSON.stringify(previous_object) == JSON.stringify(app_object)) {
response.writeHead(200, {
'Content-Type': 'text/plain; charset=utf-8',
});
response.end('/' + previous_id);
return;
}
} catch {}
}
app_object.previous = previous_id;
let newBlobId = await ssb.blobStore(JSON.stringify(app_object));
let apps = new Set();
let apps_original = await database.get('apps');
try {
apps = new Set(JSON.parse(apps_original));
} catch {}
if (!apps.has(appName)) {
apps.add(appName);
}
apps = JSON.stringify([...apps].sort());
if (apps != apps_original) {
await database.set('apps', apps);
}
await database.set('path:' + appName, newBlobId);
response.writeHead(200, {'Content-Type': 'text/plain; charset=utf-8'});
response.end('/' + newBlobId);
} else {
response.writeHead(401, {'Content-Type': 'text/plain; charset=utf-8'});
response.end('401 Unauthorized');
return;
}
} else if (blobId === '') {
let newBlobId = await ssb.blobStore(request.body);
response.writeHead(200, {'Content-Type': 'text/plain; charset=utf-8'});
response.end('/' + newBlobId);
} else {
response.writeHead(400, {'Content-Type': 'text/plain; charset=utf-8'});
response.end('Invalid name.');
}
} else if (uri == '/delete') {
let match;
if ((match = /^\/\~(\w+)\/(\w+)$/.exec(blobId))) {
let user = match[1];
let appName = match[2];
let credentials = await httpd.auth_query(request.headers);
if (
credentials &&
credentials.session &&
(credentials.session.name == user ||
(credentials.permissions.administration && user == 'core'))
) {
let database = new Database(user);
let apps = new Set();
try {
apps = new Set(JSON.parse(await database.get('apps')));
} catch {}
if (apps.delete(appName)) {
await database.set('apps', JSON.stringify([...apps].sort()));
}
database.remove('path:' + appName);
} else {
response.writeHead(401, {'Content-Type': 'text/plain; charset=utf-8'});
response.end('401 Unauthorized');
return;
}
}
response.writeHead(200, {'Content-Type': 'text/plain; charset=utf-8'});
response.end('OK');
} else {
let data;
let match;
let id;
let app_id = blobId;
let packageOwner;
let packageName;
if ((match = /^\/\~(\w+)\/(\w+)$/.exec(blobId))) {
packageOwner = match[1];
packageName = match[2];
let db = new Database(match[1]);
app_id = await db.get('path:' + match[2]);
}
let app_object = JSON.parse(utf8Decode(await ssb.blobGet(app_id)));
id = app_object?.files[uri.substring(1)];
if (!id && app_object?.files['handler.js']) {
let answer;
try {
answer = await useAppHandler(
response,
app_id,
uri.substring(1),
request.query ? form.decodeForm(request.query) : undefined,
request.headers,
packageOwner,
packageName
);
} catch (error) {
data = utf8Encode(
`Internal Server Error\n\n${error?.message}\n${error?.stack}`
);
response.writeHead(500, {
'Content-Type': 'text/plain; charset=utf-8',
'Content-Length': data.length,
});
response.end(data);
return;
}
if (answer && typeof answer.data == 'string') {
answer.data = utf8Encode(answer.data);
}
sendData(
response,
answer?.data,
answer?.content_type,
Object.assign(answer?.headers ?? {}, {
'Access-Control-Allow-Origin': '*',
'Content-Security-Policy': k_content_security_policy,
}),
answer.status_code
);
} else if (id) {
if (
request.headers['if-none-match'] &&
request.headers['if-none-match'] == '"' + id + '"'
) {
let headers = {
'Access-Control-Allow-Origin': '*',
'Content-Security-Policy': k_content_security_policy,
'Content-Length': '0',
};
response.writeHead(304, headers);
response.end();
} else {
let headers = {
ETag: '"' + id + '"',
'Access-Control-Allow-Origin': '*',
'Content-Security-Policy': k_content_security_policy,
};
data = await ssb.blobGet(id);
let type =
httpd.mime_type_from_extension(uri) ||
httpd.mime_type_from_magic_bytes(data);
sendData(response, data, type, headers);
}
} else {
sendData(response, data, undefined, {});
}
}
}
ssb.addEventListener('message', function () {
broadcastEvent('onMessage', [...arguments]);
});
@ -1211,12 +755,12 @@ async function loadSettings() {
} catch (error) {
print('Settings not found in database:', error);
}
for (let [key, value] of Object.entries(k_global_settings)) {
for (let [key, value] of Object.entries(defaultGlobalSettings())) {
if (data[key] === undefined) {
data[key] = value.default_value;
}
}
gGlobalSettings = data;
return data;
}
/**
@ -1237,34 +781,82 @@ function sendStats() {
}
}
let g_handler_index = 0;
exports.callAppHandler = async function callAppHandler(
response,
app_blob_id,
path,
query,
headers,
package_owner,
package_name
) {
let answer;
try {
let do_resolve;
let promise = new Promise(async function (resolve, reject) {
do_resolve = resolve;
});
let process;
try {
process = await getProcessBlob(
app_blob_id,
'handler_' + g_handler_index++,
{
script: 'handler.js',
imports: {
request: {
path: path,
query: query,
},
respond: do_resolve,
},
credentials: await httpd.auth_query(headers),
packageOwner: package_owner,
packageName: package_name,
}
);
await process.ready;
answer = await promise;
} finally {
if (process?.task) {
await process.task.kill();
}
}
} catch (error) {
let data = utf8Encode(
`Internal Server Error\n\n${error?.message}\n${error?.stack}`
);
response.writeHead(500, {
'Content-Type': 'text/plain; charset=utf-8',
'Content-Length': data.length,
});
response.end(data);
return;
}
if (typeof answer?.data == 'string') {
answer.data = utf8Encode(answer.data);
}
response.writeHead(answer?.status_code, {
'Content-Type': answer?.content_type,
'Content-Length': answer?.data?.length,
'Access-Control-Allow-Origin': '*',
'Content-Security-Policy':
'sandbox allow-downloads allow-top-navigation-by-user-activation',
});
response.end(answer?.data);
};
/**
* TODOC
*/
loadSettings()
.then(function () {
if (tildefriends.https_port && gGlobalSettings.http_redirect) {
httpd.set_http_redirect(gGlobalSettings.http_redirect);
.then(function (settings) {
if (tildefriends.https_port && settings.http_redirect) {
httpd.set_http_redirect(settings.http_redirect);
}
httpd.all('/app/socket', app.socket);
httpd.all('', function default_http_handler(request, response) {
let match;
if ((match = /^(\/~[^\/]+\/[^\/]+)(\/?.*)$/.exec(request.uri))) {
return blobHandler(request, response, match[1], match[2]);
} else if (
(match = /^\/([&\%][^\.]{44}(?:\.\w+)?)(\/?.*)/.exec(request.uri))
) {
return blobHandler(request, response, match[1], match[2]);
} else if ((match = /^(.*)(\/(?:save|delete)?)$/.exec(request.uri))) {
return blobHandler(request, response, match[1], match[2]);
} else {
let data = 'File not found.';
response.writeHead(404, {
'Content-Type': 'text/plain; charset=utf-8',
'Content-Length': data.length.toString(),
});
return response.end(data);
}
});
let port = httpd.start(tildefriends.http_port);
if (tildefriends.args.out_http_port_file) {
print('Writing the port file.');
@ -1311,47 +903,4 @@ loadSettings()
exit(1);
});
/**
* TODOC
* @param {*} user
* @param {*} packageOwner
* @param {*} packageName
* @param {*} permission
* @param {*} allow
*/
function storePermission(user, packageOwner, packageName, permission, allow) {
if (!gGlobalSettings.userPermissions) {
gGlobalSettings.userPermissions = {};
}
if (!gGlobalSettings.userPermissions[user]) {
gGlobalSettings.userPermissions[user] = {};
}
if (!gGlobalSettings.userPermissions[user][packageOwner]) {
gGlobalSettings.userPermissions[user][packageOwner] = {};
}
if (!gGlobalSettings.userPermissions[user][packageOwner][packageName]) {
gGlobalSettings.userPermissions[user][packageOwner][packageName] = {};
}
if (
gGlobalSettings.userPermissions[user][packageOwner][packageName][
permission
] !== allow
) {
if (allow === undefined) {
delete gGlobalSettings.userPermissions[user][packageOwner][packageName][
permission
];
} else {
gGlobalSettings.userPermissions[user][packageOwner][packageName][
permission
] = allow;
}
setGlobalSettings(gGlobalSettings);
}
}
export {
gGlobalSettings as globalSettings,
invoke,
getSessionProcessBlob,
};
export {invoke, getProcessBlob};

@ -6,6 +6,26 @@
<link type="text/css" rel="stylesheet" href="/static/w3.css" />
<link type="image/svg+xml" rel="icon" href="/static/tildefriends.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta
name="title"
content="Tilde Friends - Make friends and apps from your web browser."
/>
<meta
name="description"
content="Tilde Friends is a Secure Scuttlebutt client and a platform for building, running, and sharing web applications. "
/>
<meta property="og:type" content="website" />
<meta property="og:url" content="https://metatags.io/" />
<meta
property="og:title"
content="Tilde Friends - Make friends and apps from your web browser."
/>
<meta
property="og:description"
content="Tilde Friends is a Secure Scuttlebutt client and a platform for building, running, and sharing web applications. "
/>
<meta property="og:image" content="/static/tildefriends.svg" />
<script>
function set_access_key_title(event) {
if (!event.srcElement.title) {

@ -21,14 +21,14 @@
}:
pkgs.stdenv.mkDerivation rec {
pname = "tildefriends";
version = "0.0.22";
version = "0.0.25";
src = pkgs.fetchFromGitea {
domain = "dev.tildefriends.net";
owner = "cory";
repo = "tildefriends";
rev = "v${version}";
hash = "sha256-0x4LJXtMUxBsWEOoWAaQlStTfvqUf4Qs/5vJZ2EvBpY=";
hash = "sha256-Rfk+CUhi+Ss0z70CCgmtVM/w4nCL1GX/MsD4sPYIa5s=";
fetchSubmodules = true;
};

2
deps/c-ares vendored

File diff suppressed because one or more lines are too long

358
deps/codemirror_src/package-lock.json generated vendored

@ -19,27 +19,21 @@
}
},
"node_modules/@codemirror/autocomplete": {
"version": "6.18.0",
"resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.18.0.tgz",
"integrity": "sha512-5DbOvBbY4qW5l57cjDsmmpDh3/TeK1vXfTHa+BUMrRzdWdcxKZ4U4V7vQaTtOpApNU4kLS4FQ6cINtLg245LXA==",
"version": "6.18.4",
"resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.18.4.tgz",
"integrity": "sha512-sFAphGQIqyQZfP2ZBsSHV7xQvo9Py0rV0dW7W3IMRdS+zDuNb2l3no78CvUaWKGfzFjI4FTrLdUSj86IGb2hRA==",
"license": "MIT",
"dependencies": {
"@codemirror/language": "^6.0.0",
"@codemirror/state": "^6.0.0",
"@codemirror/view": "^6.17.0",
"@lezer/common": "^1.0.0"
},
"peerDependencies": {
"@codemirror/language": "^6.0.0",
"@codemirror/state": "^6.0.0",
"@codemirror/view": "^6.0.0",
"@lezer/common": "^1.0.0"
}
},
"node_modules/@codemirror/commands": {
"version": "6.6.0",
"resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.6.0.tgz",
"integrity": "sha512-qnY+b7j1UNcTS31Eenuc/5YJB6gQOzkUoNmJQc0rznwqSRpeaWWpjkWy2C/MPTcePpsKJEM26hXrOXl1+nceXg==",
"version": "6.7.1",
"resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.7.1.tgz",
"integrity": "sha512-llTrboQYw5H4THfhN4U3qCnSZ1SOJ60ohhz+SzU0ADGtwlc533DtklQP0vSFaQuCPDn3BPpOd1GbbnUtwNjsrw==",
"license": "MIT",
"dependencies": {
"@codemirror/language": "^6.0.0",
@ -49,16 +43,16 @@
}
},
"node_modules/@codemirror/lang-css": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/@codemirror/lang-css/-/lang-css-6.2.1.tgz",
"integrity": "sha512-/UNWDNV5Viwi/1lpr/dIXJNWiwDxpw13I4pTUAsNxZdg6E0mI2kTQb0P2iHczg1Tu+H4EBgJR+hYhKiHKko7qg==",
"version": "6.3.1",
"resolved": "https://registry.npmjs.org/@codemirror/lang-css/-/lang-css-6.3.1.tgz",
"integrity": "sha512-kr5fwBGiGtmz6l0LSJIbno9QrifNMUusivHbnA1H6Dmqy4HZFte3UAICix1VuKo0lMPKQr2rqB+0BkKi/S3Ejg==",
"license": "MIT",
"dependencies": {
"@codemirror/autocomplete": "^6.0.0",
"@codemirror/language": "^6.0.0",
"@codemirror/state": "^6.0.0",
"@lezer/common": "^1.0.2",
"@lezer/css": "^1.0.0"
"@lezer/css": "^1.1.7"
}
},
"node_modules/@codemirror/lang-html": {
@ -104,9 +98,9 @@
}
},
"node_modules/@codemirror/language": {
"version": "6.10.2",
"resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.10.2.tgz",
"integrity": "sha512-kgbTYTo0Au6dCSc/TFy7fK3fpJmgHDv1sG1KNQKJXVi+xBTEeBPY/M30YXiU6mMXeH+YIDLsbrT4ZwNRdtF+SA==",
"version": "6.10.7",
"resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.10.7.tgz",
"integrity": "sha512-aOswhVOLYhMNeqykt4P7+ukQSpGL0ynZYaEyFDVHE7fl2xgluU3yuE9MdgYNfw6EmaNidoFMIQ2iTh1ADrnT6A==",
"license": "MIT",
"dependencies": {
"@codemirror/state": "^6.0.0",
@ -118,20 +112,20 @@
}
},
"node_modules/@codemirror/lint": {
"version": "6.8.1",
"resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.8.1.tgz",
"integrity": "sha512-IZ0Y7S4/bpaunwggW2jYqwLuHj0QtESf5xcROewY6+lDNwZ/NzvR4t+vpYgg9m7V8UXLPYqG+lu3DF470E5Oxg==",
"version": "6.8.4",
"resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.8.4.tgz",
"integrity": "sha512-u4q7PnZlJUojeRe8FJa/njJcMctISGgPQ4PnWsd9268R4ZTtU+tfFYmwkBvgcrK2+QQ8tYFVALVb5fVJykKc5A==",
"license": "MIT",
"dependencies": {
"@codemirror/state": "^6.0.0",
"@codemirror/view": "^6.0.0",
"@codemirror/view": "^6.35.0",
"crelt": "^1.0.5"
}
},
"node_modules/@codemirror/search": {
"version": "6.5.6",
"resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.5.6.tgz",
"integrity": "sha512-rpMgcsh7o0GuCDUXKPvww+muLA1pDJaFrpq/CCHtpQJYz8xopu4D1hPcKRoDD0YlF8gZaqTNIRa4VRBWyhyy7Q==",
"version": "6.5.8",
"resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.5.8.tgz",
"integrity": "sha512-PoWtZvo7c1XFeZWmmyaOp2G0XVbOnm+fJzvghqGAktBW3cufwJUWvSCcNG0ppXiBEM05mZu6RhMtXPv2hpllig==",
"license": "MIT",
"dependencies": {
"@codemirror/state": "^6.0.0",
@ -140,10 +134,13 @@
}
},
"node_modules/@codemirror/state": {
"version": "6.4.1",
"resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.4.1.tgz",
"integrity": "sha512-QkEyUiLhsJoZkbumGZlswmAhA7CBU02Wrz7zvH4SrcifbsqwlXShVXg65f3v/ts57W3dqyamEriMhij1Z3Zz4A==",
"license": "MIT"
"version": "6.5.0",
"resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.5.0.tgz",
"integrity": "sha512-MwBHVK60IiIHDcoMet78lxt6iw5gJOGSbNbOIVBHWVXIH4/Nq1+GQgLLGgI1KlnN86WDXsPudVaqYHKBIx7Eyw==",
"license": "MIT",
"dependencies": {
"@marijn/find-cluster-break": "^1.0.0"
}
},
"node_modules/@codemirror/theme-one-dark": {
"version": "6.1.2",
@ -158,20 +155,20 @@
}
},
"node_modules/@codemirror/view": {
"version": "6.33.0",
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.33.0.tgz",
"integrity": "sha512-AroaR3BvnjRW8fiZBalAaK+ZzB5usGgI014YKElYZvQdNH5ZIidHlO+cyf/2rWzyBFRkvG6VhiXeAEbC53P2YQ==",
"version": "6.36.1",
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.36.1.tgz",
"integrity": "sha512-miD1nyT4m4uopZaDdO2uXU/LLHliKNYL9kB1C1wJHrunHLm/rpkb5QVSokqgw9hFqEZakrdlb/VGWX8aYZTslQ==",
"license": "MIT",
"dependencies": {
"@codemirror/state": "^6.4.0",
"@codemirror/state": "^6.5.0",
"style-mod": "^4.1.0",
"w3c-keyname": "^2.2.4"
}
},
"node_modules/@jridgewell/gen-mapping": {
"version": "0.3.5",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz",
"integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==",
"version": "0.3.8",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz",
"integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==",
"dev": true,
"license": "MIT",
"dependencies": {
@ -233,15 +230,15 @@
}
},
"node_modules/@lezer/common": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.2.1.tgz",
"integrity": "sha512-yemX0ZD2xS/73llMZIK6KplkjIjf2EvAHcinDi/TfJ9hS25G0388+ClHt6/3but0oOxinTcQHJLDXh6w1crzFQ==",
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.2.3.tgz",
"integrity": "sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA==",
"license": "MIT"
},
"node_modules/@lezer/css": {
"version": "1.1.8",
"resolved": "https://registry.npmjs.org/@lezer/css/-/css-1.1.8.tgz",
"integrity": "sha512-7JhxupKuMBaWQKjQoLtzhGj83DdnZY9MckEOG5+/iLKNK2ZJqKc6hf6uc0HjwCX7Qlok44jBNqZhHKDhEhZYLA==",
"version": "1.1.9",
"resolved": "https://registry.npmjs.org/@lezer/css/-/css-1.1.9.tgz",
"integrity": "sha512-TYwgljcDv+YrV0MZFFvYFQHCfGgbPMR6nuqLabBdmZoFH3EP1gvw8t0vae326Ne3PszQkbXfVBjCnf3ZVCr0bA==",
"license": "MIT",
"dependencies": {
"@lezer/common": "^1.2.0",
@ -270,9 +267,9 @@
}
},
"node_modules/@lezer/javascript": {
"version": "1.4.17",
"resolved": "https://registry.npmjs.org/@lezer/javascript/-/javascript-1.4.17.tgz",
"integrity": "sha512-bYW4ctpyGK+JMumDApeUzuIezX01H76R1foD6LcRX224FWfyYit/HYxiPGDjXXe/wQWASjCvVGoukTH68+0HIA==",
"version": "1.4.21",
"resolved": "https://registry.npmjs.org/@lezer/javascript/-/javascript-1.4.21.tgz",
"integrity": "sha512-lL+1fcuxWYPURMM/oFZLEDm0XuLN128QPV+VuGtKpeaOGdcl9F2LYC3nh1S9LkPqx9M0mndZFdXCipNAZpzIkQ==",
"license": "MIT",
"dependencies": {
"@lezer/common": "^1.2.0",
@ -300,16 +297,21 @@
"@lezer/common": "^1.0.0"
}
},
"node_modules/@marijn/find-cluster-break": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.2.tgz",
"integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==",
"license": "MIT"
},
"node_modules/@rollup/plugin-node-resolve": {
"version": "15.2.3",
"resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.2.3.tgz",
"integrity": "sha512-j/lym8nf5E21LwBT4Df1VD6hRO2L2iwUeUmP7litikRsVp1H6NWx20NEp0Y7su+7XGc476GnXXc4kFeZNGmaSQ==",
"version": "15.3.1",
"resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.3.1.tgz",
"integrity": "sha512-tgg6b91pAybXHJQMAAwW9VuWBO6Thi+q7BCNARLwSqlmsHz0XYURtGvh/AuwSADXSI4h/2uHbs7s4FzlZDGSGA==",
"license": "MIT",
"dependencies": {
"@rollup/pluginutils": "^5.0.1",
"@types/resolve": "1.20.2",
"deepmerge": "^4.2.2",
"is-builtin-module": "^3.2.1",
"is-module": "^1.0.0",
"resolve": "^1.22.1"
},
@ -349,14 +351,14 @@
}
},
"node_modules/@rollup/pluginutils": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.0.tgz",
"integrity": "sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==",
"version": "5.1.4",
"resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.4.tgz",
"integrity": "sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ==",
"license": "MIT",
"dependencies": {
"@types/estree": "^1.0.0",
"estree-walker": "^2.0.2",
"picomatch": "^2.3.1"
"picomatch": "^4.0.2"
},
"engines": {
"node": ">=14.0.0"
@ -371,9 +373,9 @@
}
},
"node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.21.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.21.0.tgz",
"integrity": "sha512-WTWD8PfoSAJ+qL87lE7votj3syLavxunWhzCnx3XFxFiI/BA/r3X7MUM8dVrH8rb2r4AiO8jJsr3ZjdaftmnfA==",
"version": "4.29.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.29.1.tgz",
"integrity": "sha512-ssKhA8RNltTZLpG6/QNkCSge+7mBQGUqJRisZ2MDQcEGaK93QESEgWK2iOpIDZ7k9zPVkG5AS3ksvD5ZWxmItw==",
"cpu": [
"arm"
],
@ -384,9 +386,9 @@
]
},
"node_modules/@rollup/rollup-android-arm64": {
"version": "4.21.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.21.0.tgz",
"integrity": "sha512-a1sR2zSK1B4eYkiZu17ZUZhmUQcKjk2/j9Me2IDjk1GHW7LB5Z35LEzj9iJch6gtUfsnvZs1ZNyDW2oZSThrkA==",
"version": "4.29.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.29.1.tgz",
"integrity": "sha512-CaRfrV0cd+NIIcVVN/jx+hVLN+VRqnuzLRmfmlzpOzB87ajixsN/+9L5xNmkaUUvEbI5BmIKS+XTwXsHEb65Ew==",
"cpu": [
"arm64"
],
@ -397,9 +399,9 @@
]
},
"node_modules/@rollup/rollup-darwin-arm64": {
"version": "4.21.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.21.0.tgz",
"integrity": "sha512-zOnKWLgDld/svhKO5PD9ozmL6roy5OQ5T4ThvdYZLpiOhEGY+dp2NwUmxK0Ld91LrbjrvtNAE0ERBwjqhZTRAA==",
"version": "4.29.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.29.1.tgz",
"integrity": "sha512-2ORr7T31Y0Mnk6qNuwtyNmy14MunTAMx06VAPI6/Ju52W10zk1i7i5U3vlDRWjhOI5quBcrvhkCHyF76bI7kEw==",
"cpu": [
"arm64"
],
@ -410,9 +412,9 @@
]
},
"node_modules/@rollup/rollup-darwin-x64": {
"version": "4.21.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.21.0.tgz",
"integrity": "sha512-7doS8br0xAkg48SKE2QNtMSFPFUlRdw9+votl27MvT46vo44ATBmdZdGysOevNELmZlfd+NEa0UYOA8f01WSrg==",
"version": "4.29.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.29.1.tgz",
"integrity": "sha512-j/Ej1oanzPjmN0tirRd5K2/nncAhS9W6ICzgxV+9Y5ZsP0hiGhHJXZ2JQ53iSSjj8m6cRY6oB1GMzNn2EUt6Ng==",
"cpu": [
"x64"
],
@ -422,10 +424,36 @@
"darwin"
]
},
"node_modules/@rollup/rollup-freebsd-arm64": {
"version": "4.29.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.29.1.tgz",
"integrity": "sha512-91C//G6Dm/cv724tpt7nTyP+JdN12iqeXGFM1SqnljCmi5yTXriH7B1r8AD9dAZByHpKAumqP1Qy2vVNIdLZqw==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"freebsd"
]
},
"node_modules/@rollup/rollup-freebsd-x64": {
"version": "4.29.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.29.1.tgz",
"integrity": "sha512-hEioiEQ9Dec2nIRoeHUP6hr1PSkXzQaCUyqBDQ9I9ik4gCXQZjJMIVzoNLBRGet+hIUb3CISMh9KXuCcWVW/8w==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"freebsd"
]
},
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
"version": "4.21.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.21.0.tgz",
"integrity": "sha512-pWJsfQjNWNGsoCq53KjMtwdJDmh/6NubwQcz52aEwLEuvx08bzcy6tOUuawAOncPnxz/3siRtd8hiQ32G1y8VA==",
"version": "4.29.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.29.1.tgz",
"integrity": "sha512-Py5vFd5HWYN9zxBv3WMrLAXY3yYJ6Q/aVERoeUFwiDGiMOWsMs7FokXihSOaT/PMWUty/Pj60XDQndK3eAfE6A==",
"cpu": [
"arm"
],
@ -436,9 +464,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
"version": "4.21.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.21.0.tgz",
"integrity": "sha512-efRIANsz3UHZrnZXuEvxS9LoCOWMGD1rweciD6uJQIx2myN3a8Im1FafZBzh7zk1RJ6oKcR16dU3UPldaKd83w==",
"version": "4.29.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.29.1.tgz",
"integrity": "sha512-RiWpGgbayf7LUcuSNIbahr0ys2YnEERD4gYdISA06wa0i8RALrnzflh9Wxii7zQJEB2/Eh74dX4y/sHKLWp5uQ==",
"cpu": [
"arm"
],
@ -449,9 +477,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-gnu": {
"version": "4.21.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.21.0.tgz",
"integrity": "sha512-ZrPhydkTVhyeGTW94WJ8pnl1uroqVHM3j3hjdquwAcWnmivjAwOYjTEAuEDeJvGX7xv3Z9GAvrBkEzCgHq9U1w==",
"version": "4.29.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.29.1.tgz",
"integrity": "sha512-Z80O+taYxTQITWMjm/YqNoe9d10OX6kDh8X5/rFCMuPqsKsSyDilvfg+vd3iXIqtfmp+cnfL1UrYirkaF8SBZA==",
"cpu": [
"arm64"
],
@ -462,9 +490,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-musl": {
"version": "4.21.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.21.0.tgz",
"integrity": "sha512-cfaupqd+UEFeURmqNP2eEvXqgbSox/LHOyN9/d2pSdV8xTrjdg3NgOFJCtc1vQ/jEke1qD0IejbBfxleBPHnPw==",
"version": "4.29.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.29.1.tgz",
"integrity": "sha512-fOHRtF9gahwJk3QVp01a/GqS4hBEZCV1oKglVVq13kcK3NeVlS4BwIFzOHDbmKzt3i0OuHG4zfRP0YoG5OF/rA==",
"cpu": [
"arm64"
],
@ -474,10 +502,23 @@
"linux"
]
},
"node_modules/@rollup/rollup-linux-loongarch64-gnu": {
"version": "4.29.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.29.1.tgz",
"integrity": "sha512-5a7q3tnlbcg0OodyxcAdrrCxFi0DgXJSoOuidFUzHZ2GixZXQs6Tc3CHmlvqKAmOs5eRde+JJxeIf9DonkmYkw==",
"cpu": [
"loong64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
"version": "4.21.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.21.0.tgz",
"integrity": "sha512-ZKPan1/RvAhrUylwBXC9t7B2hXdpb/ufeu22pG2psV7RN8roOfGurEghw1ySmX/CmDDHNTDDjY3lo9hRlgtaHg==",
"version": "4.29.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.29.1.tgz",
"integrity": "sha512-9b4Mg5Yfz6mRnlSPIdROcfw1BU22FQxmfjlp/CShWwO3LilKQuMISMTtAu/bxmmrE6A902W2cZJuzx8+gJ8e9w==",
"cpu": [
"ppc64"
],
@ -488,9 +529,9 @@
]
},
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
"version": "4.21.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.21.0.tgz",
"integrity": "sha512-H1eRaCwd5E8eS8leiS+o/NqMdljkcb1d6r2h4fKSsCXQilLKArq6WS7XBLDu80Yz+nMqHVFDquwcVrQmGr28rg==",
"version": "4.29.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.29.1.tgz",
"integrity": "sha512-G5pn0NChlbRM8OJWpJFMX4/i8OEU538uiSv0P6roZcbpe/WfhEO+AT8SHVKfp8qhDQzaz7Q+1/ixMy7hBRidnQ==",
"cpu": [
"riscv64"
],
@ -501,9 +542,9 @@
]
},
"node_modules/@rollup/rollup-linux-s390x-gnu": {
"version": "4.21.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.21.0.tgz",
"integrity": "sha512-zJ4hA+3b5tu8u7L58CCSI0A9N1vkfwPhWd/puGXwtZlsB5bTkwDNW/+JCU84+3QYmKpLi+XvHdmrlwUwDA6kqw==",
"version": "4.29.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.29.1.tgz",
"integrity": "sha512-WM9lIkNdkhVwiArmLxFXpWndFGuOka4oJOZh8EP3Vb8q5lzdSCBuhjavJsw68Q9AKDGeOOIHYzYm4ZFvmWez5g==",
"cpu": [
"s390x"
],
@ -514,9 +555,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-gnu": {
"version": "4.21.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.21.0.tgz",
"integrity": "sha512-e2hrvElFIh6kW/UNBQK/kzqMNY5mO+67YtEh9OA65RM5IJXYTWiXjX6fjIiPaqOkBthYF1EqgiZ6OXKcQsM0hg==",
"version": "4.29.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.29.1.tgz",
"integrity": "sha512-87xYCwb0cPGZFoGiErT1eDcssByaLX4fc0z2nRM6eMtV9njAfEE6OW3UniAoDhX4Iq5xQVpE6qO9aJbCFumKYQ==",
"cpu": [
"x64"
],
@ -527,9 +568,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-musl": {
"version": "4.21.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.21.0.tgz",
"integrity": "sha512-1vvmgDdUSebVGXWX2lIcgRebqfQSff0hMEkLJyakQ9JQUbLDkEaMsPTLOmyccyC6IJ/l3FZuJbmrBw/u0A0uCQ==",
"version": "4.29.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.29.1.tgz",
"integrity": "sha512-xufkSNppNOdVRCEC4WKvlR1FBDyqCSCpQeMMgv9ZyXqqtKBfkw1yfGMTUTs9Qsl6WQbJnsGboWCp7pJGkeMhKA==",
"cpu": [
"x64"
],
@ -540,9 +581,9 @@
]
},
"node_modules/@rollup/rollup-win32-arm64-msvc": {
"version": "4.21.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.21.0.tgz",
"integrity": "sha512-s5oFkZ/hFcrlAyBTONFY1TWndfyre1wOMwU+6KCpm/iatybvrRgmZVM+vCFwxmC5ZhdlgfE0N4XorsDpi7/4XQ==",
"version": "4.29.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.29.1.tgz",
"integrity": "sha512-F2OiJ42m77lSkizZQLuC+jiZ2cgueWQL5YC9tjo3AgaEw+KJmVxHGSyQfDUoYR9cci0lAywv2Clmckzulcq6ig==",
"cpu": [
"arm64"
],
@ -553,9 +594,9 @@
]
},
"node_modules/@rollup/rollup-win32-ia32-msvc": {
"version": "4.21.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.21.0.tgz",
"integrity": "sha512-G9+TEqRnAA6nbpqyUqgTiopmnfgnMkR3kMukFBDsiyy23LZvUCpiUwjTRx6ezYCjJODXrh52rBR9oXvm+Fp5wg==",
"version": "4.29.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.29.1.tgz",
"integrity": "sha512-rYRe5S0FcjlOBZQHgbTKNrqxCBUmgDJem/VQTCcTnA2KCabYSWQDrytOzX7avb79cAAweNmMUb/Zw18RNd4mng==",
"cpu": [
"ia32"
],
@ -566,9 +607,9 @@
]
},
"node_modules/@rollup/rollup-win32-x64-msvc": {
"version": "4.21.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.21.0.tgz",
"integrity": "sha512-2jsCDZwtQvRhejHLfZ1JY6w6kEuEtfF9nzYsZxzSlNVKDX+DpsDJ+Rbjkm74nvg2rdx0gwBS+IMdvwJuq3S9pQ==",
"version": "4.29.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.29.1.tgz",
"integrity": "sha512-+10CMg9vt1MoHj6x1pxyjPSMjHTIlqs8/tBztXvPAx24SKs9jwVnKqHJumlH/IzhaPUaj3T6T6wfZr8okdXaIg==",
"cpu": [
"x64"
],
@ -579,9 +620,9 @@
]
},
"node_modules/@types/estree": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
"integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==",
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz",
"integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==",
"license": "MIT"
},
"node_modules/@types/resolve": {
@ -591,9 +632,9 @@
"license": "MIT"
},
"node_modules/acorn": {
"version": "8.12.1",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz",
"integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==",
"version": "8.14.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz",
"integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==",
"dev": true,
"license": "MIT",
"bin": {
@ -610,18 +651,6 @@
"dev": true,
"license": "MIT"
},
"node_modules/builtin-modules": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz",
"integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==",
"license": "MIT",
"engines": {
"node": ">=6"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/codemirror": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/codemirror/-/codemirror-6.0.1.tgz",
@ -700,25 +729,10 @@
"node": ">= 0.4"
}
},
"node_modules/is-builtin-module": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.2.1.tgz",
"integrity": "sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==",
"license": "MIT",
"dependencies": {
"builtin-modules": "^3.3.0"
},
"engines": {
"node": ">=6"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/is-core-module": {
"version": "2.15.1",
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz",
"integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==",
"version": "2.16.1",
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
"integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
"license": "MIT",
"dependencies": {
"hasown": "^2.0.2"
@ -743,12 +757,12 @@
"license": "MIT"
},
"node_modules/picomatch": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
"integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
"license": "MIT",
"engines": {
"node": ">=8.6"
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
@ -765,29 +779,32 @@
}
},
"node_modules/resolve": {
"version": "1.22.8",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
"integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==",
"version": "1.22.10",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz",
"integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==",
"license": "MIT",
"dependencies": {
"is-core-module": "^2.13.0",
"is-core-module": "^2.16.0",
"path-parse": "^1.0.7",
"supports-preserve-symlinks-flag": "^1.0.0"
},
"bin": {
"resolve": "bin/resolve"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/rollup": {
"version": "4.21.0",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.21.0.tgz",
"integrity": "sha512-vo+S/lfA2lMS7rZ2Qoubi6I5hwZwzXeUIctILZLbHI+laNtvhhOIon2S1JksA5UEDQ7l3vberd0fxK44lTYjbQ==",
"version": "4.29.1",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.29.1.tgz",
"integrity": "sha512-RaJ45M/kmJUzSWDs1Nnd5DdV4eerC98idtUOVr6FfKcgxqvjwHmxc5upLF9qZU9EpsVzzhleFahrT3shLuJzIw==",
"license": "MIT",
"dependencies": {
"@types/estree": "1.0.5"
"@types/estree": "1.0.6"
},
"bin": {
"rollup": "dist/bin/rollup"
@ -797,22 +814,25 @@
"npm": ">=8.0.0"
},
"optionalDependencies": {
"@rollup/rollup-android-arm-eabi": "4.21.0",
"@rollup/rollup-android-arm64": "4.21.0",
"@rollup/rollup-darwin-arm64": "4.21.0",
"@rollup/rollup-darwin-x64": "4.21.0",
"@rollup/rollup-linux-arm-gnueabihf": "4.21.0",
"@rollup/rollup-linux-arm-musleabihf": "4.21.0",
"@rollup/rollup-linux-arm64-gnu": "4.21.0",
"@rollup/rollup-linux-arm64-musl": "4.21.0",
"@rollup/rollup-linux-powerpc64le-gnu": "4.21.0",
"@rollup/rollup-linux-riscv64-gnu": "4.21.0",
"@rollup/rollup-linux-s390x-gnu": "4.21.0",
"@rollup/rollup-linux-x64-gnu": "4.21.0",
"@rollup/rollup-linux-x64-musl": "4.21.0",
"@rollup/rollup-win32-arm64-msvc": "4.21.0",
"@rollup/rollup-win32-ia32-msvc": "4.21.0",
"@rollup/rollup-win32-x64-msvc": "4.21.0",
"@rollup/rollup-android-arm-eabi": "4.29.1",
"@rollup/rollup-android-arm64": "4.29.1",
"@rollup/rollup-darwin-arm64": "4.29.1",
"@rollup/rollup-darwin-x64": "4.29.1",
"@rollup/rollup-freebsd-arm64": "4.29.1",
"@rollup/rollup-freebsd-x64": "4.29.1",
"@rollup/rollup-linux-arm-gnueabihf": "4.29.1",
"@rollup/rollup-linux-arm-musleabihf": "4.29.1",
"@rollup/rollup-linux-arm64-gnu": "4.29.1",
"@rollup/rollup-linux-arm64-musl": "4.29.1",
"@rollup/rollup-linux-loongarch64-gnu": "4.29.1",
"@rollup/rollup-linux-powerpc64le-gnu": "4.29.1",
"@rollup/rollup-linux-riscv64-gnu": "4.29.1",
"@rollup/rollup-linux-s390x-gnu": "4.29.1",
"@rollup/rollup-linux-x64-gnu": "4.29.1",
"@rollup/rollup-linux-x64-musl": "4.29.1",
"@rollup/rollup-win32-arm64-msvc": "4.29.1",
"@rollup/rollup-win32-ia32-msvc": "4.29.1",
"@rollup/rollup-win32-x64-msvc": "4.29.1",
"fsevents": "~2.3.2"
}
},
@ -894,9 +914,9 @@
}
},
"node_modules/terser": {
"version": "5.31.6",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.31.6.tgz",
"integrity": "sha512-PQ4DAriWzKj+qgehQ7LK5bQqCFNMmlhjR2PFFLuqGCpuCAauxemVBWwWOxo3UIwWQx8+Pr61Df++r76wDmkQBg==",
"version": "5.37.0",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.37.0.tgz",
"integrity": "sha512-B8wRRkmre4ERucLM/uXx4MOV5cbnOlVAqUst+1+iLKPI0dOgFO28f84ptoQt9HEI537PMzfYa/d+GEPKTRXmYA==",
"dev": true,
"license": "BSD-2-Clause",
"dependencies": {

2
deps/libuv vendored

Submodule deps/libuv updated: e9f29cb984...e1095c7a43

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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

File diff suppressed because one or more lines are too long

@ -1,2 +1,2 @@
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge"><title>speedscope</title><link href="https://fonts.googleapis.com/css?family=Source+Code+Pro" rel="stylesheet"><script></script><link rel="stylesheet" href="reset.8c46b7a1.css"><link rel="icon" type="image/png" sizes="32x32" href="favicon-32x32.bc503437.png"><link rel="icon" type="image/png" sizes="16x16" href="favicon-16x16.f74b3187.png"></head><body> <script src="speedscope.80eb88d2.js"></script>
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge"><title>speedscope</title><link href="source-code-pro.52b1676f.css" rel="stylesheet"><script></script><link rel="stylesheet" href="reset.8c46b7a1.css"><link rel="icon" type="image/png" sizes="32x32" href="favicon-32x32.bc503437.png"><link rel="icon" type="image/png" sizes="16x16" href="favicon-16x16.f74b3187.png"></head><body> <script src="speedscope.6f107512.js"></script>
</body></html>

@ -1,3 +1,3 @@
speedscope@1.20.0
Fri Jan 12 09:57:49 PST 2024
68fd88ceaf93d89aa27f3f0a20a27c9cfdc015c5
speedscope@1.21.0
Sat Nov 16 22:13:27 PST 2024
d36c3a54424063a8df7bc67a7b824a223d73861b

@ -0,0 +1,2 @@
@font-face{font-family:Source Code Pro;font-weight:400;font-style:normal;font-stretch:normal;src:url(SourceCodePro-Regular.ttf.f546cbe0.woff2) format("woff2")}
/*# sourceMappingURL=source-code-pro.52b1676f.css.map */

@ -0,0 +1,93 @@
Copyright 2010-2019 Adobe (http://www.adobe.com/), with Reserved Font Name 'Source'. All Rights Reserved. Source is a trademark of Adobe in the United States and/or other countries.
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

File diff suppressed because one or more lines are too long

6011
deps/sqlite/shell.c vendored

File diff suppressed because it is too large Load Diff

8456
deps/sqlite/sqlite3.c vendored

File diff suppressed because it is too large Load Diff

202
deps/sqlite/sqlite3.h vendored

@ -146,9 +146,9 @@ extern "C" {
** [sqlite3_libversion_number()], [sqlite3_sourceid()],
** [sqlite_version()] and [sqlite_source_id()].
*/
#define SQLITE_VERSION "3.46.1"
#define SQLITE_VERSION_NUMBER 3046001
#define SQLITE_SOURCE_ID "2024-08-13 09:16:08 c9c2ab54ba1f5f46360f1b4f35d849cd3f080e6fc2b6c60e91b16c63f69a1e33"
#define SQLITE_VERSION "3.47.2"
#define SQLITE_VERSION_NUMBER 3047002
#define SQLITE_SOURCE_ID "2024-12-07 20:39:59 2aabe05e2e8cae4847a802ee2daddc1d7413d8fc560254d93ee3e72c14685b6c"
/*
** CAPI3REF: Run-Time Library Version Numbers
@ -652,6 +652,13 @@ SQLITE_API int sqlite3_exec(
** filesystem supports doing multiple write operations atomically when those
** write operations are bracketed by [SQLITE_FCNTL_BEGIN_ATOMIC_WRITE] and
** [SQLITE_FCNTL_COMMIT_ATOMIC_WRITE].
**
** The SQLITE_IOCAP_SUBPAGE_READ property means that it is ok to read
** from the database file in amounts that are not a multiple of the
** page size and that do not begin at a page boundary. Without this
** property, SQLite is careful to only do full-page reads and write
** on aligned pages, with the one exception that it will do a sub-page
** read of the first page to access the database header.
*/
#define SQLITE_IOCAP_ATOMIC 0x00000001
#define SQLITE_IOCAP_ATOMIC512 0x00000002
@ -668,6 +675,7 @@ SQLITE_API int sqlite3_exec(
#define SQLITE_IOCAP_POWERSAFE_OVERWRITE 0x00001000
#define SQLITE_IOCAP_IMMUTABLE 0x00002000
#define SQLITE_IOCAP_BATCH_ATOMIC 0x00004000
#define SQLITE_IOCAP_SUBPAGE_READ 0x00008000
/*
** CAPI3REF: File Locking Levels
@ -772,8 +780,8 @@ struct sqlite3_file {
** to xUnlock() is a no-op.
** The xCheckReservedLock() method checks whether any database connection,
** either in this process or in some other process, is holding a RESERVED,
** PENDING, or EXCLUSIVE lock on the file. It returns true
** if such a lock exists and false otherwise.
** PENDING, or EXCLUSIVE lock on the file. It returns, via its output
** pointer parameter, true if such a lock exists and false otherwise.
**
** The xFileControl() method is a generic interface that allows custom
** VFS implementations to directly control an open file using the
@ -814,6 +822,7 @@ struct sqlite3_file {
** <li> [SQLITE_IOCAP_POWERSAFE_OVERWRITE]
** <li> [SQLITE_IOCAP_IMMUTABLE]
** <li> [SQLITE_IOCAP_BATCH_ATOMIC]
** <li> [SQLITE_IOCAP_SUBPAGE_READ]
** </ul>
**
** The SQLITE_IOCAP_ATOMIC property means that all writes of
@ -3570,8 +3579,8 @@ SQLITE_API void sqlite3_progress_handler(sqlite3*, int, int(*)(void*), void*);
**
** [[OPEN_EXRESCODE]] ^(<dt>[SQLITE_OPEN_EXRESCODE]</dt>
** <dd>The database connection comes up in "extended result code mode".
** In other words, the database behaves has if
** [sqlite3_extended_result_codes(db,1)] where called on the database
** In other words, the database behaves as if
** [sqlite3_extended_result_codes(db,1)] were called on the database
** connection as soon as the connection is created. In addition to setting
** the extended result code mode, this flag also causes [sqlite3_open_v2()]
** to return an extended result code.</dd>
@ -4222,13 +4231,17 @@ SQLITE_API int sqlite3_limit(sqlite3*, int id, int newVal);
** and sqlite3_prepare16_v3() use UTF-16.
**
** ^If the nByte argument is negative, then zSql is read up to the
** first zero terminator. ^If nByte is positive, then it is the
** number of bytes read from zSql. ^If nByte is zero, then no prepared
** first zero terminator. ^If nByte is positive, then it is the maximum
** number of bytes read from zSql. When nByte is positive, zSql is read
** up to the first zero terminator or until the nByte bytes have been read,
** whichever comes first. ^If nByte is zero, then no prepared
** statement is generated.
** If the caller knows that the supplied string is nul-terminated, then
** there is a small performance advantage to passing an nByte parameter that
** is the number of bytes in the input string <i>including</i>
** the nul-terminator.
** Note that nByte measure the length of the input in bytes, not
** characters, even for the UTF-16 interfaces.
**
** ^If pzTail is not NULL then *pzTail is made to point to the first byte
** past the end of the first SQL statement in zSql. These routines only
@ -5599,7 +5612,7 @@ SQLITE_API int sqlite3_create_window_function(
** This flag instructs SQLite to omit some corner-case optimizations that
** might disrupt the operation of the [sqlite3_value_subtype()] function,
** causing it to return zero rather than the correct subtype().
** SQL functions that invokes [sqlite3_value_subtype()] should have this
** All SQL functions that invoke [sqlite3_value_subtype()] should have this
** property. If the SQLITE_SUBTYPE property is omitted, then the return
** value from [sqlite3_value_subtype()] might sometimes be zero even though
** a non-zero subtype was specified by the function argument expression.
@ -5615,6 +5628,15 @@ SQLITE_API int sqlite3_create_window_function(
** [sqlite3_result_subtype()] should avoid setting this property, as the
** purpose of this property is to disable certain optimizations that are
** incompatible with subtypes.
**
** [[SQLITE_SELFORDER1]] <dt>SQLITE_SELFORDER1</dt><dd>
** The SQLITE_SELFORDER1 flag indicates that the function is an aggregate
** that internally orders the values provided to the first argument. The
** ordered-set aggregate SQL notation with a single ORDER BY term can be
** used to invoke this function. If the ordered-set aggregate notation is
** used on a function that lacks this flag, then an error is raised. Note
** that the ordered-set aggregate syntax is only available if SQLite is
** built using the -DSQLITE_ENABLE_ORDERED_SET_AGGREGATES compile-time option.
** </dd>
** </dl>
*/
@ -5623,6 +5645,7 @@ SQLITE_API int sqlite3_create_window_function(
#define SQLITE_SUBTYPE 0x000100000
#define SQLITE_INNOCUOUS 0x000200000
#define SQLITE_RESULT_SUBTYPE 0x001000000
#define SQLITE_SELFORDER1 0x002000000
/*
** CAPI3REF: Deprecated Functions
@ -5820,7 +5843,7 @@ SQLITE_API int sqlite3_value_encoding(sqlite3_value*);
** one SQL function to another. Use the [sqlite3_result_subtype()]
** routine to set the subtype for the return value of an SQL function.
**
** Every [application-defined SQL function] that invoke this interface
** Every [application-defined SQL function] that invokes this interface
** should include the [SQLITE_SUBTYPE] property in the text
** encoding argument when the function is [sqlite3_create_function|registered].
** If the [SQLITE_SUBTYPE] property is omitted, then sqlite3_value_subtype()
@ -7427,9 +7450,11 @@ struct sqlite3_module {
** will be returned by the strategy.
**
** The xBestIndex method may optionally populate the idxFlags field with a
** mask of SQLITE_INDEX_SCAN_* flags. Currently there is only one such flag -
** SQLITE_INDEX_SCAN_UNIQUE. If the xBestIndex method sets this flag, SQLite
** assumes that the strategy may visit at most one row.
** mask of SQLITE_INDEX_SCAN_* flags. One such flag is
** [SQLITE_INDEX_SCAN_HEX], which if set causes the [EXPLAIN QUERY PLAN]
** output to show the idxNum has hex instead of as decimal. Another flag is
** SQLITE_INDEX_SCAN_UNIQUE, which if set indicates that the query plan will
** return at most one row.
**
** Additionally, if xBestIndex sets the SQLITE_INDEX_SCAN_UNIQUE flag, then
** SQLite also assumes that if a call to the xUpdate() method is made as
@ -7493,7 +7518,9 @@ struct sqlite3_index_info {
** [sqlite3_index_info].idxFlags field to some combination of
** these bits.
*/
#define SQLITE_INDEX_SCAN_UNIQUE 1 /* Scan visits at most 1 row */
#define SQLITE_INDEX_SCAN_UNIQUE 0x00000001 /* Scan visits at most 1 row */
#define SQLITE_INDEX_SCAN_HEX 0x00000002 /* Display idxNum as hex */
/* in EXPLAIN QUERY PLAN */
/*
** CAPI3REF: Virtual Table Constraint Operator Codes
@ -8330,6 +8357,7 @@ SQLITE_API int sqlite3_test_control(int op, ...);
#define SQLITE_TESTCTRL_JSON_SELFCHECK 14
#define SQLITE_TESTCTRL_OPTIMIZATIONS 15
#define SQLITE_TESTCTRL_ISKEYWORD 16 /* NOT USED */
#define SQLITE_TESTCTRL_GETOPT 16
#define SQLITE_TESTCTRL_SCRATCHMALLOC 17 /* NOT USED */
#define SQLITE_TESTCTRL_INTERNAL_FUNCTIONS 17
#define SQLITE_TESTCTRL_LOCALTIME_FAULT 18
@ -8349,7 +8377,7 @@ SQLITE_API int sqlite3_test_control(int op, ...);
#define SQLITE_TESTCTRL_TRACEFLAGS 31
#define SQLITE_TESTCTRL_TUNE 32
#define SQLITE_TESTCTRL_LOGEST 33
#define SQLITE_TESTCTRL_USELONGDOUBLE 34
#define SQLITE_TESTCTRL_USELONGDOUBLE 34 /* NOT USED */
#define SQLITE_TESTCTRL_LAST 34 /* Largest TESTCTRL */
/*
@ -9325,6 +9353,16 @@ typedef struct sqlite3_backup sqlite3_backup;
** APIs are not strictly speaking threadsafe. If they are invoked at the
** same time as another thread is invoking sqlite3_backup_step() it is
** possible that they return invalid values.
**
** <b>Alternatives To Using The Backup API</b>
**
** Other techniques for safely creating a consistent backup of an SQLite
** database include:
**
** <ul>
** <li> The [VACUUM INTO] command.
** <li> The [sqlite3_rsync] utility program.
** </ul>
*/
SQLITE_API sqlite3_backup *sqlite3_backup_init(
sqlite3 *pDest, /* Destination database handle */
@ -10524,6 +10562,14 @@ typedef struct sqlite3_snapshot {
** If there is not already a read-transaction open on schema S when
** this function is called, one is opened automatically.
**
** If a read-transaction is opened by this function, then it is guaranteed
** that the returned snapshot object may not be invalidated by a database
** writer or checkpointer until after the read-transaction is closed. This
** is not guaranteed if a read-transaction is already open when this
** function is called. In that case, any subsequent write or checkpoint
** operation on the database may invalidate the returned snapshot handle,
** even while the read-transaction remains open.
**
** The following must be true for this function to succeed. If any of
** the following statements are false when sqlite3_snapshot_get() is
** called, SQLITE_ERROR is returned. The final value of *P is undefined
@ -10832,8 +10878,6 @@ SQLITE_API int sqlite3_deserialize(
#if defined(__wasi__)
# undef SQLITE_WASI
# define SQLITE_WASI 1
# undef SQLITE_OMIT_WAL
# define SQLITE_OMIT_WAL 1/* because it requires shared memory APIs */
# ifndef SQLITE_OMIT_LOAD_EXTENSION
# define SQLITE_OMIT_LOAD_EXTENSION
# endif
@ -13036,6 +13080,10 @@ struct Fts5PhraseIter {
** (i.e. if it is a contentless table), then this API always iterates
** through an empty set (all calls to xPhraseFirst() set iCol to -1).
**
** In all cases, matches are visited in (column ASC, offset ASC) order.
** i.e. all those in column 0, sorted by offset, followed by those in
** column 1, etc.
**
** xPhraseNext()
** See xPhraseFirst above.
**
@ -13102,9 +13150,32 @@ struct Fts5PhraseIter {
**
** This API can be quite slow if used with an FTS5 table created with the
** "detail=none" or "detail=column" option.
**
** xColumnLocale(pFts5, iIdx, pzLocale, pnLocale)
** If parameter iCol is less than zero, or greater than or equal to the
** number of columns in the table, SQLITE_RANGE is returned.
**
** Otherwise, this function attempts to retrieve the locale associated
** with column iCol of the current row. Usually, there is no associated
** locale, and output parameters (*pzLocale) and (*pnLocale) are set
** to NULL and 0, respectively. However, if the fts5_locale() function
** was used to associate a locale with the value when it was inserted
** into the fts5 table, then (*pzLocale) is set to point to a nul-terminated
** buffer containing the name of the locale in utf-8 encoding. (*pnLocale)
** is set to the size in bytes of the buffer, not including the
** nul-terminator.
**
** If successful, SQLITE_OK is returned. Or, if an error occurs, an
** SQLite error code is returned. The final value of the output parameters
** is undefined in this case.
**
** xTokenize_v2:
** Tokenize text using the tokenizer belonging to the FTS5 table. This
** API is the same as the xTokenize() API, except that it allows a tokenizer
** locale to be specified.
*/
struct Fts5ExtensionApi {
int iVersion; /* Currently always set to 3 */
int iVersion; /* Currently always set to 4 */
void *(*xUserData)(Fts5Context*);
@ -13146,6 +13217,15 @@ struct Fts5ExtensionApi {
const char **ppToken, int *pnToken
);
int (*xInstToken)(Fts5Context*, int iIdx, int iToken, const char**, int*);
/* Below this point are iVersion>=4 only */
int (*xColumnLocale)(Fts5Context*, int iCol, const char **pz, int *pn);
int (*xTokenize_v2)(Fts5Context*,
const char *pText, int nText, /* Text to tokenize */
const char *pLocale, int nLocale, /* Locale to pass to tokenizer */
void *pCtx, /* Context passed to xToken() */
int (*xToken)(void*, int, const char*, int, int, int) /* Callback */
);
};
/*
@ -13166,7 +13246,7 @@ struct Fts5ExtensionApi {
** A tokenizer instance is required to actually tokenize text.
**
** The first argument passed to this function is a copy of the (void*)
** pointer provided by the application when the fts5_tokenizer object
** pointer provided by the application when the fts5_tokenizer_v2 object
** was registered with FTS5 (the third argument to xCreateTokenizer()).
** The second and third arguments are an array of nul-terminated strings
** containing the tokenizer arguments, if any, specified following the
@ -13190,7 +13270,7 @@ struct Fts5ExtensionApi {
** argument passed to this function is a pointer to an Fts5Tokenizer object
** returned by an earlier call to xCreate().
**
** The second argument indicates the reason that FTS5 is requesting
** The third argument indicates the reason that FTS5 is requesting
** tokenization of the supplied text. This is always one of the following
** four values:
**
@ -13214,6 +13294,13 @@ struct Fts5ExtensionApi {
** on a columnsize=0 database.
** </ul>
**
** The sixth and seventh arguments passed to xTokenize() - pLocale and
** nLocale - are a pointer to a buffer containing the locale to use for
** tokenization (e.g. "en_US") and its size in bytes, respectively. The
** pLocale buffer is not nul-terminated. pLocale may be passed NULL (in
** which case nLocale is always 0) to indicate that the tokenizer should
** use its default locale.
**
** For each token in the input string, the supplied callback xToken() must
** be invoked. The first argument to it should be a copy of the pointer
** passed as the second argument to xTokenize(). The third and fourth
@ -13237,6 +13324,30 @@ struct Fts5ExtensionApi {
** may abandon the tokenization and return any error code other than
** SQLITE_OK or SQLITE_DONE.
**
** If the tokenizer is registered using an fts5_tokenizer_v2 object,
** then the xTokenize() method has two additional arguments - pLocale
** and nLocale. These specify the locale that the tokenizer should use
** for the current request. If pLocale and nLocale are both 0, then the
** tokenizer should use its default locale. Otherwise, pLocale points to
** an nLocale byte buffer containing the name of the locale to use as utf-8
** text. pLocale is not nul-terminated.
**
** FTS5_TOKENIZER
**
** There is also an fts5_tokenizer object. This is an older, deprecated,
** version of fts5_tokenizer_v2. It is similar except that:
**
** <ul>
** <li> There is no "iVersion" field, and
** <li> The xTokenize() method does not take a locale argument.
** </ul>
**
** Legacy fts5_tokenizer tokenizers must be registered using the
** legacy xCreateTokenizer() function, instead of xCreateTokenizer_v2().
**
** Tokenizer implementations registered using either API may be retrieved
** using both xFindTokenizer() and xFindTokenizer_v2().
**
** SYNONYM SUPPORT
**
** Custom tokenizers may also support synonyms. Consider a case in which a
@ -13345,6 +13456,33 @@ struct Fts5ExtensionApi {
** inefficient.
*/
typedef struct Fts5Tokenizer Fts5Tokenizer;
typedef struct fts5_tokenizer_v2 fts5_tokenizer_v2;
struct fts5_tokenizer_v2 {
int iVersion; /* Currently always 2 */
int (*xCreate)(void*, const char **azArg, int nArg, Fts5Tokenizer **ppOut);
void (*xDelete)(Fts5Tokenizer*);
int (*xTokenize)(Fts5Tokenizer*,
void *pCtx,
int flags, /* Mask of FTS5_TOKENIZE_* flags */
const char *pText, int nText,
const char *pLocale, int nLocale,
int (*xToken)(
void *pCtx, /* Copy of 2nd argument to xTokenize() */
int tflags, /* Mask of FTS5_TOKEN_* flags */
const char *pToken, /* Pointer to buffer containing token */
int nToken, /* Size of token in bytes */
int iStart, /* Byte offset of token within input text */
int iEnd /* Byte offset of end of token within input text */
)
);
};
/*
** New code should use the fts5_tokenizer_v2 type to define tokenizer
** implementations. The following type is included for legacy applications
** that still use it.
*/
typedef struct fts5_tokenizer fts5_tokenizer;
struct fts5_tokenizer {
int (*xCreate)(void*, const char **azArg, int nArg, Fts5Tokenizer **ppOut);
@ -13364,6 +13502,7 @@ struct fts5_tokenizer {
);
};
/* Flags that may be passed as the third argument to xTokenize() */
#define FTS5_TOKENIZE_QUERY 0x0001
#define FTS5_TOKENIZE_PREFIX 0x0002
@ -13383,7 +13522,7 @@ struct fts5_tokenizer {
*/
typedef struct fts5_api fts5_api;
struct fts5_api {
int iVersion; /* Currently always set to 2 */
int iVersion; /* Currently always set to 3 */
/* Create a new tokenizer */
int (*xCreateTokenizer)(
@ -13410,6 +13549,25 @@ struct fts5_api {
fts5_extension_function xFunction,
void (*xDestroy)(void*)
);
/* APIs below this point are only available if iVersion>=3 */
/* Create a new tokenizer */
int (*xCreateTokenizer_v2)(
fts5_api *pApi,
const char *zName,
void *pUserData,
fts5_tokenizer_v2 *pTokenizer,
void (*xDestroy)(void*)
);
/* Find an existing tokenizer */
int (*xFindTokenizer_v2)(
fts5_api *pApi,
const char *zName,
void **ppUserData,
fts5_tokenizer_v2 **ppTokenizer
);
};
/*

1
docs Submodule

Submodule docs added at a40758cc4b

@ -1,209 +0,0 @@
# Philosophy
Tilde Friends is a platform for making, running, and sharing web applications.
When you visit Tilde Friends in a web browser, you are presented with a
terminal interface, typically with a big text output box covering most of the
page and an input box at the bottom, into which text or commands can be
entered. A script runs to produce text output and consume user input.
The script is a Tilde Friends application, and it runs on the server, which
means that unlike client-side JavaScript, it can have the ability to read and
write files on the server or create network connections to other machines.
Unlike node.js or other server-side runtime environments, applications are
limited for security reasons to not interfere with each other or bring the
entire server down.
Above the terminal, an "Edit" link brings a visitor to the source code for the
current Tilde Friends application, which they can then edit, save as their own,
and run.
# Architecture
Tilde Friends is a C++ application with a JavaScript runtime that provides
restricted access to filesystem, network, and other system resources. The core
process runs a core set of scripts that implement a web server, typically
starting a new process for each visitor's session which runs scripts for the
active application and stopping it when the visitor leaves.
Only the core process has access to most system resources, but session
processes can be given accesss through the core process.
Service processes are identical to session processes, but they are not tied to
a user session.
## Communication
In the same way that web browsers expose APIs for scripts running in the
browser to modify the document, play sounds and video, and draw, Tilde Friends
exposes APIs for scripts running on a Tilde Friends server to interact with a
visitor's web browser, read and write files on the server, and otherwise
interact with the world.
There are several distinct classes of APIs.
First, there are low-level functions exposed from C++ to JavaScript. Most of
these are only available to the core process. These typically only go through
a basic JavaScript to C++ transition and are relatively fast and immediate.
// Displays some text to the server's console.
print("Hello, world!");
There is a mechanism for communicating between processes. Functions can be
exported and called across process boundaries. When this is done, any
arguments are serialized to a network protocol, deserialized by the other
process, the function called, and finally any return value is passed back in
the same way. Any functions referenced by the arguments or return value are
also exported and can be subsequently called across process boundaries.
Functions called across process boundaries are always asynchronous, returning a
Promise. Care must be taken for security reasons to not pass dangerous
functions ("deleteAllMydata()") to untrusted processes, and it is best for
performance reasons to minimize the data size transferred between processes.
// Send an "add" function to any other running processes. When called, it
// will run in this process.
core.broadcast({add: function(x, y) { return x + y; }});
// Receive the above message and call the function.
core.register("onMessage", function(sender, message) {
message.add(3, 4).then(x => terminal.print(x.toString()));
});
Finally, there is a core web interface that runs on the client's browser that
extends access to a running Tilde Friends script.
// Displays a message in the client's browser.
terminal.print("Hello, world!");
## API Documentation
The Tilde Friends API is very much evolving.
All currently registered methods can be explored in the
[documentation](https://www.tildefriends.net/~cory/documentation) app.
All browser-facing methods are implemented in [client.js](core/client.js).
Most process-related methods are implemented in [core.js](core/core.js).
Higher-level behaviors are often implemented within library-style apps
themselves and are beyond the scope of this document.
### Terminal
All interaction with a human user is through a terminal-like interface. Though
it is somewhat limiting, it makes simple things easy, and it is possible to
construct complicated interfaces by creating and interacting with an iframe.
#### terminal.print(arguments...)
Print to the terminal. Arguments and lists are recursively expanded. Numerous
special values are supported as implemented in client.cs.
// Create a link.
terminal.print({href: "http://www.tildefriends.net/", value: "Tilde Friends!"});
// Create an iframe.
terminal.print({iframe: "&lt;b&gt;Hello, world!&lt;/b&gt;", width: 640, height: 480});
// Use style.
terminal.print({style: "color: #f00", value: "Hello, world!"});
// Create a link that when clicked will act as if the user typed a command.
terminal.print({command: "exit", value: "Get out of here."});
#### terminal.clear()
Clears the terminal output.
#### terminal.readLine()
Read a line of input from the user.
#### terminal.setEcho(echo)
Controls whether the terminal will automatically echo user input. Defaults to true.
#### terminal.setPrompt(prompt)
Sets the terminal prompt. The default is "&gt;".
#### terminal.setTitle(title)
Sets the browser window/tab title.
#### terminal.split(terminalList)
Reconfigures the terminal layout, potentially into multiple split panes.
terminal.split([
{
type: "horizontal",
children: [
{name: "left", basis: "2in", grow: 0, shrink: 0},
{name: "middle", grow: 1},
{name: "right", basis: "2in", grow: 0, shrink: 0},
],
},
]);
#### terminal.select(name)
Directs subsequent output to the named terminal.
#### terminal.postMessageToIframe(iframeName, message)
Sends a message to the iframe that was created with the given name, using the
browser's window.postMessage.
### Database
Tilde Friends uses lmdb as a basic key value store. Keys and values are all
expected to be of type String. Each application gets its own isolated
database.
#### database.get(key)
Retrieve the database value associated with the given key.
#### database.set(key, value)
Sets the database value for the given key, overwriting any existing value.
#### database.remove(key)
Remove the database entry for the given key.
#### database.getAlll()
Retrieve a list of all key names.
### Network
Network access is generally not extended to untrusted users.
It is necessary to grant network permissions to an app owner through the
administration app.
Apps that require network access must declare it like this:
//! { "permissions": ["network"] }
#### network.newConnection()
Creates a Connection object.
#### connection.connect(host, port)
Opens a TCP connection to host:port.
#### connection.read(readCallback)
Begins reading and calls readCallback(data) for all data received.
#### connection.write(data)
Writes data to the connection.
#### connection.close()
Closes the connection.

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