2025-01-11 14:09:42 -05:00
import { LitElement , cache , html , unsafeHTML , until } from './lit-all.min.js' ;
2023-05-02 16:47:27 +00:00
import * as tfrpc from '/static/tfrpc.js' ;
import { styles } from './tf-styles.js' ;
class TfTabNewsFeedElement extends LitElement {
static get properties ( ) {
return {
whoami : { type : String } ,
users : { type : Object } ,
hash : { type : String } ,
following : { type : Array } ,
messages : { type : Array } ,
drafts : { type : Object } ,
expanded : { type : Object } ,
2024-11-30 15:05:14 -05:00
channels _unread : { type : Object } ,
2024-12-10 21:09:55 -05:00
channels _latest : { type : Object } ,
2024-11-30 15:05:14 -05:00
loading : { type : Number } ,
time _range : { type : Array } ,
2024-12-07 14:58:01 -05:00
time _loading : { type : Array } ,
2025-02-05 18:41:37 -05:00
private _messages : { type : Array } ,
2023-05-02 16:47:27 +00:00
} ;
}
static styles = styles ;
constructor ( ) {
super ( ) ;
let self = this ;
this . whoami = null ;
this . users = { } ;
this . hash = '#' ;
this . following = [ ] ;
this . drafts = { } ;
this . expanded = { } ;
2024-11-30 15:05:14 -05:00
this . channels _unread = { } ;
2024-12-10 21:09:55 -05:00
this . channels _latest = { } ;
2024-12-05 20:47:02 -05:00
this . start _time = new Date ( ) . valueOf ( ) ;
2024-11-30 15:05:14 -05:00
this . time _range = [ 0 , 0 ] ;
2024-12-07 14:58:01 -05:00
this . time _loading = undefined ;
2024-12-04 20:28:57 -05:00
this . loading = 0 ;
2023-05-02 16:47:27 +00:00
}
2024-11-30 15:05:14 -05:00
channel ( ) {
2024-12-05 20:47:02 -05:00
return this . hash . startsWith ( '##' )
? this . hash . substring ( 2 )
: this . hash . substring ( 1 ) ;
2024-11-30 15:05:14 -05:00
}
async fetch _messages ( start _time , end _time ) {
2024-12-07 14:58:01 -05:00
this . time _loading = [ start _time , end _time ] ;
let result ;
2024-12-05 20:45:20 -05:00
if ( this . hash == '#@' ) {
2024-12-07 14:58:01 -05:00
result = await tfrpc . rpc . query (
2024-12-05 20:45:20 -05:00
`
2024-12-23 13:32:36 -05:00
WITH mentions 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 _fts ( ? 1 )
JOIN messages ON messages . rowid = messages _fts . rowid
JOIN json _each ( ? 2 ) AS following ON messages . author = following . value
WHERE
messages . author != ? 1 AND
2025-01-22 18:28:55 -05:00
( ? 3 IS NULL OR messages . timestamp >= ? 3 ) AND messages . timestamp < ? 4
2024-12-23 13:32:36 -05:00
ORDER BY timestamp DESC limit 20 )
2025-01-21 21:07:37 -05:00
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
2024-12-23 13:32:36 -05:00
FROM mentions
JOIN messages _refs ON mentions . id = messages _refs . ref
JOIN messages ON messages _refs . message = messages . id
UNION
2025-01-21 21:07:37 -05:00
SELECT TRUE AS is _primary , * FROM mentions
2024-12-05 20:47:02 -05:00
` ,
[
2024-12-05 20:45:20 -05:00
'"' + this . whoami . replace ( '"' , '""' ) + '"' ,
JSON . stringify ( this . following ) ,
start _time ,
end _time ,
2024-12-05 20:47:02 -05:00
]
) ;
2024-12-05 20:45:20 -05:00
} else if ( this . hash . startsWith ( '#@' ) ) {
2024-12-07 14:58:01 -05:00
result = await tfrpc . rpc . query (
2023-05-02 16:47:27 +00:00
`
2025-01-21 20:53:23 -05:00
WITH
2025-01-22 18:28:55 -05:00
selected AS ( SELECT rowid , id , previous , author , sequence , timestamp , hash , json ( content ) AS content , signature
2025-01-21 20:53:23 -05:00
FROM messages
2025-01-22 18:28:55 -05:00
WHERE messages . author = ? 1 AND ( ? 2 IS NULL OR messages . timestamp >= 2 ) AND messages . timestamp < ? 3
2025-01-21 20:53:23 -05:00
ORDER BY sequence DESC LIMIT 20
)
2025-01-21 21:07:37 -05:00
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
2025-01-21 20:53:23 -05:00
FROM selected
JOIN messages _refs ON selected . id = messages _refs . ref
2023-05-02 16:47:27 +00:00
JOIN messages ON messages _refs . message = messages . id
UNION
2025-01-21 21:07:37 -05:00
SELECT TRUE AS is _primary , * FROM selected
2023-05-02 16:47:27 +00:00
` ,
2024-12-07 14:25:19 -05:00
[ this . hash . substring ( 1 ) , start _time , end _time ]
2024-02-24 11:09:34 -05:00
) ;
2023-05-02 16:47:27 +00:00
} else if ( this . hash . startsWith ( '#%' ) ) {
2024-12-07 14:58:01 -05:00
result = await tfrpc . rpc . query (
2023-05-02 16:47:27 +00:00
`
2025-01-21 21:07:37 -05:00
SELECT TRUE AS is _primary , id , previous , author , sequence , timestamp , hash , json ( content ) AS content , signature
2023-05-02 16:47:27 +00:00
FROM messages
2025-01-15 19:32:31 -05:00
WHERE messages . id = ? 1
2023-05-02 16:47:27 +00:00
UNION
2025-01-21 21:07:37 -05:00
SELECT FALSE AS is _primary , id , previous , author , sequence , timestamp , hash , json ( content ) AS content , signature
2023-05-02 16:47:27 +00:00
FROM messages JOIN messages _refs
ON messages . id = messages _refs . message
WHERE messages _refs . ref = ? 1
` ,
2024-02-24 11:09:34 -05:00
[ this . hash . substring ( 1 ) ]
) ;
2024-11-30 15:05:14 -05:00
} else if ( this . hash . startsWith ( '##' ) ) {
2025-01-25 20:13:05 -05:00
result = await tfrpc . rpc . query (
`
WITH
all _news 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 ( ? ) AS following ON messages . author = following . value
WHERE messages . content - >> 'channel' = ? 4
UNION
SELECT messages . rowid , messages . id , messages . previous , messages . author , messages . sequence , messages . timestamp , messages . hash , json ( messages . content ) AS content , messages . signature
FROM messages _fts ( ? 5 )
JOIN messages ON messages . rowid = messages _fts . rowid
JOIN json _each ( ? 1 ) AS following ON messages . author = following . value
JOIN json _tree ( messages . content , '$.mentions' ) AS mention ON mention . value = '#' || ? 4 ) ,
news AS ( SELECT * FROM all _news
WHERE ( ? 2 IS NULL OR all _news . timestamp >= ? 2 ) AND all _news . timestamp < ? 3
ORDER BY all _news . 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 news
JOIN messages _refs ON news . id = messages _refs . ref
JOIN messages ON messages _refs . message = messages . id
UNION
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 news
JOIN messages _refs ON news . id = messages _refs . message
JOIN messages ON messages _refs . ref = messages . id
UNION
SELECT TRUE AS is _primary , news . * FROM news
` ,
[
JSON . stringify ( this . following ) ,
start _time ,
end _time ,
this . hash . substring ( 2 ) ,
'"#' + this . hash . substring ( 2 ) . replace ( '"' , '""' ) + '"' ,
]
) ;
2024-12-18 20:03:53 -05:00
} else if ( this . hash == '#🔐' ) {
result = await tfrpc . rpc . query (
`
2025-01-21 21:07:37 -05:00
SELECT TRUE AS is _primary , messages . rowid , messages . id , previous , author , sequence , timestamp , hash , json ( content ) AS content , signature
2025-01-29 12:15:46 -05:00
FROM messages
2025-02-05 18:41:37 -05:00
JOIN json _each ( ? 1 ) AS private _messages ON messages . id = private _messages . value
2025-01-29 12:15:46 -05:00
WHERE
( ? 2 IS NULL OR ( messages . timestamp >= ? 2 ) ) AND messages . timestamp < ? 3 AND
json ( messages . content ) LIKE '"%'
ORDER BY messages . sequence DESC LIMIT 20
2024-12-18 20:03:53 -05:00
` ,
2025-02-05 18:41:37 -05:00
[ JSON . stringify ( this . private _messages ) , start _time , end _time ]
2024-12-18 20:03:53 -05:00
) ;
2024-12-22 13:16:56 -05:00
result = ( await this . decrypt ( result ) ) . filter ( ( x ) => x . decrypted ) ;
2023-05-02 16:47:27 +00:00
} else {
2025-04-05 18:40:00 -04:00
let t0 = new Date ( ) ;
2025-01-21 21:07:37 -05:00
result = await tfrpc . rpc . query (
`
2025-01-25 20:13:05 -05:00
WITH
all _news 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
2025-04-02 12:44:17 -04:00
JOIN json _each ( ? ) AS following ON messages . author = following . value
) ,
2025-01-25 20:13:05 -05:00
news AS (
SELECT * FROM all _news
2025-03-30 13:18:16 -04:00
WHERE all _news . timestamp < ? 3 AND ( ? 2 IS NULL OR all _news . timestamp >= ? 2 )
2025-01-25 20:13:05 -05:00
ORDER BY timestamp DESC LIMIT 20
)
2025-03-30 12:16:23 -04:00
SELECT TRUE AS is _primary , news . * FROM news
2025-01-21 21:07:37 -05:00
` ,
2025-01-22 19:19:50 -05:00
[ JSON . stringify ( this . following ) , start _time , end _time ]
2025-01-21 21:07:37 -05:00
) ;
2025-04-05 18:40:00 -04:00
let news _length = result . length ;
let t1 = new Date ( ) ;
let refs = await tfrpc . rpc . query (
`
WITH
news AS (
SELECT value AS id FROM json _each ( ? )
)
SELECT refs _out . ref AS ref FROM messages _refs refs _out JOIN news ON refs _out . message = news . id
UNION
SELECT refs _in . message AS ref FROM messages _refs refs _in JOIN news ON refs _in . ref = news . id
` ,
2025-04-05 22:05:26 -04:00
[ JSON . stringify ( result . map ( ( x ) => x . id ) ) ]
2025-04-05 18:40:00 -04:00
) ;
let t2 = new Date ( ) ;
let related _messages = await tfrpc . rpc . query (
`
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 messages
JOIN json _each ( ? 2 ) refs ON messages . id = refs . value
JOIN json _each ( ? 1 ) AS following ON messages . author = following . value
` ,
2025-04-05 22:05:26 -04:00
[ JSON . stringify ( this . following ) , JSON . stringify ( refs . map ( ( x ) => x . ref ) ) ]
) ;
2025-04-05 18:40:00 -04:00
result = [ ] . concat ( result , related _messages ) ;
let t3 = new Date ( ) ;
2025-04-02 12:44:17 -04:00
console . log (
2025-04-05 18:40:00 -04:00
` load of ${ result . length } rows took ${ ( t3 - t0 ) / 1000 } ( ${ ( t1 - t0 ) / 1000 } to find ${ news _length } messages, ${ ( t2 - t1 ) / 1000 } to find ${ refs . length } refs, and ${ ( t3 - t2 ) / 1000 } to find ${ related _messages . length } related messages) following= ${ this . following . length } st= ${ start _time } et= ${ end _time } `
2025-04-02 12:44:17 -04:00
) ;
2023-05-02 16:47:27 +00:00
}
2024-12-07 14:58:01 -05:00
this . time _loading = undefined ;
return result ;
2023-05-02 16:47:27 +00:00
}
2024-12-07 14:25:19 -05:00
update _time _range _from _messages ( messages ) {
2025-01-21 21:07:37 -05:00
let only _primary = messages . filter ( ( x ) => x . is _primary ) ;
2024-12-07 14:25:19 -05:00
this . time _range = [
2025-01-21 21:07:37 -05:00
only _primary . reduce (
2024-12-07 14:25:19 -05:00
( accumulator , current ) => Math . min ( accumulator , current . timestamp ) ,
this . time _range [ 0 ]
) ,
2025-01-21 21:07:37 -05:00
only _primary . reduce (
2024-12-07 14:25:19 -05:00
( accumulator , current ) => Math . max ( accumulator , current . timestamp ) ,
this . time _range [ 1 ]
) ,
] ;
}
2023-05-03 22:32:21 +00:00
async load _more ( ) {
2024-11-30 15:05:14 -05:00
this . loading ++ ;
2024-11-30 17:49:27 -05:00
this . loading _canceled = false ;
2024-11-30 15:05:14 -05:00
try {
let more = [ ] ;
2025-01-22 18:28:55 -05:00
let last _start _time = this . time _range [ 0 ] ;
more = await this . fetch _messages ( null , last _start _time ) ;
this . update _time _range _from _messages (
2025-01-22 19:19:50 -05:00
more . filter ( ( x ) => x . timestamp < last _start _time )
2025-01-22 18:28:55 -05:00
) ;
2024-11-30 15:05:14 -05:00
this . messages = await this . decrypt ( [ ... more , ... this . messages ] ) ;
} finally {
this . loading -- ;
}
2023-09-02 02:06:29 +00:00
}
2024-11-30 17:49:27 -05:00
cancel _load ( ) {
this . loading _canceled = true ;
}
2023-09-02 02:06:29 +00:00
async decrypt ( messages ) {
let result = [ ] ;
for ( let message of messages ) {
let content ;
try {
content = JSON . parse ( message ? . content ) ;
2024-02-24 11:09:34 -05:00
} catch { }
if ( typeof content === 'string' ) {
2023-09-02 02:06:29 +00:00
let decrypted ;
try {
decrypted = await tfrpc . rpc . try _decrypt ( this . whoami , content ) ;
2024-02-24 11:09:34 -05:00
} catch { }
2023-09-02 02:06:29 +00:00
if ( decrypted ) {
try {
message . decrypted = JSON . parse ( decrypted ) ;
} catch {
message . decrypted = decrypted ;
}
}
}
result . push ( message ) ;
}
return result ;
}
2025-01-12 12:01:52 -05:00
merge _messages ( old _messages , new _messages ) {
2025-01-14 21:37:11 -05:00
let old _by _id = Object . fromEntries ( old _messages . map ( ( x ) => [ x . id , x ] ) ) ;
return new _messages . map ( ( x ) => ( old _by _id [ x . id ] ? old _by _id [ x . id ] : x ) ) ;
2025-01-12 12:01:52 -05:00
}
2024-12-01 18:20:57 -05:00
async load _latest ( ) {
this . loading ++ ;
let now = new Date ( ) . valueOf ( ) ;
let end _time = now + 24 * 60 * 60 * 1000 ;
let messages = [ ] ;
try {
2025-01-27 21:02:15 -05:00
messages = await this . fetch _messages ( this . time _range [ 0 ] , end _time ) ;
2024-12-01 18:20:57 -05:00
messages = await this . decrypt ( messages ) ;
2024-12-07 14:25:19 -05:00
this . update _time _range _from _messages (
messages . filter (
2025-01-25 18:00:06 -05:00
( x ) => x . timestamp >= this . time _range [ 0 ] && x . timestamp < end _time
2024-12-07 14:25:19 -05:00
)
) ;
2024-12-01 18:20:57 -05:00
} finally {
this . loading -- ;
}
2025-01-14 21:37:11 -05:00
this . messages = this . merge _messages (
this . messages ,
Object . values (
Object . fromEntries (
[ ... this . messages , ... messages ]
. sort ( ( x , y ) => x . timestamp - y . timestamp )
. slice ( - 1024 )
. map ( ( x ) => [ x . id , x ] )
)
2024-12-29 13:32:37 -05:00
)
2025-01-14 21:37:11 -05:00
) ;
2024-12-01 18:20:57 -05:00
console . log ( 'done loading latest messages.' ) ;
2023-05-03 22:32:21 +00:00
}
2024-11-30 15:05:14 -05:00
async load _messages ( ) {
2025-01-21 20:53:23 -05:00
let start _time = new Date ( ) ;
2024-11-30 15:05:14 -05:00
let self = this ;
2024-12-01 18:20:57 -05:00
this . loading ++ ;
2024-11-30 15:05:14 -05:00
let messages = [ ] ;
try {
2025-01-12 12:01:52 -05:00
if ( this . _messages _hash !== this . hash ) {
this . messages = [ ] ;
this . _messages _hash = this . hash ;
}
2024-11-30 15:05:14 -05:00
this . _messages _following = this . following ;
let now = new Date ( ) . valueOf ( ) ;
let start _time = now - 24 * 60 * 60 * 1000 ;
this . start _time = start _time ;
2025-02-02 10:14:05 -05:00
this . time _range = [ now + 24 * 60 * 60 * 1000 , now + 24 * 60 * 60 * 1000 ] ;
2025-01-22 19:19:50 -05:00
messages = await this . fetch _messages ( null , this . time _range [ 1 ] ) ;
2024-12-07 14:25:19 -05:00
this . update _time _range _from _messages (
2025-01-22 19:19:50 -05:00
messages . filter ( ( x ) => x . timestamp < this . time _range [ 1 ] )
2024-12-07 14:25:19 -05:00
) ;
2024-11-30 15:05:14 -05:00
messages = await this . decrypt ( messages ) ;
} finally {
2024-12-01 18:20:57 -05:00
this . loading -- ;
2024-11-30 15:05:14 -05:00
}
2025-01-12 12:01:52 -05:00
this . messages = this . merge _messages ( this . messages , messages ) ;
2024-12-07 14:58:01 -05:00
this . time _loading = undefined ;
2025-01-22 19:19:50 -05:00
console . log (
2025-04-02 12:44:17 -04:00
` loading ${ messages . length } messages done for ${ self . whoami } in ${ ( new Date ( ) - start _time ) / 1000 } s `
2025-01-22 19:19:50 -05:00
) ;
2024-11-30 15:05:14 -05:00
}
mark _all _read ( ) {
2024-12-05 20:47:02 -05:00
let newest = this . messages . reduce (
( accumulator , current ) => Math . max ( accumulator , current . rowid ) ,
2024-12-10 21:09:55 -05:00
this . channels _latest [ this . channel ( ) ] ? ? - 1
2024-12-05 20:47:02 -05:00
) ;
2024-11-30 15:05:14 -05:00
if ( newest >= 0 ) {
2024-12-05 20:47:02 -05:00
this . dispatchEvent (
new CustomEvent ( 'channelsetunread' , {
bubbles : true ,
composed : true ,
detail : {
channel : this . channel ( ) ,
unread : newest + 1 ,
} ,
} )
) ;
2024-11-30 15:05:14 -05:00
}
}
2023-05-02 16:47:27 +00:00
render ( ) {
2024-02-24 11:09:34 -05:00
if (
! this . messages ||
2023-05-02 16:47:27 +00:00
this . _messages _hash !== this . hash ||
2025-01-11 14:09:42 -05:00
JSON . stringify ( this . _messages _following ) !==
JSON . stringify ( this . following )
2024-02-24 11:09:34 -05:00
) {
console . log (
` loading messages for ${ this . whoami } (following ${ this . following . length } ) `
) ;
2024-11-30 15:05:14 -05:00
this . load _messages ( ) ;
2023-05-02 16:47:27 +00:00
}
2023-05-03 22:32:21 +00:00
let more ;
2024-12-08 09:40:02 -05:00
if ( ! this . hash . startsWith ( '#%' ) ) {
2023-05-03 22:32:21 +00:00
more = html `
2024-01-12 04:23:31 +00:00
< p >
2024-12-05 20:47:02 -05:00
< button class = "w3-button w3-theme-d1" @ click = $ { this . mark _all _read } >
Mark All Read
< / b u t t o n >
< button
? disabled = $ { this . loading }
class = "w3-button w3-theme-d1"
@ click = $ { this . load _more }
>
2024-02-24 11:09:34 -05:00
Load More
< / b u t t o n >
2024-12-05 20:47:02 -05:00
< button
class = $ { 'w3-button w3-theme-d1' + ( this . loading ? '' : ' w3-hide' ) }
@ click = $ { this . cancel _load }
>
2024-11-30 17:49:27 -05:00
Cancel
< / b u t t o n >
2024-12-05 20:47:02 -05:00
< span
2024-12-07 14:58:01 -05:00
> Showing
$ { new Date (
this . time _loading
? Math . min ( this . time _loading [ 0 ] , this . time _range [ 0 ] )
: this . time _range [ 0 ]
) . toLocaleDateString ( ) }
-
$ { new Date (
this . time _loading
? Math . max ( this . time _loading [ 1 ] , this . time _range [ 1 ] )
: this . time _range [ 1 ]
) . toLocaleDateString ( ) } . < / s p a n
2024-12-05 20:47:02 -05:00
>
2024-01-12 04:23:31 +00:00
< / p >
2023-05-03 22:32:21 +00:00
` ;
}
2025-01-11 14:09:42 -05:00
return cache ( html `
2024-12-05 20:47:02 -05:00
< button class = "w3-button w3-theme-d1" @ click = $ { this . mark _all _read } >
Mark All Read
< / b u t t o n >
2024-02-24 11:09:34 -05:00
< tf - news
id = "news"
whoami = $ { this . whoami }
. users = $ { this . users }
. messages = $ { this . messages }
. following = $ { this . following }
. drafts = $ { this . drafts }
. expanded = $ { this . expanded }
2024-11-30 15:05:14 -05:00
channel = $ { this . channel ( ) }
channel _unread = $ { this . channels _unread ? . [ this . channel ( ) ] }
2024-02-24 11:09:34 -05:00
> < / t f - n e w s >
2023-05-03 22:32:21 +00:00
$ { more }
2025-01-11 14:09:42 -05:00
` );
2023-05-02 16:47:27 +00:00
}
}
2024-02-24 11:09:34 -05:00
customElements . define ( 'tf-tab-news-feed' , TfTabNewsFeedElement ) ;