Overview
| Comment: | media uploads work now, some types can be viewed |
|---|---|
| Downloads: | Tarball | ZIP archive | SQL archive |
| Timelines: | family | ancestors | descendants | both | trunk |
| Files: | files | file ages | folders |
| SHA3-256: |
93aea04a05d7e6e0ea44a8a308c0f7d3 |
| User & Date: | lexi on 2021-01-07 20:39:57 |
| Other Links: | manifest | tags |
Context
|
2021-01-08
| ||
| 05:58 | enable passwords check-in: d6024624c6 user: lexi tags: trunk | |
|
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 | |
Changes
Modified backend/pgsql.t from [fc1c52be55] to [cb3e1743a5].
550 550 from parsav_artifact_claims as a where uid = $1::bigint and rid = $2::bigint 551 551 ]]; 552 552 }; 553 553 artifact_load = { 554 554 params = {uint64}, sql = [[ 555 555 select content, mime from parsav_artifacts where id = $1::bigint 556 556 ]]; 557 + }; 558 + artifact_folder_enum = { 559 + params = {uint64}, sql = [[ 560 + select distinct folder from parsav_artifact_claims where 561 + uid = $1::bigint and folder is not null 562 + order by folder 563 + ]]; 557 564 }; 558 565 post_attach_ctl_ins = { 559 566 params = {uint64, uint64}, cmd=true, sql = [[ 560 567 update parsav_posts set 561 568 artifacts = artifacts || $2::bigint 562 569 where id = $1::bigint and not 563 570 artifacts @> array[$2::bigint] -- prevent duplication ................................................................................ 826 833 lib.pq.PQclear(res) 827 834 return pqr {0, nil} 828 835 else 829 836 return pqr {ct, res} 830 837 end 831 838 end 832 839 end 840 + 841 +local terra row_to_artifact(res: &pqr, i: intptr): lib.mem.ptr(lib.store.artifact) 842 + var id = res:int(uint64,i,0) 843 + var idbuf: int8[lib.math.shorthand.maxlen] 844 + var idlen = lib.math.shorthand.gen(id, &idbuf[0]) 845 + var desc = res:_string(i,2) 846 + var folder = res:_string(i,3) 847 + var mime = res:_string(i,4) 848 + var m = [ lib.str.encapsulate(lib.store.artifact, { 849 + desc = {`desc.ptr, `desc.ct + 1}; 850 + folder = {`folder.ptr, `folder.ct + 1}; 851 + mime = {`mime.ptr, `mime.ct + 1}; 852 + url = {`&idbuf[0], `idlen + 1}; 853 + }) ] 854 + m.ptr.rid = id 855 + return m 856 +end 833 857 834 858 local terra row_to_post(r: &pqr, row: intptr): lib.mem.ptr(lib.store.post) 835 859 var subj: rawstring, sblen: intptr 836 860 var cvhu: rawstring, cvhlen: intptr 837 861 if r:null(row,3) 838 862 then subj = nil sblen = 0 839 863 else subj = r:string(row,3) sblen = r:len(row,3)+1 ................................................................................ 1581 1605 uid: uint64, 1582 1606 folder: pstring 1583 1607 ) 1584 1608 var res = queries.artifact_enum_uid.exec(src,uid,folder) 1585 1609 if res.sz > 0 then 1586 1610 var m = lib.mem.heapa([lib.mem.ptr(lib.store.artifact)], res.sz) 1587 1611 for i=0,res.sz do 1588 - var id = res:int(uint64,i,0) 1589 - var idbuf: int8[lib.math.shorthand.maxlen] 1590 - var idlen = lib.math.shorthand.gen(id, &idbuf[0]) 1591 - var desc = res:_string(i,2) 1592 - var folder = res:_string(i,3) 1593 - var mime = res:_string(i,4) 1594 - m.ptr[i] = [ lib.str.encapsulate(lib.store.artifact, { 1595 - desc = {`desc.ptr, `desc.ct + 1}; 1596 - folder = {`folder.ptr, `folder.ct + 1}; 1597 - mime = {`mime.ptr, `mime.ct + 1}; 1598 - url = {`&idbuf[0], `idlen + 1}; 1599 - }) ] 1600 - m(i).ptr.rid = id 1612 + m.ptr[i] = row_to_artifact(&res, i) 1601 1613 m(i).ptr.owner = uid 1602 1614 end 1603 1615 return m 1604 1616 else return [lib.mem.lstptr(lib.store.artifact)].null() end 1605 1617 end]; 1618 + 1619 + artifact_fetch = [terra( 1620 + src: &lib.store.source, 1621 + uid: uint64, 1622 + rid: uint64 1623 + ) 1624 + var res = queries.artifact_fetch.exec(src,uid,rid) 1625 + if res.sz > 0 then 1626 + var a = row_to_artifact(&res, 0) 1627 + a.ptr.owner = uid 1628 + res:free() 1629 + return a 1630 + end 1631 + return [lib.mem.ptr(lib.store.artifact)].null() 1632 + end]; 1606 1633 1607 1634 artifact_load = [terra( 1608 1635 src: &lib.store.source, 1609 1636 rid: uint64 1610 1637 ): {binblob, pstring} 1611 1638 var r = queries.artifact_load.exec(src,rid) 1612 1639 if r.sz == 0 then return binblob.null(), pstring.null() end ................................................................................ 1615 1642 var mbin = r:bin(0,0) 1616 1643 var bin = lib.mem.heapa(uint8,mbin.ct) 1617 1644 lib.mem.cpy(bin.ptr, mbin.ptr, bin.ct) 1618 1645 1619 1646 r:free() 1620 1647 return bin, mime 1621 1648 end]; 1649 + 1650 + artifact_folder_enum = [terra( 1651 + src: &lib.store.source, 1652 + uid: uint64 1653 + ) 1654 + var r = queries.artifact_folder_enum.exec(src,uid) 1655 + if r.sz == 0 then return [lib.mem.ptr(pstring)].null() end 1656 + defer r:free() 1657 + var lst = lib.mem.heapa(pstring, r.sz) 1658 + for i=0,r.sz do lst.ptr[i] = r:String(i,0) end 1659 + return lst 1660 + end]; 1622 1661 1623 1662 post_attach_ctl = [terra( 1624 1663 src: &lib.store.source, 1625 1664 post: uint64, 1626 1665 artifact: uint64, 1627 1666 detach: bool 1628 1667 ): {}
Modified html.t from [ee4d50abb4] to [f6f8bc0dcc].
1 1 -- vim: ft=terra 2 2 local m={} 3 3 local pstr = lib.mem.ptr(int8) 4 4 5 5 terra m.sanitize(txt: pstr, quo: bool) 6 + if txt.ptr == nil then return pstr.null() end 7 + if txt.ct == 0 then txt.ct = lib.str.sz(txt.ptr) end 6 8 var a: lib.str.acc a:init(txt.ct*1.3) 7 9 for i=0,txt.ct do 8 10 if txt(i) == @'<' then a:lpush('<') 9 11 elseif txt(i) == @'>' then a:lpush('>') 10 12 elseif txt(i) == @'&' then a:lpush('&') 11 13 elseif quo and txt(i) == @'"' then a:lpush('"') 12 14 else a:push(&txt(i),1) end 13 15 end 14 16 return a:finalize() 15 17 end 18 + 19 +terra m.hexdgt(i: uint8) 20 + if i >= 10 then 21 + return @'A' + (i - 10) 22 + else return 0x30 + i end 23 +end 24 + 25 +terra m.hexbyte(i: uint8): int8[2] 26 + return arrayof(int8, 27 + m.hexdgt(i / 0x10), 28 + m.hexdgt(i % 0x10)) 29 +end 30 + 31 +terra m.urlenc(txt: pstr, qparam: bool) 32 + if txt.ptr == nil then return pstr.null() end 33 + if txt.ct == 0 then txt.ct = lib.str.sz(txt.ptr) end 34 + var a: lib.str.acc a:init(txt.ct*1.3) 35 + for i=0,txt.ct do 36 + if txt(i) == @' ' then a:lpush('+') 37 + elseif txt(i) == @'&' and not qparam then a:lpush('&') 38 + elseif (txt(i) < 0x2c or 39 + (txt(i) > @';' and txt(i) < @'@') or 40 + (txt(i) > @'Z' and txt(i) < @'a') or 41 + (txt(i) >= 0x7b and txt(i) <= 0x7f)) and 42 + txt(i) ~= @'_' and (qparam == true or txt(i) ~= @'=') then 43 + var str = m.hexbyte(txt(i)) 44 + a:lpush('%'):push(&str[0], 2) 45 + else a:push(&txt(i),1) end 46 + end 47 + return a:finalize() 48 +end 16 49 17 50 return m
Modified render/media-gallery.t from [7a98efa9ff] to [da27a31f83].
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 18 + do defer ou:free() 19 + var pfx = pstr.null() 20 + if not owner then 21 + var pa: lib.str.acc pa:init(32) 22 + pa:lpush('/') 23 + if ou(0).origin ~= 0 then pa:lpush('@') end 24 + pa:push(ou(0).xid,0) 25 + pfx = pa:finalize() 26 + end 29 27 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 38 - 39 - var view = data.view.media_gallery { 40 - menu = pstr{'',0}; 41 - folders = pstr{'',0}; 42 - directory = pstr{'',0}; 43 - images = pstr{'',0}; 44 - pfx = pstr{'',0}; 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 53 - 54 - if owner then 55 - view.menu = P'<a class="pos" href="/media/upload">upload</a><hr>' 56 - end 57 - 58 - var md = co.srv:artifact_enum_uid(uid, folder) 59 - var gallery: lib.str.acc gallery:init(256) 60 - var files: lib.str.acc files:init(256) 61 - for i=0,md.ct do 62 - if lib.str.ncmp(md(i)(0).mime, 'image/', 6) == 0 then 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) 66 - :lpush('</div></a>') 28 + if path.ct >= 3 and path(1):cmp(lib.str.lit'a') then 29 + var id, idok = lib.math.shorthand.parse(path(2).ptr, path(2).ct) 30 + if not idok then goto e404 end 31 + var art = co.srv:artifact_fetch(uid, id) 32 + if not art then goto e404 end 33 + if path.ct == 3 then 34 + -- sniff out the artifact type and display the appropriate viewer 35 + var artid = cs(art(0).url) 36 + var btns: lib.str.acc 37 + if owner then 38 + btns:compose('<a class="neg button" href="',pfx,'/media/a/',artid,'/del">delete</a><a class="button" href="',pfx,'/media/a/',artid,'/edit">alter</a>') 39 + else 40 + btns:compose('<a class="pos button" href="',pfx,'/media/a/',artid,'/collect">collect</a>') 41 + end 42 + var btntxt = btns:finalize() defer btntxt:free() 43 + var desc = lib.smackdown.html(pstr{art(0).desc,0}, true) defer desc:free() 44 + var viewerprops = { 45 + pfx = pfx, desc = desc; 46 + id = artid; btns = btntxt; 47 + } 48 + if lib.str.ncmp(art(0).mime, 'image/', 6) == 0 then 49 + var view = data.view.media_image(viewerprops) 50 + var pg = view:tostr() 51 + co:stdpage([lib.srv.convo.page] { 52 + title = lib.str.plit'media :: image'; 53 + class = lib.str.plit'media viewer img'; 54 + cache = false, body = pg; 55 + }) 56 + pg:free() 57 + elseif lib.str.cmp(art(0).mime, 'text/markdown') == 0 then 58 + var view = data.view.media_text(viewerprops) 59 + var text, mime = co.srv:artifact_load(id) mime:free() 60 + view.text = lib.smackdown.html(pstr{[rawstring](text.ptr),text.ct}, false) 61 + text:free() 62 + var pg = view:tostr() 63 + view.text:free() 64 + co:stdpage([lib.srv.convo.page] { 65 + title = lib.str.plit'media :: text'; 66 + class = lib.str.plit'media viewer text'; 67 + cache = false, body = pg; 68 + }) 69 + pg:free() 70 + elseif 71 + lib.str.ncmp(art(0).mime, 'text/', 5) == 0 or 72 + lib.str.cmp(art(0).mime, 'application/x-perl') == 0 or 73 + lib.str.cmp(art(0).mime, 'application/sql') == 0 74 + -- and so on (we need a mimelib at some point) -- 75 + then 76 + var view = data.view.media_text(viewerprops) 77 + var text, mime = co.srv:artifact_load(id) mime:free() 78 + var san = lib.html.sanitize(pstr{[rawstring](text.ptr),text.ct}, false) 79 + text:free() 80 + view.text = lib.str.acc{}:compose('<pre>',san,'</pre>'):finalize() 81 + san:free() 82 + var pg = view:tostr() 83 + view.text:free() 84 + co:stdpage([lib.srv.convo.page] { 85 + title = lib.str.plit'media :: text'; 86 + class = lib.str.plit'media viewer text'; 87 + cache = false, body = pg; 88 + }) 89 + pg:free() 90 + else co:complain(500,'bad file type','this file type is not supported') end 91 + elseif path.ct == 4 then 92 + var act = path(3) 93 + var curl = lib.str.acc{}:compose(pfx, '/media/a/', path(2)):finalize() 94 + defer curl:free() 95 + if act:cmp(lib.str.lit'avi') and lib.str.ncmp(art(0).mime, 'image/', 6) == 0 then 96 + co:confirm('set avatar', 'are you sure you want this image to be your new avatar?',curl) 97 + elseif act:cmp(lib.str.lit'del') then 98 + co:confirm('delete', 'are you sure you want to permanently delete this artifact?',curl) 99 + else goto e404 end 100 + end 67 101 else 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) 70 - :lpush('</span> <span class="mime">'):push(md(i)(0).mime,0) 71 - :lpush('</span></a>') 72 - end 73 - md(i):free() 74 - end 102 + var mode: uint8 = show_new 103 + var folder: pstr 104 + if path.ct == 2 then 105 + if path(1):cmp(lib.str.lit'unfiled') then 106 + mode=show_unfiled 107 + elseif path(1):cmp(lib.str.lit'all') then 108 + mode=show_all 109 + else goto e404 end 110 + elseif path.ct == 3 and path(1):cmp(lib.str.lit'kind') then 111 + end 112 + 113 + var folders = co.srv:artifact_folder_enum(uid) 114 + 115 + if mode == show_new then 116 + folder = lib.str.plit'' 117 + elseif mode == show_all or mode == show_unfiled then 118 + folder = pstr.null() 119 + end 120 + 121 + var view = data.view.media_gallery { 122 + menu = pstr{'',0}; 123 + folders = pstr{'',0}; 124 + directory = pstr{'',0}; 125 + images = pstr{'',0}; 126 + pfx = pfx; 127 + } 128 + 129 + if folders.ct > 0 then 130 + var fa: lib.str.acc fa:init(128) 131 + var fldr = co:pgetv('folder') 132 + for i=0,folders.ct do 133 + var ule = lib.html.urlenc(folders(i), true) defer ule:free() 134 + var san = lib.html.sanitize(folders(i), true) defer san:free() 135 + fa:lpush('<a href="'):ppush(pfx):lpush('/media?folder='):ppush(ule) 136 + :lpush('">'):ppush(san):lpush('</a>') 137 + lib.dbg('checking folder ',{fldr.ptr,fldr.ct},' against ',{folders(i).ptr,folders(i).ct}) 138 + if fldr:ref() and folders(i):cmp(fldr) 139 + then folder = folders(i) lib.dbg('folder match ',{fldr.ptr,fldr.ct}) 140 + else folders(i):free() 141 + end 142 + end 143 + fa:lpush('<hr>') 144 + view.folders = fa:finalize() 145 + folders:free() 146 + end 147 + 148 + if owner then 149 + view.menu = P'<a class="pos" href="/media/upload">upload</a><hr>' 150 + end 151 + 152 + var md = co.srv:artifact_enum_uid(uid, folder) 153 + var gallery: lib.str.acc gallery:init(256) 154 + var files: lib.str.acc files:init(256) 155 + for i=0,md.ct do 156 + var desc = lib.smackdown.html(pstr{md(i)(0).desc,0}, true) defer desc:free() 157 + if lib.str.ncmp(md(i)(0).mime, 'image/', 6) == 0 then 158 + gallery:lpush('<a class="thumb" href="'):ppush(pfx):lpush('/media/a/') 159 + :push(md(i)(0).url,0):lpush('"><img src="/file/'):push(md(i)(0).url,0) 160 + :lpush('"><div class="caption">'):ppush(desc) 161 + :lpush('</div></a>') 162 + else 163 + var mime = lib.html.sanitize(pstr{md(i)(0).mime,0}, true) defer mime:free() --just in case 164 + files:lpush('<a class="file" href="'):ppush(pfx):lpush('/media/a/') 165 + :push(md(i)(0).url,0):lpush('"><span class="label">'):ppush(desc) 166 + :lpush('</span> <span class="mime">'):ppush(mime) 167 + :lpush('</span></a>') 168 + end 169 + md(i):free() 170 + end 75 171 76 - view.images = gallery:finalize() 77 - view.directory = files:finalize() 172 + view.images = gallery:finalize() 173 + view.directory = files:finalize() 78 174 79 - if acc ~= nil then 80 - view:append(acc) 81 - else 82 - lib.dbg('emitting page') 83 - var pg = view:tostr() defer pg:free() 84 - lib.dbg('compiled page') 85 - co:stdpage([lib.srv.convo.page] { 86 - title = P'media'; 87 - class = P'media manager'; 88 - cache = false; 89 - body = pg; 90 - }) 91 - lib.dbg('sent page') 92 - end 175 + if acc ~= nil then 176 + view:append(acc) 177 + else 178 + lib.dbg('emitting page') 179 + var pg = view:tostr() defer pg:free() 180 + lib.dbg('compiled page') 181 + co:stdpage([lib.srv.convo.page] { 182 + title = P'media'; 183 + class = P'media manager'; 184 + cache = false; 185 + body = pg; 186 + }) 187 + lib.dbg('sent page') 188 + end 93 189 94 - view.images:free() 95 - view.directory:free() 96 - if not owner then view.pfx:free() end 97 - if md:ref() then md:free() end 98 - do return end 190 + view.images:free() 191 + view.directory:free() 192 + if view.folders.ct > 0 then view.folders:free() end 193 + if folder.ct > 0 then folder:free() end 194 + if md:ref() then md:free() end 195 + end 196 + if not owner then pfx:free() end 197 + return end 99 198 100 199 ::e404:: co:complain(404,'media not found','no such media exists on this server') 101 200 end 102 201 103 202 return render_media_gallery
Modified route.t from [8605ea50bd] to [881176d2e1].
510 510 511 511 terra http.file_serve_raw(co: &lib.srv.convo, id: lib.mem.ptr(int8)) 512 512 var id, idok = lib.math.shorthand.parse(id.ptr, id.ct) 513 513 if not idok then goto e404 end 514 514 var data, mime = co.srv:artifact_load(id) 515 515 if not data then goto e404 end 516 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) 517 + var safemime = mime 518 + -- TODO this is not a satisfactory solution; it's a bandaid on a gaping 519 + -- chest wound. ultimately we need to compile a whitelist of safe mime 520 + -- types as part of mimelib, but that is no small task. for now, this 521 + -- will keep the patient from immediately bleeding out 522 + if mime:cmp(lib.str.plit'text/html') or 523 + mime:cmp(lib.str.plit'text/xml') or 524 + mime:cmp(lib.str.plit'application/xhtml+xml') or 525 + mime:cmp(lib.str.plit'application/vnd.wap.xhtml+xml') 526 + then -- danger will robinson 527 + safemime = lib.str.plit'text/plain' 528 + elseif mime:cmp(lib.str.plit'application/x-shockwave-flash') then 529 + safemime = lib.str.plit'application/octet-stream' 530 + end 531 + lib.net.mg_printf(co.con, "HTTP/1.1 200 OK\r\nContent-Type: %.*s\r\nContent-Length: %llu\r\nContent-Security-Policy: sandbox; default-src 'none'; form-action 'none'; navigate-to 'none';\r\nX-Content-Options: nosniff\r\n\r\n", safemime.ct, safemime.ptr, data.ct + 2) 518 532 lib.net.mg_send(co.con, data.ptr, data.ct) 519 533 lib.net.mg_send(co.con, '\r\n', 2) 520 534 return end 521 535 522 536 ::e404:: do co:complain(404, 'artifact not found', 'no such artifact has been uploaded to this instance') return end 523 537 end 524 538
Modified smackdown.t from [926ec15b69] to [e99ea3e622].
48 48 do return l+i end 49 49 ::nexti::end 50 50 end 51 51 52 52 local terra scanline_wordend(l: rawstring, max: intptr, n: rawstring, nc: intptr) 53 53 var sl = scanline(l,max,n,nc) 54 54 if sl == nil then return nil else sl = sl + nc end 55 - if sl >= l+max or isws(@sl) then return sl-nc end 55 + if sl >= l+max or not isws(@(sl-1)) then return sl-nc end 56 56 return nil 57 57 end 58 58 59 59 terra m.html(input: pstr, firstline: bool) 60 + if input.ptr == nil then return pstr.null() end 60 61 if input.ct == 0 then input.ct = lib.str.sz(input.ptr) end 61 62 62 63 var md = lib.html.sanitize(input,false) 63 64 64 65 var styled: lib.str.acc styled:init(md.ct) 65 66 66 67 do var i = 0 while i < md.ct do 67 - var wordstart = (i == 0 or isws(md.ptr[i-1])) 68 - var wordend = (i == md.ct - 1 or isws(md.ptr[i+1])) 68 + --var wordstart = (i == 0 or isws(md.ptr[i-1])) 69 + --var wordend = (i == md.ct - 1 or isws(md.ptr[i+1])) 70 + var wordstart = (i + 1 < md.ct and not isws(md.ptr[i+1])) 71 + var wordend = (i == md.ct - 1 or not isws(md.ptr[i-1])) 69 72 70 73 var here = md.ptr + i 71 74 var rem = md.ct - i 72 75 if @here == @'[' then 73 76 var sep = scanline(here,rem, '](', 2) 74 77 var term = scanline(sep+2,rem - ((sep+2)-here), ')', 1) 75 78 if sep ~= nil and term ~= nil then
Modified srv.t from [31acbbce83] to [ca6d27c8d7].
280 280 } 281 281 282 282 self:statpage(code, body) 283 283 284 284 body.title:free() 285 285 body.body:free() 286 286 end 287 + 288 +terra convo:confirm(title: pstring, msg: pstring, cancel: pstring) 289 + var conf = data.view.confirm { 290 + title = title; 291 + query = msg; 292 + cancel = cancel; 293 + } 294 + var ti: lib.str.acc ti:compose('confirm :: ', title) 295 + var body = conf:tostr() defer body:free() 296 + var cf = [convo.page] { 297 + title = ti:finalize(); 298 + class = lib.str.plit 'query'; 299 + body = body; cache = false; 300 + } 301 + self:stdpage(cf) 302 + cf.title:free() 303 +end 287 304 288 305 convo.methods.assertpow = macro(function(self, pow) 289 306 return quote 290 307 var ok = true 291 308 if self.aid == 0 or self.who.rights.powers.[pow:asvalue()]() == false then 292 309 ok = false 293 310 self:complain(403,'insufficient privileges',['you lack the <strong>'..pow:asvalue()..'</strong> power and cannot perform this action'])
Modified static/style.scss from [f5bd822625] to [fb57063208].
281 281 grid-row: 1 / 2; 282 282 display: grid; 283 283 grid-template-columns: 1.1in 1fr; 284 284 grid-template-rows: max-content 1fr; 285 285 > .avatar { 286 286 display: block; 287 287 width: 1in; height: 1in; 288 + object-fit: contain; 288 289 grid-column: 1 / 2; 289 290 grid-row: 1 / 3; 290 291 border: 1px solid black; 291 292 } 292 293 > .id { 293 294 grid-column: 2 / 3; 294 295 grid-row: 1 / 2; ................................................................................ 487 488 font-size: 1.5ex !important; 488 489 letter-spacing: 1.3px; 489 490 padding-bottom: 3px; 490 491 border-radius: 2px; 491 492 vertical-align: baseline; 492 493 box-shadow: 1px 1px 1px black; 493 494 } 495 + 496 +pre { @extend %teletype; white-space: pre-wrap; } 494 497 495 498 div.thread { 496 499 margin-left: 0.3in; 497 500 & + article.post { margin-top: 0.3in; } 498 501 } 499 502 500 503 a[href].username { ................................................................................ 1059 1062 height: max-content; 1060 1063 background-image: url(/s/file.webp); //TODO different icons for different mime types 1061 1064 background-repeat: no-repeat; 1062 1065 background-position: left; 1063 1066 padding-left: 0.4in; 1064 1067 > .label { 1065 1068 text-decoration: underline; 1069 + text-decoration-width: 1px; 1070 + text-underline-offset: 0.1em; 1071 + text-decoration-color: tone(10%,-0.5); 1072 + } 1073 + &:hover > .label { 1066 1074 } 1067 1075 > .mime { 1068 1076 font-style: italic; 1069 1077 opacity: 60%; 1070 1078 margin-left: 0.5ex; 1071 1079 } 1072 1080 } ................................................................................ 1073 1081 } 1074 1082 } 1075 1083 1076 1084 .media.upload form { 1077 1085 padding: 0.1in 0.2in; 1078 1086 @extend %box; 1079 1087 } 1088 + 1089 +body.media div.viewer { 1090 + @extend %box; 1091 + padding: 0.2in; 1092 + margin-bottom: 0.2in; 1093 + &.img { 1094 + > img { 1095 + display: block; 1096 + max-width: 100%; 1097 + margin: auto; 1098 + } 1099 + .caption { 1100 + margin-top: 0.2in; 1101 + text-align: center; 1102 + &:empty {margin: 0;} 1103 + } 1104 + } 1105 + &.text { 1106 + > .desc { 1107 + border-bottom: 1px solid tone(-5%); 1108 + box-shadow: 0 2px 0 black; 1109 + margin-bottom: 0.1in; 1110 + padding-bottom: 0.1in; 1111 + } 1112 + > article { 1113 + font-size: 90%; 1114 + padding: 0 0.2in; 1115 + max-height: calc(100vh - 3in); 1116 + overflow-y: scroll; 1117 + text-align: justify; 1118 + } 1119 + } 1120 +}
Modified store.t from [fdc1c1d9e2] to [eca94a58d5].
488 488 -- restricted by folder (empty string = new only) 489 489 artifact_fetch: {&m.source, uint64, uint64} -> lib.mem.ptr(m.artifact) 490 490 -- fetch a user's view of an artifact 491 491 -- uid: uint64 492 492 -- rid: uint64 493 493 artifact_load: {&m.source, uint64} -> {lib.mem.ptr(uint8),lib.str.t} 494 494 -- load the body of an artifact into memory (also returns mime) 495 + artifact_folder_enum: {&m.source, uint64} -> lib.mem.ptr(lib.str.t) 496 + -- enumerate all of a user's folders 495 497 496 498 nkvd_report_issue: {&m.source, &m.kompromat} -> {} 497 499 -- an incidence of Badthink has been detected. report it immediately 498 500 -- to the Supreme Soviet 499 501 nkvd_reports_enum: {&m.source, &m.kompromat} -> lib.mem.ptr(m.kompromat) 500 502 -- search through the Archives 501 503 -- proto: kompromat (null for all records, or a prototype describing the records to return)
Modified view/load.lua from [fbf23a2927] to [9f4f065de0].
9 9 'tweet'; 10 10 'profile'; 11 11 'compose'; 12 12 'notice'; 13 13 14 14 'media-gallery'; 15 15 'media-upload'; 16 + 'media-image'; 17 + 'media-text'; 16 18 17 19 'login-username'; 18 20 'login-challenge'; 19 21 20 22 'conf'; 21 23 'conf-profile'; 22 24 'conf-sec';
Added view/media-image.tpl version [df3a1820c7].
1 +<div class="viewer img"> 2 + <img src="/file/@id"> 3 + <div class="caption">@desc</div> 4 +</div> 5 +<menu class="choice horizontal"> 6 + <a class="button" href="@pfx/media">index</a> 7 + <a class="button" href="@pfx/media/a/@id/avi">set as avatar</a> 8 + @btns 9 + <a class="button" href="/file/@id" download>download</a> 10 +</menu>
Added view/media-text.tpl version [73c12d23b4].
1 +<div class="viewer text"> 2 + <div class="desc">@desc</div> 3 + <article>@text</article> 4 +</div> 5 +<menu class="choice horizontal"> 6 + <a class="button" href="@pfx/media">index</a> 7 + @btns 8 + <a class="button" href="/file/@id" download>download</a> 9 +</menu>