The site uses cookies that you may not want. Continued use means acceptance. For more information see our privacy policy.

TT-RSS Grid CSS

A DIY style improvement for the feedreader I use.

I’m a big believer in RSS feeds. My vehicle for consuming them is TT-RSS on Debian. And while I liked the UI well enough, on some days I’d see the flood of feed items in the list and found myself tending toward reading aversion, where I didn’t want to look at each item and would mark a lot as seen even though I hadn’t actually paid much attention.

That’s good and bad. If I don’t have the bandwidth for it today, I doubt I’ll find the extra bandwidth to go back through older items tomorrow, so wiping the slate is for the best. On the other hand, over longer terms if I’m not consuming feeds, it’s better to stop receiving them (or filter them more heavily (trim the fat)). That’s a calculus and decision of its own, for another day.

But I also thought the interface might be part of the issue. A big list with each item squished between other items tends to be harder for me to scan for keywords, to pick out the parts I want. So I decided to try the new-fangled grid-related styles to make the interface look a little different, and I could see if it made a difference in my ability to consume my RSS feeds.

As you can see from the comparison image, this does trade off compactness for the layout, but I find it easier to scroll and have less visible at once. It’s probably slightly worse in terms of picking what to read next from starred items, because you can’t see them all at once, but if you’re planning to read them all anyway, perhaps the order isn’t as important.

Because I do all of my modifications in Stylus, it is easy for me to swap between my grid style and my older style. Anyway, here’s the code for the grid part. Note that you may need to change some settings for TT-RSS to use this, particularly the “Group by feed” option. (It may work without it, and certainly a modified version could, but I didn’t test any variety of configurations, only what I use.)

Ahem, the code:

/* The main headline area.*/
#headlines-frame {
    display: grid !important;
    /* Use 5 columns; could be tweaked depending on your screen. */
    grid-template-columns: repeat(5, 1fr) !important;
    grid-auto-rows: min-content !important;
    column-gap: 0px !important;
    align-items: start !important;
    justify-content: space-around !important;
    row-gap: 2rem !important;
    margin-block-start: 1rem !important;
}

/* If you include .feed-title in this selector, it will make the feed group act as a horizontal divider.
    You should also enable it for the next one, which makes sure it doesn't take up too much space. */
/*.feed-title,*/
.whiteBox,
#headlines-spacer {
    grid-column: 1 / -1 !important;
}

/*.feed-title,*/
#headlines-spacer {
    max-height: 2em !important;
}

div.feed-title {
    border-width: 1px 1px 1px 1px !important;
    margin-inline: 1rem !important;
}

/* You'd disable these if you enable .feed-title above. */
.feed-title {
    /* You could span more than one, in which case it will stick out in the grid.
        Disabling the grid-column rule would treat the .feed-title as a .hl (headline),
        placing it in the next available grid slot. */
    grid-column: 1 / span 1 !important;
    padding-inline-end: 1.4rem !important;
    display: grid !important;
    grid-template-columns: 1fr auto !important;
    grid-template-rows: auto 1fr !important;
    height: 100% !important;
}

/* Positions the feed's icon in the center of the grid cell. */
.feed-title div:first-child {
    display: grid !important;
    grid-area: 2 / 1 / -1 / -1 !important;
    align-self: center !important;
    justify-self: center !important;
    justify-content: space-around !important;
    align-content: space-around !important;
    vertical-align: middle !important;
    height: 150px !important;
}

.feed-title .icon {
    vertical-align: middle !important;
}

/* Place the title in the upper-left of the cell. */
.feed-title .title {
    grid-area: 1 / 1 / 1 / 2 !important;
}

/* Place the catchup button in the top-right of the cell. */
.feed-title .catchup {
    grid-area: 1 / 2 / 1 / -1 !important;
}

/* The headlines rule.
    It defines its own grid to position the contents.
    The default (light.css and night.css) themes only give it a bottom border.
*/
div.hl {
    grid-column: auto !important;
    display: grid !important;
    height: 100% !important;
    width: 90% !important;
    grid-template-columns: 1fr 1fr 2rem !important;
    grid-template-rows: 1fr auto !important;
    column-gap: 1em !important;
    place-items: start start !important;
    margin: 0 0.25em !important;
    padding: 0.25em !important;
    border-radius: 6px !important;
    border-width: 1px 1px 1px 1px !important;
}

/* Positioning for the item's headline, author, and tags, which are members of this element. */
div.hl > div.title {
    grid-column: 1 / -2 !important;
    /* Setting the height to 100% helps to maximize the clickable link area of the cell. */
    height: 100% !important;
}

/* The headline buttons are positioned in top-right (like the catchup for the feed title).  */
.hl > .left {
    display: grid !important;
    /* Make it a vertical-axis grid. Perhaps should include auto, in case the number of buttons changes (see .pub-pic below.) */
    grid-template-rows: repeat(2, 1fr);
    height: 4rem !important;
    /* First row, third column. */
    grid-row: 1 / 1 !important;
    grid-column: 3 / -1 !important;
    align-items: stretch !important;
    align-content: space-between !important;
}

/* Remove to show the feed button in grid tiles. I don't currently use it, so I hide it. */
.pub-pic {
    display: none !important;
}

/* I prefer the star to be the top item and the checkbox to be under it. */
.hl > .left > .marked-pic {
    order: -1 !important;
}

/*  */
span.hl-content {
    display: grid !important;
    grid-template-columns: 1fr !important;
    grid-template-rows: 1fr auto !important;
    row-gap: 1em !important;
}

/* Show the title which can be multiline.
    The width bit should be removed for more general use. */
span.hl-content > .title {
    display: inline-block !important;
    word-wrap: normal !important;
    white-space: normal !important;
    width: 10em !important;
    grid-row-start: 1 !important;
    grid-row-end: 1 !important;
}

/* Show the full author which can be multiline.
    This style rule goes a bit further than it should, mixing
    with some of my personal style stuff, so may need to be tweaked
    for more general use. (width, line height, particularly.) */
span.hl-content > span.author {
    display: inline-block !important;
    grid-row-start: 2 !important;
    grid-row-end: 2 !important;
    row-gap: 1em !important;
    width: 14em !important;
    line-height: 1em !important;
    word-wrap: normal !important;
    white-space: normal !important;
}

/* Place the score icon and feed icon in the bottom right. */
.hl > .right {
    grid-column: 3 / -1 !important;
    justify-self: end !important;
}

/* Hide these items if they don't contain anything. They are a content preview and the labels. */
span.preview:empty,
span[class^="HLLCTR-"]:empty {
    display: none !important;
}

It’s worth noting I didn’t spend a ton of time cleaning this up, either when I made it or when I decided to post about it, so it may have some rough edges. It didn’t take very long to go from idea to implementation, perhaps a couple of hours, which is a sign of how much easier and more useful CSS has become with things like the grid here. Now, there are things it’s still hard to do with the grid.

For example, without changes to markup, I couldn’t come up with an easy way to make the .feed-title elements fill the rest of the row when there aren’t enough items to do so. I’m pretty sure that could be done if there were separate divs for each feed’s contents, and perhaps it could be done if the grid had pseudo-classes (e.g., :first-row-member), but otherwise I’m not sure how you could do it. (I don’t know if it would have looked better anyway.)

So far I judge it a worthwhile change, as I have found it easier to scan the feeds even if I don’t see all the items at once. It’s more natural for my eyes to jump from item to item, and each one taking up more space gives the illusion that I’m making more progress by looking over each item.

Messing with TT-RSS in Python

Some sample code I wrote to feel my way around the TT-RSS API in Python.

I use the feed reader TT-RSS to read RSS/ATOM feeds. It’s a good reader, and I also use TTRSS-Reader for Android (there are others, which I’ve not tried yet) on my (ancient) Android mobile device to quickly go through incoming feed items. But I have some itches to the web interface that I am considering scratching, so I decided to see how hard it is to start building a simpler interface.

One of the things I knew from setting up the Android application is that TT-RSS has an API, which is where I started. It communicates via JSON, which is great because it is ubiquitous and easy to use.

You have to have a session to talk to the API, so I built a class to handle all requests and the session:

class TTRSSSession():
    def __init__(self, url, user, password):
        self.session_id = None
        self.url = url
        self.user = user
        self.password = password
        self._login()

    def headlines(self, feed):
        data = {
            "feed_id": feed,
            "limit": 10,
            #"skip": 11,
        }
        resp = self._request('getHeadlines', data)
        return resp

    def _login(self):
        login_data = {
            "user": self.user,
            "password": self.password,
        }
        result = self._request("login", login_data)
        self.session_id = result['session_id']

    def _request(self, op, data):
        request_data = {
            "op": op,
        }
        if not self.session_id and op != "login":
            raise OpException("Tried to execute without a session/login.")
            return None
        elif self.session_id and op != "login":
            request_data['sid'] = self.session_id

        request_data = dict_join((request_data, data))
        request_data_j = json.dumps(request_data).encode('utf-8')
        response = urllib.request.urlopen(self.url, data=request_data_j)
        r_data_j = response.read()
        encoding = chardet.detect(r_data_j)['encoding']
        r_data_j = r_data_j.decode(encoding, 'replace')
        r_data = json.loads(r_data_j)
        if r_data['status'] != 0:
            raise OpException(
                "Bad request: {0}".format(r_data['content']['error']))
            return None
        # FIXME handling multiple parts (seq = #)?
        return r_data['content']

That’s the main guts. This isn’t very advanced/functional yet, but it’s enough for me to mess with. I also built a very simple Flask application that uses this class so I can begin sketching some UI I might want (though for now it’s very bare bones, just showing the result of TTRSSSession.headlines()).

I was going to use Django, but I think it’s too heavy for what I want out of it. Flask is very light.

A sample invocation of the class:

feeds = {
    'FRESH': -3,
    'STARRED': -1,
    'ALL': -4,
}
url = 'http://example.com/tt-rss/api/'
user = 'random_hacker'
passw = 'xyzzy'
ttr = TTRSSSession(url, user, passw)
ttr.headlines(feeds['ALL'])

The headlines() method will return the first ten items in the feed containing all items (a special feed that throws everything read and unread together).

As you can see in the class’ _request() method, I haven’t added support for pulling multiple calls together. That will likely require a JavaScript frontend to integrate with my Flask application (or the data would all get pulled by the backend first, which seems wrong). And that begs the question of why not to forgo Python altogether and just write this as a pure client-side JavaScript thing?

Maybe I will. For now I am just messing around. Depending, I might prefer to use a Python backend to help filter or manage some of the items automatically. So at this point the versatility seems useful. Also, I always feel slightly silly when I find myself doing something that feels too close to rewriting part of an application that already (and still will) have that part, so who knows. I may just use this to learn more about Flask.

Tiny Tiny RSS and Feed Reading

Discusses the Tiny Tiny RSS (self-hosted) web application as a replacement for Google Reader (which will be end-of-life in July 2013).

Google Reader, a popular web application for organizing and reading RSS feeds (computer-friendly lists of posts from websites), will go away in July 2013. Scrambling for a replacement, many flock to monolithic, hosted substitutes.

I already had a perfect substitute up and running. A few months back I decided to add some feeds that did not fit my usual reading patterns on Google Reader. I looked around for an alternative and decided upon Tiny Tiny RSS (aka tt-rss), a free software implementation that manages and allows easy reading of feeds.

I only added the feeds that didn’t fit my Google Reader usage at the time. I would check tt-rss once a day, and continued checking Google Reader more regularly as always for the bulk of my reading.

Then the Google Reader announcement came, and while I looked at and tried a few of the alternatives, tt-rss fit my use best. Google Reader did not have feed filtering (to my knowledge) where tt-rss does. The ability to “clean up” some of the noise on some feeds means a more pleasant reading experience. For a mobile solution, the web interface has a mobile version (if Google Reader does, I never tried it; I did use their Android application, though), and an Android application can talk to tt-rss as well (requires enabling the API).

Google Reader served many people well for years, but it never went as far as it could have. With people forced out, a lot of projects like tt-rss will hopefully see increased relevance and improvement. But RSS itself never achieved the recognition it deserves.

RSS would serve us best if it were somewhat automatic. Google never took the obvious step of showing you feed items from recently-viewed sites, for example. Auto-management of feeds would have gone a long way toward improving the usage and importance of RSS. Manually adding and removing feeds, not being able to disable them, no filtering, and needing to understand feeds; these things made it a mess.

When you visit a site, they do everything they can to get you to stay. They try to pull you in with other materials, they beg you to subscribe. But as with just about every other industry, they fail to see their role in that process. They should work with browser builders to add next-generation discovery and subscription models.

For its part, tt-rss now has more feeds than Google Reader ever did. I’ve added more feeds knowing I can filter them easily, though I suspect the worst feeds will soon go away. While it isn’t perfect, it’s at least on par with Google Reader. And once I set it up (admittedly easy for someone at my level of knowledge), it’s no challenge to keep using.

The main thing I wish it had (and something I’m continuing to look into for several services I run for myself) is a single-sign-on solution. The last time I checked, my various services had differing support for different authentication technologies. Having just one that worked for them all would be delightful.