Index: backend/pgsql.t ================================================================== --- backend/pgsql.t +++ backend/pgsql.t @@ -207,11 +207,32 @@ now(), now(), array[]::bigint[], array[]::bigint[] ) returning id ]]; -- TODO array handling }; - instance_timeline_fetch = { + post_enum_author_uid = { + params = {uint64,uint64,uint64,uint64, uint64}, sql = [[ + select a.origin is null, + p.id, p.author, p.subject, p.acl, p.body, + extract(epoch from p.posted )::bigint, + extract(epoch from p.discovered)::bigint, + p.parent, p.convoheaduri + from parsav_posts as p + inner join parsav_actors as a on p.author = a.id + where p.author = $5::bigint and + ($1::bigint = 0 or p.posted <= to_timestamp($1::bigint)) and + ($2::bigint = 0 or to_timestamp($2::bigint) < p.posted) + order by (p.posted, p.discovered) desc + limit case when $3::bigint = 0 then null + else $3::bigint end + offset $4::bigint + ]] + }; + + -- maybe there's some way to unify these two, idk, im tired + + timeline_instance_fetch = { params = {uint64, uint64, uint64, uint64}, sql = [[ select true, p.id, p.author, p.subject, p.acl, p.body, extract(epoch from p.posted )::bigint, extract(epoch from p.discovered)::bigint, @@ -396,22 +417,25 @@ end end end local terra row_to_post(r: &pqr, row: intptr): lib.mem.ptr(lib.store.post) - --lib.io.fmt("body ptr %p len %llu\n", r:string(row,5), r:len(row,5)) - --lib.io.fmt("acl ptr %p len %llu\n", r:string(row,4), r:len(row,4)) var subj: rawstring, sblen: intptr + var cvhu: rawstring, cvhlen: intptr if r:null(row,3) then subj = nil sblen = 0 else subj = r:string(row,3) sblen = r:len(row,3)+1 end + if r:null(row,9) + then cvhu = nil cvhlen = 0 + else cvhu = r:string(row,9) cvhlen = r:len(row,9)+1 + end var p = [ lib.str.encapsulate(lib.store.post, { subject = { `subj, `sblen }; acl = {`r:string(row,4), `r:len(row,4)+1}; body = {`r:string(row,5), `r:len(row,5)+1}; - --convoheaduri = { `nil, `0 }; --FIXME + convoheaduri = { `cvhu, `cvhlen }; --FIXME }) ] p.ptr.id = r:int(uint64,row,1) p.ptr.author = r:int(uint64,row,2) p.ptr.posted = r:int(uint64,row,6) p.ptr.discovered = r:int(uint64,row,7) @@ -684,21 +708,29 @@ defer r:free() var id = r:int(uint64,0,0) return id end]; - instance_timeline_fetch = [terra(src: &lib.store.source, rg: lib.store.range) + timeline_instance_fetch = [terra(src: &lib.store.source, rg: lib.store.range) + var r = pqr { sz = 0 } + var A,B,C,D = rg:matrix() -- :/ + r = queries.timeline_instance_fetch.exec(src,A,B,C,D) + + var ret: lib.mem.ptr(lib.mem.ptr(lib.store.post)) ret:init(r.sz) + for i=0,r.sz do ret.ptr[i] = row_to_post(&r, i) end -- MUST FREE ALL + + return ret + end]; + + post_enum_author_uid = [terra( + src: &lib.store.source, + uid: uint64, + rg: lib.store.range + ): lib.mem.ptr(lib.mem.ptr(lib.store.post)) var r = pqr { sz = 0 } - if rg.mode == 0 then - r = queries.instance_timeline_fetch.exec(src,rg.from_time,rg.to_time,0,0) - elseif rg.mode == 1 then - r = queries.instance_timeline_fetch.exec(src,rg.from_time,0,rg.to_idx,0) - elseif rg.mode == 2 then - r = queries.instance_timeline_fetch.exec(src,0,rg.to_time,0,rg.from_idx) - elseif rg.mode == 3 then - r = queries.instance_timeline_fetch.exec(src,0,0,rg.to_idx,rg.from_idx) - end + var A,B,C,D = rg:matrix() -- :/ + r = queries.post_enum_author_uid.exec(src,A,B,C,D,uid) var ret: lib.mem.ptr(lib.mem.ptr(lib.store.post)) ret:init(r.sz) for i=0,r.sz do ret.ptr[i] = row_to_post(&r, i) end -- MUST FREE ALL return ret Index: doc/acl.md ================================================================== --- doc/acl.md +++ doc/acl.md @@ -9,17 +9,17 @@ * **followers**: matches users who follow you * **groupies**: matches users who follow you, but whom you do not follow * **mentioned**: matches users who are mentioned in the post * **staff**: matches instance staff (equivalent to `~%0`) * **admin**: matches the individual named as the instance administrator, if any -* **@**`handle`: matches the user `handle` -* **+**`circle`: matches users you have categorized under `circle` -* **#**`room`: matches users who are members of `room` -* **%**`rank`: matches users of `rank` or higher (e.g. `%3` matches users of rank 3, 2, and 1). as a special case, `%0` matches ordinary users -* **#**`room`**%**`rank`: matches users who hold `rank` in `room` -* **<**`title`**>**: matches peers of the net who have been created `title` by the sovereign -* **#**`room`**<**`title`**>**: matches peers of the chat who have been created `title` by `room` staff +* **@**_handle_: matches the user *handle* +* **+**_circle_: matches users you have categorized under *circle* +* **#**_room_: matches users who are members of *room* +* **%**_rank_: matches users of *rank* or higher (e.g. `%3` matches users of rank 3, 2, and 1). as a special case, `%0` matches ordinary users +* **#**_room_**%**_rank_: matches users who hold *rank* in *room* +* **<**_title_**>**: matches peers of the net who have been created *title* by the sovereign +* **#**_room_**<**_title_**>**: matches peers of the chat who have been created *title* by *room* staff to evaluate an ACL expression, `parsav` reads each term from start to finish. for each term, it considers whether it describes the user who is attempting to access the content. if the term matches, its policy is applied and the expression completes. if the term doesn't match, the server proceeds on to the next term and the process repeats until it finds a matching term or runs out of terms, applying the fallback policy. **policy** is whether a term grants or denies access. the default term policy is **allow**, but you can control the policy with the keywords `allow` and `deny`. if a term finishes evaluating without any match being found, a fallback policy is applied; this fallback is the opposite of whatever the current policy is. this sounds confusing but makes ACL expressions much more intuitive; `allow @bob` and `deny trent` do exactly what you'd expect — the former allows bob and only bob in; the latter denies access only to trent, but grants access to the rest of the world. Index: parsav.t ================================================================== --- parsav.t +++ parsav.t @@ -371,13 +371,13 @@ lib.load { 'srv'; 'render:nav'; 'render:login'; 'render:profile'; - 'render:userpage'; 'render:compose'; 'render:tweet'; + 'render:userpage'; 'render:timeline'; 'render:docpage'; 'route'; } Index: render/profile.t ================================================================== --- render/profile.t +++ render/profile.t @@ -12,11 +12,11 @@ auxp = lib.str.plit 'alter' elseif co.aid ~= 0 then aux:compose('followchat') if co.who.rights.powers:affect_users() then - aux:push('control',17) + aux:lpush('control') end auxp = aux:finalize() else aux:compose('remote follow') auxp = aux:finalize() Index: render/timeline.t ================================================================== --- render/timeline.t +++ render/timeline.t @@ -15,11 +15,11 @@ var posts = [lib.mem.vec(lib.mem.ptr(lib.store.post))] { sz = 0, run = 0 } if mode == modes.follow then elseif mode == modes.srvlocal then - posts = co.srv:instance_timeline_fetch(lib.store.range { + posts = co.srv:timeline_instance_fetch(lib.store.range { mode = 1; -- T->I from_time = stoptime; to_idx = 64; }) elseif mode == modes.fediglobal then Index: render/tweet.t ================================================================== --- render/tweet.t +++ render/tweet.t @@ -6,27 +6,22 @@ local terra render_tweet(co: &lib.srv.convo, p: &lib.store.post, acc: &lib.str.acc) var author: &lib.store.actor for j = 0, co.actorcache.top do - lib.io.fmt('scanning cache for author %llu (%llu/%llu)\n', p.author, j, co.actorcache.top) if p.author == co.actorcache(j).ptr.id then author = co.actorcache(j).ptr - lib.io.fmt('cache hit on idx %llu, skipping db lookup\n', j) goto foundauth end end - lib.io.fmt('cache miss, checking db for id %llu\n', p.author) author = co.actorcache:insert(co.srv:actor_fetch_uid(p.author)).ptr - lib.io.fmt('got author %s\n', author.handle) ::foundauth:: var avistr: lib.str.acc if author.origin == 0 then avistr:compose('/avi/',author.handle) end var timestr: int8[26] lib.osclock.ctime_r(&p.posted, ×tr[0]) - lib.io.fmt('got body %s\n', author.handle) var bhtml = lib.smackdown.html([lib.mem.ptr(int8)] {ptr=p.body,ct=0}) defer bhtml:free() var idbuf: int8[lib.math.shorthand.maxlen] var idlen = lib.math.shorthand.gen(p.id, idbuf) Index: render/userpage.t ================================================================== --- render/userpage.t +++ render/userpage.t @@ -5,16 +5,35 @@ if co.aid ~= 0 and co.who.id == actor.id then ti:compose('my profile') else ti:compose('profile :: ', actor.handle) end - var pftxt = lib.render.profile(co,actor) defer pftxt:free() var tiptr = ti:finalize() + + var acc: lib.str.acc acc:init(1024) + var pftxt = lib.render.profile(co,actor) defer pftxt:free() + acc:ppush(pftxt) + + var stoptime = lib.osclock.time(nil) + var posts = co.srv:post_enum_author_uid(actor.id, lib.store.range { + mode = 1; -- T->I + from_time = stoptime; + to_idx = 64; + }) + + for i = 0, posts.sz do + lib.render.tweet(co, posts(i).ptr, &acc) + posts(i):free() + end + posts:free() + + var bdf = acc:finalize() co:stdpage([lib.srv.convo.page] { - title = tiptr; body = pftxt; + title = tiptr; body = bdf; class = lib.str.plit 'profile'; }) tiptr:free() + bdf:free() end return render_userpage Index: srv.t ================================================================== --- srv.t +++ srv.t @@ -20,15 +20,28 @@ terra cfgcache:free() -- :/ self.secret:free() self.instance:free() end -terra srv:instance_timeline_fetch(r: lib.store.range): lib.mem.vec(lib.mem.ptr(lib.store.post)) +terra srv:post_enum_author_uid(uid: uint64, r: lib.store.range): lib.mem.vec(lib.mem.ptr(lib.store.post)) + var all: lib.mem.vec(lib.mem.ptr(lib.store.post)) all:init(64) + for i=0,self.sources.ct do var src = self.sources.ptr + i + if src.handle ~= nil and src.backend.timeline_instance_fetch ~= nil then + var lst = src:post_enum_author_uid(uid,r) + all:assure(all.sz + lst.ct) + for j=0, lst.ct do all:push(lst.ptr[j]) end + lst:free() + end + end + return all +end + +terra srv:timeline_instance_fetch(r: lib.store.range): lib.mem.vec(lib.mem.ptr(lib.store.post)) var all: lib.mem.vec(lib.mem.ptr(lib.store.post)) all:init(64) for i=0,self.sources.ct do var src = self.sources.ptr + i - if src.handle ~= nil and src.backend.instance_timeline_fetch ~= nil then - var lst = src:instance_timeline_fetch(r) + if src.handle ~= nil and src.backend.timeline_instance_fetch ~= nil then + var lst = src:timeline_instance_fetch(r) all:assure(all.sz + lst.ct) for j=0, lst.ct do all:push(lst.ptr[j]) end lst:free() end end Index: static/style.scss ================================================================== --- static/style.scss +++ static/style.scss @@ -159,10 +159,12 @@ @supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) { background-color: adjust-color($color, $lightness: -53%, $alpha: -0.1); } } +h1 { margin-top: 0 } + header { position: fixed; height: min-content; width: 100vw; margin: 0; @@ -193,11 +195,11 @@ justify-content: flex-end; align-items: center; grid-column: 2/3; grid-row: 1/2; > a[href] { display: block; - padding: 0.25in 0.15in; + padding: 0.25in 0.10in; //padding: calc((25% - 1em)/2) 0.15in; &, &::after { transition: 0.3s; } text-shadow: 1px 1px 1px black; &:hover{ transform: scale(120%); @@ -223,23 +225,23 @@ right: 1px solid black; } } div.profile { - @extend %box; padding: 0.1in; position: relative; display: grid; + margin-bottom: 0.4in; grid-template-columns: 2fr 1fr; - grid-template-rows: 1fr 1fr; + grid-template-rows: max-content 1fr; width: 100%; > .banner { grid-column: 1 / 3; grid-row: 1 / 2; display: grid; grid-template-columns: 1.1in 1fr; - grid-template-rows: 0.3in 1fr; + grid-template-rows: max-content 1fr; > .avatar { display: block; width: 1in; height: 1in; grid-column: 1 / 2; grid-row: 1 / 3; @@ -267,19 +269,20 @@ > .stats { grid-column: 3 / 4; grid-row: 1 / 3; } > .menu { - grid-column: 1 / 3; - grid-row: 2 / 3; + grid-column: 1 / 3; grid-row: 2 / 3; + padding-top: 0.075in; + flex-wrap: wrap; display: flex; justify-content: center; align-items: center; > a[href] { @extend %button; display: block; - margin: 0 0.05in; + margin: 0.025in 0.05in; } > hr { all: unset; display: block; height: 0.3in; @@ -415,25 +418,31 @@ } } code { @extend %teletype; - background: tone(-50%); - border: 1px solid tone(-20%); + background: tone(-55%); + border: 1px inset tone(-20%); padding: 2px 6px; - text-shadow: 2px 2px black; + font-size: 1.5ex !important; + letter-spacing: 1.3px; + padding-bottom: 3px; + border-radius: 2px; + vertical-align: baseline; + box-shadow: 1px 1px 1px black; } div.post { @extend %box; display: grid; grid-template-columns: 1in 1fr max-content; - grid-template-rows: 1fr max-content; + grid-template-rows: min-content max-content; margin-bottom: 0.1in; >.avatar { grid-column: 1/2; grid-row: 1/2; - width: 1in; + img { display: block; width: 1in; margin:0; } + background: linear-gradient(to bottom, tone(-53%), tone(-57%)); } >a[href].username { display: block; grid-column: 1/3; grid-row: 2/3; @@ -471,6 +480,12 @@ body.doc main { @extend %serif; li { margin-top: 0.05in; } li:first-child { margin-top: 0; } + h1, h2, h3, h4, h5, h6 { + background: linear-gradient(to right, tone(-50%), transparent); + margin-left: -0.4in; + padding-left: 0.2in; + text-shadow: 0 2px 0 black; + } } Index: store.t ================================================================== --- store.t +++ store.t @@ -93,10 +93,22 @@ union { to_time: m.timepoint to_idx: uint64 } } + +terra m.range:matrix() + if self.mode == 0 then + return self.from_time,self.to_time,0,0 + elseif self.mode == 1 then + return self.from_time,0,self.to_idx,0 + elseif self.mode == 2 then + return 0,self.to_time,0,self.from_idx + elseif self.mode == 3 then + return 0,0,self.to_idx,self.from_idx + else lib.bail('invalid mode on timeline range!') end +end struct m.post { id: uint64 author: uint64 subject: str @@ -104,11 +116,11 @@ acl: str posted: m.timepoint discovered: m.timepoint mentions: lib.mem.ptr(uint64) circles: lib.mem.ptr(uint64) --only meaningful if scope is set to circle - convo: uint64 + convoheaduri: str parent: uint64 -- ephemera localpost: bool source: &m.source } @@ -224,16 +236,16 @@ actor_conf_str: cnf(rawstring, lib.mem.ptr(int8)) actor_conf_int: cnf(intptr, lib.stat(intptr)) post_save: {&m.source, &m.post} -> {} post_create: {&m.source, &m.post} -> uint64 - actor_post_fetch_uid: {&m.source, uint64, m.range} -> lib.mem.ptr(m.post) + post_enum_author_uid: {&m.source, uint64, m.range} -> lib.mem.ptr(lib.mem.ptr(m.post)) convo_fetch_xid: {&m.source,rawstring} -> lib.mem.ptr(m.post) convo_fetch_uid: {&m.source,uint64} -> lib.mem.ptr(m.post) - actor_timeline_fetch_uid: {&m.source, uint64, m.range} -> lib.mem.ptr(lib.mem.ptr(m.post)) - instance_timeline_fetch: {&m.source, m.range} -> lib.mem.ptr(lib.mem.ptr(m.post)) + timeline_actor_fetch_uid: {&m.source, uint64, m.range} -> lib.mem.ptr(lib.mem.ptr(m.post)) + timeline_instance_fetch: {&m.source, m.range} -> lib.mem.ptr(lib.mem.ptr(m.post)) } struct m.source { backend: &m.backend id: lib.mem.ptr(int8) Index: view/tweet.tpl ================================================================== --- view/tweet.tpl +++ view/tweet.tpl @@ -1,11 +1,11 @@
- +
@nym [@xid]
@subject
@text