Compare commits
17 Commits
v0.0.33
...
7cec0f7d61
Author | SHA1 | Date | |
---|---|---|---|
7cec0f7d61 | |||
f902d0374c | |||
b5f0a0c4f7 | |||
00623cea09 | |||
ed4f1d6f2c | |||
73f4a3407f | |||
6f11318e84 | |||
e88ee91f0e | |||
3f8daf257c | |||
dc387acadc | |||
68aa41ab96 | |||
85b23437b3 | |||
c59fba817d | |||
c3415ab75c | |||
f1d0151d71 | |||
3c5c1756d1 | |||
6a6b65d1b3 |
16
GNUmakefile
16
GNUmakefile
@@ -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 := 40
|
VERSION_CODE := 41
|
||||||
VERSION_CODE_IOS := 15
|
VERSION_CODE_IOS := 16
|
||||||
VERSION_NUMBER := 0.0.33
|
VERSION_NUMBER := 0.2025.8-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 += \
|
||||||
@@ -1140,6 +1143,11 @@ releaseapkgo: out/TildeFriends-arm-release.apk ## Build, install, and run a rele
|
|||||||
@adb shell am start com.unprompted.tildefriends/.TildeFriendsActivity
|
@adb shell am start com.unprompted.tildefriends/.TildeFriendsActivity
|
||||||
.PHONY: releaseapkgo
|
.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.
|
apklog: ## Display Android log output.
|
||||||
@adb logcat *:S tildefriends
|
@adb logcat *:S tildefriends
|
||||||
.PHONY: apklog
|
.PHONY: apklog
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"type": "tildefriends-app",
|
"type": "tildefriends-app",
|
||||||
"emoji": "🦀",
|
"emoji": "🦀",
|
||||||
"previous": "&DGtlnm5wWRZCgJMF8JsP6VtzNRrd4KLoERJRpFULqOY=.sha256"
|
"previous": "&5T+xPy3LhgmU2ape4dlJLRhYhmE5J1SQkI+wFm6Fss4=.sha256"
|
||||||
}
|
}
|
||||||
|
@@ -22,9 +22,11 @@ class TfElement extends LitElement {
|
|||||||
guest: {type: Boolean},
|
guest: {type: Boolean},
|
||||||
url: {type: String},
|
url: {type: String},
|
||||||
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},
|
||||||
|
progress: {type: Number},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,6 +58,7 @@ class TfElement extends LitElement {
|
|||||||
tfrpc.rpc.getHash().then((hash) => self.set_hash(hash));
|
tfrpc.rpc.getHash().then((hash) => self.set_hash(hash));
|
||||||
tfrpc.register(function hashChanged(hash) {
|
tfrpc.register(function hashChanged(hash) {
|
||||||
self.set_hash(hash);
|
self.set_hash(hash);
|
||||||
|
self.reset_progress();
|
||||||
});
|
});
|
||||||
tfrpc.register(async function notifyNewMessage(id) {
|
tfrpc.register(async function notifyNewMessage(id) {
|
||||||
await self.fetch_new_message(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),
|
...this.channels.map((x) => '#' + x),
|
||||||
];
|
];
|
||||||
let index = channel_names.indexOf(this.hash.substring(1));
|
let index = channel_names.indexOf(this.hash.substring(1));
|
||||||
@@ -364,6 +369,32 @@ 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.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) {
|
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);
|
||||||
@@ -436,12 +467,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]
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -450,7 +484,28 @@ class TfElement extends LitElement {
|
|||||||
this.schedule_load_latest();
|
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() {
|
schedule_load_latest() {
|
||||||
|
this.reset_progress();
|
||||||
if (!this.loading_latest) {
|
if (!this.loading_latest) {
|
||||||
this.shadowRoot.getElementById('tf-tab-news')?.load_latest();
|
this.shadowRoot.getElementById('tf-tab-news')?.load_latest();
|
||||||
this.load();
|
this.load();
|
||||||
@@ -495,6 +550,7 @@ class TfElement extends LitElement {
|
|||||||
|
|
||||||
async load() {
|
async load() {
|
||||||
this.loading_latest = true;
|
this.loading_latest = true;
|
||||||
|
this.reset_progress();
|
||||||
try {
|
try {
|
||||||
let start_time = new Date();
|
let start_time = new Date();
|
||||||
let whoami = this.whoami;
|
let whoami = this.whoami;
|
||||||
@@ -603,8 +659,10 @@ class TfElement extends LitElement {
|
|||||||
@channelsetunread=${this.channel_set_unread}
|
@channelsetunread=${this.channel_set_unread}
|
||||||
@refresh=${this.refresh}
|
@refresh=${this.refresh}
|
||||||
@toggle_stay_connected=${this.toggle_stay_connected}
|
@toggle_stay_connected=${this.toggle_stay_connected}
|
||||||
|
@loadmessages=${this.reset_progress}
|
||||||
.connections=${this.connections}
|
.connections=${this.connections}
|
||||||
.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}
|
||||||
?is_administrator=${this.is_administrator}
|
?is_administrator=${this.is_administrator}
|
||||||
?stay_connected=${this.stay_connected}
|
?stay_connected=${this.stay_connected}
|
||||||
@@ -646,6 +704,7 @@ class TfElement extends LitElement {
|
|||||||
async set_tab(tab) {
|
async set_tab(tab) {
|
||||||
this.tab = tab;
|
this.tab = tab;
|
||||||
if (tab === 'news') {
|
if (tab === 'news') {
|
||||||
|
this.schedule_load_latest();
|
||||||
await tfrpc.rpc.setHash('#');
|
await tfrpc.rpc.setHash('#');
|
||||||
} else if (tab === 'connections') {
|
} else if (tab === 'connections') {
|
||||||
await tfrpc.rpc.setHash('#connections');
|
await tfrpc.rpc.setHash('#connections');
|
||||||
@@ -751,11 +810,23 @@ class TfElement extends LitElement {
|
|||||||
Loading...
|
Loading...
|
||||||
</div>`
|
</div>`
|
||||||
: this.render_tab();
|
: 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`
|
return html`
|
||||||
<div
|
<div
|
||||||
style="width: 100vw; min-height: 100vh; height: 100vh; display: flex; flex-direction: column"
|
style="width: 100vw; min-height: 100vh; height: 100vh; display: flex; flex-direction: column"
|
||||||
class="w3-theme-dark"
|
class="w3-theme-dark"
|
||||||
>
|
>
|
||||||
|
${progress}
|
||||||
<div style="flex: 0 0">${tabs}</div>
|
<div style="flex: 0 0">${tabs}</div>
|
||||||
<div style="flex: 1 1; overflow: auto; contain: layout">
|
<div style="flex: 1 1; overflow: auto; contain: layout">
|
||||||
${contents}
|
${contents}
|
||||||
|
@@ -789,60 +789,45 @@ class TfMessageElement extends LitElement {
|
|||||||
</div>
|
</div>
|
||||||
`);
|
`);
|
||||||
} else if (content.type == 'contact') {
|
} else if (content.type == 'contact') {
|
||||||
return this.render_frame(html`
|
switch (this.format) {
|
||||||
<div class="w3-bar">
|
case 'message':
|
||||||
<div class="w3-bar-item">
|
default:
|
||||||
<tf-user id=${this.message.author} .users=${this.users}></tf-user>
|
return this.render_frame(html`
|
||||||
is
|
<div class="w3-bar">
|
||||||
${content.blocking === true
|
<div class="w3-bar-item">
|
||||||
? 'blocking'
|
<tf-user
|
||||||
: content.blocking === false
|
id=${this.message.author}
|
||||||
? 'no longer blocking'
|
.users=${this.users}
|
||||||
: content.following === true
|
></tf-user>
|
||||||
? 'following'
|
is
|
||||||
: content.following === false
|
${content.blocking === true
|
||||||
? 'no longer following'
|
? 'blocking'
|
||||||
: '?'}
|
: content.blocking === false
|
||||||
<tf-user
|
? 'no longer blocking'
|
||||||
id=${this.message.content.contact}
|
: content.following === true
|
||||||
.users=${this.users}
|
? 'following'
|
||||||
></tf-user>
|
: content.following === false
|
||||||
</div>
|
? 'no longer following'
|
||||||
<div class="w3-bar-item w3-right">
|
: '?'}
|
||||||
<button class="w3-button w3-theme-d1" @click=${this.toggle_menu}>
|
<tf-user
|
||||||
%
|
id=${this.message.content.contact}
|
||||||
</button>
|
.users=${this.users}
|
||||||
<div
|
></tf-user>
|
||||||
class="w3-dropdown-content w3-bar-block w3-card-4 w3-theme-l1"
|
</div>
|
||||||
style="right: 48px"
|
${this.render_menu()} ${this.render_votes()}
|
||||||
>
|
${this.render_actions()}
|
||||||
<a
|
|
||||||
target="_top"
|
|
||||||
class="w3-button w3-bar-item"
|
|
||||||
href=${'#' + encodeURIComponent(this.message?.id)}
|
|
||||||
>View Message</a
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
class="w3-button w3-bar-item w3-border-bottom"
|
|
||||||
@click=${this.copy_id}
|
|
||||||
>
|
|
||||||
Copy ID
|
|
||||||
</button>
|
|
||||||
${this.drafts[this.message?.id] === undefined
|
|
||||||
? html`
|
|
||||||
<button
|
|
||||||
class="w3-button w3-bar-item"
|
|
||||||
@click=${this.show_reply}
|
|
||||||
>
|
|
||||||
↩️ Reply
|
|
||||||
</button>
|
|
||||||
`
|
|
||||||
: undefined}
|
|
||||||
</div>
|
</div>
|
||||||
|
`);
|
||||||
|
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>
|
</div>
|
||||||
${this.render_votes()} ${this.render_actions()}
|
`);
|
||||||
</div>
|
break;
|
||||||
`);
|
}
|
||||||
} else if (content.type == 'post') {
|
} else if (content.type == 'post') {
|
||||||
let self = this;
|
let self = this;
|
||||||
let body;
|
let body;
|
||||||
|
@@ -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},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -106,6 +107,12 @@ class TfTabNewsFeedElement extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fetch_messages(start_time, end_time) {
|
async fetch_messages(start_time, end_time) {
|
||||||
|
this.dispatchEvent(
|
||||||
|
new CustomEvent('loadmessages', {
|
||||||
|
bubbles: true,
|
||||||
|
composed: true,
|
||||||
|
})
|
||||||
|
);
|
||||||
this.time_loading = [start_time, end_time];
|
this.time_loading = [start_time, end_time];
|
||||||
let result;
|
let result;
|
||||||
const k_max_results = 64;
|
const k_max_results = 64;
|
||||||
@@ -221,6 +228,31 @@ class TfTabNewsFeedElement extends LitElement {
|
|||||||
]
|
]
|
||||||
);
|
);
|
||||||
result = (await this.decrypt(result)).filter((x) => x.decrypted);
|
result = (await this.decrypt(result)).filter((x) => x.decrypted);
|
||||||
|
} else if (this.hash.startsWith('#🔐')) {
|
||||||
|
let ids = this.hash.substring('#🔐'.length).split(',');
|
||||||
|
console.log(this.grouped_private_messages);
|
||||||
|
result = await tfrpc.rpc.query(
|
||||||
|
`
|
||||||
|
SELECT TRUE AS is_primary, messages.rowid, messages.id, previous, author, sequence, timestamp, hash, json(content) AS content, signature
|
||||||
|
FROM messages
|
||||||
|
JOIN json_each(?1) AS private_messages ON messages.id = private_messages.value
|
||||||
|
WHERE
|
||||||
|
(?2 IS NULL OR (messages.timestamp >= ?2)) AND messages.timestamp < ?3 AND
|
||||||
|
json(messages.content) LIKE '"%'
|
||||||
|
ORDER BY messages.rowid DESC LIMIT ?4
|
||||||
|
`,
|
||||||
|
[
|
||||||
|
JSON.stringify(
|
||||||
|
this.grouped_private_messages?.[JSON.stringify(ids)]?.map(
|
||||||
|
(x) => x.id
|
||||||
|
) ?? []
|
||||||
|
),
|
||||||
|
start_time,
|
||||||
|
end_time,
|
||||||
|
k_max_results,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
result = (await this.decrypt(result)).filter((x) => x.decrypted);
|
||||||
} else if (this.hash == '#👍') {
|
} else if (this.hash == '#👍') {
|
||||||
result = await tfrpc.rpc.query(
|
result = await tfrpc.rpc.query(
|
||||||
`
|
`
|
||||||
@@ -378,7 +410,8 @@ class TfTabNewsFeedElement extends LitElement {
|
|||||||
this.messages = [];
|
this.messages = [];
|
||||||
this._messages_hash = this.hash;
|
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);
|
||||||
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;
|
||||||
@@ -421,8 +454,8 @@ class TfTabNewsFeedElement extends LitElement {
|
|||||||
if (
|
if (
|
||||||
!this.messages ||
|
!this.messages ||
|
||||||
this._messages_hash !== this.hash ||
|
this._messages_hash !== this.hash ||
|
||||||
JSON.stringify(this._messages_following) !==
|
this._messages_following !== JSON.stringify(this.following) ||
|
||||||
JSON.stringify(this.following)
|
this._private_messages !== JSON.stringify(this.private_messages)
|
||||||
) {
|
) {
|
||||||
console.log(
|
console.log(
|
||||||
`loading messages for ${this.whoami} (following ${this.following.length})`
|
`loading messages for ${this.whoami} (following ${this.following.length})`
|
||||||
|
@@ -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},
|
||||||
@@ -180,6 +181,10 @@ class TfTabNewsElement extends LitElement {
|
|||||||
await this.check_peer_exchange();
|
await this.check_peer_exchange();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
is_loading() {
|
||||||
|
return this.shadowRoot?.getElementById('news')?.loading;
|
||||||
|
}
|
||||||
|
|
||||||
render_sidebar() {
|
render_sidebar() {
|
||||||
return html`
|
return html`
|
||||||
<div
|
<div
|
||||||
@@ -253,12 +258,28 @@ 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
|
||||||
>
|
>
|
||||||
<a
|
${Object.keys(this?.grouped_private_messages ?? [])
|
||||||
href="#🔐"
|
?.sort()
|
||||||
class="w3-bar-item w3-button"
|
?.map(
|
||||||
style=${this.hash == '#🔐' ? 'font-weight: bold' : undefined}
|
(key) => html`
|
||||||
>${this.unread_status('🔐')}🔐private</a
|
<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)
|
${Object.keys(this.drafts)
|
||||||
.sort()
|
.sort()
|
||||||
.map(
|
.map(
|
||||||
@@ -430,6 +451,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>
|
||||||
|
@@ -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>`;
|
||||||
|
37
core/app.js
37
core/app.js
@@ -1,7 +1,23 @@
|
|||||||
|
/**
|
||||||
|
* \file
|
||||||
|
* \defgroup tfapp Tilde Friends App JS
|
||||||
|
* Tilde Friends server-side app wrapper.
|
||||||
|
* @{
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** \cond */
|
||||||
import * as core from './core.js';
|
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() {
|
function App() {
|
||||||
this._send_queue = [];
|
this._send_queue = [];
|
||||||
this.calls = {};
|
this.calls = {};
|
||||||
@@ -9,6 +25,12 @@ function App() {
|
|||||||
return this;
|
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) {
|
App.prototype.makeFunction = function (api) {
|
||||||
let self = this;
|
let self = this;
|
||||||
let result = function () {
|
let result = function () {
|
||||||
@@ -32,6 +54,10 @@ App.prototype.makeFunction = function (api) {
|
|||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
** Send a message to the app.
|
||||||
|
** @param message The message to send.
|
||||||
|
*/
|
||||||
App.prototype.send = function (message) {
|
App.prototype.send = function (message) {
|
||||||
if (this._send_queue) {
|
if (this._send_queue) {
|
||||||
if (this._on_output) {
|
if (this._on_output) {
|
||||||
@@ -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) {
|
exports.app_socket = async function socket(request, response) {
|
||||||
let process;
|
let process;
|
||||||
let options = {};
|
let options = {};
|
||||||
@@ -133,7 +164,7 @@ exports.app_socket = async function socket(request, response) {
|
|||||||
options.packageOwner = packageOwner;
|
options.packageOwner = packageOwner;
|
||||||
options.packageName = packageName;
|
options.packageName = packageName;
|
||||||
options.url = message.url;
|
options.url = message.url;
|
||||||
let sessionId = 'session_' + (gSessionIndex++).toString();
|
let sessionId = 'session_' + (g_session_index++).toString();
|
||||||
if (blobId) {
|
if (blobId) {
|
||||||
if (message.edit_only) {
|
if (message.edit_only) {
|
||||||
response.send(
|
response.send(
|
||||||
@@ -218,4 +249,4 @@ exports.app_socket = async function socket(request, response) {
|
|||||||
response.upgrade(100, {});
|
response.upgrade(100, {});
|
||||||
};
|
};
|
||||||
|
|
||||||
export {App};
|
/** @} */
|
||||||
|
125
core/client.js
125
core/client.js
@@ -72,7 +72,7 @@ class TfNavigationElement extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODOC
|
* Toggle editor visibility.
|
||||||
* @param event The HTML event.
|
* @param event The HTML event.
|
||||||
*/
|
*/
|
||||||
toggle_edit(event) {
|
toggle_edit(event) {
|
||||||
@@ -85,7 +85,7 @@ class TfNavigationElement extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODOC
|
* Remove a stored permission.
|
||||||
* @param key The permission to reset.
|
* @param key The permission to reset.
|
||||||
*/
|
*/
|
||||||
reset_permission(key) {
|
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 key The spark line identifier.
|
||||||
* @param options Spark line options.
|
* @param options Spark line options.
|
||||||
* @return A spark line HTML element.
|
* @return A spark line HTML element.
|
||||||
@@ -262,8 +262,8 @@ class TfNavigationElement extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODOC
|
* Render the permissions popup.
|
||||||
* @returns
|
* @return Lit HTML.
|
||||||
*/
|
*/
|
||||||
render_permissions() {
|
render_permissions() {
|
||||||
if (this.show_permissions) {
|
if (this.show_permissions) {
|
||||||
@@ -312,8 +312,8 @@ class TfNavigationElement extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODOC
|
* Render the navigation bar.
|
||||||
* @returns
|
* @return Lit HTML.
|
||||||
*/
|
*/
|
||||||
render() {
|
render() {
|
||||||
let self = this;
|
let self = this;
|
||||||
@@ -441,7 +441,7 @@ class TfNavigationElement extends LitElement {
|
|||||||
customElements.define('tf-navigation', TfNavigationElement);
|
customElements.define('tf-navigation', TfNavigationElement);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODOC
|
* A file in the files sidebar.
|
||||||
*/
|
*/
|
||||||
class TfFilesElement extends LitElement {
|
class TfFilesElement extends LitElement {
|
||||||
/**
|
/**
|
||||||
@@ -467,7 +467,7 @@ class TfFilesElement extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODOC
|
* Select a clicked file.
|
||||||
* @param file The file.
|
* @param file The file.
|
||||||
*/
|
*/
|
||||||
file_click(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.
|
* @param file The file.
|
||||||
* @returns Lit HTML.
|
* @return Lit HTML.
|
||||||
*/
|
*/
|
||||||
render_file(file) {
|
render_file(file) {
|
||||||
let classes = ['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.
|
* @param event The event.
|
||||||
*/
|
*/
|
||||||
async drop(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.
|
* @param event The event.
|
||||||
*/
|
*/
|
||||||
drag_enter(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.
|
* @param event The event.
|
||||||
*/
|
*/
|
||||||
drag_leave(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.
|
* @param event The event.
|
||||||
*/
|
*/
|
||||||
drag_over(event) {
|
drag_over(event) {
|
||||||
@@ -562,8 +562,8 @@ class TfFilesElement extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODOC
|
* Render the file.
|
||||||
* @returns
|
* @return Lit HTML.
|
||||||
*/
|
*/
|
||||||
render() {
|
render() {
|
||||||
let self = this;
|
let self = this;
|
||||||
@@ -610,7 +610,7 @@ class TfFilesElement extends LitElement {
|
|||||||
customElements.define('tf-files', TfFilesElement);
|
customElements.define('tf-files', TfFilesElement);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODOC
|
* The files pane element.
|
||||||
*/
|
*/
|
||||||
class TfFilesPaneElement extends LitElement {
|
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.
|
* @param expanded Whether the files pane is expanded.
|
||||||
*/
|
*/
|
||||||
set_expanded(expanded) {
|
set_expanded(expanded) {
|
||||||
@@ -644,8 +644,8 @@ class TfFilesPaneElement extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODOC
|
* Render the files pane element.
|
||||||
* @returns
|
* @return Lit HTML.
|
||||||
*/
|
*/
|
||||||
render() {
|
render() {
|
||||||
let self = this;
|
let self = this;
|
||||||
@@ -704,7 +704,7 @@ class TfFilesPaneElement extends LitElement {
|
|||||||
customElements.define('tf-files-pane', TfFilesPaneElement);
|
customElements.define('tf-files-pane', TfFilesPaneElement);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODOC
|
* A tiny graph.
|
||||||
*/
|
*/
|
||||||
class TfSparkLineElement extends LitElement {
|
class TfSparkLineElement extends LitElement {
|
||||||
static get properties() {
|
static get properties() {
|
||||||
@@ -724,9 +724,9 @@ class TfSparkLineElement extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODOC
|
* Add a data point to the graph.
|
||||||
* @param {*} key
|
* @param key The line to which the point applies.
|
||||||
* @param {*} value
|
* @param value The numeric value of the data point.
|
||||||
*/
|
*/
|
||||||
append(key, value) {
|
append(key, value) {
|
||||||
let line = null;
|
let line = null;
|
||||||
@@ -753,9 +753,9 @@ class TfSparkLineElement extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODOC
|
* Render a single series line.
|
||||||
* @param {*} line
|
* @param line The line data.
|
||||||
* @returns
|
* @return Lit HTML.
|
||||||
*/
|
*/
|
||||||
render_line(line) {
|
render_line(line) {
|
||||||
if (line?.values?.length >= 2) {
|
if (line?.values?.length >= 2) {
|
||||||
@@ -771,8 +771,8 @@ class TfSparkLineElement extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODOC
|
* Render the graph.
|
||||||
* @returns
|
* @return Lit HTML.
|
||||||
*/
|
*/
|
||||||
render() {
|
render() {
|
||||||
let max =
|
let max =
|
||||||
@@ -799,7 +799,9 @@ class TfSparkLineElement extends LitElement {
|
|||||||
|
|
||||||
customElements.define('tf-sparkline', TfSparkLineElement);
|
customElements.define('tf-sparkline', TfSparkLineElement);
|
||||||
|
|
||||||
// TODOC
|
/**
|
||||||
|
* A keyboard key is pressed down.
|
||||||
|
*/
|
||||||
window.addEventListener('keydown', function (event) {
|
window.addEventListener('keydown', function (event) {
|
||||||
if (event.keyCode == 83 && (event.altKey || event.ctrlKey)) {
|
if (event.keyCode == 83 && (event.altKey || event.ctrlKey)) {
|
||||||
if (editing()) {
|
if (editing()) {
|
||||||
@@ -860,24 +862,23 @@ function ensureLoaded(nodes, callback) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODOC
|
* Check whether the editior is currently visible.
|
||||||
* @returns
|
* @return true if the editor is visible.
|
||||||
*/
|
*/
|
||||||
function editing() {
|
function editing() {
|
||||||
return document.getElementById('editPane').style.display != 'none';
|
return document.getElementById('editPane').style.display != 'none';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODOC
|
* Check whether only the editor is visible and the app is hidden.
|
||||||
* @returns
|
* @return true if the editor is visible and the app is not.
|
||||||
*/
|
*/
|
||||||
function is_edit_only() {
|
function is_edit_only() {
|
||||||
return window.location.search == '?editonly=1' || window.innerWidth < 1024;
|
return window.location.search == '?editonly=1' || window.innerWidth < 1024;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODOC
|
* Show the editor.
|
||||||
* @returns
|
|
||||||
*/
|
*/
|
||||||
async function edit() {
|
async function edit() {
|
||||||
if (editing()) {
|
if (editing()) {
|
||||||
@@ -904,7 +905,7 @@ async function edit() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODOC
|
* Open a performance trace.
|
||||||
*/
|
*/
|
||||||
function trace() {
|
function trace() {
|
||||||
window.open(`/speedscope/#profileURL=${encodeURIComponent('/trace')}`);
|
window.open(`/speedscope/#profileURL=${encodeURIComponent('/trace')}`);
|
||||||
@@ -982,7 +983,7 @@ async function load(path) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODOC
|
* Hide the editor.
|
||||||
*/
|
*/
|
||||||
function closeEditor() {
|
function closeEditor() {
|
||||||
window.localStorage.setItem('editing', '0');
|
window.localStorage.setItem('editing', '0');
|
||||||
@@ -990,14 +991,6 @@ function closeEditor() {
|
|||||||
document.getElementById('viewPane').style.display = 'flex';
|
document.getElementById('viewPane').style.display = 'flex';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* TODOC
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
function explodePath() {
|
|
||||||
return /^\/~([^\/]+)\/([^\/]+)(.*)/.exec(window.location.pathname);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Save the app.
|
* Save the app.
|
||||||
* @param save_to An optional path to which to 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() {
|
function changeIcon() {
|
||||||
let value = prompt('Enter a new app icon emoji:');
|
let value = prompt('Enter a new app icon emoji:');
|
||||||
@@ -1122,7 +1115,7 @@ function changeIcon() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODOC
|
* Prompt to delete the current app.
|
||||||
*/
|
*/
|
||||||
function deleteApp() {
|
function deleteApp() {
|
||||||
let name = document.getElementById('name');
|
let name = document.getElementById('name');
|
||||||
@@ -1143,8 +1136,8 @@ function deleteApp() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODOC
|
* Get the current app URL.
|
||||||
* @returns
|
* @return The app URL.
|
||||||
*/
|
*/
|
||||||
function url() {
|
function url() {
|
||||||
let hash = window.location.href.indexOf('#');
|
let hash = window.location.href.indexOf('#');
|
||||||
@@ -1162,8 +1155,8 @@ function url() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODOC
|
* Get the window hash without the lone '#' if it is empty.
|
||||||
* @returns
|
* @return The hash.
|
||||||
*/
|
*/
|
||||||
function hash() {
|
function hash() {
|
||||||
return window.location.hash != '#' ? window.location.hash : '';
|
return window.location.hash != '#' ? window.location.hash : '';
|
||||||
@@ -1188,7 +1181,7 @@ function api_postMessage(message) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODOC
|
* Show an error.
|
||||||
* @param error The error.
|
* @param error The error.
|
||||||
*/
|
*/
|
||||||
function api_error(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() {
|
function api_print() {
|
||||||
console.log('app>', ...arguments);
|
console.log('app>', ...arguments);
|
||||||
@@ -1308,7 +1301,7 @@ function api_setHash(hash) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODOC
|
* Process an incoming WebSocket message.
|
||||||
* @param message The message.
|
* @param message The message.
|
||||||
*/
|
*/
|
||||||
function _receive_websocket_message(message) {
|
function _receive_websocket_message(message) {
|
||||||
@@ -1432,14 +1425,14 @@ function send(value) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODOC
|
* Notify the app of the window hash changing.
|
||||||
*/
|
*/
|
||||||
function hashChange() {
|
function hashChange() {
|
||||||
send({event: 'hashChange', hash: window.location.hash});
|
send({event: 'hashChange', hash: window.location.hash});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODOC
|
* Make sure the app is connected on window focus, and notify the app.
|
||||||
*/
|
*/
|
||||||
function focus() {
|
function focus() {
|
||||||
if (gSocket && gSocket.readyState == gSocket.CLOSED) {
|
if (gSocket && gSocket.readyState == gSocket.CLOSED) {
|
||||||
@@ -1450,7 +1443,7 @@ function focus() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODOC
|
* Notify the app of lost focus.
|
||||||
*/
|
*/
|
||||||
function blur() {
|
function blur() {
|
||||||
if (gSocket && gSocket.readyState == gSocket.OPEN) {
|
if (gSocket && gSocket.readyState == gSocket.OPEN) {
|
||||||
@@ -1617,7 +1610,7 @@ function openFile(name) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODOC
|
* Refresh the files list.
|
||||||
*/
|
*/
|
||||||
function updateFiles() {
|
function updateFiles() {
|
||||||
let files = document.getElementsByTagName('tf-files-pane')[0];
|
let files = document.getElementsByTagName('tf-files-pane')[0];
|
||||||
@@ -1650,7 +1643,7 @@ function makeNewFile(name) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODOC
|
* Prompt to create a new file.
|
||||||
*/
|
*/
|
||||||
function newFile() {
|
function newFile() {
|
||||||
let name = prompt('Name of new file:', 'file.js');
|
let name = prompt('Name of new file:', 'file.js');
|
||||||
@@ -1660,7 +1653,7 @@ function newFile() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODOC
|
* Prompt to remove a file.
|
||||||
*/
|
*/
|
||||||
function removeFile() {
|
function removeFile() {
|
||||||
if (confirm('Remove ' + gCurrentFile + '?')) {
|
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() {
|
async function appExport() {
|
||||||
let JsZip = (await import('/static/jszip.min.js')).default;
|
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() {
|
async function appImport() {
|
||||||
let JsZip = (await import('/static/jszip.min.js')).default;
|
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('load', function () {
|
||||||
window.addEventListener('hashchange', hashChange);
|
window.addEventListener('hashchange', hashChange);
|
||||||
window.addEventListener('focus', focus);
|
window.addEventListener('focus', focus);
|
||||||
|
34
core/http.js
34
core/http.js
@@ -1,8 +1,14 @@
|
|||||||
/**
|
/**
|
||||||
* TODOC
|
* \file
|
||||||
* TODO: document so we can improve this
|
* \defgroup tfhttp Tilde Friends HTTP Client JS
|
||||||
* @param {*} url
|
* Tilde Friends server-side HTTP client.
|
||||||
* @returns
|
* @{
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse a URL into protocol, host, path, and port parts.
|
||||||
|
* @param url
|
||||||
|
* @return An object of the URL parts.
|
||||||
*/
|
*/
|
||||||
function parseUrl(url) {
|
function parseUrl(url) {
|
||||||
// XXX: Hack.
|
// XXX: Hack.
|
||||||
@@ -16,9 +22,9 @@ function parseUrl(url) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODOC
|
* Parse an HTTP response into headers and body content.
|
||||||
* @param {*} data
|
* @param data The response data, headers and body included.
|
||||||
* @returns
|
* @return headers and body data.
|
||||||
*/
|
*/
|
||||||
function parseResponse(data) {
|
function parseResponse(data) {
|
||||||
let firstLine;
|
let firstLine;
|
||||||
@@ -36,15 +42,15 @@ function parseResponse(data) {
|
|||||||
headers[line.substring(colon)] = line.substring(colon + 1);
|
headers[line.substring(colon)] = line.substring(colon + 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return {body: data};
|
return {headers: headers, body: data};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODOC
|
* Make an HTTP request.
|
||||||
* @param {*} url
|
* @param url The URL.
|
||||||
* @param {*} options
|
* @param options Request options.
|
||||||
* @param {*} allowed_hosts
|
* @param allowed_hosts List of allowed hosts.
|
||||||
* @returns
|
* @return A promise resolved with the response headers and body.
|
||||||
*/
|
*/
|
||||||
export function fetch(url, options, allowed_hosts) {
|
export function fetch(url, options, allowed_hosts) {
|
||||||
let parsed = parseUrl(url);
|
let parsed = parseUrl(url);
|
||||||
@@ -111,3 +117,5 @@ export function fetch(url, options, allowed_hosts) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @} */
|
||||||
|
@@ -15,8 +15,8 @@ let g_next_id = 1;
|
|||||||
let g_calls = {};
|
let g_calls = {};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODOC
|
* Check if being called from a browser vs. server-side.
|
||||||
* @returns
|
* @return true if called from a browser.
|
||||||
*/
|
*/
|
||||||
function get_is_browser() {
|
function get_is_browser() {
|
||||||
try {
|
try {
|
||||||
|
@@ -25,14 +25,14 @@
|
|||||||
}:
|
}:
|
||||||
pkgs.stdenv.mkDerivation rec {
|
pkgs.stdenv.mkDerivation rec {
|
||||||
pname = "tildefriends";
|
pname = "tildefriends";
|
||||||
version = "0.0.32";
|
version = "0.0.33";
|
||||||
|
|
||||||
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-Dk0NOEQIg2LeENySK0+MgpZEtfsClGq6dZL+eOOpE0U=";
|
hash = "sha256-9D28gmaBTRVyXhY3zZd/W9PsXA1YZt/K69hz41aVP04=";
|
||||||
fetchSubmodules = true;
|
fetchSubmodules = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
2
deps/openssl_src
vendored
2
deps/openssl_src
vendored
Submodule deps/openssl_src updated: aea7aaf2ab...0893a62353
2
deps/speedscope/index.html
vendored
2
deps/speedscope/index.html
vendored
@@ -11,7 +11,7 @@
|
|||||||
<link rel="icon" type="image/x-icon" href="favicon-FOKUP5Y5.ico">
|
<link rel="icon" type="image/x-icon" href="favicon-FOKUP5Y5.ico">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<script src="speedscope-7YPLLUY2.js"></script>
|
<script src="speedscope-HCR63FMT.js"></script>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
6
deps/speedscope/release.txt
vendored
6
deps/speedscope/release.txt
vendored
@@ -1,3 +1,3 @@
|
|||||||
speedscope@1.23.0
|
speedscope@1.23.1
|
||||||
Sun Jul 6 20:04:28 PDT 2025
|
Mon Aug 11 11:43:09 PDT 2025
|
||||||
aa9bef50789a2989746b576fff182b6f01dfce6a
|
0cec0f82c334aed6cf19d43cabeadcda0d95e0fc
|
||||||
|
File diff suppressed because one or more lines are too long
6
flake.lock
generated
6
flake.lock
generated
@@ -20,11 +20,11 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1750622754,
|
"lastModified": 1753749649,
|
||||||
"narHash": "sha256-kMhs+YzV4vPGfuTpD3mwzibWUE6jotw5Al2wczI0Pv8=",
|
"narHash": "sha256-+jkEZxs7bfOKfBIk430K+tK9IvXlwzqQQnppC2ZKFj4=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "c7ab75210cb8cb16ddd8f290755d9558edde7ee1",
|
"rev": "1f08a4df998e21f4e8be8fb6fbf61d11a1a5076a",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
@@ -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="40"
|
android:versionCode="41"
|
||||||
android:versionName="0.0.33">
|
android:versionName="0.2025.8-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
|
||||||
|
@@ -81,14 +81,14 @@ public class TildeFriendsActivity extends Activity {
|
|||||||
|
|
||||||
TildeFriendsActivity activity = this;
|
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...");
|
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());
|
||||||
|
observer = make_file_observer(getFilesDir().toString(), port_file_path);
|
||||||
|
observer.startWatching();
|
||||||
|
|
||||||
Log.w("tildefriends", "Calling tf_server_main.");
|
Log.w("tildefriends", "Calling tf_server_main.");
|
||||||
int result = tf_server_main(
|
int result = tf_server_main(
|
||||||
getFilesDir().toString(),
|
getFilesDir().toString(),
|
||||||
@@ -426,7 +426,7 @@ public class TildeFriendsActivity extends Activity {
|
|||||||
Log.w("tildefriends", "onServiceDisconnected");
|
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() {
|
public static void stop_sandbox() {
|
||||||
@@ -445,6 +445,7 @@ public class TildeFriendsActivity extends Activity {
|
|||||||
hide_status();
|
hide_status();
|
||||||
web_view.loadUrl(base_url + "login/auto");
|
web_view.loadUrl(base_url + "login/auto");
|
||||||
});
|
});
|
||||||
|
observer.stopWatching();
|
||||||
observer = null;
|
observer = null;
|
||||||
} else {
|
} else {
|
||||||
runOnUiThread(() -> {
|
runOnUiThread(() -> {
|
||||||
|
@@ -13,13 +13,13 @@
|
|||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>APPL</string>
|
<string>APPL</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>0.0.33</string>
|
<string>0.2025.8</string>
|
||||||
<key>CFBundleSupportedPlatforms</key>
|
<key>CFBundleSupportedPlatforms</key>
|
||||||
<array>
|
<array>
|
||||||
<string>iPhoneOS</string>
|
<string>iPhoneOS</string>
|
||||||
</array>
|
</array>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>15</string>
|
<string>16</string>
|
||||||
<key>DTPlatformName</key>
|
<key>DTPlatformName</key>
|
||||||
<string>iphoneos</string>
|
<string>iphoneos</string>
|
||||||
<key>LSRequiresIPhoneOS</key>
|
<key>LSRequiresIPhoneOS</key>
|
||||||
|
67
src/ssb.c
67
src/ssb.c
@@ -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;
|
||||||
@@ -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)
|
||||||
|
25
src/ssb.h
25
src/ssb.h
@@ -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.
|
||||||
|
24
src/ssb.js.c
24
src/ssb.js.c
@@ -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));
|
||||||
|
@@ -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);
|
||||||
|
@@ -1,2 +1,2 @@
|
|||||||
#define VERSION_NUMBER "0.0.33"
|
#define VERSION_NUMBER "0.2025.8-wip"
|
||||||
#define VERSION_NAME "This program kills fascists."
|
#define VERSION_NAME "This program kills fascists."
|
||||||
|
Reference in New Issue
Block a user