Compare commits

..

No commits in common. "main" and "v0.0.31" have entirely different histories.

40 changed files with 1962 additions and 4052 deletions

View File

@ -1051,7 +1051,7 @@ EXAMPLE_RECURSIVE = NO
# that contain images that are to be included in the documentation (see the
# \image command).
IMAGE_PATH = docs/images/
IMAGE_PATH =
# The INPUT_FILTER tag can be used to specify a program that doxygen should
# invoke to filter for each input file. Doxygen will invoke the filter program

View File

@ -16,14 +16,14 @@ MAKEFLAGS += --no-builtin-rules
## LD := Linker.
## ANDROID_SDK := Path to the Android SDK.
VERSION_CODE := 38
VERSION_CODE_IOS := 14
VERSION_NUMBER := 0.0.32-wip
VERSION_CODE := 37
VERSION_CODE_IOS := 13
VERSION_NUMBER := 0.0.31
VERSION_NAME := This program kills fascists.
IPHONEOS_VERSION_MIN=14.0
SQLITE_URL := https://www.sqlite.org/2025/sqlite-amalgamation-3500000.zip
SQLITE_URL := https://www.sqlite.org/2025/sqlite-amalgamation-3490200.zip
BUNDLETOOL_URL := https://github.com/google/bundletool/releases/download/1.17.0/bundletool-all-1.17.0.jar
APPIMAGETOOL_URL := https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage
APPIMAGETOOL_MD5 := e989fadfc4d685fd3d6aeeb9b525d74d out/appimagetool

View File

@ -1,5 +1,5 @@
{
"type": "tildefriends-app",
"emoji": "🚪",
"previous": "&DJwkqNfYWtW9yBtJQMseEXm7l04Enpi+yAxZulLq9Vk=.sha256"
"previous": "&HXCdDG8gGYXElTyEFbg85jqa6lDXNL2ENPIA9UoJNbI=.sha256"
}

View File

@ -2,8 +2,8 @@ async function main() {
print(core.url);
let host = core.url.match(/.*?\/\/([^:/]*)/)[1];
let port = await ssb.port();
let id = (await ssb.getServerIdentity()).substring(1).split('.')[0];
let room = `net:${host}:${port}~shs:${id}:SSB+Room+PSK3TLYC2T86EHQCUHBUHASCASE18JBV24=`;
let id = (await ssb.getServerIdentity()).substring(1);
let room = `net:${host}:${port}~shs:${id}:SSB+Room+SK3TLYC2T86EHQCUHBUHASCASE18JBV24=`;
await app.setDocument(`
<body style="color: #fff">
<h1>Server</h1>

View File

@ -1,5 +1,5 @@
{
"type": "tildefriends-app",
"emoji": "🦀",
"previous": "&i6mkfmh4sW8PpILznWL8VxDzI3MX6V5OJWDs47Qxias=.sha256"
"previous": "&0Lxm4IgS3mpvSccP3bg7wNPACtLKMTbie51ea/vJbeg=.sha256"
}

View File

@ -128,13 +128,7 @@ class TfElement extends LitElement {
}
next_channel(delta) {
let channel_names = [
'',
'@',
'🔐',
'👍',
...this.channels.map((x) => '#' + x),
];
let channel_names = ['', '@', '🔐', ...this.channels.map((x) => '#' + x)];
let index = channel_names.indexOf(this.hash.substring(1));
index = index != -1 ? index + delta : 0;
tfrpc.rpc.setHash(
@ -359,43 +353,27 @@ class TfElement extends LitElement {
let latest_private = this.get_latest_private(following);
let channels = await tfrpc.rpc.query(
`
SELECT channels.value AS channel, MAX(messages.rowid) AS rowid FROM messages
JOIN json_each(?1) AS channels ON messages.content ->> 'channel' = channels.value
JOIN json_each(?2) AS following ON messages.author = following.value
WHERE
messages.content ->> 'type' = 'post' AND
messages.content ->> 'root' IS NULL AND
messages.author != ?4
GROUP by channel
UNION
SELECT channels.value AS channel, MAX(messages.rowid) AS rowid FROM messages
JOIN messages_refs ON messages.id = messages_refs.message
JOIN json_each(?1) AS channels ON messages_refs.ref = '#' || channels.value
JOIN json_each(?2) AS following ON messages.author = following.value
WHERE
messages.content ->> 'type' = 'post' AND
messages.content ->> 'root' IS NULL AND
messages.author != ?4
GROUP by channel
UNION
SELECT '' AS channel, MAX(messages.rowid) AS rowid FROM messages
JOIN json_each(?2) AS following ON messages.author = following.value
WHERE
messages.content ->> 'type' = 'post' AND
messages.content ->> 'root' IS NULL AND
messages.author != ?4
UNION
SELECT '@' AS channel, MAX(messages.rowid) AS rowid FROM messages_fts(?3)
JOIN messages ON messages.rowid = messages_fts.rowid
JOIN json_each(?2) AS following ON messages.author = following.value
WHERE messages.author != ?4
UNION
SELECT '👍' AS channel, MAX(messages.rowid) AS rowid FROM messages
JOIN json_each(?2) AS following ON messages.author = following.value
WHERE
messages.content ->> 'type' = 'vote' AND
messages.author != ?4
`,
SELECT channels.value AS channel, MAX(messages.rowid) AS rowid FROM messages
JOIN json_each(?1) AS channels ON messages.content ->> 'channel' = channels.value
JOIN json_each(?2) AS following ON messages.author = following.value
WHERE
messages.content ->> 'type' = 'post' AND
messages.content ->> 'root' IS NULL AND
messages.author != ?4
GROUP by channel
UNION
SELECT '' AS channel, MAX(messages.rowid) AS rowid FROM messages
JOIN json_each(?2) AS following ON messages.author = following.value
WHERE
messages.content ->> 'type' = 'post' AND
messages.content ->> 'root' IS NULL AND
messages.author != ?4
UNION
SELECT '@' AS channel, MAX(messages.rowid) AS rowid FROM messages_fts(?3)
JOIN messages ON messages.rowid = messages_fts.rowid
JOIN json_each(?2) AS following ON messages.author = following.value
WHERE messages.author != ?4
`,
[
JSON.stringify(this.channels),
JSON.stringify(following),
@ -403,15 +381,9 @@ class TfElement extends LitElement {
this.whoami,
]
);
let latest = {};
for (let row of channels) {
if (!latest[row.channel]) {
latest[row.channel] = row.rowid;
} else {
latest[row.channel] = Math.max(row.rowid, latest[row.channel]);
}
}
this.channels_latest = latest;
this.channels_latest = Object.fromEntries(
channels.map((x) => [x.channel, x.rowid])
);
console.log('channels took', (new Date() - start_time) / 1000.0);
let self = this;
start_time = new Date();

View File

@ -446,15 +446,12 @@ class TfComposeElement extends LitElement {
self.apps = await tfrpc.rpc.apps();
}
if (!this.apps) {
return html`<button
class="w3-button w3-bar-item w3-theme-d1"
@click=${attach_app}
>
return html`<button class="w3-button w3-theme-d1" @click=${attach_app}>
Attach App
</button>`;
} else {
return html`<button
class="w3-button w3-bar-item w3-theme-d1"
class="w3-button w3-theme-d1"
@click=${() => (this.apps = null)}
>
Discard App
@ -475,9 +472,18 @@ class TfComposeElement extends LitElement {
if (draft.content_warning !== undefined) {
return html`
<div class="w3-container w3-padding">
<p>
<input type="checkbox" class="w3-check w3-theme-d1" id="cw" @change=${() => self.set_content_warning(undefined)} checked="checked"></input>
<label for="cw">CW</label>
</p>
<input type="text" class="w3-input w3-border w3-theme-d1" id="content_warning" placeholder="Enter a content warning here." @input=${self.input} value=${draft.content_warning}></input>
</div>
`;
} else {
return html`
<input type="checkbox" class="w3-check w3-theme-d1" id="cw" @change=${() => self.set_content_warning('')}></input>
<label for="cw">CW</label>
`;
}
}
@ -540,31 +546,6 @@ class TfComposeElement extends LitElement {
this.requestUpdate();
}
toggle_menu(event) {
event.srcElement.parentNode
.querySelector('.w3-dropdown-content')
.classList.toggle('w3-show');
}
connectedCallback() {
super.connectedCallback();
this._click_callback = this.document_click.bind(this);
document.body.addEventListener('mouseup', this._click_callback);
}
disconnectedCallback() {
super.disconnectedCallback();
document.body.removeEventListener('mouseup', this._click_callback);
}
document_click(event) {
let content = this.renderRoot.querySelector('.w3-dropdown-content');
let target = event.target;
if (content && !content.contains(target)) {
content.classList.remove('w3-show');
}
}
render() {
let self = this;
let draft = self.get_draft();
@ -578,7 +559,7 @@ class TfComposeElement extends LitElement {
draft.encrypt_to !== undefined
? undefined
: html`<button
class="w3-button w3-bar-item w3-theme-d1"
class="w3-button w3-theme-d1"
@click=${() => this.set_encrypt([])}
>
🔐
@ -633,43 +614,13 @@ class TfComposeElement extends LitElement {
>
Submit
</button>
<div class="w3-dropdown-click">
<button class="w3-button w3-theme-d1" @click=${this.toggle_menu}>
</button>
<div class="w3-dropdown-content w3-bar-block">
${this.get_draft().content_warning === undefined
? html`
<button
class="w3-button w3-bar-item w3-theme-d1"
@click=${() => self.set_content_warning('')}
>
Add Content Warning
</button>
`
: html`
<button
class="w3-button w3-bar-item w3-theme-d1"
@click=${() => self.set_content_warning(undefined)}
>
Remove Content Warning
</button>
`}
<button
class="w3-button w3-bar-item w3-theme-d1"
@click=${this.attach}
>
Attach
</button>
${this.render_attach_app_button()} ${encrypt}
<button
class="w3-button w3-bar-item w3-theme-d1"
@click=${this.discard}
>
Discard
</button>
</div>
</div>
<button class="w3-button w3-theme-d1" @click=${this.attach}>
Attach
</button>
${this.render_attach_app_button()} ${encrypt}
<button class="w3-button w3-theme-d1" @click=${this.discard}>
Discard
</button>
</footer>
</div>
`;

View File

@ -1,11 +1,4 @@
import {
LitElement,
css,
html,
repeat,
render,
unsafeHTML,
} from './lit-all.min.js';
import {LitElement, html, repeat, render, unsafeHTML} from './lit-all.min.js';
import * as tfrpc from '/static/tfrpc.js';
import * as tfutils from './tf-utils.js';
import * as emojis from './emojis.js';
@ -93,18 +86,12 @@ class TfMessageElement extends LitElement {
render_votes() {
function normalize_expression(expression) {
if (
expression === 'Unlike' ||
expression === 'unlike' ||
expression == 'undig'
) {
if (expression === 'Like' || expression === 'like' || !expression) {
return '👍';
} else if (expression === 'Unlike' || expression === 'unlike') {
return '👎';
} else if (expression === 'heart') {
return '❤️';
} else if (
(expression ?? '').split('').every((x) => x.charCodeAt(0) < 256)
) {
return '👍';
} else {
return expression;
}
@ -310,35 +297,31 @@ class TfMessageElement extends LitElement {
return total;
}
expanded_key() {
return this.message?.id || this.messages?.map((x) => x.id).join(':');
}
set_expanded(expanded, tag) {
let key = this.expanded_key();
this.dispatchEvent(
new CustomEvent('tf-expand', {
bubbles: true,
composed: true,
detail: {id: key + (tag || ''), expanded: expanded},
detail: {id: (this.message.id || '') + (tag || ''), expanded: expanded},
})
);
}
toggle_expanded(tag) {
let key = this.expanded_key();
this.set_expanded(!this.expanded[key + (tag || '')], tag);
this.set_expanded(
!this.expanded[(this.message.id || '') + (tag || '')],
tag
);
}
is_expanded(tag) {
let key = this.expanded_key();
return this.expanded[key + (tag || '')];
return this.expanded[(this.message.id || '') + (tag || '')];
}
render_children() {
let self = this;
if (this.message.child_messages?.length) {
if (!this.expanded[this.expanded_key()]) {
if (!this.expanded[this.message.id]) {
return html`
<button
class="w3-button w3-theme-d1 w3-block w3-bar"
@ -414,7 +397,7 @@ class TfMessageElement extends LitElement {
class_background() {
return this.message?.decrypted
? 'w3-pale-red'
: this.allow_unread() && this.message?.rowid >= this.channel_unread
: this.message?.rowid >= this.channel_unread
? 'w3-theme-d2'
: 'w3-theme-d4';
}
@ -506,10 +489,7 @@ class TfMessageElement extends LitElement {
return html`
<header class="w3-bar">
<span class="w3-bar-item">
${this.render_unread_icon()}<tf-user
id=${this.message.author}
.users=${this.users}
></tf-user>
<tf-user id=${this.message.author} .users=${this.users}></tf-user>
</span>
${is_encrypted} ${this.render_menu()}
<div class="w3-bar-item w3-right" style="text-wrap: nowrap">
@ -594,54 +574,6 @@ class TfMessageElement extends LitElement {
`;
}
content_group_by_author() {
let sorted = this.message.messages
.map((x) => [
x.author,
x.content.blocking !== undefined
? x.content.blocking
? 'is blocking'
: 'is no longer blocking'
: x.content.following !== undefined
? x.content.following
? 'is following'
: 'is no longer following'
: '',
x.content.contact,
x,
])
.sort();
let result = [];
let last;
let group;
for (let row of sorted) {
if (last && last[0] == row[0] && last[1] == row[1]) {
group.push(row[2]);
} else {
if (group) {
result.push({author: last[0], action: last[1], users: group});
}
last = row;
group = [row[2]];
}
}
if (group) {
result.push({author: last[0], action: last[1], users: group});
}
return result;
}
allow_unread() {
return;
!this.channel.startsWith('@') && !this.channel.startsWith('%');
}
render_unread_icon() {
return this.allow_unread() && this.message?.rowid >= this.channel_unread
? html`✉️`
: undefined;
}
render() {
let content = this.message?.content;
if (this.message?.decrypted?.type == 'post') {
@ -650,90 +582,29 @@ class TfMessageElement extends LitElement {
let class_background = this.class_background();
let self = this;
if (this.message?.type === 'contact_group') {
if (this.expanded[this.expanded_key()]) {
return this.render_frame(html`
<div class="w3-padding">
${this.message.messages.map(
(x) =>
html`<tf-message
.message=${x}
whoami=${this.whoami}
.users=${this.users}
.drafts=${this.drafts}
.expanded=${this.expanded}
channel=${this.channel}
channel_unread=${this.channel_unread}
></tf-message>`
)}
</div>
<button
class="w3-button w3-theme-d1 w3-block w3-bar"
style="box-sizing: border-box"
@click=${() => self.set_expanded(false)}
>
Collapse
</button>
`);
} else {
return this.render_frame(html`
<div class="w3-padding">
${this.content_group_by_author().map(
(x) => html`
<div>
<tf-user id=${x.author} .users=${this.users}></tf-user>
${x.action}
${x.users.map(
(y) => html`
<tf-user id=${y} .users=${this.users}></tf-user>
`
)}
</div>
`
)}
</div>
<button
class="w3-button w3-theme-d1 w3-block w3-bar"
style="box-sizing: border-box"
@click=${() => self.set_expanded(true)}
>
Expand
</button>
`);
}
return this.render_frame(
html` ${this.message.messages.map(
(x) =>
html`<tf-message
.message=${x}
whoami=${this.whoami}
.users=${this.users}
.drafts=${this.drafts}
.expanded=${this.expanded}
channel=${this.channel}
channel_unread=${this.channel_unread}
></tf-message>`
)}`
);
} else if (this.message.placeholder) {
return this.render_frame(
html`<div>
<div class="w3-bar">
<a
class="w3-bar-item w3-panel w3-round-xlarge w3-theme-d1 w3-margin w3-button"
target="_top"
href=${'#' + encodeURIComponent(this.message?.id)}
html`<div class="w3-padding">
<p>
<a target="_top" href=${'#' + encodeURIComponent(this.message.id)}
>${this.message.id}</a
>
This message is not currently available.
</a>
<div class="w3-bar-item w3-right">
<button class="w3-button w3-theme-d1" @click=${this.toggle_menu}>
%
</button>
<div
class="w3-dropdown-content w3-bar-block w3-card-4 w3-theme-l1"
style="right: 48px"
>
<a
target="_top"
class="w3-button w3-bar-item"
href=${'#' + encodeURIComponent(this.message?.id)}
>View Message</a
>
<button
class="w3-button w3-bar-item w3-border-bottom"
@click=${this.copy_id}
>
Copy ID
</button>
</div>
</div>
</div>
(placeholder)
</p>
<div>${this.render_votes()}</div>
${(this.message.child_messages || []).map(
(x) => html`
@ -760,7 +631,7 @@ class TfMessageElement extends LitElement {
}
if (content.image !== undefined) {
image = html`
<div @click=${this.body_click}><img src=${'/' + (typeof content.image?.link == 'string' ? content.image.link : content.image) + '/view'} style="width: 256px; height: auto"></img></div>
<div><img src=${'/' + (typeof content.image?.link == 'string' ? content.image.link : content.image) + '/view'} style="width: 256px; height: auto"></img></div>
`;
}
if (content.description !== undefined) {
@ -783,60 +654,25 @@ class TfMessageElement extends LitElement {
</div>
`);
} else if (content.type == 'contact') {
return this.render_frame(html`
<div class="w3-bar">
<div class="w3-bar-item">
<tf-user id=${this.message.author} .users=${this.users}></tf-user>
is
${content.blocking === true
? 'blocking'
: content.blocking === false
? 'no longer blocking'
: content.following === true
? 'following'
: content.following === false
? 'no longer following'
: '?'}
<tf-user
id=${this.message.content.contact}
.users=${this.users}
></tf-user>
</div>
<div class="w3-bar-item w3-right">
<button class="w3-button w3-theme-d1" @click=${this.toggle_menu}>
%
</button>
<div
class="w3-dropdown-content w3-bar-block w3-card-4 w3-theme-l1"
style="right: 48px"
>
<a
target="_top"
class="w3-button w3-bar-item"
href=${'#' + encodeURIComponent(this.message?.id)}
>View Message</a
>
<button
class="w3-button w3-bar-item w3-border-bottom"
@click=${this.copy_id}
>
Copy ID
</button>
${this.drafts[this.message?.id] === undefined
? html`
<button
class="w3-button w3-bar-item"
@click=${this.show_reply}
>
Reply
</button>
`
: undefined}
</div>
</div>
${this.render_votes()} ${this.render_actions()}
return html`
<div class="w3-padding">
<tf-user id=${this.message.author} .users=${this.users}></tf-user>
is
${content.blocking === true
? 'blocking'
: content.blocking === false
? 'no longer blocking'
: content.following === true
? 'following'
: content.following === false
? 'no longer following'
: '?'}
<tf-user
id=${this.message.content.contact}
.users=${this.users}
></tf-user>
</div>
`);
`;
} else if (content.type == 'post') {
let self = this;
let body;

View File

@ -166,10 +166,7 @@ class TfNewsElement extends LitElement {
if (message?.content?.type === 'contact') {
group.push(message);
} else {
if (group.length == 1) {
result.push(group[0]);
group = [];
} else if (group.length > 1) {
if (group.length > 0) {
result.push({
rowid: Math.max(...group.map((x) => x.rowid)),
type: 'contact_group',
@ -180,10 +177,7 @@ class TfNewsElement extends LitElement {
result.push(message);
}
}
if (group.length == 1) {
result.push(group[0]);
group = [];
} else if (group.length > 1) {
if (group.length > 0) {
result.push({
rowid: Math.max(...group.map((x) => x.rowid)),
type: 'contact_group',

View File

@ -1,4 +1,4 @@
import {LitElement, html, until, unsafeHTML} from './lit-all.min.js';
import {LitElement, html, unsafeHTML} from './lit-all.min.js';
import * as tfrpc from '/static/tfrpc.js';
import * as tfutils from './tf-utils.js';
import {styles} from './tf-styles.js';
@ -166,74 +166,6 @@ class TfProfileElement extends LitElement {
navigator.clipboard.writeText(this.id);
}
show_image(link) {
let div = document.createElement('div');
div.style.left = 0;
div.style.top = 0;
div.style.width = '100%';
div.style.height = '100%';
div.style.position = 'fixed';
div.style.background = '#000';
div.style.zIndex = 100;
div.style.display = 'grid';
let img = document.createElement('img');
img.src = link;
img.style.maxWidth = '100%';
img.style.maxHeight = '100%';
img.style.display = 'block';
img.style.margin = 'auto';
img.style.objectFit = 'contain';
img.style.width = '100%';
div.appendChild(img);
function image_close(event) {
document.body.removeChild(div);
window.removeEventListener('keydown', image_close);
}
div.onclick = image_close;
window.addEventListener('keydown', image_close);
document.body.appendChild(div);
}
body_click(event) {
if (event.srcElement.tagName == 'IMG') {
this.show_image(event.srcElement.src);
}
}
toggle_account_list(event) {
let content = event.srcElement.nextElementSibling;
if (content.classList.toggle('w3-hide')) {
event.srcElement.innerText = 'Show Followed Accounts';
} else {
event.srcElement.innerText = 'Hide Followed Accounts';
}
}
async load_follows() {
let accounts = await tfrpc.rpc.following([this.id], 1);
return html`
<div class="w3-container">
<button
class="w3-button w3-block w3-theme-d1"
@click=${this.toggle_account_list}
>
Show Followed Accounts
</button>
<div class="w3-hide w3-card">
<ul class="w3-ul w3-theme-d4 w3-border-theme">
${Object.keys(accounts).map(
(x) => html`
<li class="w3-border-theme">
<tf-user id=${x} .users=${this.users}></tf-user>
</li>
`
)}
</ul>
</div>
</div>
`;
}
render() {
this.load();
let self = this;
@ -322,7 +254,7 @@ class TfProfileElement extends LitElement {
<header class="w3-container">
<p><tf-user id=${this.id} .users=${this.users}></tf-user> (${tfutils.human_readable_size(this.size)} in ${this.sequence} messages)</p>
</header>
<div class="w3-container" @click=${this.body_click}>
<div class="w3-container">
<div class="w3-margin-bottom" style="display: flex; flex-direction: row">
<input type="text" class="w3-input w3-border w3-theme-d1" style="display: flex 1 1" readonly value=${this.id}></input>
<button class="w3-button w3-theme-d1 w3-ripple" style="flex: 0 0 auto" @click=${this.copy_id}>Copy</button>
@ -348,7 +280,6 @@ class TfProfileElement extends LitElement {
Blocked by ${profile.blocked} identities.
</div>
</div>
${until(this.load_follows(), html`<p>Loading accounts followed...</p>`)}
<footer class="w3-container">
<p>
${edit}

View File

@ -308,12 +308,6 @@ class TfTabConnectionsElement extends LitElement {
<div class="w3-bar-item">
<tf-user id=${x.pubkey} .users=${self.users}></tf-user>
<div><small>${x.address}:${x.port}</small></div>
<div>
<small
>Last connection:
${new Date(x.last_success * 1000)}</small
>
</div>
</div>
</div>
${this.render_message(x)}

View File

@ -214,24 +214,6 @@ class TfTabNewsFeedElement extends LitElement {
[JSON.stringify(this.private_messages), start_time, end_time]
);
result = (await this.decrypt(result)).filter((x) => x.decrypted);
} else if (this.hash == '#👍') {
result = await tfrpc.rpc.query(
`
WITH votes AS (SELECT messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
FROM messages
JOIN json_each(?1) AS following ON messages.author = following.value
WHERE
messages.content ->> 'type' = 'vote' AND
(?2 IS NULL OR messages.timestamp >= ?2) AND messages.timestamp < ?3
ORDER BY timestamp DESC limit 20)
SELECT FALSE AS is_primary, messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
FROM votes
JOIN messages ON messages.id = votes.content ->> '$.vote.link'
UNION
SELECT TRUE AS is_primary, * FROM votes
`,
[JSON.stringify(this.following), start_time, end_time]
);
} else {
let t0 = new Date();
let initial_messages = await tfrpc.rpc.query(
@ -459,14 +441,9 @@ class TfTabNewsFeedElement extends LitElement {
`;
}
return cache(html`
${!this.hash.startsWith('#%')
? html`<button
class="w3-button w3-theme-d1"
@click=${this.mark_all_read}
>
Mark All Read
</button>`
: undefined}
<button class="w3-button w3-theme-d1" @click=${this.mark_all_read}>
Mark All Read
</button>
<tf-news
id="news"
whoami=${this.whoami}

View File

@ -202,12 +202,6 @@ class TfTabNewsElement extends LitElement {
style=${this.hash == '#@' ? 'font-weight: bold' : undefined}
>${this.unread_status('@')}@mentions</a
>
<a
href="#👍"
class="w3-bar-item w3-button"
style=${this.hash == '#👍' ? 'font-weight: bold' : undefined}
>${this.unread_status('👍')}👍votes</a
>
<a
href="#🔐"
class="w3-bar-item w3-button"
@ -241,18 +235,12 @@ class TfTabNewsElement extends LitElement {
<h4 style="margin: 0">Connections</h4>
</a>
${this.connections
.filter((x) => x.id)
.filter((x) => x.id && !x.destroy_reason)
.map(
(x) => html`
<tf-user
class="w3-bar-item"
style=${x.destroy_reason
? 'border-left: 4px solid red; border-right: 4px solid red'
: x.connected
? x.flags?.one_shot
? 'border-left: 4px solid blue; border-right: 4px solid blue'
: 'border-left: 4px solid green; border-right: 4px solid green'
: ''}
style="max-width: 100%"
id=${x.id}
fallback_name=${x.host}
.users=${this.users}

View File

@ -25,14 +25,14 @@
}:
pkgs.stdenv.mkDerivation rec {
pname = "tildefriends";
version = "0.0.31";
version = "0.0.30";
src = pkgs.fetchFromGitea {
domain = "dev.tildefriends.net";
owner = "cory";
repo = "tildefriends";
rev = "v${version}";
hash = "sha256-c2ZKVNikI5jN5GQuvp7S53qqnRZniSrJMF1FUZdVNPI=";
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.37.0",
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.37.0.tgz",
"integrity": "sha512-ghHIeRGfWB8h9Tc3sMdr7D5zp4sZvlCzG36Xjdh2ymmfAwvSaCJAAsL3HLyLEnHcE953+5Uox1bx5OS+YCW/7Q==",
"version": "6.36.8",
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.36.8.tgz",
"integrity": "sha512-yoRo4f+FdnD01fFt4XpfpMCcCAo9QvZOtbrXExn4SqzH32YC6LgzqxfLZw/r6Ge65xyY03mK/UfUqrVw1gFiFg==",
"dependencies": {
"@codemirror/state": "^6.5.0",
"style-mod": "^4.1.0",

947
deps/sqlite/shell.c vendored

File diff suppressed because it is too large Load Diff

4202
deps/sqlite/sqlite3.c vendored

File diff suppressed because it is too large Load Diff

146
deps/sqlite/sqlite3.h vendored
View File

@ -133,7 +133,7 @@ extern "C" {
**
** Since [version 3.6.18] ([dateof:3.6.18]),
** SQLite source code has been stored in the
** <a href="http://fossil-scm.org/">Fossil configuration management
** <a href="http://www.fossil-scm.org/">Fossil configuration management
** system</a>. ^The SQLITE_SOURCE_ID macro evaluates to
** a string which identifies a particular check-in of SQLite
** within its configuration management system. ^The SQLITE_SOURCE_ID
@ -146,9 +146,9 @@ extern "C" {
** [sqlite3_libversion_number()], [sqlite3_sourceid()],
** [sqlite_version()] and [sqlite_source_id()].
*/
#define SQLITE_VERSION "3.50.0"
#define SQLITE_VERSION_NUMBER 3050000
#define SQLITE_SOURCE_ID "2025-05-29 14:26:00 dfc790f998f450d9c35e3ba1c8c89c17466cb559f87b0239e4aab9d34e28f742"
#define SQLITE_VERSION "3.49.2"
#define SQLITE_VERSION_NUMBER 3049002
#define SQLITE_SOURCE_ID "2025-05-07 10:39:52 17144570b0d96ae63cd6f3edca39e27ebd74925252bbaf6723bcb2f6b4861fb1"
/*
** CAPI3REF: Run-Time Library Version Numbers
@ -1163,12 +1163,6 @@ struct sqlite3_io_methods {
** the value that M is to be set to. Before returning, the 32-bit signed
** integer is overwritten with the previous value of M.
**
** <li>[[SQLITE_FCNTL_BLOCK_ON_CONNECT]]
** The [SQLITE_FCNTL_BLOCK_ON_CONNECT] opcode is used to configure the
** VFS to block when taking a SHARED lock to connect to a wal mode database.
** This is used to implement the functionality associated with
** SQLITE_SETLK_BLOCK_ON_CONNECT.
**
** <li>[[SQLITE_FCNTL_DATA_VERSION]]
** The [SQLITE_FCNTL_DATA_VERSION] opcode is used to detect changes to
** a database file. The argument is a pointer to a 32-bit unsigned integer.
@ -1265,7 +1259,6 @@ struct sqlite3_io_methods {
#define SQLITE_FCNTL_CKSM_FILE 41
#define SQLITE_FCNTL_RESET_CACHE 42
#define SQLITE_FCNTL_NULL_IO 43
#define SQLITE_FCNTL_BLOCK_ON_CONNECT 44
/* deprecated names */
#define SQLITE_GET_LOCKPROXYFILE SQLITE_FCNTL_GET_LOCKPROXYFILE
@ -1996,16 +1989,13 @@ struct sqlite3_mem_methods {
**
** [[SQLITE_CONFIG_LOOKASIDE]] <dt>SQLITE_CONFIG_LOOKASIDE</dt>
** <dd> ^(The SQLITE_CONFIG_LOOKASIDE option takes two arguments that determine
** the default size of [lookaside memory] on each [database connection].
** the default size of lookaside memory on each [database connection].
** The first argument is the
** size of each lookaside buffer slot ("sz") and the second is the number of
** slots allocated to each database connection ("cnt").)^
** ^(SQLITE_CONFIG_LOOKASIDE sets the <i>default</i> lookaside size.
** The [SQLITE_DBCONFIG_LOOKASIDE] option to [sqlite3_db_config()] can
** be used to change the lookaside configuration on individual connections.)^
** The [-DSQLITE_DEFAULT_LOOKASIDE] option can be used to change the
** default lookaside configuration at compile-time.
** </dd>
** size of each lookaside buffer slot and the second is the number of
** slots allocated to each database connection.)^ ^(SQLITE_CONFIG_LOOKASIDE
** sets the <i>default</i> lookaside size. The [SQLITE_DBCONFIG_LOOKASIDE]
** option to [sqlite3_db_config()] can be used to change the lookaside
** configuration on individual connections.)^ </dd>
**
** [[SQLITE_CONFIG_PCACHE2]] <dt>SQLITE_CONFIG_PCACHE2</dt>
** <dd> ^(The SQLITE_CONFIG_PCACHE2 option takes a single argument which is
@ -2242,50 +2232,31 @@ struct sqlite3_mem_methods {
** [[SQLITE_DBCONFIG_LOOKASIDE]]
** <dt>SQLITE_DBCONFIG_LOOKASIDE</dt>
** <dd> The SQLITE_DBCONFIG_LOOKASIDE option is used to adjust the
** configuration of the [lookaside memory allocator] within a database
** configuration of the lookaside memory allocator within a database
** connection.
** The arguments to the SQLITE_DBCONFIG_LOOKASIDE option are <i>not</i>
** in the [DBCONFIG arguments|usual format].
** The SQLITE_DBCONFIG_LOOKASIDE option takes three arguments, not two,
** so that a call to [sqlite3_db_config()] that uses SQLITE_DBCONFIG_LOOKASIDE
** should have a total of five parameters.
** <ol>
** <li><p>The first argument ("buf") is a
** ^The first argument (the third parameter to [sqlite3_db_config()] is a
** pointer to a memory buffer to use for lookaside memory.
** The first argument may be NULL in which case SQLite will allocate the
** lookaside buffer itself using [sqlite3_malloc()].
** <li><P>The second argument ("sz") is the
** size of each lookaside buffer slot. Lookaside is disabled if "sz"
** is less than 8. The "sz" argument should be a multiple of 8 less than
** 65536. If "sz" does not meet this constraint, it is reduced in size until
** it does.
** <li><p>The third argument ("cnt") is the number of slots. Lookaside is disabled
** if "cnt"is less than 1. The "cnt" value will be reduced, if necessary, so
** that the product of "sz" and "cnt" does not exceed 2,147,418,112. The "cnt"
** parameter is usually chosen so that the product of "sz" and "cnt" is less
** than 1,000,000.
** </ol>
** <p>If the "buf" argument is not NULL, then it must
** point to a memory buffer with a size that is greater than
** or equal to the product of "sz" and "cnt".
** The buffer must be aligned to an 8-byte boundary.
** The lookaside memory
** ^The first argument after the SQLITE_DBCONFIG_LOOKASIDE verb
** may be NULL in which case SQLite will allocate the
** lookaside buffer itself using [sqlite3_malloc()]. ^The second argument is the
** size of each lookaside buffer slot. ^The third argument is the number of
** slots. The size of the buffer in the first argument must be greater than
** or equal to the product of the second and third arguments. The buffer
** must be aligned to an 8-byte boundary. ^If the second argument to
** SQLITE_DBCONFIG_LOOKASIDE is not a multiple of 8, it is internally
** rounded down to the next smaller multiple of 8. ^(The lookaside memory
** configuration for a database connection can only be changed when that
** connection is not currently using lookaside memory, or in other words
** when the value returned by [SQLITE_DBSTATUS_LOOKASIDE_USED] is zero.
** when the "current value" returned by
** [sqlite3_db_status](D,[SQLITE_DBSTATUS_LOOKASIDE_USED],...) is zero.
** Any attempt to change the lookaside memory configuration when lookaside
** memory is in use leaves the configuration unchanged and returns
** [SQLITE_BUSY].
** If the "buf" argument is NULL and an attempt
** to allocate memory based on "sz" and "cnt" fails, then
** lookaside is silently disabled.
** <p>
** The [SQLITE_CONFIG_LOOKASIDE] configuration option can be used to set the
** default lookaside configuration at initialization. The
** [-DSQLITE_DEFAULT_LOOKASIDE] option can be used to set the default lookaside
** configuration at compile-time. Typical values for lookaside are 1200 for
** "sz" and 40 to 100 for "cnt".
** </dd>
** [SQLITE_BUSY].)^</dd>
**
** [[SQLITE_DBCONFIG_ENABLE_FKEY]]
** <dt>SQLITE_DBCONFIG_ENABLE_FKEY</dt>
@ -3022,44 +2993,6 @@ SQLITE_API int sqlite3_busy_handler(sqlite3*,int(*)(void*,int),void*);
*/
SQLITE_API int sqlite3_busy_timeout(sqlite3*, int ms);
/*
** CAPI3REF: Set the Setlk Timeout
** METHOD: sqlite3
**
** This routine is only useful in SQLITE_ENABLE_SETLK_TIMEOUT builds. If
** the VFS supports blocking locks, it sets the timeout in ms used by
** eligible locks taken on wal mode databases by the specified database
** handle. In non-SQLITE_ENABLE_SETLK_TIMEOUT builds, or if the VFS does
** not support blocking locks, this function is a no-op.
**
** Passing 0 to this function disables blocking locks altogether. Passing
** -1 to this function requests that the VFS blocks for a long time -
** indefinitely if possible. The results of passing any other negative value
** are undefined.
**
** Internally, each SQLite database handle store two timeout values - the
** busy-timeout (used for rollback mode databases, or if the VFS does not
** support blocking locks) and the setlk-timeout (used for blocking locks
** on wal-mode databases). The sqlite3_busy_timeout() method sets both
** values, this function sets only the setlk-timeout value. Therefore,
** to configure separate busy-timeout and setlk-timeout values for a single
** database handle, call sqlite3_busy_timeout() followed by this function.
**
** Whenever the number of connections to a wal mode database falls from
** 1 to 0, the last connection takes an exclusive lock on the database,
** then checkpoints and deletes the wal file. While it is doing this, any
** new connection that tries to read from the database fails with an
** SQLITE_BUSY error. Or, if the SQLITE_SETLK_BLOCK_ON_CONNECT flag is
** passed to this API, the new connection blocks until the exclusive lock
** has been released.
*/
SQLITE_API int sqlite3_setlk_timeout(sqlite3*, int ms, int flags);
/*
** CAPI3REF: Flags for sqlite3_setlk_timeout()
*/
#define SQLITE_SETLK_BLOCK_ON_CONNECT 0x01
/*
** CAPI3REF: Convenience Routines For Running Queries
** METHOD: sqlite3
@ -5175,7 +5108,7 @@ SQLITE_API const void *sqlite3_column_decltype16(sqlite3_stmt*,int);
** other than [SQLITE_ROW] before any subsequent invocation of
** sqlite3_step(). Failure to reset the prepared statement using
** [sqlite3_reset()] would result in an [SQLITE_MISUSE] return from
** sqlite3_step(). But after [version 3.6.23.1] ([dateof:3.6.23.1]),
** sqlite3_step(). But after [version 3.6.23.1] ([dateof:3.6.23.1],
** sqlite3_step() began
** calling [sqlite3_reset()] automatically in this circumstance rather
** than returning [SQLITE_MISUSE]. This is not considered a compatibility
@ -7071,8 +7004,6 @@ SQLITE_API int sqlite3_autovacuum_pages(
**
** ^The second argument is a pointer to the function to invoke when a
** row is updated, inserted or deleted in a rowid table.
** ^The update hook is disabled by invoking sqlite3_update_hook()
** with a NULL pointer as the second parameter.
** ^The first argument to the callback is a copy of the third argument
** to sqlite3_update_hook().
** ^The second callback argument is one of [SQLITE_INSERT], [SQLITE_DELETE],
@ -11555,10 +11486,9 @@ SQLITE_API void sqlite3session_table_filter(
** is inserted while a session object is enabled, then later deleted while
** the same session object is disabled, no INSERT record will appear in the
** changeset, even though the delete took place while the session was disabled.
** Or, if one field of a row is updated while a session is enabled, and
** then another field of the same row is updated while the session is disabled,
** the resulting changeset will contain an UPDATE change that updates both
** fields.
** Or, if one field of a row is updated while a session is disabled, and
** another field of the same row is updated while the session is enabled, the
** resulting changeset will contain an UPDATE change that updates both fields.
*/
SQLITE_API int sqlite3session_changeset(
sqlite3_session *pSession, /* Session object */
@ -11630,9 +11560,8 @@ SQLITE_API sqlite3_int64 sqlite3session_changeset_size(sqlite3_session *pSession
** database zFrom the contents of the two compatible tables would be
** identical.
**
** Unless the call to this function is a no-op as described above, it is an
** error if database zFrom does not exist or does not contain the required
** compatible table.
** It an error if database zFrom does not exist or does not contain the
** required compatible table.
**
** If the operation is successful, SQLITE_OK is returned. Otherwise, an SQLite
** error code. In this case, if argument pzErrMsg is not NULL, *pzErrMsg
@ -11767,7 +11696,7 @@ SQLITE_API int sqlite3changeset_start_v2(
** The following flags may passed via the 4th parameter to
** [sqlite3changeset_start_v2] and [sqlite3changeset_start_v2_strm]:
**
** <dt>SQLITE_CHANGESETSTART_INVERT <dd>
** <dt>SQLITE_CHANGESETAPPLY_INVERT <dd>
** Invert the changeset while iterating through it. This is equivalent to
** inverting a changeset using sqlite3changeset_invert() before applying it.
** It is an error to specify this flag with a patchset.
@ -12082,6 +12011,19 @@ SQLITE_API int sqlite3changeset_concat(
void **ppOut /* OUT: Buffer containing output changeset */
);
/*
** CAPI3REF: Upgrade the Schema of a Changeset/Patchset
*/
SQLITE_API int sqlite3changeset_upgrade(
sqlite3 *db,
const char *zDb,
int nIn, const void *pIn, /* Input changeset */
int *pnOut, void **ppOut /* OUT: Inverse of input */
);
/*
** CAPI3REF: Changegroup Handle
**

View File

@ -366,8 +366,6 @@ struct sqlite3_api_routines {
/* Version 3.44.0 and later */
void *(*get_clientdata)(sqlite3*,const char*);
int (*set_clientdata)(sqlite3*, const char*, void*, void(*)(void*));
/* Version 3.50.0 and later */
int (*setlk_timeout)(sqlite3*,int,int);
};
/*
@ -701,8 +699,6 @@ typedef int (*sqlite3_loadext_entry)(
/* Version 3.44.0 and later */
#define sqlite3_get_clientdata sqlite3_api->get_clientdata
#define sqlite3_set_clientdata sqlite3_api->set_clientdata
/* Version 3.50.0 and later */
#define sqlite3_setlk_timeout sqlite3_api->setlk_timeout
#endif /* !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) */
#if !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION)

View File

@ -1,40 +0,0 @@
# Connecting with Manyverse
Communication with [Manyverse](https://www.manyver.se/) should Just Work (tm).
This document is intended as a cheat sheet for the instances where it doesn't.
If your experience differs, please share so we can make things better.
## Connecting Manyverse to the tildefriends.net room
Open the `Connections` tab. This is from the desktop app, but mobile is similar.
![Manyverse connections tab](manyverse_connections_tab.png)
Open the `Connections Panel`.
![Manyverse connections panel](manyverse_connections_panel.png)
Use the `Add Connection` button at the bottom right to open the dialog to enter
a connections string to add a new connection.
![Manyverse connections panel](manyverse_paste_invite_code.png)
Copy the tildefriends.net room code from https://www.tildefriends.net/~cory/room/.
![Tilde Friends room code](tildefriends_room_app.png)
Paste.
On mobile especially, make sure the full text is pasted without modification.
![Manyverse invite code](manyverse_code.png)
Click `Done`, and you should be connected successfully. tildefriends.net is
all things: a room, a pub, and a client, so you should be able to start replicating
immediately as well as find other similarly connected people with whom to establish
further connections.
When logged into tildefriends.net, active connections it sees can be found on
the `Connections` tab: https://www.tildefriends.net/~core/ssb/#connections,
which may indicate errors if you find yourself disconnecting.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 136 KiB

View File

@ -14,7 +14,7 @@
- upload to Apple with dist-ios on macos
- nix
- june and december: update release version
- run `nix --extra-experimental-features nix-command --extra-experimental-features flakes flake update`
- run `nix flake update`
- comment out the hash in default.nix
- update the version
- run `nix-build`

6
flake.lock generated
View File

@ -20,11 +20,11 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1748037224,
"narHash": "sha256-92vihpZr6dwEMV6g98M5kHZIttrWahb9iRPBm1atcPk=",
"lastModified": 1745279238,
"narHash": "sha256-AQ7M9wTa/Pa/kK5pcGTgX/DGqMHyzsyINfN7ktsI7Fo=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "f09dede81861f3a83f7f06641ead34f02f37597f",
"rev": "9684b53175fc6c09581e94cc85f05ab77464c7e3",
"type": "github"
},
"original": {

Binary file not shown.

After

Width:  |  Height:  |  Size: 275 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 108 KiB

After

Width:  |  Height:  |  Size: 101 KiB

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="38"
android:versionName="0.0.32-wip">
android:versionCode="37"
android:versionName="0.0.31">
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.INTERNET"/>
<application

View File

@ -813,11 +813,6 @@ void tf_http_destroy(tf_http_t* http)
return;
}
if (!http->is_shutting_down)
{
tf_printf("tf_http_destroy\n");
}
http->is_shutting_down = true;
http->is_in_destroy = true;

View File

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

View File

@ -277,13 +277,6 @@ void tf_ssb_db_init(tf_ssb_t* ssb)
"CREATE TRIGGER IF NOT EXISTS messages_ad AFTER DELETE ON messages BEGIN INSERT INTO messages_fts(messages_fts, rowid, content) VALUES ('delete', old.rowid, "
"old.content); END");
if (_tf_ssb_db_has_rows(db, "SELECT * FROM sqlite_schema WHERE type = 'trigger' AND name = 'messages_ai_refs' AND NOT sql LIKE '%ltrim%'"))
{
tf_printf("Deleting incorrect messages_refs...\n");
_tf_ssb_db_exec(db, "DROP TABLE IF EXISTS messages_refs");
tf_printf("Done.\n");
}
if (!_tf_ssb_db_has_rows(db, "PRAGMA table_list('messages_refs')"))
{
_tf_ssb_db_exec(db, "BEGIN TRANSACTION");
@ -298,9 +291,8 @@ void tf_ssb_db_init(tf_ssb_t* ssb)
"INSERT INTO messages_refs(message, ref) "
"SELECT messages.id, j.value FROM messages, json_tree(messages.content) AS j WHERE "
"j.value LIKE '&%.sha256' OR "
"j.value LIKE '!%%.sha256' ESCAPE '!' OR "
"j.value LIKE '@%.ed25519' OR "
"(j.value LIKE '#%' AND ltrim(substr(j.value, 2), 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_') = '') "
"j.value LIKE '%%%.sha256' OR "
"j.value LIKE '@%.ed25519' "
"ON CONFLICT DO NOTHING");
_tf_ssb_db_exec(db, "COMMIT TRANSACTION");
tf_printf("Done.\n");
@ -312,9 +304,8 @@ void tf_ssb_db_init(tf_ssb_t* ssb)
"INSERT INTO messages_refs(message, ref) "
"SELECT DISTINCT new.id, j.value FROM json_tree(new.content) AS j WHERE "
"j.value LIKE '&%.sha256' OR "
"j.value LIKE '!%%.sha256' ESCAPE '!' OR "
"j.value LIKE '@%.ed25519' OR "
"(j.value LIKE '#%' AND ltrim(substr(j.value, 2), 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_') = '') "
"j.value LIKE '%%%.sha256' OR "
"j.value LIKE '@%.ed25519' "
"ON CONFLICT DO NOTHING; END");
_tf_ssb_db_exec(db, "DROP TRIGGER IF EXISTS messages_ad_refs");
_tf_ssb_db_exec(db, "CREATE TRIGGER IF NOT EXISTS messages_ad_refs AFTER DELETE ON messages BEGIN DELETE FROM messages_refs WHERE messages_refs.message = old.id; END");
@ -550,7 +541,7 @@ static char* _tf_ssb_db_get_message_blob_wants(tf_ssb_t* ssb, int64_t rowid)
if (sqlite3_prepare_v2(db,
"SELECT DISTINCT json.value FROM messages, json_tree(messages.content) AS json LEFT OUTER JOIN blobs ON json.value = blobs.id WHERE messages.rowid = ?1 AND "
"json.value LIKE '&%.sha256' AND length(json.value) = ?2 AND blobs.content IS NULL",
"json.value LIKE '&%%.sha256' AND length(json.value) = ?2 AND blobs.content IS NULL",
-1, &statement, NULL) == SQLITE_OK)
{
if (sqlite3_bind_int64(statement, 1, rowid) == SQLITE_OK && sqlite3_bind_int(statement, 2, k_blob_id_len - 1) == SQLITE_OK)
@ -1897,15 +1888,13 @@ tf_ssb_db_stored_connection_t* tf_ssb_db_get_stored_connections(tf_ssb_t* ssb, i
int count = 0;
sqlite3_stmt* statement;
if (sqlite3_prepare_v2(db, "SELECT host, port, key, last_attempt, last_success FROM connections ORDER BY host, port, key", -1, &statement, NULL) == SQLITE_OK)
if (sqlite3_prepare_v2(db, "SELECT host, port, key FROM connections ORDER BY host, port, key", -1, &statement, NULL) == SQLITE_OK)
{
while (sqlite3_step(statement) == SQLITE_ROW)
{
result = tf_resize_vec(result, sizeof(tf_ssb_db_stored_connection_t) * (count + 1));
result[count] = (tf_ssb_db_stored_connection_t) {
.port = sqlite3_column_int(statement, 1),
.last_attempt = sqlite3_column_int64(statement, 3),
.last_success = sqlite3_column_int64(statement, 4),
};
snprintf(result[count].address, sizeof(result[count].address), "%s", (const char*)sqlite3_column_text(statement, 0));
snprintf(result[count].pubkey, sizeof(result[count].pubkey), "%s", (const char*)sqlite3_column_text(statement, 2));

View File

@ -340,10 +340,6 @@ typedef struct _tf_ssb_db_stored_connection_t
int port;
/** The identity. */
char pubkey[k_id_base64_len];
/** Time of last attempted connection. */
int64_t last_attempt;
/** Time of last successful connection. */
int64_t last_success;
} tf_ssb_db_stored_connection_t;
/**

View File

@ -988,8 +988,6 @@ static void _tf_ssb_stored_connections_after_work(tf_ssb_t* ssb, int status, voi
JS_SetPropertyStr(context, connection, "address", JS_NewString(context, work->connections[i].address));
JS_SetPropertyStr(context, connection, "port", JS_NewInt32(context, work->connections[i].port));
JS_SetPropertyStr(context, connection, "pubkey", JS_NewString(context, work->connections[i].pubkey));
JS_SetPropertyStr(context, connection, "last_attempt", JS_NewInt64(context, work->connections[i].last_attempt));
JS_SetPropertyStr(context, connection, "last_success", JS_NewInt64(context, work->connections[i].last_success));
JS_SetPropertyUint32(context, result, i, connection);
}
tf_free(work->connections);

View File

@ -1301,19 +1301,19 @@ JSValue tf_task_allocate_promise(tf_task_t* task, promiseid_t* out_promise)
JS_FreeValue(task->_context, error);
}
promiseid_t promise_id;
promiseid_t promiseId;
do
{
promise_id = task->_nextPromise++;
} while (_tf_task_find_promise(task, promise_id) || !promise_id);
promiseId = task->_nextPromise++;
} while (_tf_task_find_promise(task, promiseId) || !promiseId);
promise_t promise = {
.id = promise_id,
.id = promiseId,
.values = { JS_NULL, JS_NULL },
.stack_hash = stack_hash,
};
JSValue result = JS_NewPromiseCapability(task->_context, promise.values);
int index = tf_util_insert_index((void*)(intptr_t)promise_id, task->_promises, task->_promise_count, sizeof(promise_t), _promise_compare);
int index = tf_util_insert_index((void*)(intptr_t)promiseId, task->_promises, task->_promise_count, sizeof(promise_t), _promise_compare);
task->_promises = tf_resize_vec(task->_promises, sizeof(promise_t) * (task->_promise_count + 1));
if (task->_promise_count - index)
{
@ -1321,12 +1321,7 @@ JSValue tf_task_allocate_promise(tf_task_t* task, promiseid_t* out_promise)
}
task->_promises[index] = promise;
task->_promise_count++;
*out_promise = promise_id;
if (task->_shutting_down)
{
tf_task_reject_promise(task, promise_id, JS_ThrowInternalError(task->_context, "Shutting down"));
}
*out_promise = promiseId;
return result;
}
@ -1388,7 +1383,7 @@ static void _promise_release_for_task(tf_task_t* task, taskid_t task_id)
const promise_t* promise = &task->_promises[i];
if (promise->task == task_id)
{
tf_task_reject_promise(task, promise->id, JS_ThrowInternalError(task->_context, "Task is gone"));
tf_task_reject_promise(task, promise->id, JS_ThrowInternalError(task->_context, "Task is gone."));
more = true;
}
}
@ -1824,11 +1819,6 @@ JSValue tf_taskstub_kill(tf_taskstub_t* stub);
void tf_task_destroy(tf_task_t* task)
{
if (!task->_shutting_down)
{
tf_printf("tf_task_destroy\n");
}
task->_shutting_down = true;
while (task->_children)

View File

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

View File

@ -124,7 +124,7 @@ try:
select(driver, ['tf-navigation', 'shadow_root', '#close_error'], ('click',))
select(driver, ['tf-navigation', 'shadow_root', '=edit'], ('click',))
select(driver, ['#editor', '.cm-content'], ('click',))
select(driver, ['#editor', '.cm-content'], ('send_keys', 'app.setDocument(\n\t`<div id=\'test-div\' style=\'color: white; font-size: xx-large\'>\n\t\tHello, world!\n\t</div>`\n);'))
select(driver, ['#editor', '.cm-content'], ('send_keys', 'app.setDocument(\n\t"<div id=\'test-div\'>Hello, world!</div>"\n);'))
select(driver, ['#save'], ('click',))
select(driver, ['#document', 'frame', '#test-div'])