20 Commits

Author SHA1 Message Date
f1ced31f69 ssb: Faster loads by encouraging the right queries with CTEs in fetch_about.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-05-07 17:52:02 -04:00
b3cedf2baa ssb: Begin to add some CLI tests.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 33m19s
2025-05-06 12:49:57 -04:00
3bf19fabda ssb: Don't require -u for the private command. #119
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 33m44s
2025-05-05 21:51:38 -04:00
cf81ebe8ad ssb: emoji-fy other cases of some reactions.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-05-05 21:32:40 -04:00
278b5566a1 ssb: Remove some unintended whitespace at the bottom of messages.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-05-05 21:09:58 -04:00
e8c1390f09 ssb: Reply arrow that shows up better on my phone.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 32m57s
2025-05-01 12:38:11 -04:00
3c04abda45 ssb: More shutdown correctness. #108
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 32m26s
2025-04-30 12:35:40 -04:00
2597f99ccf ssb: Get recent reactions up front so that we don't need to delay showing the dialog for them.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 32m33s
2025-04-29 20:48:47 -04:00
9d3a07c1cf build: OpenBSD 7.7's SSL matches these signatures again.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 32m57s
2025-04-27 17:29:59 -04:00
bdfd8925b5 ssb: Remove some extra margin that slipped in.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 31m13s
2025-04-27 10:18:37 -04:00
1a4d1985f4 core: Revisit drag+drop into the editor. Works better but not as I intended. Use it just to update the screenshot in the welcome app.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-04-27 10:18:20 -04:00
6273f3ea53 ssb: Make the connections sidebar header also a link to the connections tab.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 33m18s
2025-04-26 18:53:08 -04:00
5bdc6fa471 update: CodeMirror.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-04-26 18:47:04 -04:00
3ba41291db update: QuickJS 2025-04-26.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-04-26 18:30:56 -04:00
0867811952 update: libuv 1.51.0.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 33m0s
2025-04-26 09:06:37 -04:00
8d961cd805 ssb: Indicate that content warnings can be expanded.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 32m38s
2025-04-24 12:35:36 -04:00
97cea7b40b build: Attempt to include native debug symbols in the Android .aab file.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 32m58s
2025-04-23 20:47:31 -04:00
4106834db8 build: nix flake update did this (but really it was nix --extra-experimental-features nix-command --extra-experimental-features flakes flake update). I don't know how any of this actually works.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 33m7s
2025-04-23 18:36:28 -04:00
a4a8f7cab2 build: Let's start work on 0.0.31.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-04-23 18:33:28 -04:00
9e209ee800 build: nix => 0.0.30. 2025-04-23 18:19:58 -04:00
33 changed files with 370 additions and 173 deletions

View File

@@ -16,9 +16,9 @@ MAKEFLAGS += --no-builtin-rules
## LD := Linker.
## ANDROID_SDK := Path to the Android SDK.
VERSION_CODE := 35
VERSION_CODE_IOS := 12
VERSION_NUMBER := 0.0.30
VERSION_CODE := 37
VERSION_CODE_IOS := 13
VERSION_NUMBER := 0.0.31-wip
VERSION_NAME := This program kills fascists.
IPHONEOS_VERSION_MIN=14.0
@@ -614,15 +614,16 @@ $(UV_OBJS): CFLAGS += \
-Ideps/libuv/include \
-Ideps/libuv/src \
-Wno-dangling-pointer \
-Wno-format-truncation \
-Wno-incompatible-pointer-types \
-Wno-maybe-uninitialized \
-Wno-nonnull \
-Wno-sign-compare \
-Wno-unknown-attributes \
-Wno-unused-but-set-parameter \
-Wno-unused-but-set-variable \
-Wno-unused-result \
-Wno-unused-variable \
-Wno-nonnull
-Wno-unused-variable
$(filter out/win%,$(UV_OBJS)): \
CFLAGS += \
-Wno-cast-function-type \
@@ -742,7 +743,7 @@ $(SQLITE_OBJS): CFLAGS += \
QUICKJS_SOURCES := \
deps/quickjs/cutils.c \
deps/quickjs/libbf.c \
deps/quickjs/dtoa.c \
deps/quickjs/libregexp.c \
deps/quickjs/libunicode.c \
deps/quickjs/quickjs.c
@@ -1015,7 +1016,7 @@ $(BUNDLETOOL):
@curl -q -L --create-dirs -o $@ $(BUNDLETOOL_URL)
out/TildeFriends.aab: out/apk/classes.dex $(filter-out %debug%, $(ANDROID_TARGETS)) $(RAW_FILES) out/apk/res.apk src/android/AndroidManifest.xml $(BUNDLETOOL)
@rm -rf out/aab/staging/
@rm -rf out/aab/staging/ out/aab/base.zip
@mkdir -p out/aab/staging
@$(ANDROID_BUILD_TOOLS)/aapt2 link --proto-format -o out/aab/temporary.apk \
-I $(ANDROID_PLATFORM)/android.jar \
@@ -1035,14 +1036,11 @@ out/TildeFriends.aab: out/apk/classes.dex $(filter-out %debug%, $(ANDROID_TARGET
@cp out/apk/classes.dex out/aab/staging/dex/
@rm -fv out/base.zip
@mkdir -p out/aab/staging/lib/arm64-v8a out/aab/staging/lib/armeabi-v7a out/aab/staging/lib/x86_64 out/aab/staging/lib/x86
@cp out/androidrelease/tildefriends out/aab/staging/lib/arm64-v8a/libtildefriends.so
@cp out/androidrelease-armv7a/tildefriends out/aab/staging/lib/armeabi-v7a/libtildefriends.so
@cp out/androidrelease-x86_64/tildefriends out/aab/staging/lib/x86_64/libtildefriends.so
@cp out/androidrelease-x86/tildefriends out/aab/staging/lib/x86/libtildefriends.so
@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip out/aab/staging/lib/arm64-v8a/libtildefriends.so
@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip out/aab/staging/lib/armeabi-v7a/libtildefriends.so
@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip out/aab/staging/lib/x86_64/libtildefriends.so
@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip out/aab/staging/lib/x86/libtildefriends.so
@mkdir -p out/aab/staging/BUNDLE-METADATA/com.android.tools.build.debugsymbols/arm64-v8a out/aab/staging/BUNDLE-METADATA/com.android.tools.build.debugsymbols/armeabi-v7a out/aab/staging/BUNDLE-METADATA/com.android.tools.build.debugsymbols/x86_64 out/aab/staging/BUNDLE-METADATA/com.android.tools.build.debugsymbols/x86
@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip out/androidrelease/tildefriends -o out/aab/staging/lib/arm64-v8a/libtildefriends.so
@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip out/androidrelease-armv7a/tildefriends -o out/aab/staging/lib/armeabi-v7a/libtildefriends.so
@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip out/androidrelease-x86_64/tildefriends -o out/aab/staging/lib/x86_64/libtildefriends.so
@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip out/androidrelease-x86/tildefriends -o out/aab/staging/lib/x86/libtildefriends.so
@cp -r apps/ out/aab/staging/root/
@rm -rf out/aab/staging/root/apps/welcome*
@cp -r core/ out/aab/staging/root/
@@ -1051,6 +1049,11 @@ out/TildeFriends.aab: out/apk/classes.dex $(filter-out %debug%, $(ANDROID_TARGET
@cp -r deps/codemirror/ out/aab/staging/root/deps/
@cd out/aab/staging/; zip -r ../base.zip *; cd ../../../
@java -jar $(BUNDLETOOL) build-bundle --overwrite --config=src/android/BundleConfig.json --modules=out/aab/base.zip --output=$@
@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip --only-keep-debug out/androidrelease/tildefriends -o out/aab/staging/BUNDLE-METADATA/com.android.tools.build.debugsymbols/arm64-v8a/libtildefriends.so.sym
@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip --only-keep-debug out/androidrelease-armv7a/tildefriends -o out/aab/staging/BUNDLE-METADATA/com.android.tools.build.debugsymbols/armeabi-v7a/libtildefriends.so.sym
@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip --only-keep-debug out/androidrelease-x86_64/tildefriends -o out/aab/staging/BUNDLE-METADATA/com.android.tools.build.debugsymbols/x86_64/libtildefriends.so.sym
@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip --only-keep-debug out/androidrelease-x86/tildefriends -o out/aab/staging/BUNDLE-METADATA/com.android.tools.build.debugsymbols/x86/libtildefriends.so.sym
@cd out/aab/staging; zip -u ../../../$@ BUNDLE-METADATA/com.android.tools.build.debugsymbols/arm64-v8a/libtildefriends.so.sym BUNDLE-METADATA/com.android.tools.build.debugsymbols/armeabi-v7a/libtildefriends.so.sym BUNDLE-METADATA/com.android.tools.build.debugsymbols/x86_64/libtildefriends.so.sym BUNDLE-METADATA/com.android.tools.build.debugsymbols/x86/libtildefriends.so.sym; cd ../../../
@$(ANDROID_BUILD_TOOLS)/apksigner sign -ks .keys/android.jks --ks-key-alias androidKey -ks-pass pass:android --min-sdk-version=$(ANDROID_MIN_SDK_VERSION) $@
aab: out/TildeFriends.aab ## Build an Android App Bundle.

View File

@@ -1,5 +1,5 @@
{
"type": "tildefriends-app",
"emoji": "🦀",
"previous": "&Zv/eOewtUPxYuALmYV8v+JDKwH4+aN8zCTYFwB7oYEw=.sha256"
"previous": "&RkHUl/WRe0UpvTB2W6cw0f4AeupL/2PEI1pXg5PDXBg=.sha256"
}

View File

@@ -14,23 +14,8 @@ function get_emojis() {
});
}
async function get_recent(author) {
let recent = await tfrpc.rpc.query(
`
SELECT DISTINCT content ->> '$.vote.expression' AS value
FROM messages
WHERE author = ? AND
content ->> '$.type' = 'vote'
ORDER BY timestamp DESC LIMIT 10
`,
[author]
);
return recent.map((x) => x.value);
}
export async function picker(callback, anchor, author) {
export async function picker(callback, anchor, author, recent) {
let json = await get_emojis();
let recent = await get_recent(author);
let div = document.createElement('div');
div.id = 'emoji_picker';

View File

@@ -21,6 +21,7 @@ class TfElement extends LitElement {
guest: {type: Boolean},
url: {type: String},
private_messages: {type: Array},
recent_reactions: {type: Array},
};
}
@@ -41,6 +42,7 @@ class TfElement extends LitElement {
this.channels_latest = {};
this.loading_latest = 0;
this.loading_latest_scheduled = 0;
this.recent_reactions = [];
tfrpc.rpc.getBroadcasts().then((b) => {
self.broadcasts = b || [];
});
@@ -175,6 +177,7 @@ class TfElement extends LitElement {
}
}
const k_account_chunk_size = 4096;
const k_chunk_size = 1024;
let min_row_id = 0;
console.log(
@@ -187,54 +190,62 @@ class TfElement extends LitElement {
);
try {
while (true) {
let abouts = await tfrpc.rpc.query(
`
SELECT * FROM (
SELECT
messages.rowid AS rowid, messages.author, json(messages.content) AS content, messages.sequence
FROM
messages,
json_each(?1) AS following
WHERE
messages.author = following.value AND
messages.content ->> 'type' = 'about' AND
messages.rowid > ?3 AND
messages.rowid <= ?4
UNION
SELECT
messages.rowid AS rowid, messages.author, json(messages.content) AS content, messages.sequence
FROM
messages,
json_each(?2) AS following
WHERE
messages.author = following.value AND
messages.content ->> 'type' = 'about' AND
messages.rowid > ?6 AND
messages.rowid <= ?4
)
ORDER BY rowid LIMIT ?5
`,
[
JSON.stringify(ids.filter((id) => cache.about[id])),
JSON.stringify(ids.filter((id) => !cache.about[id])),
cache.last_row_id,
max_row_id,
k_chunk_size,
min_row_id,
]
);
let max_seen;
for (let about of abouts) {
let content = JSON.parse(about.content);
if (content.about === about.author) {
delete content.type;
delete content.about;
cache.about[about.author] = Object.assign(
cache.about[about.author] || {},
content
);
for (let account_chunk = 0; account_chunk < ids.length; account_chunk += k_account_chunk_size) {
let ids_chunk = ids.slice(account_chunk, account_chunk + k_account_chunk_size);
let abouts = await tfrpc.rpc.query(
`
WITH
past AS (
SELECT
messages.rowid AS rowid, messages.author, json(messages.content) AS content, messages.sequence
FROM
messages
WHERE
messages.rowid > ?3 AND
messages.rowid <= ?4 AND
messages.content ->> 'type' = 'about'
),
current AS (
SELECT
messages.rowid AS rowid, messages.author, json(messages.content) AS content, messages.sequence
FROM
messages
WHERE
messages.rowid > ?6 AND
messages.rowid <= ?4 AND
messages.content ->> 'type' = 'about'
)
SELECT * FROM past
JOIN json_each(?1) AS following
ON past.author = following.value
UNION SELECT * FROM current
JOIN json_each(?2) AS following
ON current.author = following.value
ORDER BY rowid LIMIT ?5
`,
[
JSON.stringify(ids_chunk.filter((id) => cache.about[id])),
JSON.stringify(ids_chunk.filter((id) => !cache.about[id])),
cache.last_row_id,
max_row_id,
k_chunk_size,
min_row_id,
]
);
for (let about of abouts) {
let content = JSON.parse(about.content);
if (content.about === about.author) {
delete content.type;
delete content.about;
cache.about[about.author] = Object.assign(
cache.about[about.author] || {},
content
);
}
max_seen = about.rowid;
}
max_seen = about.rowid;
console.log(account_chunk, '/', ids.length, 'accounts');
}
console.log(
'cache =',
@@ -452,6 +463,19 @@ class TfElement extends LitElement {
return users;
}
async load_recent_reactions() {
this.recent_reactions = (await tfrpc.rpc.query(
`
SELECT DISTINCT content ->> '$.vote.expression' AS value
FROM messages
WHERE author = ? AND
content ->> '$.type' = 'vote'
ORDER BY timestamp DESC LIMIT 10
`,
[this.whoami]
)).map((x) => x.value);
}
async load() {
this.loading_latest = true;
try {
@@ -469,6 +493,7 @@ class TfElement extends LitElement {
};
by_count.push({count: v.of, id: id});
}
let reactions = this.load_recent_reactions();
this.load_channels_latest(Object.keys(following));
this.channels_unread = JSON.parse(
(await tfrpc.rpc.databaseGet('unread')) ?? '{}'
@@ -494,6 +519,7 @@ class TfElement extends LitElement {
console.log(
`load finished ${whoami} => ${this.whoami} in ${(new Date() - start_time) / 1000}`
);
await reactions;
this.whoami = whoami;
this.loaded = whoami;
} finally {
@@ -551,6 +577,7 @@ class TfElement extends LitElement {
@channelsetunread=${this.channel_set_unread}
.connections=${this.connections}
.private_messages=${this.private_messages}
.recent_reactions=${this.recent_reactions}
></tf-tab-news>
`;
} else if (this.tab === 'connections') {

View File

@@ -16,6 +16,7 @@ class TfMessageElement extends LitElement {
expanded: {type: Object},
channel: {type: String},
channel_unread: {type: Number},
recent_reactions: {type: Array},
};
}
@@ -31,6 +32,7 @@ class TfMessageElement extends LitElement {
this.format = 'message';
this.expanded = {};
this.channel_unread = -1;
this.recent_reactions = [];
}
connectedCallback() {
@@ -84,9 +86,9 @@ class TfMessageElement extends LitElement {
render_votes() {
function normalize_expression(expression) {
if (expression === 'Like' || !expression) {
if (expression === 'Like' || expression === 'like' || !expression) {
return '👍';
} else if (expression === 'Unlike') {
} else if (expression === 'Unlike' || expression === 'unlike') {
return '👎';
} else if (expression === 'heart') {
return '❤️';
@@ -95,9 +97,10 @@ class TfMessageElement extends LitElement {
}
}
if (this.message?.votes?.length) {
return html` <div class="w3-container">
return html` <footer class="w3-container">
<div
class="w3-button w3-bar w3-padding-small"
class="w3-button w3-bar"
style="padding: 0"
@click=${this.show_reactions}
>
${(this.message.votes || []).map(
@@ -112,7 +115,7 @@ class TfMessageElement extends LitElement {
`
)}
</div>
</div>`;
</footer>`;
}
}
@@ -155,7 +158,12 @@ class TfMessageElement extends LitElement {
}
react(event) {
emojis.picker((x) => this.vote(x), null, this.whoami);
emojis.picker(
(x) => this.vote(x),
null,
this.whoami,
this.recent_reactions
);
}
show_image(link) {
@@ -306,6 +314,10 @@ class TfMessageElement extends LitElement {
);
}
is_expanded(tag) {
return this.expanded[(this.message.id || '') + (tag || '')];
}
render_children() {
let self = this;
if (this.message.child_messages?.length) {
@@ -333,6 +345,7 @@ class TfMessageElement extends LitElement {
.expanded=${this.expanded}
channel=${this.channel}
channel_unread=${this.channel_unread}
.recent_reactions=${this.recent_reactions}
></tf-message>`
)}
</div>
@@ -441,7 +454,7 @@ class TfMessageElement extends LitElement {
${this.drafts[this.message?.id] === undefined
? html`
<button class="w3-button w3-bar-item" @click=${this.show_reply}>
Reply
↩️ Reply
</button>
`
: undefined}
@@ -529,6 +542,7 @@ class TfMessageElement extends LitElement {
.expanded=${self.expanded}
channel=${self.channel}
channel_unread=${self.channel_unread}
.recent_reactions=${self.recent_reactions}
></tf-message>
`
)}
@@ -540,19 +554,22 @@ class TfMessageElement extends LitElement {
let reply =
this.drafts[this.message?.id] !== undefined
? html`
<tf-compose
whoami=${this.whoami}
.users=${this.users}
root=${content.root || this.message.id}
branch=${this.message.id}
.drafts=${this.drafts}
@tf-discard=${this.discard_reply}
author=${this.message.author}
></tf-compose>
<div class="w3-section w3-container">
<tf-compose
whoami=${this.whoami}
.users=${this.users}
root=${content.root || this.message.id}
branch=${this.message.id}
.drafts=${this.drafts}
@tf-discard=${this.discard_reply}
author=${this.message.author}
.recent_reactions=${this.recent_reactions}
></tf-compose>
</div>
`
: undefined;
return html`
<div class="w3-section w3-container">${reply}</div>
${reply}
<footer>${this.render_children()}</footer>
`;
}
@@ -678,11 +695,14 @@ class TfMessageElement extends LitElement {
}
let content_warning = html`
<div
class="w3-panel w3-round-xlarge w3-theme-l4"
class="w3-panel w3-round-xlarge w3-theme-l4 w3"
style="cursor: pointer"
@click=${(x) => this.toggle_expanded(':cw')}
>
<p>${content.contentWarning}</p>
<p class="w3-small">
${this.is_expanded(':cw') ? 'Show less' : 'Show more'}
</p>
</div>
`;
let content_html = html`

View File

@@ -13,6 +13,7 @@ class TfNewsElement extends LitElement {
expanded: {type: Object},
channel: {type: String},
channel_unread: {type: Number},
recent_reactions: {type: Array},
};
}
@@ -28,6 +29,7 @@ class TfNewsElement extends LitElement {
this.drafts = {};
this.expanded = {};
this.channel_unread = -1;
this.recent_reactions = [];
}
process_messages(messages) {
@@ -211,6 +213,7 @@ class TfNewsElement extends LitElement {
collapsed="true"
channel=${this.channel}
channel_unread=${this.channel_unread}
.recent_reactions=${this.recent_reactions}
></tf-message>
${x.rowid == unread_rowid
? html`<div style="display: flex; flex-direction: row">

View File

@@ -18,6 +18,7 @@ class TfTabNewsFeedElement extends LitElement {
time_range: {type: Array},
time_loading: {type: Array},
private_messages: {type: Array},
recent_reactions: {type: Array},
};
}
@@ -37,6 +38,7 @@ class TfTabNewsFeedElement extends LitElement {
this.start_time = new Date().valueOf();
this.time_range = [0, 0];
this.time_loading = undefined;
this.recent_reactions = [];
this.loading = 0;
}
@@ -452,6 +454,7 @@ class TfTabNewsFeedElement extends LitElement {
.expanded=${this.expanded}
channel=${this.channel()}
channel_unread=${this.channels_unread?.[this.channel()]}
.recent_reactions=${this.recent_reactions}
></tf-news>
${more}
`);

View File

@@ -24,6 +24,7 @@ class TfTabNewsElement extends LitElement {
channels_latest: {type: Object},
connections: {type: Array},
private_messages: {type: Array},
recent_reactions: {type: Array},
};
}
@@ -43,6 +44,7 @@ class TfTabNewsElement extends LitElement {
this.channels_latest = {};
this.channels = [];
this.connections = [];
this.recent_reactions = [];
tfrpc.rpc.localStorageGet('drafts').then(function (d) {
self.drafts = JSON.parse(d || '{}');
});
@@ -223,7 +225,9 @@ class TfTabNewsElement extends LitElement {
`
)}
<h4 class="w3-bar-item w3-theme-d2">Connections</h4>
<a class="w3-bar-item w3-theme-d2 w3-button" href="#connections">
<h4 style="margin: 0">Connections</h4>
</a>
${this.connections
.filter((x) => x.id && !x.destroy_reason)
.map(
@@ -340,6 +344,7 @@ class TfTabNewsElement extends LitElement {
.channels_unread=${this.channels_unread}
.channels_latest=${this.channels_latest}
.private_messages=${this.private_messages}
.recent_reactions=${this.recent_reactions}
></tf-tab-news-feed>
</div>
</div>

View File

@@ -1,5 +1,5 @@
{
"type": "tildefriends-app",
"emoji": "👋",
"previous": "&wAb7J6E35xEXpiXsQ6t1RaWTGIvlatUnyH8ipF6pVic=.sha256"
"previous": "&1o8MrBHfH42NnO+ruajwCmW/DUCb+IT1qtnAZI/agyo=.sha256"
}

View File

@@ -45,6 +45,11 @@
<i class="fa fa-mobile-screen w3-xlarge"></i>
<i class="fa-brands fa-windows w3-xlarge"></i>
</p>
<a
class="w3-button w3-blue w3-padding-large"
href="https://www.tildefriends.net/~core/ssb/"
>🦀 Try It</a
>
<a
class="w3-button w3-black w3-padding-large"
href="https://dev.tildefriends.net/cory/tildefriends/releases"
@@ -52,12 +57,7 @@
>
<a
class="w3-button w3-black w3-padding-large"
href="https://www.tildefriends.net/~core/ssb/"
><i class="fa fa-link"></i> Try It</a
>
<a
class="w3-button w3-black w3-padding-large"
href="https://dev.tildefriends.net/"
href="https://dev.tildefriends.net/cory/tildefriends"
><i class="fa fa-mug-hot"></i> Development</a
>
<a
@@ -65,6 +65,11 @@
href="https://docs.tildefriends.net/"
><i class="fa fa-book"></i> Documentation</a
>
<a
class="w3-button w3-black w3-padding-large"
href="https://www.tildefriends.net/~cory/tildeblog/"
><i class="fa fa-solid fa-square-rss"></i> Blog</a
>
<p>
<a
class="w3-button w3-round-large w3-padding w3-blue-gray w3-margin-top"
@@ -86,6 +91,13 @@
<img src="googleplay.svg" style="height: 2em; margin: 0" />
Get it on Google Play (Open Testing)
</a>
<a
class="w3-button w3-round-large w3-padding w3-blue-gray w3-margin-top"
href="https://testflight.apple.com/join/tXxgtSpE"
>
<img src="ios.svg" style="height: 2em; margin: 0" />
Get it on iOS (TestFlight)
</a>
</p>
</div>
<div class="w3-col l4 m6">
@@ -222,9 +234,9 @@
<div class="w3-container w3-padding-64 w3-light-grey w3-center">
<h1 class="w3-jumbo"><b>Built the Old Fashioned Way</b></h1>
<p>
Tilde Friends strives to use only simple and widely adopted dependencies
in order to keep it easy to build for all sorts of platforms and
maintainable for a very long time.
Tilde Friends strives to use only simple and widely adopted
dependencies in order to keep it easy to build for all sorts of
platforms and maintainable for a very long time.
</p>
<p>
Though of course for building Tilde Friends apps, you are free to use

3
apps/welcome/ios.svg Normal file
View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="814" height="1000">
<path d="M788.1 340.9c-5.8 4.5-108.2 62.2-108.2 190.5 0 148.4 130.3 200.9 134.2 202.2-.6 3.2-20.7 71.9-68.7 141.9-42.8 61.6-87.5 123.1-155.5 123.1s-85.5-39.5-164-39.5c-76.5 0-103.7 40.8-165.9 40.8s-105.6-57-155.5-127C46.7 790.7 0 663 0 541.8c0-194.4 126.4-297.5 250.8-297.5 66.1 0 121.2 43.4 162.7 43.4 39.5 0 101.1-46 176.3-46 28.5 0 130.9 2.6 198.3 99.2zm-234-181.5c31.1-36.9 53.1-88.1 53.1-139.3 0-7.1-.6-14.3-1.9-20.1-50.6 1.9-110.8 33.7-147.1 75.8-28.5 32.4-55.1 83.6-55.1 135.5 0 7.8 1.3 15.6 1.9 18.1 3.2.6 8.4 1.3 13.6 1.3 45.4 0 102.5-30.4 135.5-71.3z"/>
</svg>

After

Width:  |  Height:  |  Size: 660 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 141 KiB

After

Width:  |  Height:  |  Size: 141 KiB

View File

@@ -1,4 +1,4 @@
/* W3.CSS 5.01 March 14 2025 by Jan Egil and Borge Refsnes */
/* W3.CSS 4.15 December 2020 by Jan Egil and Borge Refsnes */
html{box-sizing:border-box}*,*:before,*:after{box-sizing:inherit}
/* Extract from normalize.css by Nicolas Gallagher and Jonathan Neal git.io/normalize */
html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}
@@ -108,10 +108,6 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
.w3-round-small{border-radius:2px}.w3-round,.w3-round-medium{border-radius:4px}.w3-round-large{border-radius:8px}.w3-round-xlarge{border-radius:16px}.w3-round-xxlarge{border-radius:32px}
.w3-row-padding,.w3-row-padding>.w3-half,.w3-row-padding>.w3-third,.w3-row-padding>.w3-twothird,.w3-row-padding>.w3-threequarter,.w3-row-padding>.w3-quarter,.w3-row-padding>.w3-col{padding:0 8px}
.w3-container,.w3-panel{padding:0.01em 16px}.w3-panel{margin-top:16px;margin-bottom:16px}
.w3-grid{display:grid}.w3-grid-padding{display:grid;gap:16px}.w3-flex{display:flex}
.w3-text-center{text-align:center}.w3-text-bold,.w3-bold{font-weight:bold}.w3-text-italic,.w3-italic{font-style:italic}
.w3-code,.w3-codespan{font-family:Consolas,"courier new";font-size:16px}
.w3-code{width:auto;background-color:#fff;padding:8px 12px;border-left:4px solid #4CAF50;word-wrap:break-word}
.w3-codespan{color:crimson;background-color:#f1f1f1;padding-left:4px;padding-right:4px;font-size:110%}
@@ -153,9 +149,9 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
.w3-transparent,.w3-hover-none:hover{background-color:transparent!important}
.w3-hover-none:hover{box-shadow:none!important}
/* Colors */
.w3-amber,.w3-hover-amber:hover,.w3-warning{color:#000!important;background-color:#ffc107!important}
.w3-amber,.w3-hover-amber:hover{color:#000!important;background-color:#ffc107!important}
.w3-aqua,.w3-hover-aqua:hover{color:#000!important;background-color:#00ffff!important}
.w3-blue,.w3-hover-blue:hover,.w3-info,.w3-primary{color:#fff!important;background-color:#2196F3!important}
.w3-blue,.w3-hover-blue:hover{color:#fff!important;background-color:#2196F3!important}
.w3-light-blue,.w3-hover-light-blue:hover{color:#000!important;background-color:#87CEEB!important}
.w3-brown,.w3-hover-brown:hover{color:#fff!important;background-color:#795548!important}
.w3-cyan,.w3-hover-cyan:hover{color:#000!important;background-color:#00bcd4!important}
@@ -170,24 +166,15 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
.w3-pink,.w3-hover-pink:hover{color:#fff!important;background-color:#e91e63!important}
.w3-purple,.w3-hover-purple:hover{color:#fff!important;background-color:#9c27b0!important}
.w3-deep-purple,.w3-hover-deep-purple:hover{color:#fff!important;background-color:#673ab7!important}
.w3-red,.w3-hover-red:hover,.w3-danger{color:#fff!important;background-color:#f44336!important}
.w3-red,.w3-hover-red:hover{color:#fff!important;background-color:#f44336!important}
.w3-sand,.w3-hover-sand:hover{color:#000!important;background-color:#fdf5e6!important}
.w3-teal,.w3-hover-teal:hover{color:#fff!important;background-color:#009688!important}
.w3-yellow,.w3-hover-yellow:hover,.w3-note{color:#000!important;background-color:#ffeb3b!important}
.w3-yellow,.w3-hover-yellow:hover{color:#000!important;background-color:#ffeb3b!important}
.w3-white,.w3-hover-white:hover{color:#000!important;background-color:#fff!important}
.w3-black,.w3-hover-black:hover{color:#fff!important;background-color:#000!important}
.w3-grey,.w3-hover-grey:hover,.w3-gray,.w3-hover-gray:hover,.w3-secondary{color:#000!important;background-color:#9e9e9e!important}
.w3-grey,.w3-hover-grey:hover,.w3-gray,.w3-hover-gray:hover{color:#000!important;background-color:#9e9e9e!important}
.w3-light-grey,.w3-hover-light-grey:hover,.w3-light-gray,.w3-hover-light-gray:hover{color:#000!important;background-color:#f1f1f1!important}
.w3-dark-grey,.w3-hover-dark-grey:hover,.w3-dark-gray,.w3-hover-dark-gray:hover{color:#fff!important;background-color:#616161!important}
.w3-asphalt,.w3-hover-asphalt:hover{color:#fff!important;background-color:#343a40!important}.w3-crimson,.w3-hover-crimson:hover{color:#fff!important;background-color:#a20025!important}
.w3-cobalt,w3-hover-cobalt:hover{color:#fff!important;background-color:#0050ef!important}
.w3-emerald,.w3-hover-emerald:hover,.w3-success{color:#fff!important;background-color:#008a00!important}
.w3-olive,.w3-hover-olive:hover{color:#fff!important;background-color:#6d8764!important}
.w3-paper,.w3-hover-paper:hover{color:#000!important;background-color:#f8f9fa!important}.w3-sienna,.w3-hover-sienna:hover{color:#fff!important;background-color:#a0522d!important}
.w3-taupe,.w3-hover-taupe:hover{color:#fff!important;background-color:#87794e!important}
.w3-pale-red,.w3-hover-pale-red:hover{color:#000!important;background-color:#ffdddd!important}
.w3-pale-green,.w3-hover-pale-green:hover{color:#000!important;background-color:#ddffdd!important}
.w3-pale-yellow,.w3-hover-pale-yellow:hover{color:#000!important;background-color:#ffffcc!important}
@@ -245,4 +232,4 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
.w3-border-light-grey,.w3-hover-border-light-grey:hover,.w3-border-light-gray,.w3-hover-border-light-gray:hover{border-color:#f1f1f1!important}
.w3-border-dark-grey,.w3-hover-border-dark-grey:hover,.w3-border-dark-gray,.w3-hover-border-dark-gray:hover{border-color:#616161!important}
.w3-border-pale-red,.w3-hover-border-pale-red:hover{border-color:#ffe7e7!important}.w3-border-pale-green,.w3-hover-border-pale-green:hover{border-color:#e7ffe7!important}
.w3-border-pale-yellow,.w3-hover-border-pale-yellow:hover{border-color:#ffffcc!important}.w3-border-pale-blue,.w3-hover-border-pale-blue:hover{border-color:#e7ffff!important}
.w3-border-pale-yellow,.w3-hover-border-pale-yellow:hover{border-color:#ffffcc!important}.w3-border-pale-blue,.w3-hover-border-pale-blue:hover{border-color:#e7ffff!important}

View File

@@ -411,6 +411,7 @@ class TfFilesElement extends LitElement {
current: {type: String},
files: {type: Object},
dropping: {type: Number},
drop_target: {type: String},
};
}
@@ -449,6 +450,9 @@ class TfFilesElement extends LitElement {
if (!this.files[file].clean) {
classes.push('dirty');
}
if (this.drop_target == file) {
classes.push('drop');
}
return html`<div
class="${classes.join(' ')}"
@click=${(x) => this.file_click(file)}
@@ -465,11 +469,12 @@ class TfFilesElement extends LitElement {
event.preventDefault();
event.stopPropagation();
this.dropping = 0;
this.drop_target = undefined;
for (let file of event.dataTransfer.files) {
let buffer = await file.arrayBuffer();
let text = new TextDecoder('latin1').decode(buffer);
gFiles[file.name] = {
doc: new cm6.EditorState.create({
doc: cm6.EditorState.create({
doc: text,
extensions: cm6.extensions,
}),
@@ -488,6 +493,7 @@ class TfFilesElement extends LitElement {
*/
drag_enter(event) {
this.dropping++;
this.drop_target = event.srcElement.innerText.trim();
event.preventDefault();
}
@@ -497,6 +503,13 @@ class TfFilesElement extends LitElement {
*/
drag_leave(event) {
this.dropping--;
if (this.dropping == 0) {
this.drop_target = undefined;
}
}
drag_over(event) {
event.preventDefault();
}
/**
@@ -523,6 +536,10 @@ class TfFilesElement extends LitElement {
background-color: #2aa198;
}
div.file.drop {
border: 4px solid red;
}
div.file.dirty::after {
content: '*';
}
@@ -531,20 +548,12 @@ class TfFilesElement extends LitElement {
@drop=${this.drop}
@dragenter=${this.drag_enter}
@dragleave=${this.drag_leave}
@dragover=${this.drag_over}
>
${Object.keys(this.files)
.sort()
.map((x) => self.render_file(x))}
</div>
<div
?hidden=${this.dropping == 0}
@drop=${this.drop}
@dragenter=${this.drag_enter}
@dragleave=${this.drag_leave}
style="text-align: center; vertical-align: middle; outline: 16px solid red; margin: -8px; background-color: rgba(255, 0, 0, 0.5); position: absolute; left: 16px; top: 16px; width: calc(100% - 16px); height: calc(100% - 16px); z-index: 1000"
>
Drop File(s)
</div>
`;
}
}

View File

@@ -25,14 +25,14 @@
}:
pkgs.stdenv.mkDerivation rec {
pname = "tildefriends";
version = "0.0.29";
version = "0.0.30";
src = pkgs.fetchFromGitea {
domain = "dev.tildefriends.net";
owner = "cory";
repo = "tildefriends";
rev = "v${version}";
hash = "sha256-bQXFpocOYOlFmVj9OZeQhNrgFuTJ8sx2RSw1tgmelOM=";
hash = "sha256-t5yvouzSL2j/ge1VHLqzIZ+Avqj4iEDt7L+yrHoTZAQ=";
fetchSubmodules = true;
};

File diff suppressed because one or more lines are too long

View File

@@ -144,9 +144,9 @@
}
},
"node_modules/@codemirror/view": {
"version": "6.36.5",
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.36.5.tgz",
"integrity": "sha512-cd+FZEUlu3GQCYnguYm3EkhJ8KJVisqqUsCOKedBoAt/d9c76JUUap6U0UrpElln5k6VyrEOYliMuDAKIeDQLg==",
"version": "6.36.6",
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.36.6.tgz",
"integrity": "sha512-uxugGLet+Nzp0Jcit8Hn3LypM8ioMLKTsdf8FRoT3HWvZtb9GhaWMe0Cc15rz90Ljab4YFJiAulmIVB74OY0IQ==",
"dependencies": {
"@codemirror/state": "^6.5.0",
"style-mod": "^4.1.0",

2
deps/libuv vendored

Submodule deps/libuv updated: 8fb9cb9194...5152db2cbf

2
deps/quickjs vendored

6
flake.lock generated
View File

@@ -20,11 +20,11 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1739758141,
"narHash": "sha256-uq6A2L7o1/tR6VfmYhZWoVAwb3gTy7j4Jx30MIrH0rE=",
"lastModified": 1745279238,
"narHash": "sha256-AQ7M9wTa/Pa/kK5pcGTgX/DGqMHyzsyINfN7ktsI7Fo=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "c618e28f70257593de75a7044438efc1c1fc0791",
"rev": "9684b53175fc6c09581e94cc85f05ab77464c7e3",
"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="35"
android:versionName="0.0.30">
android:versionCode="37"
android:versionName="0.0.31-wip">
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.INTERNET"/>
<application

View File

@@ -13,13 +13,13 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>0.0.30</string>
<string>0.0.31</string>
<key>CFBundleSupportedPlatforms</key>
<array>
<string>iPhoneOS</string>
</array>
<key>CFBundleVersion</key>
<string>12</string>
<string>13</string>
<key>DTPlatformName</key>
<string>iphoneos</string>
<key>LSRequiresIPhoneOS</key>

View File

@@ -531,7 +531,7 @@ static int _tf_command_private(const char* file, int argc, char* argv[])
{ "id", required_argument, NULL, 'i' },
{ "recipients", required_argument, NULL, 'r' },
{ "db-path", required_argument, NULL, 'd' },
{ "text", required_argument, NULL, 'c' },
{ "text", required_argument, NULL, 't' },
{ "help", no_argument, NULL, 'h' },
{ 0 },
};
@@ -566,11 +566,11 @@ static int _tf_command_private(const char* file, int argc, char* argv[])
}
}
if (show_usage || !user || !identity || !recipients || !text)
if (show_usage || !identity || !recipients || !text)
{
tf_printf("\n%s private [options]\n\n", file);
tf_printf("options:\n");
tf_printf(" -u, --user user User owning identity with which to publish.\n");
tf_printf(" -u, --user user User owning identity with which to publish (optional).\n");
tf_printf(" -i, --id identity Identity with which to publish message.\n");
tf_printf(" -r, --recipients recipients Recipient identities.\n");
tf_printf(" -d, --db-path db_path SQLite database path (default: %s).\n", default_db_path);
@@ -591,6 +591,13 @@ static int _tf_command_private(const char* file, int argc, char* argv[])
recipient_list[recipient_count++] = identity;
bool free_user = false;
if (!user)
{
user = tf_ssb_db_get_user_for_identity(ssb, identity);
free_user = true;
}
if (tf_ssb_db_identity_get_private_key(ssb, user, identity, private_key, sizeof(private_key)))
{
char* copy = tf_strdup(recipients);
@@ -642,6 +649,11 @@ static int _tf_command_private(const char* file, int argc, char* argv[])
{
tf_printf("Did not find private key for identity %s belonging to %s.\n", identity, user);
}
if (free_user)
{
tf_free((void*)user);
}
done:
tf_ssb_destroy(ssb);
tf_free((void*)default_db_path);

View File

@@ -386,29 +386,17 @@ size_t tf_mem_get_uv_malloc_size()
return s_uv_malloc_size;
}
#if defined(__OpenBSD__)
static void* _tf_tls_alloc(size_t size)
#else
static void* _tf_tls_alloc(size_t size, const char* file, int line)
#endif
{
return _tf_alloc(&s_tls_malloc_size, size);
}
#if defined(__OpenBSD__)
static void* _tf_tls_realloc(void* ptr, size_t size)
#else
static void* _tf_tls_realloc(void* ptr, size_t size, const char* file, int line)
#endif
{
return _tf_realloc(&s_tls_malloc_size, ptr, size);
}
#if defined(__OpenBSD__)
static void _tf_tls_free(void* ptr)
#else
static void _tf_tls_free(void* ptr, const char* file, int line)
#endif
{
_tf_free(&s_tls_malloc_size, ptr);
}

View File

@@ -4630,6 +4630,10 @@ void tf_ssb_sync_start(tf_ssb_t* ssb)
bool tf_ssb_tunnel_create(tf_ssb_t* ssb, const char* portal_id, const char* target_id, int connect_flags)
{
if (!portal_id || !target_id)
{
return false;
}
tf_ssb_connection_t* connection = tf_ssb_connection_get(ssb, portal_id);
if (connection && !tf_ssb_connection_get(ssb, target_id))
{

View File

@@ -1398,6 +1398,34 @@ void tf_ssb_db_identity_visit_all(tf_ssb_t* ssb, void (*callback)(const char* id
tf_ssb_release_db_reader(ssb, db);
}
const char* tf_ssb_db_get_user_for_identity(tf_ssb_t* ssb, const char* public_key)
{
const char* result = NULL;
sqlite3* db = tf_ssb_acquire_db_reader(ssb);
sqlite3_stmt* statement = NULL;
if (sqlite3_prepare(db, "SELECT user FROM identities WHERE public_key = ?", -1, &statement, NULL) == SQLITE_OK)
{
if (sqlite3_bind_text(statement, 1, (public_key && *public_key == '@') ? public_key + 1 : public_key, -1, NULL) == SQLITE_OK)
{
if (sqlite3_step(statement) == SQLITE_ROW)
{
result = tf_strdup((const char*)sqlite3_column_text(statement, 0));
}
}
else
{
tf_printf("Bind failed: %s.\n", sqlite3_errmsg(db));
}
sqlite3_finalize(statement);
}
else
{
tf_printf("Prepare failed: %s.\n", sqlite3_errmsg(db));
}
tf_ssb_release_db_reader(ssb, db);
return result;
}
bool tf_ssb_db_identity_get_private_key(tf_ssb_t* ssb, const char* user, const char* public_key, uint8_t* out_private_key, size_t private_key_size)
{
bool success = false;

View File

@@ -249,6 +249,14 @@ void tf_ssb_db_identity_visit(tf_ssb_t* ssb, const char* user, void (*callback)(
*/
void tf_ssb_db_identity_visit_all(tf_ssb_t* ssb, void (*callback)(const char* identity, void* user_data), void* user_data);
/**
** Get the user owning an identity.
** @param ssb The SSB instance.
** @param public_key the identity.
** @return The username of the owner of the identity or NULL.
*/
const char* tf_ssb_db_get_user_for_identity(tf_ssb_t* ssb, const char* public_key);
/**
** Get the private key for an identity in the database.
** @param ssb The SSB instance.

View File

@@ -1788,7 +1788,7 @@ static JSValue _tf_ssb_createTunnel(JSContext* context, JSValueConst this_val, i
const char* portal_id = JS_ToCString(context, argv[0]);
const char* target_id = JS_ToCString(context, argv[1]);
bool result = tf_ssb_tunnel_create(ssb, portal_id, target_id, 0);
bool result = portal_id && target_id && tf_ssb_tunnel_create(ssb, portal_id, target_id, 0);
JS_FreeCString(context, target_id);
JS_FreeCString(context, portal_id);

View File

@@ -1478,4 +1478,97 @@ void tf_ssb_test_triggers(const tf_test_options_t* options)
uv_loop_close(&loop);
}
static void _subprocess_check_call(const char* command)
{
int result = system(command);
if (!WIFEXITED(result))
{
tf_printf("Command did not report exit: %s.\n", command);
abort();
}
if (WEXITSTATUS(result) != 0)
{
tf_printf("Command returned %d: %s.\n", WEXITSTATUS(result), command);
abort();
}
}
static char* _subprocess_check_output(const char* command)
{
FILE* proc = popen(command, "r");
if (!proc)
{
tf_printf("Command failed (%s): %s.\n", strerror(errno), command);
abort();
}
char* result = NULL;
size_t size = 0;
if (proc)
{
const int k_block_size = 1024;
result = tf_realloc(result, size + k_block_size);
while (true)
{
size_t bytes = fread(result + size, 1, k_block_size, proc);
if (bytes > 0)
{
size += bytes;
}
else
{
break;
}
}
pclose(proc);
}
result = tf_realloc(result, size + 1);
if (result)
{
result[size] = '\0';
}
return result;
}
static bool _isspace(char c)
{
return
c == ' ' ||
c == '\t' ||
c == '\r' ||
c == '\n';
}
static char* _trim(char* p)
{
if (!p)
{
return NULL;
}
size_t length = strlen(p);
while (length > 0 && _isspace(p[length - 1]))
{
p[length - 1] = '\0';
length--;
}
return p;
}
void tf_ssb_test_cli(const tf_test_options_t* options)
{
tf_printf("Testing CLI.\n");
unlink("out/test_db0.sqlite");
char command[1024];
snprintf(command, sizeof(command), "%s get_identity -d out/test_db0.sqlite", options->exe_path);
_subprocess_check_call(command);
char* id = _trim(_subprocess_check_output(command));
tf_printf("id = [%s]\n", id);
snprintf(command, sizeof(command), "%s private -i %s -r %s -d out/test_db0.sqlite -t '{\"type\": \"post\", \"text\": \"hello world\"}'", options->exe_path, id, id);
_subprocess_check_call(command);
tf_free(id);
}
#endif

View File

@@ -89,4 +89,10 @@ void tf_ssb_test_invite(const tf_test_options_t* options);
*/
void tf_ssb_test_triggers(const tf_test_options_t* options);
/**
** Test command-line interface.
** @param options The test options.
*/
void tf_ssb_test_cli(const tf_test_options_t* options);
/** @} */

View File

@@ -1869,15 +1869,15 @@ void tf_task_destroy(tf_task_t* task)
uv_close((uv_handle_t*)&timeout->_timer, _timeout_closed);
}
if (task->_ssb)
{
tf_ssb_destroy(task->_ssb);
}
if (task->_http)
{
tf_httpd_destroy(task->_http);
task->_http = NULL;
}
if (task->_ssb)
{
tf_ssb_destroy(task->_ssb);
}
JS_FreeContext(task->_context);
JS_FreeRuntime(task->_runtime);

View File

@@ -1079,6 +1079,7 @@ void tf_tests(const tf_test_options_t* options)
_tf_test_run(options, "connect_str", tf_ssb_test_connect_str, false);
_tf_test_run(options, "invite", tf_ssb_test_invite, false);
_tf_test_run(options, "triggers", tf_ssb_test_triggers, false);
_tf_test_run(options, "cli", tf_ssb_test_cli, false);
tf_printf("Tests completed.\n");
#endif
}

View File

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