22 Commits

Author SHA1 Message Date
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
ed4f1d6f2c android: Be smarter about the file watcher.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 32m12s
2025-08-13 17:51:04 -04:00
73f4a3407f ssb: Allow showing raw messages for contact messages. 2025-08-13 12:14:31 -04:00
6f11318e84 update: speedscope 1.23.1.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 32m56s
2025-08-13 12:06:47 -04:00
e88ee91f0e ssb: More reliably load private messages.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 32m50s
2025-08-06 12:10:51 -04:00
3f8daf257c update: OpenSSL 3.5.2.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 30m58s
2025-08-05 12:16:03 -04:00
dc387acadc ssb: Make progress bar brighter. 2025-08-02 12:16:07 -04:00
68aa41ab96 android: Tweaking random flags until ANRs subside.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 30m34s
2025-08-02 12:09:08 -04:00
85b23437b3 docs: Fix all the TODOCS. #39
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 31m17s
2025-08-02 09:07:45 -04:00
c59fba817d ssb: Show the progress indicator more consistently.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 30m54s
2025-07-31 12:48:45 -04:00
c3415ab75c docs: Expose the rest of core to docs.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 31m24s
2025-07-30 20:25:20 -04:00
f1d0151d71 ssb: Make the progress bar more indefinite-looking.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-07-30 20:04:34 -04:00
3c5c1756d1 ssb: A progress bar experiment. 2025-07-30 19:49:08 -04:00
6a6b65d1b3 build: Update nix config. Start building 0.2025.8, switching to calver.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-07-30 19:26:26 -04:00
27 changed files with 577 additions and 293 deletions

View File

@@ -16,9 +16,9 @@ MAKEFLAGS += --no-builtin-rules
## LD := Linker.
## ANDROID_SDK := Path to the Android SDK.
VERSION_CODE := 40
VERSION_CODE_IOS := 15
VERSION_NUMBER := 0.0.33
VERSION_CODE := 41
VERSION_CODE_IOS := 16
VERSION_NUMBER := 0.2025.8-wip
VERSION_NAME := This program kills fascists.
IPHONEOS_VERSION_MIN=14.0
@@ -253,7 +253,10 @@ $(ANDROID_TARGETS): CFLAGS += \
-fno-asynchronous-unwind-tables \
-funwind-tables \
-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): LDFLAGS += -Og
$(RELEASE_TARGETS): CFLAGS += \
@@ -1140,6 +1143,11 @@ releaseapkgo: out/TildeFriends-arm-release.apk ## Build, install, and run a rele
@adb shell am start com.unprompted.tildefriends/.TildeFriendsActivity
.PHONY: releaseapkgo
x86releaseapkgo: out/TildeFriends-x86-release.apk ## Build, install, and run an x86 release Android APK.
@adb install -r $<
@adb shell am start com.unprompted.tildefriends/.TildeFriendsActivity
.PHONY: x86releaseapkgo
apklog: ## Display Android log output.
@adb logcat *:S tildefriends
.PHONY: apklog

View File

@@ -1,5 +1,5 @@
{
"type": "tildefriends-app",
"emoji": "🦀",
"previous": "&DGtlnm5wWRZCgJMF8JsP6VtzNRrd4KLoERJRpFULqOY=.sha256"
"previous": "&vvbZ1UaJgi4mVonUOmevMO4iB1tL70vhCA/Jo8tnaX4=.sha256"
}

View File

@@ -22,9 +22,11 @@ class TfElement extends LitElement {
guest: {type: Boolean},
url: {type: String},
private_messages: {type: Array},
grouped_private_messages: {type: Object},
recent_reactions: {type: Array},
is_administrator: {type: Boolean},
stay_connected: {type: Boolean},
progress: {type: Number},
};
}
@@ -56,6 +58,7 @@ class TfElement extends LitElement {
tfrpc.rpc.getHash().then((hash) => self.set_hash(hash));
tfrpc.register(function hashChanged(hash) {
self.set_hash(hash);
self.reset_progress();
});
tfrpc.register(async function notifyNewMessage(id) {
await self.fetch_new_message(id);
@@ -138,7 +141,9 @@ class TfElement extends LitElement {
'',
'@',
'👍',
'🔐',
...Object.keys(this.grouped_private_messages)
.sort()
.map((x) => '🔐' + JSON.parse(x).join(',')),
...this.channels.map((x) => '#' + x),
];
let index = channel_names.indexOf(this.hash.substring(1));
@@ -364,6 +369,32 @@ class TfElement extends LitElement {
return result;
}
async group_private_messages(messages) {
let groups = {};
let result = await this.decrypt(
await tfrpc.rpc.query(
`
SELECT 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(
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) {
let start_time = new Date();
let latest_private = this.get_latest_private(following);
@@ -436,12 +467,15 @@ class TfElement extends LitElement {
console.log('channels took', (new Date() - start_time) / 1000.0);
let self = this;
start_time = new Date();
latest_private.then(function (latest) {
latest_private.then(async function (latest) {
self.channels_latest = Object.assign({}, self.channels_latest, {
'🔐': latest[0],
});
console.log('private took', (new Date() - start_time) / 1000.0);
self.private_messages = latest[1];
self.grouped_private_messages = await self.group_private_messages(
latest[1]
);
});
}
@@ -450,7 +484,28 @@ class TfElement extends LitElement {
this.schedule_load_latest();
}
reset_progress() {
if (this.progress === undefined) {
this._progress_start = new Date();
requestAnimationFrame(this.update_progress.bind(this));
}
}
update_progress() {
if (
!this.loading_latest &&
!this.loading_latest_scheduled &&
!this.shadowRoot.getElementById('tf-tab-news')?.is_loading()
) {
this.progress = undefined;
return;
}
this.progress = (new Date() - this._progress_start).valueOf();
requestAnimationFrame(this.update_progress.bind(this));
}
schedule_load_latest() {
this.reset_progress();
if (!this.loading_latest) {
this.shadowRoot.getElementById('tf-tab-news')?.load_latest();
this.load();
@@ -495,6 +550,7 @@ class TfElement extends LitElement {
async load() {
this.loading_latest = true;
this.reset_progress();
try {
let start_time = new Date();
let whoami = this.whoami;
@@ -603,8 +659,10 @@ class TfElement extends LitElement {
@channelsetunread=${this.channel_set_unread}
@refresh=${this.refresh}
@toggle_stay_connected=${this.toggle_stay_connected}
@loadmessages=${this.reset_progress}
.connections=${this.connections}
.private_messages=${this.private_messages}
.grouped_private_messages=${this.grouped_private_messages}
.recent_reactions=${this.recent_reactions}
?is_administrator=${this.is_administrator}
?stay_connected=${this.stay_connected}
@@ -646,6 +704,7 @@ class TfElement extends LitElement {
async set_tab(tab) {
this.tab = tab;
if (tab === 'news') {
this.schedule_load_latest();
await tfrpc.rpc.setHash('#');
} else if (tab === 'connections') {
await tfrpc.rpc.setHash('#connections');
@@ -751,11 +810,23 @@ class TfElement extends LitElement {
Loading...
</div>`
: this.render_tab();
let progress =
this.progress !== undefined
? html`
<div style="position: absolute; width: 100%" id="progress">
<div
class="w3-theme-l3"
style=${`height: 4px; position: absolute; right: ${Math.cos(this.progress / 250) > 0 ? 'auto' : '0'}; width: ${50 * Math.sin(this.progress / 250) + 50}%`}
></div>
</div>
`
: undefined;
return html`
<div
style="width: 100vw; min-height: 100vh; height: 100vh; display: flex; flex-direction: column"
class="w3-theme-dark"
>
${progress}
<div style="flex: 0 0">${tabs}</div>
<div style="flex: 1 1; overflow: auto; contain: layout">
${contents}

View File

@@ -16,6 +16,7 @@ class TfComposeElement extends LitElement {
author: {type: String},
channel: {type: String},
new_thread: {type: Boolean},
recipients: {type: Array},
};
}
@@ -91,7 +92,7 @@ class TfComposeElement extends LitElement {
bubbles: true,
composed: true,
detail: {
id: this.branch,
id: this.branch ?? (this.recipients ? this.recipients.join(',') : undefined),
draft: draft,
},
})
@@ -496,7 +497,17 @@ class TfComposeElement extends LitElement {
}
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) {

View File

@@ -789,60 +789,45 @@ class TfMessageElement extends LitElement {
</div>
`);
} else if (content.type == 'contact') {
return this.render_frame(html`
<div class="w3-bar">
<div class="w3-bar-item">
<tf-user id=${this.message.author} .users=${this.users}></tf-user>
is
${content.blocking === true
? 'blocking'
: content.blocking === false
? 'no longer blocking'
: content.following === true
? 'following'
: content.following === false
? 'no longer following'
: '?'}
<tf-user
id=${this.message.content.contact}
.users=${this.users}
></tf-user>
</div>
<div class="w3-bar-item w3-right">
<button class="w3-button w3-theme-d1" @click=${this.toggle_menu}>
%
</button>
<div
class="w3-dropdown-content w3-bar-block w3-card-4 w3-theme-l1"
style="right: 48px"
>
<a
target="_top"
class="w3-button w3-bar-item"
href=${'#' + encodeURIComponent(this.message?.id)}
>View Message</a
>
<button
class="w3-button w3-bar-item w3-border-bottom"
@click=${this.copy_id}
>
Copy ID
</button>
${this.drafts[this.message?.id] === undefined
? html`
<button
class="w3-button w3-bar-item"
@click=${this.show_reply}
>
↩️ Reply
</button>
`
: undefined}
switch (this.format) {
case 'message':
default:
return this.render_frame(html`
<div class="w3-bar">
<div class="w3-bar-item">
<tf-user
id=${this.message.author}
.users=${this.users}
></tf-user>
is
${content.blocking === true
? 'blocking'
: content.blocking === false
? 'no longer blocking'
: content.following === true
? 'following'
: content.following === false
? 'no longer following'
: '?'}
<tf-user
id=${this.message.content.contact}
.users=${this.users}
></tf-user>
</div>
${this.render_menu()} ${this.render_votes()}
${this.render_actions()}
</div>
`);
break;
case 'raw':
return this.render_frame(html`
${this.render_header()}
<div class="w3-container">${this.render_raw()}</div>
${this.render_votes()} ${this.render_actions()}
</div>
${this.render_votes()} ${this.render_actions()}
</div>
`);
`);
break;
}
} else if (content.type == 'post') {
let self = this;
let body;

View File

@@ -18,6 +18,7 @@ class TfTabNewsFeedElement extends LitElement {
time_range: {type: Array},
time_loading: {type: Array},
private_messages: {type: Array},
grouped_private_messages: {type: Object},
recent_reactions: {type: Array},
};
}
@@ -106,6 +107,12 @@ class TfTabNewsFeedElement extends LitElement {
}
async fetch_messages(start_time, end_time) {
this.dispatchEvent(
new CustomEvent('loadmessages', {
bubbles: true,
composed: true,
})
);
this.time_loading = [start_time, end_time];
let result;
const k_max_results = 64;
@@ -202,7 +209,9 @@ class TfTabNewsFeedElement extends LitElement {
console.log(
`load of ${result.length} rows took ${(t2 - t0) / 1000} (${(t1 - t0) / 1000} to find ${initial_messages.length} initial messages, ${(t2 - t1) / 1000} to find ${result.length} total messages) following=${this.following.length} st=${start_time} et=${end_time}`
);
} else if (this.hash == '#🔐') {
} else if (this.hash.startsWith('#🔐')) {
let ids =
this.hash == '#🔐' ? [] : this.hash.substring('#🔐'.length).split(',');
result = await tfrpc.rpc.query(
`
SELECT TRUE AS is_primary, messages.rowid, messages.id, previous, author, sequence, timestamp, hash, json(content) AS content, signature
@@ -214,7 +223,11 @@ class TfTabNewsFeedElement extends LitElement {
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,
end_time,
k_max_results,
@@ -378,7 +391,10 @@ class TfTabNewsFeedElement extends LitElement {
this.messages = [];
this._messages_hash = this.hash;
}
this._messages_following = this.following;
this._messages_following = JSON.stringify(this.following);
this._private_messages =
JSON.stringify(this.private_messages) +
JSON.stringify(this.grouped_private_messages);
let now = new Date().valueOf();
let start_time = now - 24 * 60 * 60 * 1000;
this.start_time = start_time;
@@ -421,8 +437,10 @@ class TfTabNewsFeedElement extends LitElement {
if (
!this.messages ||
this._messages_hash !== this.hash ||
JSON.stringify(this._messages_following) !==
JSON.stringify(this.following)
this._messages_following !== JSON.stringify(this.following) ||
this._private_messages !==
JSON.stringify(this.private_messages) +
JSON.stringify(this.grouped_private_messages)
) {
console.log(
`loading messages for ${this.whoami} (following ${this.following.length})`

View File

@@ -24,6 +24,7 @@ class TfTabNewsElement extends LitElement {
channels_latest: {type: Object},
connections: {type: Array},
private_messages: {type: Array},
grouped_private_messages: {type: Object},
recent_reactions: {type: Array},
peer_exchange: {type: Boolean},
is_administrator: {type: Boolean},
@@ -180,6 +181,10 @@ class TfTabNewsElement extends LitElement {
await this.check_peer_exchange();
}
is_loading() {
return this.shadowRoot?.getElementById('news')?.loading;
}
render_sidebar() {
return html`
<div
@@ -253,12 +258,28 @@ class TfTabNewsElement extends LitElement {
style=${this.hash == '#👍' ? 'font-weight: bold' : undefined}
>${this.unread_status('👍')}👍votes</a
>
<a
href="#🔐"
class="w3-bar-item w3-button"
style=${this.hash == '#🔐' ? 'font-weight: bold' : undefined}
>${this.unread_status('🔐')}🔐private</a
>
${Object.keys(this?.grouped_private_messages ?? [])
?.sort()
?.map(
(key) => html`
<a
href=${'#🔐' + JSON.parse(key).join(',')}
class="w3-bar-item w3-button"
style=${this.hash == '#🔐' + JSON.parse(key).join(',')
? 'font-weight: bold'
: undefined}
>${(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)
.sort()
.map(
@@ -414,6 +435,9 @@ class TfTabNewsElement extends LitElement {
.drafts=${this.drafts}
@tf-draft=${this.draft}
.channel=${this.channel()}
.recipients=${this.hash.startsWith('#🔐')
? this.hash.substring('#🔐'.length).split(',')
: undefined}
></tf-compose>
</div>
${profile}
@@ -430,6 +454,7 @@ class TfTabNewsElement extends LitElement {
.channels_unread=${this.channels_unread}
.channels_latest=${this.channels_latest}
.private_messages=${this.private_messages}
.grouped_private_messages=${this.grouped_private_messages}
.recent_reactions=${this.recent_reactions}
></tf-tab-news-feed>
</div>

View File

@@ -9,6 +9,7 @@ class TfUserElement extends LitElement {
fallback_name: {type: String},
icon_only: {type: Boolean},
users: {type: Object},
nolink: {type: Boolean},
};
}
@@ -37,7 +38,9 @@ class TfUserElement extends LitElement {
let name_string = name ?? this.fallback_name ?? this.id;
name = this.icon_only
? 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) {
let image_link = user.image;
@@ -56,7 +59,8 @@ class TfUserElement extends LitElement {
}
}
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}
</div>`;

View File

@@ -1,7 +1,23 @@
/**
* \file
* \defgroup tfapp Tilde Friends App JS
* Tilde Friends server-side app wrapper.
* @{
*/
/** \cond */
import * as core from './core.js';
let gSessionIndex = 0;
export {App};
/** \endcond */
/** A sequence number of apps. */
let g_session_index = 0;
/**
** App constructor.
** @return An app instance.
*/
function App() {
this._send_queue = [];
this.calls = {};
@@ -9,6 +25,12 @@ function App() {
return this;
}
/**
** Create a function wrapper that when called invokes a function on the app
** itself.
** @param api The function and argument names.
** @return A function.
*/
App.prototype.makeFunction = function (api) {
let self = this;
let result = function () {
@@ -32,6 +54,10 @@ App.prototype.makeFunction = function (api) {
return result;
};
/**
** Send a message to the app.
** @param message The message to send.
*/
App.prototype.send = function (message) {
if (this._send_queue) {
if (this._on_output) {
@@ -46,6 +72,11 @@ App.prototype.send = function (message) {
}
};
/**
** App socket handler.
** @param request The HTTP request of the WebSocket connection.
** @param response The HTTP response.
*/
exports.app_socket = async function socket(request, response) {
let process;
let options = {};
@@ -133,7 +164,7 @@ exports.app_socket = async function socket(request, response) {
options.packageOwner = packageOwner;
options.packageName = packageName;
options.url = message.url;
let sessionId = 'session_' + (gSessionIndex++).toString();
let sessionId = 'session_' + (g_session_index++).toString();
if (blobId) {
if (message.edit_only) {
response.send(
@@ -218,4 +249,4 @@ exports.app_socket = async function socket(request, response) {
response.upgrade(100, {});
};
export {App};
/** @} */

View File

@@ -72,7 +72,7 @@ class TfNavigationElement extends LitElement {
}
/**
* TODOC
* Toggle editor visibility.
* @param event The HTML event.
*/
toggle_edit(event) {
@@ -85,7 +85,7 @@ class TfNavigationElement extends LitElement {
}
/**
* TODOC
* Remove a stored permission.
* @param key The permission to reset.
*/
reset_permission(key) {
@@ -93,7 +93,7 @@ class TfNavigationElement extends LitElement {
}
/**
* TODOC
* Get or create a spark line.
* @param key The spark line identifier.
* @param options Spark line options.
* @return A spark line HTML element.
@@ -262,8 +262,8 @@ class TfNavigationElement extends LitElement {
}
/**
* TODOC
* @returns
* Render the permissions popup.
* @return Lit HTML.
*/
render_permissions() {
if (this.show_permissions) {
@@ -312,8 +312,8 @@ class TfNavigationElement extends LitElement {
}
/**
* TODOC
* @returns
* Render the navigation bar.
* @return Lit HTML.
*/
render() {
let self = this;
@@ -441,7 +441,7 @@ class TfNavigationElement extends LitElement {
customElements.define('tf-navigation', TfNavigationElement);
/**
* TODOC
* A file in the files sidebar.
*/
class TfFilesElement extends LitElement {
/**
@@ -467,7 +467,7 @@ class TfFilesElement extends LitElement {
}
/**
* TODOC
* Select a clicked file.
* @param file The file.
*/
file_click(file) {
@@ -483,9 +483,9 @@ class TfFilesElement extends LitElement {
}
/**
* TODOC
* Render a single file in the file list.
* @param file The file.
* @returns Lit HTML.
* @return Lit HTML.
*/
render_file(file) {
let classes = ['file'];
@@ -507,7 +507,7 @@ class TfFilesElement extends LitElement {
}
/**
* TODOC
* Create a file entry for a dropped file.
* @param event The event.
*/
async drop(event) {
@@ -533,7 +533,7 @@ class TfFilesElement extends LitElement {
}
/**
* TODOC
* Called when a file starts being dragged over the file.
* @param event The event.
*/
drag_enter(event) {
@@ -543,7 +543,7 @@ class TfFilesElement extends LitElement {
}
/**
* TODOC
* Called when a file stops being dragged over the file.
* @param event The event.
*/
drag_leave(event) {
@@ -554,7 +554,7 @@ class TfFilesElement extends LitElement {
}
/**
* Drag over event.
* Called when a file is being dragged over the file.
* @param event The event.
*/
drag_over(event) {
@@ -562,8 +562,8 @@ class TfFilesElement extends LitElement {
}
/**
* TODOC
* @returns
* Render the file.
* @return Lit HTML.
*/
render() {
let self = this;
@@ -610,7 +610,7 @@ class TfFilesElement extends LitElement {
customElements.define('tf-files', TfFilesElement);
/**
* TODOC
* The files pane element.
*/
class TfFilesPaneElement extends LitElement {
/**
@@ -635,7 +635,7 @@ class TfFilesPaneElement extends LitElement {
}
/**
* TODOC
* Set whether the files pane is expanded.
* @param expanded Whether the files pane is expanded.
*/
set_expanded(expanded) {
@@ -644,8 +644,8 @@ class TfFilesPaneElement extends LitElement {
}
/**
* TODOC
* @returns
* Render the files pane element.
* @return Lit HTML.
*/
render() {
let self = this;
@@ -704,7 +704,7 @@ class TfFilesPaneElement extends LitElement {
customElements.define('tf-files-pane', TfFilesPaneElement);
/**
* TODOC
* A tiny graph.
*/
class TfSparkLineElement extends LitElement {
static get properties() {
@@ -724,9 +724,9 @@ class TfSparkLineElement extends LitElement {
}
/**
* TODOC
* @param {*} key
* @param {*} value
* Add a data point to the graph.
* @param key The line to which the point applies.
* @param value The numeric value of the data point.
*/
append(key, value) {
let line = null;
@@ -753,9 +753,9 @@ class TfSparkLineElement extends LitElement {
}
/**
* TODOC
* @param {*} line
* @returns
* Render a single series line.
* @param line The line data.
* @return Lit HTML.
*/
render_line(line) {
if (line?.values?.length >= 2) {
@@ -771,8 +771,8 @@ class TfSparkLineElement extends LitElement {
}
/**
* TODOC
* @returns
* Render the graph.
* @return Lit HTML.
*/
render() {
let max =
@@ -799,7 +799,9 @@ class TfSparkLineElement extends LitElement {
customElements.define('tf-sparkline', TfSparkLineElement);
// TODOC
/**
* A keyboard key is pressed down.
*/
window.addEventListener('keydown', function (event) {
if (event.keyCode == 83 && (event.altKey || event.ctrlKey)) {
if (editing()) {
@@ -860,24 +862,23 @@ function ensureLoaded(nodes, callback) {
}
/**
* TODOC
* @returns
* Check whether the editior is currently visible.
* @return true if the editor is visible.
*/
function editing() {
return document.getElementById('editPane').style.display != 'none';
}
/**
* TODOC
* @returns
* Check whether only the editor is visible and the app is hidden.
* @return true if the editor is visible and the app is not.
*/
function is_edit_only() {
return window.location.search == '?editonly=1' || window.innerWidth < 1024;
}
/**
* TODOC
* @returns
* Show the editor.
*/
async function edit() {
if (editing()) {
@@ -904,7 +905,7 @@ async function edit() {
}
/**
* TODOC
* Open a performance trace.
*/
function trace() {
window.open(`/speedscope/#profileURL=${encodeURIComponent('/trace')}`);
@@ -982,7 +983,7 @@ async function load(path) {
}
/**
* TODOC
* Hide the editor.
*/
function closeEditor() {
window.localStorage.setItem('editing', '0');
@@ -990,14 +991,6 @@ function closeEditor() {
document.getElementById('viewPane').style.display = 'flex';
}
/**
* TODOC
* @returns
*/
function explodePath() {
return /^\/~([^\/]+)\/([^\/]+)(.*)/.exec(window.location.pathname);
}
/**
* Save the app.
* @param save_to An optional path to which to save the app.
@@ -1111,7 +1104,7 @@ function save(save_to) {
}
/**
* TODOC
* Prompt to set the app icon.
*/
function changeIcon() {
let value = prompt('Enter a new app icon emoji:');
@@ -1122,7 +1115,7 @@ function changeIcon() {
}
/**
* TODOC
* Prompt to delete the current app.
*/
function deleteApp() {
let name = document.getElementById('name');
@@ -1143,8 +1136,8 @@ function deleteApp() {
}
/**
* TODOC
* @returns
* Get the current app URL.
* @return The app URL.
*/
function url() {
let hash = window.location.href.indexOf('#');
@@ -1162,8 +1155,8 @@ function url() {
}
/**
* TODOC
* @returns
* Get the window hash without the lone '#' if it is empty.
* @return The hash.
*/
function hash() {
return window.location.hash != '#' ? window.location.hash : '';
@@ -1188,7 +1181,7 @@ function api_postMessage(message) {
}
/**
* TODOC
* Show an error.
* @param error The error.
*/
function api_error(error) {
@@ -1293,7 +1286,7 @@ function api_requestPermission(permission, id) {
}
/**
* TODOC
* Log from the app to the console.
*/
function api_print() {
console.log('app>', ...arguments);
@@ -1308,7 +1301,7 @@ function api_setHash(hash) {
}
/**
* TODOC
* Process an incoming WebSocket message.
* @param message The message.
*/
function _receive_websocket_message(message) {
@@ -1432,14 +1425,14 @@ function send(value) {
}
/**
* TODOC
* Notify the app of the window hash changing.
*/
function hashChange() {
send({event: 'hashChange', hash: window.location.hash});
}
/**
* TODOC
* Make sure the app is connected on window focus, and notify the app.
*/
function focus() {
if (gSocket && gSocket.readyState == gSocket.CLOSED) {
@@ -1450,7 +1443,7 @@ function focus() {
}
/**
* TODOC
* Notify the app of lost focus.
*/
function blur() {
if (gSocket && gSocket.readyState == gSocket.OPEN) {
@@ -1617,7 +1610,7 @@ function openFile(name) {
}
/**
* TODOC
* Refresh the files list.
*/
function updateFiles() {
let files = document.getElementsByTagName('tf-files-pane')[0];
@@ -1650,7 +1643,7 @@ function makeNewFile(name) {
}
/**
* TODOC
* Prompt to create a new file.
*/
function newFile() {
let name = prompt('Name of new file:', 'file.js');
@@ -1660,7 +1653,7 @@ function newFile() {
}
/**
* TODOC
* Prompt to remove a file.
*/
function removeFile() {
if (confirm('Remove ' + gCurrentFile + '?')) {
@@ -1670,7 +1663,7 @@ function removeFile() {
}
/**
* TODOC
* Export the app to a zip file, which is downloaded by the browser.
*/
async function appExport() {
let JsZip = (await import('/static/jszip.min.js')).default;
@@ -1728,7 +1721,7 @@ async function save_file_to_blob_id(name, file) {
}
/**
* TODOC
* Prompt to import an app from a zip file.
*/
async function appImport() {
let JsZip = (await import('/static/jszip.min.js')).default;
@@ -1855,7 +1848,9 @@ function toggleVisibleWhitespace() {
}
}
// TODOC
/**
* Register event handlers and connect the WebSocket on load.
*/
window.addEventListener('load', function () {
window.addEventListener('hashchange', hashChange);
window.addEventListener('focus', focus);

View File

@@ -1,8 +1,14 @@
/**
* TODOC
* TODO: document so we can improve this
* @param {*} url
* @returns
* \file
* \defgroup tfhttp Tilde Friends HTTP Client JS
* Tilde Friends server-side HTTP client.
* @{
*/
/**
* Parse a URL into protocol, host, path, and port parts.
* @param url
* @return An object of the URL parts.
*/
function parseUrl(url) {
// XXX: Hack.
@@ -16,9 +22,9 @@ function parseUrl(url) {
}
/**
* TODOC
* @param {*} data
* @returns
* Parse an HTTP response into headers and body content.
* @param data The response data, headers and body included.
* @return headers and body data.
*/
function parseResponse(data) {
let firstLine;
@@ -36,15 +42,15 @@ function parseResponse(data) {
headers[line.substring(colon)] = line.substring(colon + 1);
}
}
return {body: data};
return {headers: headers, body: data};
}
/**
* TODOC
* @param {*} url
* @param {*} options
* @param {*} allowed_hosts
* @returns
* Make an HTTP request.
* @param url The URL.
* @param options Request options.
* @param allowed_hosts List of allowed hosts.
* @return A promise resolved with the response headers and body.
*/
export function fetch(url, options, allowed_hosts) {
let parsed = parseUrl(url);
@@ -111,3 +117,5 @@ export function fetch(url, options, allowed_hosts) {
});
});
}
/** @} */

View File

@@ -15,8 +15,8 @@ let g_next_id = 1;
let g_calls = {};
/**
* TODOC
* @returns
* Check if being called from a browser vs. server-side.
* @return true if called from a browser.
*/
function get_is_browser() {
try {

View File

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

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

@@ -167,9 +167,9 @@
}
},
"node_modules/@jridgewell/gen-mapping": {
"version": "0.3.12",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz",
"integrity": "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==",
"version": "0.3.13",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
"integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -188,9 +188,9 @@
}
},
"node_modules/@jridgewell/source-map": {
"version": "0.3.10",
"resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.10.tgz",
"integrity": "sha512-0pPkgz9dY+bijgistcTTJ5mR+ocqRXLuhXHYdzoMmmoJ2C9S46RCm2GMUbatPEUK9Yjy26IrAy8D/M00lLkv+Q==",
"version": "0.3.11",
"resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz",
"integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -199,16 +199,16 @@
}
},
"node_modules/@jridgewell/sourcemap-codec": {
"version": "1.5.4",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz",
"integrity": "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==",
"version": "1.5.5",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
"dev": true,
"license": "MIT"
},
"node_modules/@jridgewell/trace-mapping": {
"version": "0.3.29",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz",
"integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==",
"version": "0.3.30",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.30.tgz",
"integrity": "sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -360,9 +360,9 @@
}
},
"node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.46.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.46.0.tgz",
"integrity": "sha512-9f3nSTFI2ivfxc7/tHBHcJ8pRnp8ROrELvsVprlQPVvcZ+j5zztYd+PTJGpyIOAdTvNwNrpCXswKSeoQcyGjMQ==",
"version": "4.46.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.46.2.tgz",
"integrity": "sha512-Zj3Hl6sN34xJtMv7Anwb5Gu01yujyE/cLBDB2gnHTAHaWS1Z38L7kuSG+oAh0giZMqG060f/YBStXtMH6FvPMA==",
"cpu": [
"arm"
],
@@ -373,9 +373,9 @@
]
},
"node_modules/@rollup/rollup-android-arm64": {
"version": "4.46.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.46.0.tgz",
"integrity": "sha512-tFZSEhqJ8Yrpe50TzOdeoYi72gi/jsnT7y8Qrozf3cNu28WX+s6I3XzEPUAqoaT9SAS8Xz9AzGTFlxxCH/w20w==",
"version": "4.46.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.46.2.tgz",
"integrity": "sha512-nTeCWY83kN64oQ5MGz3CgtPx8NSOhC5lWtsjTs+8JAJNLcP3QbLCtDDgUKQc/Ro/frpMq4SHUaHN6AMltcEoLQ==",
"cpu": [
"arm64"
],
@@ -386,9 +386,9 @@
]
},
"node_modules/@rollup/rollup-darwin-arm64": {
"version": "4.46.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.46.0.tgz",
"integrity": "sha512-+DikIIs+p6yU2hF51UaWG8BnHbq90X0QIOt5zqSKSZxY+G3qqdLih214e9InJal21af2PuuxkDectetGfbVPJw==",
"version": "4.46.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.46.2.tgz",
"integrity": "sha512-HV7bW2Fb/F5KPdM/9bApunQh68YVDU8sO8BvcW9OngQVN3HHHkw99wFupuUJfGR9pYLLAjcAOA6iO+evsbBaPQ==",
"cpu": [
"arm64"
],
@@ -399,9 +399,9 @@
]
},
"node_modules/@rollup/rollup-darwin-x64": {
"version": "4.46.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.46.0.tgz",
"integrity": "sha512-5a+NofhdEB/WimSlFMskbFQn1vqz1FWryYpA99trmZGO6qEmiS0IsX6w4B3d91U878Q2ZQdiaFF1gxX4P147og==",
"version": "4.46.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.46.2.tgz",
"integrity": "sha512-SSj8TlYV5nJixSsm/y3QXfhspSiLYP11zpfwp6G/YDXctf3Xkdnk4woJIF5VQe0of2OjzTt8EsxnJDCdHd2xMA==",
"cpu": [
"x64"
],
@@ -412,9 +412,9 @@
]
},
"node_modules/@rollup/rollup-freebsd-arm64": {
"version": "4.46.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.46.0.tgz",
"integrity": "sha512-igr/RlKPS3OCy4jD3XBmAmo3UAcNZkJSubRsw1JeM8bAbwf15k/3eMZXD91bnjheijJiOJcga3kfCLKjV8IXNg==",
"version": "4.46.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.46.2.tgz",
"integrity": "sha512-ZyrsG4TIT9xnOlLsSSi9w/X29tCbK1yegE49RYm3tu3wF1L/B6LVMqnEWyDB26d9Ecx9zrmXCiPmIabVuLmNSg==",
"cpu": [
"arm64"
],
@@ -425,9 +425,9 @@
]
},
"node_modules/@rollup/rollup-freebsd-x64": {
"version": "4.46.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.46.0.tgz",
"integrity": "sha512-MdigWzPSHlQzB1xZ+MdFDWTAH+kcn7UxjEBoOKuaso7z1DRlnAnrknB1mTtNOQ+GdPI8xgExAGwHeqQjntR0Cg==",
"version": "4.46.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.46.2.tgz",
"integrity": "sha512-pCgHFoOECwVCJ5GFq8+gR8SBKnMO+xe5UEqbemxBpCKYQddRQMgomv1104RnLSg7nNvgKy05sLsY51+OVRyiVw==",
"cpu": [
"x64"
],
@@ -438,9 +438,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
"version": "4.46.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.46.0.tgz",
"integrity": "sha512-dmZseE0ZwA/4yy1+BwFrDqFTjjNg24GO9xSrb1weVbt6AFkhp5pz1gVS7IMtfIvoWy8yp6q/zN0bKnefRUImvQ==",
"version": "4.46.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.46.2.tgz",
"integrity": "sha512-EtP8aquZ0xQg0ETFcxUbU71MZlHaw9MChwrQzatiE8U/bvi5uv/oChExXC4mWhjiqK7azGJBqU0tt5H123SzVA==",
"cpu": [
"arm"
],
@@ -451,9 +451,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
"version": "4.46.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.46.0.tgz",
"integrity": "sha512-fzhfn6p9Cfm3W8UrWKIa4l7Wfjs/KGdgaswMBBE3KY3Ta43jg2XsPrAtfezHpsRk0Nx+TFuS3hZk/To2N5kFPQ==",
"version": "4.46.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.46.2.tgz",
"integrity": "sha512-qO7F7U3u1nfxYRPM8HqFtLd+raev2K137dsV08q/LRKRLEc7RsiDWihUnrINdsWQxPR9jqZ8DIIZ1zJJAm5PjQ==",
"cpu": [
"arm"
],
@@ -464,9 +464,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-gnu": {
"version": "4.46.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.46.0.tgz",
"integrity": "sha512-vVDD+iPDPmJQ5nAQ5Tifq3ywdv60FartglFI8VOCK+hcU9aoG0qlQTsDJP97O5yiTaTqlneZWoARMcVC5nyUoQ==",
"version": "4.46.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.46.2.tgz",
"integrity": "sha512-3dRaqLfcOXYsfvw5xMrxAk9Lb1f395gkoBYzSFcc/scgRFptRXL9DOaDpMiehf9CO8ZDRJW2z45b6fpU5nwjng==",
"cpu": [
"arm64"
],
@@ -477,9 +477,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-musl": {
"version": "4.46.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.46.0.tgz",
"integrity": "sha512-0d0jx08fzDHCzXqrtCMEEyxKU0SvJrWmUjUDE2/KDQ2UDJql0tfiwYvEx1oHELClKO8CNdE+AGJj+RqXscZpdQ==",
"version": "4.46.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.46.2.tgz",
"integrity": "sha512-fhHFTutA7SM+IrR6lIfiHskxmpmPTJUXpWIsBXpeEwNgZzZZSg/q4i6FU4J8qOGyJ0TR+wXBwx/L7Ho9z0+uDg==",
"cpu": [
"arm64"
],
@@ -490,9 +490,9 @@
]
},
"node_modules/@rollup/rollup-linux-loongarch64-gnu": {
"version": "4.46.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.46.0.tgz",
"integrity": "sha512-XBYu9oW9eKJadWn8M7hkTZsD4yG+RrsTrVEgyKwb4L72cpJjRbRboTG9Lg9fec8MxJp/cfTHAocg4mnismQR8A==",
"version": "4.46.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.46.2.tgz",
"integrity": "sha512-i7wfGFXu8x4+FRqPymzjD+Hyav8l95UIZ773j7J7zRYc3Xsxy2wIn4x+llpunexXe6laaO72iEjeeGyUFmjKeA==",
"cpu": [
"loong64"
],
@@ -503,9 +503,9 @@
]
},
"node_modules/@rollup/rollup-linux-ppc64-gnu": {
"version": "4.46.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.46.0.tgz",
"integrity": "sha512-wJaRvcT17PoOK6Ggcfo3nouFlybHvARBS4jzT0PC/lg17fIJHcDS2fZz3sD+iA4nRlho2zE6OGbU0HvwATdokQ==",
"version": "4.46.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.46.2.tgz",
"integrity": "sha512-B/l0dFcHVUnqcGZWKcWBSV2PF01YUt0Rvlurci5P+neqY/yMKchGU8ullZvIv5e8Y1C6wOn+U03mrDylP5q9Yw==",
"cpu": [
"ppc64"
],
@@ -516,9 +516,9 @@
]
},
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
"version": "4.46.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.46.0.tgz",
"integrity": "sha512-GZ5bkMFteAGkcmh8x0Ok4LSa+L62Ez0tMsHPX6JtR0wl4Xc3bQcrFHDiR5DGLEDFtGrXih4Nd/UDaFqs968/wA==",
"version": "4.46.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.46.2.tgz",
"integrity": "sha512-32k4ENb5ygtkMwPMucAb8MtV8olkPT03oiTxJbgkJa7lJ7dZMr0GCFJlyvy+K8iq7F/iuOr41ZdUHaOiqyR3iQ==",
"cpu": [
"riscv64"
],
@@ -529,9 +529,9 @@
]
},
"node_modules/@rollup/rollup-linux-riscv64-musl": {
"version": "4.46.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.46.0.tgz",
"integrity": "sha512-7CjPw6FflFsVOUfWOrVrREiV3IYXG4RzZ1ZQUaT3BtSK8YXN6x286o+sruPZJESIaPebYuFowmg54ZdrkVBYog==",
"version": "4.46.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.46.2.tgz",
"integrity": "sha512-t5B2loThlFEauloaQkZg9gxV05BYeITLvLkWOkRXogP4qHXLkWSbSHKM9S6H1schf/0YGP/qNKtiISlxvfmmZw==",
"cpu": [
"riscv64"
],
@@ -542,9 +542,9 @@
]
},
"node_modules/@rollup/rollup-linux-s390x-gnu": {
"version": "4.46.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.46.0.tgz",
"integrity": "sha512-nmvnl0ZiuysltcB/cKjUh40Rx4FbSyueERDsl2FLvLYr6pCgSsvGr3SocUT84svSpmloS7f1DRWqtRha74Gi1w==",
"version": "4.46.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.46.2.tgz",
"integrity": "sha512-YKjekwTEKgbB7n17gmODSmJVUIvj8CX7q5442/CK80L8nqOUbMtf8b01QkG3jOqyr1rotrAnW6B/qiHwfcuWQA==",
"cpu": [
"s390x"
],
@@ -555,9 +555,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-gnu": {
"version": "4.46.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.46.0.tgz",
"integrity": "sha512-Cv+moII5C8RM6gZbR3cb21o6rquVDZrN2o81maROg1LFzBz2dZUwIQSxFA8GtGZ/F2KtsqQ2z3eFPBb6akvQNg==",
"version": "4.46.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.46.2.tgz",
"integrity": "sha512-Jj5a9RUoe5ra+MEyERkDKLwTXVu6s3aACP51nkfnK9wJTraCC8IMe3snOfALkrjTYd2G1ViE1hICj0fZ7ALBPA==",
"cpu": [
"x64"
],
@@ -568,9 +568,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-musl": {
"version": "4.46.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.46.0.tgz",
"integrity": "sha512-PHcMG8DZTM9RCIjp8QIfN0VYtX0TtBPnWOTRurFhoCDoi9zptUZL2k7pCs+5rgut7JAiUsYy+huyhVKPcmxoog==",
"version": "4.46.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.46.2.tgz",
"integrity": "sha512-7kX69DIrBeD7yNp4A5b81izs8BqoZkCIaxQaOpumcJ1S/kmqNFjPhDu1LHeVXv0SexfHQv5cqHsxLOjETuqDuA==",
"cpu": [
"x64"
],
@@ -581,9 +581,9 @@
]
},
"node_modules/@rollup/rollup-win32-arm64-msvc": {
"version": "4.46.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.46.0.tgz",
"integrity": "sha512-1SI/Rd47e8aQJeFWMDg16ET+fjvCcD/CzeaRmIEPmb05hx+3cCcwIF4ebUag4yTt/D1peE+Mgp0+Po3M358cAA==",
"version": "4.46.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.46.2.tgz",
"integrity": "sha512-wiJWMIpeaak/jsbaq2HMh/rzZxHVW1rU6coyeNNpMwk5isiPjSTx0a4YLSlYDwBH/WBvLz+EtsNqQScZTLJy3g==",
"cpu": [
"arm64"
],
@@ -594,9 +594,9 @@
]
},
"node_modules/@rollup/rollup-win32-ia32-msvc": {
"version": "4.46.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.46.0.tgz",
"integrity": "sha512-JwOCYxmumFDfDhx4kNyz6kTVK3gWzBIvVdMNzQMRDubcoGRDniOOmo6DDNP42qwZx3Bp9/6vWJ+kNzNqXoHmeA==",
"version": "4.46.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.46.2.tgz",
"integrity": "sha512-gBgaUDESVzMgWZhcyjfs9QFK16D8K6QZpwAaVNJxYDLHWayOta4ZMjGm/vsAEy3hvlS2GosVFlBlP9/Wb85DqQ==",
"cpu": [
"ia32"
],
@@ -607,9 +607,9 @@
]
},
"node_modules/@rollup/rollup-win32-x64-msvc": {
"version": "4.46.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.46.0.tgz",
"integrity": "sha512-IPMIfrfkG1GaEXi+JSsQEx8x9b4b+hRZXO7KYc2pKio3zO2/VDXDs6B9Ts/nnO+25Fk1tdAVtUn60HKKPPzDig==",
"version": "4.46.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.46.2.tgz",
"integrity": "sha512-CvUo2ixeIQGtF6WvuB87XWqPQkoFAFqW+HUo/WzHwuHDvIwZCtjdWXoYCcr06iKGydiqTclC4jU/TNObC/xKZg==",
"cpu": [
"x64"
],
@@ -799,9 +799,9 @@
}
},
"node_modules/rollup": {
"version": "4.46.0",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.46.0.tgz",
"integrity": "sha512-ONmkT3Ud3IfW15nl7l4qAZko5/2iZ5ALVBDh02ZSZ5IGVLJSYkRcRa3iB58VyEIyoofs9m2xdVrm+lTi97+3pw==",
"version": "4.46.2",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.46.2.tgz",
"integrity": "sha512-WMmLFI+Boh6xbop+OAGo9cQ3OgX9MIg7xOQjn+pTCwOkk+FNDAeAemXkJ3HzDJrVXleLOFVa1ipuc1AmEx1Dwg==",
"license": "MIT",
"dependencies": {
"@types/estree": "1.0.8"
@@ -814,26 +814,26 @@
"npm": ">=8.0.0"
},
"optionalDependencies": {
"@rollup/rollup-android-arm-eabi": "4.46.0",
"@rollup/rollup-android-arm64": "4.46.0",
"@rollup/rollup-darwin-arm64": "4.46.0",
"@rollup/rollup-darwin-x64": "4.46.0",
"@rollup/rollup-freebsd-arm64": "4.46.0",
"@rollup/rollup-freebsd-x64": "4.46.0",
"@rollup/rollup-linux-arm-gnueabihf": "4.46.0",
"@rollup/rollup-linux-arm-musleabihf": "4.46.0",
"@rollup/rollup-linux-arm64-gnu": "4.46.0",
"@rollup/rollup-linux-arm64-musl": "4.46.0",
"@rollup/rollup-linux-loongarch64-gnu": "4.46.0",
"@rollup/rollup-linux-ppc64-gnu": "4.46.0",
"@rollup/rollup-linux-riscv64-gnu": "4.46.0",
"@rollup/rollup-linux-riscv64-musl": "4.46.0",
"@rollup/rollup-linux-s390x-gnu": "4.46.0",
"@rollup/rollup-linux-x64-gnu": "4.46.0",
"@rollup/rollup-linux-x64-musl": "4.46.0",
"@rollup/rollup-win32-arm64-msvc": "4.46.0",
"@rollup/rollup-win32-ia32-msvc": "4.46.0",
"@rollup/rollup-win32-x64-msvc": "4.46.0",
"@rollup/rollup-android-arm-eabi": "4.46.2",
"@rollup/rollup-android-arm64": "4.46.2",
"@rollup/rollup-darwin-arm64": "4.46.2",
"@rollup/rollup-darwin-x64": "4.46.2",
"@rollup/rollup-freebsd-arm64": "4.46.2",
"@rollup/rollup-freebsd-x64": "4.46.2",
"@rollup/rollup-linux-arm-gnueabihf": "4.46.2",
"@rollup/rollup-linux-arm-musleabihf": "4.46.2",
"@rollup/rollup-linux-arm64-gnu": "4.46.2",
"@rollup/rollup-linux-arm64-musl": "4.46.2",
"@rollup/rollup-linux-loongarch64-gnu": "4.46.2",
"@rollup/rollup-linux-ppc64-gnu": "4.46.2",
"@rollup/rollup-linux-riscv64-gnu": "4.46.2",
"@rollup/rollup-linux-riscv64-musl": "4.46.2",
"@rollup/rollup-linux-s390x-gnu": "4.46.2",
"@rollup/rollup-linux-x64-gnu": "4.46.2",
"@rollup/rollup-linux-x64-musl": "4.46.2",
"@rollup/rollup-win32-arm64-msvc": "4.46.2",
"@rollup/rollup-win32-ia32-msvc": "4.46.2",
"@rollup/rollup-win32-x64-msvc": "4.46.2",
"fsevents": "~2.3.2"
}
},

View File

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

View File

@@ -1,3 +1,3 @@
speedscope@1.23.0
Sun Jul 6 20:04:28 PDT 2025
aa9bef50789a2989746b576fff182b6f01dfce6a
speedscope@1.23.1
Mon Aug 11 11:43:09 PDT 2025
0cec0f82c334aed6cf19d43cabeadcda0d95e0fc

File diff suppressed because one or more lines are too long

6
flake.lock generated
View File

@@ -20,11 +20,11 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1750622754,
"narHash": "sha256-kMhs+YzV4vPGfuTpD3mwzibWUE6jotw5Al2wczI0Pv8=",
"lastModified": 1753749649,
"narHash": "sha256-+jkEZxs7bfOKfBIk430K+tK9IvXlwzqQQnppC2ZKFj4=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "c7ab75210cb8cb16ddd8f290755d9558edde7ee1",
"rev": "1f08a4df998e21f4e8be8fb6fbf61d11a1a5076a",
"type": "github"
},
"original": {

View File

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

View File

@@ -81,14 +81,14 @@ public class TildeFriendsActivity extends Activity {
TildeFriendsActivity activity = this;
Log.w("tildefriends", "Watching for changes in: " + getFilesDir().toString());
observer = make_file_observer(getFilesDir().toString(), port_file_path);
observer.startWatching();
set_status("Starting server...");
server_thread = new Thread(new Runnable() {
@Override
public void run() {
Log.w("tildefriends", "Watching for changes in: " + getFilesDir().toString());
observer = make_file_observer(getFilesDir().toString(), port_file_path);
observer.startWatching();
Log.w("tildefriends", "Calling tf_server_main.");
int result = tf_server_main(
getFilesDir().toString(),
@@ -426,7 +426,7 @@ public class TildeFriendsActivity extends Activity {
Log.w("tildefriends", "onServiceDisconnected");
}
};
s_activity.bindService(intent, s_activity.service_connection, BIND_AUTO_CREATE);
s_activity.bindService(intent, s_activity.service_connection, BIND_AUTO_CREATE | BIND_NOT_FOREGROUND);
}
public static void stop_sandbox() {
@@ -445,6 +445,7 @@ public class TildeFriendsActivity extends Activity {
hide_status();
web_view.loadUrl(base_url + "login/auto");
});
observer.stopWatching();
observer = null;
} else {
runOnUiThread(() -> {

View File

@@ -13,13 +13,13 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>0.0.33</string>
<string>0.2025.8</string>
<key>CFBundleSupportedPlatforms</key>
<array>
<string>iPhoneOS</string>
</array>
<key>CFBundleVersion</key>
<string>15</string>
<string>16</string>
<key>DTPlatformName</key>
<string>iphoneos</string>
<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;
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
{
@@ -235,6 +244,9 @@ typedef struct _tf_ssb_t
tf_ssb_message_added_callback_node_t* message_added;
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;
int blob_want_added_count;
@@ -2741,6 +2753,17 @@ void tf_ssb_destroy(tf_ssb_t* ssb)
}
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)
{
tf_ssb_blob_want_added_callback_node_t* node = ssb->blob_want_added;
@@ -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)
{
tf_ssb_blob_stored_callback_node_t* next = NULL;
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)

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);
/**
** 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.
** @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);
}
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)
{
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));
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)
{
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));
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)
{
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)
{
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 };
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);
tf_ssb_notify_blob_stored(ssb0, blob_id);
tf_ssb_remove_blob_stored_callback(ssb0, _blob_stored, &blob_stored);
assert(b);
assert(blob_stored);
JSContext* context0 = tf_ssb_get_context(ssb0);
JSValue obj = JS_NewObject(context0);

View File

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