Overview
| Comment: | iterate on user mgmt UI |
|---|---|
| Downloads: | Tarball | ZIP archive | SQL archive |
| Timelines: | family | ancestors | descendants | both | trunk |
| Files: | files | file ages | folders |
| SHA3-256: |
f09cd181619364e959c3e0fd19b18d8c |
| User & Date: | lexi on 2021-01-02 18:32:02 |
| Other Links: | manifest | tags |
Context
|
2021-01-04
| ||
| 06:44 | add likes, retweets, and iterate on a whole bunch of other shit check-in: 78b0198f09 user: lexi tags: trunk | |
|
2021-01-02
| ||
| 18:32 | iterate on user mgmt UI check-in: f09cd18161 user: lexi tags: trunk | |
| 04:47 | work on admin ui check-in: 7129658e1d user: lexi tags: trunk | |
Changes
Modified render/conf/users.t from [4494d99300] to [09fa445b20].
15 15 case [uint16](3) then acc:lpush('⚜️') end 16 16 case [uint16](4) then acc:lpush('🗡') end 17 17 case [uint16](5) then acc:lpush('🗝') end 18 18 else acc:lpush('🕴') 19 19 end 20 20 end 21 21 22 -local num_field = macro(function(acc,name,lbl,min,max,value) 22 +local push_num_field = macro(function(acc,name,lbl,min,max,value,disable) 23 23 name = name:asvalue() 24 24 lbl = lbl:asvalue() 25 + local start = '<div class="elem small">' 26 + local enabled = start .. string.format('<label for="%s">%s</label><input type="number" id="%s" name="%s" min="', name, lbl, name, name) 27 + local disabled = start .. string.format('<label>%s</label><div class="txtbox">', lbl) 25 28 return quote 26 29 var decbuf: int8[21] 27 - in acc:lpush([string.format('<div class="elem small"><label for="%s">%s</label><input type="number" id="%s" name="%s" min="', name, lbl, name, name)]) 28 - :push(lib.math.decstr(min, &decbuf[20]),0) 29 - :lpush('" max="'):push(lib.math.decstr(max, &decbuf[20]),0) 30 - :lpush('" value="'):push(lib.math.decstr(value, &decbuf[20]),0):lpush('"></div>') 30 + if disable then 31 + acc:lpush([disabled]) 32 + :push(lib.math.decstr(value, &decbuf[20]),0):lpush('</div></div>') 33 + else 34 + acc:lpush([enabled]):push(lib.math.decstr(min, &decbuf[20]),0) 35 + :lpush('" max="'):push(lib.math.decstr(max, &decbuf[20]),0) 36 + :lpush('" value="'):push(lib.math.decstr(value, &decbuf[20]),0):lpush('"></div>') 37 + end 31 38 end 32 39 end) 33 40 34 -local terra 35 -push_checkbox(acc: &lib.str.acc, name: pstr, lbl: pstr, on: bool, enabled: bool) 36 - acc:lpush('<label><input type="checkbox" name="'):ppush(name):lpush('"') 37 - if on then acc:lpush(' checked') end 38 - if not enabled then acc:lpush(' disabled') end 39 - acc:lpush('> '):ppush(lbl):lpush('</label>') 41 +local input_pusher = function(kind,wrap,uniq) 42 + local fn = terra(acc: &lib.str.acc, name: pstr, val: pstr, lbl: pstr, on: bool, enabled: bool, class: pstr) 43 + if wrap then acc:lpush('<label>') end 44 + acc:lpush(['<input type="'..kind..'" name="']):ppush(name) 45 + if not wrap then 46 + acc:lpush('" id="'):ppush(name) 47 + if uniq then acc:lpush('-'):ppush(val) end 48 + end 49 + if val:ref() then acc:lpush('" value="'):ppush(val) end 50 + if class:ref() then acc:lpush('" class="'):ppush(class) end 51 + acc:lpush('"') 52 + if on then acc:lpush(' checked') end 53 + if not enabled then acc:lpush(' disabled') end 54 + acc:lpush('>') 55 + if not wrap then acc:lpush('<label for="'):ppush(name) 56 + if uniq then acc:lpush('-'):ppush(val) end 57 + acc:lpush('">') 58 + else acc:lpush(' ') end 59 + acc:ppush(lbl):lpush('</label>') 60 + end 61 + fn.name = string.format('push-input-element<%q>',kind) 62 + return fn 40 63 end 64 + 65 +local push_checkbox = input_pusher('checkbox',true,false) 66 +local push_pickbox = input_pusher('checkbox',false,false) 67 +local push_radio = input_pusher('radio',false,true) 41 68 42 69 local mode_local, mode_remote, mode_staff, mode_peers, mode_peons, mode_all = 0,1,2,3,4,5 43 70 local terra 44 71 render_conf_users(co: &lib.srv.convo, path: lib.mem.ptr(pref)): pstr 45 72 if path.ct == 3 then 46 73 var uid, ok = lib.math.shorthand.parse(path(2).ptr,path(2).ct) 47 74 if not ok then goto e404 end 48 75 var user = co.srv:actor_fetch_uid(uid) 49 76 -- FIXME allow xids as well, for manual queries 50 77 if not user then goto e404 end 51 78 defer user:free() 52 79 if not co.who:overpowers(user.ptr) then goto e403 end 53 80 54 - var islinkct = false 55 - var cinp: lib.str.acc cinp:init(128) 56 - var clnk: lib.str.acc clnk:compose('<hr>') 81 + var cinp: lib.str.acc cinp:init(256) 82 + var clnk: lib.str.acc clnk:init(512) 57 83 cinp:lpush('<div class="elem-group">') 84 + if user.ptr.rights.rank > 0 and (co.who.rights.powers.elevate() or co.who.rights.powers.demote()) then 85 + var max = co.who.rights.rank 86 + if not co.who.rights.powers.elevate() then max = user.ptr.rights.rank end 87 + var min = co.srv.cfg.nranks 88 + if not co.who.rights.powers.demote() then min = user.ptr.rights.rank end 89 + 90 + push_num_field(cinp, 'rank', 'rank', max, min, user.ptr.rights.rank, user.ptr.id == co.who.id) 91 + end 58 92 if co.who.rights.powers.herald() then 59 93 var sanitized: pstr 60 94 if user.ptr.epithet == nil 61 95 then sanitized = pstr {ptr='', ct=0} 62 96 else sanitized = lib.html.sanitize(cs(user.ptr.epithet),true) 63 97 end 64 98 cinp:lpush('<div class="elem"><label for="epithet">epithet</label><input type="text" id="epithet" name="epithet" value="'):ppush(sanitized):lpush('"></div>') 65 99 if user.ptr.epithet ~= nil then sanitized:free() end 66 100 end 67 - if user.ptr.rights.rank > 0 and (co.who.rights.powers.elevate() or co.who.rights.powers.demote()) then 68 - var max = co.who.rights.rank 69 - if not co.who.rights.powers.elevate() then max = user.ptr.rights.rank end 70 - var min = co.srv.cfg.nranks 71 - if not co.who.rights.powers.demote() then min = user.ptr.rights.rank end 72 - 73 - num_field(cinp, 'rank', 'rank', max, min, user.ptr.rights.rank) 74 - end 75 101 if co.who.rights.powers.invite() or co.who.rights.powers.discipline() then 76 102 var min = 0 77 103 if not (co.who.rights.powers.discipline() or 78 104 co.who.rights.powers.demote() and co.who.rights.powers.invite()) 79 105 then min = user.ptr.rights.invites end 80 106 var max = co.srv.cfg.maxinvites 81 107 if not co.who.rights.powers.invite() then max = user.ptr.rights.invites end 82 108 83 - num_field(cinp, 'invites', 'invites', min, max, user.ptr.rights.invites) 109 + push_num_field(cinp, 'invites', 'invites', min, max, user.ptr.rights.invites, false) 84 110 end 85 - cinp:lpush('</div><div class="check-panel">') 111 + cinp:lpush('</div><div class="elem"><div class="check-panel">') 86 112 87 - if (user.ptr.rights.rank == 0 and co.who.rights.powers.elevate()) or 88 - (user.ptr.rights.rank > 0 and co.who.rights.powers.demote()) then 89 - push_checkbox(&cinp, 'staff', 'site staff member', user.ptr.rights.rank > 0, true) 113 + if user.ptr.id ~= co.who.id and 114 + ((user.ptr.rights.rank == 0 and co.who.rights.powers.elevate()) or 115 + (user.ptr.rights.rank > 0 and co.who.rights.powers.demote())) then 116 + push_checkbox(&cinp, 'staff', pstr.null(), 'site staff member', user.ptr.rights.rank > 0, true, pstr.null()) 90 117 end 91 118 92 - cinp:lpush('</div>') 119 + cinp:lpush('</div></div>') 93 120 94 - if co.who.rights.powers.elevate() or 95 - co.who.rights.powers.demote() then 121 + if (co.who.rights.powers.elevate() or 122 + co.who.rights.powers.demote()) and user.ptr.id ~= co.who.id then 96 123 var map = array([lib.store.privmap]) 97 - cinp:lpush('<label>powers</label><div class="check-panel">') 124 + cinp:lpush('<details><summary>powers</summary><div class="pick-list">') 98 125 for i=0, [map.type.N] do 99 - if (co.who.rights.powers and map[i].priv) == map[i].priv then 100 - var name: int8[64] 101 - var on = (user.ptr.rights.powers and map[i].priv) == map[i].priv 102 - var enabled = (on and co.who.rights.powers.demote()) or 126 + if (co.who.rights.powers and map[i].priv):sz() > 0 then 127 + var on = (user.ptr.rights.powers and map[i].priv):sz() > 0 128 + var enabled = ( on and co.who.rights.powers.demote() ) or 103 129 ((not on) and co.who.rights.powers.elevate()) 104 - lib.str.cpy(&name[0], 'allow-') 105 - lib.str.ncpy(&name[6], map[i].name.ptr, map[i].name.ct) 106 - push_checkbox(&cinp, pstr{ptr=&name[0],ct=map[i].name.ct+6}, 107 - map[i].name, on, enabled) 130 + var namea: lib.str.acc namea:compose('power-', map[i].name) 131 + var name = namea:finalize() 132 + push_pickbox(&cinp, name, pstr.null(), map[i].name, on, enabled, pstr.null()) 133 + name:free() 108 134 end 109 135 end 110 - cinp:lpush('</div>') 136 + cinp:lpush('</div></details>') 111 137 end 112 138 113 139 -- TODO black mark system? e.g. resolution option for badthink reports 114 140 -- adds a black mark to the offending user; they can be automatically banned 115 141 -- or brought up for review after a certain number of offenses; possibly lower 116 142 -- set of default privs for marked users 117 143 118 144 var cinpp = cinp:finalize() defer cinpp:free() 119 145 var clnkp: pstr 120 - if islinkct then clnkp = clnk:finalize() else 146 + if clnk.sz > 0 then clnkp = clnk:finalize() else 121 147 clnk:free() 122 148 clnkp = pstr { ptr='', ct=0 } 123 149 end 124 150 var unym: lib.str.acc unym:init(64) 125 151 unym:lpush('<a href="/') 126 152 if user(0).origin ~= 0 then unym:lpush('@') end 127 153 do var sanxid = lib.html.sanitize(user(0).xid, true) ................................................................................ 133 159 var pg = data.view.conf_user_ctl { 134 160 name = unym:finalize(); 135 161 inputcontent = cinpp; 136 162 linkcontent = clnkp; 137 163 } 138 164 var ret = pg:tostr() 139 165 pg.name:free() 140 - if islinkct then clnkp:free() end 166 + if clnkp.ct > 0 then clnkp:free() end 141 167 return ret 142 168 else 143 169 var modes = array(P'local', P'remote', P'staff', P'titled', P'peons', P'all') 144 170 var idbuf: int8[lib.math.shorthand.maxlen] 145 171 var ulst: lib.str.acc ulst:init(256) 146 172 var mode: uint8 = mode_local 147 173 var modestr = co:pgetv('show')
Modified srv.t from [6be667433b] to [34fad9fa1a].
539 539 var fr = lib.file.open(befile, [lib.file.mode.read]) 540 540 if fr.ok == false then 541 541 lib.bail('could not open configuration file ', befile) 542 542 end 543 543 544 544 var f = fr.val 545 545 var c: lib.mem.vec(lib.store.source) c:init(8) 546 - var text: lib.str.acc text:init(64) 546 + var text: lib.str.acc text:init(256) 547 547 do var buf: int8[64] 548 548 while true do 549 549 var ct = f:read(buf, [buf.type.N]) 550 550 if ct == 0 then break end 551 551 text:push(buf, ct) 552 552 end 553 553 end
Modified static/style.scss from [f40a20016f] to [4fd9d6949f].
602 602 } 603 603 form { 604 604 margin: 0.15in 0; 605 605 > p:first-child { margin-top: 0; } 606 606 > p:last-child { margin-bottom: 0; } 607 607 .elem { 608 608 margin: 0.1in 0; 609 - label { display:block; font-weight: bold; padding: 0.03in 0; } 610 - .txtbox { 609 + > label,summary { display:block; font-weight: bold; padding: 0.03in 0; } 610 + > .txtbox { 611 611 @extend %serif; 612 612 box-sizing: border-box; 613 613 padding: 0.08in 0.1in; 614 614 border: 1px solid black; 615 615 background: tone(-55%); 616 616 } 617 - input, textarea, .txtbox { 617 + > input, textarea, .txtbox { 618 618 display: block; 619 619 width: 100%; 620 620 } 621 - textarea { resize: vertical; min-height: 2in; } 621 + > textarea { resize: vertical; min-height: 2in; } 622 622 } 623 - :is(.elem,.elem-group) + %button { margin-left: 50%; width: 50%; } 623 + body.conf & > %button { margin-left: 50%; width: 50%; } 624 624 .elem-group { 625 625 display: flex; 626 626 flex-flow: row; 627 627 > .elem { 628 628 flex-shrink: 1; 629 629 flex-grow: 1; 630 630 margin-left: 0.1in; ................................................................................ 767 767 text-align: center; 768 768 padding: 0.3em 0; 769 769 margin: 0.2em 0.1em; 770 770 cursor: default; 771 771 } 772 772 } 773 773 774 -:is(%button, a[href]).neg { --co: 60 } 774 +:is(%button, a[href]).neg { --co: 30 } 775 775 :is(%button, a[href]).pos { --co: -30 } 776 + 777 +.pick-list { 778 + display: flex; 779 + flex-flow: row wrap; 780 + padding: 0.1in; 781 + background-color: tone(-50%); 782 + border: 1px solid tone(-53%); 783 + border-top: 1px solid tone(-57%); 784 + border-bottom: 1px solid tone(-45%); 785 + margin: 0.3em 0; 786 + details & { border: none; background: none; } 787 + > input[type="radio"], > input[type="checkbox"] { 788 + display: none; 789 + &+label { 790 + display: block; 791 + flex-grow: 1; 792 + padding: 0.08in 0.05in; 793 + margin: 0.03in; 794 + flex-basis: 15%; 795 + cursor: pointer; 796 + transition: 0.3s; 797 + text-align: center; 798 + border: 1px solid transparent; 799 + text-shadow: 1px 1px black; 800 + color: otone(15%); 801 + border-radius: 4px; 802 + &:nth-child(7n+1) { --co: -10 } //silly hack 803 + &:nth-child(7n+2) { --co: -35 } 804 + &:nth-child(7n+3) { --co: -20 } 805 + &:nth-child(7n+4) { --co: -50 } 806 + &:nth-child(7n+5) { --co: -40 } 807 + &:nth-child(7n+6) { --co: -5 } 808 + &:nth-child(7n+7) { --co: -25 } 809 + } 810 + &+label:hover { 811 + background-color: otone(-35%); 812 + color: white; 813 + } 814 + &:checked+label { 815 + border-top: 1px solid otone(-10%); 816 + border-bottom: 1px solid otone(-50%); 817 + background: linear-gradient(to bottom, otone(-25%,-0.2), otone(-28%,-0.4) 35%, otone(-30%,-0.7)); 818 + color: white; 819 + box-shadow: 0 0 0 1px tone(-60%); 820 + &:hover { 821 + border-top: 1px solid otone(10%); 822 + border-bottom: 1px solid otone(-60%); 823 + font-weight: bold; 824 + } 825 + } 826 + } 827 +} 828 + 829 +details { 830 + //border: 1px solid tone(-60%); 831 + border-top: 1px solid tone(-40%); 832 + border-bottom: 1px solid tone(-60%); 833 + border-radius: 3px; 834 + padding: 0.05in 0.3in; 835 + margin: 0.08in 0; 836 + // background: linear-gradient(to top, tone(-55%), tone(-50%)); 837 + background: tone(-50%); 838 + & > summary { 839 + display: block; 840 + margin: 0 -0.25in; 841 + cursor: pointer; 842 + user-select: none; 843 + text-decoration: underline; 844 + text-decoration-color: tone(10%,-0.5); 845 + text-underline-offset: 0.1em; 846 + text-shadow: 1px 1px black; 847 + font-weight: normal; 848 + 849 + &:hover { 850 + color: white; 851 + text-decoration-color: tone(10%,-0.1); 852 + } 853 + &::marker { display: none; } 854 + &::-webkit-details-marker { display: none; } 855 + &::before { 856 + display: inline-block; 857 + content: '➤'; 858 + padding: 0 0.3em; 859 + color: tone(-30%); 860 + transition: 0.4s; 861 + } 862 + } 863 + &[open] { 864 + // background: linear-gradient(to bottom, tone(-55%), tone(-50%)); 865 + border-bottom: 1px solid tone(-40%); 866 + border-top: 1px solid tone(-60%); 867 + > summary { 868 + font-weight: bold; 869 + &::before { 870 + transform: rotate(90deg) scale(1.1); 871 + color: tone(-20%); 872 + text-shadow: 0 0 8px tone(-30%); 873 + } 874 + } 875 + } 876 +}
Modified str.t from [265a0fa659] to [1e93ad8eb5].
112 112 113 113 local terra biggest(a: intptr, b: intptr) 114 114 if a > b then return a else return b end 115 115 end 116 116 117 117 terra m.acc:init(run: intptr) 118 118 --lib.dbg('initializing string accumulator') 119 - self.buf = [rawstring](lib.mem.heapa_raw(run)) 120 - self.run = run 121 - self.space = run 119 + if run == 0 then 120 + lib.warn('attempted to allocate zero-length string accumulator') 121 + self.buf = nil 122 + else 123 + self.buf = [rawstring](lib.mem.heapa_raw(run)) 124 + if self.buf == nil then 125 + lib.warn('string buffer allocation failed, very little memory availble') 126 + end 127 + end 128 + self.run = lib.trn(self.buf == nil, 0, run) 129 + self.space = self.run 122 130 self.sz = 0 123 131 return self 124 132 end; 125 133 126 134 terra m.acc:free() 127 135 --lib.dbg('freeing string accumulator') 128 136 if self.buf ~= nil and self.space > 0 then ................................................................................ 159 167 160 168 terra m.acc:push(str: rawstring, len: intptr) 161 169 --var llen = len 162 170 if str == nil then return self end 163 171 --if str[len - 1] == 0xA then llen = llen - 1 end -- don't display newlines in debug output 164 172 -- lib.dbg('pushing "',{str,llen},'" onto accumulator') 165 173 if self.buf == nil then self:init(self.run) end 174 + if self.buf == nil then lib.warn('attempted to push string onto unallocated accumulator') return self end 166 175 if len == 0 then len = m.sz(str) end 167 176 if len >= self.space - self.sz then 168 177 self.space = self.space + biggest(self.run,len + 1) 169 178 self.buf = [rawstring](lib.mem.heapr_raw(self.buf, self.space)) 170 179 end 171 180 lib.mem.cpy(self.buf + self.sz, str, len) 172 181 self.sz = self.sz + len
Modified view/conf-user-ctl.tpl from [9830040aea] to [7abbc95a91].
1 1 <form method="post"> 2 2 <div class="elem"> 3 3 <label>user</label> 4 4 <div class="txtbox">@name</div> 5 5 </div> 6 6 @inputcontent 7 7 <button>alter</button> 8 - @linkcontent 9 8 </form> 9 +@linkcontent