Half-Elf on Tech

Thoughts From a Professional Lesbian

Author: Ipstenu (Mika Epstein)

  • Remoteless SVG

    Remoteless SVG

    I spent about half a year working on being able to remote load my SVG, and I wanted to for a few reasons:

    1. Having only one place to update my icons is more efficient
    2. They don’t need to be in code versioning control (like GitHub)
    3. Licensing (not all are free)

    I solved those problems when I finally figured out SVG Injection (via javascript) and CORS to allow the scripts from my objects.

    It’s not all lollipops and sunshine

    However. There was, and is, one really big issue. The icons being remote mean if the remote server isn’t accessible but my site is, I got no icons. Now, the smart dev in me knows that I should have a check. If the server is up, use the icons remotely. Otherwise, use a fallback.

    There was one big, and in the end insurmountable, problem with that. There was no way to do that without slowing my site down, which was the headache in the first place. If I check the uptime for every icon on a page, the site went from a 1 second load to up to 20 seconds. In the best solution, I added 2-5 second load to every page checking if the server was up on every page load. Which is kind of the problem with wp_remote_get() in the first place.

    Make ’em local again

    This meant the ‘best’ solution was to make them local. Again. Yaaay.

    But I really didn’t want to have them stashed in GitHub. I wanted my ‘base’ to be the cloud storage, and then that needed to sync. Normally I would use rsync for that but you can’t rsync from CEPH storage.

    Don’t panic. There are answers.

    Boto-Rsync

    If you’re on DreamPress or any DreamHost server, they’ve forked boto-rsync and (if you set up a .boto file) you can do this:

    boto-rsync --endpoint objects-us-west-1.dream.io s3://my-cool-icons/ /home/my_user/mydomain.com/wp-content/uploads/my-cool-icons/
    

    Using cron or some other scheduling tool, setting up a server to run that at regular intervals isn’t too terrible.

    AWS CLI

    There’s also the option of AWS CLI, which is very similar, only it’s actually maintained. The advantage with boto-rsync is that it’s installed on the servers at DreamHost. If you have AWS CLI on your server, you can do this:

    aws s3 --endpoint-url https://objects-us-west-1.dream.io sync s3://my-cool-icons/ /home/my_user/mydomain.com/wp-content/uploads/my-cool-icons/
    

    Keep in mind, you need to configure this either with aws configure or an ~/.aws/credentials files.

    Pushing from the outside inside

    You may have noticed that, unlike rsync, I can’t tell it to push to another server. That is, I can say “sync files from server A to server B” with rsync and I can’t with either boto-rsync or the AWS CLI.

    And no, you just can’t.

    Unless you use rclone. Which is a little messy but totally do-able.

    There’s one other option though… I’ve been using Codeship to push code. That is, every time my GitHub repos update, it triggers a Codeship deploy. And in that deploy I usually just say “Rsync a git clone, k’thnx’bai.”

    Now I tell it this too:

    # Download from S3
    pip install awscli
    aws s3 --endpoint-url https://objects-us-west-1.dream.io sync s3://my-cool-icons/ ~/my-cool-icons/
    # Copy up to Server
    rsync -avz -e "ssh" ~/lezpress-icons/ USER@DOMAIN:/home/USER/DOMAIN/wp-content/uploads/my-cool-icons/ --delete
    

    I will note there’s one extra trick. You have to add Environment Variables for AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY on Codeship.

    This works for me since there are few times I push an icon without also making some code changes to the server code anyway.

    Hopefully if gives you some directions.

  • CORS – Not Beer

    CORS – Not Beer

    I’ve been struggling with the idea of remote loading SVGs. There are a lot of problems with this, mostly due to the DOM structure of how your browser loads an SVG file.

    What’s an SVG and the DOM?

    Scalable Vector Graphics, or SVG, is a special XML file. It’s generally used like an image, like JPG or PNG, and contains vector graphics. This makes it smaller than PNGs and scalable without resolution loss. Pretty nifty for icons. In fact, better than my beloved Fonts.

    This is a part of the Document Object Model, or DOM. In the file, you tell it how you want the content to render. It’s why an SVG can be so powerful, having all the code you need inside a small file. It’s also why it’s so dangerous. Specifically if can contain Javascript code, which can be embedded and hidden in an image. And that can do a lot of damage. If you’re not paying attention, you can upload an SVG with javascript that runs code on the user’s browser. Bad times all around.

    Making this worse, if you use <img> tags to embed the SVG, many browsers will execute the code. Even today. Yeah… Not really a good thing.

    Embedding SVGs externally has a cost

    If you want to include an SVG file in your web page, you can use that dangerous <img> tag, but you can also use <svg> code right in the document. The problem there is that requires the SVG to essentially be pasted into the code. For PHP, you can use code like file_get_contents() (which doesn’t always work remotely) or if you’re WordPressy, wp_remote_get()

    The catch there? They’re slow if you list, oh, 69 images on a page. You can, of course, use <img> tags and that loads the SVG remotely, perfectly happily. If you’re sure about your SVGs, this is okay. You’re just going to lose one big, super big feature about SVGs.

    You can’t use CSS to edit them (color, resizing, etc) anymore.

    And that, my friends, is a big problem.

    Javascript End Run

    I wasn’t willing to compromise speed or flexibility. I’m particular like that. So I looked up how you can use javascript to hack the DOM. I quickly found SVGInjector, a fast, caching, dynamic inline SVG DOM injection library. Installing and configuring was easy buuuuut…

    You saw that but coming, right?

    Right.

    It didn’t work. Because of CORS.

    What the heck is CORS?

    Cross Origin Resource Sharing, CORS, is that magical thing that says “Hey you can’t run this resource remotely, yo!” Generally it’s related to Fonts (if you’ve ever tried to embed your own fonts from your own servers, you’d have run into this). I happened to hit it on SVGs, and basically the server where I host the icons wasn’t allowing my servers (local or otherwise) to load the icons.

    Thankfully! I use DreamObjects, and you can totally set up CORS on DreamObjects. You can do it on Amazon S3, and I think all the various CEPH-esque services let you.

    The catch here (hah you saw that coming, right?) is that not all clients let you do it. Lucky for me, I have s3cmd on my laptop (you should too if you’re going to work with S3 type services – it’ll make your life easier). Installing is easy, and you just have to make a .s3cfg file (DreamHost has some instructions).

    Once that’s set up, you can make your rules file like this:

    <CORSConfiguration>
    <CORSRule>
        <ID>Allow My Icons</ID>
        <AllowedOrigin>https://www.example.com</AllowedOrigin>
        <AllowedOrigin>http://www.example.com</AllowedOrigin>
        <AllowedOrigin>https://example.com</AllowedOrigin>
        <AllowedOrigin>http://example.com</AllowedOrigin>
        <AllowedMethod>GET</AllowedMethod>
        <AllowedMethod>HEAD</AllowedMethod>
        <AllowedHeader>Content-*</AllowedHeader>
        <AllowedHeader>Host</AllowedHeader>
        <ExposeHeader>ETag</ExposeHeader>
        <MaxAgeSeconds>86400</MaxAgeSeconds>
    </CORSRule>
    </CORSConfiguration>
    

    and then you can set (and check) your CORS:

    ipstenu@Uhura:~$ s3cmd setcors Development/example.com/cors-rules.xml s3://my-icons
    ipstenu@Uhura:~$ s3cmd info s3://my-icons
    s3://my-icons/ (bucket):
       Location:  us-west-1
       Payer:     BucketOwner
       Expiration Rule: none
       Policy:    none
       CORS:      <CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/"><CORSRule><ID>Allow My Icons</ID><AllowedMethod>GET</AllowedMethod><AllowedMethod>HEAD</AllowedMethod><AllowedOrigin>http://example.com</AllowedOrigin><AllowedOrigin>http://example.com </AllowedOrigin><AllowedOrigin>https://www.example.com </AllowedOrigin><AllowedOrigin>https://www.example.com </AllowedOrigin><AllowedHeader>Content-*</AllowedHeader><AllowedHeader>Host</AllowedHeader><MaxAgeSeconds>86400</MaxAgeSeconds><ExposeHeader>ETag</ExposeHeader></CORSRule></CORSConfiguration>
       ACL:       lezpress: FULL_CONTROL
    

    And now you can embed your SVGs remotely.

  • Reordering Facet Displays … For Death

    Reordering Facet Displays … For Death

    Isn’t that a catchy title?

    I’m using FacetWP to help me order and sort archives in ways that are reflective of the content. One of the things I sort are characters (yeah yeah yeah) and some of those characters are dead. It occurred to me that wouldn’t it be nifty if I could sort the characters by when they died?

    There was just one problem. Actually there were two. One was Sara Lance, and she’s my personal demon. The other was my own stupidity and lack of foresight. Neither were insurmountable.

    How FacetWP Changes Sort Order

    Before I get into the weeds, let’s have a moment to talk about sort order. FacetWP has a way to filter the sort orders.

    So for an example, I have post meta value for the number of characters saved as lezshows_char_count for all shows. If I wanted to sort shows by the most characters to least, I can add this in:

    $options['most_queers'] = array(
    	'label' => 'Number of Characters (Descending)',
    	'query_args' => array(
    		'orderby'  => 'meta_value_num', // sort by numerical custom field
    		'meta_key' => 'lezshows_char_count', // required when sorting by custom fields
    		'order'    => 'DESC', // descending order
    	)
    );
    

    It looks very similar to WP_Query and that’s what makes it easy. Except for my two problems…

    Problem One: Formats

    The first problem was not the Sara Lance problem. It was the ‘Mika didn’t think about things 4 years ago’ problem. I was saving the dates of death in the format of MM/DD/YYYY

    If you’re an American, you’re wondering “So what?” and if you’re anyone else, you’re giving me a death glare because “08/05/2010” could be August 05 OR May 08, and damn it, I knew better. For what it’s worth, the output on the front end is always “05 August 2010” but that’s not here nor there.

    You see, the issue isn’t that I was using stupid date/time formats, the issue is sorting.

    In the previous example, I have an order of meta_value_num which is literally a number. What’s the one for dates? You get meta_value_date or meta_value_datetime and neither of them work with the date format I’d chosen.

    So for this to work, I had to go and change everything from MM/DD/YYYY to YYYY/MM/DD – Not fun, but doable. And it led me to my Sara Lance Drama…

    Problem Two: Arrays

    How many times has Sara Lance died? Right now, three.

    When I decided to sort by the date of death, which one did I pick? Long pause.

    I decided to pick the last one. That is the most recent death. If someone’s actually still dead, the most recent death is the one that stuck. If they’re not, then death was pretty arbitrary to begin with and there ya go.

    The question became how and where did I save the death? I went with a post meta of lezchars_last_death and had it auto update on post save, like this:

    add_action( 'save_post_post_type_characters', 'characters_update_meta', 10, 3 );
    function characters_update_meta( $post_id ) {
    
    	// unhook this function so it doesn't loop infinitely
    	remove_action( 'save_post_post_type_characters', 'characters_update_meta' );
    		
    	// get the most recent death and save it as a new meta
    	$character_death = get_post_meta( $post_id, 'lezchars_death_year', true );
    	$newest_death    = 0000-00-00;
    	foreach ( $character_death as $death ) {
    		if ( $death > $newest_death ) $newest_death = $death;
    	}
    
    	if ( $newest_death !== 0000-00-00 ) {
    		update_post_meta( $post_id, 'lezchars_last_death', $newest_death );
    	}
    
    	// re-hook this function
    	add_action( 'save_post_post_type_characters', 'characters_update_meta' );
    }
    

    If there is a latest death, we get to set it as the YYYY-MM-DD value and off we go. But…

    Problem Three: Orderby Hellscape

    Surprise! I ran into a third problem! Remember how I was using the orderby of meta_value_num in my example? And I mentioned that I wanted to use meta_value_date or meta_value_datetime to sort by date?

    Yeah no.

    If I’d converted the date into unix time, sure. But I was reusing this logic in a couple places, and I didn’t want to re-save everything like that. I also use a query to grab all deaths in a year, and basically I do need to keep it with the format I have. That just messed up my sort until I found the magic of orderby' => 'meta_value',

    End Result?

    It works. It’s got yet another post meta, which I’m not super happy about, but sometimes that’s really just the simplest way to solve a problem. The data is always updating itself, and it’s relatively easy for me to tweak it. Also now I can do a lot more searches in different ways.

    Since I don’t have to worry about database size at the moment, nor speed, since I’ve designed it well, this works for me.

  • The Invisible Facets

    The Invisible Facets

    I’ve been using FacetWP for a year or so and it’s, hands down, the smartest WordPress plugin I’ve ever bought. Certainly I could have coded it, but not having to and being able to extend it to do what I need has saved me months of work and support.

    This is not to say it’s perfect. I’ve run into multiple quirks and headaches that resulted in me writing weird code to solve. And the solution to the null entry was no different. But it was, at the end, solvable.

    A Null Entry

    The majority of my data is saved in custom taxonomies. This makes it easy for me to grab and process. It’s also easy to list, because I can point people at /taxonomy/term and FacetWP magically populates properly in the sidebar.

    However. In one case, I have a checkbox. This is a simple post-meta to say if we love a show or not, and that check box, if it exists, is detected by FacetWP and I can easily get a list of all loved shows. The reverse is, sadly, not easy.

    That’s because if the checkbox is empty, there is nothing saved. No post meta. And if there’s no data, then when FacetWP builds out it’s list, there’s nothing saved for the non-existent data, and therefore no way to list nothing.

    An Imaginary Entry

    The other problem, related to this, is that I use Taxonomies in a different way. That is, while I use them like everyone else does with tags and categories, I also use them to track ‘stars’ – gold or silver etc. Obviously that makes it easy to track with /stars/gold/ buuuuuuut what if I wanted to list all the shows without any stars?

    How do I tell FacetWP ‘if there’s no taxonomy data saved for this, use a default?’

    A Fake Facet

    The answer lies within making a fake, that is unused, facet.

    The tl;dr to how FacetWP works is that it generates it’s own table with the data it collects from it’s facets. In general, there’s a 1-to-1 relationship with the facet and how it outputs. If you’re saving the terms of a taxonomy (like star colors), then there’s an entry in the database for the show and it’s star. If something has multiple values (like tags) then it has multiple entries.

    You can then alter the

    The Facet

    In order to make entries for my null or imaginary values, I made a facet that I didn’t use that I called all_the_missing and I gave it the data source of “Post Types”:

    An example of the facet

    The rest doesn’t matter. I’m not planing to display this, and I picked post types because it’s a quick bit to add the database without making it too heavy or complicated. Also I know it’ll exist for all my data.

    The Filter

    The magic to all this is my filter for facetwp_index_row:

    if ( 'all_the_missing' == $params['facet_name'] ) {
    	// If we do not love the show...
    	$loved = get_post_meta( $params['post_id'], 'shows_worthit_show_we_love', true);
    	if ( empty( $loved ) ) {
    		$params_loved = $params;
    		$params_loved['facet_name']           = 'show_loved';
    		$params_loved['facet_source']         = 'cf/shows_worthit_show_we_love';
    		$params_loved['facet_value']          = 'no';
    		$params_loved['facet_display_value']  = 'No';
    		$class->insert( $params_loved );
    	}
    	// If there are no stars
    	$stars = get_the_terms( $params['post_id'], 'the_stars' );
    	if ( empty( $stars ) ) {
    		$params_stars = $params;
    		$params_stars['facet_name']           = 'show_stars';
    		$params_stars['facet_source']         = 'tax/the_stars';
    		$params_stars['facet_value']          = 'none';
    		$params_stars['facet_display_value']  = 'None';
    		$class->insert( $params_stars );
    	}
    	return false; // skip default indexing
    }
    

    Now this is also wrapped in an if ( get_post_type( $params['post_id'] ) == 'post_type_shows' ) {...} check so this particular one only runs when shows are saved. But you can see what I do is when I run that specific facet, I check if the other data is available and if so, save it.

    Improvements

    I’d like to actually not have to save data when I don’t need it, but the need is enough that having this work was paramount. I can now sort when there’s no data, and that was what I needed.

  • Hey, Twitter, Why Do You Hate Us?

    Hey, Twitter, Why Do You Hate Us?

    Hi, Twitter.

    I know we fight a lot. You know I report a lot of abuse and harassment, and you do nothing about the Nazis, and we have our differences. But this isn’t about that. I mean, yeah, I’m salty about the Russian thing, but we need to talk about something else.

    We need to talk about using Twitter on a desktop when you have multiple accounts.

    Multiple Twitter Accounts Happen

    I have a legit reason to have multiple accounts. A good one, in fact. I have my personal account, but I have two others for brands I manage. And that means I kind of need to be able to log in to all three at once and wrangle things.

    If you use Twitter on the web, your choices are regular Twitter or Tweetdeck. The latter makes you sign up via a very convoluted process in order to grant access to accounts. Basically, you have to give your ‘main’ account access to the ones you want to manage. It’s not very obvious.

    And there are weird things missing from Tweetdeck. Like … no decent notifications. You can’t tell what you’ve read or when people @ you or anything like that. Not easily. Oh, and there’s no GIF button.

    Finally … with three accounts I get to have NINE columns. Three each for ‘home,’ ‘mentions,’ and ‘messages.’ Thanks. A lot.

    No Great Desktop App

    Here’s my problem. There’s no good Twitter desktop app. Your own app went unloved until you pulled the plug. In a tweet. Nice. Really nice. That leaves me with a few choices.

    TweetBot: I like Tweetbot, except that I can’t see polls in it, and I can’t navigate to embed Gifs. But it has a pretty decent interface. The biggest issue is that you can’t see group DMs. Sometimes keep on top breaks. Sometimes not.

    Twitterific: This is a wonderful app except that scrolling sucks. If you switch to a different account, keep on top stops working, and ⌘↑ (which should take you to the top of whatever you’re on) doesn’t scroll right. Oh and no embedding Gifs. And again, no group DMs and no polls.

    What about TweetDeck’s desktop app? It hasn’t been updated since 2015. The best version I’ve seen is Tweeten but again, I’m back to 3 columns per account.

    What I Want Is Simple

    I want the iOS app, but for the desktop. I want to have the following features:

    1. Multiple Account Support
    2. One visual ‘column’ per account (it can have sub tabs, whatever)
    3. The ability to insert and read polls
    4. Support for multi-person DMs
    5. Notifications
    6. A damn GIF button

    Instead, I get to use Tweetdeck in my browser. At least, until Twitter dumps that too.

  • Let’s Talk, Slack

    Let’s Talk, Slack

    Hi, Slack. You’re the cool product everyone uses to communicate on scale. You’ve introduced a lot of features and aspects that are great. We all like to use you for our non-company work, but I’ve noticed something interesting.

    See. You constantly remind us that Slack is for Business. But you don’t seem to have actually spent enough time in corporate land to understand what that means. So, as someone who worked for nearly 15 years (and recently at that) with The Man, and the last five with a smaller company, let me try to explain to you what mistakes you’re making. Oh, and before anyone asks, yes, I’ve pitched all of this in tickets/suggestions to Slack already.

    Constant Barrage

    Being able to tune alerts on Slack is basically the only way you have to live or die. I can mute channels or group-chats pretty easily, to allow a conversation I need to be aware of, but not right now to carry on around me.

    What I can’t do is mute my really, really, really chatty and annoying coworker for an hour so I can get work done.

    Oh sure, Slack, it’s passive aggressive to just mute Bob over there who knows I love the Cleveland Problematically Named Baseball Team, and wants to tell me something I will care about in an hour or so. But right now? I have a job. And I want to concentrate without your alerts popping up on my screen and showing that dreaded unread icon. And yes, Slack, I could mute everything, but what about my coworker Jane, the nice one who pings me with an apology because she knows I’m super busy, but she has a critical work problem, and I’m the expert.

    Come on, Slack.

    Asynchronicity vs Work/Life

    While everyone in startup land likes to brag about how they work 80 hours a week, the reality is that most business aren’t actually that stupid. We take vacations. We don’t work weekends. We like to spend time with family, go to a sports game, and not  be distracted by the ping of work.

    While you have do not disturb settings, Slack, I can only set them for specific hours. So yes, I do set them for 4pm to 7am, because I actually do have an end of day. But I can’t set my work days, I can’t connect Slack to (say) my Google Calendar and have it automatically detect that I’m out of the office. I have to constantly fiddle and tweak things. It’s a mess.

    Out of Office Messages

    Speaking of this, if I (perchance) happen to forget to mark myself as out of the office, I’m going to get alerts. Fine, that’s on me. But. You introduced custom status messages, which you tout I can use to announce I’m on vacation. Awesome! Now can you make them useful?

    See the problem is I put in “Out of the office until Feb 20” pretty recently, and I thought “My coworkers are intelligent, they’ll see this message and know ‘Aha! Mika is out!’ They don’t. And looking at this, I can’t blame them becuase of two things:

    1. Readability on MacOS is shit
    2. The message doesn’t fully show on iOS

    Don’t believe me? Here:

    Slack Example from iOS
    Slack example from MacOS

    Those are hard to read! And why don’t they auto-alert like a DND message does when someone DMs me? “Mika is currently [status message]” — Oh yes, Slack, I know people like to use those for jokes. Want to stop them? Make them auto-reply. Then people would only use them for real.

    And by the way…

    You’re Ageist

    Let me tell you a story.

    Once upon a time, not very long ago either, I supported desktop software. I received a phone call from someone in the Big Building, aka where the real bankers worked, and she couldn’t use a product because the screen was unreadable. She couldn’t see the buttons or dropdown. I asked her to give me 30 minutes and I would call her back. Quickly I went through a few steps to size and resize the window, and I couldn’t figure it out. I called her back and asked if I could come to her office.

    One 20 minute bus ride later, I’m at the fancy building, going through metal detectors, and I head up to her floor. I apologize for not being in a suit and ask her to please show me her desktop. One glance and I realized the problem was that her desktop itself had been resized. I explained I was going to change the resolution, resize it, and see if that fixed it. I promised I would reset everything.

    Nervous, she allowed this. After all, if I closed a specific window, I could cost the company a hefty bit of money. I very cautiously (without minimizing anything), changed the resolution.

    “Oh, that’s how it was this morning! My coworker was using my workstation.”

    After I head-desked a few times, I checked the app I was responsible for. It was set to take up most of the screen but not all. I resized it, manually, and then restored her preferred resolution. I then wrote down how I did that, how to fix it in the future, and went to give her coworker a stern word that began with “The first rule of using someone else’s workstation is THOU SHALT NOT MESS WITH THEIR SETTINGS.”

    A few years later, when I no longer worked on that team, I got a phone call from her again. “My new coworker is having the weird screen problem I had a million years ago. Can we pay you with lunch to fix it again?”

    Of course I said yes.

    Now re-read those problems I have with you, Slack. Because you’re worse.

    To Review

    I look at Slack, and I look at the problems I have, and I think “If I wasn’t technically competent, I would be lost.” And I realized “I am technically competent and I still get lost.”

    Slack. If you want to make it bigger, if you want big companies and banks to start using you instead of Lotus Notes Messenger, you need to step up your game. Provide business tools, the ones they need to make sure if they’re not available, someone knows who to contact next. Treat people like grown ups with mortgages, not 20-somethings who exist on packing peanuts and internships.

    Basically, Slack, you want the grown ups? Grow up.