parsav  Check-in [f09cd18161]

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: f09cd181619364e959c3e0fd19b18d8c0615e70bdbd0ff6e17ab0fe7ec47afdf
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