parsav  Check-in [b9cf14c14b]

Overview
Comment:add like + retweets buttons, keyboard nav
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: b9cf14c14b7e9cacecd6ccdebeca2cd72c1510d4e4678dedd5bc2aa7003f6ec8
User & Date: lexi on 2021-01-04 15:29:40
Other Links: manifest | tags
Context
2021-01-04
20:33
more jabbascript improvements check-in: b6c2a79945 user: lexi tags: trunk
15:29
add like + retweets buttons, keyboard nav check-in: b9cf14c14b user: lexi tags: trunk
06:44
add likes, retweets, and iterate on a whole bunch of other shit check-in: 78b0198f09 user: lexi tags: trunk
Changes

Modified static/live.js from [682908b4c8] to [9100f46c08].

     3      3    * interactivity beyond what native HTML+CSS can provide. if so,
     4      4    * we attach the appropriate listeners to them. */
     5      5   window.addEventListener('load', function() {
     6      6   	/* social media is less fun when you can't just click on a tweet
     7      7   	 * to insta-like or -retweet it. this is unfortunately not possible
     8      8   	 * (except in various hideously shitty ways) without javascript. */
     9      9   	function mk(elt) { return document.createElement(elt); }
           10  +	function posturl(post) {
           11  +		return post.querySelector('.permalink').attributes.getNamedItem('href').value;
           12  +	}
           13  +	function postReq(url,act,elt) {
           14  +		fetch(new Request(url, {
           15  +			method: 'POST',
           16  +			body: 'act='+act
           17  +		})).then(function(resp) {
           18  +			if (resp.ok && resp.status == 200) {
           19  +				var i = parseInt(elt.innerHTML)
           20  +				if (isNaN(i)) {i=0}
           21  +				elt.innerHTML = (i+1).toString()
           22  +			}
           23  +		})
           24  +	}
           25  +
           26  +	/* div-based like and rt aren't very keyboard-friendly. add a replacement */
           27  +	if (document.querySelector('body.timeline, body.profile') != null) {
           28  +		window.addEventListener('keydown', function(event) {
           29  +			if (!window._liveTweetMap) { return; }
           30  +			if (event.isComposing || event.keyCode === 229) { return; } // 🙄
           31  +			var cururl = window._liveTweetMap.cur;
           32  +			var nexturl = null;
           33  +			if (event.key == 'j') { // down
           34  +				if (cururl == null) {
           35  +					nexturl = window._liveTweetMap.first
           36  +				} else {
           37  +					nexturl = window._liveTweetMap.map.get(cururl).next
           38  +				}
           39  +			} else if (event.key == 'k') { // up
           40  +				if (cururl == null) {
           41  +					nexturl = window._liveTweetMap.last
           42  +				} else {
           43  +					nexturl = window._liveTweetMap.map.get(cururl).prev
           44  +				}
           45  +			} else if (cururl != null) {
           46  +				var post = window._liveTweetMap.map.get(cururl).me
           47  +				if (event.key == 'f') { // fave
           48  +					postReq(cururl, 'like', post.querySelector('.stats>.like'))
           49  +				} else if (event.key == 'r') { // rt
           50  +					postReq(cururl, 'rt', post.querySelector('.stats>.rt'))
           51  +				} else if (event.key == 'Enter') { // nav
           52  +					window.location = cururl;
           53  +					return;
           54  +				}
           55  +			}
           56  +			if (nexturl != null) {
           57  +				if (cururl != null) {
           58  +					var cur = window._liveTweetMap.map.get(cururl);
           59  +					cur.me.classList.remove('live-selected')
           60  +				}
           61  +				var next = window._liveTweetMap.map.get(nexturl);
           62  +				next.me.classList.add('live-selected')
           63  +				window._liveTweetMap.cur = nexturl
           64  +			}
           65  +		});
           66  +	}
           67  +
    10     68   	function attachButtons() {
    11         -		document.querySelectorAll('body:not(.post) main div.post').forEach(function(post){
    12         -			let url = post.querySelector('.permalink').attributes.getNamedItem('href').value;
    13         -			function postReq(act,elt) {
    14         -				fetch(new Request(url, {
    15         -					method: 'POST',
    16         -					body: 'act='+act
    17         -				})).then(function(resp) {
    18         -					if (resp.ok && resp.status == 200) {
    19         -						var i = parseInt(elt.innerHTML)
    20         -						if (isNaN(i)) {i=0}
    21         -						elt.innerHTML = (i+1).toString()
    22         -					}
    23         -				})
           69  +		var last = null;
           70  +		var newmap = { cur: null, first: null, last: null, map: new Map() }
           71  +		document.querySelectorAll('body:not(.post) main article.post').forEach(function(post){
           72  +			let url = posturl(post);
           73  +			if (last == null) { newmap.first = url; } else {
           74  +				newmap.map.get(last).next = url
           75  +			}
           76  +			newmap.map.set(url, {me: post, prev: last, next: null})
           77  +			last = url
           78  +			if (window._liveTweetMap && window._liveTweetMap.cur == url) {
           79  +				post.classList.add('live-selected');
    24     80   			}
    25     81   
    26     82   			var stats = post.querySelector('.stats');
    27     83   			if (stats == null) {
    28     84   				/* no stats box; create one */
    29     85   				var n = mk('div');
    30     86   				n.classList.add('stats');
................................................................................
    41     97   					}
    42     98   					return n
    43     99   				} else { return s }
    44    100   			}
    45    101   			var like = ensureElt('like', null);
    46    102   			var rt   = ensureElt('rt','.like');
    47    103   			function activate(elt,name) {
    48         -				elt.addEventListener('click', function(e) { postReq(name,elt) });
          104  +				elt.addEventListener('click', function(e) { postReq(url,name,elt) });
    49    105   				elt.style.setProperty('cursor','pointer');
          106  +				elt.setAttribute('tabindex','0');
    50    107   			}
    51    108   			activate(like,'like');
    52    109   			activate(rt,'rt');
    53    110   		});
          111  +		newmap.last = last
          112  +		if (window._liveTweetMap) {
          113  +			newmap.cur = window._liveTweetMap.cur // TODO handle vanishments
          114  +		}
          115  +		window._liveTweetMap = newmap
    54    116   	}
    55    117   
    56    118   	/* update hue-picker background when slider is adjusted */
    57    119   	document.querySelectorAll('.color-picker').forEach(function(box) {
    58    120   		let slider = box.querySelector('[data-color-pick]');
    59    121   		box.style.setProperty('--hue', slider.value);
    60    122   		slider.addEventListener('input', function(e) {

Modified static/style.scss from [322d30fa17] to [ea5ab728f6].

   480    480   	border-radius: 2px;
   481    481   	vertical-align: baseline;
   482    482   	box-shadow: 1px 1px 1px black;
   483    483   }
   484    484   
   485    485   div.thread {
   486    486   	margin-left: 0.3in;
   487         -	& + div.post { margin-top: 0.3in; }
          487  +	& + article.post { margin-top: 0.3in; }
   488    488   }
   489    489   
   490    490   a[href].username {
   491    491   	>.nym { font-weight: bold; }
   492    492   	color: tone(0%,-0.4);
   493    493   	> span.nym { color: tone(10%) }
   494    494   	> span.handle { color: tone(-5%) }
   495    495   	&:hover {
   496    496   		> span.nym { color: white; }
   497    497   		> span.handle { color: tone(15%) }
   498    498   	}
   499    499   }
   500         -div.post {
          500  +article.post {
   501    501   	@extend %box;
   502    502   	display: grid;
   503    503   	margin: unset;
   504    504   	grid-template-columns: 1in 1fr max-content max-content;
   505    505   	grid-template-rows: min-content max-content;
   506    506   	margin-bottom: 0.1in;
          507  +	transition: 0.3s;
   507    508   	>.avatar {
   508    509   		grid-column: 1/2; grid-row: 1/2;
   509    510   		img { display: block; width: 1in; height: 1in; margin:0; }
   510    511   		background: linear-gradient(to bottom, tone(-53%), tone(-57%));
   511    512   	}
   512    513   	>a[href].username {
   513    514   		display: block;
................................................................................
   542    543   		grid-column: 3/4; grid-row: 2/3;
   543    544   		justify-content: center;
   544    545   		> .like, > .rt {
   545    546   			margin: 0.5em 0.3em;
   546    547   			padding-left: 1.3em;
   547    548   			background-size: 1.1em;
   548    549   			background-repeat: no-repeat;
          550  +			pointer-events: all;
   549    551   			min-width: 0.3em;
   550    552   			&:empty {
   551    553   				transition: 0.3s;
   552         -				opacity: 0.1;
   553         -				&:hover { opacity: 0.6 !important; }
          554  +				opacity: 0.0001; // qutebrowser won't show hints if opacity=0 :(
          555  +				&:hover, &:focus { opacity: 0.6 !important; }
   554    556   			}
   555    557   		}
   556         -		> .like {
   557         -			background-image: url(/s/heart.webp);
   558         -		}
   559         -		> .rt {
   560         -			background-image: url(/s/retweet.webp);
   561         -		}
          558  +		> .like { background-image: url(/s/heart.webp); }
          559  +		> .rt   { background-image: url(/s/retweet.webp); }
          560  +	}
          561  +
          562  +	// used for keyboard navigation
          563  +	&.live-selected {
          564  +		margin-left: 0.4in;
          565  +		margin-right: -0.4in;
          566  +		box-shadow: 0 0 0 1px tone(15%), 0 0 1in tone(5%, -0.5);
   562    567   	}
   563    568   }
   564    569   
   565         -div.post:hover div.stats { > .like, > .rt { &:empty {opacity: 0.3;} } }
          570  +article.post:hover div.stats { > .like, > .rt { &:empty {opacity: 0.3;} } }
   566    571   
   567    572   a[href].rawlink {
   568    573   	@extend %teletype;
   569    574   }
   570    575   
   571    576   body.doc main {
   572    577   	@extend %serif;

Modified view/tweet.tpl from [aefe28dd4e] to [80bcf01f8a].

     1         -<div class="post"@attr>
            1  +<article class="post"@attr>
     2      2   	<div class="avatar"><img src="@:avatar"></div>
     3      3   	<a class="username" href="/@:acctlink">@nym</a>
     4      4   	<div class="content">
     5      5   		<div class="subject">@!subject</div>
     6      6   		<div class="text">@text</div>
     7      7   	</div>
     8      8   	@stats
     9         -	<a class="permalink" href="@permalink">@when</a>
    10         -</div>
            9  +	<a class="permalink" href="@permalink"><time>@when</time></a>
           10  +</article>