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 # that contain images that are to be included in the documentation (see the
# \image command). # \image command).
IMAGE_PATH = docs/images/ IMAGE_PATH =
# The INPUT_FILTER tag can be used to specify a program that doxygen should # 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 # 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. ## LD := Linker.
## ANDROID_SDK := Path to the Android SDK. ## ANDROID_SDK := Path to the Android SDK.
VERSION_CODE := 38 VERSION_CODE := 37
VERSION_CODE_IOS := 14 VERSION_CODE_IOS := 13
VERSION_NUMBER := 0.0.32-wip VERSION_NUMBER := 0.0.31
VERSION_NAME := This program kills fascists. VERSION_NAME := This program kills fascists.
IPHONEOS_VERSION_MIN=14.0 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 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_URL := https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage
APPIMAGETOOL_MD5 := e989fadfc4d685fd3d6aeeb9b525d74d out/appimagetool APPIMAGETOOL_MD5 := e989fadfc4d685fd3d6aeeb9b525d74d out/appimagetool

View File

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

View File

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

View File

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

View File

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

View File

@ -446,15 +446,12 @@ class TfComposeElement extends LitElement {
self.apps = await tfrpc.rpc.apps(); self.apps = await tfrpc.rpc.apps();
} }
if (!this.apps) { if (!this.apps) {
return html`<button return html`<button class="w3-button w3-theme-d1" @click=${attach_app}>
class="w3-button w3-bar-item w3-theme-d1"
@click=${attach_app}
>
Attach App Attach App
</button>`; </button>`;
} else { } else {
return html`<button return html`<button
class="w3-button w3-bar-item w3-theme-d1" class="w3-button w3-theme-d1"
@click=${() => (this.apps = null)} @click=${() => (this.apps = null)}
> >
Discard App Discard App
@ -475,9 +472,18 @@ class TfComposeElement extends LitElement {
if (draft.content_warning !== undefined) { if (draft.content_warning !== undefined) {
return html` return html`
<div class="w3-container w3-padding"> <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> <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> </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(); 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() { render() {
let self = this; let self = this;
let draft = self.get_draft(); let draft = self.get_draft();
@ -578,7 +559,7 @@ class TfComposeElement extends LitElement {
draft.encrypt_to !== undefined draft.encrypt_to !== undefined
? undefined ? undefined
: html`<button : html`<button
class="w3-button w3-bar-item w3-theme-d1" class="w3-button w3-theme-d1"
@click=${() => this.set_encrypt([])} @click=${() => this.set_encrypt([])}
> >
🔐 🔐
@ -633,43 +614,13 @@ class TfComposeElement extends LitElement {
> >
Submit Submit
</button> </button>
<div class="w3-dropdown-click"> <button class="w3-button w3-theme-d1" @click=${this.attach}>
<button class="w3-button w3-theme-d1" @click=${this.toggle_menu}> Attach
</button>
</button> ${this.render_attach_app_button()} ${encrypt}
<div class="w3-dropdown-content w3-bar-block"> <button class="w3-button w3-theme-d1" @click=${this.discard}>
${this.get_draft().content_warning === undefined Discard
? html` </button>
<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>
</footer> </footer>
</div> </div>
`; `;

View File

@ -1,11 +1,4 @@
import { import {LitElement, html, repeat, render, unsafeHTML} from './lit-all.min.js';
LitElement,
css,
html,
repeat,
render,
unsafeHTML,
} from './lit-all.min.js';
import * as tfrpc from '/static/tfrpc.js'; import * as tfrpc from '/static/tfrpc.js';
import * as tfutils from './tf-utils.js'; import * as tfutils from './tf-utils.js';
import * as emojis from './emojis.js'; import * as emojis from './emojis.js';
@ -93,18 +86,12 @@ class TfMessageElement extends LitElement {
render_votes() { render_votes() {
function normalize_expression(expression) { function normalize_expression(expression) {
if ( if (expression === 'Like' || expression === 'like' || !expression) {
expression === 'Unlike' || return '👍';
expression === 'unlike' || } else if (expression === 'Unlike' || expression === 'unlike') {
expression == 'undig'
) {
return '👎'; return '👎';
} else if (expression === 'heart') { } else if (expression === 'heart') {
return '❤️'; return '❤️';
} else if (
(expression ?? '').split('').every((x) => x.charCodeAt(0) < 256)
) {
return '👍';
} else { } else {
return expression; return expression;
} }
@ -310,35 +297,31 @@ class TfMessageElement extends LitElement {
return total; return total;
} }
expanded_key() {
return this.message?.id || this.messages?.map((x) => x.id).join(':');
}
set_expanded(expanded, tag) { set_expanded(expanded, tag) {
let key = this.expanded_key();
this.dispatchEvent( this.dispatchEvent(
new CustomEvent('tf-expand', { new CustomEvent('tf-expand', {
bubbles: true, bubbles: true,
composed: true, composed: true,
detail: {id: key + (tag || ''), expanded: expanded}, detail: {id: (this.message.id || '') + (tag || ''), expanded: expanded},
}) })
); );
} }
toggle_expanded(tag) { toggle_expanded(tag) {
let key = this.expanded_key(); this.set_expanded(
this.set_expanded(!this.expanded[key + (tag || '')], tag); !this.expanded[(this.message.id || '') + (tag || '')],
tag
);
} }
is_expanded(tag) { is_expanded(tag) {
let key = this.expanded_key(); return this.expanded[(this.message.id || '') + (tag || '')];
return this.expanded[key + (tag || '')];
} }
render_children() { render_children() {
let self = this; let self = this;
if (this.message.child_messages?.length) { if (this.message.child_messages?.length) {
if (!this.expanded[this.expanded_key()]) { if (!this.expanded[this.message.id]) {
return html` return html`
<button <button
class="w3-button w3-theme-d1 w3-block w3-bar" class="w3-button w3-theme-d1 w3-block w3-bar"
@ -414,7 +397,7 @@ class TfMessageElement extends LitElement {
class_background() { class_background() {
return this.message?.decrypted return this.message?.decrypted
? 'w3-pale-red' ? 'w3-pale-red'
: this.allow_unread() && this.message?.rowid >= this.channel_unread : this.message?.rowid >= this.channel_unread
? 'w3-theme-d2' ? 'w3-theme-d2'
: 'w3-theme-d4'; : 'w3-theme-d4';
} }
@ -506,10 +489,7 @@ class TfMessageElement extends LitElement {
return html` return html`
<header class="w3-bar"> <header class="w3-bar">
<span class="w3-bar-item"> <span class="w3-bar-item">
${this.render_unread_icon()}<tf-user <tf-user id=${this.message.author} .users=${this.users}></tf-user>
id=${this.message.author}
.users=${this.users}
></tf-user>
</span> </span>
${is_encrypted} ${this.render_menu()} ${is_encrypted} ${this.render_menu()}
<div class="w3-bar-item w3-right" style="text-wrap: nowrap"> <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() { render() {
let content = this.message?.content; let content = this.message?.content;
if (this.message?.decrypted?.type == 'post') { if (this.message?.decrypted?.type == 'post') {
@ -650,90 +582,29 @@ class TfMessageElement extends LitElement {
let class_background = this.class_background(); let class_background = this.class_background();
let self = this; let self = this;
if (this.message?.type === 'contact_group') { if (this.message?.type === 'contact_group') {
if (this.expanded[this.expanded_key()]) { return this.render_frame(
return this.render_frame(html` html` ${this.message.messages.map(
<div class="w3-padding"> (x) =>
${this.message.messages.map( html`<tf-message
(x) => .message=${x}
html`<tf-message whoami=${this.whoami}
.message=${x} .users=${this.users}
whoami=${this.whoami} .drafts=${this.drafts}
.users=${this.users} .expanded=${this.expanded}
.drafts=${this.drafts} channel=${this.channel}
.expanded=${this.expanded} channel_unread=${this.channel_unread}
channel=${this.channel} ></tf-message>`
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>
`);
}
} else if (this.message.placeholder) { } else if (this.message.placeholder) {
return this.render_frame( return this.render_frame(
html`<div> html`<div class="w3-padding">
<div class="w3-bar"> <p>
<a <a target="_top" href=${'#' + encodeURIComponent(this.message.id)}
class="w3-bar-item w3-panel w3-round-xlarge w3-theme-d1 w3-margin w3-button" >${this.message.id}</a
target="_top"
href=${'#' + encodeURIComponent(this.message?.id)}
> >
This message is not currently available. (placeholder)
</a> </p>
<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>
<div>${this.render_votes()}</div> <div>${this.render_votes()}</div>
${(this.message.child_messages || []).map( ${(this.message.child_messages || []).map(
(x) => html` (x) => html`
@ -760,7 +631,7 @@ class TfMessageElement extends LitElement {
} }
if (content.image !== undefined) { if (content.image !== undefined) {
image = html` 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) { if (content.description !== undefined) {
@ -783,60 +654,25 @@ class TfMessageElement extends LitElement {
</div> </div>
`); `);
} else if (content.type == 'contact') { } else if (content.type == 'contact') {
return this.render_frame(html` return html`
<div class="w3-bar"> <div class="w3-padding">
<div class="w3-bar-item"> <tf-user id=${this.message.author} .users=${this.users}></tf-user>
<tf-user id=${this.message.author} .users=${this.users}></tf-user> is
is ${content.blocking === true
${content.blocking === true ? 'blocking'
? 'blocking' : content.blocking === false
: content.blocking === false ? 'no longer blocking'
? 'no longer blocking' : content.following === true
: content.following === true ? 'following'
? 'following' : content.following === false
: content.following === false ? 'no longer following'
? 'no longer following' : '?'}
: '?'} <tf-user
<tf-user id=${this.message.content.contact}
id=${this.message.content.contact} .users=${this.users}
.users=${this.users} ></tf-user>
></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()}
</div> </div>
`); `;
} else if (content.type == 'post') { } else if (content.type == 'post') {
let self = this; let self = this;
let body; let body;

View File

@ -166,10 +166,7 @@ class TfNewsElement extends LitElement {
if (message?.content?.type === 'contact') { if (message?.content?.type === 'contact') {
group.push(message); group.push(message);
} else { } else {
if (group.length == 1) { if (group.length > 0) {
result.push(group[0]);
group = [];
} else if (group.length > 1) {
result.push({ result.push({
rowid: Math.max(...group.map((x) => x.rowid)), rowid: Math.max(...group.map((x) => x.rowid)),
type: 'contact_group', type: 'contact_group',
@ -180,10 +177,7 @@ class TfNewsElement extends LitElement {
result.push(message); result.push(message);
} }
} }
if (group.length == 1) { if (group.length > 0) {
result.push(group[0]);
group = [];
} else if (group.length > 1) {
result.push({ result.push({
rowid: Math.max(...group.map((x) => x.rowid)), rowid: Math.max(...group.map((x) => x.rowid)),
type: 'contact_group', 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 tfrpc from '/static/tfrpc.js';
import * as tfutils from './tf-utils.js'; import * as tfutils from './tf-utils.js';
import {styles} from './tf-styles.js'; import {styles} from './tf-styles.js';
@ -166,74 +166,6 @@ class TfProfileElement extends LitElement {
navigator.clipboard.writeText(this.id); 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() { render() {
this.load(); this.load();
let self = this; let self = this;
@ -322,7 +254,7 @@ class TfProfileElement extends LitElement {
<header class="w3-container"> <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> <p><tf-user id=${this.id} .users=${this.users}></tf-user> (${tfutils.human_readable_size(this.size)} in ${this.sequence} messages)</p>
</header> </header>
<div class="w3-container" @click=${this.body_click}> <div class="w3-container">
<div class="w3-margin-bottom" style="display: flex; flex-direction: row"> <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> <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> <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. Blocked by ${profile.blocked} identities.
</div> </div>
</div> </div>
${until(this.load_follows(), html`<p>Loading accounts followed...</p>`)}
<footer class="w3-container"> <footer class="w3-container">
<p> <p>
${edit} ${edit}

View File

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

View File

@ -214,24 +214,6 @@ class TfTabNewsFeedElement extends LitElement {
[JSON.stringify(this.private_messages), start_time, end_time] [JSON.stringify(this.private_messages), start_time, end_time]
); );
result = (await this.decrypt(result)).filter((x) => x.decrypted); 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 { } else {
let t0 = new Date(); let t0 = new Date();
let initial_messages = await tfrpc.rpc.query( let initial_messages = await tfrpc.rpc.query(
@ -459,14 +441,9 @@ class TfTabNewsFeedElement extends LitElement {
`; `;
} }
return cache(html` return cache(html`
${!this.hash.startsWith('#%') <button class="w3-button w3-theme-d1" @click=${this.mark_all_read}>
? html`<button Mark All Read
class="w3-button w3-theme-d1" </button>
@click=${this.mark_all_read}
>
Mark All Read
</button>`
: undefined}
<tf-news <tf-news
id="news" id="news"
whoami=${this.whoami} whoami=${this.whoami}

View File

@ -202,12 +202,6 @@ class TfTabNewsElement extends LitElement {
style=${this.hash == '#@' ? 'font-weight: bold' : undefined} style=${this.hash == '#@' ? 'font-weight: bold' : undefined}
>${this.unread_status('@')}@mentions</a >${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 <a
href="#🔐" href="#🔐"
class="w3-bar-item w3-button" class="w3-bar-item w3-button"
@ -241,18 +235,12 @@ class TfTabNewsElement extends LitElement {
<h4 style="margin: 0">Connections</h4> <h4 style="margin: 0">Connections</h4>
</a> </a>
${this.connections ${this.connections
.filter((x) => x.id) .filter((x) => x.id && !x.destroy_reason)
.map( .map(
(x) => html` (x) => html`
<tf-user <tf-user
class="w3-bar-item" class="w3-bar-item"
style=${x.destroy_reason style="max-width: 100%"
? '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'
: ''}
id=${x.id} id=${x.id}
fallback_name=${x.host} fallback_name=${x.host}
.users=${this.users} .users=${this.users}

View File

@ -25,14 +25,14 @@
}: }:
pkgs.stdenv.mkDerivation rec { pkgs.stdenv.mkDerivation rec {
pname = "tildefriends"; pname = "tildefriends";
version = "0.0.31"; version = "0.0.30";
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-c2ZKVNikI5jN5GQuvp7S53qqnRZniSrJMF1FUZdVNPI="; hash = "sha256-t5yvouzSL2j/ge1VHLqzIZ+Avqj4iEDt7L+yrHoTZAQ=";
fetchSubmodules = true; fetchSubmodules = true;
}; };

File diff suppressed because one or more lines are too long

View File

@ -144,9 +144,9 @@
} }
}, },
"node_modules/@codemirror/view": { "node_modules/@codemirror/view": {
"version": "6.37.0", "version": "6.36.8",
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.37.0.tgz", "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.36.8.tgz",
"integrity": "sha512-ghHIeRGfWB8h9Tc3sMdr7D5zp4sZvlCzG36Xjdh2ymmfAwvSaCJAAsL3HLyLEnHcE953+5Uox1bx5OS+YCW/7Q==", "integrity": "sha512-yoRo4f+FdnD01fFt4XpfpMCcCAo9QvZOtbrXExn4SqzH32YC6LgzqxfLZw/r6Ge65xyY03mK/UfUqrVw1gFiFg==",
"dependencies": { "dependencies": {
"@codemirror/state": "^6.5.0", "@codemirror/state": "^6.5.0",
"style-mod": "^4.1.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]), ** Since [version 3.6.18] ([dateof:3.6.18]),
** SQLite source code has been stored in the ** 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 ** system</a>. ^The SQLITE_SOURCE_ID macro evaluates to
** a string which identifies a particular check-in of SQLite ** a string which identifies a particular check-in of SQLite
** within its configuration management system. ^The SQLITE_SOURCE_ID ** within its configuration management system. ^The SQLITE_SOURCE_ID
@ -146,9 +146,9 @@ extern "C" {
** [sqlite3_libversion_number()], [sqlite3_sourceid()], ** [sqlite3_libversion_number()], [sqlite3_sourceid()],
** [sqlite_version()] and [sqlite_source_id()]. ** [sqlite_version()] and [sqlite_source_id()].
*/ */
#define SQLITE_VERSION "3.50.0" #define SQLITE_VERSION "3.49.2"
#define SQLITE_VERSION_NUMBER 3050000 #define SQLITE_VERSION_NUMBER 3049002
#define SQLITE_SOURCE_ID "2025-05-29 14:26:00 dfc790f998f450d9c35e3ba1c8c89c17466cb559f87b0239e4aab9d34e28f742" #define SQLITE_SOURCE_ID "2025-05-07 10:39:52 17144570b0d96ae63cd6f3edca39e27ebd74925252bbaf6723bcb2f6b4861fb1"
/* /*
** CAPI3REF: Run-Time Library Version Numbers ** 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 ** the value that M is to be set to. Before returning, the 32-bit signed
** integer is overwritten with the previous value of M. ** 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]] ** <li>[[SQLITE_FCNTL_DATA_VERSION]]
** The [SQLITE_FCNTL_DATA_VERSION] opcode is used to detect changes to ** 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. ** 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_CKSM_FILE 41
#define SQLITE_FCNTL_RESET_CACHE 42 #define SQLITE_FCNTL_RESET_CACHE 42
#define SQLITE_FCNTL_NULL_IO 43 #define SQLITE_FCNTL_NULL_IO 43
#define SQLITE_FCNTL_BLOCK_ON_CONNECT 44
/* deprecated names */ /* deprecated names */
#define SQLITE_GET_LOCKPROXYFILE SQLITE_FCNTL_GET_LOCKPROXYFILE #define SQLITE_GET_LOCKPROXYFILE SQLITE_FCNTL_GET_LOCKPROXYFILE
@ -1996,16 +1989,13 @@ struct sqlite3_mem_methods {
** **
** [[SQLITE_CONFIG_LOOKASIDE]] <dt>SQLITE_CONFIG_LOOKASIDE</dt> ** [[SQLITE_CONFIG_LOOKASIDE]] <dt>SQLITE_CONFIG_LOOKASIDE</dt>
** <dd> ^(The SQLITE_CONFIG_LOOKASIDE option takes two arguments that determine ** <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 ** The first argument is the
** size of each lookaside buffer slot ("sz") and the second is the number of ** size of each lookaside buffer slot and the second is the number of
** slots allocated to each database connection ("cnt").)^ ** slots allocated to each database connection.)^ ^(SQLITE_CONFIG_LOOKASIDE
** ^(SQLITE_CONFIG_LOOKASIDE sets the <i>default</i> lookaside size. ** sets the <i>default</i> lookaside size. The [SQLITE_DBCONFIG_LOOKASIDE]
** The [SQLITE_DBCONFIG_LOOKASIDE] option to [sqlite3_db_config()] can ** option to [sqlite3_db_config()] can be used to change the lookaside
** be used to change the lookaside configuration on individual connections.)^ ** configuration on individual connections.)^ </dd>
** The [-DSQLITE_DEFAULT_LOOKASIDE] option can be used to change the
** default lookaside configuration at compile-time.
** </dd>
** **
** [[SQLITE_CONFIG_PCACHE2]] <dt>SQLITE_CONFIG_PCACHE2</dt> ** [[SQLITE_CONFIG_PCACHE2]] <dt>SQLITE_CONFIG_PCACHE2</dt>
** <dd> ^(The SQLITE_CONFIG_PCACHE2 option takes a single argument which is ** <dd> ^(The SQLITE_CONFIG_PCACHE2 option takes a single argument which is
@ -2242,50 +2232,31 @@ struct sqlite3_mem_methods {
** [[SQLITE_DBCONFIG_LOOKASIDE]] ** [[SQLITE_DBCONFIG_LOOKASIDE]]
** <dt>SQLITE_DBCONFIG_LOOKASIDE</dt> ** <dt>SQLITE_DBCONFIG_LOOKASIDE</dt>
** <dd> The SQLITE_DBCONFIG_LOOKASIDE option is used to adjust the ** <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. ** connection.
** The arguments to the SQLITE_DBCONFIG_LOOKASIDE option are <i>not</i> ** The arguments to the SQLITE_DBCONFIG_LOOKASIDE option are <i>not</i>
** in the [DBCONFIG arguments|usual format]. ** in the [DBCONFIG arguments|usual format].
** The SQLITE_DBCONFIG_LOOKASIDE option takes three arguments, not two, ** The SQLITE_DBCONFIG_LOOKASIDE option takes three arguments, not two,
** so that a call to [sqlite3_db_config()] that uses SQLITE_DBCONFIG_LOOKASIDE ** so that a call to [sqlite3_db_config()] that uses SQLITE_DBCONFIG_LOOKASIDE
** should have a total of five parameters. ** should have a total of five parameters.
** <ol> ** ^The first argument (the third parameter to [sqlite3_db_config()] is a
** <li><p>The first argument ("buf") is a
** pointer to a memory buffer to use for lookaside memory. ** pointer to a memory buffer to use for lookaside memory.
** The first argument may be NULL in which case SQLite will allocate the ** ^The first argument after the SQLITE_DBCONFIG_LOOKASIDE verb
** lookaside buffer itself using [sqlite3_malloc()]. ** may be NULL in which case SQLite will allocate the
** <li><P>The second argument ("sz") is the ** lookaside buffer itself using [sqlite3_malloc()]. ^The second argument is the
** size of each lookaside buffer slot. Lookaside is disabled if "sz" ** size of each lookaside buffer slot. ^The third argument is the number of
** is less than 8. The "sz" argument should be a multiple of 8 less than ** slots. The size of the buffer in the first argument must be greater than
** 65536. If "sz" does not meet this constraint, it is reduced in size until ** or equal to the product of the second and third arguments. The buffer
** it does. ** must be aligned to an 8-byte boundary. ^If the second argument to
** <li><p>The third argument ("cnt") is the number of slots. Lookaside is disabled ** SQLITE_DBCONFIG_LOOKASIDE is not a multiple of 8, it is internally
** if "cnt"is less than 1. The "cnt" value will be reduced, if necessary, so ** rounded down to the next smaller multiple of 8. ^(The lookaside memory
** 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
** configuration for a database connection can only be changed when that ** configuration for a database connection can only be changed when that
** connection is not currently using lookaside memory, or in other words ** 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 ** Any attempt to change the lookaside memory configuration when lookaside
** memory is in use leaves the configuration unchanged and returns ** memory is in use leaves the configuration unchanged and returns
** [SQLITE_BUSY]. ** [SQLITE_BUSY].)^</dd>
** 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_DBCONFIG_ENABLE_FKEY]] ** [[SQLITE_DBCONFIG_ENABLE_FKEY]]
** <dt>SQLITE_DBCONFIG_ENABLE_FKEY</dt> ** <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); 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 ** CAPI3REF: Convenience Routines For Running Queries
** METHOD: sqlite3 ** 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 ** other than [SQLITE_ROW] before any subsequent invocation of
** sqlite3_step(). Failure to reset the prepared statement using ** sqlite3_step(). Failure to reset the prepared statement using
** [sqlite3_reset()] would result in an [SQLITE_MISUSE] return from ** [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 ** sqlite3_step() began
** calling [sqlite3_reset()] automatically in this circumstance rather ** calling [sqlite3_reset()] automatically in this circumstance rather
** than returning [SQLITE_MISUSE]. This is not considered a compatibility ** 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 ** ^The second argument is a pointer to the function to invoke when a
** row is updated, inserted or deleted in a rowid table. ** 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 ** ^The first argument to the callback is a copy of the third argument
** to sqlite3_update_hook(). ** to sqlite3_update_hook().
** ^The second callback argument is one of [SQLITE_INSERT], [SQLITE_DELETE], ** ^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 ** 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 ** 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. ** 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 ** Or, if one field of a row is updated while a session is disabled, and
** then another field of the same row is updated while the session is disabled, ** another field of the same row is updated while the session is enabled, the
** the resulting changeset will contain an UPDATE change that updates both ** resulting changeset will contain an UPDATE change that updates both fields.
** fields.
*/ */
SQLITE_API int sqlite3session_changeset( SQLITE_API int sqlite3session_changeset(
sqlite3_session *pSession, /* Session object */ 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 ** database zFrom the contents of the two compatible tables would be
** identical. ** identical.
** **
** Unless the call to this function is a no-op as described above, it is an ** It an error if database zFrom does not exist or does not contain the
** error if database zFrom does not exist or does not contain the required ** required compatible table.
** compatible table.
** **
** If the operation is successful, SQLITE_OK is returned. Otherwise, an SQLite ** If the operation is successful, SQLITE_OK is returned. Otherwise, an SQLite
** error code. In this case, if argument pzErrMsg is not NULL, *pzErrMsg ** 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 ** The following flags may passed via the 4th parameter to
** [sqlite3changeset_start_v2] and [sqlite3changeset_start_v2_strm]: ** [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 ** Invert the changeset while iterating through it. This is equivalent to
** inverting a changeset using sqlite3changeset_invert() before applying it. ** inverting a changeset using sqlite3changeset_invert() before applying it.
** It is an error to specify this flag with a patchset. ** 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 */ 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 ** CAPI3REF: Changegroup Handle
** **

View File

@ -366,8 +366,6 @@ struct sqlite3_api_routines {
/* Version 3.44.0 and later */ /* Version 3.44.0 and later */
void *(*get_clientdata)(sqlite3*,const char*); void *(*get_clientdata)(sqlite3*,const char*);
int (*set_clientdata)(sqlite3*, const char*, void*, void(*)(void*)); 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 */ /* Version 3.44.0 and later */
#define sqlite3_get_clientdata sqlite3_api->get_clientdata #define sqlite3_get_clientdata sqlite3_api->get_clientdata
#define sqlite3_set_clientdata sqlite3_api->set_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) */ #endif /* !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) */
#if !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 - upload to Apple with dist-ios on macos
- nix - nix
- june and december: update release version - 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 - comment out the hash in default.nix
- update the version - update the version
- run `nix-build` - run `nix-build`

6
flake.lock generated
View File

@ -20,11 +20,11 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1748037224, "lastModified": 1745279238,
"narHash": "sha256-92vihpZr6dwEMV6g98M5kHZIttrWahb9iRPBm1atcPk=", "narHash": "sha256-AQ7M9wTa/Pa/kK5pcGTgX/DGqMHyzsyINfN7ktsI7Fo=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "f09dede81861f3a83f7f06641ead34f02f37597f", "rev": "9684b53175fc6c09581e94cc85f05ab77464c7e3",
"type": "github" "type": "github"
}, },
"original": { "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"?> <?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="38" android:versionCode="37"
android:versionName="0.0.32-wip"> android:versionName="0.0.31">
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.INTERNET"/>
<application <application

View File

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

View File

@ -13,13 +13,13 @@
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>APPL</string> <string>APPL</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>0.0.32</string> <string>0.0.31</string>
<key>CFBundleSupportedPlatforms</key> <key>CFBundleSupportedPlatforms</key>
<array> <array>
<string>iPhoneOS</string> <string>iPhoneOS</string>
</array> </array>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>14</string> <string>13</string>
<key>DTPlatformName</key> <key>DTPlatformName</key>
<string>iphoneos</string> <string>iphoneos</string>
<key>LSRequiresIPhoneOS</key> <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, " "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"); "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')")) if (!_tf_ssb_db_has_rows(db, "PRAGMA table_list('messages_refs')"))
{ {
_tf_ssb_db_exec(db, "BEGIN TRANSACTION"); _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) " "INSERT INTO messages_refs(message, ref) "
"SELECT messages.id, j.value FROM messages, json_tree(messages.content) AS j WHERE " "SELECT messages.id, j.value FROM messages, json_tree(messages.content) AS j WHERE "
"j.value LIKE '&%.sha256' OR " "j.value LIKE '&%.sha256' OR "
"j.value LIKE '!%%.sha256' ESCAPE '!' OR " "j.value LIKE '%%%.sha256' OR "
"j.value LIKE '@%.ed25519' OR " "j.value LIKE '@%.ed25519' "
"(j.value LIKE '#%' AND ltrim(substr(j.value, 2), 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_') = '') "
"ON CONFLICT DO NOTHING"); "ON CONFLICT DO NOTHING");
_tf_ssb_db_exec(db, "COMMIT TRANSACTION"); _tf_ssb_db_exec(db, "COMMIT TRANSACTION");
tf_printf("Done.\n"); tf_printf("Done.\n");
@ -312,9 +304,8 @@ void tf_ssb_db_init(tf_ssb_t* ssb)
"INSERT INTO messages_refs(message, ref) " "INSERT INTO messages_refs(message, ref) "
"SELECT DISTINCT new.id, j.value FROM json_tree(new.content) AS j WHERE " "SELECT DISTINCT new.id, j.value FROM json_tree(new.content) AS j WHERE "
"j.value LIKE '&%.sha256' OR " "j.value LIKE '&%.sha256' OR "
"j.value LIKE '!%%.sha256' ESCAPE '!' OR " "j.value LIKE '%%%.sha256' OR "
"j.value LIKE '@%.ed25519' OR " "j.value LIKE '@%.ed25519' "
"(j.value LIKE '#%' AND ltrim(substr(j.value, 2), 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_') = '') "
"ON CONFLICT DO NOTHING; END"); "ON CONFLICT DO NOTHING; END");
_tf_ssb_db_exec(db, "DROP TRIGGER IF EXISTS messages_ad_refs"); _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"); _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, 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 " "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) -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) 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; int count = 0;
sqlite3_stmt* statement; 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) while (sqlite3_step(statement) == SQLITE_ROW)
{ {
result = tf_resize_vec(result, sizeof(tf_ssb_db_stored_connection_t) * (count + 1)); result = tf_resize_vec(result, sizeof(tf_ssb_db_stored_connection_t) * (count + 1));
result[count] = (tf_ssb_db_stored_connection_t) { result[count] = (tf_ssb_db_stored_connection_t) {
.port = sqlite3_column_int(statement, 1), .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].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)); 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; int port;
/** The identity. */ /** The identity. */
char pubkey[k_id_base64_len]; 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; } 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, "address", JS_NewString(context, work->connections[i].address));
JS_SetPropertyStr(context, connection, "port", JS_NewInt32(context, work->connections[i].port)); 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, "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); JS_SetPropertyUint32(context, result, i, connection);
} }
tf_free(work->connections); 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); JS_FreeValue(task->_context, error);
} }
promiseid_t promise_id; promiseid_t promiseId;
do do
{ {
promise_id = task->_nextPromise++; promiseId = task->_nextPromise++;
} while (_tf_task_find_promise(task, promise_id) || !promise_id); } while (_tf_task_find_promise(task, promiseId) || !promiseId);
promise_t promise = { promise_t promise = {
.id = promise_id, .id = promiseId,
.values = { JS_NULL, JS_NULL }, .values = { JS_NULL, JS_NULL },
.stack_hash = stack_hash, .stack_hash = stack_hash,
}; };
JSValue result = JS_NewPromiseCapability(task->_context, promise.values); 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)); task->_promises = tf_resize_vec(task->_promises, sizeof(promise_t) * (task->_promise_count + 1));
if (task->_promise_count - index) 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->_promises[index] = promise;
task->_promise_count++; task->_promise_count++;
*out_promise = promise_id; *out_promise = promiseId;
if (task->_shutting_down)
{
tf_task_reject_promise(task, promise_id, JS_ThrowInternalError(task->_context, "Shutting down"));
}
return result; 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]; const promise_t* promise = &task->_promises[i];
if (promise->task == task_id) 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; more = true;
} }
} }
@ -1824,11 +1819,6 @@ JSValue tf_taskstub_kill(tf_taskstub_t* stub);
void tf_task_destroy(tf_task_t* task) void tf_task_destroy(tf_task_t* task)
{ {
if (!task->_shutting_down)
{
tf_printf("tf_task_destroy\n");
}
task->_shutting_down = true; task->_shutting_down = true;
while (task->_children) 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." #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', '#close_error'], ('click',))
select(driver, ['tf-navigation', 'shadow_root', '=edit'], ('click',)) select(driver, ['tf-navigation', 'shadow_root', '=edit'], ('click',))
select(driver, ['#editor', '.cm-content'], ('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, ['#save'], ('click',))
select(driver, ['#document', 'frame', '#test-div']) select(driver, ['#document', 'frame', '#test-div'])