parsav  Check-in [f4c6e72a22]

Overview
Comment:tentative beginnings of upload + media management system
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: f4c6e72a22221e80abe7e4088cf97cb9b1d6aca0cd3a9593343d12aaa6e4ec2f
User & Date: lexi on 2021-01-07 07:35:14
Other Links: manifest | tags
Context
2021-01-07
09:39
further iteration on media check-in: af5ed65b68 user: lexi tags: trunk
07:35
tentative beginnings of upload + media management system check-in: f4c6e72a22 user: lexi tags: trunk
2021-01-06
22:31
unfuck last commit check-in: 8d8ab01573 user: lexi tags: trunk
Changes

Modified backend/pgsql.t from [80ca2205e4] to [5d4cebec04].

477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
...
529
530
531
532
533
534
535





















536
537
538
539
540
541
542
....
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546







































1547
1548
1549
1550
1551
1552
1553
			limit case when $4::bigint = 0 then null
					   else $4::bigint end
			offset $5::bigint
		]];
	};

	artifact_instantiate = {
		params = {binblob, binblob, pstring}, sql = [[
			insert into parsav_artifacts (content,hash,mime) values (
				$1::bytea, $2::bytea, $3::text
			) on conflict do nothing returning id
		]];
	};
	artifact_expropriate = {
		params = {uint64, uint64, pstring}, cmd = true, sql = [[
			insert into parsav_artifact_claims (uid,rid,description,folder) values (
				$1::bigint, $2::bigint, $3::text, 'new'
			) on conflict do nothing
		]];
	};
	artifact_quicksearch = {
		params = {binblob}, sql = [[
			select id, (content is null) from parsav_artifacts where hash = $1::bytea
				limit 1
................................................................................
	-- "ERROR:  cannot insert multiple commands into a prepared
	--  statement" are you fucking shitting me with this shit
		params = {uint64}, sql = [[
			delete from parsav_artifact_claims where
				rid = $1::bigint
			returning uid, description, birth, folder;
		]];





















	};
	post_attach_ctl_ins = {
		params = {uint64, uint64}, cmd=true, sql = [[
			update parsav_posts set
				artifacts = artifacts || $2::bigint
			where id = $1::bigint and not
				artifacts @> array[$2::bigint] -- prevent duplication
................................................................................
			if ban then
				lib.report('user attempted to instantiate forsaken artifact')
				return 0
			end
			var oldid = srec:int(uint64,0,0)
			return oldid
		else -- not in db, insert
			var nrec = queries.artifact_instantiate.exec(src, artifact, hashb, mime)
			if nrec.sz == 0 then
				lib.warn('failed to instantiate artifact -- are you running out of storage?')
				return 0
			else defer nrec:free()
				var newid = nrec:int(uint64,0,0)
				return newid
			end
		end
	end];








































	post_attach_ctl = [terra(
		src: &lib.store.source,
		post: uint64,
		artifact: uint64,
		detach: bool
	): {}







|
|
|




|
|
|







 







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







 







|









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







477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
...
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
....
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
			limit case when $4::bigint = 0 then null
					   else $4::bigint end
			offset $5::bigint
		]];
	};

	artifact_instantiate = {
		params = {binblob, binblob, pstring, int64}, sql = [[
			insert into parsav_artifacts (content,hash,mime,birth) values (
				$1::bytea, $2::bytea, $3::text,$4::bigint
			) on conflict do nothing returning id
		]];
	};
	artifact_expropriate = {
		params = {uint64, uint64, pstring, pstring, int64}, cmd = true, sql = [[
			insert into parsav_artifact_claims (uid,rid,description,folder,birth) values (
				$1::bigint, $2::bigint, $3::text, $4::text, $5::bigint
			) on conflict do nothing
		]];
	};
	artifact_quicksearch = {
		params = {binblob}, sql = [[
			select id, (content is null) from parsav_artifacts where hash = $1::bytea
				limit 1
................................................................................
	-- "ERROR:  cannot insert multiple commands into a prepared
	--  statement" are you fucking shitting me with this shit
		params = {uint64}, sql = [[
			delete from parsav_artifact_claims where
				rid = $1::bigint
			returning uid, description, birth, folder;
		]];
	};
	artifact_enum_uid = {
		params = {uint64, pstring}, sql = [[
			select (pg_temp.parsavpg_translate_artifact(a)).*
			from parsav_artifact_claims as a where uid = $1::bigint and
				($2::text is null or
				 ($2::text = '' and folder is null) or
				 $2::text = folder)
			order by birth desc
		]];
	};
	artifact_fetch = {
		params = {uint64, uint64}, sql = [[
			select (pg_temp.parsavpg_translate_artifact(a)).*
			from parsav_artifact_claims as a where uid = $1::bigint and rid = $2::bigint
		]];
	};
	artifact_load = {
		params = {uint64}, sql = [[
			select content, mime from parsav_artifacts where id = $1::bigint
		]];
	};
	post_attach_ctl_ins = {
		params = {uint64, uint64}, cmd=true, sql = [[
			update parsav_posts set
				artifacts = artifacts || $2::bigint
			where id = $1::bigint and not
				artifacts @> array[$2::bigint] -- prevent duplication
................................................................................
			if ban then
				lib.report('user attempted to instantiate forsaken artifact')
				return 0
			end
			var oldid = srec:int(uint64,0,0)
			return oldid
		else -- not in db, insert
			var nrec = queries.artifact_instantiate.exec(src, artifact, hashb, mime, lib.osclock.time(nil))
			if nrec.sz == 0 then
				lib.warn('failed to instantiate artifact -- are you running out of storage?')
				return 0
			else defer nrec:free()
				var newid = nrec:int(uint64,0,0)
				return newid
			end
		end
	end];

	artifact_expropriate = [terra(
		src: &lib.store.source,
		uid: uint64,
		artifact: uint64,
		desc: pstring,
		folder: pstring
	): {}
		queries.artifact_expropriate.exec(src,uid,artifact,desc,folder, lib.osclock.time(nil))
	end];

	artifact_enum_uid = [terra(
		src: &lib.store.source,
		uid: uint64,
		folder: pstring
	)
		var res = queries.artifact_enum_uid.exec(src,uid,folder)
		if res.sz > 0 then
			var m = lib.mem.heapa([lib.mem.ptr(lib.store.artifact)], res.sz)
			for i=0,res.sz do
				var id = res:int(uint64,i,0)
				var idbuf: int8[lib.math.shorthand.maxlen]
				var idlen = lib.math.shorthand.gen(id, &idbuf[0])
				var desc = res:_string(i,2)
				var folder = res:_string(i,3)
				var mime = res:_string(i,4)
				var url = lib.str.acc{}:init(48):lpush('/media/a/'):push(&idbuf[0],idlen):finalize() defer url:free()
				m.ptr[i] = [ lib.str.encapsulate(lib.store.artifact, {
					desc =  {`desc.ptr, `desc.ct + 1};
					folder = {`folder.ptr, `folder.ct + 1};
					mime = {`mime.ptr, `mime.ct + 1};
					url = {`url.ptr, `url.ct + 1};
				}) ]
				m(i).ptr.rid = id
				m(i).ptr.owner = uid
			end
			return m
		else return [lib.mem.lstptr(lib.store.artifact)].null() end
	end];

	post_attach_ctl = [terra(
		src: &lib.store.source,
		post: uint64,
		artifact: uint64,
		detach: bool
	): {}

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

15
16
17
18
19
20
21
















22
23
24
25
26
27
28
	select p.id as post,
		coalesce((select counts.ct from counts where counts.subject = p.id
			and counts.kind = 'like'),0)::integer as likes,
		coalesce((select counts.ct from counts where counts.subject = p.id
			and counts.kind = 'rt'  ),0)::integer as rts
	from parsav_posts as p
);

















create type pg_temp.parsavpg_intern_notice as (
	kind	smallint,
	"when"	bigint,
	who		bigint,
	what	bigint,
	reply	bigint,







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







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
	select p.id as post,
		coalesce((select counts.ct from counts where counts.subject = p.id
			and counts.kind = 'like'),0)::integer as likes,
		coalesce((select counts.ct from counts where counts.subject = p.id
			and counts.kind = 'rt'  ),0)::integer as rts
	from parsav_posts as p
);

create type pg_temp.parsavpg_intern_artifact as (
	rid		bigint,
	owner	bigint,
	"desc"	text,
	folder	text,
	mime	text
);

create or replace function
pg_temp.parsavpg_translate_artifact(parsav_artifact_claims)
returns pg_temp.parsavpg_intern_artifact as $$
	select ($1).rid, ($1).uid, ($1).description, ($1).folder, a.mime
	from parsav_artifacts a where
		a.id = ($1).rid limit 1
$$ language sql;

create type pg_temp.parsavpg_intern_notice as (
	kind	smallint,
	"when"	bigint,
	who		bigint,
	what	bigint,
	reply	bigint,

Modified config.lua from [eb323f3b45] to [6cff716428].

57
58
59
60
61
62
63

64
65
66
67
68
69
70
		{'bell.svg', 'image/svg+xml'};
		{'heart.webp', 'image/webp'};
		{'retweet.webp', 'image/webp'};
		{'padlock.svg', 'image/svg+xml'};
		{'warn.svg', 'image/svg+xml'};
		{'query.webp', 'image/webp'};
		{'reply.webp', 'image/webp'};

		-- keep in mind before you add anything to this list: these are not
		-- just files parsav can access, they are files that are *kept in
		-- memory* for fast access the entire time parsav is running, and
		-- which need to be loaded into memory before the program can even
		-- start. it's imperative to keep these as small and few in number
		-- as is realistically possible.
	};







>







57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
		{'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'};
		-- keep in mind before you add anything to this list: these are not
		-- just files parsav can access, they are files that are *kept in
		-- memory* for fast access the entire time parsav is running, and
		-- which need to be loaded into memory before the program can even
		-- start. it's imperative to keep these as small and few in number
		-- as is realistically possible.
	};

Modified http.t from [7092b409fb] to [4c9f723184].

1
2
3
4
5
6
7
8
9
10
11
12
..
18
19
20
21
22
23
24






25
26
27
28
29
30
31
-- vim: ft=terra
local m = {}
local util = lib.util

m.method = lib.enum { 'get', 'post', 'head', 'options', 'put', 'delete' }
m.mime = lib.enum {
	'html'; -- default
	'json';
	'mkdown';
	'text';
	'ansi';
	'none';
................................................................................
	key: rawstring
	value: rawstring
}
struct m.page {
	respcode: uint16
	body: lib.mem.ptr(int8)
	headers: lib.mem.ptr(m.header)






}

local resps = {
	[200] = 'OK';
	[201] = 'Created';
	[301] = 'Moved Permanently';
	[302] = 'Found';




|







 







>
>
>
>
>
>







1
2
3
4
5
6
7
8
9
10
11
12
..
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
-- vim: ft=terra
local m = {}
local util = lib.util

m.method = lib.enum { 'get', 'post', 'post_file', 'head', 'options', 'put', 'delete' }
m.mime = lib.enum {
	'html'; -- default
	'json';
	'mkdown';
	'text';
	'ansi';
	'none';
................................................................................
	key: rawstring
	value: rawstring
}
struct m.page {
	respcode: uint16
	body: lib.mem.ptr(int8)
	headers: lib.mem.ptr(m.header)
}
struct m.upload {
	ctype: lib.str.t;
	filename: lib.str.t;
	field: lib.str.t;
	body: lib.str.t;
}

local resps = {
	[200] = 'OK';
	[201] = 'Created';
	[301] = 'Moved Permanently';
	[302] = 'Found';

Modified makefile from [0e9ec3efb8] to [8559ac7b2c].

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

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

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



|







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

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

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

Modified parsav.t from [8bdefbaeb6] to [7b59e1e979].

434
435
436
437
438
439
440


441
442
443
444
445
446
447
	'render:profile';
	'render:compose';
	'render:tweet';
	'render:tweet-page';
	'render:user-page';
	'render:timeline';
	'render:notices';



	'render:docpage';

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







>
>







434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
	'render:profile';
	'render:compose';
	'render:tweet';
	'render:tweet-page';
	'render:user-page';
	'render:timeline';
	'render:notices';

	'render:media-gallery';

	'render:docpage';

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

Added render/media-gallery.t version [79b3557b2e].























































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
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
-- vim: ft=terra
local pstr = lib.str.t 
local P = lib.str.plit
local terra cs(s: rawstring)
	return pstr { ptr = s, ct = lib.str.sz(s) }
end

local show_all,show_new,show_files,show_vid,show_img=1,2,3,4,5

local terra 
render_media_gallery(co: &lib.srv.convo, path: lib.mem.ptr(lib.mem.ref(int8)), uid: uint64, acc: &lib.str.acc)
 -- note that when calling this function, path must be adjusted so that path(0)
 -- eq "media"
	var owner = false
	if co.aid ~= 0 and co.who.id == uid then owner = true end
	var ou = co.srv:actor_fetch_uid(uid)
	if not ou then goto e404 end

	var view = data.view.media_gallery {
		menu = pstr{'',0};
		folders = pstr{'',0};
		directory = pstr{'',0};
		images = pstr{'',0};
	}

	if owner then
		view.menu = P'<a class="pos" href="/media/upload">upload</a><hr>'
	end
	var mode: uint8 = show_new
	var folder: pstr
	if mode == show_new then
		folder = lib.str.plit''
	elseif mode == show_all then
		folder = pstr.null()
	-- else get folder from query str
	end

	var md = co.srv:artifact_enum_uid(uid, folder)
	var gallery: lib.str.acc gallery:init(256)
	var files: lib.str.acc files:init(256) 
	for i=0,md.ct do
		if lib.str.ncmp(md(i)(0).mime, 'image/', 6) == 0 then
			gallery:lpush('<a class="thumb" href="')
			if not owner then
				gallery:lpush('/')
				if ou(0).origin ~= 0 then gallery:lpush('@') end
				gallery:push(ou(0).xid,0):lpush('/')
			end
			gallery:push(md(i)(0).url,0)
				:lpush('"><img src="') :push(md(i)(0).url,0)
				:lpush('/raw"><div class="caption">') :push(md(i)(0).desc,0)
				:lpush('</div></a>')
		else
			files:lpush('<a class="file" href="')
			if not owner then
				gallery:lpush('/')
				if ou(0).origin ~= 0 then gallery:lpush('@') end
				gallery:push(ou(0).xid,0):lpush('/')
			end
			files:push(md(i)(0).url,0)
				:lpush('"><span class="label">'):push(md(i)(0).desc,0)
				:lpush('</span> <span class="mime">'):push(md(i)(0).mime,0)
				:lpush('</span></a>')
		end
		md(i):free()
	end

	view.images = gallery:finalize()
	view.directory = files:finalize()

	if acc ~= nil then
		view:append(acc)
	else
		var pg = view:tostr() defer pg:free()
		co:stdpage([lib.srv.convo.page] {
			title = P'media';
			class = P'media manager';
			cache = false;
			body = pg;
		})
	end

	view.images:free()
	view.directory:free()
	if md:ref() then md:free() end
	do return end

	::e404:: co:complain(404,'media not found','no such media exists on this server')
end

return render_media_gallery

Modified render/nav.t from [62959bc993] to [5194b2263f].

3
4
5
6
7
8
9
10
11
12
13
14
15
16
render_nav(co: &lib.srv.convo)
	var t: lib.str.acc t:init(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="o" href="/conf">configure</a> <a accesskey="d" href="/doc">docs</a> <a accesskey="g" href="/logout">log out</a> <a class="bell" 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







|






3
4
5
6
7
8
9
10
11
12
13
14
15
16
render_nav(co: &lib.srv.convo)
	var t: lib.str.acc t:init(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> <a accesskey="g" href="/logout">log out</a> <a class="bell" 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

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

1
2
3
4
5

6
7
8
9
10
11
12
...
395
396
397
398
399
400
401
402


































































403
404
405
406
407
408
409
...
483
484
485
486
487
488
489


490
491
492
493
494
495
496
-- vim: ft=terra
local r = lib.srv.route
local method = lib.http.method
local pstring = lib.mem.ptr(int8)
local rstring = lib.mem.ref(int8)

local hpath = lib.mem.ptr(rstring)
local http = {}

terra meth_get(meth: method.t) return (meth == method.get) or (meth == method.head) end

terra http.actor_profile(co: &lib.srv.convo, actor: &lib.store.actor, meth: method.t)
	var rel: lib.store.relationship
................................................................................
			return
		else goto badop end
	end

	lib.render.notices(co)
	do return end

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


































































end

do local branches = quote end
	local filename, flen = symbol(&int8), symbol(intptr)
	local page = symbol(lib.http.page)
	local send = label()
	local storage = data.stmap
................................................................................
		var path = lib.http.hier(uri) defer path:free()
		if path.ct > 1 and path(0):cmp(lib.str.lit('user')) then
			http.actor_profile_uid(co, path, meth)
		elseif path.ct > 1 and path(0):cmp(lib.str.lit('post')) then
			http.tweet_page(co, path, meth)
		elseif path(0):cmp(lib.str.lit('tl')) then
			http.timeline(co, path)


		elseif path(0):cmp(lib.str.lit('doc')) then
			if not meth_get(meth) then goto wrongmeth end
			http.documentation(co, path)
		elseif path(0):cmp(lib.str.lit('conf')) then
			if co.aid == 0 then goto unauth end
			http.configure(co,path,meth)
		else goto notfound end





>







 







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







 







>
>







1
2
3
4
5
6
7
8
9
10
11
12
13
...
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
...
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
-- vim: ft=terra
local r = lib.srv.route
local method = lib.http.method
local pstring = lib.mem.ptr(int8)
local rstring = lib.mem.ref(int8)
local binblob = lib.mem.ptr(uint8)
local hpath = lib.mem.ptr(rstring)
local http = {}

terra meth_get(meth: method.t) return (meth == method.get) or (meth == method.head) end

terra http.actor_profile(co: &lib.srv.convo, actor: &lib.store.actor, meth: method.t)
	var rel: lib.store.relationship
................................................................................
			return
		else goto badop end
	end

	lib.render.notices(co)
	do return end

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

terra http.media_manager(co: &lib.srv.convo, path: hpath, meth: method.t)
	if meth == method.post then
		goto badop
	end

	if path.ct == 1 or (path.ct >= 3 and path(1):cmp(lib.str.lit'a')) then
		if meth == method.post then goto badop end
		lib.render.media_gallery(co,path,co.who.id,nil)
	elseif path.ct == 2 then
		if path(1):cmp(lib.str.lit'upload') and co.who.rights.powers.artifact() then
			if meth == method.get then
				var view = data.view.media_upload {
					folders = ''
				}
				var pg = view:tostr() defer pg:free()
				co:stdpage([lib.srv.convo.page] {
					title = lib.str.plit'media :: upload';
					class = lib.str.plit'media upload';
					cache = false; body = pg;
				})
			elseif meth == method.post_file then
				var desc = pstring.null()
				var folder = pstring.null()
				var mime = pstring.null()
				var name = pstring.null()
				var body = binblob.null()
				for i=0, co.uploads.sz do var up = co.uploads.storage.ptr + i
					if up.body.ct > 0 then
						if up.field:cmp(lib.str.plit'desc') then
							desc = up.body
						elseif up.field:cmp(lib.str.plit'folder') then
							folder = up.body
						elseif up.field:cmp(lib.str.plit'file') then
							mime = up.ctype
							body = binblob {ptr = [&uint8](up.body.ptr), ct = up.body.ct}
							name = up.filename
						end
					end
				end
				if not body then goto badop end
				if body.ct > co.srv.cfg.maxupsz then
					co:complain(403, 'file too long', "the file you have attempted to upload exceeds the maximum length permitted by this server's upload policy. if it is an image or video, try compressing it at a lower quality setting or resolution")
					return
				end
				var id = co.srv:artifact_instantiate(body,mime)
				if id == 0 then
					co:complain(500,'upload failed','artifact rejected. either the server is running out of space or this file is banned from the server')
					return
				end
				co.srv:artifact_expropriate(co.who.id,id,desc,folder)

				var idbuf: int8[lib.math.shorthand.maxlen]
				var idlen = lib.math.shorthand.gen(id,&idbuf[0])

				var url = lib.str.acc{}:compose('/media/a/',pstring{&idbuf[0],idlen}):finalize()
				co:reroute(url.ptr)
				url:free()
			else goto badop end
		end
	else goto e404 end
	do return end

	::badop:: do co:complain(405, 'invalid operation', 'the operation you have attempted on this post is not meaningful') return end
	::e404:: do co:complain(404, 'artifact not found', 'no such artifact has been uploaded by this user') return end
end

do local branches = quote end
	local filename, flen = symbol(&int8), symbol(intptr)
	local page = symbol(lib.http.page)
	local send = label()
	local storage = data.stmap
................................................................................
		var path = lib.http.hier(uri) defer path:free()
		if path.ct > 1 and path(0):cmp(lib.str.lit('user')) then
			http.actor_profile_uid(co, path, meth)
		elseif path.ct > 1 and path(0):cmp(lib.str.lit('post')) then
			http.tweet_page(co, path, meth)
		elseif path(0):cmp(lib.str.lit('tl')) then
			http.timeline(co, path)
		elseif path(0):cmp(lib.str.lit('media')) then
			http.media_manager(co, path, meth)
		elseif path(0):cmp(lib.str.lit('doc')) then
			if not meth_get(meth) then goto wrongmeth end
			http.documentation(co, path)
		elseif path(0):cmp(lib.str.lit('conf')) then
			if co.aid == 0 then goto unauth end
			http.configure(co,path,meth)
		else goto notfound end

Modified srv.t from [d4dcecb4e5] to [a1d0408148].

134
135
136
137
138
139
140


141
142
143
144
145
146
147
...
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
...
391
392
393
394
395
396
397

398
399
400
401
402
403
404
...
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
	aid: uint64 -- 0 if logged out
	aid_issue: lib.store.timepoint
	who: &lib.store.actor -- who we're logged in as, if aid ~= 0
	peer: lib.store.inet
	reqtype: lib.http.mime.t -- negotiated content type
	method: lib.http.method.t
	live_last: lib.store.timepoint


-- cache
	ui_hue: uint16
	navbar: lib.mem.ptr(int8)
	actorcache: lib.mem.cache(lib.mem.ptr(lib.store.actor),32) -- naive cache to avoid unnecessary queries
-- private
	varbuf: lib.mem.ptr(int8)
	vbofs: &int8
................................................................................
	else return nil, 0 end
end
terra convo:pgetv(name: rawstring)
	var s,l = self:getv(name)
	return pstring { ptr = s, ct = l }
end

local urimatch = macro(function(uri, ptn)
	return `lib.net.mg_globmatch(ptn, [#ptn], uri.ptr, uri.ct+1)
end)

local route = {} -- these are defined in route.t, as they need access to renderers
terra route.dispatch_http ::  {&convo, lib.mem.ptr(int8), lib.http.method.t} -> {}

local mimetypes = {
	{'html', 'text/html'};
	{'json', 'application/json'};
	{'mkdown', 'text/markdown'};
................................................................................
					reqtype = lib.http.mime.none;
					peer = peer, live_last = 0;
				} co.varbuf.ptr = nil
				  co.navbar.ptr = nil
				  co.actorcache.top = 0
				  co.actorcache.cur = 0
				  co.ui_hue = server.cfg.ui_hue


				-- first, check for an accept header. if it's there, we need to
				-- iterate over the values and pick the highest-priority one
				do var acc = lib.http.findheader(msg, 'Accept')
					-- TODO handle q-value
					if acc ~= nil and acc.ptr ~= nil then
						var [mimevar] = [lib.mem.ref(int8)] { ptr = acc.ptr }
................................................................................
					end
					uri.ct = msg.uri.len
				else uri.ct = urideclen end
				lib.dbg('routing URI ', {uri.ptr, uri.ct})
				
				if lib.str.ncmp('GET', msg.method.ptr, msg.method.len) == 0 then
					co.method = [lib.http.method.get]
					route.dispatch_http(&co, uri, [lib.http.method.get])
				elseif lib.str.ncmp('POST', msg.method.ptr, msg.method.len) == 0 then
					co.method = [lib.http.method.get]
					route.dispatch_http(&co, uri, [lib.http.method.post])
				elseif lib.str.ncmp('HEAD', msg.method.ptr, msg.method.len) == 0 then
					co.method = [lib.http.method.head]
					route.dispatch_http(&co, uri, [lib.http.method.head])
				elseif lib.str.ncmp('OPTIONS', msg.method.ptr, msg.method.len) == 0 then
					co.method = [lib.http.method.options]
					route.dispatch_http(&co, uri, [lib.http.method.options])
				else
					co:complain(400,'unknown method','you have submitted an invalid http request')

				end
















































































































				if co.aid ~= 0 then lib.mem.heapf(co.who) end
				if co.varbuf.ptr ~= nil then co.varbuf:free() end
				if co.navbar.ptr ~= nil then co.navbar:free() end
				co.actorcache:free()
			end
		end
	end;







>
>







 







<
<
<
<







 







>







 







<

|
<


<


<


>

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

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







134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
...
330
331
332
333
334
335
336




337
338
339
340
341
342
343
...
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
...
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
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
	aid: uint64 -- 0 if logged out
	aid_issue: lib.store.timepoint
	who: &lib.store.actor -- who we're logged in as, if aid ~= 0
	peer: lib.store.inet
	reqtype: lib.http.mime.t -- negotiated content type
	method: lib.http.method.t
	live_last: lib.store.timepoint
	uploads: lib.mem.vec(lib.http.upload)
	body: lib.str.t
-- cache
	ui_hue: uint16
	navbar: lib.mem.ptr(int8)
	actorcache: lib.mem.cache(lib.mem.ptr(lib.store.actor),32) -- naive cache to avoid unnecessary queries
-- private
	varbuf: lib.mem.ptr(int8)
	vbofs: &int8
................................................................................
	else return nil, 0 end
end
terra convo:pgetv(name: rawstring)
	var s,l = self:getv(name)
	return pstring { ptr = s, ct = l }
end





local route = {} -- these are defined in route.t, as they need access to renderers
terra route.dispatch_http ::  {&convo, lib.mem.ptr(int8), lib.http.method.t} -> {}

local mimetypes = {
	{'html', 'text/html'};
	{'json', 'application/json'};
	{'mkdown', 'text/markdown'};
................................................................................
					reqtype = lib.http.mime.none;
					peer = peer, live_last = 0;
				} co.varbuf.ptr = nil
				  co.navbar.ptr = nil
				  co.actorcache.top = 0
				  co.actorcache.cur = 0
				  co.ui_hue = server.cfg.ui_hue
				  co.body.ptr = msg.body.ptr co.body.ct = msg.body.len

				-- first, check for an accept header. if it's there, we need to
				-- iterate over the values and pick the highest-priority one
				do var acc = lib.http.findheader(msg, 'Accept')
					-- TODO handle q-value
					if acc ~= nil and acc.ptr ~= nil then
						var [mimevar] = [lib.mem.ref(int8)] { ptr = acc.ptr }
................................................................................
					end
					uri.ct = msg.uri.len
				else uri.ct = urideclen end
				lib.dbg('routing URI ', {uri.ptr, uri.ct})
				
				if lib.str.ncmp('GET', msg.method.ptr, msg.method.len) == 0 then
					co.method = [lib.http.method.get]

				elseif lib.str.ncmp('POST', msg.method.ptr, msg.method.len) == 0 then
					co.method = [lib.http.method.post]

				elseif lib.str.ncmp('HEAD', msg.method.ptr, msg.method.len) == 0 then
					co.method = [lib.http.method.head]

				elseif lib.str.ncmp('OPTIONS', msg.method.ptr, msg.method.len) == 0 then
					co.method = [lib.http.method.options]

				else
					co:complain(400,'unknown method','you have submitted an invalid http request')
					goto fail
				end
				-- check for a content-type header, and see if it's a multipart/
				-- form-data encoded POST request so we can handle file uploads
				co.uploads.sz = 0 co.uploads.run = 0
				if co.method == [lib.http.method.post] then
					var ctt = lib.http.findheader(msg, 'Content-Type')
					if ctt ~= nil then
						lib.dbg('found content type', {ctt.ptr,ctt.ct})
						if lib.str.ncmp(ctt.ptr,'multipart/form-data;',20) == 0 then
							var p = lib.str.ffw(ctt.ptr + 20,ctt.ct-20)
							if lib.str.ncmp(p,'boundary=',9) ~= 0 then
								co:complain(400,'bad request','unrecognized content-type')
								goto fail
							end
							var boundary = pstring {ptr=p+9,ct=ctt.ct - ((p - ctt.ptr) + 9)}
							lib.dbg('got boundary ',{boundary.ptr,boundary.ct})
							co.method = lib.http.method.post_file
							co.uploads:init(8)

							var bsr = (lib.str.acc{}):compose('\r\n--',boundary,'\r\n'):finalize()

							var upmap = lib.str.splitmap(co.body,bsr,8)
							-- first entry may not be preceded by header-break
							if lib.str.find(upmap(0), pstring {
								ptr = bsr.ptr + 2, ct = bsr.ct - 2
							}):ref() then
								upmap(0).ptr = upmap(0).ptr + (bsr.ct - 2)
								upmap(0).ct = upmap(0).ct - (bsr.ct - 2)
							end

							-- last entry is weird
							do var lsr = (lib.str.acc{}):compose('\r\n--',boundary,'--\r\n'):finalize()
								var lsent = upmap.ptr + (upmap.ct - 1)
								var halt = lib.str.find(@lsent, lsr)
								if halt:ref() then
									lsent.ct = halt.ptr - lsent.ptr
								end
								lsr:free() end

							for i=0,upmap.ct do
								var hdrbrk = lib.str.find(upmap(i), lib.str.plit'\r\n\r\n')
								if hdrbrk:ref() then
									lib.dbg('got new entry')
									var hdrtxt = pstring {upmap(i).ptr,upmap(i).ct - hdrbrk.ct}
									var hdrs = lib.str.splitmap(hdrtxt, '\r\n',6)
									var ctt = pstring.null()
									var ctd = pstring.null()
									for j=0, hdrs.ct do
										var brk = lib.str.find(hdrs(j),lib.str.plit':')
										if brk:ref() then
											var hdr = pstring{hdrs(j).ptr,hdrs(j).ct - brk.ct}
											var val = pstring{brk.ptr+1, brk.ct-1}:ffw()
											if hdr:cmp(lib.str.plit'Content-Type') then
												ctt = val
											elseif hdr:cmp(lib.str.plit'Content-Disposition') then
												ctd = val
											end
										end
									end
									if ctd:ref() then
										var ctdvals = lib.str.splitmap(ctd, ';', 4) defer ctdvals:free()
										if ctdvals(0):cmp(lib.str.plit'form-data') and ctdvals.ct > 1 then
											lib.dbg('found form data')
											var fld = pstring.null()
											var file = pstring.null()
											for j=1, ctdvals.ct do var v = ctdvals(j):ffw()
												var x = lib.str.find(v,lib.str.plit'=')
												if x:ref() then
													var key = pstring{v.ptr, v.ct - x.ct}
													var val = pstring{x.ptr + 1, x.ct - 1}
													var decval, ofs, sp = lib.str.toknext(val,@';',true)
													if key:cmp(lib.str.plit'name') then
														fld = decval
													elseif key:cmp(lib.str.plit'filename') then
														file = decval
													else decval:free() end
												end
											end
											if fld:ref() then
												var nextup = co.uploads:new()
												if ctt:ref() then
													nextup.ctype = ctt
												else
													nextup.ctype = pstring.null()
												end
												nextup.body = pstring {
													ptr = hdrbrk.ptr + 4;
													ct = hdrbrk.ct - 4;
												}
												nextup.ctype = ctt
												nextup.field = fld
												nextup.filename = file
											end
										end
									end
								end
							end
							bsr:free()
							upmap:free()
						end
					end
				end

				route.dispatch_http(&co, uri, co.method)
				if co.uploads.run > 0 then
					for i=0,co.uploads.sz do
						co.uploads(i).filename:free()
						co.uploads(i).field:free()
					end
					co.uploads:free()
				end

				::fail::
				if co.aid ~= 0 then lib.mem.heapf(co.who) end
				if co.varbuf.ptr ~= nil then co.varbuf:free() end
				if co.navbar.ptr ~= nil then co.navbar:free() end
				co.actorcache:free()
			end
		end
	end;

Added static/file.svg version [c89d070bea].

























































































































































































































































































































































































































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
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
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->

<svg
   xmlns:dc="http://purl.org/dc/elements/1.1/"
   xmlns:cc="http://creativecommons.org/ns#"
   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
   xmlns:svg="http://www.w3.org/2000/svg"
   xmlns="http://www.w3.org/2000/svg"
   xmlns:xlink="http://www.w3.org/1999/xlink"
   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
   width="20"
   height="20"
   viewBox="0 0 5.2916664 5.2916665"
   version="1.1"
   id="svg8"
   inkscape:version="0.92.4 (5da689c313, 2019-01-14)"
   sodipodi:docname="file.svg">
  <defs
     id="defs2">
    <linearGradient
       inkscape:collect="always"
       id="linearGradient935">
      <stop
         style="stop-color:#39104b;stop-opacity:1;"
         offset="0"
         id="stop931" />
      <stop
         style="stop-color:#39104b;stop-opacity:0;"
         offset="1"
         id="stop933" />
    </linearGradient>
    <linearGradient
       id="linearGradient923"
       inkscape:collect="always">
      <stop
         id="stop919"
         offset="0"
         style="stop-color:#f0cfff;stop-opacity:1" />
      <stop
         id="stop921"
         offset="1"
         style="stop-color:#eabcff;stop-opacity:0;" />
    </linearGradient>
    <linearGradient
       inkscape:collect="always"
       id="linearGradient904">
      <stop
         style="stop-color:#df9aff;stop-opacity:1;"
         offset="0"
         id="stop900" />
      <stop
         style="stop-color:#df9aff;stop-opacity:0;"
         offset="1"
         id="stop902" />
    </linearGradient>
    <linearGradient
       inkscape:collect="always"
       id="linearGradient896">
      <stop
         style="stop-color:#eabcff;stop-opacity:1;"
         offset="0"
         id="stop892" />
      <stop
         style="stop-color:#eabcff;stop-opacity:0;"
         offset="1"
         id="stop894" />
    </linearGradient>
    <linearGradient
       inkscape:collect="always"
       id="linearGradient954">
      <stop
         style="stop-color:#ffffff;stop-opacity:1;"
         offset="0"
         id="stop950" />
      <stop
         style="stop-color:#ffffff;stop-opacity:0;"
         offset="1"
         id="stop952" />
    </linearGradient>
    <linearGradient
       inkscape:collect="always"
       id="linearGradient938">
      <stop
         style="stop-color:#d9fff6;stop-opacity:1;"
         offset="0"
         id="stop934" />
      <stop
         style="stop-color:#d9fff6;stop-opacity:0;"
         offset="1"
         id="stop936" />
    </linearGradient>
    <linearGradient
       inkscape:collect="always"
       id="linearGradient1403">
      <stop
         style="stop-color:#ccaaff;stop-opacity:1;"
         offset="0"
         id="stop1399" />
      <stop
         style="stop-color:#ccaaff;stop-opacity:0;"
         offset="1"
         id="stop1401" />
    </linearGradient>
    <linearGradient
       id="linearGradient1395"
       inkscape:collect="always">
      <stop
         id="stop1391"
         offset="0"
         style="stop-color:#ff1616;stop-opacity:1" />
      <stop
         id="stop1393"
         offset="1"
         style="stop-color:#ff1d1d;stop-opacity:0" />
    </linearGradient>
    <linearGradient
       inkscape:collect="always"
       id="linearGradient1383">
      <stop
         style="stop-color:#980000;stop-opacity:1;"
         offset="0"
         id="stop1379" />
      <stop
         style="stop-color:#980000;stop-opacity:0;"
         offset="1"
         id="stop1381" />
    </linearGradient>
    <linearGradient
       inkscape:collect="always"
       id="linearGradient832">
      <stop
         style="stop-color:#ffcfcf;stop-opacity:1;"
         offset="0"
         id="stop828" />
      <stop
         style="stop-color:#ffcfcf;stop-opacity:0;"
         offset="1"
         id="stop830" />
    </linearGradient>
    <radialGradient
       inkscape:collect="always"
       xlink:href="#linearGradient832"
       id="radialGradient834"
       cx="3.2286437"
       cy="286.62921"
       fx="3.2286437"
       fy="286.62921"
       r="1.0866126"
       gradientTransform="matrix(1.8608797,0.8147617,-0.38242057,0.87343168,106.71446,33.692223)"
       gradientUnits="userSpaceOnUse" />
    <radialGradient
       inkscape:collect="always"
       xlink:href="#linearGradient1383"
       id="radialGradient1385"
       cx="4.1787109"
       cy="286.89261"
       fx="4.1787109"
       fy="286.89261"
       r="1.2260786"
       gradientTransform="matrix(1.7016464,0,0,1.6348586,-2.9319775,-182.10895)"
       gradientUnits="userSpaceOnUse" />
    <radialGradient
       inkscape:collect="always"
       xlink:href="#linearGradient1395"
       id="radialGradient1389"
       gradientUnits="userSpaceOnUse"
       gradientTransform="matrix(0.66230313,-1.6430738,1.0154487,0.40931507,-290.06307,177.39489)"
       cx="4.02069"
       cy="287.79269"
       fx="4.02069"
       fy="287.79269"
       r="1.0866126" />
    <linearGradient
       inkscape:collect="always"
       xlink:href="#linearGradient1403"
       id="linearGradient1405"
       x1="8.3939333"
       y1="288.1091"
       x2="7.0158253"
       y2="287.32819"
       gradientUnits="userSpaceOnUse" />
    <linearGradient
       inkscape:collect="always"
       xlink:href="#linearGradient938"
       id="linearGradient940"
       x1="7.609839"
       y1="288.73215"
       x2="7.609839"
       y2="283.78305"
       gradientUnits="userSpaceOnUse" />
    <linearGradient
       inkscape:collect="always"
       xlink:href="#linearGradient954"
       id="linearGradient956"
       x1="3.0150654"
       y1="285.94464"
       x2="3.0150654"
       y2="282.40109"
       gradientUnits="userSpaceOnUse" />
    <linearGradient
       inkscape:collect="always"
       xlink:href="#linearGradient954"
       id="linearGradient1138"
       gradientUnits="userSpaceOnUse"
       x1="3.0150654"
       y1="285.94464"
       x2="3.0150654"
       y2="284.62277" />
    <linearGradient
       inkscape:collect="always"
       xlink:href="#linearGradient896"
       id="linearGradient898"
       x1="2.6224887"
       y1="20"
       x2="2.6224887"
       y2="-0.44642866"
       gradientUnits="userSpaceOnUse"
       gradientTransform="matrix(0.26458333,0,0,0.26458333,2.6134662,283.36966)" />
    <linearGradient
       inkscape:collect="always"
       xlink:href="#linearGradient904"
       id="linearGradient906"
       x1="5.1028705"
       y1="285.45639"
       x2="6.1422977"
       y2="284.41696"
       gradientUnits="userSpaceOnUse"
       gradientTransform="translate(1.4605056e-7,1.403324e-5)" />
    <linearGradient
       inkscape:collect="always"
       xlink:href="#linearGradient923"
       id="linearGradient915"
       gradientUnits="userSpaceOnUse"
       x1="2.6224887"
       y1="-7.0215807"
       x2="2.6224887"
       y2="19.346249" />
    <linearGradient
       inkscape:collect="always"
       xlink:href="#linearGradient904"
       id="linearGradient927"
       gradientUnits="userSpaceOnUse"
       gradientTransform="matrix(1.1002873,0,0,1.1002873,-0.68825328,-28.478577)"
       x1="5.2755661"
       y1="285.28369"
       x2="5.7849226"
       y2="284.77432" />
    <radialGradient
       inkscape:collect="always"
       xlink:href="#linearGradient935"
       id="radialGradient939"
       cx="6.3029079"
       cy="284.65445"
       fx="6.3029079"
       fy="284.65445"
       r="1.6035197"
       gradientTransform="matrix(1.3125186,0,0,1.1401099,-1.643629,-40.275795)"
       gradientUnits="userSpaceOnUse" />
  </defs>
  <sodipodi:namedview
     id="base"
     pagecolor="#181818"
     bordercolor="#666666"
     borderopacity="1.0"
     inkscape:pageopacity="0"
     inkscape:pageshadow="2"
     inkscape:zoom="2.8"
     inkscape:cx="-104.22073"
     inkscape:cy="-48.222179"
     inkscape:document-units="mm"
     inkscape:current-layer="layer1"
     showgrid="false"
     units="px"
     inkscape:window-width="1920"
     inkscape:window-height="1042"
     inkscape:window-x="0"
     inkscape:window-y="38"
     inkscape:window-maximized="0"
     showguides="false"
     fit-margin-top="0"
     fit-margin-left="0"
     fit-margin-right="0"
     fit-margin-bottom="0" />
  <metadata
     id="metadata5">
    <rdf:RDF>
      <cc:Work
         rdf:about="">
        <dc:format>image/svg+xml</dc:format>
        <dc:type
           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
        <dc:title></dc:title>
      </cc:Work>
    </rdf:RDF>
  </metadata>
  <g
     inkscape:label="Layer 1"
     inkscape:groupmode="layer"
     id="layer1"
     transform="translate(-2.6134661,-283.36966)">
    <path
       sodipodi:type="inkscape:offset"
       inkscape:radius="1.2412"
       inkscape:original="M 5.9453125 2.2695312 C 4.8334737 2.2695312 3.9394531 3.1635518 3.9394531 4.2753906 L 3.9394531 15.724609 C 3.9394531 16.836448 4.8334737 17.730469 5.9453125 17.730469 L 14.054688 17.730469 C 15.166526 17.730469 16.060547 16.836448 16.060547 15.724609 L 16.060547 7.6113281 L 10.71875 2.2695312 L 5.9453125 2.2695312 z "
       style="opacity:0.223;vector-effect:none;fill:url(#linearGradient915);fill-opacity:1;stroke:none;stroke-width:0.62362206;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
       id="path913"
       d="m 5.9453125,1.0292969 c -1.777198,0 -3.2460937,1.4688957 -3.2460937,3.2460937 V 15.724609 c -1e-7,1.777199 1.4688954,3.246094 3.2460937,3.246094 h 8.1093755 c 1.777198,0 3.246093,-1.468895 3.246093,-3.246094 V 7.6113281 A 1.2413241,1.2413241 0 0 0 16.9375,6.734375 L 11.595703,1.3925781 A 1.2413241,1.2413241 0 0 0 10.71875,1.0292969 Z"
       transform="matrix(0.26458333,0,0,0.26458333,2.6134662,283.36966)" />
    <path
       style="opacity:1;vector-effect:none;fill:url(#linearGradient898);fill-opacity:1;stroke:none;stroke-width:0.16500001;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
       d="m 4.1864967,283.97014 c -0.294174,0 -0.5307169,0.23654 -0.5307169,0.53072 v 3.02927 c 0,0.29417 0.2365429,0.53072 0.5307169,0.53072 h 2.1456056 c 0.2941738,0 0.5307169,-0.23655 0.5307169,-0.53072 v -2.14664 l -1.4133505,-1.41335 z"
       id="rect882"
       inkscape:connector-curvature="0" />
    <path
       inkscape:connector-curvature="0"
       id="path929"
       d="m 4.1864967,283.97014 c -0.294174,0 -0.5307169,0.23654 -0.5307169,0.53072 v 3.02927 c 0,0.29417 0.2365429,0.53072 0.5307169,0.53072 h 2.1456056 c 0.2941738,0 0.5307169,-0.23655 0.5307169,-0.53072 v -2.14664 l -1.4133505,-1.41335 z"
       style="opacity:0.71;vector-effect:none;fill:url(#radialGradient939);fill-opacity:1;stroke:none;stroke-width:0.16500001;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
    <path
       inkscape:connector-curvature="0"
       id="path925"
       d="m 5.3077278,283.97015 v 0.97114 c 0,0.32367 0.2602653,0.58395 0.583941,0.58395 h 0.9711506 z"
       style="fill:url(#linearGradient927);fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;opacity:0.449" />
    <path
       style="fill:url(#linearGradient906);fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
       d="m 5.4494687,283.97014 v 0.88263 c 0,0.29417 0.2365431,0.53072 0.5307169,0.53072 h 0.8826336 z"
       id="path890"
       inkscape:connector-curvature="0" />
  </g>
</svg>

Modified static/style.scss from [37c2876b01] to [7f55f7d5d1].

1002
1003
1004
1005
1006
1007
1008






























































		}
		> article.post {
			margin: 0.1in 0.2in;
			margin-left: 0.4in;
		}
	}
}





































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
		}
		> article.post {
			margin: 0.1in 0.2in;
			margin-left: 0.4in;
		}
	}
}

.media.manager main, .media.gallery {
	display: grid;
	grid-template-columns: 2in 1fr;
	grid-template-rows: max-content 1fr;
	menu {
		@extend %navmenu;
	}
	.gallery, .dir {
		background: tone(-55%,-0.5);
		border: 1px solid tone(-60%);
		padding: 0.2in;
		display: flex;
		flex-wrap: wrap;
	}
	.gallery {
		grid-row: 1/2; grid-column: 2/3;
		margin-left: 0.1in;
		flex-flow: row;
		> a[href].thumb {
			display: block;
			width: 1.5in;
			padding: 0.1in;
			height: max-content;
			> img {
				width: 1.5in; height: 1.5in;
			}
			> .caption {
				text-align: center;
				font-size: 80%;
			}
		}
	}
	.dir {
		grid-row: 2/3; grid-column: 1/3;
		margin-top: 0.1in;
		flex-flow: column;
		flex-grow: 1;
		> a[href].file {
			padding: 0.1in 0.15in;
			text-decoration: none;
			height: max-content;
			background-image: url(/s/file.webp); //TODO different icons for different mime types
			background-repeat: no-repeat;
			background-position: left;
			padding-left: 0.4in;
			> .label {
				text-decoration: underline;
			}
			> .mime {
				font-style: italic;
				opacity: 60%;
				margin-left: 0.5ex;
			}
		}
	}
}

.media.upload form {
	padding: 0.1in 0.2in;
	@extend %box;
}

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

226
227
228
229
230
231
232









233
234
235
236
237
238
239
...
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
	rtdby: uint64 -- 0 if not rt
	rtact: uint64 -- 0 if not rt, id of rt action otherwise
	isreply: bool
	source: &m.source

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










m.user_conf_funcs = function(be,n,ty,rty,rty2)
	rty = rty or ty
	local gt
	if not rty2 -- what the fuck?
		then gt = {&m.source, uint64, rawstring} -> rty;
		else gt = {&m.source, uint64, rawstring} -> {rty, rty2};
................................................................................
			-- artifact: bytea
			-- mime:     pstring
	artifact_quicksearch: {&m.source, lib.mem.ptr(uint8)} -> {uint64,bool}
		-- checks whether a hash is already in the database without uploading
		-- the entire file to the database server
			-- hash: bytea
				--> artifact id (0 if null), suppressed?
	artifact_expropriate: {&m.source, uint64, uint64, lib.mem.ptr(int8)} -> {}
		-- claims an existing artifact for the user's own collection
			-- uid:         uint64
			-- artifact id: uint64
			-- description: pstring




	artifact_disclaim: {&m.source, uint64, uint64} -> {}
		-- a user disclaims their ownership stake in an artifact, removing it from
		-- the database entirely if they were the only owner, and removing their
		-- description of it either way
			-- uid:         uint64
			-- artifact id: uint64
	artifact_excise: {&m.source, uint64, bool} -> {}
................................................................................
		-- (admin action) forcibly excise an artifact from the database, deleting
		-- all links to it and removing it from users' collections. if "blacklist,"
		-- the artifact will be banned and attempts to upload it in the future
		-- will fail, triggering a report. mainly intended for dealing with spam,
		-- IP violations, That Which Shall Not Be Named, and various other infohazards.
			-- artifact id: uint64
			-- blacklist:   bool










	nkvd_report_issue: {&m.source, &m.kompromat} -> {}
		-- an incidence of Badthink has been detected. report it immediately
		-- to the Supreme Soviet
	nkvd_reports_enum: {&m.source, &m.kompromat} -> lib.mem.ptr(m.kompromat)
		-- search through the Archives
			-- proto: kompromat (null for all records, or a prototype describing the records to return)







>
>
>
>
>
>
>
>
>







 







|




>
>
>
>







 







>
>
>
>
>
>
>
>
>







226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
...
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
	rtdby: uint64 -- 0 if not rt
	rtact: uint64 -- 0 if not rt, id of rt action otherwise
	isreply: bool
	source: &m.source

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

struct m.artifact {
	rid: uint64
	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};
................................................................................
			-- artifact: bytea
			-- mime:     pstring
	artifact_quicksearch: {&m.source, lib.mem.ptr(uint8)} -> {uint64,bool}
		-- checks whether a hash is already in the database without uploading
		-- the entire file to the database server
			-- hash: bytea
				--> artifact id (0 if null), suppressed?
	artifact_expropriate: {&m.source, uint64, uint64, lib.str.t, lib.str.t} -> {}
		-- claims an existing artifact for the user's own collection
			-- uid:         uint64
			-- artifact id: uint64
			-- description: pstring
			-- folder:      pstring
	artifact_claim_alter: {&m.source, uint64, uint64, lib.str.t, lib.str.t} -> {}
		-- edits an existing claim to an artifact
			-- ibid
	artifact_disclaim: {&m.source, uint64, uint64} -> {}
		-- a user disclaims their ownership stake in an artifact, removing it from
		-- the database entirely if they were the only owner, and removing their
		-- description of it either way
			-- uid:         uint64
			-- artifact id: uint64
	artifact_excise: {&m.source, uint64, bool} -> {}
................................................................................
		-- (admin action) forcibly excise an artifact from the database, deleting
		-- all links to it and removing it from users' collections. if "blacklist,"
		-- the artifact will be banned and attempts to upload it in the future
		-- will fail, triggering a report. mainly intended for dealing with spam,
		-- IP violations, That Which Shall Not Be Named, and various other infohazards.
			-- artifact id: uint64
			-- blacklist:   bool
	artifact_enum_uid: {&m.source, uint64, lib.str.t} -> lib.mem.lstptr(m.artifact)
		-- produces a list of artifacts claimed by a user, optionally
		-- restricted by folder (empty string = new only)
	artifact_fetch: {&m.source, uint64, uint64} -> lib.mem.ptr(m.artifact)
		-- fetch a user's view of an artifact
			-- uid: uint64
			-- rid: uint64
	artifact_load: {&m.source, uint64} -> {lib.mem.ptr(uint8),lib.str.t}
		-- load the body of an artifact into memory (also returns mime)

	nkvd_report_issue: {&m.source, &m.kompromat} -> {}
		-- an incidence of Badthink has been detected. report it immediately
		-- to the Supreme Soviet
	nkvd_reports_enum: {&m.source, &m.kompromat} -> lib.mem.ptr(m.kompromat)
		-- search through the Archives
			-- proto: kompromat (null for all records, or a prototype describing the records to return)

Modified str.t from [004fceba8a] to [d98a573fe7].

17
18
19
20
21
22
23











24
25
26
27
28
29
30
..
51
52
53
54
55
56
57
58





59
60
61
62
63
64
65
...
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
	ndup = terralib.externfunction('strndup',{rawstring, intptr} -> rawstring);
	fmt = terralib.externfunction('asprintf',
		terralib.types.funcpointer({&rawstring,rawstring},{int},true));
	bfmt = terralib.externfunction('sprintf',
		terralib.types.funcpointer({rawstring,rawstring},{int},true));
	span = terralib.externfunction('strspn',{rawstring, rawstring} -> rawstring);
}












do local strptr = (lib.mem.ptr(int8))
	local strref = (lib.mem.ref(int8))
	local byteptr = (lib.mem.ptr(uint8))
	strptr.metamethods.__cast = function(from,to,e)
		if from == &int8 then
			return `strptr {ptr = e, ct = m.sz(e)}
................................................................................
		var sz = lib.math.biggest(self.ct, other.ct)
		for i = 0, sz do
			if self.ptr[i] == 0 and other.ptr[i] == 0 then return true end
			if self.ptr[i] ~= other.ptr[i] then return false end
		end
		return true
	end






	strptr.methods.cmpl = macro(function(self,other)
		return `self:cmp(strptr { ptr = [other:asvalue()], ct = [#(other:asvalue())] })
	end)
	strref.methods.cmpl = macro(function(self,other)
		return `self:cmp(strref { ptr = [other:asvalue()], ct = [#(other:asvalue())] })
	end)

................................................................................
		for j=0, reject.ct do
			if str.ptr[i] == reject.ptr[j] then return i end
		end
	end
	return maxlen
end

terra m.ffw(str: &int8, maxlen: intptr)
	while maxlen > 0 and @str ~= 0 and
	      (@str == @' ' or @str == @'\t' or @str == @'\n') do
		str = str + 1
		maxlen = maxlen - 1
	end
	return str
end


terra m.ffw_unsafe(str: &int8)














	while  @str ~= 0 and
	      (@str == @' ' or @str == @'\t' or @str == @'\n') do
		str = str + 1






	end

	return str
end






































return m







>
>
>
>
>
>
>
>
>
>
>







 







<
>
>
>
>
>







 







|
|


<




>
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
<
<
>
>
>
>
>
>

>
|


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

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
..
62
63
64
65
66
67
68

69
70
71
72
73
74
75
76
77
78
79
80
...
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
	ndup = terralib.externfunction('strndup',{rawstring, intptr} -> rawstring);
	fmt = terralib.externfunction('asprintf',
		terralib.types.funcpointer({&rawstring,rawstring},{int},true));
	bfmt = terralib.externfunction('sprintf',
		terralib.types.funcpointer({rawstring,rawstring},{int},true));
	span = terralib.externfunction('strspn',{rawstring, rawstring} -> rawstring);
}

terra m.ffw(str: &int8, maxlen: intptr)
	if maxlen == 0 then maxlen = m.sz(str) end
	while maxlen > 0 and @str ~= 0 and
	      (@str == @' ' or @str == @'\t' or @str == @'\n') do
		str = str + 1
		maxlen = maxlen - 1
	end
	return str
end


do local strptr = (lib.mem.ptr(int8))
	local strref = (lib.mem.ref(int8))
	local byteptr = (lib.mem.ptr(uint8))
	strptr.metamethods.__cast = function(from,to,e)
		if from == &int8 then
			return `strptr {ptr = e, ct = m.sz(e)}
................................................................................
		var sz = lib.math.biggest(self.ct, other.ct)
		for i = 0, sz do
			if self.ptr[i] == 0 and other.ptr[i] == 0 then return true end
			if self.ptr[i] ~= other.ptr[i] then return false end
		end
		return true
	end

	terra strptr:ffw()
		var newp = m.ffw(self.ptr,self.ct)
		var newct = self.ct - (newp - self.ptr)
		return strptr { ptr = newp, ct = newct }
	end
	strptr.methods.cmpl = macro(function(self,other)
		return `self:cmp(strptr { ptr = [other:asvalue()], ct = [#(other:asvalue())] })
	end)
	strref.methods.cmpl = macro(function(self,other)
		return `self:cmp(strref { ptr = [other:asvalue()], ct = [#(other:asvalue())] })
	end)

................................................................................
		for j=0, reject.ct do
			if str.ptr[i] == reject.ptr[j] then return i end
		end
	end
	return maxlen
end

terra m.ffw_unsafe(str: &int8)
	while  @str ~= 0 and
	      (@str == @' ' or @str == @'\t' or @str == @'\n') do
		str = str + 1

	end
	return str
end

terra m.find(haystack: pstr, needle: pstr): pstr
	for i=0,haystack.ct do
		for j=0, needle.ct do
			if haystack(i + j) ~= needle(j) then goto nomatch end
		end
		do return pstr {
			ptr = haystack.ptr + i;
			ct = haystack.ct - i;
		} end
	::nomatch::end
	return pstr.null()
end

terra m.splitmap(str: pstr, delim: pstr, expect: uint16)
	var vec: lib.mem.vec(pstr) vec:init(expect)
	var start = pstr{str.ptr, str.ct}
	while true do


		var n = m.find(start, delim)
		if not n then break end
		vec:push(pstr {ptr = start.ptr, ct = start.ct - n.ct})
		n.ptr = n.ptr + delim.ct
		n.ct = n.ct - delim.ct
		start = n
	end
	vec:push(start)
	return vec:crush()
end

terra m.toknext(str: m.t, delim: int8, brkspace: bool): {pstr,intptr,bool}
	var b: m.acc b:init(48)
	var mode: int8 = 0
	var esc = false
	var spacebroke = false
	var max = 0
	for i=0, str.ct do
		max = i
		if str(i) == 0         then break
		elseif esc == true     then b:push(str.ptr + i,1) esc = false
		elseif str(i) == @'\\' then esc = true

		elseif mode == 0 and str(i) == delim then break
		elseif mode ~= 2 and str(i) == @'"'  then
			if mode == 1
				then mode = 0
				else mode = 1
			end
		elseif mode ~= 1 and str(i) == @"'" then
			if mode == 2
				then mode = 0
				else mode = 2
			end

		elseif brkspace and mode == 0 and (
			str(i) == @' ' or str(i) == @'\t' or
			str(i) == @'\r' or str(i) == @'\n') then
			spacebroke = true
			break

		else b:push(str.ptr + i,1) end
	end
	if mode ~= 0 then return m.t.null(), 0, false end

	return b:finalize(), max, spacebroke
end

return m

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

6
7
8
9
10
11
12



13
14
15
16
17
18
19
local sources = {
	'docskel';
	'confirm';
	'tweet';
	'profile';
	'compose';
	'notice';




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

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







>
>
>







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

	'media-gallery';
	'media-upload';

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

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

Added view/media-gallery.tpl version [d752c55f41].







































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<menu>@menu
	<a href="/media">new uploads</a>
	<a href="/media/unfiled">unfiled</a>
	<hr>
	@folders
	<a href="/media/all">all uploads</a>
	<a href="/media/kind/img">all images</a>
	<a href="/media/kind/vid">all videos</a>
	<a href="/media/kind/txt">all text files</a>
	<a href="/media/king/misc">all others</a>
</menu>

<div class="dir">
	@directory
</div>

<div class="gallery">
	@images
</div>

Added view/media-upload.tpl version [687485d89c].













































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
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" enctype="multipart/form-data">
	<div class="elem">
		<label for="file">file</label>
		<input type="file" name="file" id="file" required>
	</div>
	<div class="elem">
		<label for="desc">description</label>
		<textarea name="desc" id="desc" placeholder="soviet troops planting the red flag on olympus mons after the battle of tharsis (1969)"></textarea>
	</div>
	<div class="elem">
		<label for="folder">folder</label>
		<input type="text" name="folder" id="folder" list="folders">
	</div>
	<menu class="choice horizontal">
		<button>upload</button>
		<a class="button" href="/media">cancel</a>
	</menu>
</form>

<datalist id="folders">
	@folders
</datalist>

Deleted view/media.tpl version [5a68c18a8e].

1
2
3
4
5
6
7
8
9
10
11
12
<menu>
	<a href="/user/@:xid/media">new uploads</a>
	@folders
</menu>

<div name="gallery">
	@images
</div>

<div name="files">
	@files
</div>
<
<
<
<
<
<
<
<
<
<
<
<