parsav  Check-in [9cc5d48cd8]

Overview
Comment:notifications work now
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: 9cc5d48cd80df268d79a98772788050764957de51848144865aa4be80ff72455
User & Date: lexi on 2021-01-06 22:18:59
Other Links: manifest | tags
Context
2021-01-06
22:31
unfuck last commit check-in: 8d8ab01573 user: lexi tags: trunk
22:18
notifications work now check-in: 9cc5d48cd8 user: lexi tags: trunk
2021-01-05
22:48
adjusted bell check-in: 0faa879398 user: lexi tags: trunk
Changes

Modified backend/pgsql.t from [7305c1c258] to [80ca2205e4].

693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
...
847
848
849
850
851
852
853

854
855
856
857
858
859
860
....
1416
1417
1418
1419
1420
1421
1422


























1423
1424
1425
1426
1427
1428
1429
			return buf
		end
	end;
}

local sqlvars = {}
for i, n in ipairs(lib.store.noticetype.members) do
	sqlvars['notice:' .. n] = lib.store.noticetype[n]
end

for i, n in ipairs(lib.store.relation.members) do
	sqlvars['rel:' .. n] = lib.store.relation.idvmap[n]
end

local con = symbol(&lib.pq.PGconn)
................................................................................
		else p.ptr.chgcount = r:int(uint32,row,11)
	end 
	p.ptr.accent = r:int(int16,row,12)
	p.ptr.rtdby = r:int(uint64,row,13)
	p.ptr.rtact = r:int(uint64,row,14)
	p.ptr.likes = r:int(uint32,row,15)
	p.ptr.rts = r:int(uint32,row,16)

	p.ptr.localpost = r:bool(row,0)

	return p
end
local terra row_to_actor(r: &pqr, row: intptr): lib.mem.ptr(lib.store.actor)
	var a: lib.mem.ptr(lib.store.actor)
	var av: rawstring, avlen: intptr
................................................................................
		return r
	end];

	actor_purge_uid = [terra(
		src: &lib.store.source,
		uid: uint64
	) queries.actor_purge_uid.exec(src,uid) end];



























	auth_enum_uid = [terra(
		src: &lib.store.source,
		uid: uint64
	): lib.mem.ptr(lib.mem.ptr(lib.store.auth))
		var r = queries.auth_enum_uid.exec(src,uid)
		if r.sz == 0 then return [lib.mem.ptr(lib.mem.ptr(lib.store.auth))].null() end







|







 







>







 







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
...
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
....
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
			return buf
		end
	end;
}

local sqlvars = {}
for i, n in ipairs(lib.store.noticetype.members) do
	sqlvars['notice:' .. n] = lib.store.noticetype[n]:asvalue()
end

for i, n in ipairs(lib.store.relation.members) do
	sqlvars['rel:' .. n] = lib.store.relation.idvmap[n]
end

local con = symbol(&lib.pq.PGconn)
................................................................................
		else p.ptr.chgcount = r:int(uint32,row,11)
	end 
	p.ptr.accent = r:int(int16,row,12)
	p.ptr.rtdby = r:int(uint64,row,13)
	p.ptr.rtact = r:int(uint64,row,14)
	p.ptr.likes = r:int(uint32,row,15)
	p.ptr.rts = r:int(uint32,row,16)
	p.ptr.isreply = r:bool(row,17)
	p.ptr.localpost = r:bool(row,0)

	return p
end
local terra row_to_actor(r: &pqr, row: intptr): lib.mem.ptr(lib.store.actor)
	var a: lib.mem.ptr(lib.store.actor)
	var av: rawstring, avlen: intptr
................................................................................
		return r
	end];

	actor_purge_uid = [terra(
		src: &lib.store.source,
		uid: uint64
	) queries.actor_purge_uid.exec(src,uid) end];

	actor_notice_enum = [terra(
		src: &lib.store.source,
		uid: uint64
	): lib.mem.ptr(lib.store.notice)
		var r = queries.actor_notice_enum.exec(src,uid);
		if r.sz == 0 then return [lib.mem.ptr(lib.store.notice)].null() end
		defer r:free()

		var notes = lib.mem.heapa(lib.store.notice, r.sz)
		for i=0, r.sz do
			var n = notes.ptr + i
			n.kind = r:int(uint16,i,0)
			n.when = r:int(int64,i,1)
			n.who = r:int(int64,i,2)
			n.what = r:int(uint64,i,3)
			if n.kind == lib.store.noticetype.reply then
				n.reply = r:int(uint64,i,4)
			elseif n.kind == lib.store.noticetype.react then
				var react = r:_string(i,5)
				lib.str.ncpy(n.reaction, react.ptr, lib.math.smallest(react.ct,[(`n.reaction).tree.type.N]))
			end
		end

		return notes
	end];

	auth_enum_uid = [terra(
		src: &lib.store.source,
		uid: uint64
	): lib.mem.ptr(lib.mem.ptr(lib.store.auth))
		var r = queries.auth_enum_uid.exec(src,uid)
		if r.sz == 0 then return [lib.mem.ptr(lib.mem.ptr(lib.store.auth))].null() end

Modified backend/schema/pgsql-views.sql from [54b7851d27] to [d567971842].

70
71
72
73
74
75
76

77
78
79
80
81

82
83
84
85
86
87
88
89
90
91
92
93
94

95
96
97
98
99
100
101
	body		text,
	posted		bigint,
	discovered	bigint,
	edited		bigint,
	parent		bigint,
	convoheaduri text,
	chgcount	integer,

	accent		smallint,
	rtdby		bigint, -- note that these must be 0 if the record
	rtid		bigint, -- in question does not represent an RT!
	n_likes		integer,
	n_rts		integer

);

create or replace function
pg_temp.parsavpg_translate_post(parsav_posts,bigint,bigint)
returns pg_temp.parsavpg_intern_post as $$
	select a.origin is null,
		($1).id,     ($1).author,
		($1).subject,($1).acl,         ($1).body,
		($1).posted, ($1).discovered,  ($1).edited,
		($1).parent, ($1).convoheaduri,($1).chgcount,
		coalesce(c.value, -1)::smallint,
		$2 as rtdby, $3 as rtid,
		re.likes, re.rts

	from parsav_actors as a 
		left join parsav_actor_conf_ints as c
		          on c.key = 'ui-accent' and
		             c.uid = a.id
		left join pg_temp.parsavpg_post_react_counts as re
		          on re.post = ($1).id
	where a.id = ($1).author







>




|
>












|
>







70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
	body		text,
	posted		bigint,
	discovered	bigint,
	edited		bigint,
	parent		bigint,
	convoheaduri text,
	chgcount	integer,
-- ephemeral
	accent		smallint,
	rtdby		bigint, -- note that these must be 0 if the record
	rtid		bigint, -- in question does not represent an RT!
	n_likes		integer,
	n_rts		integer,
	isreply		bool -- true if parent in (table posts); saves us a bunch of queries
);

create or replace function
pg_temp.parsavpg_translate_post(parsav_posts,bigint,bigint)
returns pg_temp.parsavpg_intern_post as $$
	select a.origin is null,
		($1).id,     ($1).author,
		($1).subject,($1).acl,         ($1).body,
		($1).posted, ($1).discovered,  ($1).edited,
		($1).parent, ($1).convoheaduri,($1).chgcount,
		coalesce(c.value, -1)::smallint,
		$2 as rtdby, $3 as rtid,
		re.likes, re.rts,
		($1).parent in (select id from parsav_posts)
	from parsav_actors as a 
		left join parsav_actor_conf_ints as c
		          on c.key = 'ui-accent' and
		             c.uid = a.id
		left join pg_temp.parsavpg_post_react_counts as re
		          on re.post = ($1).id
	where a.id = ($1).author

Modified config.lua from [5768880cb8] to [eb323f3b45].

56
57
58
59
60
61
62







63
64
65
66
67
68
69
		{'default-avatar.webp', 'image/webp'}; -- needs inkscape-exclusive svg features
		{'bell.svg', 'image/svg+xml'};
		{'heart.webp', 'image/webp'};
		{'retweet.webp', 'image/webp'};
		{'padlock.svg', 'image/svg+xml'};
		{'warn.svg', 'image/svg+xml'};
		{'query.webp', 'image/webp'};







	};
	default_ui_accent = tonumber(default('parsav_ui_default_accent',323));
}
if os.getenv('parsav_let_me_be_an_idiot') == "i know what i'm doing" then
	conf.braingeniousmode = true -- SOUND GENERAL QUARTERS
end
if u.ping '.fslckout' or u.ping '_FOSSIL_' then







>
>
>
>
>
>
>







56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
		{'default-avatar.webp', 'image/webp'}; -- needs inkscape-exclusive svg features
		{'bell.svg', 'image/svg+xml'};
		{'heart.webp', 'image/webp'};
		{'retweet.webp', 'image/webp'};
		{'padlock.svg', 'image/svg+xml'};
		{'warn.svg', 'image/svg+xml'};
		{'query.webp', 'image/webp'};
		{'reply.webp', 'image/webp'};
		-- keep in mind before you add anything to this list: these are not
		-- just files parsav can access, they are files that are *kept in
		-- memory* for fast access the entire time parsav is running, and
		-- which need to be loaded into memory before the program can even
		-- start. it's imperative to keep these as small and few in number
		-- as is realistically possible.
	};
	default_ui_accent = tonumber(default('parsav_ui_default_accent',323));
}
if os.getenv('parsav_let_me_be_an_idiot') == "i know what i'm doing" then
	conf.braingeniousmode = true -- SOUND GENERAL QUARTERS
end
if u.ping '.fslckout' or u.ping '_FOSSIL_' then

Modified makefile from [5c5158676d] to [0e9ec3efb8].

1
2
3
4
5
6
7
8
9
10
11
dl = git
dbg-flags = $(if $(dbg),-g)

images = static/default-avatar.webp static/query.webp static/heart.webp static/retweet.webp
#$(addsuffix .webp, $(basename $(wildcard static/*.svg)))
styles = $(addsuffix .css, $(basename $(wildcard static/*.scss)))

parsav parsavd: parsav.t config.lua pkgdata.lua $(images) $(styles)
	terra $(dbg-flags) $<
parsav.o parsavd.o: parsav.t config.lua pkgdata.lua $(images) $(styles)
	env parsav_link=no terra $(dbg-flags) $<



|







1
2
3
4
5
6
7
8
9
10
11
dl = git
dbg-flags = $(if $(dbg),-g)

images = static/default-avatar.webp static/query.webp static/heart.webp static/retweet.webp static/reply.webp
#$(addsuffix .webp, $(basename $(wildcard static/*.svg)))
styles = $(addsuffix .css, $(basename $(wildcard static/*.scss)))

parsav parsavd: parsav.t config.lua pkgdata.lua $(images) $(styles)
	terra $(dbg-flags) $<
parsav.o parsavd.o: parsav.t config.lua pkgdata.lua $(images) $(styles)
	env parsav_link=no terra $(dbg-flags) $<

Modified parsav.t from [5b9672ebb4] to [8bdefbaeb6].

238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
...
433
434
435
436
437
438
439

440
441
442
443
444
445
446
	local ty = uint8
	if #tbl >= 2^32 then ty = uint64 -- hey, can't be too safe
	elseif #tbl >= 2^16 then ty = uint32
	elseif #tbl >= 2^8 then ty = uint16 end
	local o = { t = ty, members = tbl }
	local strings = {}
	for i, name in ipairs(tbl) do
		o[name] = i - 1
		strings[i] = `[lib.mem.ref(int8)]{ptr=[name], ct=[#name]}
	end
	o._str = terra(val: ty)
		var l = array([strings])
		return l[val]
	end
	return o
................................................................................
	'render:login';
	'render:profile';
	'render:compose';
	'render:tweet';
	'render:tweet-page';
	'render:user-page';
	'render:timeline';


	'render:docpage';

	'render:conf:profile';
	'render:conf:sec';
	'render:conf:users';
	'render:conf';







|







 







>







238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
...
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
	local ty = uint8
	if #tbl >= 2^32 then ty = uint64 -- hey, can't be too safe
	elseif #tbl >= 2^16 then ty = uint32
	elseif #tbl >= 2^8 then ty = uint16 end
	local o = { t = ty, members = tbl }
	local strings = {}
	for i, name in ipairs(tbl) do
		o[name] = `[ty]([i - 1])
		strings[i] = `[lib.mem.ref(int8)]{ptr=[name], ct=[#name]}
	end
	o._str = terra(val: ty)
		var l = array([strings])
		return l[val]
	end
	return o
................................................................................
	'render:login';
	'render:profile';
	'render:compose';
	'render:tweet';
	'render:tweet-page';
	'render:user-page';
	'render:timeline';
	'render:notices';

	'render:docpage';

	'render:conf:profile';
	'render:conf:sec';
	'render:conf:users';
	'render:conf';

Added render/notices.t version [99b348e685].

































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
-- vim: ft=terra
local pstr = lib.mem.ptr(int8)
local P = lib.str.plit
local terra cs(s: rawstring)
	return pstr { ptr = s, ct = lib.str.sz(s) }
end

local terra 
render_notices(
	co: &lib.srv.convo
): {}
	var notes = co.srv:actor_notice_enum(co.who.id)
	
	if notes.ct == 0 then
		co:complain(200,'no news is good news',"you don't have any notices to review")
		return
	end
	defer notes:free()

	var pg: lib.str.acc pg:init(512) defer pg:free()
	var pflink: lib.str.acc pflink:init(64)
	var body: lib.str.acc body:init(256)
	var latest: lib.store.timepoint = 0
	for i=0,notes.ct do
		if notes(i).when > latest then latest = notes(i).when end
		var who = co.srv:actor_fetch_uid(notes(i).who) defer who:free()
		if not who then lib.bail('schema integrity violation: nonexistent actor referenced in notification, this is almost certainly an SQL error or bug in the backend implementation') end
		pflink:cue(lib.str.sz(who(0).xid) + 4)
		if who(0).origin == 0 then pflink:lpush('/')
		                      else pflink:lpush('/@') end
		pflink:push(who(0).xid,0)
		var n = data.view.notice {
			avatar = cs(who(0).avatar);
			nym = lib.render.nym(who.ptr,0,nil,true);
			pflink = pstr{ptr = pflink.buf; ct = pflink.sz};
		}
		var notweet = true
		var what = co.srv:post_fetch(notes(i).what) defer what:free()
		switch notes(i).kind do
			case lib.store.noticetype.rt then
				n.kind = P'rt'
				n.act = P'retweeted your post'
			end
			case lib.store.noticetype.like then
				n.kind = P'like'
				n.act = P'likes your post'
			end
			case lib.store.noticetype.reply then
				n.kind = P'reply'
				n.act = P'replied to your post'
				notweet = false
			end
		else goto skip end
		do var idbuf: int8[lib.math.shorthand.maxlen]
			var idlen = lib.math.shorthand.gen(notes(i).what, idbuf)
			var b = lib.smackdown.html(pstr {ptr=what(0).body,ct=0},true) defer b:free()
			body:lpush(' <a class="quote" href="/post/'):push(&idbuf[0],idlen):lpush('">'):ppush(b):lpush('</a>')
		end
		if not notweet then
			var reply = co.srv:post_fetch(notes(i).reply)
				lib.render.tweet(co,reply.ptr,&body)
			reply:free()
		end
		n.ref = pstr {ptr = body.buf, ct = body.sz}

		n:append(&pg)
		::skip:: n.nym:free()
		         pflink:reset()
				 body:reset()
	end
	pflink:free()
	pg:lpush('<form method="post"><button name="act" value="clear">clear all notices</button></form>')
	co:livepage([lib.srv.convo.page] {
		title = P'notices', class = P'notices';
		body = pstr {ptr = pg.buf, ct = pg.sz};
		cache = false;
	}, latest)
end

return render_notices

Modified render/profile.t from [edefc21455] to [3525ca58bc].

34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
	var stats = co.srv:actor_stats(actor.id)
		var sn_posts     = cs(lib.math.decstr_friendly(stats.posts, &strfbuf[ [strfbuf.type.N - 1] ]))
		var sn_follows   = cs(lib.math.decstr_friendly(stats.follows, sn_posts.ptr - 1))
		var sn_followers = cs(lib.math.decstr_friendly(stats.followers, sn_follows.ptr - 1))
		var sn_mutuals   = cs(lib.math.decstr_friendly(stats.mutuals, sn_followers.ptr - 1))
	var bio = lib.str.plit '<em style="opacity:0.6">tall, dark, and mysterious</em>'
	if actor.bio ~= nil then
		bio = lib.smackdown.html(cs(actor.bio))
	end
	var fullname = lib.render.nym(actor,0,nil,false) defer fullname:free()
	var comments: lib.str.acc comments:init(64)

	if co.srv.cfg.master == actor.id then
		var foundertxt = lib.str.plit 'founder'
		if co.srv.cfg.ui_cue_founder:ref() then







|







34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
	var stats = co.srv:actor_stats(actor.id)
		var sn_posts     = cs(lib.math.decstr_friendly(stats.posts, &strfbuf[ [strfbuf.type.N - 1] ]))
		var sn_follows   = cs(lib.math.decstr_friendly(stats.follows, sn_posts.ptr - 1))
		var sn_followers = cs(lib.math.decstr_friendly(stats.followers, sn_follows.ptr - 1))
		var sn_mutuals   = cs(lib.math.decstr_friendly(stats.mutuals, sn_followers.ptr - 1))
	var bio = lib.str.plit '<em style="opacity:0.6">tall, dark, and mysterious</em>'
	if actor.bio ~= nil then
		bio = lib.smackdown.html(cs(actor.bio),false)
	end
	var fullname = lib.render.nym(actor,0,nil,false) defer fullname:free()
	var comments: lib.str.acc comments:init(64)

	if co.srv.cfg.master == actor.id then
		var foundertxt = lib.str.plit 'founder'
		if co.srv.cfg.ui_cue_founder:ref() then

Modified render/tweet.t from [8bee6c6ab8] to [0700e85db2].

37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
..
54
55
56
57
58
59
60

61














62
63
64
65
66
67
68
..
85
86
87
88
89
90
91

92
93
94
95
96
97
98

99
100
101

102
103
104
105
106
	::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, &timestr[0])
	for i=0,26 do if timestr[i] == @'\n' then timestr[i] = 0 break end end -- 🙄

	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)
	var permalink: lib.str.acc permalink:compose('/post/',{idbuf,idlen})
	var fullname = lib.render.nym(author,0,nil, false) defer fullname:free()
	var tpl = data.view.tweet {
................................................................................
		nym = fullname;
		when = cs(&timestr[0]);
		avatar = cs(author.avatar);
		acctlink = cs(author.xid);
		permalink = permalink:finalize();
		attr = pstr{'',0};
		stats = pstr{'',0};

	}














	if p.rts + p.likes > 0 then
		var s: lib.str.acc s:init(128)
		s:lpush('<div class="stats">')
		if p.rts   > 0 then s:lpush('<div class="rt">'  ):dpush(p.rts  ):lpush('</div>') end
		if p.likes > 0 then s:lpush('<div class="like">'):dpush(p.likes):lpush('</div>') end
		s:lpush('</div>')
		tpl.stats = s:finalize()
................................................................................

	defer tpl.permalink:free()
	if acc ~= nil then
		if retweeter ~= nil then push_promo_header(co, acc, retweeter, p.rtact) end
		tpl:append(acc)
		if retweeter ~= nil then acc:lpush('</div>') end
		if p.rts + p.likes > 0 then tpl.stats:free() end

		return [lib.mem.ptr(int8)]{ptr=nil,ct=0}
	end

	if retweeter ~= nil then
		var rta: lib.str.acc rta:init(512)
		push_promo_header(co, &rta, retweeter, p.rtact)
		tpl:append(&rta) rta:lpush('</div>')

		return rta:finalize()
	else
		var txt = tpl:tostr()

		if p.rts + p.likes > 0 then tpl.stats:free() end
		return txt
	end
end
return render_tweet







|







 







>

>
>
>
>
>
>
>
>
>
>
>
>
>
>







 







>







>



>





37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
..
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
...
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
	::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, &timestr[0])
	for i=0,26 do if timestr[i] == @'\n' then timestr[i] = 0 break end end -- 🙄

	var bhtml = lib.smackdown.html([lib.mem.ptr(int8)] {ptr=p.body,ct=0},false)
	defer bhtml:free()

	var idbuf: int8[lib.math.shorthand.maxlen]
	var idlen = lib.math.shorthand.gen(p.id, idbuf)
	var permalink: lib.str.acc permalink:compose('/post/',{idbuf,idlen})
	var fullname = lib.render.nym(author,0,nil, false) defer fullname:free()
	var tpl = data.view.tweet {
................................................................................
		nym = fullname;
		when = cs(&timestr[0]);
		avatar = cs(author.avatar);
		acctlink = cs(author.xid);
		permalink = permalink:finalize();
		attr = pstr{'',0};
		stats = pstr{'',0};
		extra = pstr{'',0};
	}
	if p.isreply then
		var parent = co.srv:post_fetch(p.parent) defer parent:free()
		if not parent then
			lib.bail('schema integrity violation - could not match post to parent')
		end
		var pauth = co.srv:actor_fetch_uid(parent(0).author) defer pauth:free()
		var pidbuf: int8[lib.math.shorthand.maxlen]
		var pidlen = lib.math.shorthand.gen(p.parent, pidbuf)
		var pa: lib.str.acc pa:init(128)
		pa:lpush('<small>in reply to <a class="username" href="'):push(&pidbuf[0],pidlen):lpush('">')
		lib.render.nym(pauth.ptr,0,&pa,true)
		pa:lpush('</a></small>')
		tpl.extra = pa:finalize()
	end
	if p.rts + p.likes > 0 then
		var s: lib.str.acc s:init(128)
		s:lpush('<div class="stats">')
		if p.rts   > 0 then s:lpush('<div class="rt">'  ):dpush(p.rts  ):lpush('</div>') end
		if p.likes > 0 then s:lpush('<div class="like">'):dpush(p.likes):lpush('</div>') end
		s:lpush('</div>')
		tpl.stats = s:finalize()
................................................................................

	defer tpl.permalink:free()
	if acc ~= nil then
		if retweeter ~= nil then push_promo_header(co, acc, retweeter, p.rtact) end
		tpl:append(acc)
		if retweeter ~= nil then acc:lpush('</div>') end
		if p.rts + p.likes > 0 then tpl.stats:free() end
		if tpl.extra.ct > 0 then tpl.extra:free() end
		return [lib.mem.ptr(int8)]{ptr=nil,ct=0}
	end

	if retweeter ~= nil then
		var rta: lib.str.acc rta:init(512)
		push_promo_header(co, &rta, retweeter, p.rtact)
		tpl:append(&rta) rta:lpush('</div>')
		if tpl.extra.ct > 0 then tpl.extra:free() end
		return rta:finalize()
	else
		var txt = tpl:tostr()
		if tpl.extra.ct > 0 then tpl.extra:free() end
		if p.rts + p.likes > 0 then tpl.stats:free() end
		return txt
	end
end
return render_tweet

Modified route.t from [cc5cf170ae] to [e8c2143a0c].

164
165
166
167
168
169
170

171
172
173
174
175
176
177
...
380
381
382
383
384
385
386
















387
388
389
390
391
392
393
...
442
443
444
445
446
447
448
449
450



451
452

453
454
455
456
457
458
459
460
461
462
463
464
			return
		end
		if subj == nil then subj = '' end

		var p = lib.store.post {
			author = co.who.id, acl = acl;
			body = text, subject = subj;

		}
		var newid = p:publish(co.srv)

		var idbuf: int8[lib.math.shorthand.maxlen]
		var idlen = lib.math.shorthand.gen(newid, idbuf)
		var redirto: lib.str.acc redirto:compose('/post/',{idbuf,idlen}) defer redirto:free()
		co:reroute(redirto.buf)
................................................................................
		end
	end
	lib.render.conf(co,path,msg)
	do return end

	::nopriv:: co:complain(403,'insufficient privileges','you do not have the necessary powers to perform this action')
end

















do local branches = quote end
	local filename, flen = symbol(&int8), symbol(intptr)
	local page = symbol(lib.http.page)
	local send = label()
	local storage = data.stmap
	for i,e in ipairs(config.embeds) do local id,mime = e[1],e[2]
................................................................................
	elseif uri.ptr[1] == @'@' then
		http.actor_profile_xid(co, uri, meth)
	elseif uri.ptr[1] == @'s' and uri.ptr[2] == @'/' and uri.ct > 3 then
		if not meth_get(meth) then goto wrongmeth end
		if not http.static_content(co, uri.ptr + 3, uri.ct - 3) then goto notfound end
	elseif lib.str.ncmp('/avi/', uri.ptr, 5) == 0 then
		http.local_avatar(co, [lib.mem.ptr(int8)] {ptr = uri.ptr + 5, ct = uri.ct - 5})
	elseif lib.str.ncmp('/compose', uri.ptr, lib.math.biggest(uri.ct,8)) == 0 then
		if co.aid == 0 then co:reroute('/login') return end



		http.post_compose(co,meth)
	elseif lib.str.ncmp('/login', uri.ptr, lib.math.biggest(uri.ct,6)) == 0 then

		if co.aid == 0
			then http.login_form(co, meth)
			else co:reroute('/')
		end
	elseif lib.str.ncmp('/logout', uri.ptr, lib.math.biggest(uri.ct,7)) == 0 then
		if co.aid == 0
			then goto notfound
			else co:reroute_cookie('/','auth=; Path=/')
		end
	else -- hierarchical routes
		var path = lib.http.hier(uri) defer path:free()
		if path.ct > 1 and path(0):cmp(lib.str.lit('user')) then







>







 







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







 







|

>
>
>

<
>




|







164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
...
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
...
459
460
461
462
463
464
465
466
467
468
469
470
471

472
473
474
475
476
477
478
479
480
481
482
483
484
			return
		end
		if subj == nil then subj = '' end

		var p = lib.store.post {
			author = co.who.id, acl = acl;
			body = text, subject = subj;
			parent = 0;
		}
		var newid = p:publish(co.srv)

		var idbuf: int8[lib.math.shorthand.maxlen]
		var idlen = lib.math.shorthand.gen(newid, idbuf)
		var redirto: lib.str.acc redirto:compose('/post/',{idbuf,idlen}) defer redirto:free()
		co:reroute(redirto.buf)
................................................................................
		end
	end
	lib.render.conf(co,path,msg)
	do return end

	::nopriv:: co:complain(403,'insufficient privileges','you do not have the necessary powers to perform this action')
end

terra http.user_notices(co: &lib.srv.convo, meth: method.t)
	if meth == method.post then
		var act = co:ppostv('act')
		if act:cmp(lib.str.plit'clear') then
			co.srv:actor_conf_int_set(co.who.id, 'notice-clear-time', lib.osclock.time(nil))
			co:reroute('/')
			return
		else goto badop end
	end

	lib.render.notices(co)
	do return end

	::badop :: do co:complain(405, 'invalid operation', 'the operation you have attempted on this post is not meaningful') return end
end

do local branches = quote end
	local filename, flen = symbol(&int8), symbol(intptr)
	local page = symbol(lib.http.page)
	local send = label()
	local storage = data.stmap
	for i,e in ipairs(config.embeds) do local id,mime = e[1],e[2]
................................................................................
	elseif uri.ptr[1] == @'@' then
		http.actor_profile_xid(co, uri, meth)
	elseif uri.ptr[1] == @'s' and uri.ptr[2] == @'/' and uri.ct > 3 then
		if not meth_get(meth) then goto wrongmeth end
		if not http.static_content(co, uri.ptr + 3, uri.ct - 3) then goto notfound end
	elseif lib.str.ncmp('/avi/', uri.ptr, 5) == 0 then
		http.local_avatar(co, [lib.mem.ptr(int8)] {ptr = uri.ptr + 5, ct = uri.ct - 5})
	elseif uri:cmp(lib.str.plit '/notices') then
		if co.aid == 0 then co:reroute('/login') return end
		http.user_notices(co,meth)
	elseif uri:cmp(lib.str.plit '/compose') then
		if co.aid == 0 then co:reroute('/login') return end
		http.post_compose(co,meth)

	elseif uri:cmp(lib.str.plit '/login') then
		if co.aid == 0
			then http.login_form(co, meth)
			else co:reroute('/')
		end
	elseif uri:cmp(lib.str.plit '/logout') then
		if co.aid == 0
			then goto notfound
			else co:reroute_cookie('/','auth=; Path=/')
		end
	else -- hierarchical routes
		var path = lib.http.hier(uri) defer path:free()
		if path.ct > 1 and path(0):cmp(lib.str.lit('user')) then

Modified smackdown.t from [c51eb8a6b2] to [926ec15b69].

52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
local terra scanline_wordend(l: rawstring, max: intptr, n: rawstring, nc: intptr)
	var sl = scanline(l,max,n,nc)
	if sl == nil then return nil else sl = sl + nc end
	if sl >= l+max or isws(@sl) then return sl-nc end
	return nil
end

terra m.html(input: pstr)
	if input.ct == 0 then input.ct = lib.str.sz(input.ptr) end

	var md = lib.html.sanitize(input,false)

	var styled: lib.str.acc styled:init(md.ct)

	do var i = 0 while i < md.ct do







|







52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
local terra scanline_wordend(l: rawstring, max: intptr, n: rawstring, nc: intptr)
	var sl = scanline(l,max,n,nc)
	if sl == nil then return nil else sl = sl + nc end
	if sl >= l+max or isws(@sl) then return sl-nc end
	return nil
end

terra m.html(input: pstr, firstline: bool)
	if input.ct == 0 then input.ct = lib.str.sz(input.ptr) end

	var md = lib.html.sanitize(input,false)

	var styled: lib.str.acc styled:init(md.ct)

	do var i = 0 while i < md.ct do

Added static/reply.svg version [0a0b88e351].





























































































































































































































































































































































































































































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->

<svg
   xmlns:dc="http://purl.org/dc/elements/1.1/"
   xmlns:cc="http://creativecommons.org/ns#"
   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
   xmlns:svg="http://www.w3.org/2000/svg"
   xmlns="http://www.w3.org/2000/svg"
   xmlns:xlink="http://www.w3.org/1999/xlink"
   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
   width="20"
   height="20"
   viewBox="0 0 5.2916664 5.2916665"
   version="1.1"
   id="svg8"
   inkscape:version="0.92.4 (5da689c313, 2019-01-14)"
   sodipodi:docname="reply.svg">
  <defs
     id="defs2">
    <linearGradient
       inkscape:collect="always"
       id="linearGradient954">
      <stop
         style="stop-color:#ffffff;stop-opacity:1;"
         offset="0"
         id="stop950" />
      <stop
         style="stop-color:#ffffff;stop-opacity:0;"
         offset="1"
         id="stop952" />
    </linearGradient>
    <linearGradient
       inkscape:collect="always"
       id="linearGradient938">
      <stop
         style="stop-color:#d9fff6;stop-opacity:1;"
         offset="0"
         id="stop934" />
      <stop
         style="stop-color:#d9fff6;stop-opacity:0;"
         offset="1"
         id="stop936" />
    </linearGradient>
    <linearGradient
       inkscape:collect="always"
       id="linearGradient1403">
      <stop
         style="stop-color:#ccaaff;stop-opacity:1;"
         offset="0"
         id="stop1399" />
      <stop
         style="stop-color:#ccaaff;stop-opacity:0;"
         offset="1"
         id="stop1401" />
    </linearGradient>
    <linearGradient
       id="linearGradient1395"
       inkscape:collect="always">
      <stop
         id="stop1391"
         offset="0"
         style="stop-color:#ff1616;stop-opacity:1" />
      <stop
         id="stop1393"
         offset="1"
         style="stop-color:#ff1d1d;stop-opacity:0" />
    </linearGradient>
    <linearGradient
       inkscape:collect="always"
       id="linearGradient1383">
      <stop
         style="stop-color:#980000;stop-opacity:1;"
         offset="0"
         id="stop1379" />
      <stop
         style="stop-color:#980000;stop-opacity:0;"
         offset="1"
         id="stop1381" />
    </linearGradient>
    <linearGradient
       inkscape:collect="always"
       id="linearGradient832">
      <stop
         style="stop-color:#ffcfcf;stop-opacity:1;"
         offset="0"
         id="stop828" />
      <stop
         style="stop-color:#ffcfcf;stop-opacity:0;"
         offset="1"
         id="stop830" />
    </linearGradient>
    <radialGradient
       inkscape:collect="always"
       xlink:href="#linearGradient832"
       id="radialGradient834"
       cx="3.2286437"
       cy="286.62921"
       fx="3.2286437"
       fy="286.62921"
       r="1.0866126"
       gradientTransform="matrix(1.8608797,0.8147617,-0.38242057,0.87343168,106.71446,33.692223)"
       gradientUnits="userSpaceOnUse" />
    <radialGradient
       inkscape:collect="always"
       xlink:href="#linearGradient1383"
       id="radialGradient1385"
       cx="4.1787109"
       cy="286.89261"
       fx="4.1787109"
       fy="286.89261"
       r="1.2260786"
       gradientTransform="matrix(1.7016464,0,0,1.6348586,-2.9319775,-182.10895)"
       gradientUnits="userSpaceOnUse" />
    <radialGradient
       inkscape:collect="always"
       xlink:href="#linearGradient1395"
       id="radialGradient1389"
       gradientUnits="userSpaceOnUse"
       gradientTransform="matrix(0.66230313,-1.6430738,1.0154487,0.40931507,-290.06307,177.39489)"
       cx="4.02069"
       cy="287.79269"
       fx="4.02069"
       fy="287.79269"
       r="1.0866126" />
    <linearGradient
       inkscape:collect="always"
       xlink:href="#linearGradient1403"
       id="linearGradient1405"
       x1="8.3939333"
       y1="288.1091"
       x2="7.0158253"
       y2="287.32819"
       gradientUnits="userSpaceOnUse" />
    <linearGradient
       inkscape:collect="always"
       xlink:href="#linearGradient938"
       id="linearGradient940"
       x1="7.609839"
       y1="288.73215"
       x2="7.609839"
       y2="283.78305"
       gradientUnits="userSpaceOnUse" />
    <linearGradient
       inkscape:collect="always"
       xlink:href="#linearGradient954"
       id="linearGradient956"
       x1="3.0150654"
       y1="285.94464"
       x2="3.0150654"
       y2="282.40109"
       gradientUnits="userSpaceOnUse" />
    <linearGradient
       inkscape:collect="always"
       xlink:href="#linearGradient938"
       id="linearGradient960"
       gradientUnits="userSpaceOnUse"
       x1="7.609839"
       y1="288.73215"
       x2="7.609839"
       y2="283.78305" />
    <linearGradient
       inkscape:collect="always"
       xlink:href="#linearGradient954"
       id="linearGradient1138"
       gradientUnits="userSpaceOnUse"
       x1="3.0150654"
       y1="285.94464"
       x2="3.0150654"
       y2="284.62277" />
    <linearGradient
       inkscape:collect="always"
       xlink:href="#linearGradient938"
       id="linearGradient1150"
       gradientUnits="userSpaceOnUse"
       x1="7.609839"
       y1="288.73215"
       x2="7.609839"
       y2="283.78305" />
    <linearGradient
       inkscape:collect="always"
       xlink:href="#linearGradient938"
       id="linearGradient1152"
       gradientUnits="userSpaceOnUse"
       x1="7.609839"
       y1="288.73215"
       x2="7.609839"
       y2="283.78305"
       gradientTransform="translate(-3.1738256,6.8821903)" />
    <linearGradient
       inkscape:collect="always"
       xlink:href="#linearGradient954"
       id="linearGradient1154"
       gradientUnits="userSpaceOnUse"
       x1="3.0150654"
       y1="285.94464"
       x2="3.0150654"
       y2="282.40109"
       gradientTransform="translate(-3.1738256,6.8821903)" />
    <linearGradient
       inkscape:collect="always"
       xlink:href="#linearGradient954"
       id="linearGradient1156"
       gradientUnits="userSpaceOnUse"
       x1="3.0150654"
       y1="285.94464"
       x2="3.0150654"
       y2="284.62277" />
    <linearGradient
       inkscape:collect="always"
       xlink:href="#linearGradient938"
       id="linearGradient1170"
       gradientUnits="userSpaceOnUse"
       x1="7.609839"
       y1="288.73215"
       x2="7.609839"
       y2="283.78305"
       gradientTransform="matrix(0.93088299,0,0,0.93088299,0.40825643,19.623427)" />
    <linearGradient
       inkscape:collect="always"
       xlink:href="#linearGradient954"
       id="linearGradient1172"
       gradientUnits="userSpaceOnUse"
       x1="3.0150654"
       y1="285.94464"
       x2="3.0150654"
       y2="282.40109"
       gradientTransform="matrix(0.93088299,0,0,0.93088299,0.40825643,19.623427)" />
    <linearGradient
       inkscape:collect="always"
       xlink:href="#linearGradient954"
       id="linearGradient1174"
       gradientUnits="userSpaceOnUse"
       x1="3.0150654"
       y1="285.94464"
       x2="3.0150654"
       y2="284.62277" />
  </defs>
  <sodipodi:namedview
     id="base"
     pagecolor="#181818"
     bordercolor="#666666"
     borderopacity="1.0"
     inkscape:pageopacity="0"
     inkscape:pageshadow="2"
     inkscape:zoom="3.959798"
     inkscape:cx="0.97863268"
     inkscape:cy="46.024492"
     inkscape:document-units="mm"
     inkscape:current-layer="layer1"
     showgrid="false"
     units="px"
     inkscape:window-width="1920"
     inkscape:window-height="1042"
     inkscape:window-x="0"
     inkscape:window-y="38"
     inkscape:window-maximized="0"
     showguides="false"
     fit-margin-top="0"
     fit-margin-left="0"
     fit-margin-right="0"
     fit-margin-bottom="0" />
  <metadata
     id="metadata5">
    <rdf:RDF>
      <cc:Work
         rdf:about="">
        <dc:format>image/svg+xml</dc:format>
        <dc:type
           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
        <dc:title></dc:title>
      </cc:Work>
    </rdf:RDF>
  </metadata>
  <g
     inkscape:label="Layer 1"
     inkscape:groupmode="layer"
     id="layer1"
     transform="translate(-2.6134661,-283.36966)">
    <g
       id="g1168"
       transform="matrix(0.92817904,-0.24870482,0.24870482,0.92817904,-70.834504,21.905842)"
       style="stroke-width:1.04066753">
      <path
         d="m 3.1152344,284.35547 c -0.1591337,0 -0.2949219,0.13579 -0.2949219,0.29492 v 2.9668 c 0,0.15913 0.1357882,0.29492 0.2949219,0.29492 h 4.3945312 c 0.1591337,0 0.2949219,-0.13579 0.2949219,-0.29492 v -2.9668 c 0,-0.15913 -0.1357882,-0.29492 -0.2949219,-0.29492 z"
         id="rect958"
         style="opacity:0.25200004;vector-effect:none;fill:url(#linearGradient960);fill-opacity:1;stroke:none;stroke-width:0.18445945;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
         inkscape:original="M 3.1152344 284.55078 C 3.0598344 284.55078 3.015625 284.59499 3.015625 284.65039 L 3.015625 287.61719 C 3.015625 287.67259 3.0598344 287.7168 3.1152344 287.7168 L 7.5097656 287.7168 C 7.5651656 287.7168 7.609375 287.67259 7.609375 287.61719 L 7.609375 284.65039 C 7.609375 284.59499 7.5651656 284.55078 7.5097656 284.55078 L 3.1152344 284.55078 z "
         inkscape:radius="0.19498466"
         sodipodi:type="inkscape:offset"
         transform="matrix(0.93088299,0,0,0.93088299,0.40825643,19.623427)" />
      <rect
         rx="0.093088299"
         y="284.50693"
         x="3.2149298"
         height="2.9467573"
         width="4.2771964"
         id="rect932"
         style="opacity:1;vector-effect:none;fill:url(#linearGradient1170);fill-opacity:1;stroke:none;stroke-width:0.17171016;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
      <path
         d="m 3.015625,284.38867 a 0.16537863,0.16537863 0 0 0 -0.089844,0.30469 l 2.296875,1.50391 a 0.16537863,0.16537863 0 0 0 0.1796876,0 l 2.296875,-1.50391 a 0.16537863,0.16537863 0 0 0 -0.089844,-0.30469 z"
         id="path1136"
         style="opacity:0.13699999;fill:url(#linearGradient1174);fill-opacity:1;stroke:none;stroke-width:0.18445943;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
         inkscape:original="M 3.015625 284.55469 L 5.3125 286.05859 L 7.609375 284.55469 L 3.015625 284.55469 z "
         inkscape:radius="0.16536209"
         sodipodi:type="inkscape:offset"
         transform="matrix(0.93088299,0,0,0.93088299,0.40825643,19.623427)" />
      <path
         sodipodi:nodetypes="cccc"
         inkscape:connector-curvature="0"
         d="M 7.4105013,284.507 5.3535277,285.91094 3.3022018,284.5076 Z"
         style="fill:url(#linearGradient1172);fill-opacity:1;stroke:none;stroke-width:0.17171013;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
         id="path929" />
    </g>
    <g
       id="g1162">
      <path
         transform="translate(-3.1738256,6.8821903)"
         sodipodi:type="inkscape:offset"
         inkscape:radius="0.19498466"
         inkscape:original="M 3.1152344 284.55078 C 3.0598344 284.55078 3.015625 284.59499 3.015625 284.65039 L 3.015625 287.61719 C 3.015625 287.67259 3.0598344 287.7168 3.1152344 287.7168 L 7.5097656 287.7168 C 7.5651656 287.7168 7.609375 287.67259 7.609375 287.61719 L 7.609375 284.65039 C 7.609375 284.59499 7.5651656 284.55078 7.5097656 284.55078 L 3.1152344 284.55078 z "
         style="opacity:0.25200004;vector-effect:none;fill:url(#linearGradient1150);fill-opacity:1;stroke:none;stroke-width:0.16500001;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
         id="path1142"
         d="m 3.1152344,284.35547 c -0.1591337,0 -0.2949219,0.13579 -0.2949219,0.29492 v 2.9668 c 0,0.15913 0.1357882,0.29492 0.2949219,0.29492 h 4.3945312 c 0.1591337,0 0.2949219,-0.13579 0.2949219,-0.29492 v -2.9668 c 0,-0.15913 -0.1357882,-0.29492 -0.2949219,-0.29492 z" />
      <rect
         style="opacity:1;vector-effect:none;fill:url(#linearGradient1152);fill-opacity:1;stroke:none;stroke-width:0.16500001;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
         id="rect1144"
         width="4.5947733"
         height="3.1655507"
         x="-0.15875995"
         y="291.43301"
         rx="0.1" />
      <path
         id="path1146"
         style="fill:url(#linearGradient1154);fill-opacity:1;stroke:none;stroke-width:0.16499999;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
         d="m 4.4360131,291.43669 -2.2973866,1.50456 -2.29738662,-1.50456 z"
         inkscape:connector-curvature="0"
         sodipodi:nodetypes="cccc" />
      <path
         transform="translate(-3.1738256,6.8821903)"
         sodipodi:type="inkscape:offset"
         inkscape:radius="0.16536209"
         inkscape:original="M 3.015625 284.55469 L 5.3125 286.05859 L 7.609375 284.55469 L 3.015625 284.55469 z "
         style="opacity:0.13699999;fill:url(#linearGradient1156);fill-opacity:1;stroke:none;stroke-width:0.16499999;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
         id="path1148"
         d="m 3.015625,284.38867 a 0.16537863,0.16537863 0 0 0 -0.089844,0.30469 l 2.296875,1.50391 a 0.16537863,0.16537863 0 0 0 0.1796876,0 l 2.296875,-1.50391 a 0.16537863,0.16537863 0 0 0 -0.089844,-0.30469 z" />
    </g>
  </g>
</svg>

Modified static/style.scss from [dc435bcc7e] to [fa60ad7c82].

224
225
226
227
228
229
230

231

232
233
234
235
236
237
238
239
...
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
...
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
...
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
...
949
950
951
952
953
954
955











































					transform: scale(1.2);
				}
			}
			> a[href].bell {
				content: url(/s/bell.svg);
				height: 2em;
				padding: 0.125in 0.10in;

				&:hover {

					filter: drop-shadow(0 0 10px tone(-5%));
				}
			}
		}
	}
}

main {
................................................................................
	padding: 0.4in;
	> .msg {
		text-align: center;
		padding: 0.3in;
	}
	> .msg:first-child { padding-top: 0; }
	> .user {
		width: min-content; margin: auto;
		background: tone(-20%,-0.3);
		border: 1px solid black;
		color: tone(-50%);
		padding: 0.1in;
		> img { width: 1in; height: 1in; border: 1px solid black; }
		> .name { @extend %serif; text-align: center; font-size: 130%; font-weight: bold; margin-top: 0.08in; }
	}
	>form {
		display: grid;
		grid-template-columns: 1fr 1fr;
		grid-template-rows: 1.2em 1fr 1fr;
		grid-gap: 5px;
................................................................................
		font-size: 110%;
		text-align: justify;
		color: tone(25%);
	}
	> a[href].permalink {
		display: block;
		grid-column: 4/5; grid-row: 2/3;
		font-size: 80%;
		text-align: right;
		padding: 0.1in;
		padding-right: 0.15in;
		font-style: italic;
		background: linear-gradient(to left, tone(-55%,-0.5), transparent);
	}
	div.stats {
................................................................................
			padding-left: 1.3em;
			background-size: 1.1em;
			background-repeat: no-repeat;
			min-width: 0.3em;
			&:focus {
				outline: none;
				opacity: 0.9 !important;
				filter: brightness(1.7) drop-shadow(0 0 15px rgb(255,150,200));
			}
			&:empty {
				transition: 0.3s;
				opacity: 0.0001; // qutebrowser won't show hints if opacity=0 :(
				&:hover, &:focus { opacity: 0.6 !important; }
			}
		}
................................................................................
		grid-row: 1/2; grid-column: 2/3;
		text-decoration: none;
	}
	> .post {
		grid-row: 2/3; grid-column: 1/3;
	}
}


















































>

>
|







 







|




|







 







|







 







|







 







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
...
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
...
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
...
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
...
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
					transform: scale(1.2);
				}
			}
			> a[href].bell {
				content: url(/s/bell.svg);
				height: 2em;
				padding: 0.125in 0.10in;
				filter: drop-shadow(1px 1px 3px tone(-5%));
				&:hover {
					filter: drop-shadow(1px 1px 3px tone(-5%))
						drop-shadow(0 0 10px tone(-5%));
				}
			}
		}
	}
}

main {
................................................................................
	padding: 0.4in;
	> .msg {
		text-align: center;
		padding: 0.3in;
	}
	> .msg:first-child { padding-top: 0; }
	> .user {
		width: max-content; margin: auto;
		background: tone(-20%,-0.3);
		border: 1px solid black;
		color: tone(-50%);
		padding: 0.1in;
		> img { display: block; width: 1in; height: 1in; margin: auto; border: 1px solid black; }
		> .name { @extend %serif; text-align: center; font-size: 130%; font-weight: bold; margin-top: 0.08in; }
	}
	>form {
		display: grid;
		grid-template-columns: 1fr 1fr;
		grid-template-rows: 1.2em 1fr 1fr;
		grid-gap: 5px;
................................................................................
		font-size: 110%;
		text-align: justify;
		color: tone(25%);
	}
	> a[href].permalink {
		display: block;
		grid-column: 4/5; grid-row: 2/3;
		font-size: 90%;
		text-align: right;
		padding: 0.1in;
		padding-right: 0.15in;
		font-style: italic;
		background: linear-gradient(to left, tone(-55%,-0.5), transparent);
	}
	div.stats {
................................................................................
			padding-left: 1.3em;
			background-size: 1.1em;
			background-repeat: no-repeat;
			min-width: 0.3em;
			&:focus {
				outline: none;
				opacity: 0.9 !important;
				filter: drop-shadow(0 0 7px tone(-10%));
			}
			&:empty {
				transition: 0.3s;
				opacity: 0.0001; // qutebrowser won't show hints if opacity=0 :(
				&:hover, &:focus { opacity: 0.6 !important; }
			}
		}
................................................................................
		grid-row: 1/2; grid-column: 2/3;
		text-decoration: none;
	}
	> .post {
		grid-row: 2/3; grid-column: 1/3;
	}
}

body.notices {
	form { text-align: center; }
	div.notice {
		padding: 0.15in;
		background: linear-gradient(to bottom, tone(10%, -0.9), transparent);
		border: 1px solid tone(-60%);
		& + div.notice { border-top: none; }
		&.rt, &.like, &.reply { &::before {
			display: inline-block;
			width: 1em; height: 1em;
			margin-right: 1ex;
			background-size: contain;
			vertical-align: bottom;
			content: ""; // 🙄
		}}
		&.rt::before    { background-image: url(/s/retweet.webp); }
		&.like::before  { background-image: url(/s/heart.webp);   }
		&.reply::before { background-image: url(/s/reply.webp);   }
		> .action {
			display: inline-block;
			color: tone(5%);
			> .id {
				display: inline-block;
				> img {
					width: 1em; height: 1em;
					vertical-align: middle;
					margin-right: 0.5ex;
				}
			}
		}
		> a[href].quote {
			&::before { content: "“"; }
			&::after { content: "”"; }
			font-style: italic; color: tone(20%);
			text-decoration: none;
		}
		> article.post {
			margin: 0.1in 0.2in;
			margin-left: 0.4in;
		}
	}
}

Modified store.t from [e1429862ec] to [54fed43947].

7
8
9
10
11
12
13

14
15
16
17
18
19
20
...
220
221
222
223
224
225
226

227
228
229
230
231
232
233
...
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
...
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
	};
	noticetype = lib.enum {
		'none', 'mention', 'reply', 'like', 'rt', 'react'
	};

	relation = lib.set {
		'follow',

		'mute', -- posts will be completely hidden at all times
		'block', -- no interactions will be permitted, but posts will remain visible
		'silence', -- messages will not be accepted
		'collapse', -- posts will be collapsed by default
		'disemvowel', -- posts will be ritually humiliated, but shown
		'avoid', -- posts will be kept out of the timeline but will show on users' posts and in conversations
		'exclude', -- own posts will not be visible to this user
................................................................................
-- ephemera
	localpost: bool
	accent: int16
	rts: uint32
	likes: uint32
	rtdby: uint64 -- 0 if not rt
	rtact: uint64 -- 0 if not rt, id of rt action otherwise

	source: &m.source

	-- save :: bool -> {} (defined in acl.t due to dep. hell)
}

m.user_conf_funcs = function(be,n,ty,rty,rty2)
	rty = rty or ty
................................................................................
struct m.notice {
	kind: m.noticetype.t
	when: uint64
	who: uint64
	what: uint64
	union {
		reply: uint64
		reaction: int8[16]
	}
}

struct m.inet {
	pv: uint8 -- 0 = null, 4 = ipv4, 6 = ipv6
	union {
		v4: uint8[4]
................................................................................
			-- origin: inet
			-- cookie issue time: m.timepoint
	actor_auth_register_uid: {&m.source, uint64, uint64} -> {}
		-- notifies the backend module of the UID that has been assigned for
		-- an authentication ID
			-- aid: uint64
			-- uid: uint64
	actor_notice_enum: {&m.source, uint64} -> lib.mem.lstptr(m.notice)
	actor_rel_create: {&m.source, uint16, uint64, uint64} -> {}
	actor_rel_destroy: {&m.source, uint16, uint64, uint64} -> {}
	actor_rel_calc: {&m.source, uint64, uint64} -> m.relationship

	auth_enum_uid:    {&m.source, uint64}    -> lib.mem.lstptr(m.auth)
	auth_enum_handle: {&m.source, rawstring} -> lib.mem.lstptr(m.auth)
	auth_attach_pw: {&m.source, uint64, bool, pstr, pstr} -> {}







>







 







>







 







|







 







|







7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
...
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
...
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
...
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
	};
	noticetype = lib.enum {
		'none', 'mention', 'reply', 'like', 'rt', 'react'
	};

	relation = lib.set {
		'follow',
		'subscribe', -- get a notification for every post
		'mute', -- posts will be completely hidden at all times
		'block', -- no interactions will be permitted, but posts will remain visible
		'silence', -- messages will not be accepted
		'collapse', -- posts will be collapsed by default
		'disemvowel', -- posts will be ritually humiliated, but shown
		'avoid', -- posts will be kept out of the timeline but will show on users' posts and in conversations
		'exclude', -- own posts will not be visible to this user
................................................................................
-- ephemera
	localpost: bool
	accent: int16
	rts: uint32
	likes: uint32
	rtdby: uint64 -- 0 if not rt
	rtact: uint64 -- 0 if not rt, id of rt action otherwise
	isreply: bool
	source: &m.source

	-- save :: bool -> {} (defined in acl.t due to dep. hell)
}

m.user_conf_funcs = function(be,n,ty,rty,rty2)
	rty = rty or ty
................................................................................
struct m.notice {
	kind: m.noticetype.t
	when: uint64
	who: uint64
	what: uint64
	union {
		reply: uint64
		reaction: int8[32] -- are you shitting me, unichode
	}
}

struct m.inet {
	pv: uint8 -- 0 = null, 4 = ipv4, 6 = ipv6
	union {
		v4: uint8[4]
................................................................................
			-- origin: inet
			-- cookie issue time: m.timepoint
	actor_auth_register_uid: {&m.source, uint64, uint64} -> {}
		-- notifies the backend module of the UID that has been assigned for
		-- an authentication ID
			-- aid: uint64
			-- uid: uint64
	actor_notice_enum: {&m.source, uint64} -> lib.mem.ptr(m.notice)
	actor_rel_create: {&m.source, uint16, uint64, uint64} -> {}
	actor_rel_destroy: {&m.source, uint16, uint64, uint64} -> {}
	actor_rel_calc: {&m.source, uint64, uint64} -> m.relationship

	auth_enum_uid:    {&m.source, uint64}    -> lib.mem.lstptr(m.auth)
	auth_enum_handle: {&m.source, rawstring} -> lib.mem.lstptr(m.auth)
	auth_attach_pw: {&m.source, uint64, bool, pstr, pstr} -> {}

Modified str.t from [7fbe47e2ae] to [004fceba8a].

160
161
162
163
164
165
166




167
168
169
170
171
172
173
	if sz <= self.run then return end
	self.run = sz
	if self.space - self.sz < self.run then
		self.space = self.sz + self.run
		self.buf = [rawstring](lib.mem.heapr_raw(self.buf, self.space))
	end
end





terra m.acc:push(str: rawstring, len: intptr)
	--var llen = len
	if str == nil then return self end
	--if str[len - 1] == 0xA then llen = llen - 1 end -- don't display newlines in debug output
	-- lib.dbg('pushing "',{str,llen},'" onto accumulator')
	if self.buf == nil then self:init(self.run) end







>
>
>
>







160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
	if sz <= self.run then return end
	self.run = sz
	if self.space - self.sz < self.run then
		self.space = self.sz + self.run
		self.buf = [rawstring](lib.mem.heapr_raw(self.buf, self.space))
	end
end

terra m.acc:reset() -- semantic convenience function
	self.sz = 0
end

terra m.acc:push(str: rawstring, len: intptr)
	--var llen = len
	if str == nil then return self end
	--if str[len - 1] == 0xA then llen = llen - 1 end -- don't display newlines in debug output
	-- lib.dbg('pushing "',{str,llen},'" onto accumulator')
	if self.buf == nil then self:init(self.run) end

Modified view/load.lua from [dd2878563c] to [1dbf5e584d].

5
6
7
8
9
10
11

12
13
14
15
16
17
18
local path = ...
local sources = {
	'docskel';
	'confirm';
	'tweet';
	'profile';
	'compose';


	'login-username';
	'login-challenge';

	'conf';
	'conf-profile';
	'conf-sec';







>







5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
local path = ...
local sources = {
	'docskel';
	'confirm';
	'tweet';
	'profile';
	'compose';
	'notice';

	'login-username';
	'login-challenge';

	'conf';
	'conf-profile';
	'conf-sec';

Added view/notice.tpl version [6c1a6f12b0].

















>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
<div class="notice @kind">
	<div class="action">
		<div class="id">
			<img src="@avatar">
			<a class="username" href="@pflink">@nym</a>
		</div> @act
	</div> @ref
</div>

Modified view/tweet.tpl from [80bcf01f8a] to [5d1f823e24].

1
2
3
4
5
6
7
8
9
10
<article class="post"@attr>
	<div class="avatar"><img src="@:avatar"></div>
	<a class="username" href="/@:acctlink">@nym</a>
	<div class="content">
		<div class="subject">@!subject</div>
		<div class="text">@text</div>
	</div>
	@stats
	<a class="permalink" href="@permalink"><time>@when</time></a>
</article>



|






1
2
3
4
5
6
7
8
9
10
<article class="post"@attr>
	<div class="avatar"><img src="@:avatar"></div>
	<a class="username" href="/@:acctlink">@nym</a>
	<div class="content">@extra
		<div class="subject">@!subject</div>
		<div class="text">@text</div>
	</div>
	@stats
	<a class="permalink" href="@permalink"><time>@when</time></a>
</article>