parsav  Check-in [03ea3dffe7]

Overview
Comment:more work on UI and circles
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: 03ea3dffe75fb71e9ab8b0502034c8c9011a7ea1ba0b87e189541bd4ee331683
User & Date: lexi on 2021-01-14 01:11:51
Other Links: manifest | tags
Context
2021-01-14
05:31
more progress on ui & circles check-in: e78334fe04 user: lexi tags: trunk
01:11
more work on UI and circles check-in: 03ea3dffe7 user: lexi tags: trunk
2021-01-13
15:01
begin work on circles check-in: a4b4af5ca4 user: lexi tags: trunk
Changes

Modified backend/pgsql.t from [96ec125a09] to [dc07991d31].

13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
...
291
292
293
294
295
296
297
















298
299








300
301
302
303
304
305
306
307
308
309
310
311
...
557
558
559
560
561
562
563






















564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
...
580
581
582
583
584
585
586
587

588
589
590
591
592
593
594
...
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
....
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
....
1546
1547
1548
1549
1550
1551
1552


















1553
1554
1555
1556
1557
1558
1559
....
2032
2033
2034
2035
2036
2037
2038
2039





2040
2041
2042
2043
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
2059
2060
2061
2062
2063
2064
2065
2066
2067
2068
2069
2070
2071
....
2074
2075
2076
2077
2078
2079
2080
2081
2082




































2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110
		params = {uint64}, sql = [[
			select domain, key, knownsince, parsav from parsav_servers
				where id = $1::bigint
		]];
	};

	conf_get = {
		params = {rawstring}, sql = [[
			select value from parsav_config
				where key = $1::text limit 1
		]];
	};

	conf_set = {
		params = {rawstring,rawstring}, cmd=true, sql = [[
			insert into parsav_config (key, value)
				values ($1::text, $2::text)
				on conflict (key) do update set value = $2::text
		]];
	};

	conf_reset = {
................................................................................
		params = {uint64,uint64}, sql = [[
			select name, id, owner, array_length(members,1) from parsav_circles where
				($1::bigint = 0 or $1::bigint = owner) and
				($2::bigint = 0 or $2::bigint = id)
		]];
	};

















	circle_members_fetch_cid = {
		params = {uint64, uint64}, sql = [[








			select unnest(members) from parsav_circles where
				($1::bigint = 0 or owner = $1::bigint) and
				id = $2::bigint
		]];
	};

	circle_members_fetch_name = {
		params = {uint64, pstring}, sql = [[
			select unnest(members) from parsav_circles where
				($1::bigint = 0 or owner = $1::bigint) and
				name = $2::text
		]];
................................................................................
			order by c.tltime desc

			limit case when $3::bigint = 0 then null
					   else $3::bigint end
			offset $4::bigint
		]];
	};























	timeline_actor_fetch = {
		params = {uint64, uint64, uint64, uint64, uint64}, sql = [[
			with followed as (
				select relatee from parsav_rels where
					kind = <rel:follow> and
					relator = $1::bigint
			), avoided as (
				select relatee as avoidee from parsav_rels where
					kind = <rel:avoid> or kind = <rel:mute> and
					relator = $1::bigint
				union select relator as avoidee from parsav_rels where
					kind = <rel:exclude> and
					relatee = $1::bigint
			)
................................................................................
			select (c.post).*
			from pg_temp.parsavpg_known_content as c

			where ($2::bigint = 0 or c.tltime   <= $2::bigint) and
				  ($3::bigint = 0 or $3::bigint <  c.tltime) and
				  (c.promoter in (table followed) or
				   c.promoter = $1::bigint) and
				  not ((c.post).author in (table avoided))

			order by c.tltime desc

			limit case when $4::bigint = 0 then null
					   else $4::bigint end
			offset $5::bigint
		]];
	};
................................................................................
				artifacts = array_remove(artifacts, $2::bigint)
			where id = $1::bigint and
				artifacts @> array[$2::bigint]
		]];
	};

	actor_conf_str_get = {
		params = {uint64, rawstring}, sql = [[
			select value from parsav_actor_conf_strs where
				uid = $1::bigint and
				key = $2::text
			limit 1
		]];
	};
	actor_conf_str_set = {
		params = {uint64, rawstring, rawstring}, cmd = true, sql = [[
			insert into parsav_actor_conf_strs (uid,key,value)
				values ($1::bigint, $2::text, $3::text)
			on conflict (uid,key) do update set value = $3::text
		]];
	};
	actor_conf_str_enum = {
		params = {uint64}, sql = [[
			select value from parsav_actor_conf_strs where uid = $1::bigint
		]];
	};
	actor_conf_str_reset = {
		params = {uint64, rawstring}, cmd = true, sql = [[
			delete from parsav_actor_conf_strs where
				uid = $1::bigint and ($2::text is null or key = $2::text)
		]]
	};

	actor_conf_int_get = {
		params = {uint64, rawstring}, sql = [[
			select value from parsav_actor_conf_ints where
				uid = $1::bigint and
				key = $2::text
			limit 1
		]];
	};
	actor_conf_int_set = {
		params = {uint64, rawstring, uint64}, cmd = true, sql = [[
			insert into parsav_actor_conf_ints (uid,key,value)
				values ($1::bigint, $2::text, $3::bigint)
			on conflict (uid,key) do update set value = $3::bigint
		]];
	};
	actor_conf_int_enum = {
		params = {uint64}, sql = [[
			select value from parsav_actor_conf_ints where uid = $1::bigint
		]];
	};
	actor_conf_int_reset = {
		params = {uint64, rawstring}, cmd = true, sql = [[
			delete from parsav_actor_conf_ints where
				uid = $1::bigint and ($2::text is null or key = $2::text)
		]]
	};
}

local struct pqr {
................................................................................
			return true
		else
			lib.warn('backend pgsql - failed to obliterate database: \n', lib.pq.PQresultErrorMessage(res))
			return false
		end
	end];

	conf_get = [terra(src: &lib.store.source, key: rawstring)
		var r = queries.conf_get.exec(src, key)
		if r.sz == 0 then return [lib.mem.ptr(int8)] { ptr = nil, ct = 0 } else
			defer r:free()
			return r:String(0,0)
		end
	end];
	conf_set = [terra(src: &lib.store.source, key: rawstring, val: rawstring)
		queries.conf_set.exec(src, key, val):free() end];
	conf_reset = [terra(src: &lib.store.source, key: rawstring)
		queries.conf_reset.exec(src, key):free() end];
	
	actor_fetch_uid = [terra(src: &lib.store.source, uid: uint64)
		var r = queries.actor_fetch_uid.exec(src, uid)
		if r.sz == 0 then
................................................................................
	timeline_instance_fetch = [terra(
		src: &lib.store.source,
		rg: lib.store.range
	): lib.mem.lstptr(lib.store.post)
		var r = pqr { sz = 0 }
		var A,B,C,D = rg:matrix() -- :/
		r = queries.timeline_instance_fetch.exec(src,A,B,C,D)


















		
		var ret: lib.mem.ptr(lib.mem.ptr(lib.store.post)) ret:init(r.sz)
		for i=0,r.sz do
			ret.ptr[i] = row_to_post(&r, i) -- MUST FREE ALL
			ret.ptr[i].ptr.source = src
		end

................................................................................
	auth_sigtime_user_alter = [terra(
		src: &lib.store.source,
		uid: uint64,
		time: lib.store.timepoint
	): {} queries.auth_sigtime_user_alter.exec(src, uid, time) end];

	actor_conf_str_enum = nil;
	actor_conf_str_get = [terra(src: &lib.store.source, uid: uint64, key: rawstring): pstring





			var r = queries.actor_conf_str_get.exec(src, uid, key)
			if r.sz > 0 then
				var ret = r:String(0,0)
				r:free()
				return ret
			else return pstring.null() end
		end];
	actor_conf_str_set = [terra(src: &lib.store.source, uid: uint64, key: rawstring, value: rawstring): {}
			queries.actor_conf_str_set.exec(src,uid,key,value) end];
	actor_conf_str_reset = [terra(src: &lib.store.source, uid: uint64, key: rawstring): {}
			queries.actor_conf_str_reset.exec(src,uid,key) end];

	actor_conf_int_enum = nil;
	actor_conf_int_get = [terra(src: &lib.store.source, uid: uint64, key: rawstring)
			var r = queries.actor_conf_int_get.exec(src, uid, key)
			if r.sz > 0 then
				var ret = r:int(uint64,0,0)
				r:free()
				return ret, true
			end
			return 0, false
		end];
	actor_conf_int_set = [terra(src: &lib.store.source, uid: uint64, key: rawstring, value: uint64): {}
			queries.actor_conf_int_set.exec(src,uid,key,value) end];
	actor_conf_int_reset = [terra(src: &lib.store.source, uid: uint64, key: rawstring): {}
			queries.actor_conf_int_reset.exec(src,uid,key) end];
	
	circle_search = [terra(
		src: &lib.store.source,
		pool:&lib.mem.pool,
		uid: uint64,
		cid: uint64
................................................................................
		if res.sz == 0 then return [lib.mem.ptr(lib.store.circle)].null() end
		defer res:free()

		var rt = pool:alloc(lib.store.circle, res.sz)
		for i = 0, res.sz do
			var name = res:_string(i,0)
			rt(i) = lib.store.circle {
				name = name:pdup(pool);
				cid = res:int(uint64,i,1);




































				owner = res:int(uint64,i,2);
				memcount = res:int(uint64,i,3);
			}
		end

		return rt
	end];

	circle_members_fetch_cid = [terra(
		src: &lib.store.source,
		pool:&lib.mem.pool,
		uid: uint64,
		cid: uint64
	): lib.mem.ptr(uint64)
		var res = queries.circle_members_fetch_cid.exec(src,uid,cid)
		if res.sz == 0 then return [lib.mem.ptr(uint64)].null() end
		defer res:free()

		var rt = pool:alloc(uint64, res.sz)
		for i = 0, res.sz do rt(i) = res:int(uint64,i,0) end

		return rt
	end];

	actor_auth_register_uid = nil; -- TODO better support non-view based auth
}

return b







|






|







 







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|

>
>
>
>
>
>
>
>

<
|









 







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







|







 







|
>







 







|







|











|






|







|











|







 







|






|







 







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







 







|
>
>
>
>
>


<
<
|


|

|



|








|

|







 







|
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
|









<


|













13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
...
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
...
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
...
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
...
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
....
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
....
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
....
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110


2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
....
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
2161
2162
2163
2164
2165
2166
2167
2168
2169
2170
2171
2172
2173
2174
2175
2176
2177
2178
2179
2180
2181
2182
2183
2184
2185
2186
2187
2188
2189
2190
2191
2192
2193
2194
2195
2196

2197
2198
2199
2200
2201
2202
2203
2204
2205
2206
2207
2208
2209
2210
2211
2212
		params = {uint64}, sql = [[
			select domain, key, knownsince, parsav from parsav_servers
				where id = $1::bigint
		]];
	};

	conf_get = {
		params = {pstring}, sql = [[
			select value from parsav_config
				where key = $1::text limit 1
		]];
	};

	conf_set = {
		params = {pstring,pstring}, cmd=true, sql = [[
			insert into parsav_config (key, value)
				values ($1::text, $2::text)
				on conflict (key) do update set value = $2::text
		]];
	};

	conf_reset = {
................................................................................
		params = {uint64,uint64}, sql = [[
			select name, id, owner, array_length(members,1) from parsav_circles where
				($1::bigint = 0 or $1::bigint = owner) and
				($2::bigint = 0 or $2::bigint = id)
		]];
	};

	circle_create = {
		params = {uint64,pstring}, sql = [[
			insert into parsav_circles (owner,name)
				values ($1::bigint, $2::text)
			returning id
		]];
	};

	circle_destroy = {
		params = {uint64,uint64}, cmd = true, sql = [[
			delete from parsav_circles where
				owner = $1::bigint and
				id = $2::bigint
		]];
	};

	circle_memberships_uid = {
		params = {uint64, uint64}, sql = [[
			select name, id, owner, array_length(members,1) from parsav_circles where
				owner   =  $1::bigint and
				members @> array[$2::bigint]
		]];
	};

	circle_members_fetch_cid = {
		params = {uint64}, sql = [[
			select unnest(members) from parsav_circles where

				id = $1::bigint
		]];
	};

	circle_members_fetch_name = {
		params = {uint64, pstring}, sql = [[
			select unnest(members) from parsav_circles where
				($1::bigint = 0 or owner = $1::bigint) and
				name = $2::text
		]];
................................................................................
			order by c.tltime desc

			limit case when $3::bigint = 0 then null
					   else $3::bigint end
			offset $4::bigint
		]];
	};

	timeline_circle_fetch = {
		params = {uint64, uint64, uint64, uint64, uint64}, sql = [[
			with circle as (
				select unnest(members) from parsav_circles where id = $1::bigint
			)

			select (c.post).*
			from pg_temp.parsavpg_known_content as c

			where ($2::bigint = 0 or c.tltime   <= $2::bigint) and
				  ($3::bigint = 0 or $3::bigint <  c.tltime) and
				  (c.promoter in (table circle) or
				   c.promoter = (select owner from parsav_circles where id = $1::bigint))

			order by c.tltime desc

			limit case when $4::bigint = 0 then null
					   else $4::bigint end
			offset $5::bigint
		]];
	};

	timeline_actor_fetch = {
		params = {uint64, uint64, uint64, uint64, uint64}, sql = [[
			with followed as (
				select relatee from parsav_rels where
					kind = <rel:follow> and
					relator = $1::bigint
			), avoided as ( -- not strictly necessary but lets us minimize how much data needs to be sent back to parsav for filtering
				select relatee as avoidee from parsav_rels where
					kind = <rel:avoid> or kind = <rel:mute> and
					relator = $1::bigint
				union select relator as avoidee from parsav_rels where
					kind = <rel:exclude> and
					relatee = $1::bigint
			)
................................................................................
			select (c.post).*
			from pg_temp.parsavpg_known_content as c

			where ($2::bigint = 0 or c.tltime   <= $2::bigint) and
				  ($3::bigint = 0 or $3::bigint <  c.tltime) and
				  (c.promoter in (table followed) or
				   c.promoter = $1::bigint) and
				  not ((c.post).author in (table avoided)) and
				  not (c.promoter      in (table avoided))
			order by c.tltime desc

			limit case when $4::bigint = 0 then null
					   else $4::bigint end
			offset $5::bigint
		]];
	};
................................................................................
				artifacts = array_remove(artifacts, $2::bigint)
			where id = $1::bigint and
				artifacts @> array[$2::bigint]
		]];
	};

	actor_conf_str_get = {
		params = {uint64, pstring}, sql = [[
			select value from parsav_actor_conf_strs where
				uid = $1::bigint and
				key = $2::text
			limit 1
		]];
	};
	actor_conf_str_set = {
		params = {uint64, pstring, pstring}, cmd = true, sql = [[
			insert into parsav_actor_conf_strs (uid,key,value)
				values ($1::bigint, $2::text, $3::text)
			on conflict (uid,key) do update set value = $3::text
		]];
	};
	actor_conf_str_enum = {
		params = {uint64}, sql = [[
			select value from parsav_actor_conf_strs where uid = $1::bigint
		]];
	};
	actor_conf_str_reset = {
		params = {uint64, pstring}, cmd = true, sql = [[
			delete from parsav_actor_conf_strs where
				uid = $1::bigint and ($2::text is null or key = $2::text)
		]]
	};

	actor_conf_int_get = {
		params = {uint64, pstring}, sql = [[
			select value from parsav_actor_conf_ints where
				uid = $1::bigint and
				key = $2::text
			limit 1
		]];
	};
	actor_conf_int_set = {
		params = {uint64, pstring, uint64}, cmd = true, sql = [[
			insert into parsav_actor_conf_ints (uid,key,value)
				values ($1::bigint, $2::text, $3::bigint)
			on conflict (uid,key) do update set value = $3::bigint
		]];
	};
	actor_conf_int_enum = {
		params = {uint64}, sql = [[
			select value from parsav_actor_conf_ints where uid = $1::bigint
		]];
	};
	actor_conf_int_reset = {
		params = {uint64, pstring}, cmd = true, sql = [[
			delete from parsav_actor_conf_ints where
				uid = $1::bigint and ($2::text is null or key = $2::text)
		]]
	};
}

local struct pqr {
................................................................................
			return true
		else
			lib.warn('backend pgsql - failed to obliterate database: \n', lib.pq.PQresultErrorMessage(res))
			return false
		end
	end];

	conf_get = [terra(src: &lib.store.source, key: pstring)
		var r = queries.conf_get.exec(src, key)
		if r.sz == 0 then return [lib.mem.ptr(int8)] { ptr = nil, ct = 0 } else
			defer r:free()
			return r:String(0,0)
		end
	end];
	conf_set = [terra(src: &lib.store.source, key: pstring, val: pstring)
		queries.conf_set.exec(src, key, val):free() end];
	conf_reset = [terra(src: &lib.store.source, key: rawstring)
		queries.conf_reset.exec(src, key):free() end];
	
	actor_fetch_uid = [terra(src: &lib.store.source, uid: uint64)
		var r = queries.actor_fetch_uid.exec(src, uid)
		if r.sz == 0 then
................................................................................
	timeline_instance_fetch = [terra(
		src: &lib.store.source,
		rg: lib.store.range
	): lib.mem.lstptr(lib.store.post)
		var r = pqr { sz = 0 }
		var A,B,C,D = rg:matrix() -- :/
		r = queries.timeline_instance_fetch.exec(src,A,B,C,D)
		
		var ret: lib.mem.ptr(lib.mem.ptr(lib.store.post)) ret:init(r.sz)
		for i=0,r.sz do
			ret.ptr[i] = row_to_post(&r, i) -- MUST FREE ALL
			ret.ptr[i].ptr.source = src
		end

		return ret
	end];

	timeline_circle_fetch = [terra(
		src: &lib.store.source,
		cid: uint64,
		rg: lib.store.range
	): lib.mem.lstptr(lib.store.post)
		var r = pqr { sz = 0 }
		var A,B,C,D = rg:matrix() -- :/
		r = queries.timeline_circle_fetch.exec(src,cid,A,B,C,D)
		
		var ret: lib.mem.ptr(lib.mem.ptr(lib.store.post)) ret:init(r.sz)
		for i=0,r.sz do
			ret.ptr[i] = row_to_post(&r, i) -- MUST FREE ALL
			ret.ptr[i].ptr.source = src
		end

................................................................................
	auth_sigtime_user_alter = [terra(
		src: &lib.store.source,
		uid: uint64,
		time: lib.store.timepoint
	): {} queries.auth_sigtime_user_alter.exec(src, uid, time) end];

	actor_conf_str_enum = nil;
	actor_conf_str_get = [terra(
		src: &lib.store.source,
		pool: &lib.mem.pool,
		uid: uint64,
		key: pstring
	): pstring
			var r = queries.actor_conf_str_get.exec(src, uid, key)
			if r.sz > 0 then


				return r:_string(0,0):pdup(pool)
			else return pstring.null() end
		end];
	actor_conf_str_set = [terra(src: &lib.store.source, uid: uint64, key: pstring, value: pstring): {}
			queries.actor_conf_str_set.exec(src,uid,key,value) end];
	actor_conf_str_reset = [terra(src: &lib.store.source, uid: uint64, key: pstring): {}
			queries.actor_conf_str_reset.exec(src,uid,key) end];

	actor_conf_int_enum = nil;
	actor_conf_int_get = [terra(src: &lib.store.source, uid: uint64, key: pstring)
			var r = queries.actor_conf_int_get.exec(src, uid, key)
			if r.sz > 0 then
				var ret = r:int(uint64,0,0)
				r:free()
				return ret, true
			end
			return 0, false
		end];
	actor_conf_int_set = [terra(src: &lib.store.source, uid: uint64, key: pstring, value: uint64): {}
			queries.actor_conf_int_set.exec(src,uid,key,value) end];
	actor_conf_int_reset = [terra(src: &lib.store.source, uid: uint64, key: pstring): {}
			queries.actor_conf_int_reset.exec(src,uid,key) end];
	
	circle_search = [terra(
		src: &lib.store.source,
		pool:&lib.mem.pool,
		uid: uint64,
		cid: uint64
................................................................................
		if res.sz == 0 then return [lib.mem.ptr(lib.store.circle)].null() end
		defer res:free()

		var rt = pool:alloc(lib.store.circle, res.sz)
		for i = 0, res.sz do
			var name = res:_string(i,0)
			rt(i) = lib.store.circle {
				name = name:pdup(pool);      cid = res:int(uint64,i,1);
				owner = res:int(uint64,i,2); memcount = res:int(uint64,i,3);
			}
		end

		return rt
	end];

	circle_create = [terra(
		src: &lib.store.source,
		owner: uint64,
		name: pstring
	): uint64
		var r = queries.circle_create.exec(src, owner, name)
		if r.sz > 0 then defer r:free() return r:int(uint64,0,0) end
		return 0
	end];

	circle_destroy = [terra(
		src: &lib.store.source,
		owner: uint64,
		cid: uint64
	): {} queries.circle_destroy.exec(src, owner, cid) end];

	circle_memberships_uid = [terra(
		src: &lib.store.source,
		pool:&lib.mem.pool,
		owner: uint64,
		subject: uint64
	): lib.mem.ptr(lib.store.circle)
		var res = queries.circle_memberships_uid.exec(src, owner, subject)
		if res.sz == 0 then return [lib.mem.ptr(lib.store.circle)].null() end
		defer res:free()

		var rt = pool:alloc(lib.store.circle, res.sz)
		for i = 0, res.sz do
			var name = res:_string(i,0)
			rt(i) = lib.store.circle {
				name = name:pdup(pool);      cid = res:int(uint64,i,1);
				owner = res:int(uint64,i,2); memcount = res:int(uint64,i,3);
			}
		end

		return rt
	end];

	circle_members_fetch_cid = [terra(
		src: &lib.store.source,
		pool:&lib.mem.pool,

		cid: uint64
	): lib.mem.ptr(uint64)
		var res = queries.circle_members_fetch_cid.exec(src,cid)
		if res.sz == 0 then return [lib.mem.ptr(uint64)].null() end
		defer res:free()

		var rt = pool:alloc(uint64, res.sz)
		for i = 0, res.sz do rt(i) = res:int(uint64,i,0) end

		return rt
	end];

	actor_auth_register_uid = nil; -- TODO better support non-view based auth
}

return b

Modified config.lua from [a1f76576fd] to [b2a2580dbd].

51
52
53
54
55
56
57

58
59
60
61
62
63
64
		-- TODO with gzip compression, svg is dramatically superior to webp
		-- we should add support for content-encoding headers and pre-compress
		-- the damn things before compiling (also making the binary smaller)
		{'style.css', 'text/css'};
		{'live.js', 'text/javascript'}; -- rrrrrrrr
		{'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'};
		{'file.webp', 'image/webp'};







>







51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
		-- TODO with gzip compression, svg is dramatically superior to webp
		-- we should add support for content-encoding headers and pre-compress
		-- the damn things before compiling (also making the binary smaller)
		{'style.css', 'text/css'};
		{'live.js', 'text/javascript'}; -- rrrrrrrr
		{'default-avatar.webp', 'image/webp'}; -- needs inkscape-exclusive svg features
		{'bell.svg', 'image/svg+xml'};
		{'gear.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'};
		{'file.webp', 'image/webp'};

Modified mem.t from [a251bec86e] to [8c252399a5].

108
109
110
111
112
113
114

















115
116
117
118
119
120
121
	end
	terra t.methods.null(): t return t { ptr = nil, ct = 0 } end -- maybe should be a macro?
	terra t:ref() return self.ptr ~= nil end
	t.metamethods.__not = macro(function(self) return `not self:ref() end)
	t.metamethods.__apply = macro(function(self,idx) return `self.ptr[ [idx or 0] ] end)
	t.metamethods.__update = macro(function(self,idx,rhs)
		return quote self.ptr[idx] = rhs end end)


















	if not ty:isstruct() then
		terra t:cmp_raw(other: &ty)
			for i=0, self.ct do
				if self.ptr[i] ~= other[i] then return false end
			end
			return true







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







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
	end
	terra t.methods.null(): t return t { ptr = nil, ct = 0 } end -- maybe should be a macro?
	terra t:ref() return self.ptr ~= nil end
	t.metamethods.__not = macro(function(self) return `not self:ref() end)
	t.metamethods.__apply = macro(function(self,idx) return `self.ptr[ [idx or 0] ] end)
	t.metamethods.__update = macro(function(self,idx,rhs)
		return quote self.ptr[idx] = rhs end end)
	t.metamethods.__cast = function(from,to,exp)
		if to == t then
			if from == niltype then return `t.null()
			elseif from == &ty then return `t {ptr = exp, ct = 1}
			elseif from == ty then return `t {ptr = &exp, ct = 1}
			elseif from.N and from.type == ty then
				return `t {ptr = &exp[0], ct = from.N }
			end
			error('invalid cast to ' .. t.name .. ' from ' .. tostring(from))
		elseif from == t then
			if     to == &ty  then return `exp.ptr
			elseif to == ty   then return `@exp.ptr
			elseif to == bool then return `exp:ref() end
			error('invalid cast from ' .. t.name .. ' to ' .. tostring(to))
		end
		error('invalid pointer cast')
	end

	if not ty:isstruct() then
		terra t:cmp_raw(other: &ty)
			for i=0, self.ct do
				if self.ptr[i] ~= other[i] then return false end
			end
			return true

Modified parsav.md from [23851a52a8] to [d83ecc5b3e].

142
143
144
145
146
147
148
149
* ldap for auth (and maybe actors?)
* cdb (for static content, maybe? does this make sense?)
* mariadb/mysql
* the various nosql horrors, e.g. redis, mongo, and so on

parsav urgently needs an internationalization framework as well. right now everything is just hardcoded in english. yuck.

parsav could be significantly improved by adjusting its memory management strategy. instead of allocating everything with lib.mem.heapa (which currently maps to malloc on all platforms), we should allocate a static buffer for the server overlord object which can simply be cleared and re-used for each http request, and enlarged with `realloc` when necessary. the entire region could be `mlock`ed for better performance, and it would no longer be necessary to track and free memory, as the entire buffer would simply be discarded after use (similar to PHP's original memory management strategy). this would remove possibly the largest source of latency in the codebase, as `parsav` is regrettably quite heavy on malloc, performing numerous allocations for each page rendered. **update:** this is now in progress







|
142
143
144
145
146
147
148
149
* ldap for auth (and maybe actors?)
* cdb (for static content, maybe? does this make sense?)
* mariadb/mysql
* the various nosql horrors, e.g. redis, mongo, and so on

parsav urgently needs an internationalization framework as well. right now everything is just hardcoded in english. yuck.

parsav could be significantly improved by adjusting its memory management strategy. instead of allocating everything with lib.mem.heapa (which currently maps to malloc on all platforms), we can allocate a static buffer for the server overlord object which can simply be cleared and re-used for each http request, and enlarged with `realloc` when necessary. the entire region could be `mlock`ed for better performance, and it would no longer be necessary to track and free memory, as the entire buffer would simply be discarded after use (similar to PHP's original memory management strategy). this would remove possibly the largest source of latency in the codebase, as `parsav` is regrettably quite heavy on malloc, performing numerous allocations for each page rendered. **update:** this is now in progress, and much of the UI code has been converted; the database code will also need to be converted, however, and this will be too time-consuming to be worth tackling any time soon. new functions should be written to use the memory pooling strategy, however.

Modified parsav.t from [d3351b7874] to [8d6924c4e0].

490
491
492
493
494
495
496

497
498
499
500
501
502
503
	'render:notices';

	'render:media-gallery';

	'render:docpage';

	'render:conf:profile';

	'render:conf:sec';
	'render:conf:users';
	'render:conf:avi';
	'render:conf';
	'route';
}








>







490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
	'render:notices';

	'render:media-gallery';

	'render:docpage';

	'render:conf:profile';
	'render:conf:circles';
	'render:conf:sec';
	'render:conf:users';
	'render:conf:avi';
	'render:conf';
	'route';
}

Added render/conf/circles.t version [2450dbe8a2].











































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
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
-- vim: ft=terra
local pstr = lib.mem.ptr(int8)
local pref = lib.mem.ref(int8)


local terra 
render_conf_circles(co: &lib.srv.convo, path: lib.mem.ptr(pref)): pstr
	if path.ct == 2 then
		var circs = co.srv:circle_search(&co.srv.pool, co.who.id, 0)
		var ui: data.view.conf_circles
		if circs.ct == 0 then
			ui.newattr = ' open';
		else
			ui.newattr = '';
			var circlst = co:stra(86)
			for i = 0, circs.ct do
				circlst:lpush '<li><a href="/conf/circles/':shpush(circs(i).cid):lpush'">'
					   :ppush(circs(i).name)
					   :lpush '</a></li>'
			end
			ui.circles = circlst:finalize()
		end

		return ui:poolstr(&co.srv.pool)
	elseif path.ct == 3 then
		var cid, cid_ok = lib.math.shorthand.parse(path(2).ptr, path(2).ct)
		if not cid_ok then return pstr.null() end
		var circ = co.srv:circle_search(&co.srv.pool, co.who.id, cid)
		if not circ then return pstr.null() end
		var actrs = co.srv:circle_members_fetch_cid(&co.srv.pool, cid)

		var acta = co:stra(86)
		if actrs:ref() then
			for i=0, actrs.ct do
				var a = co:uid2actor(actrs(i))
				if a ~= nil then
					acta:lpush '<li><a class="username" href="/':push(a.xid,0):lpush '#rel">'
					lib.render.nym(a, 0, &acta, false)
					acta:lpush '</a></li>'
				end
			end
		end

		var ui = data.view.conf_circle_view {
			circle = circ().name;
			actors = acta:finalize();
		}

		return ui:poolstr(&co.srv.pool)
	else return pstr.null() end
end

return render_conf_circles

Modified render/conf/profile.t from [5b3736f2f4] to [5726a3ab36].

10
11
12
13
14
15
16


17
18
19
20
21
render_conf_profile(co: &lib.srv.convo, path: lib.mem.ptr(pref)): pstr
	var hue: int8[21]
	var c = data.view.conf_profile {
		handle = cs(co.who.handle);
		nym = cs(lib.coalesce(co.who.nym,''));
		bio = cs(lib.coalesce(co.who.bio,''));
		hue = lib.math.decstr(co.ui_hue, &hue[20]);


	}
	return c:poolstr(&co.srv.pool)
end

return render_conf_profile







>
>





10
11
12
13
14
15
16
17
18
19
20
21
22
23
render_conf_profile(co: &lib.srv.convo, path: lib.mem.ptr(pref)): pstr
	var hue: int8[21]
	var c = data.view.conf_profile {
		handle = cs(co.who.handle);
		nym = cs(lib.coalesce(co.who.nym,''));
		bio = cs(lib.coalesce(co.who.bio,''));
		hue = lib.math.decstr(co.ui_hue, &hue[20]);
		acl_follow = co:usercfg_str(co.who.id, 'acl-follow');
		acl_follow_req = co:usercfg_str(co.who.id, 'acl-follow-req');
	}
	return c:poolstr(&co.srv.pool)
end

return render_conf_profile

Modified render/conf/users.t from [24aad61532] to [be5a9e1656].

368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
		var users: lib.mem.lstptr(lib.store.actor)
		if mode == mode_local then
			users = co.srv:actor_enum_local()
		else
			users = co.srv:actor_enum()
		end
		ulst:lpush('</em></div>')
		ulst:lpush('<ul class="user-list">')
		for i=0,users.ct do var usr = users(i).ptr
			if mode == mode_staff and usr.rights.rank == 0 then goto skip
			elseif mode == mode_peons and usr.rights.rank ~= 0 then goto skip
			elseif mode == mode_remote and usr.origin == 0 then goto skip 
			elseif mode == mode_peers and usr.epithet == nil then goto skip end
			var idlen = lib.math.shorthand.gen(usr.id, &idbuf[0])
			ulst:lpush('<li>')







|







368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
		var users: lib.mem.lstptr(lib.store.actor)
		if mode == mode_local then
			users = co.srv:actor_enum_local()
		else
			users = co.srv:actor_enum()
		end
		ulst:lpush('</em></div>')
		ulst:lpush('<ul class="directory">')
		for i=0,users.ct do var usr = users(i).ptr
			if mode == mode_staff and usr.rights.rank == 0 then goto skip
			elseif mode == mode_peons and usr.rights.rank ~= 0 then goto skip
			elseif mode == mode_remote and usr.origin == 0 then goto skip 
			elseif mode == mode_peers and usr.epithet == nil then goto skip end
			var idlen = lib.math.shorthand.gen(usr.id, &idbuf[0])
			ulst:lpush('<li>')

Modified render/nav.t from [9f2c55cf5b] to [5e68ddf43b].

1
2
3
4
5
6
7
8
9
10
11
12

13
14
15
16
17
18
-- vim: ft=terra
local terra 
render_nav(co: &lib.srv.convo)
	var t = co:stra(64)
	if co.who ~= nil or co.srv.cfg.pol_sec == lib.srv.secmode.public then
		t:lpush(' <a accesskey="t" href="/">timeline</a>')
	end
	if co.who ~= nil then
		t:lpush(' <a accesskey="c" href="/compose">compose</a> <a accesskey="p" href="/'):push(co.who.xid,0)
		t:lpush('">profile</a> <a accesskey="m" href="/media">media</a> <a accesskey="o" href="/conf">configure</a> <a accesskey="d" href="/doc">docs</a> <div class="ident">@')
		t:push(co.who.handle,0)
		t:lpush('</div> <a accesskey="g" href="/logout">log out</a> <a class="bell" accesskey="x" href="/notices">notices</a>')

	else
		t:lpush(' <a accesskey="d" href="/doc">docs</a> <a accesskey="g" href="/login">log in</a>')
	end
	return t:finalize()
end
return render_nav





|


|
<

<
>

|




1
2
3
4
5
6
7
8
9

10

11
12
13
14
15
16
17
-- vim: ft=terra
local terra 
render_nav(co: &lib.srv.convo)
	var t = co:stra(64)
	if co.who ~= nil or co.srv.cfg.pol_sec == lib.srv.secmode.public then
		t:lpush(' <a accesskey="t" href="/"><u>t</u>imeline</a>')
	end
	if co.who ~= nil then
		t:lpush(' <a accesskey="c" href="/compose"><u>c</u>ompose</a> <a accesskey="m" href="/media"><u>m</u>edia</a> <a accesskey="d" href="/doc"><u>d</u>ocs</a> <hr> <a accesskey="p" href="'):push(co.who.xid,0):lpush('" class="ident">')

		t:push(co.who.handle,0)

		t:lpush('</a> <a accesskey="g" href="/logout">lo<u>g</u> out</a> <a class="gear" accesskey="o" href="/conf">c<u>o</u>nfigure</a> <a class="bell" accesskey="x" href="/notices">notices (<u>x</u>)</a>')
	else
		t:lpush(' <a accesskey="d" href="/doc"><u>d</u>ocs</a> <a accesskey="g" href="/login">lo<u>g</u> in</a>')
	end
	return t:finalize()
end
return render_nav

Modified render/profile.t from [0e11aad870] to [06727d1481].

58
59
60
61
62
63
64








65
66
67
68
69
70
71
		{ id = 'disemvowel', start = {
			text = 'disemvowel'; -- translations should not translate this literally
			desc = "this user's posts will be ritually mutilated in a humorous fashion as appropriate to the script in which they are written; e.g. the removal of vowels in roman text and deletion of kana in japanese text";
		}, stop = {
			text = 're-emvowel';
			desc = "this user's posts will once again appear normally";
		}};









		{ id = 'block', start = {
			text = 'block';
			desc = "this user will not be able to interact with you in any fashion and they will be forced to unfollow you";
		}, stop = {
			text = 'unblock';
			desc = "this user will once again be able to interact with you";







>
>
>
>
>
>
>
>







58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
		{ id = 'disemvowel', start = {
			text = 'disemvowel'; -- translations should not translate this literally
			desc = "this user's posts will be ritually mutilated in a humorous fashion as appropriate to the script in which they are written; e.g. the removal of vowels in roman text and deletion of kana in japanese text";
		}, stop = {
			text = 're-emvowel';
			desc = "this user's posts will once again appear normally";
		}};

		{ id = 'attenuate', start = {
			text = 'attenuate'; 
			desc = "this user will no longer be able to retweet things into your timeline";
		}, stop = {
			text = 'amplify';
			desc = "this user's retweets will be allowed to reach your timeline again";
		}};

		{ id = 'block', start = {
			text = 'block';
			desc = "this user will not be able to interact with you in any fashion and they will be forced to unfollow you";
		}, stop = {
			text = 'unblock';
			desc = "this user will once again be able to interact with you";

Modified render/timeline.t from [900216dd8b] to [5c434424ea].

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
		sz = 0, run = 0
	}
	var fetchmode = lib.store.range {
		mode = 1; -- T->I
		from_time = stoptime;
		to_idx = 64;
	}

	if mode == modes.follow or mode == modes.mutual then
		posts = co.srv:timeline_actor_fetch_uid(co.who.id,fetchmode)
	elseif mode == [modes['local']] then
		posts = co.srv:timeline_instance_fetch(fetchmode)
	elseif mode == modes.fedi then
	elseif mode == modes.circle then







	end

	var acc = co:stra(1024)
	var modelabels = arrayof(pstr, 'followed', 'mutuals', 'local instance', 'fediverse', 'circle')

	var modelinks = arrayof(pstr, [modes.members])
	acc:lpush('<div style="text-align: right"><em>showing ')

	for i=0, [modelabels.type.N] do
		if co.aid ~= 0 or not requires_login(i) then
			if i > 0 then acc:lpush(' · ') end
			if i == mode and not (mode == modes.circle and spec:ref()) then
				acc:lpush('<strong>'):ppush(modelabels[i]):lpush('</strong>')
			else
				acc:lpush('<a href="/tl/'):ppush(modelinks[i]):lpush('">'):ppush(modelabels[i]):lpush('</a>')






			end
		end
	end
	acc:lpush('</em></div>')
	var newest: lib.store.timepoint = 0
	if mode == modes.circle and not spec then
		var circles = co.srv:circle_search(&co.srv.pool, co.who.id, 0)
		acc:lpush '<menu class="circles">'
		for i:intptr = 0, circles.ct do
			acc:lpush '<li><a href="/tl/circle/'
			   :shpush(circles(i).cid)



			   :lpush '">'
			   :ppush(circles(i).name)
			   :lpush '</a></li>'
		end
		-- TODO list circles
		acc:lpush '</menu>'
	else
		acc:lpush('<div id="tl" data-live="10">')
		for i = 0, posts.sz do
			var author = co:uid2actor(posts(i).ptr.author)
			if mode == modes.mutual and posts(i).ptr.author ~= co.who.id then
				if not author.relationship.recip.follow() then goto skip end
			end
			if author.relationship.rel.mute() or 
			   author.relationship.rel.avoid() or 
			   author.relationship.recip.exclude() then goto skip end







			lib.render.tweet(co, posts(i).ptr, &acc)
			var t = lib.math.biggest(lib.math.biggest(posts(i).ptr.posted, posts(i).ptr.discovered),posts(i).ptr.edited)
			if t > newest then newest = t end
			::skip:: posts(i):free()
		end
		if posts.run > 0 then posts:free() end
		acc:lpush('</div>')







>





|
>
>
>
>
>
>
>



|
>

<
>



|


|
>
>
>
>
>
>



|





|

>
>
>
|

|













>
>
>
>
>
>
>







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
		sz = 0, run = 0
	}
	var fetchmode = lib.store.range {
		mode = 1; -- T->I
		from_time = stoptime;
		to_idx = 64;
	}
	var circ: lib.mem.ptr(lib.store.circle) = nil
	if mode == modes.follow or mode == modes.mutual then
		posts = co.srv:timeline_actor_fetch_uid(co.who.id,fetchmode)
	elseif mode == [modes['local']] then
		posts = co.srv:timeline_instance_fetch(fetchmode)
	elseif mode == modes.fedi then
	elseif mode == modes.circle and spec:ref() then
		var cid, ok = lib.math.shorthand.parse(spec.ptr,spec.ct)
		if ok then
			circ = co.srv:circle_search(&co.srv.pool,co.who.id,cid)
			if circ.ct == 1 then
				posts = co.srv:timeline_circle_fetch(cid,fetchmode)
			end
		end
	end

	var acc = co:stra(1024)
	var modelabels = arrayof(pstr, '<u>f</u>ollowed', 'm<u>u</u>tuals', '<u>l</u>ocal instance', 'fedi<u>v</u>erse', 'ci<u>r</u>cle')
	var keybinds = arrayof(pstr, 'f', 'u', 'l', 'v', 'r')
	var modelinks = arrayof(pstr, [modes.members])

	acc:lpush('<div class="kind-picker">showing ')
	for i=0, [modelabels.type.N] do
		if co.aid ~= 0 or not requires_login(i) then
			if i > 0 then acc:lpush(' · ') end
			if i == mode and not circ then
				acc:lpush('<strong>'):ppush(modelabels[i]):lpush('</strong>')
			else
				acc:lpush('<a href="/tl/'):ppush(modelinks[i]):lpush('" accesskey="'):ppush(keybinds[i]):lpush('">')
				if i == mode and circ:ref() then
					acc:lpush'<strong>':ppush(modelabels[i]):lpush'</strong> (':ppush(circ().name):lpush(')')
				else
					acc:ppush(modelabels[i])
				end
				acc:lpush('</a>')
			end
		end
	end
	acc:lpush('</div>')
	var newest: lib.store.timepoint = 0
	if mode == modes.circle and not spec then
		var circles = co.srv:circle_search(&co.srv.pool, co.who.id, 0)
		acc:lpush '<menu class="circles">'
		for i:intptr = 0, circles.ct do
			acc:lpush '<a href="/tl/circle/'
			   :shpush(circles(i).cid)
			if i <= 10 then
				acc:lpush '" accesskey="':ipush((i+1) % 10)
			end
			acc:lpush '">'
			   :ppush(circles(i).name)
			   :lpush '</a>'
		end
		-- TODO list circles
		acc:lpush '</menu>'
	else
		acc:lpush('<div id="tl" data-live="10">')
		for i = 0, posts.sz do
			var author = co:uid2actor(posts(i).ptr.author)
			if mode == modes.mutual and posts(i).ptr.author ~= co.who.id then
				if not author.relationship.recip.follow() then goto skip end
			end
			if author.relationship.rel.mute() or 
			   author.relationship.rel.avoid() or 
			   author.relationship.recip.exclude() then goto skip end
			if posts(i).ptr.rtdby ~= 0 then
				var rter = co:uid2actor(posts(i).ptr.rtdby)
				if rter.relationship.rel.mute()
				or rter.relationship.rel.attenuate()
				or rter.relationship.rel.avoid()
				or rter.relationship.recip.exclude() then goto skip end
			end
			lib.render.tweet(co, posts(i).ptr, &acc)
			var t = lib.math.biggest(lib.math.biggest(posts(i).ptr.posted, posts(i).ptr.discovered),posts(i).ptr.edited)
			if t > newest then newest = t end
			::skip:: posts(i):free()
		end
		if posts.run > 0 then posts:free() end
		acc:lpush('</div>')

Modified render/tweet-page.t from [6d8fb63eed] to [1e93b5b498].

38
39
40
41
42
43
44



45
46
47
48
49
50
51
52
53
54
55
56
57
58
	if co.aid ~= 0 then
		pg:lpush('<form class="action-bar" method="post">')
		if not co.srv:post_liked_uid(co.who.id, p.id)
			then pg:lpush('<button class="pos" name="act" accesskey="l" value="like">like</button>')
			else pg:lpush('<button class="neg" name="act" accesskey="l" value="dislike">dislike</button>')
		end
		pg:lpush('<button class="pos" name="act" accesskey="r" value="rt">retweet</button>')



		if p.author == co.who.id then
			if co.who.rights.powers.edit() then
				pg:lpush('<a class="button" accesskey="e" href="/post/'):rpush(path(1)):lpush('/edit">edit</a>')
			end
			pg:lpush('<a class="neg button" accesskey="d" href="/post/'):rpush(path(1)):lpush('/del">delete</a>')
		elseif co.who.rights.powers.snitch() then
			pg:lpush('<a class="neg button" accesskey="s" href="/post/'):rpush(path(1)):lpush('/report">report</a>')
		end
		-- TODO list user's chosen reaction emoji
		pg:lpush('</form>')

	end
	pg:lpush('<div id="convo" data-live="10">')
	render_tweet_replies(co, &pg, p.id)







>
>
>






|







38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
	if co.aid ~= 0 then
		pg:lpush('<form class="action-bar" method="post">')
		if not co.srv:post_liked_uid(co.who.id, p.id)
			then pg:lpush('<button class="pos" name="act" accesskey="l" value="like">like</button>')
			else pg:lpush('<button class="neg" name="act" accesskey="l" value="dislike">dislike</button>')
		end
		pg:lpush('<button class="pos" name="act" accesskey="r" value="rt">retweet</button>')
		if co.who.rights.powers.crier() then
			pg:lpush('<button name="act" accesskey="p" value="promote">promote</button>')
		end
		if p.author == co.who.id then
			if co.who.rights.powers.edit() then
				pg:lpush('<a class="button" accesskey="e" href="/post/'):rpush(path(1)):lpush('/edit">edit</a>')
			end
			pg:lpush('<a class="neg button" accesskey="d" href="/post/'):rpush(path(1)):lpush('/del">delete</a>')
		elseif co.who.rights.powers.snitch() then
			pg:lpush('<a class="neg button" accesskey="s" href="/post/'):rpush(path(1)):lpush('/snitch">report</a>')
		end
		-- TODO list user's chosen reaction emoji
		pg:lpush('</form>')

	end
	pg:lpush('<div id="convo" data-live="10">')
	render_tweet_replies(co, &pg, p.id)

Modified route.t from [bb5bfba2ee] to [89c9cb6c12].

254
255
256
257
258
259
260
















261
262
263
264
265
266
267
268
...
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
...
480
481
482
483
484
485
486
487

488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
...
519
520
521
522
523
524
525









526
527
528
529
530
531
532
533
534
535
536
537
538
















539
540
541
542
543
544
545
...
674
675
676
677
678
679
680

681
682
683
684
685
686
687
...
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
		end
	end
	defer post:free() -- NOP on null

	if path.ct == 3 then
		var lnk: lib.str.acc lnk:compose('/post/', path(1))
		var lnkp = lnk:finalize() defer lnkp:free()
















		if post:ref() and post(0).author ~= co.who.id then
			co:complain(403, 'forbidden', 'you cannot alter other people\'s posts')
			return
		elseif post:ref() and path(2):cmp(lib.str.lit 'edit') then
			if not co:assertpow('edit') then return end
			if meth_get(meth) then
				lib.render.compose(co, post.ptr, nil)
				return
................................................................................
				else
					conf = data.view.confirm {
						title =  'cancel retweet';
						query =  'are you sure you want to undo this retweet?';
						cancel = '/';
					}
				end
				var fr = co.srv.pool:frame()
				var body = conf:poolstr(&co.srv.pool) --defer body:free()
				co:stdpage([lib.srv.convo.page] {
					title =  'post :: delete';
					class =  'query';
					body = body; cache = false;
				})
				co.srv.pool:reset(fr)
				return
			elseif meth == method.post then
				var act = co:ppostv('act')
				if act:cmp( 'confirm') then
					if post:ref() then
						post(0).source:post_destroy(post(0).id)
					elseif rt.kind ~= 0 then
						co.srv:post_act_cancel(pid)
					end
					co:reroute('/') -- TODO maybe return to parent or conversation if possible
					return
				else goto badop end
			end
................................................................................
			path(1):cmp('brand')
		) then goto nopriv

		elseif not co.who.rights.powers.account() and (
			path(1):cmp('profile') or
			path(1):cmp('sec') or
			path(1):cmp('avi') or
			path(1):cmp('ui')

		) then goto nopriv

		elseif not co.who.rights.powers:affect_users() and (
			path(1):cmp(lib.str.lit 'users')
		) then goto nopriv end
	end

	if meth == method.post and path.ct >= 1 then
		var user_refresh = false var fail = false
		if path(1):cmp(lib.str.lit 'profile') then
			lib.dbg('updating profile')
			co.who.bio = co:postv('bio')._0
			co.who.nym = co:postv('nym')._0
			if co.who.bio ~= nil and @co.who.bio == 0 then co.who.bio = nil end
			if co.who.nym ~= nil and @co.who.nym == 0 then co.who.nym = nil end
			co.who.source:actor_save(co.who)

................................................................................
					co.ui_hue = nhue
				end
			end
			if resethue then
				co.srv:actor_conf_int_reset(co.who.id, 'ui-accent')
				co.ui_hue = co.srv.cfg.ui_hue
			end










			msg = 'profile changes saved'
			--user_refresh = true -- not really necessary here, actually

		elseif path(1):cmp('sec') then
			if not credsec_for_uid(co, co.who.id) then return end
		elseif path(1):cmp('avi') then
			var act = co:ppostv('act')
			if act:ref() and act:cmp('clear') then
				co.who.avatarid = 0
				co.who.source:actor_save(co.who)
				msg = 'avatar reset to default'
			else goto badop end
















		elseif path(1):cmp('users') then
			if path.ct >= 3 then
				var userid, ok = lib.math.shorthand.parse(path(2).ptr, path(2).ct)
				if ok then
					var usr = co.srv:actor_fetch_uid(userid)
					if usr:ref() then --defer usr:free()
						if not co.who:overpowers(usr.ptr) then
................................................................................
		end
	end
	lib.render.conf(co,path,msg)
	do return end

	::nopriv:: do co:complain(403,'insufficient privileges','you do not have the necessary powers to perform this action') return end
	::badop:: do co:complain(400,'bad request','the operation you have requested is not meaningful in this context') return end

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('clear') then
			co.srv:actor_conf_int_set(co.who.id, 'notice-clear-time', lib.osclock.time(nil))
................................................................................
	-- we run through those first before giving up and parsing the URI
	if uri.ptr == nil or uri.ptr[0] ~= @'/' then
		co:complain(404, 'what the hell', 'how did you do that')
	elseif uri.ct == 1 then -- root
		if (co.srv.cfg.pol_sec == lib.srv.secmode.private or
		   co.srv.cfg.pol_sec == lib.srv.secmode.lockdown) and co.aid == 0 then
		   http.login_form(co, meth)
		else http.timeline(co, hpath {ptr=nil}) end
	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('/file/', uri.ptr, 6) == 0 then
		http.file_serve_raw(co, [lib.mem.ptr(int8)] {ptr = uri.ptr + 6, ct = uri.ct - 6})
	elseif uri:cmp( '/notices') then
		if co.aid == 0 then co:reroute('/login') return end
		http.user_notices(co,meth)
	elseif uri:cmp( '/compose') then
		if co.aid == 0 then co:reroute('/login') return end
		http.post_compose(co,meth)
	elseif uri:cmp( '/login') then
		if co.aid == 0
			then http.login_form(co, meth)
			else co:reroute('/')
		end







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|







 







<






<



|

|







 







|
>









|







 







>
>
>
>
>
>
>
>
>













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







 







>







 







|









|


|







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
...
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
...
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
...
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
...
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
...
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
		end
	end
	defer post:free() -- NOP on null

	if path.ct == 3 then
		var lnk: lib.str.acc lnk:compose('/post/', path(1))
		var lnkp = lnk:finalize() defer lnkp:free()
		if post:ref() and path(2):cmp(lib.str.lit 'snitch') then
			if meth_get(meth) then
				var ui = data.view.report {
					badtweet = lib.render.tweet(co, post.ptr, nil);
					clnk = lnkp;
				}

				co:stdpage([lib.srv.convo.page] {
					title = 'post :: report';
					class = 'report';
					body = ui:poolstr(&co.srv.pool);
					cache = false;
				})
			else
			end
			return
		elseif post:ref() and post(0).author ~= co.who.id then
			co:complain(403, 'forbidden', 'you cannot alter other people\'s posts')
			return
		elseif post:ref() and path(2):cmp(lib.str.lit 'edit') then
			if not co:assertpow('edit') then return end
			if meth_get(meth) then
				lib.render.compose(co, post.ptr, nil)
				return
................................................................................
				else
					conf = data.view.confirm {
						title =  'cancel retweet';
						query =  'are you sure you want to undo this retweet?';
						cancel = '/';
					}
				end

				var body = conf:poolstr(&co.srv.pool) --defer body:free()
				co:stdpage([lib.srv.convo.page] {
					title =  'post :: delete';
					class =  'query';
					body = body; cache = false;
				})

				return
			elseif meth == method.post then
				var act = co:ppostv('act')
				if act:cmp('confirm') then
					if post:ref() then
						post().source:post_destroy(post().id)
					elseif rt.kind ~= 0 then
						co.srv:post_act_cancel(pid)
					end
					co:reroute('/') -- TODO maybe return to parent or conversation if possible
					return
				else goto badop end
			end
................................................................................
			path(1):cmp('brand')
		) then goto nopriv

		elseif not co.who.rights.powers.account() and (
			path(1):cmp('profile') or
			path(1):cmp('sec') or
			path(1):cmp('avi') or
			path(1):cmp('ui') or
			path(1):cmp('circles')
		) then goto nopriv

		elseif not co.who.rights.powers:affect_users() and (
			path(1):cmp(lib.str.lit 'users')
		) then goto nopriv end
	end

	if meth == method.post and path.ct >= 1 then
		var user_refresh = false var fail = false
		if path(1):cmp('profile') then
			lib.dbg('updating profile')
			co.who.bio = co:postv('bio')._0
			co.who.nym = co:postv('nym')._0
			if co.who.bio ~= nil and @co.who.bio == 0 then co.who.bio = nil end
			if co.who.nym ~= nil and @co.who.nym == 0 then co.who.nym = nil end
			co.who.source:actor_save(co.who)

................................................................................
					co.ui_hue = nhue
				end
			end
			if resethue then
				co.srv:actor_conf_int_reset(co.who.id, 'ui-accent')
				co.ui_hue = co.srv.cfg.ui_hue
			end

			var aclfollow = co:ppostv('acl-follow')
			var aclfollowreq = co:ppostv('acl-follow-req')
			if aclfollow:ref() and aclfollow.ct > 0 then
				co.srv:actor_conf_str_set(co.who.id, 'acl-follow', aclfollow)
			end
			if aclfollowreq:ref() and aclfollowreq.ct > 0 then
				co.srv:actor_conf_str_set(co.who.id, 'acl-follow-req', aclfollowreq)
			end

			msg = 'profile changes saved'
			--user_refresh = true -- not really necessary here, actually

		elseif path(1):cmp('sec') then
			if not credsec_for_uid(co, co.who.id) then return end
		elseif path(1):cmp('avi') then
			var act = co:ppostv('act')
			if act:ref() and act:cmp('clear') then
				co.who.avatarid = 0
				co.who.source:actor_save(co.who)
				msg = 'avatar reset to default'
			else goto badop end
		elseif path(1):cmp('circles') then
			if meth == method.post then
				var act = co:ppostv('act')
				if path.ct == 2 and act:cmp('create') then
					var newcirc = co:ppostv('name')
					if newcirc.ct > 0 then
						co.srv:circle_create(co.who.id, newcirc)
					end
				elseif path.ct == 3 and act:cmp('del') then
					var id, ok = lib.math.shorthand.parse(path(2).ptr,path(2).ct)
					if not ok then goto e404 end
					co.srv:circle_destroy(co.who.id, id)
					co:reroute('/conf/circles')
					return
				else goto badop end
			end
		elseif path(1):cmp('users') then
			if path.ct >= 3 then
				var userid, ok = lib.math.shorthand.parse(path(2).ptr, path(2).ct)
				if ok then
					var usr = co.srv:actor_fetch_uid(userid)
					if usr:ref() then --defer usr:free()
						if not co.who:overpowers(usr.ptr) then
................................................................................
		end
	end
	lib.render.conf(co,path,msg)
	do return end

	::nopriv:: do co:complain(403,'insufficient privileges','you do not have the necessary powers to perform this action') return end
	::badop:: do co:complain(400,'bad request','the operation you have requested is not meaningful in this context') return end
	::e404:: do co:complain(404,'not found','the resource you have requested is not known to this server') return end
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('clear') then
			co.srv:actor_conf_int_set(co.who.id, 'notice-clear-time', lib.osclock.time(nil))
................................................................................
	-- we run through those first before giving up and parsing the URI
	if uri.ptr == nil or uri.ptr[0] ~= @'/' then
		co:complain(404, 'what the hell', 'how did you do that')
	elseif uri.ct == 1 then -- root
		if (co.srv.cfg.pol_sec == lib.srv.secmode.private or
		   co.srv.cfg.pol_sec == lib.srv.secmode.lockdown) and co.aid == 0 then
		   http.login_form(co, meth)
		else http.timeline(co, hpath {ptr=nil,ct=0}) end
	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('/file/', uri.ptr, 6) == 0 then
		http.file_serve_raw(co, [lib.mem.ptr(int8)] {ptr = uri.ptr + 6, ct = uri.ct - 6})
	elseif uri:cmp('/notices') then
		if co.aid == 0 then co:reroute('/login') return end
		http.user_notices(co,meth)
	elseif uri:cmp('/compose') then
		if co.aid == 0 then co:reroute('/login') return end
		http.post_compose(co,meth)
	elseif uri:cmp( '/login') then
		if co.aid == 0
			then http.login_form(co, meth)
			else co:reroute('/')
		end

Modified srv.t from [6a947d38ca] to [36e7e8aa84].

15
16
17
18
19
20
21



22
23
24
25
26
27
28
..
30
31
32
33
34
35
36


37
38
39
40
41
42
43
..
66
67
68
69
70
71
72

73
74
75
76
77
78
79
...
163
164
165
166
167
168
169




























170
171
172
173
174
175
176
....
1078
1079
1080
1081
1082
1083
1084



1085
1086
1087
1088
1089
1090
1091
1092
	overlord: &srv
	ui_cue_staff: pstring
	ui_cue_founder: pstring
	ui_hue: uint16
	nranks: uint16
	maxinvites: uint16
	master: uint64



}
local struct srv {
	sources: lib.mem.ptr(lib.store.source)
	webmgr: lib.net.mg_mgr
	webcon: &lib.net.mg_connection
	cfg: cfgcache
	id: rawstring
................................................................................
}

terra cfgcache:free() -- :/
	self.secret:free()
	self.instance:free()
	self.ui_cue_staff:free()
	self.ui_cue_founder:free()


end

terra srv:post_enum_author_uid(uid: uint64, r: lib.store.range): lib.mem.vec(lib.mem.ptr(lib.store.post))
	var all: lib.mem.vec(lib.mem.ptr(lib.store.post)) all:init(64)
	for i=0,self.sources.ct do var src = self.sources.ptr + i
		if src.handle ~= nil and src.backend.timeline_instance_fetch ~= nil then
			var lst = src:post_enum_author_uid(uid,r)
................................................................................
		end
		return all
	end
end

deftlfetch('instance_fetch')
deftlfetch('actor_fetch_uid', uint64)


srv.metamethods.__methodmissing = macro(function(meth, self, ...)
	local primary, ptr, stat, simple, oid = 0,1,2,3,4
	local tk, rt = primary
	local expr = {...}
	for _,f in pairs(lib.store.backend.entries) do
		local fn = f.field or f[1]
................................................................................

struct convo.page {
	title: pstring
	body: pstring
	class: pstring
	cache: bool
}





























-- this is unfortunately necessary to work around a terra bug
-- it can't seem to handle forward-declarations of structs in C

local getpeer
do local struct strucheader {
		next: &lib.net.mg_connection
................................................................................
			self.master = wma(0).id
			wma:free()
		end
	end

	self.ui_cue_staff = self.overlord:conf_get('ui-profile-cue-staff')
	self.ui_cue_founder = self.overlord:conf_get('ui-profile-cue-master')



end

return {
	overlord = srv;
	convo = convo;
	route = route;
	secmode = secmode;
}







>
>
>







 







>
>







 







>







 







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







 







>
>
>








15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
..
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
..
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
...
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
....
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
	overlord: &srv
	ui_cue_staff: pstring
	ui_cue_founder: pstring
	ui_hue: uint16
	nranks: uint16
	maxinvites: uint16
	master: uint64

	usrdef_pol_follow: pstring
	usrdef_pol_follow_req: pstring
}
local struct srv {
	sources: lib.mem.ptr(lib.store.source)
	webmgr: lib.net.mg_mgr
	webcon: &lib.net.mg_connection
	cfg: cfgcache
	id: rawstring
................................................................................
}

terra cfgcache:free() -- :/
	self.secret:free()
	self.instance:free()
	self.ui_cue_staff:free()
	self.ui_cue_founder:free()
	self.usrdef_pol_follow:free()
	self.usrdef_pol_follow_req:free()
end

terra srv:post_enum_author_uid(uid: uint64, r: lib.store.range): lib.mem.vec(lib.mem.ptr(lib.store.post))
	var all: lib.mem.vec(lib.mem.ptr(lib.store.post)) all:init(64)
	for i=0,self.sources.ct do var src = self.sources.ptr + i
		if src.handle ~= nil and src.backend.timeline_instance_fetch ~= nil then
			var lst = src:post_enum_author_uid(uid,r)
................................................................................
		end
		return all
	end
end

deftlfetch('instance_fetch')
deftlfetch('actor_fetch_uid', uint64)
deftlfetch('circle_fetch', uint64)

srv.metamethods.__methodmissing = macro(function(meth, self, ...)
	local primary, ptr, stat, simple, oid = 0,1,2,3,4
	local tk, rt = primary
	local expr = {...}
	for _,f in pairs(lib.store.backend.entries) do
		local fn = f.field or f[1]
................................................................................

struct convo.page {
	title: pstring
	body: pstring
	class: pstring
	cache: bool
}

local usrdefs = {
	str = {
		['acl-follow'    ] = {cfgfld = 'usrdef_pol_follow', fallback = 'local'};
		['acl-follow-req'] = {cfgfld = 'usrdef_pol_follow_req', fallback = 'all'};
	};
}

terra convo:usercfg_str(uid: uint64, setting: pstring): pstring
	var set = self.srv:actor_conf_str_get(&self.srv.pool, uid, setting)
	if not set then
		[(function()
			local q = quote return pstring.null() end
			for key, dfl in pairs(usrdefs.str) do
				local rv
				if dfl.cfgfld then
					rv = quote
						var cf = self.srv.cfg.[dfl.cfgfld]
					in terralib.select(not cf, pstring([dfl.fallback]), cf) end
				elseif dfl.lit then rv = dfl.lit end
				q = quote
					if setting:cmp([key]) then return [rv] else [q] end
				end
			end
			return q
		end)()]
	else return set end
end

-- this is unfortunately necessary to work around a terra bug
-- it can't seem to handle forward-declarations of structs in C

local getpeer
do local struct strucheader {
		next: &lib.net.mg_connection
................................................................................
			self.master = wma(0).id
			wma:free()
		end
	end

	self.ui_cue_staff = self.overlord:conf_get('ui-profile-cue-staff')
	self.ui_cue_founder = self.overlord:conf_get('ui-profile-cue-master')

	self.usrdef_pol_follow = self.overlord:conf_get('user-default-acl-follow')
	self.usrdef_pol_follow_req = self.overlord:conf_get('user-default-acl-follow-req')
end

return {
	overlord = srv;
	convo = convo;
	route = route;
	secmode = secmode;
}

Added static/gear.svg version [0623970aec].







































































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
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
<?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="0.2in"
   height="0.2in"
   viewBox="0 0 5.0800002 5.0800002"
   version="1.1"
   id="svg8"
   inkscape:version="0.92.4 (5da689c313, 2019-01-14)"
   sodipodi:docname="gear.svg">
  <defs
     id="defs2">
    <linearGradient
       inkscape:collect="always"
       id="linearGradient863">
      <stop
         style="stop-color:#ffffff;stop-opacity:0.5518868"
         offset="0"
         id="stop859" />
      <stop
         style="stop-color:#ffffff;stop-opacity:0;"
         offset="1"
         id="stop861" />
    </linearGradient>
    <radialGradient
       inkscape:collect="always"
       xlink:href="#linearGradient863"
       id="radialGradient865"
       cx="2.5399754"
       cy="295.73944"
       fx="2.5399754"
       fy="295.91135"
       r="1.6620824"
       gradientTransform="matrix(2.9231276,0,0,2.7982783,-11.452033,-531.55554)"
       gradientUnits="userSpaceOnUse" />
    <radialGradient
       inkscape:collect="always"
       xlink:href="#linearGradient863"
       id="radialGradient865-3"
       cx="2.5399754"
       cy="295.73944"
       fx="2.5399754"
       fy="295.91135"
       r="1.6620824"
       gradientTransform="matrix(11.048041,0,0,10.57617,-21.779867,-3114.7234)"
       gradientUnits="userSpaceOnUse" />
    <radialGradient
       inkscape:collect="always"
       xlink:href="#linearGradient863"
       id="radialGradient980"
       gradientUnits="userSpaceOnUse"
       gradientTransform="matrix(2.9231276,0,0,2.7982783,-11.452033,-531.55554)"
       cx="4.5320868"
       cy="295.40598"
       fx="4.5320868"
       fy="295.57788"
       r="1.6620824" />
    <radialGradient
       inkscape:collect="always"
       xlink:href="#linearGradient863"
       id="radialGradient980-9"
       gradientUnits="userSpaceOnUse"
       gradientTransform="matrix(11.048041,0,0,10.57617,-45.140697,-3114.3717)"
       cx="4.5320868"
       cy="295.40598"
       fx="4.5320868"
       fy="295.57788"
       r="1.6620824" />
    <radialGradient
       inkscape:collect="always"
       xlink:href="#linearGradient863"
       id="radialGradient1008"
       gradientUnits="userSpaceOnUse"
       gradientTransform="matrix(2.9231276,0,0,2.7982783,-11.452033,-531.55554)"
       cx="5.1548281"
       cy="294.85077"
       fx="5.1548281"
       fy="295.02267"
       r="1.6620824" />
    <radialGradient
       inkscape:collect="always"
       xlink:href="#linearGradient863"
       id="radialGradient1028"
       gradientUnits="userSpaceOnUse"
       gradientTransform="matrix(2.7672014,0,0,2.6490119,-10.744057,-487.4606)"
       cx="4.5320868"
       cy="295.40598"
       fx="4.5320868"
       fy="295.57788"
       r="1.6620824" />
    <radialGradient
       inkscape:collect="always"
       xlink:href="#linearGradient863"
       id="radialGradient1031"
       gradientUnits="userSpaceOnUse"
       gradientTransform="matrix(11.048041,0,0,10.57617,-43.283274,-3112.3484)"
       cx="4.5320868"
       cy="295.40598"
       fx="4.5320868"
       fy="295.57788"
       r="1.6620824" />
  </defs>
  <sodipodi:namedview
     id="base"
     pagecolor="#2f000f"
     bordercolor="#666666"
     borderopacity="1.0"
     inkscape:pageopacity="0"
     inkscape:pageshadow="2"
     inkscape:zoom="11.2"
     inkscape:cx="5.6507928"
     inkscape:cy="22.571234"
     inkscape:document-units="mm"
     inkscape:current-layer="layer1"
     showgrid="false"
     units="in"
     inkscape:window-width="1920"
     inkscape:window-height="1042"
     inkscape:window-x="0"
     inkscape:window-y="38"
     inkscape:window-maximized="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(0,-291.91998)">
    <path
       style="opacity:1;vector-effect:none;fill:url(#radialGradient1031);fill-opacity:1;stroke:none;stroke-width:0.99999994px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
       d="M 12.113281 2.0234375 A 4.1239589 4.1239589 0 0 0 11.5625 2.1816406 A 4.1239589 4.1239589 0 0 0 11.044922 2.4238281 L 11.435547 3.4628906 A 3.0271613 3.0271613 0 0 0 10.544922 4.2949219 L 9.5332031 3.8359375 A 4.1239589 4.1239589 0 0 0 9.0625 4.875 L 10.068359 5.3320312 A 3.0271613 3.0271613 0 0 0 10.029297 6.5527344 L 8.9921875 6.9414062 A 4.1239589 4.1239589 0 0 0 9.1503906 7.4921875 A 4.1239589 4.1239589 0 0 0 9.3925781 8.0117188 L 10.429688 7.6210938 A 3.0271613 3.0271613 0 0 0 11.263672 8.5117188 L 10.804688 9.5234375 A 4.1239589 4.1239589 0 0 0 11.84375 9.9921875 L 12.302734 8.984375 A 3.0271613 3.0271613 0 0 0 13.521484 9.0253906 L 13.910156 10.0625 A 4.1239589 4.1239589 0 0 0 14.458984 9.90625 A 4.1239589 4.1239589 0 0 0 14.978516 9.6640625 L 14.587891 8.625 A 3.0271613 3.0271613 0 0 0 15.478516 7.7910156 L 16.490234 8.2519531 A 4.1239589 4.1239589 0 0 0 16.958984 7.2109375 L 15.953125 6.7539062 A 3.0271613 3.0271613 0 0 0 15.994141 5.5351562 L 17.03125 5.1464844 A 4.1239589 4.1239589 0 0 0 16.875 4.5957031 A 4.1239589 4.1239589 0 0 0 16.630859 4.0761719 L 15.591797 4.4667969 A 3.0271613 3.0271613 0 0 0 14.759766 3.5761719 L 15.21875 2.5664062 A 4.1239589 4.1239589 0 0 0 14.179688 2.09375 L 13.720703 3.1015625 A 3.0271613 3.0271613 0 0 0 12.501953 3.0605469 L 12.113281 2.0234375 z M 13.074219 4.421875 A 1.6230732 1.5835511 84.711522 0 1 14.494141 5.4765625 A 1.6230732 1.5835511 84.711522 0 1 13.572266 7.5644531 A 1.6230732 1.5835511 84.711522 0 1 11.529297 6.609375 A 1.6230732 1.5835511 84.711522 0 1 12.453125 4.5214844 A 1.6230732 1.5835511 84.711522 0 1 12.916016 4.4238281 A 1.6230732 1.5835511 84.711522 0 1 13.074219 4.421875 z "
       transform="matrix(0.26458333,0,0,0.26458333,0,291.91998)"
       id="path1024" />
    <path
       style="opacity:1;vector-effect:none;fill:url(#radialGradient1028);fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
       d="m 1.8205469,293.82898 a 1.2578638,1.2578638 0 0 0 -0.1824712,0.0142 v 0.29596 a 0.96596175,0.96596175 0 0 0 -0.359072,0.14921 l -0.2093772,-0.20938 a 1.2578638,1.2578638 0 0 0 -0.25731862,0.25781 l 0.20839872,0.2084 a 0.96596175,0.96596175 0 0 0 -0.14871646,0.35956 H 0.57749238 a 1.2578638,1.2578638 0 0 0 -0.0151652,0.18198 1.2578638,1.2578638 0 0 0 0.014676,0.18247 h 0.29498693 a 0.96596175,0.96596175 0 0 0 0.14920569,0.35956 l -0.20839876,0.20791 a 1.2578638,1.2578638 0 0 0 0.25731866,0.25781 l 0.2083988,-0.2084 a 0.96596175,0.96596175 0 0 0 0.3595612,0.14921 v 0.2945 a 1.2578638,1.2578638 0 0 0 0.1824712,0.0147 1.2578638,1.2578638 0 0 0 0.1814929,-0.0142 v -0.29547 a 0.96596175,0.96596175 0 0 0 0.3595612,-0.14872 l 0.2088879,0.20889 a 1.2578638,1.2578638 0 0 0 0.2573188,-0.25732 l -0.208399,-0.2084 a 0.96596175,0.96596175 0 0 0 0.1487165,-0.36005 h 0.2954763 a 1.2578638,1.2578638 0 0 0 0.014675,-0.18247 1.2578638,1.2578638 0 0 0 -0.014187,-0.18198 H 2.7681252 a 0.96596175,0.96596175 0 0 0 -0.1487165,-0.35907 l 0.2088881,-0.20938 a 1.2578638,1.2578638 0 0 0 -0.2578079,-0.25732 l -0.2083987,0.2084 a 0.96596175,0.96596175 0 0 0 -0.3600504,-0.14872 v -0.29498 a 1.2578638,1.2578638 0 0 0 -0.1814929,-0.0147 z m 0,0.67656 a 0.58097694,0.58097694 0 0 1 0.5806792,0.58117 0.58097694,0.58097694 0 0 1 -0.5806792,0.58068 0.58097694,0.58097694 0 0 1 -0.5811683,-0.58068 0.58097694,0.58097694 0 0 1 0.5811683,-0.58117 z"
       id="path1020"
       inkscape:connector-curvature="0" />
    <path
       id="circle917"
       style="opacity:1;vector-effect:none;fill:url(#radialGradient1008);fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;font-variant-east_asian:normal"
       d="m 3.6161875,293.51895 a 0.1735429,0.1735429 0 0 1 -0.1735429,0.17354 0.1735429,0.1735429 0 0 1 -0.1735429,-0.17354 0.1735429,0.1735429 0 0 1 0.1735429,-0.17354 0.1735429,0.1735429 0 0 1 0.1735429,0.17354 z m -1.5573776,1.56775 a 0.23850755,0.23850755 0 0 1 -0.2385075,0.23851 0.23850755,0.23850755 0 0 1 -0.2385076,-0.23851 0.23850755,0.23850755 0 0 1 0.2385076,-0.23851 0.23850755,0.23850755 0 0 1 0.2385075,0.23851 z" />
  </g>
</svg>

Added static/logo.svg version [0ada266140].































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
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
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
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
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
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
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
<?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="1in"
   height="1in"
   viewBox="0 0 25.4 25.400001"
   version="1.1"
   id="svg8"
   inkscape:version="0.92.4 (5da689c313, 2019-01-14)"
   sodipodi:docname="logo.svg">
  <defs
     id="defs2">
    <linearGradient
       id="linearGradient920"
       inkscape:collect="always">
      <stop
         id="stop916"
         offset="0"
         style="stop-color:#f8bac8;stop-opacity:1" />
      <stop
         id="stop918"
         offset="1"
         style="stop-color:#ea2b55;stop-opacity:0" />
    </linearGradient>
    <linearGradient
       id="linearGradient3597"
       inkscape:collect="always">
      <stop
         id="stop3593"
         offset="0"
         style="stop-color:#bc0087;stop-opacity:1" />
      <stop
         id="stop3595"
         offset="1"
         style="stop-color:#6f004e;stop-opacity:1" />
    </linearGradient>
    <marker
       inkscape:stockid="Arrow1Lstart"
       orient="auto"
       refY="0.0"
       refX="0.0"
       id="marker3321"
       style="overflow:visible"
       inkscape:isstock="true">
      <path
         id="path3319"
         d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
         style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1;fill:#5e003e;fill-opacity:1"
         transform="scale(0.8) translate(12.5,0)" />
    </marker>
    <marker
       inkscape:stockid="Arrow1Lstart"
       orient="auto"
       refY="0.0"
       refX="0.0"
       id="marker3281"
       style="overflow:visible"
       inkscape:isstock="true">
      <path
         id="path3279"
         d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
         style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1;fill:#5e003e;fill-opacity:1"
         transform="scale(0.8) translate(12.5,0)" />
    </marker>
    <marker
       inkscape:stockid="Arrow1Lstart"
       orient="auto"
       refY="0.0"
       refX="0.0"
       id="marker3247"
       style="overflow:visible"
       inkscape:isstock="true">
      <path
         id="path3245"
         d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
         style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1;fill:#5e003e;fill-opacity:1"
         transform="scale(0.8) translate(12.5,0)" />
    </marker>
    <marker
       inkscape:stockid="Arrow1Lstart"
       orient="auto"
       refY="0.0"
       refX="0.0"
       id="marker3219"
       style="overflow:visible"
       inkscape:isstock="true">
      <path
         id="path3217"
         d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
         style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1;fill:#5e003e;fill-opacity:1"
         transform="scale(0.8) translate(12.5,0)" />
    </marker>
    <marker
       inkscape:stockid="Arrow1Lstart"
       orient="auto"
       refY="0.0"
       refX="0.0"
       id="marker3197"
       style="overflow:visible"
       inkscape:isstock="true">
      <path
         id="path3195"
         d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
         style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1;fill:#5e003e;fill-opacity:1"
         transform="scale(0.8) translate(12.5,0)" />
    </marker>
    <marker
       inkscape:stockid="Arrow1Lstart"
       orient="auto"
       refY="0.0"
       refX="0.0"
       id="Arrow1Lstart"
       style="overflow:visible"
       inkscape:isstock="true">
      <path
         id="path2919"
         d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
         style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1;fill:#5e003e;fill-opacity:1"
         transform="scale(0.8) translate(12.5,0)" />
    </marker>
    <linearGradient
       inkscape:collect="always"
       id="linearGradient2907">
      <stop
         style="stop-color:#4d003a;stop-opacity:0.12264151"
         offset="0"
         id="stop2903" />
      <stop
         style="stop-color:#4d003a;stop-opacity:0.91509432"
         offset="1"
         id="stop2905" />
    </linearGradient>
    <linearGradient
       id="linearGradient2893"
       inkscape:collect="always">
      <stop
         id="stop2889"
         offset="0"
         style="stop-color:#e00092;stop-opacity:1;" />
      <stop
         id="stop2891"
         offset="1"
         style="stop-color:#e00092;stop-opacity:0.01886792" />
    </linearGradient>
    <linearGradient
       inkscape:collect="always"
       id="linearGradient1775">
      <stop
         style="stop-color:#fef5f7;stop-opacity:1;"
         offset="0"
         id="stop1771" />
      <stop
         style="stop-color:#ea2b55;stop-opacity:0"
         offset="1"
         id="stop1773" />
    </linearGradient>
    <linearGradient
       inkscape:collect="always"
       id="linearGradient929">
      <stop
         style="stop-color:#e00092;stop-opacity:1;"
         offset="0"
         id="stop925" />
      <stop
         style="stop-color:#e00092;stop-opacity:0"
         offset="1"
         id="stop927" />
    </linearGradient>
    <linearGradient
       inkscape:collect="always"
       id="linearGradient871">
      <stop
         style="stop-color:#fff8fd;stop-opacity:1;"
         offset="0"
         id="stop867" />
      <stop
         style="stop-color:#ff85dc;stop-opacity:1"
         offset="1"
         id="stop869" />
    </linearGradient>
    <linearGradient
       inkscape:collect="always"
       id="linearGradient863">
      <stop
         style="stop-color:#ff0ebb;stop-opacity:1"
         offset="0"
         id="stop859" />
      <stop
         style="stop-color:#98006b;stop-opacity:1"
         offset="1"
         id="stop861" />
    </linearGradient>
    <linearGradient
       inkscape:collect="always"
       id="linearGradient855">
      <stop
         style="stop-color:#ff1ab2;stop-opacity:1"
         offset="0"
         id="stop851" />
      <stop
         style="stop-color:#5e003e;stop-opacity:1"
         offset="1"
         id="stop853" />
    </linearGradient>
    <linearGradient
       inkscape:collect="always"
       xlink:href="#linearGradient855"
       id="linearGradient857"
       x1="204.78238"
       y1="192.05012"
       x2="215.23915"
       y2="192.05012"
       gradientUnits="userSpaceOnUse"
       gradientTransform="matrix(1.1660945,0,0,1.1660945,-34.675674,-31.692532)" />
    <radialGradient
       inkscape:collect="always"
       xlink:href="#linearGradient871"
       id="radialGradient873"
       cx="12.700097"
       cy="284.29999"
       fx="12.700097"
       fy="284.29999"
       r="4.7926919"
       gradientTransform="matrix(1.5582358,0,0,1.558162,-7.0897352,-158.99167)"
       gradientUnits="userSpaceOnUse" />
    <linearGradient
       inkscape:collect="always"
       xlink:href="#linearGradient2893"
       id="linearGradient931"
       x1="10.734375"
       y1="68.553711"
       x2="74.320656"
       y2="68.553711"
       gradientUnits="userSpaceOnUse"
       gradientTransform="matrix(0.30281136,0,0,0.30279703,-1.8350659,269.43243)" />
    <linearGradient
       inkscape:collect="always"
       xlink:href="#linearGradient929"
       id="linearGradient935"
       gradientUnits="userSpaceOnUse"
       gradientTransform="matrix(-0.30281136,0,0,-0.30279703,27.235067,299.16751)"
       x1="10.734375"
       y1="68.553711"
       x2="72.836235"
       y2="68.553711" />
    <radialGradient
       inkscape:collect="always"
       xlink:href="#linearGradient1775"
       id="radialGradient1777"
       cx="35.70248"
       cy="275.61121"
       fx="35.70248"
       fy="275.61121"
       r="5.0648633"
       gradientTransform="matrix(2.6183476,-4.2817295e-8,0,0.33968294,-59.424913,181.99078)"
       gradientUnits="userSpaceOnUse" />
    <radialGradient
       inkscape:collect="always"
       xlink:href="#linearGradient1775"
       id="radialGradient1785"
       cx="54.650433"
       cy="276.4209"
       fx="55.498787"
       fy="276.4209"
       r="5.0648627"
       gradientTransform="matrix(-2.5056647,-4.662863e-8,0,-0.4043156,191.5861,387.46216)"
       gradientUnits="userSpaceOnUse" />
    <radialGradient
       inkscape:collect="always"
       xlink:href="#linearGradient1775"
       id="radialGradient1787"
       gradientUnits="userSpaceOnUse"
       gradientTransform="matrix(-2.5354014,-4.7182009e-8,0,-0.40911394,193.02586,388.9687)"
       cx="54.650433"
       cy="276.4209"
       fx="55.498787"
       fy="276.4209"
       r="5.0648627" />
    <radialGradient
       inkscape:collect="always"
       xlink:href="#linearGradient1775"
       id="radialGradient1789"
       gradientUnits="userSpaceOnUse"
       gradientTransform="matrix(-2.5354014,-4.7182009e-8,0,-0.40911394,193.02586,388.9687)"
       cx="54.650433"
       cy="276.4209"
       fx="55.498787"
       fy="276.4209"
       r="5.0648627" />
    <radialGradient
       inkscape:collect="always"
       xlink:href="#linearGradient1775"
       id="radialGradient1791"
       gradientUnits="userSpaceOnUse"
       gradientTransform="matrix(-2.5056647,-4.662863e-8,0,-0.4043156,191.5861,387.46216)"
       cx="54.650433"
       cy="276.4209"
       fx="55.498787"
       fy="276.4209"
       r="5.0648627" />
    <radialGradient
       inkscape:collect="always"
       xlink:href="#linearGradient1775"
       id="radialGradient1793"
       gradientUnits="userSpaceOnUse"
       gradientTransform="matrix(2.6183476,-4.2817295e-8,0,0.33968294,-59.424913,181.99078)"
       cx="35.70248"
       cy="275.61121"
       fx="35.70248"
       fy="275.61121"
       r="5.0648633" />
    <radialGradient
       inkscape:collect="always"
       xlink:href="#linearGradient1775"
       id="radialGradient1795"
       gradientUnits="userSpaceOnUse"
       gradientTransform="matrix(2.6183476,-4.2817295e-8,0,0.33968294,-59.424913,181.99078)"
       cx="35.70248"
       cy="275.61121"
       fx="35.70248"
       fy="275.61121"
       r="5.0648633" />
    <radialGradient
       inkscape:collect="always"
       xlink:href="#linearGradient1775"
       id="radialGradient1797"
       gradientUnits="userSpaceOnUse"
       gradientTransform="matrix(2.6183476,-4.2817295e-8,0,0.33968294,-59.424913,181.99078)"
       cx="35.70248"
       cy="275.61121"
       fx="35.70248"
       fy="275.61121"
       r="5.0648633" />
    <linearGradient
       inkscape:collect="always"
       xlink:href="#linearGradient855"
       id="linearGradient1805"
       gradientUnits="userSpaceOnUse"
       gradientTransform="matrix(1.1660945,0,0,1.1660945,-68.579304,2.5050309)"
       x1="204.78238"
       y1="192.05012"
       x2="215.23915"
       y2="192.05012" />
    <linearGradient
       inkscape:collect="always"
       xlink:href="#linearGradient863"
       id="linearGradient1807"
       gradientUnits="userSpaceOnUse"
       gradientTransform="matrix(1.1660945,0,0,1.1660945,-50.264227,-46.989234)"
       x1="20.094147"
       y1="284.52951"
       x2="12.70009"
       y2="277.13544" />
    <linearGradient
       inkscape:collect="always"
       xlink:href="#linearGradient855"
       id="linearGradient1812"
       gradientUnits="userSpaceOnUse"
       gradientTransform="matrix(0.94368798,0.94364332,-0.94368798,0.94364332,-4.2492401,-95.102051)"
       x1="204.78238"
       y1="192.05012"
       x2="215.23915"
       y2="192.05012" />
    <radialGradient
       inkscape:collect="always"
       xlink:href="#linearGradient1775"
       id="radialGradient2879"
       gradientUnits="userSpaceOnUse"
       gradientTransform="matrix(-2.5056647,-4.662863e-8,0,-0.4043156,191.5861,387.46216)"
       cx="54.650433"
       cy="276.4209"
       fx="55.498787"
       fy="276.4209"
       r="5.0648627" />
    <radialGradient
       inkscape:collect="always"
       xlink:href="#linearGradient1775"
       id="radialGradient2901"
       gradientUnits="userSpaceOnUse"
       gradientTransform="matrix(-2.5354014,-4.7182009e-8,0,-0.40911394,193.02586,388.9687)"
       cx="54.650433"
       cy="276.4209"
       fx="55.498787"
       fy="276.4209"
       r="5.0648627" />
    <radialGradient
       inkscape:collect="always"
       xlink:href="#linearGradient2907"
       id="radialGradient2909"
       cx="210.21639"
       cy="192.25669"
       fx="210.21639"
       fy="192.25669"
       r="5.949986"
       gradientTransform="matrix(1.1727006,0,0,1.1727006,-36.505524,-33.40402)"
       gradientUnits="userSpaceOnUse" />
    <linearGradient
       inkscape:collect="always"
       xlink:href="#linearGradient3597"
       id="linearGradient2917"
       x1="15.985148"
       y1="289.71997"
       x2="7.5198202"
       y2="281.35651"
       gradientUnits="userSpaceOnUse"
       gradientTransform="matrix(1.1727283,0,0,1.1726728,-2.1931076,-49.432382)" />
    <radialGradient
       inkscape:collect="always"
       xlink:href="#linearGradient1775"
       id="radialGradient904"
       cx="203.03343"
       cy="185.0731"
       fx="203.03343"
       fy="185.0731"
       r="7.9787183"
       gradientUnits="userSpaceOnUse"
       gradientTransform="matrix(1.484981,0,0,1.484981,-99.129567,-90.419502)" />
    <radialGradient
       inkscape:collect="always"
       xlink:href="#linearGradient920"
       id="radialGradient914"
       gradientUnits="userSpaceOnUse"
       gradientTransform="matrix(1.484981,0,0,1.484981,-501.1906,-492.48021)"
       cx="203.03343"
       cy="185.0731"
       fx="203.03343"
       fy="185.0731"
       r="7.9787183" />
  </defs>
  <sodipodi:namedview
     id="base"
     pagecolor="#48032c"
     bordercolor="#666666"
     borderopacity="1.0"
     inkscape:pageopacity="0"
     inkscape:pageshadow="2"
     inkscape:zoom="4.0000001"
     inkscape:cx="52.332667"
     inkscape:cy="84.328551"
     inkscape:document-units="mm"
     inkscape:current-layer="layer1"
     showgrid="false"
     units="in"
     inkscape:window-width="1920"
     inkscape:window-height="1042"
     inkscape:window-x="0"
     inkscape:window-y="38"
     inkscape:window-maximized="0"
     inkscape:snap-intersection-paths="true"
     inkscape:object-paths="true"
     inkscape:snap-global="true" />
  <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 />
      </cc:Work>
    </rdf:RDF>
  </metadata>
  <g
     inkscape:label="Layer 1"
     inkscape:groupmode="layer"
     id="layer1"
     transform="translate(0,-271.59998)">
    <rect
       transform="matrix(0.70710678,-0.70710678,-0.70710678,-0.70710678,0,0)"
       y="-217.65018"
       x="-199.68983"
       height="15.957437"
       width="15.957437"
       id="rect912"
       style="opacity:1;vector-effect:none;fill:url(#radialGradient914);fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
    <rect
       style="opacity:1;vector-effect:none;fill:url(#linearGradient1805);fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
       id="rect1801"
       width="12.193584"
       height="12.193584"
       x="170.21625"
       y="220.35686"
       transform="rotate(45)" />
    <path
       inkscape:connector-curvature="0"
       id="path1803"
       style="fill:none;stroke:url(#linearGradient1807);stroke-width:2.0650003;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
       d="m -35.454712,279.49101 v 10.08081 m 2e-6,-10.08081 5.040403,5.04041 -5.040403,5.0404 -5.040405,-5.0404 z" />
    <path
       id="path1693"
       style="opacity:1;vector-effect:none;fill:none;fill-opacity:1;stroke:#fef5f7;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
       d="m 34.893237,267.49632 v -3.19965 m -1.685,-0.0372 h 3.369999 v 3.27405 h -3.369999 z" />
    <path
       style="opacity:1;vector-effect:none;fill:none;fill-opacity:1;stroke:#fef5f7;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
       d="m 37.184806,267.53352 v -3.27405 h 3.369999 v 3.27405"
       id="rect1709"
       inkscape:connector-curvature="0"
       sodipodi:nodetypes="cccc" />
    <path
       id="path1717"
       style="opacity:1;vector-effect:none;fill:none;fill-opacity:1;stroke:#fef5f7;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
       d="m 41.180522,265.89649 h 3.331704 m 0.01915,-1.63702 v 3.27405 h -3.369999 v -3.27405" />
    <path
       id="path1723"
       style="opacity:1;vector-effect:none;fill:none;fill-opacity:1;stroke:#fef5f7;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
       d="m 45.157089,265.89649 h 3.331704 m -3.350851,1.63703 v -3.27405 h 3.369999 v 3.27405" />
    <path
       style="opacity:1;vector-effect:none;fill:none;fill-opacity:1;stroke:#fef5f7;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
       d="m 49.11451,267.53352 v -3.27405 h 3.369999 v 3.27405"
       id="rect1727"
       inkscape:connector-curvature="0"
       sodipodi:nodetypes="cccc" />
    <path
       id="path1737"
       style="opacity:1;vector-effect:none;fill:none;fill-opacity:1;stroke:#fef5f7;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
       d="m 54.776077,267.49632 v -3.19965 m -1.684997,3.23685 v -3.27405 h 3.369999 v 3.27405" />
    <rect
       style="opacity:1;vector-effect:none;fill:url(#radialGradient2909);fill-opacity:1;stroke:none;stroke-width:0.2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
       id="rect2877"
       width="13.955104"
       height="13.955101"
       x="203.03781"
       y="185.07791"
       transform="matrix(0.7071235,0.70709006,-0.7071235,0.70709006,0,0)" />
    <path
       style="opacity:1;vector-effect:none;fill:url(#linearGradient1812);fill-opacity:1;stroke:url(#linearGradient2917);stroke-width:0.07055556;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
       d="m 12.699879,274.43273 -9.8679832,9.86752 9.8679832,9.86692 9.867983,-9.86692 z m -0.0012,2.12077 7.441943,7.441 -7.441944,7.43864 -7.4389871,-7.43864 6.6056641,-6.60535 z m -1.179899,4.52421 -2.9169251,2.91679 2.9169251,2.91679 z m 2.362757,0 v 5.83121 l 2.916924,-2.91442 z"
       id="rect836"
       inkscape:connector-curvature="0" />
    <path
       id="path845"
       style="fill:none;stroke:url(#radialGradient873);stroke-width:0.41804168;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
       d="m 12.700008,278.22531 v 11.53678 m 3e-6,-11.53678 5.768659,5.76839 -5.768659,5.76839 -5.7686618,-5.76839 z"
       inkscape:connector-curvature="0" />
    <path
       style="opacity:1;vector-effect:none;fill:url(#linearGradient931);fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
       d="M 24.425539,283.38063 12.699879,295.10395 2.3593445,284.77218 1.4154249,285.71666 12.699879,296.99998 25.370051,284.32449 Z"
       id="path883"
       inkscape:connector-curvature="0"
       sodipodi:nodetypes="ccccccc" />
    <path
       sodipodi:nodetypes="ccccccc"
       inkscape:connector-curvature="0"
       id="path933"
       d="M 0.9744608,285.21933 12.700121,273.496 23.040656,283.82778 23.984576,282.8833 12.700121,271.59998 0.0299491,284.27546 Z"
       style="opacity:1;vector-effect:none;fill:url(#linearGradient935);fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
    <g
       id="g1764"
       transform="matrix(0.50352276,-0.50349892,0.50352276,0.50349892,-154.92304,162.62301)"
       style="stroke:url(#radialGradient1777);stroke-width:0.28087056;stroke-linecap:square;stroke-miterlimit:4;stroke-dasharray:none">
      <path
         inkscape:connector-curvature="0"
         id="path1749"
         style="opacity:1;vector-effect:none;fill:none;fill-opacity:1;stroke:url(#radialGradient1793);stroke-width:0.28087056;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
         d="m 34.056591,276.36215 v -1.50187 m -0.848355,-0.0175 h 1.696711 v 1.5368 h -1.696711 z" />
      <path
         style="opacity:1;vector-effect:none;fill:none;fill-opacity:1;stroke:url(#radialGradient1795);stroke-width:0.28087056;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
         d="m 37.327005,276.37961 v -1.5368 h 1.69671 v 1.5368"
         id="path1751"
         inkscape:connector-curvature="0"
         sodipodi:nodetypes="cccc" />
      <path
         inkscape:connector-curvature="0"
         id="path1753"
         style="opacity:1;vector-effect:none;fill:none;fill-opacity:1;stroke:url(#radialGradient1797);stroke-width:0.28087056;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
         d="m 41.455454,275.61121 h 1.677431 m 0.0096,-0.7684 v 1.5368 h -1.696711 v -1.5368" />
    </g>
    <g
       id="g1769"
       transform="matrix(0.4999787,-0.49995505,0.4999787,0.49995505,-140.73822,173.71849)"
       style="stroke:url(#radialGradient2879);stroke-width:0.28286147;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none">
      <path
         style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:url(#radialGradient1787);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.28286147;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
         d="m 45.103283,274.84576 v 1.72137 0.16798 h 0.335972 v -0.16798 -0.60871 h 1.381436 v 0.60871 0.16798 h 0.335972 v -0.16798 -1.72137 z m 0.335972,0.334 h 1.381436 v 0.44269 h -1.381436 z"
         id="path1755"
         inkscape:connector-curvature="0" />
      <path
         style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:url(#radialGradient1789);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.28286147;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
         d="m 49.271309,274.84576 v 1.72137 0.16798 h 0.335971 v -0.16798 -1.38737 h 1.381437 v 1.38737 0.16798 h 0.333996 v -0.16798 -1.72137 z"
         id="path1757"
         inkscape:connector-curvature="0" />
      <path
         style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:url(#radialGradient2901);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.28286147;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
         d="m 53.439335,274.84576 v 1.72137 0.16798 h 0.335971 v -0.16798 -1.38737 h 0.521745 v 1.36958 0.16798 h 0.335971 v -0.16798 -1.36958 h 0.521744 v 1.38737 0.16798 h 0.335972 v -0.16798 -1.72137 z"
         id="path1759"
         inkscape:connector-curvature="0" />
    </g>
    <rect
       style="opacity:1;vector-effect:none;fill:url(#radialGradient904);fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
       id="rect896"
       width="15.957437"
       height="15.957437"
       x="202.37122"
       y="184.41054"
       transform="rotate(45)" />
  </g>
</svg>

Modified static/style.scss from [d08e77f56c] to [2db0a5b279].

33
34
35
36
37
38
39



40
41
42
43
44
45
46
..
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
...
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
...
285
286
287
288
289
290
291
292
293
294

295
296
297
298
299
300
301
...
479
480
481
482
483
484
485
486
487
488
489
490


491
492
493
494
495
496
497
...
694
695
696
697
698
699
700



701
702
703
704
705
706
707
...
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
...
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
....
1210
1211
1212
1213
1214
1215
1216
































	text-underline-offset: 0.1em;
	&:hover, &:focus {
		color: white;
		text-shadow: 0 0 15px tone(20%);
		text-decoration-color: tone(10%,-0.1);
		outline: none;
	}



}
a[href^="//"],
a[href^="http://"],
a[href^="https://"] { // external link
	&:hover::after {
		color: black;
		background-color: white;
................................................................................

.button, a[href].button { // 🙄
	@extend %sans;
	font-size: 14pt;
	box-sizing: border-box;
	padding: 0.1in 0.2in;
	border: 1px solid black;

	color: otone(25%);
	text-shadow: 1px 1px black;
	text-decoration: none;
	text-align: center;
	cursor: default;
	user-select: none;
	-webkit-user-drag: none;
	-webkit-app-region: no-drag;
	--icon: url(/s/heart.webp);
	background-image: linear-gradient(to bottom,
		otone(-47%),
		otone(-50%) 15%,
		otone(-50%) 75%,
		otone(-53%)
	);
	&:hover, &:focus {
		@extend %glow;
		outline: none;
		color: tone(-55%);
		text-shadow: none;
		background: linear-gradient(to bottom,
			otone(-27%),
			otone(-30%) 15%,
			otone(-30%) 75%,
			otone(-35%)
		);
	}
	&:active {
		color: black;
		padding-bottom: calc(0.1in - 2px);
		padding-top: calc(0.1in + 2px);


		background: linear-gradient(to top,
			otone(-25%),
			otone(-30%) 15%,
			otone(-30%) 75%,
			otone(-35%)
		);
	}
}

button { @extend .button;
	&:first-of-type {
		@extend .button;
		color: white;
		box-shadow: inset 0 1px  otone(-25%),
		            inset 0 -1px otone(-50%);
		background: linear-gradient(to bottom,
			otone(-35%),
			otone(-40%) 15%,
			otone(-40%) 75%,
			otone(-45%)
		);
		&:hover, &:focus {
			box-shadow: inset 0 1px  otone(-15%),
						inset 0 -1px otone(-40%);
		}
		&:active {
			box-shadow: inset 0 1px  otone(-50%),
						inset 0 -1px otone(-25%);
			background: linear-gradient(to top,
				otone(-30%),
				otone(-35%) 15%,
				otone(-35%) 75%,
				otone(-40%)
			);
		}
	}
	//&:hover { font-weight: bold; }
}

................................................................................
		}
		nav {
			all: unset;
			display: flex;
			justify-content: flex-end;
			align-items: center;
			grid-column: 2/3; grid-row: 1/2;







			.ident {
				color: tone(-20%);
				margin-left: 0.2em;
				border-left: 1px solid tone(-40%);
				padding-left: 0.5em;





			}
			> a[href] {
				display: block;
				padding: 0.25in 0.10in;
				//padding: calc((25% - 1em)/2) 0.15in;
				&, &::after { transition: 0.3s; }
				text-shadow: 1px 1px 1px black;
				&:hover{
					transform: scale(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 {
	@extend %content;
	display: block;
................................................................................
				border-radius: 5px;
				border: 1px solid transparent;
				&.on {
					background-color: tone(-30%, -0.7);
					box-shadow: 0 0 10px tone(-30%);
					border-color: tone(-20%);
				}
				> button, > p { display: block; }
				> p { text-align: center; font-size: 80%; margin: 0; margin-top: 0.1in; }
				> button {

					margin: auto;
				}
				&:last-child:nth-child(2n-1) {
					grid-column: 1/3;
				}
			}

................................................................................
	padding: 0.1in;
	> img { grid-column: 1/2; grid-row: 1/3; width: 1in; height: 1in;}
	> textarea {
		grid-column: 2/6; grid-row: 1/2; height: 3in;
		resize: vertical;
		margin-bottom: 0.08in;
	}
	> input[name="acl"] { grid-column: 2/3; grid-row: 2/3; }
	> button[value="post"] { grid-column: 5/6; grid-row: 2/3; }
	> button[value="attach"] { grid-column: 4/5; grid-row: 2/3; }
	a.help[href] { margin-right: 0.05in }
}



a.help[href] {
	display: block;
	text-align: center;
	padding: 0.09in 0.2in;
	background: tone(-40%);
	border: 1px solid black;
................................................................................
	> a[href] {
		display: block;
		text-align: left;
	}
	> a[href] + a[href] {
		border-top: none;
	}



	hr {
		border: none;
	}
}

menu { all: unset; display: block; }
body.conf main {
................................................................................
.color-picker {
	/* implemented using javascript, alas */
	@extend %box;
	label { text-shadow: 1px 1px black; }
	padding: 0.1in;
}

ul.user-list {
	list-style-type: none;
	margin: 0.5em 0;
	padding: 0;
	box-shadow: 0 0 10px -3px black inset;
	border: 1px solid tone(-50%);
	li {
		background-color: tone(-20%, -0.8);
................................................................................
		text-align: center;
		padding: 0.3em 0;
		margin: 0.2em 0.1em;
		cursor: default;
	}
}

.button, a[href] {
	.neg { --co:  30 }
	.pos { --co: -30 }
}

.pick-list {
	display: flex;
	flex-flow: row wrap;
	padding: 0.1in;
	background-color: tone(-50%);
	border: 1px solid tone(-53%);
................................................................................
			padding: 0 0.2in;
			max-height: calc(100vh - 3in);
			overflow-y: scroll;
			text-align: justify;
		}
	}
}







































>
>
>







 







>










|
|
|
|









|







>
>



|






|







|












|







 







>
>
>
>
>
>
>
|


<

>
>
>
>
>











|
<

|






>
>







 







|

|
>







 







<




>
>







 







>
>
>







 







|







 







<
|
|
<







 







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
..
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
...
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
...
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
...
498
499
500
501
502
503
504

505
506
507
508
509
510
511
512
513
514
515
516
517
...
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
...
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
...
944
945
946
947
948
949
950

951
952

953
954
955
956
957
958
959
....
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
	text-underline-offset: 0.1em;
	&:hover, &:focus {
		color: white;
		text-shadow: 0 0 15px tone(20%);
		text-decoration-color: tone(10%,-0.1);
		outline: none;
	}
	u {
		text-decoration-color: tone(10%,-0.3);
	}
}
a[href^="//"],
a[href^="http://"],
a[href^="https://"] { // external link
	&:hover::after {
		color: black;
		background-color: white;
................................................................................

.button, a[href].button { // 🙄
	@extend %sans;
	font-size: 14pt;
	box-sizing: border-box;
	padding: 0.1in 0.2in;
	border: 1px solid black;
	border-bottom: 2px solid black;
	color: otone(25%);
	text-shadow: 1px 1px black;
	text-decoration: none;
	text-align: center;
	cursor: default;
	user-select: none;
	-webkit-user-drag: none;
	-webkit-app-region: no-drag;
	--icon: url(/s/heart.webp);
	background-image: linear-gradient(to bottom,
		otone(-41%),
		otone(-43%) 15%,
		otone(-46%) 75%,
		otone(-50%)
	);
	&:hover, &:focus {
		@extend %glow;
		outline: none;
		color: tone(-55%);
		text-shadow: none;
		background: linear-gradient(to bottom,
			otone(-27%),
			otone(-30%) 15%,
			otone(-32%) 75%,
			otone(-35%)
		);
	}
	&:active {
		color: black;
		padding-bottom: calc(0.1in - 2px);
		padding-top: calc(0.1in + 2px);
		border: 1px solid black;
		border-top: 2px solid black;
		background: linear-gradient(to top,
			otone(-25%),
			otone(-30%) 15%,
			otone(-32%) 75%,
			otone(-35%)
		);
	}
}

button { @extend .button;
	form > &:first-of-type, menu > &:first-of-type {
		@extend .button;
		color: white;
		box-shadow: inset 0 1px  otone(-25%),
		            inset 0 -1px otone(-50%);
		background: linear-gradient(to bottom,
			otone(-35%),
			otone(-40%) 15%,
			otone(-43%) 75%,
			otone(-45%)
		);
		&:hover, &:focus {
			box-shadow: inset 0 1px  otone(-15%),
						inset 0 -1px otone(-40%);
		}
		&:active {
			box-shadow: inset 0 1px  otone(-50%),
						inset 0 -1px otone(-25%);
			background: linear-gradient(to top,
				otone(-30%),
				otone(-35%) 15%,
				otone(-38%) 75%,
				otone(-40%)
			);
		}
	}
	//&:hover { font-weight: bold; }
}

................................................................................
		}
		nav {
			all: unset;
			display: flex;
			justify-content: flex-end;
			align-items: center;
			grid-column: 2/3; grid-row: 1/2;
			hr {
				width: 1px;
				height: 1.5em;
				border: none;
				border-left: 1px solid tone(-40%);
				margin-left: 0.5em;
			}
			a[href].ident {
				color: tone(-20%);
				margin-left: 0.2em;

				padding-left: 0.5em;
				&::before {
					content: '@';
					display: inline-block; // remove underline - i don't want to know why this works
					opacity: 0.7;
				}
			}
			> a[href] {
				display: block;
				padding: 0.25in 0.10in;
				//padding: calc((25% - 1em)/2) 0.15in;
				&, &::after { transition: 0.3s; }
				text-shadow: 1px 1px 1px black;
				&:hover{
					transform: scale(1.2);
				}
			}
			> a[href].bell, a[href].gear {

				height: 2em;
				padding: 0.125in 0.05in;
				filter: drop-shadow(1px 1px 3px tone(-5%));
				&:hover {
					filter: drop-shadow(1px 1px 3px tone(-5%))
						drop-shadow(0 0 10px tone(-5%));
				}
			}
			> a[href].bell { content: url(/s/bell.svg); }
			> a[href].gear { content: url(/s/gear.svg); }
		}
	}
}

main {
	@extend %content;
	display: block;
................................................................................
				border-radius: 5px;
				border: 1px solid transparent;
				&.on {
					background-color: tone(-30%, -0.7);
					box-shadow: 0 0 10px tone(-30%);
					border-color: tone(-20%);
				}
				> button, > p, > a[href] { display: block; }
				> p { text-align: center; font-size: 80%; margin: 0; margin-top: 0.1in; }
				> button, > a[href] {
					width: max-content;
					margin: auto;
				}
				&:last-child:nth-child(2n-1) {
					grid-column: 1/3;
				}
			}

................................................................................
	padding: 0.1in;
	> img { grid-column: 1/2; grid-row: 1/3; width: 1in; height: 1in;}
	> textarea {
		grid-column: 2/6; grid-row: 1/2; height: 3in;
		resize: vertical;
		margin-bottom: 0.08in;
	}

	> button[value="post"] { grid-column: 5/6; grid-row: 2/3; }
	> button[value="attach"] { grid-column: 4/5; grid-row: 2/3; }
	a.help[href] { margin-right: 0.05in }
}

input[name="acl"] { grid-column: 2/3; grid-row: 2/3; }

a.help[href] {
	display: block;
	text-align: center;
	padding: 0.09in 0.2in;
	background: tone(-40%);
	border: 1px solid black;
................................................................................
	> a[href] {
		display: block;
		text-align: left;
	}
	> a[href] + a[href] {
		border-top: none;
	}
	.button, .button:active {
		border: 1px solid black;
	}
	hr {
		border: none;
	}
}

menu { all: unset; display: block; }
body.conf main {
................................................................................
.color-picker {
	/* implemented using javascript, alas */
	@extend %box;
	label { text-shadow: 1px 1px black; }
	padding: 0.1in;
}

ul.directory {
	list-style-type: none;
	margin: 0.5em 0;
	padding: 0;
	box-shadow: 0 0 10px -3px black inset;
	border: 1px solid tone(-50%);
	li {
		background-color: tone(-20%, -0.8);
................................................................................
		text-align: center;
		padding: 0.3em 0;
		margin: 0.2em 0.1em;
		cursor: default;
	}
}


.neg { --co:  30 !important }
.pos { --co: -30 !important }


.pick-list {
	display: flex;
	flex-flow: row wrap;
	padding: 0.1in;
	background-color: tone(-50%);
	border: 1px solid tone(-53%);
................................................................................
			padding: 0 0.2in;
			max-height: calc(100vh - 3in);
			overflow-y: scroll;
			text-align: justify;
		}
	}
}

div.kind-picker {
	text-align: right;
	font-style: italic;
	padding: 0.2em;
}

body.timeline {
	menu.circles {
		@extend %box;
		width: 3in;
		margin-right: 0;
		margin-left: auto;
		padding: 0.1in;
		a[href] {
			transition: 0.4s;
			text-align: center;
			display: block;
			padding: 0.4em;
			background: linear-gradient(to right, tone(-30%, -0.6), transparent) no-repeat;
			background-position: -3in 0;
			text-decoration: none;
			& + a[href] {
				border-bottom: 1px solid tone(-40%);
				border-image: linear-gradient(to right, transparent, tone(-45%), transparent) 1 0 0 / 1px;
			}
			&:hover {
				background-position: 0 0;
			}
		}
	}
}

Modified store.t from [8e00371af1] to [0871cb7e85].

11
12
13
14
15
16
17

18
19
20
21
22
23
24
...
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
...
345
346
347
348
349
350
351

352
353
354
355
356
357
358
...
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
...
471
472
473
474
475
476
477
478
479
480
481
482
483

484
485
486
487
488
489
490
...
539
540
541
542
543
544
545

546
547
548
549
550
551
552
553
554
555
556
		'follow',
		'sub', -- 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
	};
	credset = lib.set {
		'pw', 'otp', 'challenge', 'trust'
	};
	privset = lib.set {
................................................................................
	owner: uint64
	desc: str
	folder: str
	mime: str
	url: str
}

m.user_conf_funcs = function(be,n,ty,rty,rty2)
	rty = rty or ty
	local gt

	if not rty2 -- what the fuck?
		then gt = {&m.source, uint64, rawstring} -> rty;
		else gt = {&m.source, uint64, rawstring} -> {rty, rty2};
	end






	for k, t in pairs {
		enum = {&m.source, uint64, rawstring} -> lib.mem.ptr(rty);

		get = gt;
		set = {&m.source, uint64, rawstring, ty} -> {};
		reset = {&m.source, uint64, rawstring} -> {};
	} do
		be.entries[#be.entries+1] = {
			field = 'actor_conf_'..n..'_'..k, type = t
		}
	end
end

................................................................................
	comment: str
	netmask: m.inet
	privs: m.privset
	blacklist: bool
}

-- backends only handle content on the local server

struct m.backend { id: rawstring
	open: &m.source -> &opaque
	close: &m.source -> {}
	dbsetup: &m.source -> bool -- creates the schema needed to call conprep (called only once per database e.g. with `parsav db init`)
	conprep: {&m.source, m.prepmode.t} -> {} -- prepares queries and similar tasks that require the schema to already be in place
	obliterate_everything: &m.source -> bool -- wipes everything parsav-related out of the database

................................................................................
	-- these two functions are special, in that they should be called
	-- directly on a specific backend, rather than passed down to the
	-- backends by the server; that is pathological behavior that will
	-- not have the desired effect

	server_setup_self: {&m.source, rawstring, lib.mem.ptr(uint8)} -> {}

	conf_get: {&m.source, rawstring} -> lib.mem.ptr(int8)
	conf_set: {&m.source, rawstring, rawstring} -> {}
	conf_reset: {&m.source, rawstring} -> {}

	actor_create: {&m.source, &m.actor} -> uint64
	actor_save: {&m.source, &m.actor} -> {}
	actor_save_privs: {&m.source, &m.actor} -> {}
	actor_purge_uid: {&m.source, uint64} -> {}
	actor_fetch_xid: {&m.source, lib.mem.ptr(int8)} -> lib.mem.ptr(m.actor)
................................................................................
			-- emoji: pstring (null to delete previous reaction, otherwise adds/changes)
	post_act_cancel: {&m.source, uint64} -> {}
	post_liked_uid: {&m.source, uint64, uint64} -> bool
	post_reacted_uid: {&m.source, uint64, uint64} -> bool
	post_act_fetch_notice: {&m.source, uint64} -> m.notice

	circle_search:  {&m.source, &lib.mem.pool, uint64, uint64} -> lib.mem.ptr(m.circle)
	circle_create: {&m.source, uint64, pstring} -> {}
	circle_destroy: {&m.source, uint64, uint64} -> {}
	circle_members_fetch_cid:  {&m.source, &lib.mem.pool, uint64, uint64} -> lib.mem.ptr(uint64)
	circle_members_fetch_name: {&m.source, &lib.mem.pool, uint64, pstring} -> lib.mem.ptr(uint64)
	circle_members_add_uid: {&m.source, uint64, uint64} -> {}
	circle_members_del_uid: {&m.source, uint64, uint64} -> {}


	thread_latest_arrival_calc: {&m.source, uint64} -> m.timepoint

	artifact_instantiate: {&m.source, lib.mem.ptr(uint8), lib.mem.ptr(int8)} -> uint64
		-- instantiate an artifact in the database, either installing a new
		-- artifact or returning the id of an existing artifact with the same hash
			-- artifact: bytea
................................................................................
	nkvd_sanction_vacate: {&m.source, uint64} -> {}
	nkvd_sanction_enum_target: {&m.source, uint64} -> {}
	nkvd_sanction_enum_issuer: {&m.source, uint64} -> {}
	nkvd_sanction_review: {&m.source, m.timepoint} -> {}

	timeline_actor_fetch_uid: {&m.source, uint64, m.range} -> lib.mem.lstptr(m.post)
	timeline_instance_fetch: {&m.source, m.range} -> lib.mem.lstptr(m.post)

}

m.user_conf_funcs(m.backend, 'str', rawstring, lib.mem.ptr(int8))
m.user_conf_funcs(m.backend, 'int', intptr, intptr, bool)

struct m.source {
	backend: &m.backend
	id: lib.mem.ptr(int8)
	handle: &opaque
	string: lib.mem.ptr(int8)
}







>







 







|


>
|
|
|
|
>
>
>
>
>
>

<
>

|
|







 







>







 







|
|







 







|

|



>







 







>


|
|







11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
...
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
...
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
...
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
...
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
...
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
		'follow',
		'sub', -- 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
		'attenuate', -- user's retweets will not be 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
	};
	credset = lib.set {
		'pw', 'otp', 'challenge', 'trust'
	};
	privset = lib.set {
................................................................................
	owner: uint64
	desc: str
	folder: str
	mime: str
	url: str
}

m.user_conf_funcs = function(be,n,mem,ty,rty,rty2)
	rty = rty or ty
	local gt
	if not mem then
		if not rty2 -- what the fuck?
			then gt = {&m.source, uint64, lib.str.t} -> rty;
			else gt = {&m.source, uint64, lib.str.t} -> {rty, rty2};
		end
	else
		if not rty2 -- what the fuck?
			then gt = {&m.source, &lib.mem.pool, uint64, lib.str.t} -> rty;
			else gt = {&m.source, &lib.mem.pool, uint64, lib.str.t} -> {rty, rty2};
		end
	end
	for k, t in pairs {

		enum = {&m.source, &lib.mem.pool, uint64, lib.str.t} -> lib.mem.ptr(rty);
		get = gt;
		set = {&m.source, uint64, lib.str.t, ty} -> {};
		reset = {&m.source, uint64, lib.str.t} -> {};
	} do
		be.entries[#be.entries+1] = {
			field = 'actor_conf_'..n..'_'..k, type = t
		}
	end
end

................................................................................
	comment: str
	netmask: m.inet
	privs: m.privset
	blacklist: bool
}

-- backends only handle content on the local server
local pstring = lib.str.t
struct m.backend { id: rawstring
	open: &m.source -> &opaque
	close: &m.source -> {}
	dbsetup: &m.source -> bool -- creates the schema needed to call conprep (called only once per database e.g. with `parsav db init`)
	conprep: {&m.source, m.prepmode.t} -> {} -- prepares queries and similar tasks that require the schema to already be in place
	obliterate_everything: &m.source -> bool -- wipes everything parsav-related out of the database

................................................................................
	-- these two functions are special, in that they should be called
	-- directly on a specific backend, rather than passed down to the
	-- backends by the server; that is pathological behavior that will
	-- not have the desired effect

	server_setup_self: {&m.source, rawstring, lib.mem.ptr(uint8)} -> {}

	conf_get: {&m.source, lib.str.t} -> lib.mem.ptr(int8)
	conf_set: {&m.source, lib.str.t, lib.str.t} -> {}
	conf_reset: {&m.source, rawstring} -> {}

	actor_create: {&m.source, &m.actor} -> uint64
	actor_save: {&m.source, &m.actor} -> {}
	actor_save_privs: {&m.source, &m.actor} -> {}
	actor_purge_uid: {&m.source, uint64} -> {}
	actor_fetch_xid: {&m.source, lib.mem.ptr(int8)} -> lib.mem.ptr(m.actor)
................................................................................
			-- emoji: pstring (null to delete previous reaction, otherwise adds/changes)
	post_act_cancel: {&m.source, uint64} -> {}
	post_liked_uid: {&m.source, uint64, uint64} -> bool
	post_reacted_uid: {&m.source, uint64, uint64} -> bool
	post_act_fetch_notice: {&m.source, uint64} -> m.notice

	circle_search:  {&m.source, &lib.mem.pool, uint64, uint64} -> lib.mem.ptr(m.circle)
	circle_create: {&m.source, uint64, lib.str.t} -> uint64
	circle_destroy: {&m.source, uint64, uint64} -> {}
	circle_members_fetch_cid:  {&m.source, &lib.mem.pool, uint64} -> lib.mem.ptr(uint64)
	circle_members_fetch_name: {&m.source, &lib.mem.pool, uint64, pstring} -> lib.mem.ptr(uint64)
	circle_members_add_uid: {&m.source, uint64, uint64} -> {}
	circle_members_del_uid: {&m.source, uint64, uint64} -> {}
	circle_memberships_uid: {&m.source, &lib.mem.pool, uint64, uint64} -> lib.mem.ptr(m.circle)

	thread_latest_arrival_calc: {&m.source, uint64} -> m.timepoint

	artifact_instantiate: {&m.source, lib.mem.ptr(uint8), lib.mem.ptr(int8)} -> uint64
		-- instantiate an artifact in the database, either installing a new
		-- artifact or returning the id of an existing artifact with the same hash
			-- artifact: bytea
................................................................................
	nkvd_sanction_vacate: {&m.source, uint64} -> {}
	nkvd_sanction_enum_target: {&m.source, uint64} -> {}
	nkvd_sanction_enum_issuer: {&m.source, uint64} -> {}
	nkvd_sanction_review: {&m.source, m.timepoint} -> {}

	timeline_actor_fetch_uid: {&m.source, uint64, m.range} -> lib.mem.lstptr(m.post)
	timeline_instance_fetch: {&m.source, m.range} -> lib.mem.lstptr(m.post)
	timeline_circle_fetch: {&m.source, uint64, m.range} -> lib.mem.lstptr(m.post)
}

m.user_conf_funcs(m.backend, 'str', true,  lib.str.t, lib.str.t)
m.user_conf_funcs(m.backend, 'int', false, intptr, intptr, bool)

struct m.source {
	backend: &m.backend
	id: lib.mem.ptr(int8)
	handle: &opaque
	string: lib.mem.ptr(int8)
}

Modified tpl.t from [586a17ee3f] to [7216746946].

34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
	str = str:gsub('%s+[\n$]','')
	str = str:gsub('\n','')
	str = str:gsub('</a><a ','</a> <a ') -- keep nav links from getting smooshed
	str = str:gsub(tplchar .. '%?([-%w]+)', function(file)
		if not docs[file] then docs[file] = data.doc[file] end
		return string.format('<a href="#help-%s" class="help">?</a>', file)
	end)
	for start, mode, key, stop in string.gmatch(str,'()'..tplchar..'([:!]?)(%w+)()') do
		if string.sub(str,start-1,start-1) ~= '\\' then
			segs[#segs+1] = string.sub(str,last,start-1)
			fields[#segs] = { key = key, mode = (mode ~= '' and mode or nil) }
			last = stop
		end
	end
	segs[#segs+1] = string.sub(str,last)

	for i, s in ipairs(segs) do
		segs[i] = string.gsub(s, '\\'..tplchar, tplchar_o)







|


|







34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
	str = str:gsub('%s+[\n$]','')
	str = str:gsub('\n','')
	str = str:gsub('</a><a ','</a> <a ') -- keep nav links from getting smooshed
	str = str:gsub(tplchar .. '%?([-%w]+)', function(file)
		if not docs[file] then docs[file] = data.doc[file] end
		return string.format('<a href="#help-%s" class="help">?</a>', file)
	end)
	for start, mode, key, stop in string.gmatch(str,'()'..tplchar..'([:!]?)([-a-zA-Z0-9_]+)()') do
		if string.sub(str,start-1,start-1) ~= '\\' then
			segs[#segs+1] = string.sub(str,last,start-1)
			fields[#segs] = { key = key:gsub('-','_'), mode = (mode ~= '' and mode or nil) }
			last = stop
		end
	end
	segs[#segs+1] = string.sub(str,last)

	for i, s in ipairs(segs) do
		segs[i] = string.gsub(s, '\\'..tplchar, tplchar_o)

Added view/conf-circles.tpl version [9128c77c5c].





























>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<ul class="directory">
	@circles
</ul>

<details@newattr>
	<summary>create new circle</summary>
	<form method="post">
		<div class="elem">
			<label for="name">circle name</label>
			<input type="text" id="name" name="name" placeholder="dorks">
		</div>
		<button name="act" value="create">create</button>
	</form>
</details>

Modified view/conf-profile.tpl from [48e88ad45a] to [7ff9d56e20].

1
2
3
4
5












6
7
8
9
10
<form method="post">
	<div class="elem"><label>handle</label> <div class="txtbox">@!handle</div></div>
	<div class="elem"><label for="nym">display name</label> <input type="text" name="nym" id="nym" placeholder="j. random poster" value="@:nym"></div>
	<div class="elem"><label for="bio">bio</label><textarea name="bio" id="bio" placeholder="tall, dark, and mysterious">@!bio</textarea></div>
	<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>












	<menu class="choice vertical">
		<button>commit all</button>
		<button name="act" value="reset-hue">use server colors</button>
	</menu> 
</form>





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





1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<form method="post">
	<div class="elem"><label>handle</label> <div class="txtbox">@!handle</div></div>
	<div class="elem"><label for="nym">display name</label> <input type="text" name="nym" id="nym" placeholder="j. random poster" value="@:nym"></div>
	<div class="elem"><label for="bio">bio</label><textarea name="bio" id="bio" placeholder="tall, dark, and mysterious">@!bio</textarea></div>
	<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>
	<div class="elem-group">
		<div class="elem">
			<label for="acl-follow">who can follow me</label>
			<input type="text" class="acl" name="acl-follow"
				   id="acl-follow" placeholder="allow local" value="@:acl-follow">
		</div>
		<div class="elem">
			<label for="acl-follow-req">who can request to follow me</label>
			<input type="text" class="acl" name="acl-follow-req"
				   id="acl-follow-req" placeholder="deny +dorks" value="@:acl-follow-req">
		</div>
	</div>
	<menu class="choice vertical">
		<button>commit all</button>
		<button name="act" value="reset-hue">use server colors</button>
	</menu> 
</form>

Modified view/docskel.tpl from [e84fb6bf18] to [e2ac83ad65].

5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
		<link rel="stylesheet" type="text/css" href="/s/style.css">
		<script type="text/javascript" src="/s/live.js" async></script>
	</head>
	<body class="@class"@attr>
		<header><div>
			<h1>@title</h1>
			<nav>
				<a accesskey="i" href="/instance">instance</a>
				@navlinks
			</nav>
		</div></header>
		<main>
			@body
		</main>
	</body>
</html>







|








5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
		<link rel="stylesheet" type="text/css" href="/s/style.css">
		<script type="text/javascript" src="/s/live.js" async></script>
	</head>
	<body class="@class"@attr>
		<header><div>
			<h1>@title</h1>
			<nav>
				<a accesskey="i" href="/instance"><u>i</u>nstance</a>
				@navlinks
			</nav>
		</div></header>
		<main>
			@body
		</main>
	</body>
</html>

Modified view/load.lua from [3d222d2b19] to [bd867ef191].

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
local sources = {
	'docskel';
	'confirm';
	'tweet';
	'profile';
	'compose';
	'notice';


	'media-gallery';
	'media-upload';
	'media-image';
	'media-text';

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

	'conf';
	'conf-profile';


	'conf-sec';
	'conf-sec-credmg';
	'conf-sec-pwnew';
	'conf-sec-keynew';
	'conf-user-ctl';
}








>











>
>







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
local sources = {
	'docskel';
	'confirm';
	'tweet';
	'profile';
	'compose';
	'notice';
	'report';

	'media-gallery';
	'media-upload';
	'media-image';
	'media-text';

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

	'conf';
	'conf-profile';
	'conf-circles';
	'conf-circle-view';
	'conf-sec';
	'conf-sec-credmg';
	'conf-sec-pwnew';
	'conf-sec-keynew';
	'conf-user-ctl';
}

Modified view/profile.tpl from [240a608504] to [ae30d737ce].

38
39
40
41
42
43
44
45
46
47
48
49
50
		<button name="act" value="encircle">commit</button>
	</details>
	<details>
		<summary>sanctions</summary>
		<menu>
			@sanctions
			<div class="opt">
				<button class="neg" name="act" value="report">report</button>
				<p>if this user is violating instance rules, you can report this behavior to moderation staff and ask them to take action. please do not report users simply because you dislike them; this is what the above options are for.</p>
			</div>
		</menu>
	</details>
</div></form>







|





38
39
40
41
42
43
44
45
46
47
48
49
50
		<button name="act" value="encircle">commit</button>
	</details>
	<details>
		<summary>sanctions</summary>
		<menu>
			@sanctions
			<div class="opt">
				<a class="neg button" href="/@:xid/report">report</a>
				<p>if this user is violating instance rules, you can report this behavior to moderation staff and ask them to take action. please do not report users simply because you dislike them; this is what the above options are for.</p>
			</div>
		</menu>
	</details>
</div></form>

Added view/report.tpl version [34643d2a6d].

























>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
<form method="post">
	<p>if you feel that this post has violated the rules of this instance, you can report it to the local moderation team and request that they take action. this may include suppressing the offending post, suspending the user, or passing complaints onwards to the offending user's instance, if remote. please explain how you believe the post violates instance rules and what, if any, action you believe is appropriate.</p>
	@badtweet
	<div class="elem">
		<label for="report">report</label>
		<textarea id="report" name="report" placeholder="this running dog is spreading counterrevolutionary agitprop amongst the people, sir! he must be dealt with by the cadres at once"></textarea>
	</div>
	<menu class="horizontal choice">
		<button>issue report</button>
		<a class="button" href="@clnk">cancel</a>
	</menu>
</form>