| Comment: | add live updates, system to only update when necessary almost works |
|---|---|
| Downloads: | Tarball | ZIP archive | SQL archive |
| Timelines: | family | ancestors | descendants | both | trunk |
| Files: | files | file ages | folders |
| SHA3-256: |
24ec4090837417ad087eff63c16c1336 |
| User & Date: | lexi on 2021-01-01 04:33:10 |
| Other Links: | manifest | tags |
|
2021-01-01
| ||
| 16:24 | move from webp to svg except where necessary check-in: aa17a03321 user: lexi tags: trunk | |
| 04:33 | add live updates, system to only update when necessary almost works check-in: 24ec409083 user: lexi tags: trunk | |
|
2020-12-31
| ||
| 02:18 | start work on user mgmt check-in: db4c5fd644 user: lexi tags: trunk | |
Modified acl.t from [7cc6c4467d] to [db020952e6].
10 10 id: uint64 11 11 } 12 12 13 13 terra m.eval(expr: lib.str.t, agent: m.agent) 14 14 15 15 end 16 16 17 + 18 +terra lib.store.post:comp() 19 + -- TODO extract mentions from body, circles from acl 20 + self.mentions = [lib.mem.ptr(uint64)].null() 21 + self.circles = [lib.mem.ptr(uint64)].null() 22 + self.convoheaduri = nil 23 +end 24 + 17 25 terra lib.store.post:save(ctupdate: bool) 18 --- this post handles the messy details of registering a post's 19 --- circles and actors, and increments the edit-count if ctupdate 20 --- is true, which is should be in almost all cases. 26 + -- this post handles the messy details of registering a post's 27 + -- circles and actors, and increments the edit-count if ctupdate 28 + -- is true, which is should be in almost all cases. 21 29 if ctupdate then 22 30 self.chgcount = self.chgcount + 1 23 31 self.edited = lib.osclock.time(nil) 24 32 end 25 - -- TODO extract mentions from body, circles from acl 33 + self:comp() 26 34 self.source:post_save(self) 27 35 end 28 36 29 37 return m
Modified backend/pgsql.t from [35848d4bf0] to [3e99c3d4ab].
254 254 update parsav_actors set 255 255 authtime = to_timestamp($2::bigint) 256 256 where id = $1::bigint 257 257 ]]; 258 258 }; 259 259 260 260 auth_create_pw = { 261 - params = {uint64, lib.mem.ptr(uint8)}, cmd = true, sql = [[ 262 - insert into parsav_auth (uid, name, kind, cred) values ( 261 + params = {uint64, binblob, pstring}, cmd = true, sql = [[ 262 + insert into parsav_auth (uid, name, kind, cred, comment) values ( 263 263 $1::bigint, 264 264 (select handle from parsav_actors where id = $1::bigint), 265 - 'pw-sha256', $2::bytea 265 + 'pw-sha256', $2::bytea, 266 + $3::text 266 267 ) 267 268 ]] 268 269 }; 269 270 270 271 auth_purge_type = { 271 272 params = {rawstring, uint64, rawstring}, cmd = true, sql = [[ 272 273 delete from parsav_auth where 273 274 ((uid = 0 and name = $1::text) or uid = $2::bigint) and 274 275 kind like $3::text 275 276 ]] 276 277 }; 278 + 279 + auth_enum_uid = { 280 + params = {uint64}, sql = [[ 281 + select aid, kind, comment, netmask, blacklist from parsav_auth where uid = $1::bigint 282 + ]]; 283 + }; 284 + 285 + auth_enum_handle = { 286 + params = {rawstring}, sql = [[ 287 + select aid, kind, comment, netmask, blacklist from parsav_auth where name = $1::text 288 + ]]; 289 + }; 277 290 278 291 post_save = { 279 292 params = { 280 293 uint64, uint32, int64; 281 294 rawstring, rawstring, rawstring; 282 295 }, cmd = true, sql = [[ 283 296 update parsav_posts set ................................................................................ 287 300 chgcount = $2::integer, 288 301 edited = to_timestamp($3::bigint) 289 302 where id = $1::bigint 290 303 ]] 291 304 }; 292 305 293 306 post_create = { 294 - params = {uint64, rawstring, rawstring, rawstring}, sql = [[ 307 + params = { 308 + uint64, rawstring, rawstring, rawstring, 309 + uint64, uint64, rawstring 310 + }, sql = [[ 295 311 insert into parsav_posts ( 296 312 author, subject, acl, body, 297 - posted, discovered, 298 - circles, mentions 313 + parent, posted, discovered, 314 + circles, mentions, convoheaduri 299 315 ) values ( 300 316 $1::bigint, case when $2::text = '' then null else $2::text end, 301 317 $3::text, $4::text, 302 - now(), now(), array[]::bigint[], array[]::bigint[] 318 + $5::bigint, to_timestamp($6::bigint), now(), 319 + array[]::bigint[], array[]::bigint[], $7::text 303 320 ) returning id 304 321 ]]; -- TODO array handling 305 322 }; 306 323 307 324 post_destroy_prepare = { 308 325 params = {uint64}, cmd = true, sql = [[ 309 326 update parsav_posts set ................................................................................ 321 338 post_fetch = { 322 339 params = {uint64}, sql = [[ 323 340 select a.origin is null, 324 341 p.id, p.author, p.subject, p.acl, p.body, 325 342 extract(epoch from p.posted )::bigint, 326 343 extract(epoch from p.discovered)::bigint, 327 344 extract(epoch from p.edited )::bigint, 328 - p.parent, p.convoheaduri, p.chgcount 345 + p.parent, p.convoheaduri, p.chgcount, 346 + coalesce(c.value, -1)::smallint 347 + 329 348 from parsav_posts as p 330 - inner join parsav_actors as a on p.author = a.id 349 + inner join parsav_actors as a on p.author = a.id 350 + left join parsav_actor_conf_ints as c on c.uid = a.id and c.key = 'ui-accent' 331 351 where p.id = $1::bigint 332 352 ]]; 333 353 }; 354 + 355 + post_enum_parent = { 356 + params = {uint64}, sql = [[ 357 + select a.origin is null, 358 + p.id, p.author, p.subject, p.acl, p.body, 359 + extract(epoch from p.posted )::bigint, 360 + extract(epoch from p.discovered)::bigint, 361 + extract(epoch from p.edited )::bigint, 362 + p.parent, p.convoheaduri, p.chgcount, 363 + coalesce(c.value, -1)::smallint 364 + 365 + from parsav_posts as p 366 + inner join parsav_actors as a on a.id = p.author 367 + left join parsav_actor_conf_ints as c on c.uid = a.id and c.key = 'ui-accent' 368 + where p.parent = $1::bigint 369 + order by p.posted, p.discovered asc 370 + ]] 371 + }; 372 + 373 + thread_latest_arrival_calc = { 374 + params = {uint64}, sql = [[ 375 + with recursive posts(id) as ( 376 + select id from parsav_posts where parent = $1::bigint 377 + union 378 + select p.id from parsav_posts as p 379 + inner join posts on posts.id = p.parent 380 + ), 381 + 382 + maxes as ( 383 + select unnest(array[max(p.posted), max(p.discovered), max(p.edited)]) as m 384 + from posts 385 + inner join parsav_posts as p 386 + on p.id = posts.id 387 + ) 388 + 389 + select extract(epoch from max(m))::bigint from maxes 390 + ]]; 391 + }; 334 392 335 393 post_enum_author_uid = { 336 394 params = {uint64,uint64,uint64,uint64, uint64}, sql = [[ 337 395 select a.origin is null, 338 396 p.id, p.author, p.subject, p.acl, p.body, 339 397 extract(epoch from p.posted )::bigint, 340 398 extract(epoch from p.discovered)::bigint, 341 399 extract(epoch from p.edited )::bigint, 342 - p.parent, p.convoheaduri, p.chgcount 400 + p.parent, p.convoheaduri, p.chgcount, 401 + coalesce((select value from parsav_actor_conf_ints as c where 402 + c.uid = $1::bigint and c.key = 'ui-accent'),-1)::smallint 403 + 343 404 from parsav_posts as p 344 405 inner join parsav_actors as a on p.author = a.id 345 406 where p.author = $5::bigint and 346 407 ($1::bigint = 0 or p.posted <= to_timestamp($1::bigint)) and 347 408 ($2::bigint = 0 or to_timestamp($2::bigint) < p.posted) 348 409 order by (p.posted, p.discovered) desc 349 410 limit case when $3::bigint = 0 then null ................................................................................ 357 418 timeline_instance_fetch = { 358 419 params = {uint64, uint64, uint64, uint64}, sql = [[ 359 420 select true, 360 421 p.id, p.author, p.subject, p.acl, p.body, 361 422 extract(epoch from p.posted )::bigint, 362 423 extract(epoch from p.discovered)::bigint, 363 424 extract(epoch from p.edited )::bigint, 364 - p.parent, null::text, p.chgcount 425 + p.parent, null::text, p.chgcount, 426 + coalesce(c.value, -1)::smallint 427 + 365 428 from parsav_posts as p 366 - inner join parsav_actors as a on p.author = a.id 429 + inner join parsav_actors as a on p.author = a.id 430 + left join parsav_actor_conf_ints as c on c.uid = a.id and c.key = 'ui-accent' 367 431 where 368 432 ($1::bigint = 0 or p.posted <= to_timestamp($1::bigint)) and 369 433 ($2::bigint = 0 or to_timestamp($2::bigint) < p.posted) and 370 434 (a.origin is null) 371 435 order by (p.posted, p.discovered) desc 372 436 limit case when $3::bigint = 0 then null 373 437 else $3::bigint end ................................................................................ 430 494 ]]; 431 495 }; 432 496 post_attach_ctl_ins = { 433 497 params = {uint64, uint64}, cmd=true, sql = [[ 434 498 update parsav_posts set 435 499 artifacts = artifacts || $2::bigint 436 500 where id = $1::bigint and not 437 - artifacts @> array[$2::bigint] 501 + artifacts @> array[$2::bigint] -- prevent duplication 438 502 ]]; 439 503 }; 440 504 post_attach_ctl_del = { 441 505 params = {uint64, uint64}, cmd=true, sql = [[ 442 506 update parsav_posts set 443 507 artifacts = array_remove(artifacts, $2::bigint) 444 508 where id = $1::bigint and 445 509 artifacts @> array[$2::bigint] 446 510 ]]; 447 511 }; 512 + 513 + actor_conf_str_get = { 514 + params = {uint64, rawstring}, sql = [[ 515 + select value from parsav_actor_conf_strs where 516 + uid = $1::bigint and 517 + key = $2::text 518 + limit 1 519 + ]]; 520 + }; 521 + actor_conf_str_set = { 522 + params = {uint64, rawstring, rawstring}, cmd = true, sql = [[ 523 + insert into parsav_actor_conf_strs (uid,key,value) 524 + values ($1::bigint, $2::text, $3::text) 525 + on conflict (uid,key) do update set value = $3::text 526 + ]]; 527 + }; 528 + actor_conf_str_enum = { 529 + params = {uint64}, sql = [[ 530 + select value from parsav_actor_conf_strs where uid = $1::bigint 531 + ]]; 532 + }; 533 + actor_conf_str_reset = { 534 + params = {uint64, rawstring}, cmd = true, sql = [[ 535 + delete from parsav_actor_conf_strs where 536 + uid = $1::bigint and ($2::text is null or key = $2::text) 537 + ]] 538 + }; 539 + 540 + actor_conf_int_get = { 541 + params = {uint64, rawstring}, sql = [[ 542 + select value from parsav_actor_conf_ints where 543 + uid = $1::bigint and 544 + key = $2::text 545 + limit 1 546 + ]]; 547 + }; 548 + actor_conf_int_set = { 549 + params = {uint64, rawstring, uint64}, cmd = true, sql = [[ 550 + insert into parsav_actor_conf_ints (uid,key,value) 551 + values ($1::bigint, $2::text, $3::bigint) 552 + on conflict (uid,key) do update set value = $3::bigint 553 + ]]; 554 + }; 555 + actor_conf_int_enum = { 556 + params = {uint64}, sql = [[ 557 + select value from parsav_actor_conf_ints where uid = $1::bigint 558 + ]]; 559 + }; 560 + actor_conf_int_reset = { 561 + params = {uint64, rawstring}, cmd = true, sql = [[ 562 + delete from parsav_actor_conf_ints where 563 + uid = $1::bigint and ($2::text is null or key = $2::text) 564 + ]] 565 + }; 448 566 } 449 567 450 568 local struct pqr { 451 569 sz: intptr 452 570 res: &lib.pq.PGresult 453 571 } 454 572 terra pqr:free() if self.sz > 0 then lib.pq.PQclear(self.res) end end ................................................................................ 651 769 then p.ptr.parent = 0 652 770 else p.ptr.parent = r:int(uint64,row,9) 653 771 end 654 772 if r:null(row,11) 655 773 then p.ptr.chgcount = 0 656 774 else p.ptr.chgcount = r:int(uint32,row,11) 657 775 end 776 + p.ptr.accent = r:int(int16,row,12) 658 777 p.ptr.localpost = r:bool(row,0) 659 778 660 779 return p 661 780 end 662 781 local terra row_to_actor(r: &pqr, row: intptr): lib.mem.ptr(lib.store.actor) 663 782 var a: lib.mem.ptr(lib.store.actor) 664 783 var av: rawstring, avlen: intptr ................................................................................ 1025 1144 [lib.mem.ptr(lib.store.actor)] { ptr = nil, ct = 0 } 1026 1145 end]; 1027 1146 1028 1147 post_create = [terra( 1029 1148 src: &lib.store.source, 1030 1149 post: &lib.store.post 1031 1150 ): uint64 1032 - var r = queries.post_create.exec(src,post.author,post.subject,post.acl,post.body) 1151 + var r = queries.post_create.exec(src, 1152 + post.author,post.subject,post.acl,post.body, 1153 + post.parent,post.posted,post.convoheaduri 1154 + ) 1033 1155 if r.sz == 0 then return 0 end 1034 1156 defer r:free() 1035 1157 var id = r:int(uint64,0,0) 1158 + post.source = src 1036 1159 return id 1037 1160 end]; 1038 1161 1039 1162 post_destroy = [terra( 1040 1163 src: &lib.store.source, 1041 1164 post: uint64 1042 1165 ): {} ................................................................................ 1114 1237 lib.dbg('created new actor, establishing powers') 1115 1238 privupdate(src,ac) 1116 1239 1117 1240 lib.dbg('powers established') 1118 1241 return ac.id 1119 1242 end]; 1120 1243 1121 - auth_create_pw = [terra( 1244 + auth_enum_uid = [terra( 1245 + src: &lib.store.source, 1246 + uid: uint64 1247 + ): lib.mem.ptr(lib.mem.ptr(lib.store.auth)) 1248 + var r = queries.auth_enum_uid.exec(src,uid) 1249 + if r.sz == 0 then return [lib.mem.ptr(lib.mem.ptr(lib.store.auth))].null() end 1250 + var ret = lib.mem.heapa([lib.mem.ptr(lib.store.auth)], r.sz) 1251 + for i=0, r.sz do 1252 + var kind = r:_string(i, 1) 1253 + var comment = r:_string(i, 2) 1254 + var a = [ lib.str.encapsulate(lib.store.auth, { 1255 + kind = {`kind.ptr, `kind.ct}; 1256 + comment = {`comment.ptr, `comment.ct}; 1257 + }) ] 1258 + a.ptr.aid = r:int(uint64, i, 0) 1259 + a.ptr.netmask = r:cidr(i, 3) 1260 + a.ptr.blacklist = r:bool(i, 4) 1261 + ret.ptr[i] = a 1262 + end 1263 + return ret 1264 + end]; 1265 + 1266 + auth_attach_pw = [terra( 1122 1267 src: &lib.store.source, 1123 1268 uid: uint64, 1124 1269 reset: bool, 1125 - pw: lib.mem.ptr(int8) 1270 + pw: pstring, 1271 + comment: pstring 1126 1272 ): {} 1127 1273 var hash: uint8[lib.crypt.algsz.sha256] 1128 1274 if lib.md.mbedtls_md(lib.md.mbedtls_md_info_from_type(lib.crypt.alg.sha256.id), 1129 1275 [&uint8](pw.ptr), pw.ct, &hash[0]) ~= 0 then 1130 1276 lib.bail('cannot hash password') 1131 1277 end 1132 1278 if reset then queries.auth_purge_type.exec(src, nil, uid, 'pw-%') end 1133 - queries.auth_create_pw.exec(src, uid, [lib.mem.ptr(uint8)] {ptr = &hash[0], ct = [hash.type.N]}) 1279 + queries.auth_create_pw.exec(src, uid, binblob {ptr = &hash[0], ct = [hash.type.N]}, comment) 1134 1280 end]; 1135 1281 1136 1282 auth_purge_pw = [terra(src: &lib.store.source, uid: uint64, handle: rawstring): {} 1137 1283 queries.auth_purge_type.exec(src, handle, uid, 'pw-%') 1138 1284 end]; 1139 1285 1140 1286 auth_purge_otp = [terra(src: &lib.store.source, uid: uint64, handle: rawstring): {} ................................................................................ 1208 1354 src: &lib.store.source, 1209 1355 post: &lib.store.post 1210 1356 ): {} 1211 1357 queries.post_save.exec(src, 1212 1358 post.id, post.chgcount, post.edited, 1213 1359 post.subject, post.acl, post.body) 1214 1360 end]; 1361 + 1362 + post_enum_parent = [terra( 1363 + src: &lib.store.source, 1364 + post: uint64 1365 + ): lib.mem.ptr(lib.mem.ptr(lib.store.post)) 1366 + var r = queries.post_enum_parent.exec(src,post) 1367 + if r.sz == 0 then 1368 + return [lib.mem.ptr(lib.mem.ptr(lib.store.post))].null() 1369 + end 1370 + defer r:free() 1371 + var lst = lib.mem.heapa([lib.mem.ptr(lib.store.post)], r.sz) 1372 + 1373 + for i=0, r.sz do lst.ptr[i] = row_to_post(&r, i) end 1374 + 1375 + return lst 1376 + end]; 1377 + 1378 + thread_latest_arrival_calc = [terra( 1379 + src: &lib.store.source, 1380 + post: uint64 1381 + ): lib.store.timepoint 1382 + var r = queries.thread_latest_arrival_calc.exec(src,post) 1383 + if r.sz == 0 or r:null(0,0) then return 0 end 1384 + var tp: lib.store.timepoint = r:int(int64,0,0) 1385 + r:free() 1386 + return tp 1387 + end]; 1215 1388 1216 1389 auth_sigtime_user_fetch = [terra( 1217 1390 src: &lib.store.source, 1218 1391 uid: uint64 1219 1392 ): lib.store.timepoint 1220 1393 var r = queries.auth_sigtime_user_fetch.exec(src, uid) 1221 1394 if r.sz > 0 then defer r:free() ................................................................................ 1226 1399 1227 1400 auth_sigtime_user_alter = [terra( 1228 1401 src: &lib.store.source, 1229 1402 uid: uint64, 1230 1403 time: lib.store.timepoint 1231 1404 ): {} queries.auth_sigtime_user_alter.exec(src, uid, time) end]; 1232 1405 1406 + actor_conf_str_enum = nil; 1407 + actor_conf_str_get = [terra(src: &lib.store.source, uid: uint64, key: rawstring): pstring 1408 + var r = queries.actor_conf_str_get.exec(src, uid, key) 1409 + if r.sz > 0 then 1410 + var ret = r:String(0,0) 1411 + r:free() 1412 + return ret 1413 + else return pstring.null() end 1414 + end]; 1415 + actor_conf_str_set = [terra(src: &lib.store.source, uid: uint64, key: rawstring, value: rawstring): {} 1416 + queries.actor_conf_str_set.exec(src,uid,key,value) end]; 1417 + actor_conf_str_reset = [terra(src: &lib.store.source, uid: uint64, key: rawstring): {} 1418 + queries.actor_conf_str_reset.exec(src,uid,key) end]; 1419 + 1420 + actor_conf_int_enum = nil; 1421 + actor_conf_int_get = [terra(src: &lib.store.source, uid: uint64, key: rawstring) 1422 + var r = queries.actor_conf_int_get.exec(src, uid, key) 1423 + if r.sz > 0 then 1424 + var ret = r:int(uint64,0,0) 1425 + r:free() 1426 + return ret, true 1427 + end 1428 + return 0, false 1429 + end]; 1430 + actor_conf_int_set = [terra(src: &lib.store.source, uid: uint64, key: rawstring, value: uint64): {} 1431 + queries.actor_conf_int_set.exec(src,uid,key,value) end]; 1432 + actor_conf_int_reset = [terra(src: &lib.store.source, uid: uint64, key: rawstring): {} 1433 + queries.actor_conf_int_reset.exec(src,uid,key) end]; 1434 + 1233 1435 actor_auth_register_uid = nil; -- TODO better support non-view based auth 1234 1436 } 1235 1437 1236 1438 return b
Modified backend/schema/pgsql-drop.sql from [17a37aa5f6] to [fa02548662].
1 1 -- destroy absolutely everything 2 2 3 3 drop table if exists parsav_config cascade; 4 4 drop table if exists parsav_servers cascade; 5 5 drop table if exists parsav_actors cascade; 6 +drop table if exists parsav_actor_conf_strs cascade; 7 +drop table if exists parsav_actor_conf_ints cascade; 6 8 drop table if exists parsav_rights cascade; 7 9 drop table if exists parsav_posts cascade; 8 10 drop table if exists parsav_conversations cascade; 9 11 drop table if exists parsav_rels cascade; 10 12 drop table if exists parsav_acts cascade; 11 13 drop table if exists parsav_log cascade; 12 14 drop table if exists parsav_artifacts cascade;
Modified backend/schema/pgsql.sql from [135f2b367a] to [b4d8dee98e].
167 167 nature smallint not null, -- silence, suspend, disemvowel, censor, noreply, etc 168 168 victim bigint not null, -- can be user, room, or post 169 169 expire timestamp, -- auto-expires if set 170 170 review timestamp, -- brings up for review at given time if set 171 171 reason text, -- visible to victim if set 172 172 context text -- admin-only note 173 173 ); 174 + 175 +create table parsav_actor_conf_strs ( 176 + uid bigint not null references parsav_actors(id) on delete cascade, 177 + key text not null, value text not null, unique (uid,key) 178 +); 179 +create table parsav_actor_conf_ints ( 180 + uid bigint not null references parsav_actors(id) on delete cascade, 181 + key text not null, value bigint not null, unique (uid,key) 182 +); 174 183 175 184 -- create a temporary managed auth table; we can delete this later 176 185 -- if it ends up being replaced with a view 177 186 %include pgsql-auth.sql%
Modified config.lua from [3d0e5432a4] to [cd48dd2db6].
50 50 embeds = { 51 51 -- TODO with gzip compression, svg is dramatically superior to webp 52 52 -- we should have a build-time option to serve svg so instances 53 53 -- proxied behind nginx can serve svgz, or possibly just straight-up 54 54 -- add support for content-encoding headers and pre-compress the 55 55 -- damn things before compiling 56 56 {'style.css', 'text/css'}; 57 + {'live.js', 'text/javascript'}; -- rrrrrrrr 57 58 {'default-avatar.webp', 'image/webp'}; 58 59 {'padlock.webp', 'image/webp'}; 59 60 {'warn.webp', 'image/webp'}; 60 61 {'query.webp', 'image/webp'}; 61 62 }; 63 + default_ui_accent = tonumber(default('parsav_ui_default_accent',323)); 62 64 } 63 65 if os.getenv('parsav_let_me_be_an_idiot') == "i know what i'm doing" then 64 66 conf.braingeniousmode = true -- SOUND GENERAL QUARTERS 65 67 end 66 68 if u.ping '.fslckout' or u.ping '_FOSSIL_' then 67 69 if u.ping '_FOSSIL_' then default_os = 'windows' end 68 70 conf.build.branch = u.exec { 'fossil', 'branch', 'current' }
Modified math.t from [bc01716315] to [1d1d177061].
184 184 else dgtct = dgtct + 1 end 185 185 end else 186 186 buf = buf - 1 187 187 @buf = 0x30 188 188 end 189 189 return buf 190 190 end 191 + 192 +terra m.decparse(s: pstring): {intptr, bool} 193 + if not s then return 0, false end 194 + var val:intptr = 0 195 + var c = s.ptr 196 + while @c ~= 0 do 197 + if @c >= 0x30 and @c <= 0x39 then 198 + val = val * 10 199 + val = val + (@c - 0x30) 200 + else 201 + return 0, false 202 + end 203 + 204 + c = c + 1 205 + if s.ct ~= 0 and (c - s.ptr > s.ct) then lib.dbg('reached end') return val, true end 206 + end 207 + return val, true 208 +end 191 209 192 210 terra m.ndigits(n: intptr, base: intptr): intptr 193 211 var c = base 194 212 var i = 1 195 213 while true do 196 214 if n < c then return i end 197 215 c = c * base
Modified mgtool.t from [faf7450a82] to [39281cf1cf].
59 59 60 60 sid: uint64 61 61 iname: rawstring 62 62 } 63 63 idelegate.metamethods.__methodmissing = macro(function(meth, self, ...) 64 64 local expr = {...} 65 65 local rt 66 + 66 67 for _,f in pairs(lib.store.backend.entries) do 67 68 local fn = f.field or f[1] 68 69 local ft = f.type or f[2] 69 70 if fn == meth then rt = ft.type.returntype break end 70 71 end 71 72 72 73 return quote ................................................................................ 113 114 if tmppw[i] >= 36 then 114 115 tmppw[i] = tmppw[i] + (0x61 - 36) 115 116 elseif tmppw[i] >= 10 then 116 117 tmppw[i] = tmppw[i] + (0x41 - 10) 117 118 else tmppw[i] = tmppw[i] + 0x30 end 118 119 end 119 120 lib.dbg('assigning temporary password') 120 - dlg:auth_create_pw(uid, reset, pstr { 121 - ptr = [rawstring](tmppw), ct = 32 122 - }) 121 + dlg:auth_attach_pw(uid, reset, 122 + pstr { ptr = [rawstring](tmppw), ct = 32 }, 123 + lib.str.plit 'temporary password'); 123 124 end 124 125 125 126 local terra ipc_report(acks: lib.mem.ptr(lib.ipc.ack), rep: rawstring) 126 127 var decbuf: int8[21] 127 128 for i=0,acks.ct do 128 129 var num = lib.math.decstr(acks(i).clid, &decbuf[20]) 129 130 if acks(i).success then ................................................................................ 334 335 if lib.str.cmp(cfmode.arglist(0),'chsec') == 0 then 335 336 var sec: int8[65] gensec(&sec[0]) 336 337 dlg:conf_set('server-secret', &sec[0]) 337 338 lib.report('server secret reset') 338 339 elseif lib.str.cmp(cfmode.arglist(0),'refresh') == 0 then 339 340 cfmode.no_notify = false -- duh 340 341 else goto cmderr end 342 + elseif cfmode.arglist.ct == 2 and 343 + lib.str.cmp(cfmode.arglist(0),'reset') == 0 or 344 + lib.str.cmp(cfmode.arglist(0),'clear') == 0 or 345 + lib.str.cmp(cfmode.arglist(0),'unset') == 0 then 346 + dlg:conf_reset(cfmode.arglist(1)) 347 + lib.report('parameter cleared') 341 348 elseif cfmode.arglist.ct == 3 and 342 349 lib.str.cmp(cfmode.arglist(0),'set') == 0 then 343 350 dlg:conf_set(cfmode.arglist(1),cfmode.arglist(2)) 344 351 lib.report('parameter set') 345 352 else goto cmderr end 346 353 347 354 -- successful commands fall through
Modified parsav.md from [409d9b6b60] to [bfa0a26bd5].
10 10 ## dependencies 11 11 12 12 * runtime 13 13 * mongoose 14 14 * json-c 15 15 * mbedtls 16 16 * **postgresql backend:** 17 - * postgresql-libs 17 + * postgresql-libs 18 18 * compile-time 19 19 * cmark (commonmark implementation), for transformation of the help files, whose source is in commonmark. online documentation transforms these into html and embeds them in the binary; cmark is also used to to produce the troff source which is used to build the offline documentation. disable with `parsav_online_documentation=no parsav_offline_documentation=no` 20 20 * troff implementation (tested with groff but as far as i know we don't need any groff-specific extensions) to produce PDFs and manpages from the cmark-generated intermediate forms. disable with `parsav_offline_documentation=no` 21 21 22 22 additional preconfigure dependencies are necessary if you are building directly from trunk, rather than from a release tarball that includes certain build artifacts which need to be embedded in the binary: 23 23 24 24 * inkscape, for rendering out UI graphics 25 25 * cwebp (libwebp package), for transforming inkscape PNGs to webp 26 26 * sassc, for compiling the SCSS stylesheet into its final CSS 27 27 28 28 all builds require terra, which, unfortunately, requires installing an older version of llvm, v9 at the latest (which i develop parsav under). with any luck, your distro will be clever enough to package terra and its dependencies properly (it's trivial on nix, tho you'll need to tweak the terra expression to select a more recent llvm package); Arch Linux is one of those distros which is not so clever, and whose (AUR) terra package is totally broken. due to these unfortunate circumstances, terra is distributed not just in source form, but also in the the form of LLVM IR. distributions will also be made in the form of tarballed object code and assembly listings for various common platforms, currently including x86-32/64, arm7hf, aarch64, riscv, mips32/64, and ppc64/64le. 29 29 30 -i've noticed that terra (at least with llvm9) seems to get a bit cantankerous and trigger llvm to fail with bizarre errors when you try to cross-compile parsav from x86-64 to any other platform, even x86-32. i don't know if this problem exists on other architectures or in what form, but as a workaround, the current cross-compile process consists of generating LLVM IR (ostensible for x86-64, though this is in reality an architecture-independent language), and then compiling that down to an object file with llc. this is an enormous hassle; hopefully the terra people will fix this eventually. 30 +i've noticed that terra (at least with llvm9) seems to get a bit cantankerous and trigger llvm to fail with bizarre errors when you try to cross-compile parsav from x86-64 to any other platform, even x86-32. i don't know if this problem exists on other architectures or in what form, but as a workaround, the current cross-compile process consists of generating LLVM IR (ostensibly for x86-64, though this is in reality an architecture-independent language), and then compiling that down to an object file with llc. this is an enormous hassle; hopefully the terra (or llvm?) people will fix this eventually. 31 31 32 32 also note that, while parsav has a flag to build with ASAN, ASAN has proven unusable for most purposes as it routinely reports false positive buffer-heap-overflows. if you figure out how to defuckulate this, i will be overjoyed. 33 33 34 34 ## building 35 35 36 36 first, either install any missing dependencies as shared libraries, or build them as static libraries with the command `make dep.$LIBRARY`. as a shortcut, `make dep` will build all dependencies as static libraries. note that if the build system finds a static version of a library in the `lib/` folder, it will use that instead of any system library. note that these commands require GNU make (it may be installed as `gmake` on your system), although this is a fairly soft dependency -- if you really need to build it on BSD make, you can probably translate it with a minute or so of work; you'll just have to do some of the various gmake functions' work manually. this may be worthwhile if you're packaging for a BSD. 37 37
Modified parsav.t from [90c24eca6f] to [4b4b2876e4].
602 602 print(util.dump(config)) 603 603 os.exit(0) 604 604 end 605 605 606 606 local holler = print 607 607 local suffix = config.exe and '' or ('.'..config.outform) 608 608 local out = 'parsavd' .. suffix 609 -local linkargs = {'-O4'} 609 +local linkargs = {} 610 610 local target = config.tgttrip and terralib.newtarget { 611 611 Triple = config.tgttrip; 612 612 CPU = config.tgtcpu; 613 613 FloatABIHard = config.tgthf; 614 614 } or nil 615 615 616 616 if bflag('quiet','q') then holler = function() end end
Modified render/conf.t from [1b75d5dd6d] to [fa178f73b5].
1 1 -- vim: ft=terra 2 2 local pstr = lib.mem.ptr(int8) 3 3 local pref = lib.mem.ref(int8) 4 4 5 5 local mappings = { 6 6 {url = 'profile', title = 'account profile', render = 'profile'}; 7 7 {url = 'avi', title = 'avatar', render = 'avatar'}; 8 + {url = 'ui', title = 'user interface', render = 'ui'}; 8 9 {url = 'sec', title = 'security', render = 'sec'}; 9 10 {url = 'rel', title = 'relationships', render = 'rel'}; 10 11 {url = 'qnt', title = 'quarantine', render = 'quarantine'}; 11 12 {url = 'acl', title = 'access control shortcuts', render = 'acl'}; 12 13 {url = 'rooms', title = 'chatrooms', render = 'rooms'}; 13 14 {url = 'circles', title = 'circles', render = 'circles'}; 14 15
Modified render/conf/profile.t from [7f970c2f4a] to [864a63a85e].
4 4 5 5 local terra cs(s: rawstring) 6 6 return pstr { ptr = s, ct = lib.str.sz(s) } 7 7 end 8 8 9 9 local terra 10 10 render_conf_profile(co: &lib.srv.convo, path: lib.mem.ptr(pref)): pstr 11 + var hue: int8[21] 11 12 var c = data.view.conf_profile { 12 13 handle = cs(co.who.handle); 13 14 nym = cs(lib.coalesce(co.who.nym,'')); 14 15 bio = cs(lib.coalesce(co.who.bio,'')); 16 + hue = lib.math.decstr(co.ui_hue, &hue[20]); 15 17 } 16 18 return c:tostr() 17 19 end 18 20 19 21 return render_conf_profile
Modified render/timeline.t from [2afba48373] to [ab5808172b].
23 23 to_idx = 64; 24 24 }) 25 25 elseif mode == modes.fediglobal then 26 26 elseif mode == modes.circle then 27 27 end 28 28 29 29 var acc: lib.str.acc acc:init(1024) 30 + acc:lpush('<div id="tl" data-live="10">') 31 + var newest: lib.store.timepoint = 0 30 32 for i = 0, posts.sz do 31 33 lib.render.tweet(co, posts(i).ptr, &acc) 34 + var t = lib.math.biggest(lib.math.biggest(posts(i).ptr.posted, posts(i).ptr.discovered),posts(i).ptr.edited) 35 + if t > newest then newest = t end 32 36 posts(i):free() 33 37 end 34 38 posts:free() 39 + acc:lpush('</div>') 35 40 36 41 var doc = [lib.srv.convo.page] { 37 42 title = lib.str.plit'timeline'; 38 43 body = acc:finalize(); 39 44 class = lib.str.plit'timeline'; 40 45 cache = false; 41 46 } 42 - co:stdpage(doc) 47 + co:livepage(doc,newest) 43 48 doc.body:free() 44 49 end 45 50 return render_timeline
Modified render/tweet-page.t from [ad592d16bd] to [005ba03599].
1 1 -- vim: ft=terra 2 2 local pstr = lib.mem.ptr(int8) 3 3 local pref = lib.mem.ref(int8) 4 4 local terra cs(s: rawstring) 5 5 return pstr { ptr = s, ct = lib.str.sz(s) } 6 6 end 7 + 8 +local terra 9 +render_tweet_replies( 10 + co: &lib.srv.convo, 11 + acc: &lib.str.acc, 12 + id: uint64 13 +): {} 14 + var replies = co.srv:post_enum_parent(id) 15 + if replies.ct == 0 then return end 16 + acc:lpush('<div class="thread">') 17 + for i=0, replies.ct do 18 + var post = replies(i).ptr 19 + lib.render.tweet(co, post, acc) 20 + render_tweet_replies(co, acc, post.id) 21 + end 22 + acc:lpush('</div>') 23 +end 7 24 8 25 local terra 9 26 render_tweet_page( 10 27 co: &lib.srv.convo, 11 28 path: lib.mem.ptr(pref), 12 29 p: &lib.store.post 13 30 ): {} 31 + var livetime = co.srv:thread_latest_arrival_calc(p.id) 32 + 14 33 var pg: lib.str.acc pg:init(256) 15 34 lib.render.tweet(co, p, &pg) 16 - pg:lpush('<form class="action-bar" method="post">') 17 35 18 36 if co.aid ~= 0 then 37 + pg:lpush('<form class="action-bar" method="post">') 19 38 var liked = false -- FIXME 20 39 var rtd = false 21 40 if not liked 22 41 then pg:lpush('<button class="pos" name="act" value="like">like</button>') 23 42 else pg:lpush('<button class="neg" name="act" value="dislike">dislike</button>') 24 43 end 25 44 if not rtd ................................................................................ 28 47 end 29 48 if p.author == co.who.id then 30 49 pg:lpush('<a class="button" href="/post/'):rpush(path(1)):lpush('/edit">edit</a><a class="neg button" href="/post/'):rpush(path(1)):lpush('/del">delete</a>') 31 50 end 32 51 -- TODO list user's chosen reaction emoji 33 52 pg:lpush('</form>') 34 53 35 - if co.who.rights.powers.post() then 36 - lib.render.compose(co, nil, &pg) 37 - end 54 + end 55 + pg:lpush('<div id="convo" data-live="10">') 56 + render_tweet_replies(co, &pg, p.id) 57 + pg:lpush('</div>') 58 + 59 + if co.aid ~= 0 and co.who.rights.powers.post() then 60 + lib.render.compose(co, nil, &pg) 38 61 end 39 62 40 63 var ppg = pg:finalize() defer ppg:free() 41 - co:stdpage([lib.srv.convo.page] { 64 + co:livepage([lib.srv.convo.page] { 42 65 title = lib.str.plit 'post'; cache = false; 43 66 class = lib.str.plit 'post'; body = ppg; 44 - }) 67 + }, livetime) 45 68 46 69 -- TODO display conversation 47 70 -- perhaps display descendant nodes here, and have a link to the top of the whole tree? 48 71 end 49 72 50 73 return render_tweet_page
Modified render/tweet.t from [ac0f8e680f] to [77ab77b2db].
32 32 subject = cs(lib.coalesce(p.subject,'')); 33 33 nym = fullname; 34 34 when = cs(×tr[0]); 35 35 avatar = cs(lib.trn(author.origin == 0, avistr.buf, 36 36 lib.coalesce(author.avatar, '/s/default-avatar.webp'))); 37 37 acctlink = cs(author.xid); 38 38 permalink = permalink:finalize(); 39 + attr = '' 39 40 } 41 + 42 + var attrbuf: int8[32] 43 + if p.accent ~= -1 and p.accent ~= co.ui_hue then 44 + var hdecbuf: int8[21] 45 + var hdec = lib.math.decstr(p.accent, &hdecbuf[20]) 46 + lib.str.cpy(&attrbuf[0], ' style="--hue:') 47 + lib.str.cpy(&attrbuf[14], hdec) 48 + var len = &hdecbuf[20] - hdec 49 + lib.str.cpy(&attrbuf[14] + len, '"') 50 + tpl.attr = &attrbuf[0] 51 + end 52 + 40 53 defer tpl.permalink:free() 41 54 if acc ~= nil then tpl:append(acc) return [lib.mem.ptr(int8)]{ptr=nil,ct=0} end 42 55 var txt = tpl:tostr() 43 56 return txt 44 57 end 45 58 return render_tweet
Modified render/user-page.t from [8e478d7a95] to [08cdf2fd9f].
16 16 var stoptime = lib.osclock.time(nil) 17 17 var posts = co.srv:post_enum_author_uid(actor.id, lib.store.range { 18 18 mode = 1; -- T->I 19 19 from_time = stoptime; 20 20 to_idx = 64; 21 21 }) 22 22 23 + acc:lpush('<div id="feed" data-live="10">') 24 + var newest: lib.store.timepoint = 0 23 25 for i = 0, posts.sz do 24 26 lib.render.tweet(co, posts(i).ptr, &acc) 27 + var t = lib.math.biggest(lib.math.biggest(posts(i).ptr.posted, posts(i).ptr.discovered),posts(i).ptr.edited) 28 + if t > newest then newest = t end 25 29 posts(i):free() 26 30 end 27 31 posts:free() 32 + acc:lpush('</div>') 28 33 29 34 var bdf = acc:finalize() 30 - co:stdpage([lib.srv.convo.page] { 35 + co:livepage([lib.srv.convo.page] { 31 36 title = tiptr; body = bdf; 32 37 class = lib.str.plit 'profile'; 33 38 cache = false; 34 - }) 39 + }, newest) 35 40 36 41 tiptr:free() 37 42 bdf:free() 38 43 end 39 44 40 45 return render_userpage
Modified route.t from [a9eb70a00e] to [f59188addc].
2 2 local r = lib.srv.route 3 3 local method = lib.http.method 4 4 local pstring = lib.mem.ptr(int8) 5 5 local rstring = lib.mem.ref(int8) 6 6 local hpath = lib.mem.ptr(rstring) 7 7 local http = {} 8 8 9 +terra meth_get(meth: method.t) return (meth == method.get) or (meth == method.head) end 9 10 terra http.actor_profile_xid(co: &lib.srv.convo, uri: lib.mem.ptr(int8), meth: method.t) 10 11 var handle = [lib.mem.ptr(int8)] { ptr = &uri.ptr[2], ct = 0 } 11 12 for i=2,uri.ct do 12 13 if uri.ptr[i] == @'/' or uri.ptr[i] == 0 then handle.ct = i - 2 break end 13 14 end 14 15 if handle.ct == 0 then 15 16 handle.ct = uri.ct - 2 ................................................................................ 56 57 end 57 58 defer actor:free() 58 59 59 60 lib.render.user_page(co, actor.ptr) 60 61 end 61 62 62 63 terra http.login_form(co: &lib.srv.convo, meth: method.t) 63 - if meth == method.get then 64 + if meth_get(meth) then 64 65 -- request a username 65 66 lib.render.login(co, nil, nil, lib.str.plit(nil)) 66 67 elseif meth == method.post then 67 68 var usn, usnl = co:postv('user') 68 69 var am, aml = co:postv('authmethod') 69 70 var chrs, chrsl = co:postv('response') 70 71 var cs, authok = co.srv:actor_auth_how(co.peer, usn) ................................................................................ 122 123 end 123 124 124 125 terra http.post_compose(co: &lib.srv.convo, meth: method.t) 125 126 if not co:assertpow('post') then return end 126 127 --if co.who.rights.powers.post() == false then 127 128 --co:complain(403,'insufficient privileges','you lack the <strong>post</strong> power and cannot perform this action') 128 129 129 - if meth == method.get then 130 + if meth_get(meth) then 130 131 lib.render.compose(co, nil, nil) 131 132 elseif meth == method.post then 132 133 var text, textlen = co:postv("post") 133 134 var acl, acllen = co:postv("acl") 134 135 var subj, subjlen = co:postv("subject") 135 136 if text == nil or acl == nil then 136 137 co:complain(405, 'invalid post', 'every post must have at least body text and an ACL') ................................................................................ 138 139 end 139 140 if subj == nil then subj = '' end 140 141 141 142 var p = lib.store.post { 142 143 author = co.who.id, acl = acl; 143 144 body = text, subject = subj; 144 145 } 145 - var newid = co.srv:post_create(&p) 146 + var newid = p:publish(co.srv) 146 147 147 148 var idbuf: int8[lib.math.shorthand.maxlen] 148 149 var idlen = lib.math.shorthand.gen(newid, idbuf) 149 150 var redirto: lib.str.acc redirto:compose('/post/',{idbuf,idlen}) defer redirto:free() 150 151 co:reroute(redirto.buf) 151 152 end 152 153 end ................................................................................ 181 182 if path.ct == 3 then 182 183 var lnk: lib.str.acc lnk:compose('/post/', path(1)) 183 184 var lnkp = lnk:finalize() defer lnkp:free() 184 185 if post(0).author ~= co.who.id then 185 186 co:complain(403, 'forbidden', 'you cannot alter other people\'s posts') 186 187 return 187 188 elseif path(2):cmp(lib.str.lit 'edit') then 188 - if meth == method.get then 189 + if meth_get(meth) then 189 190 lib.render.compose(co, post.ptr, nil) 190 191 return 191 192 elseif meth == method.post then 192 193 var newbody = co:postv('post')._0 193 194 var newacl = co:postv('acl')._0 194 195 var newsubj = co:postv('subject')._0 195 196 if newbody ~= nil then post(0).body = newbody end ................................................................................ 196 197 if newacl ~= nil then post(0).acl = newacl end 197 198 if newsubj ~= nil then post(0).subject = newsubj end 198 199 post(0):save(true) 199 200 co:reroute(lnkp.ptr) 200 201 end 201 202 return 202 203 elseif path(2):cmp(lib.str.lit 'del') then 203 - if meth == method.get then 204 + if meth_get(meth) then 204 205 var conf = data.view.confirm { 205 206 title = lib.str.plit 'delete post'; 206 207 query = lib.str.plit 'are you sure you want to delete this post?'; 207 208 cancel = lnkp 208 209 } 209 210 var body = conf:tostr() defer body:free() 210 211 co:stdpage([lib.srv.convo.page] { ................................................................................ 220 221 co:reroute('/') -- TODO maybe return to parent or conversation if possible 221 222 return 222 223 else goto badop end 223 224 end 224 225 else goto badurl end 225 226 end 226 227 227 - if meth == method.post then goto badop end 228 + if meth == method.post then 229 + var replytext = co:ppostv('post') 230 + var acl = co:ppostv('acl') 231 + var subj = co:ppostv('subject') 232 + if not acl then acl = lib.str.plit 'all' end 233 + if not replytext then goto badop end 234 + 235 + var reply = lib.store.post { 236 + author = co.who.id, parent = pid; 237 + subject = subj.ptr, acl = acl.ptr, body = replytext.ptr; 238 + } 239 + 240 + reply:publish(co.srv) 241 + end 228 242 229 243 lib.render.tweet_page(co, path, post.ptr) 230 244 do return end 231 245 232 246 ::badurl:: do co:complain(404, 'invalid URL', 'this URL does not reference extant content or functionality') return end 233 247 ::badop :: do co:complain(405, 'invalid operation', 'the operation you have attempted on this post is not meaningful') return end 234 248 end ................................................................................ 240 254 if path(1):cmp(lib.str.lit 'profile') then 241 255 lib.dbg('updating profile') 242 256 co.who.bio = co:postv('bio')._0 243 257 co.who.nym = co:postv('nym')._0 244 258 if co.who.bio ~= nil and @co.who.bio == 0 then co.who.bio = nil end 245 259 if co.who.nym ~= nil and @co.who.nym == 0 then co.who.nym = nil end 246 260 co.who.source:actor_save(co.who) 261 + 262 + var act = co:ppostv('act') 263 + var resethue = false 264 + if act:ref() then 265 + resethue = act:cmp(lib.str.plit 'reset-hue') 266 + end 267 + 268 + if not resethue then 269 + var shue = co:ppostv('hue') 270 + var nhue, okhue = lib.math.decparse(shue) 271 + if okhue and nhue ~= co.ui_hue then 272 + if nhue == co.srv.cfg.ui_hue 273 + then resethue = true 274 + else co.srv:actor_conf_int_set(co.who.id, 'ui-accent', nhue) 275 + end 276 + co.ui_hue = nhue 277 + end 278 + end 279 + if resethue then 280 + co.srv:actor_conf_int_reset(co.who.id, 'ui-accent') 281 + co.ui_hue = co.srv.cfg.ui_hue 282 + end 283 + 247 284 msg = lib.str.plit 'profile changes saved' 248 285 --user_refresh = true -- not really necessary here, actually 249 286 elseif path(1):cmp(lib.str.lit 'srv') then 250 287 if not co.who.rights.powers.config() then goto nopriv end 251 288 elseif path(1):cmp(lib.str.lit 'brand') then 252 289 if not co.who.rights.powers.rebrand() then goto nopriv end 253 290 elseif path(1):cmp(lib.str.lit 'users') then ................................................................................ 322 359 323 360 -- entry points 324 361 terra r.dispatch_http(co: &lib.srv.convo, uri: lib.mem.ptr(int8), meth: method.t) 325 362 lib.dbg('handling URI of form ', {uri.ptr,uri.ct}) 326 363 co.navbar = lib.render.nav(co) 327 364 -- some routes are non-hierarchical, and can be resolved with a simple strcmp 328 365 -- we run through those first before giving up and parsing the URI 329 - if uri.ptr[0] ~= @'/' then 366 + if uri.ptr == nil or uri.ptr[0] ~= @'/' then 330 367 co:complain(404, 'what the hell', 'how did you do that') 331 - return 332 368 elseif uri.ct == 1 then -- root 333 369 if (co.srv.cfg.pol_sec == lib.srv.secmode.private or 334 370 co.srv.cfg.pol_sec == lib.srv.secmode.lockdown) and co.aid == 0 then 335 371 http.login_form(co, meth) 336 - else 337 - -- FIXME display home screen 338 - http.timeline(co, hpath {ptr=nil}) 339 - goto notfound 340 - end 341 - return 372 + else http.timeline(co, hpath {ptr=nil}) end 342 373 elseif uri.ptr[1] == @'@' then 343 374 http.actor_profile_xid(co, uri, meth) 344 - return 345 375 elseif uri.ptr[1] == @'s' and uri.ptr[2] == @'/' and uri.ct > 3 then 346 - if meth ~= method.get then goto wrongmeth end 376 + if not meth_get(meth) then goto wrongmeth end 347 377 if not http.static_content(co, uri.ptr + 3, uri.ct - 3) then goto notfound end 348 - return 349 378 elseif lib.str.ncmp('/avi/', uri.ptr, 5) == 0 then 350 379 http.local_avatar(co, [lib.mem.ptr(int8)] {ptr = uri.ptr + 5, ct = uri.ct - 5}) 351 - return 352 380 elseif lib.str.ncmp('/compose', uri.ptr, lib.math.biggest(uri.ct,8)) == 0 then 353 381 if co.aid == 0 then co:reroute('/login') return end 354 382 http.post_compose(co,meth) 355 - return 356 383 elseif lib.str.ncmp('/login', uri.ptr, lib.math.biggest(uri.ct,6)) == 0 then 357 384 if co.aid == 0 358 385 then http.login_form(co, meth) 359 386 else co:reroute('/') 360 387 end 361 - return 362 388 elseif lib.str.ncmp('/logout', uri.ptr, lib.math.biggest(uri.ct,7)) == 0 then 363 389 if co.aid == 0 364 390 then goto notfound 365 391 else co:reroute_cookie('/','auth=; Path=/') 366 392 end 367 - return 368 393 else -- hierarchical routes 369 394 var path = lib.http.hier(uri) defer path:free() 370 395 if path.ct > 1 and path(0):cmp(lib.str.lit('user')) then 371 396 http.actor_profile_uid(co, path, meth) 372 397 elseif path.ct > 1 and path(0):cmp(lib.str.lit('post')) then 373 398 http.tweet_page(co, path, meth) 374 399 elseif path(0):cmp(lib.str.lit('tl')) then 375 400 http.timeline(co, path) 376 401 elseif path(0):cmp(lib.str.lit('doc')) then 377 - if meth ~= method.get and meth ~= method.head then goto wrongmeth end 402 + if not meth_get(meth) then goto wrongmeth end 378 403 http.documentation(co, path) 379 404 elseif path(0):cmp(lib.str.lit('conf')) then 380 405 if co.aid == 0 then goto unauth end 381 406 http.configure(co,path,meth) 382 407 else goto notfound end 383 - return 384 408 end 409 + do return end 385 410 386 411 ::wrongmeth:: co:complain(405, 'method not allowed', 'that method is not meaningful for this endpoint') do return end 387 412 ::notfound:: co:complain(404, 'not found', 'no such resource available') do return end 388 413 ::unauth:: co:complain(401, 'unauthorized', 'this content is not available at your clearance level') do return end 389 414 end
Modified srv.t from [9e0d1b7489] to [675eda18a7].
7 7 secret: lib.mem.ptr(int8) 8 8 pol_sec: secmode.t 9 9 pol_reg: bool 10 10 credmgd: bool 11 11 maxupsz: intptr 12 12 instance: lib.mem.ptr(int8) 13 13 overlord: &srv 14 + ui_hue: uint16 14 15 } 15 16 local struct srv { 16 17 sources: lib.mem.ptr(lib.store.source) 17 18 webmgr: lib.net.mg_mgr 18 19 webcon: &lib.net.mg_connection 19 20 cfg: cfgcache 20 21 id: rawstring ................................................................................ 104 105 if [ok] then break 105 106 else r = empty end 106 107 end 107 108 end 108 109 in r end 109 110 end 110 111 end) 112 + 113 +terra lib.store.post:publish(s: &srv) 114 + self:comp() 115 + self.posted = lib.osclock.time(nil) 116 + self.discovered = self.posted 117 + self.chgcount = 0 118 + self.edited = 0 119 + self.id = s:post_create(self) 120 + return self.id 121 +end 111 122 112 123 local struct convo { 113 124 srv: &srv 114 125 con: &lib.net.mg_connection 115 126 msg: &lib.net.mg_http_message 116 127 aid: uint64 -- 0 if logged out 117 128 aid_issue: lib.store.timepoint 118 129 who: &lib.store.actor -- who we're logged in as, if aid ~= 0 119 130 peer: lib.store.inet 120 131 reqtype: lib.http.mime.t -- negotiated content type 132 + method: lib.http.method.t 133 + live_last: lib.store.timepoint 121 134 -- cache 135 + ui_hue: uint16 122 136 navbar: lib.mem.ptr(int8) 123 137 actorcache: lib.mem.cache(lib.mem.ptr(lib.store.actor),32) -- naive cache to avoid unnecessary queries 124 138 -- private 125 139 varbuf: lib.mem.ptr(int8) 126 140 vbofs: &int8 127 141 } 142 + 143 +struct convo.page { 144 + title: pstring 145 + body: pstring 146 + class: pstring 147 + cache: bool 148 +} 128 149 129 150 -- this is unfortunately necessary to work around a terra bug 130 151 -- it can't seem to handle forward-declarations of structs in C 131 152 132 153 local getpeer 133 154 do local struct strucheader { 134 155 next: &lib.net.mg_connection ................................................................................ 136 157 peer: lib.net.mg_addr 137 158 } 138 159 terra getpeer(con: &lib.net.mg_connection) 139 160 return [&strucheader](con).peer 140 161 end 141 162 end 142 163 164 +terra convo:rawpage(code: uint16, pg: convo.page, hdrs: lib.mem.ptr(lib.http.header)) 165 + var doc = data.view.docskel { 166 + instance = self.srv.cfg.instance; 167 + title = pg.title; 168 + body = pg.body; 169 + class = pg.class; 170 + navlinks = self.navbar; 171 + attr = ''; 172 + } 173 + var attrbuf: int8[32] 174 + if self.aid ~= 0 and self.ui_hue ~= 323 then 175 + var hdecbuf: int8[21] 176 + var hdec = lib.math.decstr(self.ui_hue, &hdecbuf[20]) 177 + lib.str.cpy(&attrbuf[0], ' style="--hue:') 178 + lib.str.cpy(&attrbuf[14], hdec) 179 + var len = &hdecbuf[20] - hdec 180 + lib.str.cpy(&attrbuf[14] + len, '"') 181 + doc.attr = &attrbuf[0] 182 + end 183 + 184 + if self.method == [lib.http.method.head] 185 + then doc:head(self.con,code,hdrs) 186 + else doc:send(self.con,code,hdrs) 187 + end 188 +end 189 + 190 +terra convo:statpage(code: uint16, pg: convo.page) 191 + var hdrs = array( 192 + lib.http.header { key = 'Content-Type', value = 'text/html; charset=UTF-8' }, 193 + lib.http.header { key = 'Cache-Control', value = 'no-store' } 194 + ) 195 + self:rawpage(code,pg, [lib.mem.ptr(lib.http.header)] { 196 + ptr = &hdrs[0]; 197 + ct = [hdrs.type.N] - lib.trn(pg.cache,1,0); 198 + }) 199 +end 200 + 201 +terra convo:livepage(pg: convo.page, lastup: lib.store.timepoint) 202 + var nbuf: int8[21] 203 + var hdrs = array( 204 + lib.http.header { key = 'Content-Type', value = 'text/html; charset=UTF-8' }, 205 + lib.http.header { key = 'Cache-Control', value = 'no-store' }, 206 + lib.http.header { 207 + key = 'X-Live-Newest-Artifact'; 208 + value = lib.math.decstr(lastup, &nbuf[20]); 209 + }, 210 + lib.http.header { key = 'Content-Length', value = '0' } 211 + ) 212 + if self.live_last ~= 0 and self.live_last <= lastup then 213 + lib.net.mg_printf(self.con, 'HTTP/1.1 %s', lib.http.codestr(200)) 214 + for i = 0, [hdrs.type.N] do 215 + lib.net.mg_printf(self.con, '%s: %s\r\n', hdrs[i].key, hdrs[i].value) 216 + end 217 + lib.net.mg_printf(self.con, '\r\n') 218 + else 219 + self:rawpage(200, pg, [lib.mem.ptr(lib.http.header)] { 220 + ptr = &hdrs[0], ct = 3 221 + }) 222 + end 223 +end 224 + 225 +terra convo:stdpage(pg: convo.page) self:statpage(200, pg) end 226 + 143 227 terra convo:reroute_cookie(dest: rawstring, cookie: rawstring) 144 228 var hdrs = array( 145 229 lib.http.header { key = 'Content-Type', value = 'text/html; charset=UTF-8' }, 146 230 lib.http.header { key = 'Location', value = dest }, 147 231 lib.http.header { key = 'Set-Cookie', value = cookie } 148 232 ) 149 233 150 234 var body = data.view.docskel { 151 235 instance = self.srv.cfg.instance.ptr; 152 236 title = 'rerouting'; 153 237 body = 'you are being redirected'; 154 238 class = 'error'; 155 239 navlinks = ''; 240 + attr = ''; 156 241 } 157 242 158 243 body:send(self.con, 303, [lib.mem.ptr(lib.http.header)] { 159 244 ptr = &hdrs[0], ct = [hdrs.type.N] - lib.trn(cookie == nil,1,0) 160 245 }) 161 246 end 162 247 ................................................................................ 170 255 lib.dbg('sending cookie ',{&sesskey[0],15}) 171 256 p = lib.str.ncpy(p, '; Path=/', 9) 172 257 end 173 258 self:reroute_cookie(dest, &sesskey[0]) 174 259 end 175 260 176 261 terra convo:complain(code: uint16, title: rawstring, msg: rawstring) 177 - var hdrs = array( 178 - lib.http.header { key = 'Content-Type', value = 'text/html; charset=UTF-8' }, 179 - lib.http.header { key = 'Cache-Control', value = 'no-store' } 180 - ) 262 + if msg == nil then msg = "i'm sorry, dave. i can't let you do that" end 181 263 182 264 var ti: lib.str.acc ti:compose('error :: ', title) 183 265 var bo: lib.str.acc bo:compose('<div class="message"><img class="icon" src="/s/warn.webp"><h1>',title,'</h1><p>',msg,'</p></div>') 184 - var body = data.view.docskel { 185 - instance = self.srv.cfg.instance; 266 + var body = [convo.page] { 186 267 title = ti:finalize(); 187 268 body = bo:finalize(); 188 269 class = lib.str.plit 'error'; 189 - navlinks = lib.coalesce(self.navbar, [lib.mem.ptr(int8)]{ptr='',ct=0}); 270 + cache = false; 190 271 } 191 272 192 - if body.body.ptr == nil then 193 - body.body = lib.str.plit"i'm sorry, dave. i can't let you do that" 194 - end 195 - 196 - body:send(self.con, code, [lib.mem.ptr(lib.http.header)] { 197 - ptr = &hdrs[0], ct = [hdrs.type.N] 198 - }) 273 + self:statpage(code, body) 199 274 200 275 body.title:free() 201 276 body.body:free() 202 277 end 203 278 204 279 convo.methods.assertpow = macro(function(self, pow) 205 280 return quote ................................................................................ 207 282 if self.aid == 0 or self.who.rights.powers.[pow:asvalue()]() == false then 208 283 ok = false 209 284 self:complain(403,'insufficient privileges',['you lack the <strong>'..pow:asvalue()..'</strong> power and cannot perform this action']) 210 285 end 211 286 in ok end 212 287 end) 213 288 214 -struct convo.page { 215 - title: pstring 216 - body: pstring 217 - class: pstring 218 - cache: bool 219 -} 220 - 221 -terra convo:stdpage(pg: convo.page) 222 - var doc = data.view.docskel { 223 - instance = self.srv.cfg.instance; 224 - title = pg.title; 225 - body = pg.body; 226 - class = pg.class; 227 - navlinks = self.navbar; 228 - } 229 - 230 - var hdrs = array( 231 - lib.http.header { key = 'Content-Type', value = 'text/html; charset=UTF-8' }, 232 - lib.http.header { key = 'Cache-Control', value = 'no-store' } 233 - ) 234 - 235 - doc:send(self.con,200,[lib.mem.ptr(lib.http.header)] {ct = [hdrs.type.N] - lib.trn(pg.cache,1,0), ptr = &hdrs[0]}) 236 -end 237 - 238 289 -- CALL ONLY ONCE PER VAR 239 290 terra convo:postv(name: rawstring) 240 291 if self.varbuf.ptr == nil then 241 292 self.varbuf = lib.mem.heapa(int8, self.msg.body.len + self.msg.query.len) 242 293 self.vbofs = self.varbuf.ptr 243 294 end 244 295 var o = lib.net.mg_http_get_var(&self.msg.body, name, self.vbofs, self.varbuf.ct - (self.vbofs - self.varbuf.ptr)) ................................................................................ 299 350 if lib.str.ncmp(mimevar.ptr, mime, lib.math.biggest(mimevar.ct, [#mime])) == 0 then 300 351 ret = [lib.http.mime[name]] 301 352 else ret = [mimeneg] end 302 353 in ret end 303 354 end 304 355 305 356 local handle = { 306 - http = terra(con: &lib.net.mg_connection, event: int, p: &opaque, ext: &opaque) 307 - var server = [&srv](ext) 357 + http = terra(con: &lib.net.mg_connection, event_kind: int, event: &opaque, userdata: &opaque) 358 + var server = [&srv](userdata) 308 359 var mgpeer = getpeer(con) 309 360 var peer = lib.store.inet { port = mgpeer.port; } 310 361 if mgpeer.is_ip6 then peer.pv = 6 else peer.pv = 4 end 311 362 if peer.pv == 6 then 312 363 for i = 0, 16 do peer.v6[i] = mgpeer.ip6[i] end 313 364 else -- v4 314 365 @[&uint32](&peer.v4) = mgpeer.ip ................................................................................ 319 370 -- for now i'm leaving it as is, but note that netmask restrictions 320 371 -- WILL NOT WORK until upstream gets its shit together. FIXME 321 372 322 373 -- needs to check for an X-Forwarded-For header from nginx and 323 374 -- use that instead of the peer iff peer is ::1/127.1 FIXME 324 375 -- maybe also haproxy support? 325 376 326 - switch event do 377 + switch event_kind do 327 378 case lib.net.MG_EV_HTTP_MSG then 328 379 lib.dbg('routing HTTP request') 329 - var msg = [&lib.net.mg_http_message](p) 380 + var msg = [&lib.net.mg_http_message](event) 330 381 var co = convo { 331 382 con = con, srv = server, msg = msg; 332 383 aid = 0, aid_issue = 0, who = nil; 333 384 reqtype = lib.http.mime.none; 334 - peer = peer; 385 + peer = peer, live_last = 0; 335 386 } co.varbuf.ptr = nil 336 387 co.navbar.ptr = nil 337 388 co.actorcache.top = 0 338 389 co.actorcache.cur = 0 390 + co.ui_hue = server.cfg.ui_hue 339 391 340 392 -- first, check for an accept header. if it's there, we need to 341 393 -- iterate over the values and pick the highest-priority one 342 394 do var acc = lib.http.findheader(msg, 'Accept') 343 395 -- TODO handle q-value 344 - if acc.ptr ~= nil then 396 + if acc ~= nil and acc.ptr ~= nil then 345 397 var [mimevar] = [lib.mem.ref(int8)] { ptr = acc.ptr } 346 398 var i = 0 while i < acc.ct do 347 399 if acc.ptr[i] == @',' or acc.ptr[i] == @';' then 348 400 mimevar.ct = (acc.ptr+i) - mimevar.ptr 349 401 var t = [mimeneg] 350 402 if t ~= lib.http.mime.none then 351 403 co.reqtype = t ................................................................................ 377 429 else co.reqtype = lib.http.mime.html end 378 430 ::foundtype::end 379 431 380 432 -- we need to check if there's any cookies sent with the request, 381 433 -- and if so, whether they contain any credentials. this will be 382 434 -- used to set the auth parameters in the http conversation 383 435 var cookies_p = lib.http.findheader(msg, 'Cookie') 384 - if cookies_p ~= nil then 436 + if cookies_p ~= nil and cookies_p.ptr ~= nil then 385 437 var cookies = cookies_p.ptr 386 438 var key = [lib.mem.ref(int8)] {ptr = cookies, ct = 0} 387 439 var val = [lib.mem.ref(int8)] {ptr = nil, ct = 0} 388 440 var i = 0 while i < cookies_p.ct and 389 441 cookies[i] ~= 0 and 390 442 cookies[i] ~= @'\r' and 391 443 cookies[i] ~= @'\n' do -- cover all the bases ................................................................................ 423 475 end 424 476 425 477 if co.aid ~= 0 then 426 478 var sess, usr = co.srv:actor_session_fetch(co.aid, peer, co.aid_issue) 427 479 if sess.ok == false then co.aid = 0 co.aid_issue = 0 else 428 480 co.who = usr.ptr 429 481 co.who.rights.powers = server:actor_powers_fetch(co.who.id) 482 + var userhue, hueok = server:actor_conf_int_get(co.who.id, 'ui-accent') 483 + if hueok then co.ui_hue = userhue end 430 484 end 431 485 end 486 + 487 + var livelast_p = lib.http.findheader(msg, 'X-Live-Last-Arrival') 488 + if livelast_p ~= nil and livelast_p.ptr ~= nil then 489 + var ll, ok = lib.math.decparse(pstring{ptr = livelast_p.ptr, ct = livelast_p.ct - 1}) 490 + if ok then co.live_last = ll end 491 + end 492 + 432 493 433 494 var uridec = lib.mem.heapa(int8, msg.uri.len) defer uridec:free() 434 495 var urideclen = lib.net.mg_url_decode(msg.uri.ptr, msg.uri.len, uridec.ptr, uridec.ct, 1) 435 496 436 497 var uri = uridec 437 498 if urideclen == -1 then 438 499 for i = 0,msg.uri.len do ................................................................................ 442 503 end 443 504 end 444 505 uri.ct = msg.uri.len 445 506 else uri.ct = urideclen end 446 507 lib.dbg('routing URI ', {uri.ptr, uri.ct}) 447 508 448 509 if lib.str.ncmp('GET', msg.method.ptr, msg.method.len) == 0 then 510 + co.method = [lib.http.method.get] 449 511 route.dispatch_http(&co, uri, [lib.http.method.get]) 450 512 elseif lib.str.ncmp('POST', msg.method.ptr, msg.method.len) == 0 then 513 + co.method = [lib.http.method.get] 451 514 route.dispatch_http(&co, uri, [lib.http.method.post]) 452 515 elseif lib.str.ncmp('HEAD', msg.method.ptr, msg.method.len) == 0 then 516 + co.method = [lib.http.method.head] 453 517 route.dispatch_http(&co, uri, [lib.http.method.head]) 454 518 elseif lib.str.ncmp('OPTIONS', msg.method.ptr, msg.method.len) == 0 then 519 + co.method = [lib.http.method.options] 455 520 route.dispatch_http(&co, uri, [lib.http.method.options]) 456 521 else 457 522 co:complain(400,'unknown method','you have submitted an invalid http request') 458 523 end 459 524 460 525 if co.aid ~= 0 then lib.mem.heapf(co.who) end 461 526 if co.varbuf.ptr ~= nil then co.varbuf:free() end ................................................................................ 670 735 do self.pol_reg = false 671 736 var sreg = self.overlord:conf_get('policy-self-register') 672 737 if sreg:ref() then 673 738 if lib.str.cmp(sreg.ptr, 'on') == 0 674 739 then self.pol_reg = true 675 740 else self.pol_reg = false 676 741 end 677 - end 678 - sreg:free() end 742 + sreg:free() 743 + end end 679 744 680 745 do self.credmgd = false 681 746 var sreg = self.overlord:conf_get('credential-store') 682 747 if sreg:ref() then 683 748 if lib.str.cmp(sreg.ptr, 'managed') == 0 684 749 then self.credmgd = true 685 750 else self.credmgd = false 686 751 end 687 - end 688 - sreg:free() end 752 + sreg:free() 753 + end end 689 754 690 755 do self.maxupsz = [1024 * 100] -- 100 kilobyte default 691 756 var sreg = self.overlord:conf_get('maximum-artifact-size') 692 757 if sreg:ref() then 693 758 var sz, ok = lib.math.fsz_parse(sreg) 694 759 if ok then self.maxupsz = sz else 695 760 lib.warn('invalid configuration value for maximum-artifact-size; keeping default 100K upload limit') 696 761 end 762 + sreg:free() end 697 763 end 698 - sreg:free() end 699 764 700 765 self.pol_sec = secmode.lockdown 701 766 var smode = self.overlord:conf_get('policy-security') 702 767 if smode.ptr ~= nil then 703 768 if lib.str.cmp(smode.ptr, 'public') == 0 then 704 769 self.pol_sec = secmode.public 705 770 elseif lib.str.cmp(smode.ptr, 'private') == 0 then 706 771 self.pol_sec = secmode.private 707 772 elseif lib.str.cmp(smode.ptr, 'lockdown') == 0 then 708 773 self.pol_sec = secmode.lockdown 709 774 elseif lib.str.cmp(smode.ptr, 'isolate') == 0 then 710 775 self.pol_sec = secmode.isolate 711 776 end 777 + smode:free() 712 778 end 713 - smode:free() 779 + 780 + self.ui_hue = config.default_ui_accent 781 + var shue = self.overlord:conf_get('ui-accent') 782 + if shue.ptr ~= nil then 783 + var hue,ok = lib.math.decparse(shue) 784 + if ok then self.ui_hue = hue end 785 + shue:free() 786 + end 714 787 end 715 788 716 789 return { 717 790 overlord = srv; 718 791 convo = convo; 719 792 route = route; 720 793 secmode = secmode; 721 794 }
Added static/live.js version [6fb4c9ec70].
1 +/* first things first, we need to scan over the document and see 2 + * if there are any UI elements unfortunate enough to need 3 + * interactivity beyond what native HTML+CSS can provide. if so, 4 + * we attach the appropriate listeners to them. */ 5 +window.addEventListener('load', function() { 6 + /* update hue-picker background when slider is adjusted */ 7 + document.querySelectorAll('.color-picker').forEach(function(box) { 8 + let slider = box.querySelector('[data-color-pick]'); 9 + box.style.setProperty('--hue', slider.value); 10 + slider.addEventListener('input', function(e) { 11 + box.style.setProperty('--hue', e.target.value); 12 + }); 13 + }); 14 + 15 + /* the main purpose of this script -- by marking itself with the 16 + * data-live property, an html element registers itself for live 17 + * updates from the server. this is pretty straightforward: we 18 + * retrieve this url from the server as a get request, create a 19 + * tree from its html, find the element in question, ferret out 20 + * any deltas, and apply them. */ 21 + document.querySelectorAll('*[data-live]').forEach(function(container) { 22 + let interv = parseFloat(container.attributes.getNamedItem('data-live').nodeValue) * 1000; 23 + container._liveLastArrival = '0'; /* TODO include header for this */ 24 + 25 + window.setInterval(function() { 26 + var req = new Request(window.location, { 27 + method: 'GET', 28 + headers: { 29 + 'X-Live-Last-Arrival': container._liveLastArrival 30 + } 31 + }) 32 + 33 + fetch(req).then(function(resp) { 34 + if (!resp.ok) return; 35 + let newest = resp.headers.get('X-Live-Newest-Artifact'); 36 + if (newest <= container._liveLastArrival) { 37 + resp.body.cancel(); 38 + return; 39 + } 40 + container._liveLastArrival = newest 41 + 42 + resp.text().then(function(htmlbody) { 43 + var parser = new DOMParser(); 44 + var newdoc = parser.parseFromString(htmlbody,'text/html') 45 + // console.log(newdoc.getElementById(container.id).innerHTML) 46 + container.innerHTML = newdoc.getElementById(container.id).innerHTML 47 + }) 48 + }) 49 + }, interv) 50 + }); 51 +});
Modified static/style.scss from [ada3763759] to [0e6b10a9e2].
1 -$color: hsl(323,100%,65%); 1 +$default-color: hsl(323,100%,65%); 2 2 %sans { font-family: "Alegreya Sans", "Open Sans", sans-serif; } 3 3 %serif { font-family: Alegreya, GaramondNo8, "Garamond Premier Pro", "Adobe Garamond", Garamond, Junicode, serif; } 4 4 %teletype { font-family: "Inconsolata LGC", Inconsolata, monospace; font-size: 12pt !important; } 5 5 6 -@function tone($pct, $alpha: 0) { @return adjust-color($color, $lightness: $pct, $alpha: $alpha) } 6 +// @function tone($pct, $alpha: 0) { @return adjust-color($color, $lightness: $pct, $alpha: $alpha) } 7 +@function tone($pct, $alpha: 0) { 8 + @return hsla(var(--hue), 100%, 65% + $pct, 1 + $alpha) 9 +} 7 10 11 +:root { --hue: 323; } 8 12 body { 9 13 @extend %sans; 10 14 background-color: tone(-55%); 11 15 color: tone(25%); 12 16 font-size: 14pt; 13 17 margin: 0; 14 18 padding: 0; ................................................................................ 20 24 ::placeholder { 21 25 color: tone(0,-0.3); 22 26 font-style: italic; 23 27 } 24 28 a[href] { 25 29 color: tone(10%); 26 30 text-decoration-color: tone(10%,-0.5); 27 - &:hover { 31 + &:hover, &:focus { 28 32 color: white; 29 33 text-shadow: 0 0 15px tone(20%); 30 34 text-decoration-color: tone(10%,-0.1); 35 + outline: none; 31 36 } 32 37 &.button { @extend %button; } 33 38 } 34 39 a[href^="//"], 35 40 a[href^="http://"], 36 41 a[href^="https://"] { // external link 37 42 &:hover::after { ................................................................................ 69 74 border: 1px solid black; 70 75 color: tone(25%); 71 76 text-shadow: 1px 1px black; 72 77 text-decoration: none; 73 78 text-align: center; 74 79 cursor: default; 75 80 user-select: none; 81 + -webkit-user-drag: none; 82 + -webkit-app-region: no-drag; 76 83 background: linear-gradient(to bottom, 77 84 tone(-47%), 78 85 tone(-50%) 15%, 79 86 tone(-50%) 75%, 80 87 tone(-53%) 81 88 ); 82 89 &:hover, &:focus { ................................................................................ 206 213 > a[href] { 207 214 display: block; 208 215 padding: 0.25in 0.10in; 209 216 //padding: calc((25% - 1em)/2) 0.15in; 210 217 &, &::after { transition: 0.3s; } 211 218 text-shadow: 1px 1px 1px black; 212 219 &:hover{ 213 - transform: scale(120%); 220 + transform: scale(1.2); 214 221 } 215 222 } 216 223 } 217 224 } 218 225 } 219 226 220 227 main { ................................................................................ 341 348 @extend %box; 342 349 display: block; 343 350 width: 4in; 344 351 margin:auto; 345 352 padding: 0.5in; 346 353 text-align: center; 347 354 menu:first-of-type { margin-top: 0.3in; } 355 + img.icon { width: 1.875in; height: 1.875in; } 348 356 } 349 357 350 358 div.login { 351 359 @extend %box; 352 360 width: 4in; 353 361 padding: 0.4in; 354 362 > .msg { ................................................................................ 458 466 font-size: 1.5ex !important; 459 467 letter-spacing: 1.3px; 460 468 padding-bottom: 3px; 461 469 border-radius: 2px; 462 470 vertical-align: baseline; 463 471 box-shadow: 1px 1px 1px black; 464 472 } 473 + 474 +div.thread { 475 + margin-left: 0.3in; 476 + & + div.post { margin-top: 0.3in; } 477 +} 465 478 466 479 div.post { 467 480 @extend %box; 468 481 display: grid; 469 482 grid-template-columns: 1in 1fr max-content; 470 483 grid-template-rows: min-content max-content; 471 484 margin-bottom: 0.1in; 472 485 >.avatar { 473 486 grid-column: 1/2; grid-row: 1/2; 474 - img { display: block; width: 1in; margin:0; } 487 + img { display: block; width: 1in; height: 1in; margin:0; } 475 488 background: linear-gradient(to bottom, tone(-53%), tone(-57%)); 476 489 } 477 490 >a[href].username { 478 491 display: block; 479 492 grid-column: 1/3; 480 493 grid-row: 2/3; 481 494 text-align: left; ................................................................................ 494 507 } 495 508 >.content { 496 509 grid-column: 2/4; grid-row: 1/2; 497 510 padding: 0.2in; 498 511 @extend %serif; 499 512 font-size: 110%; 500 513 text-align: justify; 514 + color: tone(25%); 501 515 } 502 516 > a[href].permalink { 503 517 display: block; 504 518 grid-column: 3/4; grid-row: 2/3; 505 519 font-size: 80%; 506 520 text-align: right; 507 521 padding: 0.1in; ................................................................................ 523 537 h1, h2, h3, h4, h5, h6 { 524 538 background: linear-gradient(to right, tone(-50%), transparent); 525 539 margin-left: -0.4in; 526 540 padding-left: 0.2in; 527 541 text-shadow: 0 2px 0 black; 528 542 } 529 543 } 544 + 545 +%navmenu, body.profile main > menu { 546 + margin-left: -0.25in; 547 + grid-column: 1/2; grid-row: 1/2; 548 + background: linear-gradient(to bottom, tone(-45%),tone(-55%)); 549 + border: 1px solid black; 550 + padding: 0.1in; 551 + > a[href] { 552 + @extend %button; 553 + display: block; 554 + text-align: left; 555 + } 556 + > a[href] + a[href] { 557 + border-top: none; 558 + } 559 + hr { 560 + border: none; 561 + } 562 +} 530 563 531 564 menu { all: unset; display: block; } 532 565 body.conf main { 533 566 display: grid; 534 567 grid-template-columns: 2in 1fr; 535 568 grid-template-rows: max-content 1fr; 536 - > menu { 537 - margin-left: -0.25in; 538 - grid-column: 1/2; grid-row: 1/2; 539 - background: linear-gradient(to bottom, tone(-45%),tone(-55%)); 540 - border: 1px solid black; 541 - padding: 0.1in; 542 - > a[href] { 543 - @extend %button; 544 - display: block; 545 - text-align: left; 546 - } 547 - > a[href] + a[href] { 548 - border-top: none; 549 - } 550 - hr { 551 - border: none; 552 - } 553 - } 569 + > menu { @extend %navmenu; } 554 570 > .panel { 555 571 grid-column: 2/3; grid-row: 1/3; 556 572 padding-left: 0.15in; 557 573 > h1 { 558 574 padding-bottom: 0.1in; 559 575 margin-bottom: 0.1in; 560 576 margin-left: -0.15in; ................................................................................ 610 626 &.vertical-float { 611 627 flex-flow: column; 612 628 float: right; 613 629 width: 40%; 614 630 margin-left: 0.1in; 615 631 } 616 632 > %button { 617 - flex-basis: 0; 633 + flex-basis: min-content; 618 634 flex-grow: 1; 619 635 display: block; margin: 2px; 620 636 } 621 637 } 622 638 623 639 .check-panel { 624 640 display: flex; ................................................................................ 664 680 100% { opacity: 0; transform: scale(0.9) translateY(-0.12in); display: none; } 665 681 } 666 682 .flashmsg { 667 683 display: block; 668 684 position: fixed; 669 685 top: 1.3in; 670 686 max-width: 3in; 671 - padding: 0.5in 0.2in; 687 + padding: 0.4in 0.2in; 672 688 left: 0; right: 0; 673 689 text-align: center; 674 690 text-shadow: 0 0 15px tone(10%); 675 691 margin: auto; 676 692 background: linear-gradient(to bottom, tone(-49%), tone(-43%,-0.1)); 677 693 border: 1px solid tone(0%); 678 694 border-radius: 3px; 679 695 box-shadow: 0 0 50px tone(-55%); 680 696 color: white; 681 697 animation: ease forwards flashup; 682 698 //cubic-bezier(0.4, 0.63, 0.6, 0.31) 683 - animation-duration: 3s; 699 + animation-duration: 2.5s; 684 700 } 685 701 686 702 form.action-bar { 687 703 display: flex; 688 704 > * { 689 705 flex-grow: 1; 690 706 flex-basis: 0; 691 707 margin-left: 0.1in; 692 708 } 693 709 > *:first-child { 694 710 margin-left: 0; 695 711 } 696 712 } 713 + 714 +.color-picker { 715 + /* implemented using javascript, alas */ 716 + @extend %box; 717 + label { text-shadow: 1px 1px black; } 718 + padding: 0.1in; 719 +}
Modified store.t from [d79d41c9fe] to [e7b33e5534].
156 156 chgcount: uint 157 157 mentions: lib.mem.ptr(uint64) 158 158 circles: lib.mem.ptr(uint64) --only meaningful if scope is set to circle 159 159 convoheaduri: str 160 160 parent: uint64 161 161 -- ephemera 162 162 localpost: bool 163 + accent: int16 164 + depth: uint16 -- used in conversations to indicate tree depth 163 165 source: &m.source 164 166 165 167 -- save :: bool -> {} (defined in acl.t due to dep. hell) 166 168 } 167 169 168 -local cnf = terralib.memoize(function(ty,rty) 170 +m.user_conf_funcs = function(be,n,ty,rty,rty2) 169 171 rty = rty or ty 170 - return struct { 171 - enum: {&opaque, uint64, rawstring} -> intptr 172 - get: {&opaque, uint64, rawstring} -> rty 173 - set: {&opaque, uint64, rawstring, ty} -> {} 174 - reset: {&opaque, uint64, rawstring} -> {} 175 - } 176 -end) 172 + local gt 173 + if not rty2 -- what the fuck? 174 + then gt = {&m.source, uint64, rawstring} -> rty; 175 + else gt = {&m.source, uint64, rawstring} -> {rty, rty2}; 176 + end 177 + for k, t in pairs { 178 + enum = {&m.source, uint64, rawstring} -> lib.mem.ptr(rty); 179 + get = gt; 180 + set = {&m.source, uint64, rawstring, ty} -> {}; 181 + reset = {&m.source, uint64, rawstring} -> {}; 182 + } do 183 + be.entries[#be.entries+1] = { 184 + field = 'actor_conf_'..n..'_'..k, type = t 185 + } 186 + end 187 +end 177 188 178 189 struct m.notif { 179 190 kind: m.notiftype.t 180 191 when: uint64 181 192 union { 182 193 post: uint64 183 - reaction: int8[8] 194 + reaction: int8[16] 184 195 } 185 196 } 186 197 187 198 struct m.inet { 188 199 pv: uint8 -- 0 = null, 4 = ipv4, 6 = ipv6 189 200 union { 190 201 v4: uint8[4] ................................................................................ 232 243 context: str 233 244 } 234 245 235 246 struct m.auth { 236 247 -- a credential record 237 248 aid: uint64 238 249 uid: uint64 250 + kind: str 239 251 aname: str 252 + comment: str 240 253 netmask: m.inet 241 254 privs: m.privset 242 255 blacklist: bool 243 256 } 244 257 245 258 struct m.relationship { 246 259 agent: uint64 ................................................................................ 313 326 -- cookie issue time: m.timepoint 314 327 actor_auth_register_uid: {&m.source, uint64, uint64} -> {} 315 328 -- notifies the backend module of the UID that has been assigned for 316 329 -- an authentication ID 317 330 -- aid: uint64 318 331 -- uid: uint64 319 332 320 - actor_conf_str: cnf(rawstring, lib.mem.ptr(int8)) 321 - actor_conf_int: cnf(intptr, lib.stat(intptr)) 322 - 323 - auth_create_pw: {&m.source, uint64, bool, lib.mem.ptr(int8)} -> {} 333 + auth_enum_uid: {&m.source, uint64} -> lib.mem.ptr(lib.mem.ptr(m.auth)) 334 + auth_enum_handle: {&m.source, rawstring} -> lib.mem.ptr(lib.mem.ptr(m.auth)) 335 + auth_attach_pw: {&m.source, uint64, bool, pstr, pstr} -> {} 324 336 -- uid: uint64 325 337 -- reset: bool (delete other passwords?) 326 338 -- pw: pstring 339 + -- comment: pstring 327 340 auth_purge_pw: {&m.source, uint64, rawstring} -> {} 328 341 auth_purge_otp: {&m.source, uint64, rawstring} -> {} 329 342 auth_purge_trust: {&m.source, uint64, rawstring} -> {} 330 343 auth_sigtime_user_fetch: {&m.source, uint64} -> m.timepoint 331 344 -- authentication tokens and accounts have a property that controls 332 345 -- whether auth cookies dated to a certain point are valid. cookies 333 346 -- that are generated before the timepoint are considered invalid. ................................................................................ 338 351 -- timestamp: timepoint 339 352 340 353 post_save: {&m.source, &m.post} -> {} 341 354 post_create: {&m.source, &m.post} -> uint64 342 355 post_destroy: {&m.source, uint64} -> {} 343 356 post_fetch: {&m.source, uint64} -> lib.mem.ptr(m.post) 344 357 post_enum_author_uid: {&m.source, uint64, m.range} -> lib.mem.ptr(lib.mem.ptr(m.post)) 358 + post_enum_parent: {&m.source, uint64} -> lib.mem.ptr(lib.mem.ptr(m.post)) 345 359 post_attach_ctl: {&m.source, uint64, uint64, bool} -> {} 346 360 -- attaches or detaches an existing database artifact 347 361 -- post id: uint64 348 362 -- artifact id: uint64 349 363 -- detach: bool 364 + 365 + thread_latest_arrival_calc: {&m.source, uint64} -> m.timepoint 366 + 350 367 artifact_instantiate: {&m.source, lib.mem.ptr(uint8), lib.mem.ptr(int8)} -> uint64 351 368 -- instantiate an artifact in the database, either installing a new 352 369 -- artifact or returning the id of an existing artifact with the same hash 353 370 -- artifact: bytea 354 371 -- mime: pstring 355 372 artifact_quicksearch: {&m.source, lib.mem.ptr(uint8)} -> {uint64,bool} 356 373 -- checks whether a hash is already in the database without uploading ................................................................................ 385 402 -- proto: kompromat (null for all records, or a prototype describing the records to return) 386 403 nkvd_sanction_issue: {&m.source, &m.sanction} -> uint64 387 404 nkvd_sanction_vacate: {&m.source, uint64} -> {} 388 405 nkvd_sanction_enum_target: {&m.source, uint64} -> {} 389 406 nkvd_sanction_enum_issuer: {&m.source, uint64} -> {} 390 407 nkvd_sanction_review: {&m.source, m.timepoint} -> {} 391 408 392 - convo_fetch_xid: {&m.source,rawstring} -> lib.mem.ptr(m.post) 393 - convo_fetch_cid: {&m.source,uint64} -> lib.mem.ptr(m.post) 394 - 395 409 timeline_actor_fetch_uid: {&m.source, uint64, m.range} -> lib.mem.ptr(lib.mem.ptr(m.post)) 396 410 timeline_instance_fetch: {&m.source, m.range} -> lib.mem.ptr(lib.mem.ptr(m.post)) 397 411 } 412 + 413 +m.user_conf_funcs(m.backend, 'str', rawstring, lib.mem.ptr(int8)) 414 +m.user_conf_funcs(m.backend, 'int', intptr, intptr, bool) 398 415 399 416 struct m.source { 400 417 backend: &m.backend 401 418 id: lib.mem.ptr(int8) 402 419 handle: &opaque 403 420 string: lib.mem.ptr(int8) 404 421 }
Modified tpl.t from [682e534236] to [9f68e11c52].
156 156 rec.methods.append = terra([symself], [accumulator]) 157 157 lib.dbg(['appending template ' .. tid]) 158 158 [tallyup] 159 159 accumulator:cue([runningtally]) 160 160 [appenders] 161 161 return accumulator 162 162 end 163 - rec.methods.send = terra([symself], [destcon], code: uint16, hd: lib.mem.ptr(lib.http.header)) 164 - lib.dbg(['transmitting template ' .. tid]) 163 + rec.methods.head = terra([symself], [destcon], code: uint16, hd: lib.mem.ptr(lib.http.header)) 164 + lib.dbg(['transmitting template headers ' .. tid]) 165 165 [tallyup] 166 166 lib.net.mg_printf([destcon], 'HTTP/1.1 %s', lib.http.codestr(code)) 167 167 for i = 0, hd.ct do 168 168 lib.net.mg_printf([destcon], '%s: %s\r\n', hd.ptr[i].key, hd.ptr[i].value) 169 169 end 170 170 lib.net.mg_printf([destcon],'Content-Length: %llu\r\n\r\n', [runningtally] + 1) 171 + end 172 + rec.methods.send = terra([symself], [destcon], code: uint16, hd: lib.mem.ptr(lib.http.header)) 173 + lib.dbg(['transmitting template ' .. tid]) 174 + 175 + symself:head(destcon,code,hd) 176 + 171 177 [senders] 172 178 lib.net.mg_send([destcon],'\r\n',2) 179 + end 180 + rec.methods.sz = terra([symself]) 181 + lib.dbg(['tallying template ' .. tid]) 182 + [tallyup] 183 + return [runningtally] + 1 173 184 end 174 185 175 186 return rec 176 187 end 177 188 178 189 return m
Modified view/conf-profile.tpl from [d384fd3b9f] to [48e88ad45a].
1 1 <form method="post"> 2 2 <div class="elem"><label>handle</label> <div class="txtbox">@!handle</div></div> 3 3 <div class="elem"><label for="nym">display name</label> <input type="text" name="nym" id="nym" placeholder="j. random poster" value="@:nym"></div> 4 4 <div class="elem"><label for="bio">bio</label><textarea name="bio" id="bio" placeholder="tall, dark, and mysterious">@!bio</textarea></div> 5 - <button>commit</button> 5 + <div class="elem color-picker"><label for="hue">accent</label><input type="range" min="0" max="360" value="@hue" name="hue" id="hue" data-color-pick></div> 6 + <menu class="choice vertical"> 7 + <button>commit all</button> 8 + <button name="act" value="reset-hue">use server colors</button> 9 + </menu> 6 10 </form>
Modified view/conf.tpl from [09b447ff32] to [6af7c57c63].
1 1 <menu> 2 2 <a href="/conf/profile">profile</a> 3 3 <a href="/conf/avi">avatar</a> 4 + <a href="/conf/ui">interface</a> 4 5 <a href="/conf/sec">security</a> 5 6 <a href="/conf/rel">relationships</a> 6 7 <a href="/conf/qnt">quarantine</a> 7 8 <a href="/conf/acl">ACL shortcuts</a> 8 9 <a href="/conf/rooms">chatrooms</a> 9 10 <a href="/conf/circles">circles</a> 10 11 @menu 11 12 </menu> 12 13 13 14 <div class="panel"> 14 15 @panel 15 16 </div>
Modified view/docskel.tpl from [cb4a31dcc6] to [3229efd171].
1 1 <!doctype html> 2 2 <html> 3 3 <head> 4 4 <title>@instance :: @title</title> 5 - <link rel="stylesheet" href="/s/style.css"> 5 + <link rel="stylesheet" type="text/css" href="/s/style.css"> 6 + <script type="text/javascript" src="/s/live.js" async></script> 6 7 </head> 7 - <body class="@class"> 8 + <body class="@class"@attr> 8 9 <header><div> 9 10 <h1>@title</h1> 10 11 <nav> 11 12 <a href="/instance">instance</a> 12 13 @navlinks 13 14 </nav> 14 15 </div></header> 15 16 <main> 16 17 @body 17 18 </main> 18 19 </body> 19 20 </html>
Added view/media.tpl version [5a68c18a8e].
1 +<menu> 2 + <a href="/user/@:xid/media">new uploads</a> 3 + @folders 4 +</menu> 5 + 6 +<div name="gallery"> 7 + @images 8 +</div> 9 + 10 +<div name="files"> 11 + @files 12 +</div>
Modified view/profile.tpl from [cfeb837b05] to [694fa2eec6].
11 11 <tr><th>following</th> <td>@nfollows</td></tr> 12 12 <tr><th>followers</th> <td>@nfollowers</td></tr> 13 13 <tr><th>mutuals</th> <td>@nmutuals</td></tr> 14 14 <tr><th>@timephrase</th> <td>@tweetday</td></tr> 15 15 </table> 16 16 <form class="actions"> 17 17 <a class="button" href="/@:xid">posts</a> 18 + <a class="button" href="/@:xid/arc">archive</a> 18 19 <a class="button" href="/@:xid/media">media</a> 19 20 <a class="button" href="/@:xid/social">associates</a> 20 21 <hr> 21 22 @auxbtn 22 23 </form> 23 24 </div>
Added view/tweet-mini.tpl version [4c5364412a].
1 +<div class="post-mini"> 2 + <span class="permalink">[<a href="@permalink">@when</a>]</span> 3 + <span class="username">‹<a href="/@:acctlink">@nym</a>›</span> <span class="content">@text</span> 4 +</div>
Modified view/tweet.tpl from [806c88c01c] to [e117899db4].
1 -<div class="post"> 1 +<div class="post"@attr> 2 2 <div class="avatar"><img src="@:avatar"></div> 3 3 <a class="username" href="/@:acctlink">@nym</a> 4 4 <div class="content"> 5 5 <div class="subject">@!subject</div> 6 6 <div class="text">@text</div> 7 7 </div> 8 8 <a class="permalink" href="@permalink">@when</a> 9 9 </div>