34 Commits

Author SHA1 Message Date
91fd515d39 android: Cleaner shutdown still.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 31m38s
2025-08-27 20:23:02 -04:00
be6e841d3d android: This order seems more sensible.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-08-27 20:12:20 -04:00
af6afa6903 android: Don't log from the main thread. It might block?
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-08-27 19:20:02 -04:00
6ab5d2a28d build: Start work on 0.2025.9.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-08-27 18:55:24 -04:00
4be033f288 build: Do the nix dance. 2025-08-27 18:54:41 -04:00
c550f92003 docs: Fix changelog version for f-droid.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 30m4s
2025-08-27 18:22:23 -04:00
ed836b3ee0 build: Bump this, too.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-08-27 18:01:57 -04:00
ac7a43abf4 build: Just kidding. This is the real 0.2025.8.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-08-27 18:00:48 -04:00
49f19fce91 build: Let's build 0.2025.8.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 31m42s
2025-08-27 12:13:16 -04:00
b2197eb8e9 docs: Update the changelog. 2025-08-27 12:13:16 -04:00
5fbc2cae1c ssb: Don't indent blockquotes so much.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 32m51s
2025-08-24 20:35:48 -04:00
730abb49ce android: Keep the splash screen up until we're connected to our server and loaded the page. Smooths out the launch.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 31m38s
2025-08-22 19:24:03 -04:00
edccab054a ssb: Make the close chat button work even when a chat isn't preexisting.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 31m39s
2025-08-20 20:40:02 -04:00
e8210c6fdd core: Never-ending quest to fix clean shutdown.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-08-20 20:20:21 -04:00
55d69d7c13 test: This seems to make -t=auto more reliable. 2025-08-20 20:19:45 -04:00
50fb18d4ff core: Remove the want: log noise.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-08-20 19:53:18 -04:00
77b1ea1fc8 ssb: Don't show messages that were slow to load from another channel.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-08-20 19:38:52 -04:00
61501a9b64 ssb: Fix closing self-chat.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-08-20 19:28:23 -04:00
c1507adac5 docs: Start the next changelog. 2025-08-20 19:25:44 -04:00
45fb9eda1c ssb: Add a button on profiles to open a private chat.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-08-20 19:19:34 -04:00
982a61f4bf ssb: Remove some debug. 2025-08-20 19:15:20 -04:00
18e5b41663 ssb: Add a button to close a private chat, removing it from the sidebar. 2025-08-20 19:08:07 -04:00
910c39cbd0 update: CodeMirror. 2025-08-20 18:07:41 -04:00
9952dfd49d ssb: Fix @-completion.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 31m40s
2025-08-19 12:54:19 -04:00
00f75d5382 ssb: Unread status for private messages.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 32m23s
2025-08-14 12:40:58 -04:00
d78828554b ssb: Fix composing private drafts.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 31m55s
2025-08-13 20:28:03 -04:00
b84b561109 prettier.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-08-13 20:14:47 -04:00
a618815500 ssb: Fix issues with private messages to one's self.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-08-13 20:12:27 -04:00
e1f3dc6ae4 ssb: Fix an issue with loading directly into private messages.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-08-13 19:58:02 -04:00
f378db6c6f ssb: Better handling of private message drafts.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-08-13 19:53:28 -04:00
7cec0f7d61 ssb: Fix private conversation keyboard alt+navigation. #125
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-08-13 19:24:25 -04:00
f902d0374c ssb: Start to break out private messages by conversation. #125 2025-08-13 19:16:34 -04:00
b5f0a0c4f7 ssb: Add support for registering for blob added notifications similarly to messages. I want to use this to load images on the fly. 2025-08-13 18:26:42 -04:00
00623cea09 android: Recompile your app with 16 KB native library alignment. 2025-08-13 18:02:20 -04:00
25 changed files with 548 additions and 200 deletions

View File

@@ -16,9 +16,9 @@ MAKEFLAGS += --no-builtin-rules
## LD := Linker. ## LD := Linker.
## ANDROID_SDK := Path to the Android SDK. ## ANDROID_SDK := Path to the Android SDK.
VERSION_CODE := 41 VERSION_CODE := 43
VERSION_CODE_IOS := 16 VERSION_CODE_IOS := 17
VERSION_NUMBER := 0.2025.8-wip VERSION_NUMBER := 0.2025.9-wip
VERSION_NAME := This program kills fascists. VERSION_NAME := This program kills fascists.
IPHONEOS_VERSION_MIN=14.0 IPHONEOS_VERSION_MIN=14.0
@@ -253,7 +253,10 @@ $(ANDROID_TARGETS): CFLAGS += \
-fno-asynchronous-unwind-tables \ -fno-asynchronous-unwind-tables \
-funwind-tables \ -funwind-tables \
-Wno-unknown-warning-option -Wno-unknown-warning-option
$(ANDROID_TARGETS): LDFLAGS += --sysroot $(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/sysroot -fPIC $(ANDROID_TARGETS): LDFLAGS += \
--sysroot $(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/sysroot \
-Wl,-z,max-page-size=16384 \
-fPIC
$(DEBUG_TARGETS): CFLAGS += -DDEBUG -Og $(DEBUG_TARGETS): CFLAGS += -DDEBUG -Og
$(DEBUG_TARGETS): LDFLAGS += -Og $(DEBUG_TARGETS): LDFLAGS += -Og
$(RELEASE_TARGETS): CFLAGS += \ $(RELEASE_TARGETS): CFLAGS += \

View File

@@ -1,5 +1,5 @@
{ {
"type": "tildefriends-app", "type": "tildefriends-app",
"emoji": "🦀", "emoji": "🦀",
"previous": "&TTGzyovmfKozjELCGPBFLLEXQcpfaArOMmqzemvz9J8=.sha256" "previous": "&Hd6CuhhnZIf13PdFJYZBUYLYZO84WdaKvWXLC29M7Ac=.sha256"
} }

View File

@@ -21,7 +21,9 @@ class TfElement extends LitElement {
channels_latest: {type: Object}, channels_latest: {type: Object},
guest: {type: Boolean}, guest: {type: Boolean},
url: {type: String}, url: {type: String},
private_closed: {type: Object},
private_messages: {type: Array}, private_messages: {type: Array},
grouped_private_messages: {type: Object},
recent_reactions: {type: Array}, recent_reactions: {type: Array},
is_administrator: {type: Boolean}, is_administrator: {type: Boolean},
stay_connected: {type: Boolean}, stay_connected: {type: Boolean},
@@ -48,6 +50,7 @@ class TfElement extends LitElement {
this.loading_latest = 0; this.loading_latest = 0;
this.loading_latest_scheduled = 0; this.loading_latest_scheduled = 0;
this.recent_reactions = []; this.recent_reactions = [];
this.private_closed = {};
tfrpc.rpc.getBroadcasts().then((b) => { tfrpc.rpc.getBroadcasts().then((b) => {
self.broadcasts = b || []; self.broadcasts = b || [];
}); });
@@ -85,9 +88,22 @@ class TfElement extends LitElement {
this.whoami = whoami ?? (ids.length ? ids[0] : undefined); this.whoami = whoami ?? (ids.length ? ids[0] : undefined);
this.guest = !this.whoami?.length; this.guest = !this.whoami?.length;
this.ids = ids; this.ids = ids;
let private_closed =
(await tfrpc.rpc.databaseGet('private_closed')) ?? '{}';
this.private_closed = JSON.parse(private_closed);
await this.load_channels(); await this.load_channels();
} }
async close_private_chat(event) {
let update = {};
update[event.detail.key] = true;
this.private_closed = Object.assign(update, this.private_closed);
await tfrpc.rpc.databaseSet(
'private_closed',
JSON.stringify(this.private_closed)
);
}
async load_channels() { async load_channels() {
let channels = await tfrpc.rpc.query( let channels = await tfrpc.rpc.query(
` `
@@ -135,12 +151,32 @@ class TfElement extends LitElement {
} }
} }
visible_private() {
if (!this.grouped_private_messages || !this.private_closed) {
return [];
}
let self = this;
return Object.fromEntries(
Object.entries(this.grouped_private_messages).filter(([key, value]) => {
let channel = '🔐' + [...new Set(JSON.parse(key))].sort().join(',');
let grouped_latest = Math.max(...value.map((x) => x.rowid));
return (
!self.private_closed[key] ||
self.channels_unread[channel] === undefined ||
grouped_latest > self.channels_unread[channel]
);
})
);
}
next_channel(delta) { next_channel(delta) {
let channel_names = [ let channel_names = [
'', '',
'@', '@',
'👍', '👍',
'🔐', ...Object.keys(this.visible_private())
.sort()
.map((x) => '🔐' + JSON.parse(x).join(',')),
...this.channels.map((x) => '#' + x), ...this.channels.map((x) => '#' + x),
]; ];
let index = channel_names.indexOf(this.hash.substring(1)); let index = channel_names.indexOf(this.hash.substring(1));
@@ -366,6 +402,36 @@ class TfElement extends LitElement {
return result; return result;
} }
async group_private_messages(messages) {
let groups = {};
let result = await this.decrypt(
await tfrpc.rpc.query(
`
SELECT messages.rowid, messages.id, author, timestamp, json(content) AS content
FROM messages
JOIN json_each(?) AS ids
WHERE messages.id = ids.value
ORDER BY timestamp DESC
`,
[JSON.stringify(messages)]
)
);
for (let message of result) {
let key = JSON.stringify(
[
...new Set(
message?.decrypted?.recps?.filter((x) => x != this.whoami)
),
].sort() ?? []
);
if (!groups[key]) {
groups[key] = [];
}
groups[key].push(message);
}
return groups;
}
async load_channels_latest(following) { async load_channels_latest(following) {
let start_time = new Date(); let start_time = new Date();
let latest_private = this.get_latest_private(following); let latest_private = this.get_latest_private(following);
@@ -438,12 +504,15 @@ class TfElement extends LitElement {
console.log('channels took', (new Date() - start_time) / 1000.0); console.log('channels took', (new Date() - start_time) / 1000.0);
let self = this; let self = this;
start_time = new Date(); start_time = new Date();
latest_private.then(function (latest) { latest_private.then(async function (latest) {
self.channels_latest = Object.assign({}, self.channels_latest, { self.channels_latest = Object.assign({}, self.channels_latest, {
'🔐': latest[0], '🔐': latest[0],
}); });
console.log('private took', (new Date() - start_time) / 1000.0); console.log('private took', (new Date() - start_time) / 1000.0);
self.private_messages = latest[1]; self.private_messages = latest[1];
self.grouped_private_messages = await self.group_private_messages(
latest[1]
);
}); });
} }
@@ -628,8 +697,10 @@ class TfElement extends LitElement {
@refresh=${this.refresh} @refresh=${this.refresh}
@toggle_stay_connected=${this.toggle_stay_connected} @toggle_stay_connected=${this.toggle_stay_connected}
@loadmessages=${this.reset_progress} @loadmessages=${this.reset_progress}
@closeprivatechat=${this.close_private_chat}
.connections=${this.connections} .connections=${this.connections}
.private_messages=${this.private_messages} .private_messages=${this.private_messages}
.grouped_private_messages=${this.visible_private()}
.recent_reactions=${this.recent_reactions} .recent_reactions=${this.recent_reactions}
?is_administrator=${this.is_administrator} ?is_administrator=${this.is_administrator}
?stay_connected=${this.stay_connected} ?stay_connected=${this.stay_connected}

View File

@@ -16,6 +16,7 @@ class TfComposeElement extends LitElement {
author: {type: String}, author: {type: String},
channel: {type: String}, channel: {type: String},
new_thread: {type: Boolean}, new_thread: {type: Boolean},
recipients: {type: Array},
}; };
} }
@@ -91,7 +92,9 @@ class TfComposeElement extends LitElement {
bubbles: true, bubbles: true,
composed: true, composed: true,
detail: { detail: {
id: this.branch, id:
this.branch ??
(this.recipients ? this.recipients.join(',') : undefined),
draft: draft, draft: draft,
}, },
}) })
@@ -291,7 +294,7 @@ class TfComposeElement extends LitElement {
} }
} }
firstUpdated() { get_values() {
let values = Object.entries(this.users).map((x) => ({ let values = Object.entries(this.users).map((x) => ({
key: x[1].name ?? x[0], key: x[1].name ?? x[0],
value: x[0], value: x[0],
@@ -307,11 +310,15 @@ class TfComposeElement extends LitElement {
values values
); );
} }
return values;
}
firstUpdated() {
let tribute = new Tribute({ let tribute = new Tribute({
iframe: this.shadowRoot, iframe: this.shadowRoot,
collection: [ collection: [
{ {
values: values, values: this.get_values(),
selectTemplate: function (item) { selectTemplate: function (item) {
return item return item
? `[@${item.original.key}](${item.original.value})` ? `[@${item.original.key}](${item.original.value})`
@@ -330,6 +337,7 @@ class TfComposeElement extends LitElement {
], ],
}); });
tribute.attach(this.renderRoot.getElementById('edit')); tribute.attach(this.renderRoot.getElementById('edit'));
this._tribute = tribute;
} }
updated() { updated() {
@@ -340,6 +348,7 @@ class TfComposeElement extends LitElement {
preview.innerHTML = this.process_text(edit.innerText); preview.innerHTML = this.process_text(edit.innerText);
this.last_updated_text = edit.innerText; this.last_updated_text = edit.innerText;
} }
this._tribute.collection[0].values = this.get_values();
let encrypt = this.renderRoot.getElementById('encrypt_to'); let encrypt = this.renderRoot.getElementById('encrypt_to');
if (encrypt) { if (encrypt) {
let tribute = new Tribute({ let tribute = new Tribute({
@@ -496,7 +505,17 @@ class TfComposeElement extends LitElement {
} }
get_draft() { get_draft() {
return this.drafts[this.branch || ''] || {}; let key =
this.branch ||
(this.recipients ? this.recipients.join(',') : undefined) ||
'';
let draft = this.drafts[key] || {};
if (this.recipients && !draft.encrypt_to?.length) {
draft.encrypt_to = [
...new Set(this.recipients).union(new Set(draft.encrypt_to ?? [])),
];
}
return draft;
} }
update_encrypt(event) { update_encrypt(event) {

View File

@@ -349,6 +349,9 @@ class TfProfileElement extends LitElement {
${until(this.load_follows(), html`<p>Loading accounts followed...</p>`)} ${until(this.load_follows(), html`<p>Loading accounts followed...</p>`)}
<footer class="w3-container"> <footer class="w3-container">
<p> <p>
<a class="w3-button w3-theme-d1" href=${'#🔐' + (this.id != this.whoami ? this.id : '')}>
Open Private Chat
</a>
${edit} ${edit}
${follow} ${follow}
${block} ${block}

View File

@@ -43,6 +43,8 @@ const tf = css`
border-left: 4px solid #fff; border-left: 4px solid #fff;
padding: 8px; padding: 8px;
padding-left: 12px; padding-left: 12px;
margin-left: 0;
margin-right: 0;
} }
`; `;

View File

@@ -18,6 +18,7 @@ class TfTabNewsFeedElement extends LitElement {
time_range: {type: Array}, time_range: {type: Array},
time_loading: {type: Array}, time_loading: {type: Array},
private_messages: {type: Array}, private_messages: {type: Array},
grouped_private_messages: {type: Object},
recent_reactions: {type: Array}, recent_reactions: {type: Array},
}; };
} }
@@ -208,7 +209,9 @@ class TfTabNewsFeedElement extends LitElement {
console.log( console.log(
`load of ${result.length} rows took ${(t2 - t0) / 1000} (${(t1 - t0) / 1000} to find ${initial_messages.length} initial messages, ${(t2 - t1) / 1000} to find ${result.length} total messages) following=${this.following.length} st=${start_time} et=${end_time}` `load of ${result.length} rows took ${(t2 - t0) / 1000} (${(t1 - t0) / 1000} to find ${initial_messages.length} initial messages, ${(t2 - t1) / 1000} to find ${result.length} total messages) following=${this.following.length} st=${start_time} et=${end_time}`
); );
} else if (this.hash == '#🔐') { } else if (this.hash.startsWith('#🔐')) {
let ids =
this.hash == '#🔐' ? [] : this.hash.substring('#🔐'.length).split(',');
result = await tfrpc.rpc.query( result = await tfrpc.rpc.query(
` `
SELECT TRUE AS is_primary, messages.rowid, messages.id, previous, author, sequence, timestamp, hash, json(content) AS content, signature SELECT TRUE AS is_primary, messages.rowid, messages.id, previous, author, sequence, timestamp, hash, json(content) AS content, signature
@@ -220,7 +223,11 @@ class TfTabNewsFeedElement extends LitElement {
ORDER BY messages.rowid DESC LIMIT ?4 ORDER BY messages.rowid DESC LIMIT ?4
`, `,
[ [
JSON.stringify(this.private_messages), JSON.stringify(
this.grouped_private_messages?.[JSON.stringify(ids)]?.map(
(x) => x.id
) ?? []
),
start_time, start_time,
end_time, end_time,
k_max_results, k_max_results,
@@ -379,13 +386,16 @@ class TfTabNewsFeedElement extends LitElement {
let self = this; let self = this;
this.loading++; this.loading++;
let messages = []; let messages = [];
let original_hash = this.hash;
try { try {
if (this._messages_hash !== this.hash) { if (this._messages_hash !== this.hash) {
this.messages = []; this.messages = [];
this._messages_hash = this.hash; this._messages_hash = this.hash;
} }
this._messages_following = JSON.stringify(this.following); this._messages_following = JSON.stringify(this.following);
this._private_messages = JSON.stringify(this.private_messages); this._private_messages =
JSON.stringify(this.private_messages) +
JSON.stringify(this.grouped_private_messages);
let now = new Date().valueOf(); let now = new Date().valueOf();
let start_time = now - 24 * 60 * 60 * 1000; let start_time = now - 24 * 60 * 60 * 1000;
this.start_time = start_time; this.start_time = start_time;
@@ -398,7 +408,9 @@ class TfTabNewsFeedElement extends LitElement {
} finally { } finally {
this.loading--; this.loading--;
} }
if (this.hash == original_hash) {
this.messages = this.merge_messages(this.messages, messages); this.messages = this.merge_messages(this.messages, messages);
}
this.time_loading = undefined; this.time_loading = undefined;
console.log( console.log(
`loading ${messages.length} messages done for ${self.whoami} in ${(new Date() - start_time) / 1000}s` `loading ${messages.length} messages done for ${self.whoami} in ${(new Date() - start_time) / 1000}s`
@@ -424,12 +436,42 @@ class TfTabNewsFeedElement extends LitElement {
} }
} }
close_private_chat() {
this.mark_all_read();
this.dispatchEvent(
new CustomEvent('closeprivatechat', {
bubbles: true,
composed: true,
detail: {
key: JSON.stringify(
this.hash == '#🔐'
? []
: this.hash.substring('#🔐'.length).split(',')
),
},
})
);
tfrpc.rpc.setHash('#');
}
render_close_chat_button() {
if (this.hash.startsWith('#🔐')) {
return html`
<button class="w3-button w3-theme-d1" @click=${this.close_private_chat}>
Close Chat
</button>
`;
}
}
render() { render() {
if ( if (
!this.messages || !this.messages ||
this._messages_hash !== this.hash || this._messages_hash !== this.hash ||
this._messages_following !== JSON.stringify(this.following) || this._messages_following !== JSON.stringify(this.following) ||
this._private_messages !== JSON.stringify(this.private_messages) this._private_messages !==
JSON.stringify(this.private_messages) +
JSON.stringify(this.grouped_private_messages)
) { ) {
console.log( console.log(
`loading messages for ${this.whoami} (following ${this.following.length})` `loading messages for ${this.whoami} (following ${this.following.length})`
@@ -489,6 +531,7 @@ class TfTabNewsFeedElement extends LitElement {
Mark All Read Mark All Read
</button>` </button>`
: undefined} : undefined}
${this.render_close_chat_button()}
<tf-news <tf-news
id="news" id="news"
whoami=${this.whoami} whoami=${this.whoami}

View File

@@ -24,6 +24,7 @@ class TfTabNewsElement extends LitElement {
channels_latest: {type: Object}, channels_latest: {type: Object},
connections: {type: Array}, connections: {type: Array},
private_messages: {type: Array}, private_messages: {type: Array},
grouped_private_messages: {type: Object},
recent_reactions: {type: Array}, recent_reactions: {type: Array},
peer_exchange: {type: Boolean}, peer_exchange: {type: Boolean},
is_administrator: {type: Boolean}, is_administrator: {type: Boolean},
@@ -115,6 +116,19 @@ class TfTabNewsElement extends LitElement {
) { ) {
return '✉️ '; return '✉️ ';
} }
} else if (channel?.startsWith('🔐')) {
let key = JSON.stringify(channel.substring('🔐'.length).split(','));
if (this.grouped_private_messages?.[key]) {
let grouped_latest = Math.max(
...this.grouped_private_messages?.[key]?.map((x) => x.rowid)
);
if (
this.channels_unread[channel] === undefined ||
grouped_latest > this.channels_unread[channel]
) {
return '✉️ ';
}
}
} else if ( } else if (
this.channels_latest[channel] && this.channels_latest[channel] &&
this.channels_latest[channel] > 0 && this.channels_latest[channel] > 0 &&
@@ -257,12 +271,29 @@ class TfTabNewsElement extends LitElement {
style=${this.hash == '#👍' ? 'font-weight: bold' : undefined} style=${this.hash == '#👍' ? 'font-weight: bold' : undefined}
>${this.unread_status('👍')}👍votes</a >${this.unread_status('👍')}👍votes</a
> >
${Object.keys(this?.grouped_private_messages ?? [])
?.sort()
?.map(
(key) => html`
<a <a
href="#🔐" href=${'#🔐' + JSON.parse(key).join(',')}
class="w3-bar-item w3-button" class="w3-bar-item w3-button"
style=${this.hash == '#🔐' ? 'font-weight: bold' : undefined} style=${this.hash == '#🔐' + JSON.parse(key).join(',')
>${this.unread_status('🔐')}🔐private</a ? 'font-weight: bold'
: undefined}
>${this.unread_status('🔐' + JSON.parse(key).join(','))}
${(key != '[]' ? JSON.parse(key) : [this.whoami]).map(
(id) => html`
<tf-user
id=${id}
nolink="true"
.users=${this.users}
></tf-user>
`
)}</a
> >
`
)}
${Object.keys(this.drafts) ${Object.keys(this.drafts)
.sort() .sort()
.map( .map(
@@ -418,6 +449,9 @@ class TfTabNewsElement extends LitElement {
.drafts=${this.drafts} .drafts=${this.drafts}
@tf-draft=${this.draft} @tf-draft=${this.draft}
.channel=${this.channel()} .channel=${this.channel()}
.recipients=${this.hash.startsWith('#🔐')
? this.hash.substring('#🔐'.length).split(',')
: undefined}
></tf-compose> ></tf-compose>
</div> </div>
${profile} ${profile}
@@ -434,6 +468,7 @@ class TfTabNewsElement extends LitElement {
.channels_unread=${this.channels_unread} .channels_unread=${this.channels_unread}
.channels_latest=${this.channels_latest} .channels_latest=${this.channels_latest}
.private_messages=${this.private_messages} .private_messages=${this.private_messages}
.grouped_private_messages=${this.grouped_private_messages}
.recent_reactions=${this.recent_reactions} .recent_reactions=${this.recent_reactions}
></tf-tab-news-feed> ></tf-tab-news-feed>
</div> </div>

View File

@@ -9,6 +9,7 @@ class TfUserElement extends LitElement {
fallback_name: {type: String}, fallback_name: {type: String},
icon_only: {type: Boolean}, icon_only: {type: Boolean},
users: {type: Object}, users: {type: Object},
nolink: {type: Boolean},
}; };
} }
@@ -37,7 +38,9 @@ class TfUserElement extends LitElement {
let name_string = name ?? this.fallback_name ?? this.id; let name_string = name ?? this.fallback_name ?? this.id;
name = this.icon_only name = this.icon_only
? undefined ? undefined
: html`<a target="_top" href=${'#' + this.id}>${name_string}</a>`; : !this.nolink
? html`<a target="_top" href=${'#' + this.id}>${name_string}</a>`
: html`<span>${name_string}</span>`;
if (user) { if (user) {
let image_link = user.image; let image_link = user.image;
@@ -56,7 +59,8 @@ class TfUserElement extends LitElement {
} }
} }
return html` <div return html` <div
style="display: inline-block; vertical-align: middle; font-weight: bold; text-wrap: nowrap; max-width: 100%; overflow: hidden; text-overflow: ellipsis" style=${'display: inline-block; vertical-align: middle; text-wrap: nowrap; max-width: 100%; overflow: hidden; text-overflow: ellipsis' +
(this.nolink ? '' : '; font-weight: bold')}
> >
${image} ${name} ${image} ${name}
</div>`; </div>`;

View File

@@ -25,14 +25,14 @@
}: }:
pkgs.stdenv.mkDerivation rec { pkgs.stdenv.mkDerivation rec {
pname = "tildefriends"; pname = "tildefriends";
version = "0.0.33"; version = "0.2025.8";
src = pkgs.fetchFromGitea { src = pkgs.fetchFromGitea {
domain = "dev.tildefriends.net"; domain = "dev.tildefriends.net";
owner = "cory"; owner = "cory";
repo = "tildefriends"; repo = "tildefriends";
rev = "v${version}"; rev = "v${version}";
hash = "sha256-9D28gmaBTRVyXhY3zZd/W9PsXA1YZt/K69hz41aVP04="; hash = "sha256-N/5lp8RL19B6Z43kRxx7c01WVJkK44a/wwNgRJPk5uI=";
fetchSubmodules = true; fetchSubmodules = true;
}; };

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

@@ -98,9 +98,9 @@
} }
}, },
"node_modules/@codemirror/language": { "node_modules/@codemirror/language": {
"version": "6.11.2", "version": "6.11.3",
"resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.11.2.tgz", "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.11.3.tgz",
"integrity": "sha512-p44TsNArL4IVXDTbapUmEkAlvWs2CFQbcfc0ymDsis1kH2wh0gcY96AS29c/vp2d0y2Tquk1EDSaawpzilUiAw==", "integrity": "sha512-9HBM2XnwDj7fnu0551HkGdrUrrqmYq/WC5iv6nbY2WdicXdGbhR/gfbZOH73Aqj4351alY1+aoG9rCNfiwS1RA==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@codemirror/state": "^6.0.0", "@codemirror/state": "^6.0.0",
@@ -167,9 +167,9 @@
} }
}, },
"node_modules/@jridgewell/gen-mapping": { "node_modules/@jridgewell/gen-mapping": {
"version": "0.3.12", "version": "0.3.13",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
"integrity": "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==", "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -188,9 +188,9 @@
} }
}, },
"node_modules/@jridgewell/source-map": { "node_modules/@jridgewell/source-map": {
"version": "0.3.10", "version": "0.3.11",
"resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.10.tgz", "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz",
"integrity": "sha512-0pPkgz9dY+bijgistcTTJ5mR+ocqRXLuhXHYdzoMmmoJ2C9S46RCm2GMUbatPEUK9Yjy26IrAy8D/M00lLkv+Q==", "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -199,16 +199,16 @@
} }
}, },
"node_modules/@jridgewell/sourcemap-codec": { "node_modules/@jridgewell/sourcemap-codec": {
"version": "1.5.4", "version": "1.5.5",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
"integrity": "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==", "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/@jridgewell/trace-mapping": { "node_modules/@jridgewell/trace-mapping": {
"version": "0.3.29", "version": "0.3.30",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.30.tgz",
"integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==", "integrity": "sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -360,9 +360,9 @@
} }
}, },
"node_modules/@rollup/rollup-android-arm-eabi": { "node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.46.0", "version": "4.46.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.46.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.46.4.tgz",
"integrity": "sha512-9f3nSTFI2ivfxc7/tHBHcJ8pRnp8ROrELvsVprlQPVvcZ+j5zztYd+PTJGpyIOAdTvNwNrpCXswKSeoQcyGjMQ==", "integrity": "sha512-B2wfzCJ+ps/OBzRjeds7DlJumCU3rXMxJJS1vzURyj7+KBHGONm7c9q1TfdBl4vCuNMkDvARn3PBl2wZzuR5mw==",
"cpu": [ "cpu": [
"arm" "arm"
], ],
@@ -373,9 +373,9 @@
] ]
}, },
"node_modules/@rollup/rollup-android-arm64": { "node_modules/@rollup/rollup-android-arm64": {
"version": "4.46.0", "version": "4.46.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.46.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.46.4.tgz",
"integrity": "sha512-tFZSEhqJ8Yrpe50TzOdeoYi72gi/jsnT7y8Qrozf3cNu28WX+s6I3XzEPUAqoaT9SAS8Xz9AzGTFlxxCH/w20w==", "integrity": "sha512-FGJYXvYdn8Bs6lAlBZYT5n+4x0ciEp4cmttsvKAZc/c8/JiPaQK8u0c/86vKX8lA7OY/+37lIQSe0YoAImvBAA==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -386,9 +386,9 @@
] ]
}, },
"node_modules/@rollup/rollup-darwin-arm64": { "node_modules/@rollup/rollup-darwin-arm64": {
"version": "4.46.0", "version": "4.46.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.46.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.46.4.tgz",
"integrity": "sha512-+DikIIs+p6yU2hF51UaWG8BnHbq90X0QIOt5zqSKSZxY+G3qqdLih214e9InJal21af2PuuxkDectetGfbVPJw==", "integrity": "sha512-/9qwE/BM7ATw/W/OFEMTm3dmywbJyLQb4f4v5nmOjgYxPIGpw7HaxRi6LnD4Pjn/q7k55FGeHe1/OD02w63apA==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -399,9 +399,9 @@
] ]
}, },
"node_modules/@rollup/rollup-darwin-x64": { "node_modules/@rollup/rollup-darwin-x64": {
"version": "4.46.0", "version": "4.46.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.46.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.46.4.tgz",
"integrity": "sha512-5a+NofhdEB/WimSlFMskbFQn1vqz1FWryYpA99trmZGO6qEmiS0IsX6w4B3d91U878Q2ZQdiaFF1gxX4P147og==", "integrity": "sha512-QkWfNbeRuzFnv2d0aPlrzcA3Ebq2mE8kX/5Pl7VdRShbPBjSnom7dbT8E3Jmhxo2RL784hyqGvR5KHavCJQciw==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -412,9 +412,9 @@
] ]
}, },
"node_modules/@rollup/rollup-freebsd-arm64": { "node_modules/@rollup/rollup-freebsd-arm64": {
"version": "4.46.0", "version": "4.46.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.46.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.46.4.tgz",
"integrity": "sha512-igr/RlKPS3OCy4jD3XBmAmo3UAcNZkJSubRsw1JeM8bAbwf15k/3eMZXD91bnjheijJiOJcga3kfCLKjV8IXNg==", "integrity": "sha512-+ToyOMYnSfV8D+ckxO6NthPln/PDNp1P6INcNypfZ7muLmEvPKXqduUiD8DlJpMMT8LxHcE5W0dK9kXfJke9Zw==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -425,9 +425,9 @@
] ]
}, },
"node_modules/@rollup/rollup-freebsd-x64": { "node_modules/@rollup/rollup-freebsd-x64": {
"version": "4.46.0", "version": "4.46.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.46.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.46.4.tgz",
"integrity": "sha512-MdigWzPSHlQzB1xZ+MdFDWTAH+kcn7UxjEBoOKuaso7z1DRlnAnrknB1mTtNOQ+GdPI8xgExAGwHeqQjntR0Cg==", "integrity": "sha512-cGT6ey/W+sje6zywbLiqmkfkO210FgRz7tepWAzzEVgQU8Hn91JJmQWNqs55IuglG8sJdzk7XfNgmGRtcYlo1w==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -438,9 +438,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-arm-gnueabihf": { "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
"version": "4.46.0", "version": "4.46.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.46.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.46.4.tgz",
"integrity": "sha512-dmZseE0ZwA/4yy1+BwFrDqFTjjNg24GO9xSrb1weVbt6AFkhp5pz1gVS7IMtfIvoWy8yp6q/zN0bKnefRUImvQ==", "integrity": "sha512-9fhTJyOb275w5RofPSl8lpr4jFowd+H4oQKJ9XTYzD1JWgxdZKE8bA6d4npuiMemkecQOcigX01FNZNCYnQBdA==",
"cpu": [ "cpu": [
"arm" "arm"
], ],
@@ -451,9 +451,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-arm-musleabihf": { "node_modules/@rollup/rollup-linux-arm-musleabihf": {
"version": "4.46.0", "version": "4.46.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.46.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.46.4.tgz",
"integrity": "sha512-fzhfn6p9Cfm3W8UrWKIa4l7Wfjs/KGdgaswMBBE3KY3Ta43jg2XsPrAtfezHpsRk0Nx+TFuS3hZk/To2N5kFPQ==", "integrity": "sha512-+6kCIM5Zjvz2HwPl/udgVs07tPMIp1VU2Y0c72ezjOvSvEfAIWsUgpcSDvnC7g9NrjYR6X9bZT92mZZ90TfvXw==",
"cpu": [ "cpu": [
"arm" "arm"
], ],
@@ -464,9 +464,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-arm64-gnu": { "node_modules/@rollup/rollup-linux-arm64-gnu": {
"version": "4.46.0", "version": "4.46.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.46.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.46.4.tgz",
"integrity": "sha512-vVDD+iPDPmJQ5nAQ5Tifq3ywdv60FartglFI8VOCK+hcU9aoG0qlQTsDJP97O5yiTaTqlneZWoARMcVC5nyUoQ==", "integrity": "sha512-SWuXdnsayCZL4lXoo6jn0yyAj7TTjWE4NwDVt9s7cmu6poMhtiras5c8h6Ih6Y0Zk6Z+8t/mLumvpdSPTWub2Q==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -477,9 +477,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-arm64-musl": { "node_modules/@rollup/rollup-linux-arm64-musl": {
"version": "4.46.0", "version": "4.46.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.46.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.46.4.tgz",
"integrity": "sha512-0d0jx08fzDHCzXqrtCMEEyxKU0SvJrWmUjUDE2/KDQ2UDJql0tfiwYvEx1oHELClKO8CNdE+AGJj+RqXscZpdQ==", "integrity": "sha512-vDknMDqtMhrrroa5kyX6tuC0aRZZlQ+ipDfbXd2YGz5HeV2t8HOl/FDAd2ynhs7Ki5VooWiiZcCtxiZ4IjqZwQ==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -490,9 +490,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-loongarch64-gnu": { "node_modules/@rollup/rollup-linux-loongarch64-gnu": {
"version": "4.46.0", "version": "4.46.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.46.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.46.4.tgz",
"integrity": "sha512-XBYu9oW9eKJadWn8M7hkTZsD4yG+RrsTrVEgyKwb4L72cpJjRbRboTG9Lg9fec8MxJp/cfTHAocg4mnismQR8A==", "integrity": "sha512-mCBkjRZWhvjtl/x+Bd4fQkWZT8canStKDxGrHlBiTnZmJnWygGcvBylzLVCZXka4dco5ymkWhZlLwKCGFF4ivw==",
"cpu": [ "cpu": [
"loong64" "loong64"
], ],
@@ -503,9 +503,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-ppc64-gnu": { "node_modules/@rollup/rollup-linux-ppc64-gnu": {
"version": "4.46.0", "version": "4.46.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.46.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.46.4.tgz",
"integrity": "sha512-wJaRvcT17PoOK6Ggcfo3nouFlybHvARBS4jzT0PC/lg17fIJHcDS2fZz3sD+iA4nRlho2zE6OGbU0HvwATdokQ==", "integrity": "sha512-YMdz2phOTFF+Z66dQfGf0gmeDSi5DJzY5bpZyeg9CPBkV9QDzJ1yFRlmi/j7WWRf3hYIWrOaJj5jsfwgc8GTHQ==",
"cpu": [ "cpu": [
"ppc64" "ppc64"
], ],
@@ -516,9 +516,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-riscv64-gnu": { "node_modules/@rollup/rollup-linux-riscv64-gnu": {
"version": "4.46.0", "version": "4.46.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.46.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.46.4.tgz",
"integrity": "sha512-GZ5bkMFteAGkcmh8x0Ok4LSa+L62Ez0tMsHPX6JtR0wl4Xc3bQcrFHDiR5DGLEDFtGrXih4Nd/UDaFqs968/wA==", "integrity": "sha512-r0WKLSfFAK8ucG024v2yiLSJMedoWvk8yWqfNICX28NHDGeu3F/wBf8KG6mclghx4FsLePxJr/9N8rIj1PtCnw==",
"cpu": [ "cpu": [
"riscv64" "riscv64"
], ],
@@ -529,9 +529,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-riscv64-musl": { "node_modules/@rollup/rollup-linux-riscv64-musl": {
"version": "4.46.0", "version": "4.46.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.46.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.46.4.tgz",
"integrity": "sha512-7CjPw6FflFsVOUfWOrVrREiV3IYXG4RzZ1ZQUaT3BtSK8YXN6x286o+sruPZJESIaPebYuFowmg54ZdrkVBYog==", "integrity": "sha512-IaizpPP2UQU3MNyPH1u0Xxbm73D+4OupL0bjo4Hm0496e2wg3zuvoAIhubkD1NGy9fXILEExPQy87mweujEatA==",
"cpu": [ "cpu": [
"riscv64" "riscv64"
], ],
@@ -542,9 +542,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-s390x-gnu": { "node_modules/@rollup/rollup-linux-s390x-gnu": {
"version": "4.46.0", "version": "4.46.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.46.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.46.4.tgz",
"integrity": "sha512-nmvnl0ZiuysltcB/cKjUh40Rx4FbSyueERDsl2FLvLYr6pCgSsvGr3SocUT84svSpmloS7f1DRWqtRha74Gi1w==", "integrity": "sha512-aCM29orANR0a8wk896p6UEgIfupReupnmISz6SUwMIwTGaTI8MuKdE0OD2LvEg8ondDyZdMvnaN3bW4nFbATPA==",
"cpu": [ "cpu": [
"s390x" "s390x"
], ],
@@ -555,9 +555,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-x64-gnu": { "node_modules/@rollup/rollup-linux-x64-gnu": {
"version": "4.46.0", "version": "4.46.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.46.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.46.4.tgz",
"integrity": "sha512-Cv+moII5C8RM6gZbR3cb21o6rquVDZrN2o81maROg1LFzBz2dZUwIQSxFA8GtGZ/F2KtsqQ2z3eFPBb6akvQNg==", "integrity": "sha512-0Xj1vZE3cbr/wda8d/m+UeuSL+TDpuozzdD4QaSzu/xSOMK0Su5RhIkF7KVHFQsobemUNHPLEcYllL7ZTCP/Cg==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -568,9 +568,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-x64-musl": { "node_modules/@rollup/rollup-linux-x64-musl": {
"version": "4.46.0", "version": "4.46.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.46.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.46.4.tgz",
"integrity": "sha512-PHcMG8DZTM9RCIjp8QIfN0VYtX0TtBPnWOTRurFhoCDoi9zptUZL2k7pCs+5rgut7JAiUsYy+huyhVKPcmxoog==", "integrity": "sha512-kM/orjpolfA5yxsx84kI6bnK47AAZuWxglGKcNmokw2yy9i5eHY5UAjcX45jemTJnfHAWo3/hOoRqEeeTdL5hw==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -581,9 +581,9 @@
] ]
}, },
"node_modules/@rollup/rollup-win32-arm64-msvc": { "node_modules/@rollup/rollup-win32-arm64-msvc": {
"version": "4.46.0", "version": "4.46.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.46.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.46.4.tgz",
"integrity": "sha512-1SI/Rd47e8aQJeFWMDg16ET+fjvCcD/CzeaRmIEPmb05hx+3cCcwIF4ebUag4yTt/D1peE+Mgp0+Po3M358cAA==", "integrity": "sha512-cNLH4psMEsWKILW0isbpQA2OvjXLbKvnkcJFmqAptPQbtLrobiapBJVj6RoIvg6UXVp5w0wnIfd/Q56cNpF+Ew==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -594,9 +594,9 @@
] ]
}, },
"node_modules/@rollup/rollup-win32-ia32-msvc": { "node_modules/@rollup/rollup-win32-ia32-msvc": {
"version": "4.46.0", "version": "4.46.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.46.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.46.4.tgz",
"integrity": "sha512-JwOCYxmumFDfDhx4kNyz6kTVK3gWzBIvVdMNzQMRDubcoGRDniOOmo6DDNP42qwZx3Bp9/6vWJ+kNzNqXoHmeA==", "integrity": "sha512-OiEa5lRhiANpv4SfwYVgQ3opYWi/QmPDC5ve21m8G9pf6ZO+aX1g2EEF1/IFaM1xPSP7mK0msTRXlPs6mIagkg==",
"cpu": [ "cpu": [
"ia32" "ia32"
], ],
@@ -607,9 +607,9 @@
] ]
}, },
"node_modules/@rollup/rollup-win32-x64-msvc": { "node_modules/@rollup/rollup-win32-x64-msvc": {
"version": "4.46.0", "version": "4.46.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.46.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.46.4.tgz",
"integrity": "sha512-IPMIfrfkG1GaEXi+JSsQEx8x9b4b+hRZXO7KYc2pKio3zO2/VDXDs6B9Ts/nnO+25Fk1tdAVtUn60HKKPPzDig==", "integrity": "sha512-IKL9mewGZ5UuuX4NQlwOmxPyqielvkAPUS2s1cl6yWjjQvyN3h5JTdVFGD5Jr5xMjRC8setOfGQDVgX8V+dkjg==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -799,9 +799,9 @@
} }
}, },
"node_modules/rollup": { "node_modules/rollup": {
"version": "4.46.0", "version": "4.46.4",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.46.0.tgz", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.46.4.tgz",
"integrity": "sha512-ONmkT3Ud3IfW15nl7l4qAZko5/2iZ5ALVBDh02ZSZ5IGVLJSYkRcRa3iB58VyEIyoofs9m2xdVrm+lTi97+3pw==", "integrity": "sha512-YbxoxvoqNg9zAmw4+vzh1FkGAiZRK+LhnSrbSrSXMdZYsRPDWoshcSd/pldKRO6lWzv/e9TiJAVQyirYIeSIPQ==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@types/estree": "1.0.8" "@types/estree": "1.0.8"
@@ -814,26 +814,26 @@
"npm": ">=8.0.0" "npm": ">=8.0.0"
}, },
"optionalDependencies": { "optionalDependencies": {
"@rollup/rollup-android-arm-eabi": "4.46.0", "@rollup/rollup-android-arm-eabi": "4.46.4",
"@rollup/rollup-android-arm64": "4.46.0", "@rollup/rollup-android-arm64": "4.46.4",
"@rollup/rollup-darwin-arm64": "4.46.0", "@rollup/rollup-darwin-arm64": "4.46.4",
"@rollup/rollup-darwin-x64": "4.46.0", "@rollup/rollup-darwin-x64": "4.46.4",
"@rollup/rollup-freebsd-arm64": "4.46.0", "@rollup/rollup-freebsd-arm64": "4.46.4",
"@rollup/rollup-freebsd-x64": "4.46.0", "@rollup/rollup-freebsd-x64": "4.46.4",
"@rollup/rollup-linux-arm-gnueabihf": "4.46.0", "@rollup/rollup-linux-arm-gnueabihf": "4.46.4",
"@rollup/rollup-linux-arm-musleabihf": "4.46.0", "@rollup/rollup-linux-arm-musleabihf": "4.46.4",
"@rollup/rollup-linux-arm64-gnu": "4.46.0", "@rollup/rollup-linux-arm64-gnu": "4.46.4",
"@rollup/rollup-linux-arm64-musl": "4.46.0", "@rollup/rollup-linux-arm64-musl": "4.46.4",
"@rollup/rollup-linux-loongarch64-gnu": "4.46.0", "@rollup/rollup-linux-loongarch64-gnu": "4.46.4",
"@rollup/rollup-linux-ppc64-gnu": "4.46.0", "@rollup/rollup-linux-ppc64-gnu": "4.46.4",
"@rollup/rollup-linux-riscv64-gnu": "4.46.0", "@rollup/rollup-linux-riscv64-gnu": "4.46.4",
"@rollup/rollup-linux-riscv64-musl": "4.46.0", "@rollup/rollup-linux-riscv64-musl": "4.46.4",
"@rollup/rollup-linux-s390x-gnu": "4.46.0", "@rollup/rollup-linux-s390x-gnu": "4.46.4",
"@rollup/rollup-linux-x64-gnu": "4.46.0", "@rollup/rollup-linux-x64-gnu": "4.46.4",
"@rollup/rollup-linux-x64-musl": "4.46.0", "@rollup/rollup-linux-x64-musl": "4.46.4",
"@rollup/rollup-win32-arm64-msvc": "4.46.0", "@rollup/rollup-win32-arm64-msvc": "4.46.4",
"@rollup/rollup-win32-ia32-msvc": "4.46.0", "@rollup/rollup-win32-ia32-msvc": "4.46.4",
"@rollup/rollup-win32-x64-msvc": "4.46.0", "@rollup/rollup-win32-x64-msvc": "4.46.4",
"fsevents": "~2.3.2" "fsevents": "~2.3.2"
} }
}, },

6
flake.lock generated
View File

@@ -20,11 +20,11 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1753749649, "lastModified": 1756217674,
"narHash": "sha256-+jkEZxs7bfOKfBIk430K+tK9IvXlwzqQQnppC2ZKFj4=", "narHash": "sha256-TH1SfSP523QI7kcPiNtMAEuwZR3Jdz0MCDXPs7TS8uo=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "1f08a4df998e21f4e8be8fb6fbf61d11a1a5076a", "rev": "4e7667a90c167f7a81d906e5a75cba4ad8bee620",
"type": "github" "type": "github"
}, },
"original": { "original": {

View File

@@ -0,0 +1,9 @@
* Private messages interface overhaul in progress.
* Added a loading indicator.
* Documented the core JavaScript.
* Fixed @-completion.
* Covered up launch on Android with the splash screen.
* Update:
* CodeMirror
* OpenSSL 3.5.2
* speedscope 1.23.1

View File

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

View File

@@ -19,12 +19,12 @@ import android.os.RemoteException;
import android.os.StrictMode; import android.os.StrictMode;
import android.text.InputType; import android.text.InputType;
import android.util.Base64; import android.util.Base64;
import android.util.Log;
import android.view.KeyEvent; import android.view.KeyEvent;
import android.view.MotionEvent; import android.view.MotionEvent;
import android.view.View; import android.view.View;
import android.view.ViewGroup.LayoutParams; import android.view.ViewGroup.LayoutParams;
import android.view.Window; import android.view.Window;
import android.view.ViewTreeObserver;
import android.webkit.CookieManager; import android.webkit.CookieManager;
import android.webkit.DownloadListener; import android.webkit.DownloadListener;
import android.webkit.JsPromptResult; import android.webkit.JsPromptResult;
@@ -43,6 +43,7 @@ import java.io.File;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.FileReader; import java.io.FileReader;
import java.io.OutputStream; import java.io.OutputStream;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
public class TildeFriendsActivity extends Activity { public class TildeFriendsActivity extends Activity {
@@ -52,28 +53,41 @@ public class TildeFriendsActivity extends Activity {
String port_file_path; String port_file_path;
Thread create_thread; Thread create_thread;
Thread server_thread; Thread server_thread;
Thread log_thread;
ServiceConnection service_connection; ServiceConnection service_connection;
FileObserver observer; FileObserver observer;
LinkedBlockingQueue<String> log_queue = new LinkedBlockingQueue<String>();
private ValueCallback<Uri[]> upload_message; private ValueCallback<Uri[]> upload_message;
private final static int FILECHOOSER_RESULT = 1; private final static int FILECHOOSER_RESULT = 1;
private float touch_down_y; private float touch_down_y;
private boolean ready = false;
private boolean loaded = false;
static { static {
Log.w("tildefriends", "Calling system.loadLibrary()."); log("Calling system.loadLibrary().");
System.loadLibrary("tildefriends"); System.loadLibrary("tildefriends");
Log.w("tildefriends", "system.loadLibrary() completed."); log("system.loadLibrary() completed.");
} }
public static native int tf_server_main(String files_dir, String apk_path, String out_port_file_path, ConnectivityManager connectivity_manager); public static native int tf_server_main(String files_dir, String apk_path, String out_port_file_path, ConnectivityManager connectivity_manager);
public static native int tf_sandbox_main(int pipe_fd); public static native int tf_sandbox_main(int pipe_fd);
public static void log(String message) {
if (s_activity != null && s_activity.log_queue != null && message != null) {
try {
s_activity.log_queue.put(message);
} catch (InterruptedException e) {
android.util.Log.w("tildefriends", message);
}
}
}
private void createThread() { private void createThread() {
web_view = (TildeFriendsWebView)findViewById(R.id.web); web_view = (TildeFriendsWebView)findViewById(R.id.web);
set_status("Extracting executable..."); log(String.format("getFilesDir() is %s", getFilesDir().toString()));
Log.w("tildefriends", String.format("getFilesDir() is %s", getFilesDir().toString())); log(String.format("getPackageResourcePath() is %s", getPackageResourcePath().toString()));
Log.w("tildefriends", String.format("getPackageResourcePath() is %s", getPackageResourcePath().toString())); log(String.format("nativeLibraryDir is %s", getApplicationInfo().nativeLibraryDir));
Log.w("tildefriends", String.format("nativeLibraryDir is %s", getApplicationInfo().nativeLibraryDir));
port_file_path = getFilesDir().toString() + "/port.txt"; port_file_path = getFilesDir().toString() + "/port.txt";
new File(port_file_path).delete(); new File(port_file_path).delete();
@@ -81,21 +95,20 @@ public class TildeFriendsActivity extends Activity {
TildeFriendsActivity activity = this; TildeFriendsActivity activity = this;
set_status("Starting server...");
server_thread = new Thread(new Runnable() { server_thread = new Thread(new Runnable() {
@Override @Override
public void run() { public void run() {
Log.w("tildefriends", "Watching for changes in: " + getFilesDir().toString()); log("Watching for changes in: " + getFilesDir().toString());
observer = make_file_observer(getFilesDir().toString(), port_file_path); observer = make_file_observer(getFilesDir().toString(), port_file_path);
observer.startWatching(); observer.startWatching();
Log.w("tildefriends", "Calling tf_server_main."); log("Calling tf_server_main.");
int result = tf_server_main( int result = tf_server_main(
getFilesDir().toString(), getFilesDir().toString(),
getPackageResourcePath().toString(), getPackageResourcePath().toString(),
port_file_path, port_file_path,
(ConnectivityManager)getApplicationContext().getSystemService(CONNECTIVITY_SERVICE)); (ConnectivityManager)getApplicationContext().getSystemService(CONNECTIVITY_SERVICE));
Log.w("tildefriends", "tf_server_main returned " + result + "."); log("tf_server_main returned " + result + ".");
} }
}); });
server_thread.start(); server_thread.start();
@@ -109,17 +122,17 @@ public class TildeFriendsActivity extends Activity {
web_view.setDownloadListener(new DownloadListener() { web_view.setDownloadListener(new DownloadListener() {
public void onDownloadStart(String url, String userAgent, String content_disposition, String mime_type, long content_length) { public void onDownloadStart(String url, String userAgent, String content_disposition, String mime_type, long content_length) {
Log.w("tildefriends", "Let's download: " + url + " (" + content_disposition + ")"); log("Let's download: " + url + " (" + content_disposition + ")");
String file_name = URLUtil.guessFileName(url, content_disposition, mime_type); String file_name = URLUtil.guessFileName(url, content_disposition, mime_type);
if (url.startsWith("data:") && url.indexOf(',') != -1) { if (url.startsWith("data:") && url.indexOf(',') != -1) {
String b64 = url.substring(url.indexOf(',') + 1); String b64 = url.substring(url.indexOf(',') + 1);
byte[] data = Base64.decode(b64, Base64.DEFAULT); byte[] data = Base64.decode(b64, Base64.DEFAULT);
Log.w("tildefriends", "Downloaded " + data.length + " bytes."); log("Downloaded " + data.length + " bytes.");
File path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS); File path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
try (OutputStream stream = new FileOutputStream(new File(path, file_name))) { try (OutputStream stream = new FileOutputStream(new File(path, file_name))) {
stream.write(data); stream.write(data);
} catch (java.io.IOException e) { } catch (java.io.IOException e) {
Log.w("tildefriends", "IOException: " + e.toString()); log("IOException: " + e.toString());
} }
Toast.makeText(getApplicationContext(), "Downloaded File", Toast.LENGTH_LONG).show(); Toast.makeText(getApplicationContext(), "Downloaded File", Toast.LENGTH_LONG).show();
} else { } else {
@@ -227,12 +240,13 @@ public class TildeFriendsActivity extends Activity {
@Override @Override
public boolean onConsoleMessage(android.webkit.ConsoleMessage consoleMessage) { public boolean onConsoleMessage(android.webkit.ConsoleMessage consoleMessage) {
Log.d("tildefriends", consoleMessage.message() + " -- From line " + consoleMessage.lineNumber() + " of " + consoleMessage.sourceId()); log(consoleMessage.message() + " -- From line " + consoleMessage.lineNumber() + " of " + consoleMessage.sourceId());
return true; return true;
} }
}); });
web_view.setWebViewClient(new WebViewClient() { web_view.setWebViewClient(new WebViewClient() {
@Override
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request)
{ {
if (request.getUrl() != null && request.getUrl().toString().startsWith(base_url)) { if (request.getUrl() != null && request.getUrl().toString().startsWith(base_url)) {
@@ -242,6 +256,11 @@ public class TildeFriendsActivity extends Activity {
return true; return true;
} }
} }
@Override
public void onPageFinished(WebView view, String url) {
s_activity.loaded = true;
}
}); });
}); });
@@ -252,6 +271,23 @@ public class TildeFriendsActivity extends Activity {
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
s_activity = this; s_activity = this;
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
log_thread = new Thread(new Runnable() {
@Override
public void run() {
while (true) {
try {
String message = log_queue.take();
if (message != null) {
android.util.Log.w("tildefriends", message);
} else {
break;
}
} catch (InterruptedException e) {
}
}
}
});
log_thread.start();
StrictMode.setThreadPolicy( StrictMode.setThreadPolicy(
new StrictMode.ThreadPolicy.Builder() new StrictMode.ThreadPolicy.Builder()
.detectAll() .detectAll()
@@ -271,6 +307,21 @@ public class TildeFriendsActivity extends Activity {
refresh.setVisibility(View.GONE); refresh.setVisibility(View.GONE);
refresh.setText("REFRESH"); refresh.setText("REFRESH");
final View content = findViewById(android.R.id.content);
content.getViewTreeObserver().addOnPreDrawListener(
new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
if (s_activity.ready && s_activity.loaded) {
content.getViewTreeObserver().removeOnPreDrawListener(this);
return true;
} else {
return false;
}
}
}
);
create_thread = new Thread(new Runnable() { create_thread = new Thread(new Runnable() {
@Override @Override
public void run() { public void run() {
@@ -283,8 +334,16 @@ public class TildeFriendsActivity extends Activity {
@Override @Override
protected void onDestroy() protected void onDestroy()
{ {
super.onDestroy(); try {
if (log_queue != null) {
log_queue.put(null);
}
log_thread.join();
} catch (InterruptedException e) {
}
log_thread = null;
s_activity = null; s_activity = null;
super.onDestroy();
} }
@Override @Override
@@ -376,46 +435,33 @@ public class TildeFriendsActivity extends Activity {
return -1; return -1;
} }
private void set_status(String text) {
TextView text_view = (TextView)findViewById(R.id.text);
web_view.setVisibility(View.GONE);
text_view.setVisibility(View.VISIBLE);
text_view.setText(text);
}
private void hide_status() {
TextView text_view = (TextView)findViewById(R.id.text);
web_view.setVisibility(View.VISIBLE);
text_view.setVisibility(View.GONE);
}
public static void start_sandbox(int pipe_fd) { public static void start_sandbox(int pipe_fd) {
Log.w("tildefriends", "starting service with fd: " + pipe_fd); log("starting service with fd: " + pipe_fd);
Intent intent = new Intent(s_activity, TildeFriendsSandboxService.class); Intent intent = new Intent(s_activity, TildeFriendsSandboxService.class);
s_activity.service_connection = new ServiceConnection() { s_activity.service_connection = new ServiceConnection() {
@Override @Override
public void onBindingDied(ComponentName name) { public void onBindingDied(ComponentName name) {
Log.w("tildefriends", "onBindingDied"); log("onBindingDied");
} }
@Override @Override
public void onNullBinding(ComponentName name) { public void onNullBinding(ComponentName name) {
Log.w("tildefriends", "onNullBinding"); log("onNullBinding");
} }
@Override @Override
public void onServiceConnected(ComponentName name, IBinder binder) { public void onServiceConnected(ComponentName name, IBinder binder) {
Log.w("tildefriends", "onServiceConnected"); log("onServiceConnected");
Parcel data = Parcel.obtain(); Parcel data = Parcel.obtain();
try (ParcelFileDescriptor pfd = ParcelFileDescriptor.fromFd(pipe_fd)) { try (ParcelFileDescriptor pfd = ParcelFileDescriptor.fromFd(pipe_fd)) {
data.writeParcelable(pfd, 0); data.writeParcelable(pfd, 0);
} catch (java.io.IOException e) { } catch (java.io.IOException e) {
Log.w("tildefriends", "IOException: " + e); log("IOException: " + e);
} }
try { try {
binder.transact(TildeFriendsSandboxService.START_CALL, data, null, IBinder.FLAG_ONEWAY); binder.transact(TildeFriendsSandboxService.START_CALL, data, null, IBinder.FLAG_ONEWAY);
} catch (RemoteException e) { } catch (RemoteException e) {
Log.w("tildefriends", "RemoteException"); log("RemoteException");
} finally { } finally {
data.recycle(); data.recycle();
} }
@@ -423,14 +469,14 @@ public class TildeFriendsActivity extends Activity {
@Override @Override
public void onServiceDisconnected(ComponentName name) { public void onServiceDisconnected(ComponentName name) {
Log.w("tildefriends", "onServiceDisconnected"); log("onServiceDisconnected");
} }
}; };
s_activity.bindService(intent, s_activity.service_connection, BIND_AUTO_CREATE | BIND_NOT_FOREGROUND); s_activity.bindService(intent, s_activity.service_connection, BIND_AUTO_CREATE | BIND_NOT_FOREGROUND);
} }
public static void stop_sandbox() { public static void stop_sandbox() {
Log.w("tildefriends", "stop_sandbox"); log("stop_sandbox");
if (s_activity.service_connection != null) { if (s_activity.service_connection != null) {
s_activity.unbindService(s_activity.service_connection); s_activity.unbindService(s_activity.service_connection);
s_activity.service_connection = null; s_activity.service_connection = null;
@@ -442,15 +488,11 @@ public class TildeFriendsActivity extends Activity {
if (port >= 0) { if (port >= 0) {
base_url = "http://127.0.0.1:" + String.valueOf(port) + "/"; base_url = "http://127.0.0.1:" + String.valueOf(port) + "/";
runOnUiThread(() -> { runOnUiThread(() -> {
hide_status(); ready = true;
web_view.loadUrl(base_url + "login/auto"); web_view.loadUrl(base_url + "login/auto");
}); });
observer.stopWatching(); observer.stopWatching();
observer = null; observer = null;
} else {
runOnUiThread(() -> {
set_status("Waiting to connect...");
});
} }
} }

View File

@@ -6,7 +6,6 @@ import android.os.Binder;
import android.os.IBinder; import android.os.IBinder;
import android.os.Parcel; import android.os.Parcel;
import android.os.ParcelFileDescriptor; import android.os.ParcelFileDescriptor;
import android.util.Log;
public class TildeFriendsSandboxService extends Service { public class TildeFriendsSandboxService extends Service {
public static final int START_CALL = IBinder.FIRST_CALL_TRANSACTION; public static final int START_CALL = IBinder.FIRST_CALL_TRANSACTION;
@@ -14,12 +13,12 @@ public class TildeFriendsSandboxService extends Service {
Thread thread; Thread thread;
public int onStartCommand(Intent intent, int flags, int start_id) { public int onStartCommand(Intent intent, int flags, int start_id) {
Log.w("tildefriends", "TildeFriendsSandboxService: onStartCommand"); TildeFriendsActivity.log("TildeFriendsSandboxService: onStartCommand");
return super.onStartCommand(intent, flags, start_id); return super.onStartCommand(intent, flags, start_id);
} }
public void onDestroy() { public void onDestroy() {
Log.w("tildefriends", "TildeFriendsSandboxService: onDestroy"); TildeFriendsActivity.log("TildeFriendsSandboxService: onDestroy");
super.onDestroy(); super.onDestroy();
} }
@@ -27,9 +26,9 @@ public class TildeFriendsSandboxService extends Service {
thread = new Thread(new Runnable() { thread = new Thread(new Runnable() {
@Override @Override
public void run() { public void run() {
Log.w("tildefriends", "Calling tf_sandbox_main."); TildeFriendsActivity.log("Calling tf_sandbox_main.");
int result = TildeFriendsActivity.tf_sandbox_main(pipe_fd); int result = TildeFriendsActivity.tf_sandbox_main(pipe_fd);
Log.w("tildefriends", "tf_sandbox_main returned " + result + "."); TildeFriendsActivity.log("tf_sandbox_main returned " + result + ".");
} }
}); });
thread.start(); thread.start();
@@ -43,7 +42,7 @@ public class TildeFriendsSandboxService extends Service {
if (code == START_CALL) { if (code == START_CALL) {
ParcelFileDescriptor pfd = read_pfd(data); ParcelFileDescriptor pfd = read_pfd(data);
if (pfd != null) { if (pfd != null) {
Log.w("tildefriends", "fd is " + pfd.getFd()); TildeFriendsActivity.log("fd is " + pfd.getFd());
start_thread(pfd.detachFd()); start_thread(pfd.detachFd());
try { try {
pfd.close(); pfd.close();

View File

@@ -10,11 +10,6 @@
android:id="@+id/web" android:id="@+id/web"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"/> android:layout_height="match_parent"/>
<TextView
android:id="@+id/text"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_horizontal|center_vertical"/>
<TextView <TextView
android:id="@+id/refresh" android:id="@+id/refresh"
android:layout_width="match_parent" android:layout_width="match_parent"

View File

@@ -13,13 +13,13 @@
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>APPL</string> <string>APPL</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>0.2025.8</string> <string>0.2025.9</string>
<key>CFBundleSupportedPlatforms</key> <key>CFBundleSupportedPlatforms</key>
<array> <array>
<string>iPhoneOS</string> <string>iPhoneOS</string>
</array> </array>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>16</string> <string>17</string>
<key>DTPlatformName</key> <key>DTPlatformName</key>
<string>iphoneos</string> <string>iphoneos</string>
<key>LSRequiresIPhoneOS</key> <key>LSRequiresIPhoneOS</key>

View File

@@ -133,6 +133,15 @@ typedef struct _tf_ssb_message_added_callback_node_t
tf_ssb_message_added_callback_node_t* next; tf_ssb_message_added_callback_node_t* next;
} tf_ssb_message_added_callback_node_t; } tf_ssb_message_added_callback_node_t;
typedef struct _tf_ssb_blob_stored_callback_node_t tf_ssb_blob_stored_callback_node_t;
typedef struct _tf_ssb_blob_stored_callback_node_t
{
tf_ssb_blob_stored_callback_t* callback;
tf_ssb_callback_cleanup_t* cleanup;
void* user_data;
tf_ssb_blob_stored_callback_node_t* next;
} tf_ssb_blob_stored_callback_node_t;
typedef struct _tf_ssb_blob_want_added_callback_node_t tf_ssb_blob_want_added_callback_node_t; typedef struct _tf_ssb_blob_want_added_callback_node_t tf_ssb_blob_want_added_callback_node_t;
typedef struct _tf_ssb_blob_want_added_callback_node_t typedef struct _tf_ssb_blob_want_added_callback_node_t
{ {
@@ -235,6 +244,9 @@ typedef struct _tf_ssb_t
tf_ssb_message_added_callback_node_t* message_added; tf_ssb_message_added_callback_node_t* message_added;
int message_added_count; int message_added_count;
tf_ssb_blob_stored_callback_node_t* blob_stored;
int blob_stored_count;
tf_ssb_blob_want_added_callback_node_t* blob_want_added; tf_ssb_blob_want_added_callback_node_t* blob_want_added;
int blob_want_added_count; int blob_want_added_count;
@@ -2741,6 +2753,17 @@ void tf_ssb_destroy(tf_ssb_t* ssb)
} }
tf_free(node); tf_free(node);
} }
while (ssb->blob_stored)
{
tf_ssb_blob_stored_callback_node_t* node = ssb->blob_stored;
ssb->blob_stored = node->next;
ssb->blob_stored_count--;
if (node->cleanup)
{
node->cleanup(ssb, node->user_data);
}
tf_free(node);
}
while (ssb->blob_want_added) while (ssb->blob_want_added)
{ {
tf_ssb_blob_want_added_callback_node_t* node = ssb->blob_want_added; tf_ssb_blob_want_added_callback_node_t* node = ssb->blob_want_added;
@@ -2783,6 +2806,16 @@ void tf_ssb_destroy(tf_ssb_t* ssb)
uv_run(ssb->loop, UV_RUN_NOWAIT); uv_run(ssb->loop, UV_RUN_NOWAIT);
if (ssb->own_context)
{
if (!ssb->quiet)
{
tf_printf("closing ssb context\n");
}
JS_FreeContext(ssb->context);
JS_FreeRuntime(ssb->runtime);
ssb->own_context = false;
}
if (ssb->loop == &ssb->own_loop) if (ssb->loop == &ssb->own_loop)
{ {
if (!ssb->quiet) if (!ssb->quiet)
@@ -2799,16 +2832,6 @@ void tf_ssb_destroy(tf_ssb_t* ssb)
{ {
tf_printf("uv loop closed.\n"); tf_printf("uv loop closed.\n");
} }
if (ssb->own_context)
{
if (!ssb->quiet)
{
tf_printf("closing ssb context\n");
}
JS_FreeContext(ssb->context);
JS_FreeRuntime(ssb->runtime);
ssb->own_context = false;
}
while (ssb->broadcasts) while (ssb->broadcasts)
{ {
tf_ssb_broadcast_t* broadcast = ssb->broadcasts; tf_ssb_broadcast_t* broadcast = ssb->broadcasts;
@@ -3960,9 +3983,53 @@ void tf_ssb_remove_message_added_callback(tf_ssb_t* ssb, tf_ssb_message_added_ca
} }
} }
void tf_ssb_add_blob_stored_callback(tf_ssb_t* ssb, tf_ssb_blob_stored_callback_t* callback, void (*cleanup)(tf_ssb_t* ssb, void* user_data), void* user_data)
{
tf_ssb_blob_stored_callback_node_t* node = tf_malloc(sizeof(tf_ssb_blob_stored_callback_node_t));
*node = (tf_ssb_blob_stored_callback_node_t) {
.callback = callback,
.cleanup = cleanup,
.user_data = user_data,
.next = ssb->blob_stored,
};
ssb->blob_stored = node;
ssb->blob_stored_count++;
}
void tf_ssb_remove_blob_stored_callback(tf_ssb_t* ssb, tf_ssb_blob_stored_callback_t* callback, void* user_data)
{
tf_ssb_blob_stored_callback_node_t** it = &ssb->blob_stored;
while (*it)
{
if ((*it)->callback == callback && (*it)->user_data == user_data)
{
tf_ssb_blob_stored_callback_node_t* node = *it;
*it = node->next;
ssb->blob_stored_count--;
if (node->cleanup)
{
node->cleanup(ssb, node->user_data);
}
tf_free(node);
}
else
{
it = &(*it)->next;
}
}
}
void tf_ssb_notify_blob_stored(tf_ssb_t* ssb, const char* id) void tf_ssb_notify_blob_stored(tf_ssb_t* ssb, const char* id)
{ {
tf_ssb_blob_stored_callback_node_t* next = NULL;
ssb->blobs_stored++; ssb->blobs_stored++;
for (tf_ssb_blob_stored_callback_node_t* node = ssb->blob_stored; node; node = next)
{
next = node->next;
tf_trace_begin(ssb->trace, "blob stored callback");
node->callback(ssb, id, node->user_data);
tf_trace_end(ssb->trace);
}
} }
void tf_ssb_notify_message_added(tf_ssb_t* ssb, const char* author, int32_t sequence, const char* id, JSValue message_keys) void tf_ssb_notify_message_added(tf_ssb_t* ssb, const char* author, int32_t sequence, const char* id, JSValue message_keys)

View File

@@ -903,10 +903,6 @@ void tf_ssb_db_add_blob_wants(sqlite3* db, const char* id)
{ {
tf_printf("blob wants cache update failed: %s.\n", sqlite3_errmsg(db)); tf_printf("blob wants cache update failed: %s.\n", sqlite3_errmsg(db));
} }
else
{
tf_printf("want: %s\n", id);
}
} }
sqlite3_finalize(statement); sqlite3_finalize(statement);
} }

View File

@@ -680,6 +680,31 @@ void tf_ssb_remove_message_added_callback(tf_ssb_t* ssb, tf_ssb_message_added_ca
*/ */
void tf_ssb_notify_message_added(tf_ssb_t* ssb, const char* author, int32_t sequence, const char* id, JSValue message_with_keys); void tf_ssb_notify_message_added(tf_ssb_t* ssb, const char* author, int32_t sequence, const char* id, JSValue message_with_keys);
/**
** A callback called when a blob is added to the database.
** @param ssb The SSB instance.
** @param id The blob identifier.
** @param user_data The user data.
*/
typedef void(tf_ssb_blob_stored_callback_t)(tf_ssb_t* ssb, const char* id, void* user_data);
/**
** Register a callback called when a blob is added to the database.
** @param ssb The SSB instance.
** @param callback The callback function.
** @param cleanup A function to call when the callback is removed.
** @param user_data User data to pass to the callback.
*/
void tf_ssb_add_blob_stored_callback(tf_ssb_t* ssb, tf_ssb_blob_stored_callback_t* callback, tf_ssb_callback_cleanup_t* cleanup, void* user_data);
/**
** Remove a callback registered for when a blob is added to the database.
** @param ssb The SSB instance.
** @param callback The callback function.
** @param user_data User data registered with the callback.
*/
void tf_ssb_remove_blob_stored_callback(tf_ssb_t* ssb, tf_ssb_blob_stored_callback_t* callback, void* user_data);
/** /**
** Record that a new blob was stored. ** Record that a new blob was stored.
** @param ssb The SSB instance. ** @param ssb The SSB instance.

View File

@@ -1627,6 +1627,20 @@ static void _tf_ssb_on_message_added_callback(tf_ssb_t* ssb, const char* author,
JS_FreeValue(context, string); JS_FreeValue(context, string);
} }
static void _tf_ssb_on_blob_stored_callback(tf_ssb_t* ssb, const char* id, void* user_data)
{
JSContext* context = tf_ssb_get_context(ssb);
JSValue callback = JS_MKPTR(JS_TAG_OBJECT, user_data);
JSValue string = JS_NewString(context, id);
JSValue response = JS_Call(context, callback, JS_UNDEFINED, 1, &string);
if (tf_util_report_error(context, response))
{
tf_ssb_remove_blob_stored_callback(ssb, _tf_ssb_on_blob_stored_callback, user_data);
}
JS_FreeValue(context, response);
JS_FreeValue(context, string);
}
static void _tf_ssb_on_blob_want_added_callback(tf_ssb_t* ssb, const char* id, void* user_data) static void _tf_ssb_on_blob_want_added_callback(tf_ssb_t* ssb, const char* id, void* user_data)
{ {
JSContext* context = tf_ssb_get_context(ssb); JSContext* context = tf_ssb_get_context(ssb);
@@ -1747,6 +1761,11 @@ static JSValue _tf_ssb_add_event_listener(JSContext* context, JSValueConst this_
void* ptr = JS_VALUE_GET_PTR(JS_DupValue(context, callback)); void* ptr = JS_VALUE_GET_PTR(JS_DupValue(context, callback));
tf_ssb_add_message_added_callback(ssb, _tf_ssb_on_message_added_callback, _tf_ssb_cleanup_value, ptr); tf_ssb_add_message_added_callback(ssb, _tf_ssb_on_message_added_callback, _tf_ssb_cleanup_value, ptr);
} }
else if (strcmp(event_name, "blob") == 0)
{
void* ptr = JS_VALUE_GET_PTR(JS_DupValue(context, callback));
tf_ssb_add_blob_stored_callback(ssb, _tf_ssb_on_blob_stored_callback, _tf_ssb_cleanup_value, ptr);
}
else if (strcmp(event_name, "blob_want_added") == 0) else if (strcmp(event_name, "blob_want_added") == 0)
{ {
void* ptr = JS_VALUE_GET_PTR(JS_DupValue(context, callback)); void* ptr = JS_VALUE_GET_PTR(JS_DupValue(context, callback));
@@ -1790,6 +1809,11 @@ static JSValue _tf_ssb_remove_event_listener(JSContext* context, JSValueConst th
void* ptr = JS_VALUE_GET_PTR(JS_DupValue(context, callback)); void* ptr = JS_VALUE_GET_PTR(JS_DupValue(context, callback));
tf_ssb_remove_message_added_callback(ssb, _tf_ssb_on_message_added_callback, ptr); tf_ssb_remove_message_added_callback(ssb, _tf_ssb_on_message_added_callback, ptr);
} }
else if (strcmp(event_name, "blob") == 0)
{
void* ptr = JS_VALUE_GET_PTR(JS_DupValue(context, callback));
tf_ssb_remove_blob_stored_callback(ssb, _tf_ssb_on_blob_stored_callback, ptr);
}
else if (strcmp(event_name, "blob_want_added") == 0) else if (strcmp(event_name, "blob_want_added") == 0)
{ {
void* ptr = JS_VALUE_GET_PTR(JS_DupValue(context, callback)); void* ptr = JS_VALUE_GET_PTR(JS_DupValue(context, callback));

View File

@@ -152,6 +152,12 @@ static void _wait_stored(tf_ssb_t* ssb, bool* stored)
} }
} }
static void _blob_stored(tf_ssb_t* ssb, const char* id, void* user_data)
{
tf_printf("blob stored %s\n", id);
*(bool*)user_data = true;
}
void tf_ssb_test_ssb(const tf_test_options_t* options) void tf_ssb_test_ssb(const tf_test_options_t* options)
{ {
tf_printf("Testing SSB.\n"); tf_printf("Testing SSB.\n");
@@ -224,8 +230,13 @@ void tf_ssb_test_ssb(const tf_test_options_t* options)
char blob_id[k_id_base64_len] = { 0 }; char blob_id[k_id_base64_len] = { 0 };
const char* k_blob = "Hello, blob!"; const char* k_blob = "Hello, blob!";
bool blob_stored = false;
tf_ssb_add_blob_stored_callback(ssb0, _blob_stored, NULL, &blob_stored);
b = tf_ssb_db_blob_store(ssb0, (const uint8_t*)k_blob, strlen(k_blob), blob_id, sizeof(blob_id), NULL); b = tf_ssb_db_blob_store(ssb0, (const uint8_t*)k_blob, strlen(k_blob), blob_id, sizeof(blob_id), NULL);
tf_ssb_notify_blob_stored(ssb0, blob_id);
tf_ssb_remove_blob_stored_callback(ssb0, _blob_stored, &blob_stored);
assert(b); assert(b);
assert(blob_stored);
JSContext* context0 = tf_ssb_get_context(ssb0); JSContext* context0 = tf_ssb_get_context(ssb0);
JSValue obj = JS_NewObject(context0); JSValue obj = JS_NewObject(context0);

View File

@@ -1,2 +1,2 @@
#define VERSION_NUMBER "0.2025.8-wip" #define VERSION_NUMBER "0.2025.9-wip"
#define VERSION_NAME "This program kills fascists." #define VERSION_NAME "This program kills fascists."

View File

@@ -93,7 +93,7 @@ try:
select(driver, ['#document', 'frame', '#gs_room_name'], ('send_keys', 'test room')) select(driver, ['#document', 'frame', '#gs_room_name'], ('send_keys', 'test room'))
select(driver, ['#document', 'frame', '//*[@id="gs_room_name"]/following-sibling::button'], ('click',)) select(driver, ['#document', 'frame', '//*[@id="gs_room_name"]/following-sibling::button'], ('click',))
select(driver, ['//button[text()="✅ Allow"]'], ('click',)) select(driver, ['//button[text()="✅ Allow"]'], ('click',))
driver.switch_to.alert.accept() wait.until(expected_conditions.alert_is_present()).accept()
select(driver, ['tf-navigation', 'shadow_root', '#identity'], ('click',)) select(driver, ['tf-navigation', 'shadow_root', '#identity'], ('click',))
select(driver, ['tf-navigation', 'shadow_root', '#logout'], ('click',)) select(driver, ['tf-navigation', 'shadow_root', '#logout'], ('click',))