Overview
| Comment: | further iteration on media |
|---|---|
| Downloads: | Tarball | ZIP archive | SQL archive |
| Timelines: | family | ancestors | descendants | both | trunk |
| Files: | files | file ages | folders |
| SHA3-256: |
af5ed65b68e96856432eb7477e8852b2 |
| User & Date: | lexi on 2021-01-07 09:39:29 |
| Other Links: | manifest | tags |
Context
|
2021-01-07
| ||
| 20:39 | media uploads work now, some types can be viewed check-in: 93aea04a05 user: lexi tags: trunk | |
| 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 | |
Changes
Modified backend/pgsql.t from [5d4cebec04] to [fc1c52be55].
1587 1587 for i=0,res.sz do 1588 1588 var id = res:int(uint64,i,0) 1589 1589 var idbuf: int8[lib.math.shorthand.maxlen] 1590 1590 var idlen = lib.math.shorthand.gen(id, &idbuf[0]) 1591 1591 var desc = res:_string(i,2) 1592 1592 var folder = res:_string(i,3) 1593 1593 var mime = res:_string(i,4) 1594 - var url = lib.str.acc{}:init(48):lpush('/media/a/'):push(&idbuf[0],idlen):finalize() defer url:free() 1595 1594 m.ptr[i] = [ lib.str.encapsulate(lib.store.artifact, { 1596 1595 desc = {`desc.ptr, `desc.ct + 1}; 1597 1596 folder = {`folder.ptr, `folder.ct + 1}; 1598 1597 mime = {`mime.ptr, `mime.ct + 1}; 1599 - url = {`url.ptr, `url.ct + 1}; 1598 + url = {`&idbuf[0], `idlen + 1}; 1600 1599 }) ] 1601 1600 m(i).ptr.rid = id 1602 1601 m(i).ptr.owner = uid 1603 1602 end 1604 1603 return m 1605 1604 else return [lib.mem.lstptr(lib.store.artifact)].null() end 1606 1605 end]; 1606 + 1607 + artifact_load = [terra( 1608 + src: &lib.store.source, 1609 + rid: uint64 1610 + ): {binblob, pstring} 1611 + var r = queries.artifact_load.exec(src,rid) 1612 + if r.sz == 0 then return binblob.null(), pstring.null() end 1613 + 1614 + var mime = r:String(0,1) 1615 + var mbin = r:bin(0,0) 1616 + var bin = lib.mem.heapa(uint8,mbin.ct) 1617 + lib.mem.cpy(bin.ptr, mbin.ptr, bin.ct) 1618 + 1619 + r:free() 1620 + return bin, mime 1621 + end]; 1607 1622 1608 1623 post_attach_ctl = [terra( 1609 1624 src: &lib.store.source, 1610 1625 post: uint64, 1611 1626 artifact: uint64, 1612 1627 detach: bool 1613 1628 ): {}
Modified math.t from [1d1d177061] to [60110bc615].
224 224 var sz: intptr = 0 225 225 for i = 0, f.ct do 226 226 if f(i) == @',' then goto skip end 227 227 if f(i) >= 0x30 and f(i) <= 0x39 then 228 228 sz = sz * 10 229 229 sz = sz + f(i) - 0x30 230 230 else 231 - if i+1 == f.ct or f(i) == 0 then return sz, true end 232 - if i+2 == f.ct or f(i+1) == 0 then 231 + if i+0 == f.ct or f(i) == 0 then return sz, true end 232 + if i+1 == f.ct or f(i+1) == 0 then 233 233 if f(i) == @'b' then return sz/8, true end -- bits 234 234 else 235 235 var s: intptr = 0 236 - if i+3 == f.ct or f(i+2) == 0 then 236 + if i+2 == f.ct or f(i+2) == 0 then 237 237 s = i + 1 238 - elseif (i+4 == f.ct or f(i+3) == 0) and f(i+1) == @'i' then 238 + elseif (i+3 == f.ct or f(i+3) == 0) and f(i+1) == @'i' then 239 239 -- grudgingly tolerate ~mebibits~ and its ilk, without 240 240 -- affecting the result in any way 241 241 s = i + 2 242 242 else return 0, false end 243 243 244 244 if f(s) == @'b' then sz = sz/8 -- bits 245 245 elseif f(s) ~= @'B' then return 0, false end -- wth 246 246 end 247 247 var c = f(i) 248 - if c >= @'A' and c <= @'Z' then c = c - 0x20 end 248 + if c >= @'A' and c <= @'Z' then c = c + 0x20 end 249 249 switch c do -- normal char literal syntax doesn't work here, leads to llvm error (!!) 250 250 case [uint8]([string.byte('k')]) then return sz * [1024ULL ^ 1], true end 251 251 case [uint8]([string.byte('m')]) then return sz * [1024ULL ^ 2], true end 252 252 case [uint8]([string.byte('g')]) then return sz * [1024ULL ^ 3], true end 253 253 case [uint8]([string.byte('t')]) then return sz * [1024ULL ^ 4], true end 254 - case [uint8]([string.byte('e')]) then return sz * [1024ULL ^ 5], true end 255 - case [uint8]([string.byte('y')]) then return sz * [1024ULL ^ 6], true end 256 - else return sz, true 254 + case [uint8]([string.byte('p')]) then return sz * [1024ULL ^ 5], true end 255 + case [uint8]([string.byte('e')]) then return sz * [1024ULL ^ 6], true end 256 + case [uint8]([string.byte('z')]) then return sz * [1024ULL ^ 7], true end 257 + case [uint8]([string.byte('y')]) then return sz * [1024ULL ^ 8], true end 258 + else return 0, false 257 259 end 258 260 end 259 261 ::skip::end 260 262 return sz, true 261 263 end 262 264 263 265 return m
Modified render/media-gallery.t from [79b3557b2e] to [7a98efa9ff].
1 1 -- vim: ft=terra 2 2 local pstr = lib.str.t 3 3 local P = lib.str.plit 4 4 local terra cs(s: rawstring) 5 5 return pstr { ptr = s, ct = lib.str.sz(s) } 6 6 end 7 7 8 -local show_all,show_new,show_files,show_vid,show_img=1,2,3,4,5 8 +local show_all,show_new,show_unfiled,show_files,show_vid,show_img=1,2,3,4,5,6 9 9 10 10 local terra 11 11 render_media_gallery(co: &lib.srv.convo, path: lib.mem.ptr(lib.mem.ref(int8)), uid: uint64, acc: &lib.str.acc) 12 12 -- note that when calling this function, path must be adjusted so that path(0) 13 13 -- eq "media" 14 14 var owner = false 15 15 if co.aid ~= 0 and co.who.id == uid then owner = true end 16 16 var ou = co.srv:actor_fetch_uid(uid) 17 17 if not ou then goto e404 end 18 + 19 + var mode: uint8 = show_new 20 + var folder: pstr 21 + if path.ct == 2 then 22 + if path(1):cmp(lib.str.lit'unfiled') then 23 + mode=show_unfiled 24 + elseif path(1):cmp(lib.str.lit'all') then 25 + mode=show_all 26 + else goto e404 end 27 + elseif path.ct == 3 and path(1):cmp(lib.str.lit'kind') then 28 + end 29 + 30 + if mode == show_new then 31 + folder = lib.str.plit'' 32 + elseif mode == show_all then 33 + folder = pstr.null() 34 + elseif mode == show_unfiled then 35 + folder = lib.str.plit'' -- TODO 36 + -- else get folder from query str 37 + end 18 38 19 39 var view = data.view.media_gallery { 20 40 menu = pstr{'',0}; 21 41 folders = pstr{'',0}; 22 42 directory = pstr{'',0}; 23 43 images = pstr{'',0}; 44 + pfx = pstr{'',0}; 24 45 } 46 + if not owner then 47 + var pa: lib.str.acc pa:init(32) 48 + pa:lpush('/') 49 + if ou(0).origin ~= 0 then pa:lpush('@') end 50 + pa:push(ou(0).xid,0) 51 + view.pfx = pa:finalize() 52 + end 25 53 26 54 if owner then 27 55 view.menu = P'<a class="pos" href="/media/upload">upload</a><hr>' 28 56 end 29 - var mode: uint8 = show_new 30 - var folder: pstr 31 - if mode == show_new then 32 - folder = lib.str.plit'' 33 - elseif mode == show_all then 34 - folder = pstr.null() 35 - -- else get folder from query str 36 - end 37 57 38 58 var md = co.srv:artifact_enum_uid(uid, folder) 39 59 var gallery: lib.str.acc gallery:init(256) 40 60 var files: lib.str.acc files:init(256) 41 61 for i=0,md.ct do 42 62 if lib.str.ncmp(md(i)(0).mime, 'image/', 6) == 0 then 43 - gallery:lpush('<a class="thumb" href="') 44 - if not owner then 45 - gallery:lpush('/') 46 - if ou(0).origin ~= 0 then gallery:lpush('@') end 47 - gallery:push(ou(0).xid,0):lpush('/') 48 - end 49 - gallery:push(md(i)(0).url,0) 50 - :lpush('"><img src="') :push(md(i)(0).url,0) 51 - :lpush('/raw"><div class="caption">') :push(md(i)(0).desc,0) 63 + gallery:lpush('<a class="thumb" href="'):ppush(view.pfx):lpush('/media/a/') 64 + :push(md(i)(0).url,0):lpush('"><img src="/file/'):push(md(i)(0).url,0) 65 + :lpush('"><div class="caption">'):push(md(i)(0).desc,0) 52 66 :lpush('</div></a>') 53 67 else 54 - files:lpush('<a class="file" href="') 55 - if not owner then 56 - gallery:lpush('/') 57 - if ou(0).origin ~= 0 then gallery:lpush('@') end 58 - gallery:push(ou(0).xid,0):lpush('/') 59 - end 60 - files:push(md(i)(0).url,0) 61 - :lpush('"><span class="label">'):push(md(i)(0).desc,0) 68 + files:lpush('<a class="file" href="'):ppush(view.pfx):lpush('/media/a/') 69 + :push(md(i)(0).url,0):lpush('"><span class="label">'):push(md(i)(0).desc,0) 62 70 :lpush('</span> <span class="mime">'):push(md(i)(0).mime,0) 63 71 :lpush('</span></a>') 64 72 end 65 73 md(i):free() 66 74 end 67 75 68 76 view.images = gallery:finalize() 69 77 view.directory = files:finalize() 70 78 71 79 if acc ~= nil then 72 80 view:append(acc) 73 81 else 82 + lib.dbg('emitting page') 74 83 var pg = view:tostr() defer pg:free() 84 + lib.dbg('compiled page') 75 85 co:stdpage([lib.srv.convo.page] { 76 86 title = P'media'; 77 87 class = P'media manager'; 78 88 cache = false; 79 89 body = pg; 80 90 }) 91 + lib.dbg('sent page') 81 92 end 82 93 83 94 view.images:free() 84 95 view.directory:free() 96 + if not owner then view.pfx:free() end 85 97 if md:ref() then md:free() end 86 98 do return end 87 99 88 100 ::e404:: co:complain(404,'media not found','no such media exists on this server') 89 101 end 90 102 91 103 return render_media_gallery
Modified route.t from [af3d41e739] to [8605ea50bd].
404 404 end 405 405 406 406 terra http.media_manager(co: &lib.srv.convo, path: hpath, meth: method.t) 407 407 if meth == method.post then 408 408 goto badop 409 409 end 410 410 411 - if path.ct == 1 or (path.ct >= 3 and path(1):cmp(lib.str.lit'a')) then 412 - if meth == method.post then goto badop end 413 - lib.render.media_gallery(co,path,co.who.id,nil) 414 - elseif path.ct == 2 then 415 - if path(1):cmp(lib.str.lit'upload') and co.who.rights.powers.artifact() then 416 - if meth == method.get then 417 - var view = data.view.media_upload { 418 - folders = '' 419 - } 420 - var pg = view:tostr() defer pg:free() 421 - co:stdpage([lib.srv.convo.page] { 422 - title = lib.str.plit'media :: upload'; 423 - class = lib.str.plit'media upload'; 424 - cache = false; body = pg; 425 - }) 426 - elseif meth == method.post_file then 427 - var desc = pstring.null() 428 - var folder = pstring.null() 429 - var mime = pstring.null() 430 - var name = pstring.null() 431 - var body = binblob.null() 432 - for i=0, co.uploads.sz do var up = co.uploads.storage.ptr + i 433 - if up.body.ct > 0 then 434 - if up.field:cmp(lib.str.plit'desc') then 435 - desc = up.body 436 - elseif up.field:cmp(lib.str.plit'folder') then 437 - folder = up.body 438 - elseif up.field:cmp(lib.str.plit'file') then 439 - mime = up.ctype 440 - body = binblob {ptr = [&uint8](up.body.ptr), ct = up.body.ct} 441 - name = up.filename 442 - end 411 + if path.ct == 2 and path(1):cmp(lib.str.lit'upload') and co.who.rights.powers.artifact() then 412 + if meth == method.get then 413 + var view = data.view.media_upload { 414 + folders = '' 415 + } 416 + var pg = view:tostr() defer pg:free() 417 + co:stdpage([lib.srv.convo.page] { 418 + title = lib.str.plit'media :: upload'; 419 + class = lib.str.plit'media upload'; 420 + cache = false; body = pg; 421 + }) 422 + elseif meth == method.post_file then 423 + var desc = pstring.null() 424 + var folder = pstring.null() 425 + var mime = pstring.null() 426 + var name = pstring.null() 427 + var body = binblob.null() 428 + for i=0, co.uploads.sz do var up = co.uploads.storage.ptr + i 429 + if up.body.ct > 0 then 430 + if up.field:cmp(lib.str.plit'desc') then 431 + desc = up.body 432 + elseif up.field:cmp(lib.str.plit'folder') then 433 + folder = up.body 434 + elseif up.field:cmp(lib.str.plit'file') then 435 + mime = up.ctype 436 + body = binblob {ptr = [&uint8](up.body.ptr), ct = up.body.ct} 437 + name = up.filename 443 438 end 444 439 end 445 - if not body then goto badop end 446 - if body.ct > co.srv.cfg.maxupsz then 447 - 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") 448 - return 449 - end 450 - var id = co.srv:artifact_instantiate(body,mime) 451 - if id == 0 then 452 - co:complain(500,'upload failed','artifact rejected. either the server is running out of space or this file is banned from the server') 453 - return 454 - end 455 - co.srv:artifact_expropriate(co.who.id,id,desc,folder) 440 + end 441 + if not body then goto badop end 442 + if body.ct > co.srv.cfg.maxupsz then 443 + 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") 444 + return 445 + end 446 + var id = co.srv:artifact_instantiate(body,mime) 447 + if id == 0 then 448 + co:complain(500,'upload failed','artifact rejected. either the server is running out of space or this file is banned from the server') 449 + return 450 + end 451 + co.srv:artifact_expropriate(co.who.id,id,desc,folder) 452 + 453 + var idbuf: int8[lib.math.shorthand.maxlen] 454 + var idlen = lib.math.shorthand.gen(id,&idbuf[0]) 456 455 457 - var idbuf: int8[lib.math.shorthand.maxlen] 458 - var idlen = lib.math.shorthand.gen(id,&idbuf[0]) 459 - 460 - var url = lib.str.acc{}:compose('/media/a/',pstring{&idbuf[0],idlen}):finalize() 461 - co:reroute(url.ptr) 462 - url:free() 463 - else goto badop end 464 - end 465 - else goto e404 end 456 + var url = lib.str.acc{}:compose('/media/a/',pstring{&idbuf[0],idlen}):finalize() 457 + co:reroute(url.ptr) 458 + url:free() 459 + else goto badop end 460 + else 461 + if meth == method.post then goto badop end 462 + lib.render.media_gallery(co,path,co.who.id,nil) 463 + end 466 464 do return end 467 465 468 466 ::badop:: do co:complain(405, 'invalid operation', 'the operation you have attempted on this post is not meaningful') return end 469 467 ::e404:: do co:complain(404, 'artifact not found', 'no such artifact has been uploaded by this user') return end 470 468 end 471 469 472 470 do local branches = quote end ................................................................................ 505 503 end 506 504 507 505 508 506 terra http.local_avatar(co: &lib.srv.convo, handle: lib.mem.ptr(int8)) 509 507 -- TODO retrieve user avatars 510 508 co:reroute('/s/default-avatar.webp') 511 509 end 510 + 511 +terra http.file_serve_raw(co: &lib.srv.convo, id: lib.mem.ptr(int8)) 512 + var id, idok = lib.math.shorthand.parse(id.ptr, id.ct) 513 + if not idok then goto e404 end 514 + var data, mime = co.srv:artifact_load(id) 515 + if not data then goto e404 end 516 + do defer data:free() defer mime:free() 517 + lib.net.mg_printf(co.con, 'HTTP/1.1 200 OK\r\nContent-Type: %.*s\r\nContent-Length: %llu\r\n\r\n', mime.ct, mime.ptr, data.ct + 2) 518 + lib.net.mg_send(co.con, data.ptr, data.ct) 519 + lib.net.mg_send(co.con, '\r\n', 2) 520 + return end 521 + 522 + ::e404:: do co:complain(404, 'artifact not found', 'no such artifact has been uploaded to this instance') return end 523 +end 512 524 513 525 -- entry points 514 526 terra r.dispatch_http(co: &lib.srv.convo, uri: lib.mem.ptr(int8), meth: method.t) 515 527 lib.dbg('handling URI of form ', {uri.ptr,uri.ct}) 516 528 co.navbar = lib.render.nav(co) 517 529 -- some routes are non-hierarchical, and can be resolved with a simple strcmp 518 530 -- we run through those first before giving up and parsing the URI ................................................................................ 526 538 elseif uri.ptr[1] == @'@' then 527 539 http.actor_profile_xid(co, uri, meth) 528 540 elseif uri.ptr[1] == @'s' and uri.ptr[2] == @'/' and uri.ct > 3 then 529 541 if not meth_get(meth) then goto wrongmeth end 530 542 if not http.static_content(co, uri.ptr + 3, uri.ct - 3) then goto notfound end 531 543 elseif lib.str.ncmp('/avi/', uri.ptr, 5) == 0 then 532 544 http.local_avatar(co, [lib.mem.ptr(int8)] {ptr = uri.ptr + 5, ct = uri.ct - 5}) 545 + elseif lib.str.ncmp('/file/', uri.ptr, 6) == 0 then 546 + http.file_serve_raw(co, [lib.mem.ptr(int8)] {ptr = uri.ptr + 6, ct = uri.ct - 6}) 533 547 elseif uri:cmp(lib.str.plit '/notices') then 534 548 if co.aid == 0 then co:reroute('/login') return end 535 549 http.user_notices(co,meth) 536 550 elseif uri:cmp(lib.str.plit '/compose') then 537 551 if co.aid == 0 then co:reroute('/login') return end 538 552 http.post_compose(co,meth) 539 553 elseif uri:cmp(lib.str.plit '/login') then ................................................................................ 551 565 if path.ct > 1 and path(0):cmp(lib.str.lit('user')) then 552 566 http.actor_profile_uid(co, path, meth) 553 567 elseif path.ct > 1 and path(0):cmp(lib.str.lit('post')) then 554 568 http.tweet_page(co, path, meth) 555 569 elseif path(0):cmp(lib.str.lit('tl')) then 556 570 http.timeline(co, path) 557 571 elseif path(0):cmp(lib.str.lit('media')) then 572 + if co.aid == 0 then goto unauth end 558 573 http.media_manager(co, path, meth) 559 574 elseif path(0):cmp(lib.str.lit('doc')) then 560 575 if not meth_get(meth) then goto wrongmeth end 561 576 http.documentation(co, path) 562 577 elseif path(0):cmp(lib.str.lit('conf')) then 563 578 if co.aid == 0 then goto unauth end 564 579 http.configure(co,path,meth)
Modified srv.t from [a1d0408148] to [31acbbce83].
526 526 end 527 527 -- check for a content-type header, and see if it's a multipart/ 528 528 -- form-data encoded POST request so we can handle file uploads 529 529 co.uploads.sz = 0 co.uploads.run = 0 530 530 if co.method == [lib.http.method.post] then 531 531 var ctt = lib.http.findheader(msg, 'Content-Type') 532 532 if ctt ~= nil then 533 - lib.dbg('found content type', {ctt.ptr,ctt.ct}) 534 533 if lib.str.ncmp(ctt.ptr,'multipart/form-data;',20) == 0 then 535 534 var p = lib.str.ffw(ctt.ptr + 20,ctt.ct-20) 536 535 if lib.str.ncmp(p,'boundary=',9) ~= 0 then 537 536 co:complain(400,'bad request','unrecognized content-type') 538 537 goto fail 539 538 end 540 539 var boundary = pstring {ptr=p+9,ct=ctt.ct - ((p - ctt.ptr) + 9)} 541 - lib.dbg('got boundary ',{boundary.ptr,boundary.ct}) 542 540 co.method = lib.http.method.post_file 543 541 co.uploads:init(8) 544 542 545 543 var bsr = (lib.str.acc{}):compose('\r\n--',boundary,'\r\n'):finalize() 546 544 547 545 var upmap = lib.str.splitmap(co.body,bsr,8) 548 546 -- first entry may not be preceded by header-break ................................................................................ 561 559 lsent.ct = halt.ptr - lsent.ptr 562 560 end 563 561 lsr:free() end 564 562 565 563 for i=0,upmap.ct do 566 564 var hdrbrk = lib.str.find(upmap(i), lib.str.plit'\r\n\r\n') 567 565 if hdrbrk:ref() then 568 - lib.dbg('got new entry') 569 566 var hdrtxt = pstring {upmap(i).ptr,upmap(i).ct - hdrbrk.ct} 570 567 var hdrs = lib.str.splitmap(hdrtxt, '\r\n',6) 571 568 var ctt = pstring.null() 572 569 var ctd = pstring.null() 573 570 for j=0, hdrs.ct do 574 571 var brk = lib.str.find(hdrs(j),lib.str.plit':') 575 572 if brk:ref() then ................................................................................ 581 578 ctd = val 582 579 end 583 580 end 584 581 end 585 582 if ctd:ref() then 586 583 var ctdvals = lib.str.splitmap(ctd, ';', 4) defer ctdvals:free() 587 584 if ctdvals(0):cmp(lib.str.plit'form-data') and ctdvals.ct > 1 then 588 - lib.dbg('found form data') 589 585 var fld = pstring.null() 590 586 var file = pstring.null() 591 587 for j=1, ctdvals.ct do var v = ctdvals(j):ffw() 592 588 var x = lib.str.find(v,lib.str.plit'=') 593 589 if x:ref() then 594 590 var key = pstring{v.ptr, v.ct - x.ct} 595 591 var val = pstring{x.ptr + 1, x.ct - 1} ................................................................................ 776 772 end 777 773 end 778 774 end 779 775 780 776 return 0 781 777 end 782 778 783 ---9twh8y94i5c1qqr7hxu20fyd 784 779 terra cfgcache.methods.load :: {&cfgcache} -> {} 785 780 terra cfgcache:init(o: &srv) 786 781 self.overlord = o 787 782 self:load() 788 783 end 789 784 790 785 terra srv:setup(befile: rawstring)
Modified static/live.js from [45ade869ea] to [76c21b64a1].
117 117 if (ert != null) { lede = post; post = ert; } 118 118 let purl = posturl(post); 119 119 let url = null; 120 120 if (lede == null) {url = purl;} else { 121 121 url = lede.querySelector('a[href].del'). 122 122 attributes.getNamedItem('href').value; 123 123 } 124 - console.log('post',post,'lede',lede,url); 125 124 126 125 if (last == null) { newmap.first = url; } else { 127 126 newmap.map.get(last).next = url; 128 127 } 129 128 newmap.map.set(url, {me: post, go: purl, prev: last, next: null}) 130 129 last = url; 131 130 if (window._liveTweetMap &&
Modified static/style.scss from [7f55f7d5d1] to [f5bd822625].
1018 1018 background: tone(-55%,-0.5); 1019 1019 border: 1px solid tone(-60%); 1020 1020 padding: 0.2in; 1021 1021 display: flex; 1022 1022 flex-wrap: wrap; 1023 1023 } 1024 1024 .gallery { 1025 - grid-row: 1/2; grid-column: 2/3; 1026 - margin-left: 0.1in; 1025 + grid-row: 2/3; grid-column: 1/3; 1026 + margin-top: 0.1in; 1027 1027 flex-flow: row; 1028 + padding: 0 0.1in; 1028 1029 > a[href].thumb { 1030 + background: linear-gradient(to top, tone(10%,-0.8), tone(10%,-0.94) 30%, transparent); 1031 + border-radius: 4px; 1032 + border-bottom: 1px solid tone(15%, -0.5); 1029 1033 display: block; 1030 1034 width: 1.5in; 1031 1035 padding: 0.1in; 1032 1036 height: max-content; 1037 + margin: 0.1in; 1033 1038 > img { 1034 1039 width: 1.5in; height: 1.5in; 1040 + object-fit: contain; 1041 + object-position: center; 1042 + outline: none; 1035 1043 } 1036 1044 > .caption { 1037 1045 text-align: center; 1038 1046 font-size: 80%; 1047 + text-shadow: 1px 1px 0 black; 1039 1048 } 1040 1049 } 1041 1050 } 1042 1051 .dir { 1043 - grid-row: 2/3; grid-column: 1/3; 1044 - margin-top: 0.1in; 1052 + grid-row: 1/2; grid-column: 2/3; 1053 + margin-left: 0.1in; 1045 1054 flex-flow: column; 1046 1055 flex-grow: 1; 1047 1056 > a[href].file { 1048 1057 padding: 0.1in 0.15in; 1049 1058 text-decoration: none; 1050 1059 height: max-content; 1051 1060 background-image: url(/s/file.webp); //TODO different icons for different mime types
Modified view/media-gallery.tpl from [d752c55f41] to [18862037ee].
1 1 <menu>@menu 2 - <a href="/media">new uploads</a> 3 - <a href="/media/unfiled">unfiled</a> 2 + <a href="@pfx/media">new uploads</a> 3 + <a href="@pfx/media/unfiled">unfiled</a> 4 4 <hr> 5 5 @folders 6 - <a href="/media/all">all uploads</a> 7 - <a href="/media/kind/img">all images</a> 8 - <a href="/media/kind/vid">all videos</a> 9 - <a href="/media/kind/txt">all text files</a> 10 - <a href="/media/king/misc">all others</a> 6 + <a href="@pfx/media/all">all uploads</a> 7 + <a href="@pfx/media/kind/img">all images</a> 8 + <a href="@pfx/media/kind/vid">all videos</a> 9 + <a href="@pfx/media/kind/txt">all text files</a> 10 + <a href="@pfx/media/kind/misc">all others</a> 11 11 </menu> 12 12 13 13 <div class="dir"> 14 14 @directory 15 15 </div> 16 16 17 17 <div class="gallery"> 18 18 @images 19 19 </div>