<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet href="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/rss-style.xsl" type="text/xsl"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	    xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	     xmlns:dc="http://purl.org/dc/elements/1.1/"
	   xmlns:atom="http://www.w3.org/2005/Atom"
	     xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	  xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>
<channel>
	<title>blogging &#8211; Terence Eden’s Blog</title>
	<atom:link href="https://shkspr.mobi/blog/tag/blogging/feed/" rel="self" type="application/rss+xml" />
	<link>https://shkspr.mobi/blog</link>
	<description>Regular nonsense about tech and its effects 🙃</description>
	<lastBuildDate>Sat, 09 May 2026 07:51:20 +0000</lastBuildDate>
	<language>en-GB</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>https://wordpress.org/?v=7.0</generator>

<image>
	<url>https://shkspr.mobi/blog/wp-content/uploads/2023/07/cropped-avatar-32x32.jpeg</url>
	<title>blogging &#8211; Terence Eden’s Blog</title>
	<link>https://shkspr.mobi/blog</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title><![CDATA[[RSS Club] A Sneak Preview of Upcoming Posts]]></title>
		<link>https://shkspr.mobi/blog/2026/05/rss-club-a-sneak-preview-of-upcoming-posts/</link>
					<comments>https://shkspr.mobi/blog/2026/05/rss-club-a-sneak-preview-of-upcoming-posts/#respond</comments>
				<dc:creator><![CDATA[@edent]]></dc:creator>
		<pubDate>Sun, 10 May 2026 11:34:38 +0000</pubDate>
				<category><![CDATA[[RSS Club]]]></category>
		<category><![CDATA[blogging]]></category>
		<category><![CDATA[RSS Club]]></category>
		<guid isPermaLink="false">https://shkspr.mobi/blog/?p=71376</guid>

					<description><![CDATA[Psssst! This top secret post is only available to RSS subscribers!  As a little thank-you for being a member of RSS Club I thought I&#039;d show you some trailers for upcoming blog posts.  I use the brilliant Editorial Calendar Plugin to organise all my scheduled blog posts. Here&#039;s what you can expect over the next month:    I tend to write in bursts - rather than once per day - and then spread the…]]></description>
										<content:encoded><![CDATA[<p><mark><em>Psssst!</em> This <strong>top secret</strong> post is only available to RSS subscribers!</mark></p>

<p>As a little thank-you for being a member of <a href="https://daverupert.com/rss-club/">RSS Club</a> I thought I'd show you some trailers for upcoming blog posts.</p>

<p>I use the brilliant <a href="https://editorialcalendarwp.com/">Editorial Calendar Plugin</a> to organise all my scheduled blog posts. Here's what you can expect over the next month:</p>

<img src="https://shkspr.mobi/blog/wp-content/uploads/2026/05/calendar.webp" alt="List of blog posts in a calendar layout. They are 
Put an AV test at the start of your slides. Stupidly Simple SVG Sparklines. Find blog posts with missing featured images - and missing alt text - without a plugin. This blog is written in en-GB. Game Review: Lovers In A Dangerous Spacetime. Death to px, long live ch!. Which age-gates should be skill-gates and vice-versa?. PHP - simple way to send HTTP headers before a script ends. Are Index Funds a Socialist Plot to Destroy America?. The UK Government's Low Value Purchase System is a Waste of Time. Whale Fall. Using FourSquare's API to post location checkins to social media. Virgin Media Hub 5 API. There's still no point in gigabit broadband." width="1609" height="835" class="aligncenter size-full wp-image-71378">

<p>I tend to write in bursts - rather than once per day - and then spread the posts out. As I'm going on a long break soon, I want to make sure there are plenty of posts in the queue.  There are also a bunch of posts scheduled over the next few years on specific dates.</p>

<p>Of course, if I unexpectedly die, I guess they be <em>post</em>humous…</p>

<p>I'm also working on what will be (I hope) a reasonably big political story. I'm under embargo until my media partner publishes it - but I hope it'll go live in the early hours of Tuesday. Stay tuned 😊</p>

<p>If there's something you'd like to see me write about, please <a href="https://edent.tel/">drop me a comment via your favourite method</a>.</p>
<img src="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/info/okgo.php?ID=71376&HTTP_REFERER=RSS" alt="" width="1" height="1" loading="eager">]]></content:encoded>
					
					<wfw:commentRss>https://shkspr.mobi/blog/2026/05/rss-club-a-sneak-preview-of-upcoming-posts/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title><![CDATA[RSS Feeds Send Me More Traffic Than Google]]></title>
		<link>https://shkspr.mobi/blog/2026/05/rss-feeds-send-me-more-traffic-than-google/</link>
					<comments>https://shkspr.mobi/blog/2026/05/rss-feeds-send-me-more-traffic-than-google/#comments</comments>
				<dc:creator><![CDATA[@edent]]></dc:creator>
		<pubDate>Tue, 05 May 2026 11:34:12 +0000</pubDate>
				<category><![CDATA[/etc/]]></category>
		<category><![CDATA[blog]]></category>
		<category><![CDATA[blogging]]></category>
		<category><![CDATA[meta]]></category>
		<category><![CDATA[statistics]]></category>
		<guid isPermaLink="false">https://shkspr.mobi/blog/?p=70238</guid>

					<description><![CDATA[Yeah yeah, I know, data-point of 1.  I recently read Susam&#039;s blog post where they said that &#34;most of the traffic to my personal website still comes from web feeds&#34; - I wondered if that was true for my site.  I&#039;ve been writing this blog for a while. I&#039;ve never much bothered with &#34;aggressive&#34; SEO - I have a fairly semantic layout, all my reviews have metadata, and stuff like that - but I&#039;m not…]]></description>
										<content:encoded><![CDATA[<p>Yeah yeah, I know, data-point of 1.</p>

<p>I recently read <a href="https://susam.net/from-rss-to-atom.html">Susam's blog post</a> where they said that "most of the traffic to my personal website still comes from web feeds" - I wondered if that was true for my site.</p>

<p>I've been writing this blog for a while. I've never much bothered with "aggressive" <abbr title="Search Engine Optimisation">SEO</abbr> - I have a fairly <a href="https://shkspr.mobi/blog/2026/01/maximally-semantic-structure-for-a-blog-post/">semantic layout</a>, all my <a href="https://shkspr.mobi/blog/2023/01/restaurant-review-metadata/">reviews have metadata</a>, and stuff like that - but I'm not cramming in keywords, using AMP, or whatever other chickens Google requires to be sacrificed for a higher ranking. Nevertheless, I do OK.</p>

<p>Last year, I added a bit of <a href="https://shkspr.mobi/blog/2025/09/reasonably-accurate-privacy-conscious-cookieless-visitor-tracking-for-wordpress/">local-only, lightweight statistics-gathering</a> to my blog. I can see which sites people click on to reach mine. Google is right up the top, DuckDuckGo is surprisingly high, Bing is lucky to crack the top 20 on any day. Similarly, I can see <a href="https://shkspr.mobi/blog/2025/11/now-witness-the-power-of-this-fully-operational-fediverse/">how much traffic I get from the Fediverse</a> and BlueSky (Twitter has all but vanished).</p>

<p>A few weeks ago I added RSS and Newsletter tracking. These data are <em>very</em> lossy. If someone is subscribed to my RSS feed <em>and</em> opens a post <em>and</em> their client downloads a lazy-loaded image at the end of the post, I get a hit. For email it's broadly the same. If an email is opened and the tracker image is loaded, I get a hit (although <a href="https://gmail.googleblog.com/2013/12/images-now-showing.html">Gmail does obfuscate that somewhat</a>).</p>

<p>I'm not looking for super-accurate numbers (although I do block as many AI crawlers and bots as possible). I'm not creepily following people around the web nor am I trying to sell them anything. I just want a rough idea of where people find me.</p>

<p>Here are my blog's views for the last 28 days.</p>

<img src="https://shkspr.mobi/blog/wp-content/uploads/2026/05/page-views.webp" alt="Atom 13774. Google 10833. RSS 10419. DuckDuckGo 2302. Email 2123." width="553" height="395" class="aligncenter size-full wp-image-71208 no-border-radius">

<p>Some months I get a surge of hits from link aggregators like HN or Reddit. Sometimes I'm linked to from a popular site or <a href="https://shkspr.mobi/blog/citations">cited in academic work</a>. But most of the time I bumble along getting hits from here, there, and everywhere. Nevertheless, it's lovely to see so many people choosing to subscribe<sup id="fnref:rss"><a href="https://shkspr.mobi/blog/2026/05/rss-feeds-send-me-more-traffic-than-google/#fn:rss" class="footnote-ref" title="For historic reasons, I have separate Atom and RSS feeds. Perhaps I should consider merging them? But it doesn't take much effort to publish in two subtly different formats." role="doc-noteref">0</a></sup> (for free!) and astonishing that they provide more traffic than a major search engine.</p>

<p>Obviously, these are two <em>very</em> different types of traffic. People who are searching for a specific thing and stumble upon my blog are different from those who decide to like and subscribe.</p>

<p>But, yeah, about 25% of my traffic comes from people who have chosen to subscribe.</p>

<p>I'm just delighted that so many people read my random thoughts.</p>

<div id="footnotes" role="doc-endnotes">
<hr aria-label="Footnotes">
<ol start="0">

<li id="fn:rss">
<p>For historic reasons, I have separate Atom and RSS feeds. Perhaps I should consider merging them? But it doesn't take much effort to publish in two subtly different formats.&nbsp;<a href="https://shkspr.mobi/blog/2026/05/rss-feeds-send-me-more-traffic-than-google/#fnref:rss" class="footnote-backref" role="doc-backlink">↩︎</a></p>
</li>

</ol>
</div>
<img src="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/info/okgo.php?ID=70238&HTTP_REFERER=RSS" alt="" width="1" height="1" loading="eager">]]></content:encoded>
					
					<wfw:commentRss>https://shkspr.mobi/blog/2026/05/rss-feeds-send-me-more-traffic-than-google/feed/</wfw:commentRss>
			<slash:comments>14</slash:comments>
		
		
			</item>
		<item>
		<title><![CDATA[Sneaky spam in conversational replies to blog posts]]></title>
		<link>https://shkspr.mobi/blog/2026/04/sneaky-spam-in-conversational-replies-to-blog-posts/</link>
					<comments>https://shkspr.mobi/blog/2026/04/sneaky-spam-in-conversational-replies-to-blog-posts/#comments</comments>
				<dc:creator><![CDATA[@edent]]></dc:creator>
		<pubDate>Thu, 23 Apr 2026 11:34:48 +0000</pubDate>
				<category><![CDATA[/etc/]]></category>
		<category><![CDATA[blog]]></category>
		<category><![CDATA[blogging]]></category>
		<category><![CDATA[spam]]></category>
		<category><![CDATA[WordPress]]></category>
		<guid isPermaLink="false">https://shkspr.mobi/blog/?p=70528</guid>

					<description><![CDATA[I&#039;m grateful that my blog posts attract lots of engaged, funny, and challenging comments. But any popular post also attracts spammers. I use Antispam Bee to automatically eradicate a couple of hundred crappy comments per day.    Nevertheless, some get through. Here&#039;s a particularly pernicious one - it appeared as three comments ostensibly in reply to each other.    At first glance these look like …]]></description>
										<content:encoded><![CDATA[<p>I'm grateful that my blog posts attract lots of engaged, funny, and challenging comments. But any popular post also attracts spammers. I use <a href="https://antispambee.pluginkollektiv.org/">Antispam Bee</a> to automatically eradicate a couple of hundred crappy comments <em>per day</em>.</p>

<img src="https://shkspr.mobi/blog/wp-content/uploads/2026/04/Antispam-Bee.webp" alt="Graph showing 272 comments blocked in a single day." width="762" height="292" class="aligncenter size-full wp-image-70529">

<p>Nevertheless, some get through. Here's a particularly pernicious one - it appeared as three comments ostensibly in reply to each other.</p>

<img src="https://shkspr.mobi/blog/wp-content/uploads/2026/04/spam-comments.webp" alt="First &quot;I read that article about why it’s so hard to passively track friends’ locations, and it actually makes sense. It talks about wanting automatic alerts when friends are nearby, but no app really does it well because of privacy and social awkwardness.&quot; Second &quot;Yeah, and even if the tech exists, people don’t always want to share their location 24/7. It’s like checking promos on spam domain promotions you might see potential, but there’s always uncertainty behind it. You’re kind of taking a chance on incomplete info.&quot; Third &quot;Exactly. Most location features are opt-in for a reason. Apps require consent because constantly tracking someone without them knowing would feel invasive, even if the intention is harmless.&quot;" width="2316" height="1598" class="aligncenter size-full wp-image-70530">

<p>At first glance these look like normal comments. They each address the content of the blog post albeit somewhat superficially. The first comment looks like it was from a social media post sharing my link - I get a lot of those as pingbacks, so it initially didn't trigger any suspicions from me.</p>

<p>The second is ostensibly a reply to the first and continues the conversation. Again, a bit shallow, but seems to be engaging in good faith.</p>

<p>The third looks like yet another reply. They all have unique email addresses, none of them have set their username to anything overly odd, and none of the users have filled out their URl.</p>

<p>But notice, in the second one, there's a link to a dodgy casino! There's no <code>https://</code> so it didn't jump out as a link.</p>

<p>All three came from the same IP address in the Philippines, so easy to block for now.</p>

<p>Each reply is spaced exactly 3 minutes apart which, in retrospect, looks a little odd.</p>

<p>Re-reading them carefully, they all look like AI slop. A plausible sounding summary, written in a casual style, but with very little semantic content. Seeing them as replies to each other primed me to think they were genuine because I'm used to spam coming in individual replies. Having the spam in the middle comment made it easy to glaze over.</p>

<p>Remember, there are no technological solutions to social problems. Sticking more and more barriers in the way of commenting only discourages genuine replies while the profit motive incentivises spammers to work around them.</p>
<img src="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/info/okgo.php?ID=70528&HTTP_REFERER=RSS" alt="" width="1" height="1" loading="eager">]]></content:encoded>
					
					<wfw:commentRss>https://shkspr.mobi/blog/2026/04/sneaky-spam-in-conversational-replies-to-blog-posts/feed/</wfw:commentRss>
			<slash:comments>11</slash:comments>
		
		
			</item>
		<item>
		<title><![CDATA[Maximally Semantic Structure for a Blog Post]]></title>
		<link>https://shkspr.mobi/blog/2026/01/maximally-semantic-structure-for-a-blog-post/</link>
					<comments>https://shkspr.mobi/blog/2026/01/maximally-semantic-structure-for-a-blog-post/#comments</comments>
				<dc:creator><![CDATA[@edent]]></dc:creator>
		<pubDate>Mon, 12 Jan 2026 12:34:53 +0000</pubDate>
				<category><![CDATA[/etc/]]></category>
		<category><![CDATA[blogging]]></category>
		<category><![CDATA[HTML]]></category>
		<category><![CDATA[schema.org]]></category>
		<category><![CDATA[semantic web]]></category>
		<guid isPermaLink="false">https://shkspr.mobi/blog/?p=63440</guid>

					<description><![CDATA[Yes, I know the cliché that bloggers are always blogging about blogging!  I like semantics. It tickles that part of my delicious meaty brain that longs for structure. Semantics are good for computers and humans. Computers can easily understand the structure of the data, humans can use tools like screen-readers to extract the data they&#039;re interested in.  In HTML, there are three main ways to …]]></description>
										<content:encoded><![CDATA[<p>Yes, I know the cliché that bloggers are always blogging about blogging!</p>

<p>I like semantics. It tickles that part of my delicious meaty brain that longs for structure. Semantics are good for computers and humans. Computers can easily understand the structure of the data, humans can use tools like screen-readers to extract the data they're interested in.</p>

<p>In HTML, there are three main ways to impose semantics - elements, attributes, and hierarchical microdata.</p>

<p>Elements are easy to understand. Rather than using a generic element like <code>&lt;div&gt;</code> you can use something like <code>&lt;nav&gt;</code> to show an element's contents are for navigation. Or <code>&lt;address&gt;</code> to show that the contents are an address. Or <code>&lt;article&gt;&lt;section&gt;</code> to show that the section is part of a parent article.</p>

<p>Attributes are also common.  You can use <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Attributes/rel">relational attributes</a> to show how a link relates to the page it is on. For example <code>&lt;a rel=author href=https://example.com&gt;</code> shows that the link is to the author of the current page. Or, to see that a link goes to the previous page in a series <code>&lt;a rel=prev href=/page5&gt;</code>.</p>

<p>Finally, we enter the complex and frightening world of <em>microdata</em>.</p>

<p>Using the <a href="https://schema.org/">Schema.org vocabulary</a> it's possible to add semantic metadata <em>within</em> an HTML element. For example, <code>&lt;body itemtype=https://schema.org/Blog itemscope&gt;</code> says that the body of this page is a Blog. Or, to say how many words a piece has, <code>&lt;span itemprop=wordCount content=1100&gt;1,100 words&lt;/span&gt;</code>.</p>

<p>There are <em>many</em> properties you can use. Here's the outline structure of a single blog post with a code sample, a footnote, and a comment. You can <a href="https://validator.schema.org/">check its structured data</a> and verify that it is <a href="https://validator.w3.org/">conformant HTML</a>.</p>

<p>Feel free to reuse.</p>

<pre><code class="language-html">&lt;!doctype html&gt;
&lt;html lang=en-gb&gt;
&lt;head&gt;&lt;title&gt;My Blog&lt;/title&gt;&lt;/head&gt;
&lt;body itemtype=https://schema.org/Blog itemscope&gt;

    &lt;header itemprop=headline&gt;
        &lt;a rel=home href=https://example.com&gt;My Blog&lt;/a&gt;
    &lt;/header&gt;

    &lt;main itemtype=https://schema.org/BlogPosting itemprop=blogPost itemscope&gt;
        &lt;article&gt;
            &lt;header&gt;
                &lt;time itemprop=https://schema.org/datePublished datetime=2025-12-01T12:34:39+01:00&gt;
                    1st January, 2025
                &lt;/time&gt;
                &lt;h1 itemprop=headline&gt;
                    &lt;a rel=bookmark href=https://example.com/page&gt;Post Title&lt;/a&gt;
                &lt;/h1&gt;
                &lt;span itemtype=https://schema.org/Person itemprop=author itemscope&gt;
                    &lt;a itemprop=url href=https://example.org/&gt;
                        By &lt;span itemprop=name&gt;Author Name&lt;/span&gt;
                    &lt;/a&gt;
                    &lt;img itemprop=image src=/photo.jpg alt&gt;
                &lt;/span&gt;
                &lt;p&gt;
                    &lt;a itemprop=keywords content=HTML rel=tag href=/tag/html/&gt;HTML&lt;/a&gt; 
                    &lt;a itemprop=keywords content=semantics rel=tag href=/tag/semantics/&gt;semantics&lt;/a&gt; 
                    &lt;a itemprop=commentCount content=6 href=#comments&gt;6 comments&lt;/a&gt;
                    &lt;span itemprop=wordCount content=1100&gt;1,100 words&lt;/span&gt;
                    &lt;span itemtype=https://schema.org/InteractionCounter itemprop=interactionStatistic itemscope&gt;
                        &lt;meta content=https://schema.org/ReadAction itemprop=interactionType&gt;
                        &lt;span itemprop=userInteractionCount content=5150&gt;
                            Viewed ~5,150 times
                        &lt;/span&gt;
                    &lt;/span&gt;
                &lt;/p&gt;
            &lt;/header&gt;

            &lt;div itemprop=articleBody&gt;
                &lt;img itemprop=image src=/hero.png alt&gt;
                &lt;p&gt;Text of the post.&lt;/p&gt;
                &lt;p&gt;Text with a footnote&lt;sup id=fnref&gt;&lt;a role=doc-noteref href=#fn&gt;0&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;

                &lt;pre itemtype=https://schema.org/SoftwareSourceCode itemscope translate=no&gt;
                    &lt;span itemprop=programmingLanguage&gt;PHP&lt;/span&gt;
                    &lt;code itemprop=text&gt;&amp;amp;lt;?php echo $postID ?&amp;amp;gt;&lt;/code&gt;
                &lt;/pre&gt;

                &lt;section role=doc-endnotes&gt;
                    &lt;h2&gt;Footnotes&lt;/h2&gt;
                    &lt;ol&gt;
                        &lt;li id=fn&gt;
                            &lt;p&gt;Footnote text. &lt;a role=doc-backlink href=#fnref&gt;↩︎&lt;/a&gt;&lt;/p&gt;
                        &lt;/li&gt;
                    &lt;/ol&gt;
                &lt;/section&gt;
            &lt;/div&gt;
        &lt;/article&gt;

        &lt;section id=comments&gt;
            &lt;h2&gt;Comments&lt;/h2&gt;
            &lt;article itemtype=https://schema.org/Comment itemscope id="comment-123465"&gt;
                &lt;time itemprop=dateCreated datetime=2025-09-11T13:24:54+01:00&gt;
                    &lt;a itemprop=url href=#comment-123465&gt;2025-09-11 13:24&lt;/a&gt;
                &lt;/time&gt;
                &lt;div itemtype=https://schema.org/Person itemprop=author itemscope&gt;
                    &lt;img itemprop=image src="/avatar.jpg" alt&gt;
                    &lt;h3&gt;
                        &lt;span itemprop=name&gt;Alice&lt;/span&gt; says:
                    &lt;/h3&gt;
                &lt;/div&gt;
                &lt;div itemprop=text&gt;
                    &lt;p&gt;Comment text&lt;/p&gt;
                &lt;/div&gt;
            &lt;/article&gt;
        &lt;/section&gt;
    &lt;/main&gt;
&lt;/body&gt;
&lt;/html&gt;
</code></pre>

<p>This blog post is entitled "maximally" but, of course, <a href="https://schema.org/BlogPosting">there is <em>lots</em> more that you can add</a> if you really want to.</p>

<p>Remember, none of this is <em>necessary</em>. Computers and humans are pretty good at extracting meaning from unstructured text. But making things easier for others is always time well spent.</p>
<img src="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/info/okgo.php?ID=63440&HTTP_REFERER=RSS" alt="" width="1" height="1" loading="eager">]]></content:encoded>
					
					<wfw:commentRss>https://shkspr.mobi/blog/2026/01/maximally-semantic-structure-for-a-blog-post/feed/</wfw:commentRss>
			<slash:comments>3</slash:comments>
		
		
			</item>
		<item>
		<title><![CDATA[A small collection of text-only websites]]></title>
		<link>https://shkspr.mobi/blog/2025/12/a-small-collection-of-text-only-websites/</link>
					<comments>https://shkspr.mobi/blog/2025/12/a-small-collection-of-text-only-websites/#comments</comments>
				<dc:creator><![CDATA[@edent]]></dc:creator>
		<pubDate>Tue, 30 Dec 2025 12:34:20 +0000</pubDate>
				<category><![CDATA[/etc/]]></category>
		<category><![CDATA[blogging]]></category>
		<category><![CDATA[blogs]]></category>
		<category><![CDATA[text]]></category>
		<category><![CDATA[unicode]]></category>
		<category><![CDATA[utf-8]]></category>
		<guid isPermaLink="false">https://shkspr.mobi/blog/?p=64224</guid>

					<description><![CDATA[A couple of years ago, I started serving my blog posts as plain text.  Add .txt to the end of any URl and get a deliciously lo-fi, UTF-8, mono[chrome&#124;space] alternative.  Here&#039;s this post in plain text - https://shkspr.mobi/blog/2025/12/a-small-collection-of-text-only-websites.txt  Obviously a webpage without links is like a fish without a bicycle, but the joy of the web is that there are no…]]></description>
										<content:encoded><![CDATA[<p>A couple of years ago, I started <a href="https://shkspr.mobi/blog/2024/05/link-relalternate-typetext-plain/">serving my blog posts as plain text</a>.  Add <code>.txt</code> to the end of any URl and get a deliciously lo-fi, UTF-8, mono[chrome|space] alternative.</p>

<p>Here's this post in plain text - <a href="https://shkspr.mobi/blog/2025/12/a-small-collection-of-text-only-websites.txt">https://shkspr.mobi/blog/2025/12/a-small-collection-of-text-only-websites.txt</a></p>

<p>Obviously a webpage without links is like a fish without a bicycle, but the joy of the web is that there are no gatekeepers. People can try new concepts and, if enough people join in, it becomes normal.  I'm not saying the plain-text is the <em>best</em> web experience. But it is <em>an</em> experience. Perfect if you like your browsing fast, simple, and readable. There are no cookie banners, pop-ups, permission prompts, autoplaying videos, or garish colour schemes.</p>

<p>I'm certainly not the first person to do this, so I thought it might be fun to gather a list of websites which you browse in text-only mode.  If you know of any more - including your own site - please drop a comment in the box!</p>

<ul>
<li><a href="https://shkspr.mobi/blog/2024/05/link-relalternate-typetext-plain/">Terence Eden's blog</a> - add <code>.txt</code> to any URl.</li>
<li><a href="https://daringfireball.net/2025/10/apple_uk_lawsuit_app_store_commissions.text">Daring Fireball</a> - add <code>.text</code> to any URl.</li>
<li><a href="https://flower.codes/2025/10/23/onion-mirror.txt">Zach Flowers</a> - replace <code>.html</code> with <code>.txt</code>.</li>
<li><a href="https://fabien.benetou.fr/Content/SwappingPartsOfTheRestrictionStack?action=source">Fabien Benetou's PIM</a> - add <code>?action=source</code> to any URl.</li>
<li><a href="https://m0yng.uk/2025/03/Tracking-the-benefits-of-Solar-and-Battery.txt">M0YNG</a> - add <code>.txt</code> to any URl.</li>
<li><a href="https://gwern.net/speedrunning.md">Gwern</a> - add <code>.md</code> to any URl or send an HTTP Accept for Markdown.</li>
<li><a href="https://textplain.blog/">Dan Q's textplain.blog</a> - the <em>entire</em> blog is plain text!</li>
<li><a href="https://nooshu.com/feed/feed.txt">Matt Hobbs</a> - there is a <em>feed</em> of plaintext which allows you to read recent posts.</li>
<li><a href="https://www.bananas-playground.net/projekt/portagefilelist/index.txt">Bananas Playground</a> - add <code>index.txt</code> to any post. Also works with <code>index.md</code>.</li>
<li><a href="https://www.jorsys.org/index.md">Jorvik Systems</a> - change <code>.html</code> to <code>.md</code> for Markdown.</li>
<li><a href="https://blog.omgmog.net/post/moving-to-github-actions-and-adding-txt-posts.txt">Max Glenister's blog</a> - add <code>.txt</code> to any post's URl.</li>
<li><a href="https://notes.philippdubach.com/0003.txt">Philipp Dubach's notes</a> - add <code>.txt</code> to any post's URl.</li>
<li><a href="https://derickrethans.nl/php-500.txt">Derick Rethans' blog</a> - add <code>.txt</code> to any post's URl.</li>
<li><a href="https://4c6e.xyz/TEXT-MANIFEST.txt">Ricardson's blog</a> - add <code>.txt</code> to any post's URl.</li>
<li><a href="https://elle.sh/blog">elle's blog</a> - text mode only available via <code>curl elle.sh/blog</code>.</li>
<li><a href="https://www.benji.dog/notes/1761683274.txt">Benji.dog</a> - add <code>.txt</code> to any note's URl.</li>
<li><a href="https://denisdefreyne.com/weeknotes/2026-w18.md">Denis Defreyne's weeknotes</a> - add <code>.md</code> to any weeknote's URl.</li>
</ul>

<p>If you'd like to add a site, please get in touch. The rules are simple - content which has the MIME type of <code>text/plain</code>. No HTML, no multimedia, no RTF, no XML, no ANSI colour escape sequences.</p>

<p>Emoji are fine though; emoji are cool.</p>
<img src="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/info/okgo.php?ID=64224&HTTP_REFERER=RSS" alt="" width="1" height="1" loading="eager">]]></content:encoded>
					
					<wfw:commentRss>https://shkspr.mobi/blog/2025/12/a-small-collection-of-text-only-websites/feed/</wfw:commentRss>
			<slash:comments>23</slash:comments>
		
		
			</item>
		<item>
		<title><![CDATA[Change the way dates are presented in WordPress's admin view]]></title>
		<link>https://shkspr.mobi/blog/2025/02/change-the-way-dates-are-presented-in-wordpresss-admin-view/</link>
					<comments>https://shkspr.mobi/blog/2025/02/change-the-way-dates-are-presented-in-wordpresss-admin-view/#comments</comments>
				<dc:creator><![CDATA[@edent]]></dc:creator>
		<pubDate>Wed, 26 Feb 2025 12:34:21 +0000</pubDate>
				<category><![CDATA[/etc/]]></category>
		<category><![CDATA[blogging]]></category>
		<category><![CDATA[HowTo]]></category>
		<category><![CDATA[php]]></category>
		<category><![CDATA[WordPress]]></category>
		<guid isPermaLink="false">https://shkspr.mobi/blog/?p=58427</guid>

					<description><![CDATA[WordPress does not respect an admin&#039;s preferred date format.  Here&#039;s how the admin list of posts looks to me:    I don&#039;t want it to look like that. I want it in RFC3339 format.  I know what you&#039;re thinking, just change the default date display - but that only seems to work in some areas of WordPress. It doesn&#039;t change the column-date format.  Here&#039;s what mine is set to:    So that doesn&#039;t work. …]]></description>
										<content:encoded><![CDATA[<p>WordPress does not respect an admin's preferred date format.</p>

<p>Here's how the admin list of posts looks to me:</p>

<img src="https://shkspr.mobi/blog/wp-content/uploads/2025/02/WP-Date-Wrong-fs8.png" alt="Column with the date format separated by slashes." width="420" height="674" class="aligncenter size-full wp-image-58437">

<p>I don't want it to look like that. I want it in RFC3339 format.</p>

<p>I know what you're thinking, <a href="https://wordpress.org/documentation/article/customize-date-and-time-format/">just change the default date display</a> - but that only seems to work in some areas of WordPress. It doesn't change the <code>column-date</code> format.  Here's what mine is set to:</p>

<img src="https://shkspr.mobi/blog/wp-content/uploads/2025/02/WP-date-format-fs8.png" alt="Settings screen showing date format set to dashes." width="940" height="414" class="aligncenter size-full wp-image-58432">

<p>So that doesn't work.</p>

<p>Instead, you need to use <a href="https://developer.wordpress.org/reference/hooks/post_date_column_time/">the slightly obscure <code>post_date_column_time</code> filter</a></p>

<p>Add this to your theme's <code>functions.php</code>:</p>

<pre><code class="language-php">//  Admin view - change date format
function rfc3339_post_date_time( $time, $post ) {
    //  Modify the default time format
    $rfc3339_time = date( "Y-m-d H:i", strtotime( $post-&gt;post_date ) );
    return $rfc3339_time;
}
add_filter( "post_date_column_time", "rfc3339_post_date_time", 10, 2 );
</code></pre>

<p>And, hey presto, your date column will look like this:
<img src="https://shkspr.mobi/blog/wp-content/uploads/2025/02/WP-Date-Rigth-fs8.png" alt="Column with the date format separated by dashes." width="420" height="670" class="aligncenter size-full wp-image-58438"></p>

<p>Obviously, you can change that code to whichever date format you prefer.</p>
<img src="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/info/okgo.php?ID=58427&HTTP_REFERER=RSS" alt="" width="1" height="1" loading="eager">]]></content:encoded>
					
					<wfw:commentRss>https://shkspr.mobi/blog/2025/02/change-the-way-dates-are-presented-in-wordpresss-admin-view/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
			</item>
		<item>
		<title><![CDATA[Graphing the connections between my blog posts]]></title>
		<link>https://shkspr.mobi/blog/2025/01/graphing-the-connections-between-my-blog-posts/</link>
					<comments>https://shkspr.mobi/blog/2025/01/graphing-the-connections-between-my-blog-posts/#comments</comments>
				<dc:creator><![CDATA[@edent]]></dc:creator>
		<pubDate>Thu, 09 Jan 2025 12:34:56 +0000</pubDate>
				<category><![CDATA[/etc/]]></category>
		<category><![CDATA[blogging]]></category>
		<category><![CDATA[graphs]]></category>
		<category><![CDATA[HTML]]></category>
		<category><![CDATA[php]]></category>
		<category><![CDATA[WordPress]]></category>
		<guid isPermaLink="false">https://shkspr.mobi/blog/?p=55159</guid>

					<description><![CDATA[I love ripping off good ideas from other people&#039;s blogs.  I was reading Alvaro Graves-Fuenzalida&#039;s blog when I saw this nifty little force-directed graph:    When zoomed in, it shows the relation between posts and tags.    In this case, I can see that the posts about Small Gods and Pyramids both share the tags of Discworld, Fantasy, and Book Review. But only Small Gods has the tag of Religion. …]]></description>
										<content:encoded><![CDATA[<p>I love ripping off good ideas from other people's blogs.  I was reading <a href="https://stuff.graves.cl/posts/2024-03-05_20_41-book-review---small-gods-by-terry-pratchett.html">Alvaro Graves-Fuenzalida's blog</a> when I saw this nifty little force-directed graph:</p>

<img src="https://shkspr.mobi/blog/wp-content/uploads/2024/12/Graph-fs8.png" alt="A graph of interconnected nodes." width="800" height="600" class="aligncenter size-full wp-image-55160">

<p>When zoomed in, it shows the relation between posts and tags.</p>

<img src="https://shkspr.mobi/blog/wp-content/uploads/2024/12/Graph-detail-fs8.png" alt="Text labels on the nodes show that the two of the posts share a common tag." width="1600" height="1200" class="aligncenter size-full wp-image-55161">

<p>In this case, I can see that the posts about Small Gods and Pyramids both share the tags of Discworld, Fantasy, and Book Review. But only Small Gods has the tag of Religion.</p>

<p>Isn't that cool! It is a native feature of <a href="https://quartz.jzhao.xyz/features/graph-view">Quartz's GraphView</a>. How can I build something like that for my WordPress blog?</p>

<h2 id="aim"><a href="https://shkspr.mobi/blog/2025/01/graphing-the-connections-between-my-blog-posts/#aim">Aim</a></h2>

<p>Create an interactive graph which shows the relationship between a post, its links, and their tags.</p>

<p>It will end up looking something like this:</p>

<img src="https://shkspr.mobi/blog/wp-content/uploads/2024/12/fdg.png" alt="A force directed graph showing how four different posts link to each other and how their hashtags relate." width="1188" height="1088" class="aligncenter size-full wp-image-55169">

<p>You can <a href="https://gitlab.com/edent/blog-theme/-/blob/master/includes/graph.php">get the code</a> or follow along to see how it works.</p>

<p>This is a multi-stage process. Let's begin!</p>

<h2 id="what-we-need"><a href="https://shkspr.mobi/blog/2025/01/graphing-the-connections-between-my-blog-posts/#what-we-need">What We Need</a></h2>

<p>When on a single Post, we need the following:</p>

<ul>
<li>The tags assigned to that Post.</li>
<li>Internal links back to that Post.</li>
<li>Internal links from that Post.</li>
<li>The tags assigned to links to and from that Post.</li>
</ul>

<h2 id="tags-assigned-to-that-post"><a href="https://shkspr.mobi/blog/2025/01/graphing-the-connections-between-my-blog-posts/#tags-assigned-to-that-post">Tags assigned to that Post.</a></h2>

<p>This is pretty easy!  Using the <a href="https://developer.wordpress.org/reference/functions/get_the_tag_list/"><code>get_the_tag_list()</code> function</a> we can, unsurprisingly, get all the tags associated with a post.</p>

<pre><code class="language-php">$post_tags_text = get_the_tag_list( "", ",", $ID );
$post_tags_array = explode( "," , $post_tags_text );
</code></pre>

<p>That just gets the list of tag names. If we want the tag IDs as well, we need to use <a href="https://developer.wordpress.org/reference/functions/get_the_tags/">the <code>get_the_tags()</code> function</a>.</p>

<pre><code class="language-php">$post_tags = get_the_tags($ID);
$tags = array();
foreach($post_tags as $tag) {
    $tags[$tag-&gt;term_id] = $tag-&gt;name; 
}
</code></pre>

<h2 id="backlinks"><a href="https://shkspr.mobi/blog/2025/01/graphing-the-connections-between-my-blog-posts/#backlinks">Backlinks</a></h2>

<p>Internal links back to the Post is slightly trickier. WordPress doesn't save relational information like that. Instead, we get the Post's URl and <a href="https://shkspr.mobi/blog/2023/10/displaying-internal-linkbacks-on-wordpress/">search for that in the database</a>. Then we get the post IDs of all the posts which contain that string.</p>

<pre><code class="language-php">//  Get all the posts which link to this one, oldest first
$the_query = new WP_Query(
    array(
        's' =&gt; $search_url,
        'post_type' =&gt; 'post',
        "posts_per_page" =&gt; "-1",
        "order" =&gt; "ASC"
    )
);

//  Nothing to do if there are no inbound links
if ( !$the_query-&gt;have_posts() ) {
    return;
}
</code></pre>

<h2 id="backlinks-tags"><a href="https://shkspr.mobi/blog/2025/01/graphing-the-connections-between-my-blog-posts/#backlinks-tags">Backlinks' Tags</a></h2>

<p>Once we have an array of posts which link back here, we can get their tags as above:</p>

<pre><code class="language-php">//  Loop through the posts
while ( $the_query-&gt;have_posts() ) {
    //  Set it up
    $the_query-&gt;the_post();
    $id                  = get_the_ID();
    $title               = esc_html( get_the_title() );
    $url                 = get_the_permalink();
    $backlink_tags_text  = get_the_tag_list( "", ",", $ID );
    $backlink_tags_array = explode( "," , $backlink_tags_text );
}
</code></pre>

<h2 id="links-from-the-post"><a href="https://shkspr.mobi/blog/2025/01/graphing-the-connections-between-my-blog-posts/#links-from-the-post">Links from the Post</a></h2>

<p>Again, WordPress's lack of relational links is a weakness. In order to get internal links, we need to:</p>

<ol>
<li>Render the HTML using all the filters</li>
<li>Search for all <code>&lt;a href="…"&gt;</code></li>
<li>Extract the ones which start with the blog's domain</li>
<li>Get those posts' IDs.</li>
</ol>

<p>Rendering the content into HTML is done with:</p>

<pre><code class="language-php">$content = apply_filters( "the_content", get_the_content( null, false, $ID ) );
</code></pre>

<p>Searching for links is slightly more complex. The easiest way is to load the HTML into a DOMDocument, then extract all the anchors. All my blog posts start <code>/blog/YYYY</code> so I can avoid selecting links to tags, uploaded files, or other things. Your blog may be different.</p>

<pre><code class="language-php">$dom = new DOMDocument();
libxml_use_internal_errors( true ); //  Suppress warnings from malformed HTML
$dom-&gt;loadHTML( $content );
libxml_clear_errors();

$links = [];
foreach ( $dom-&gt;getElementsByTagName( "a" ) as $anchor ) {
    $href = $anchor-&gt;getAttribute( "href" );
    if (preg_match('/^https:\/\/shkspr\.mobi\/blog\/\d{4}$/', $href)) {
        $links[] = $href;
    }
}
</code></pre>

<p>The ID of each post can be found with <a href="https://developer.wordpress.org/reference/functions/url_to_postid/">the <code>url_to_postid()</code> function</a>. That means we can re-use the earlier code to see what tags those posts have.</p>

<h2 id="building-a-graph"><a href="https://shkspr.mobi/blog/2025/01/graphing-the-connections-between-my-blog-posts/#building-a-graph">Building a graph</a></h2>

<p>OK, so we have all our constituent parts. Let's build a graph!</p>

<p>Graphs consist of nodes (posts and tags) and edges (links between them). The exact format of the graph is going to depend on the graph library we use.</p>

<p>I've decided to use <a href="https://github.com/d3/d3-force?tab=readme-ov-file">D3.js's Force Graph</a> as it is relatively simple and produces a reasonably good looking interactive SVG.</p>

<p>Imagine there are two blog posts and two hashtags.</p>

<pre><code class="language-js">const nodes = [
    { id: 1, label: "Blog Post 1",    url: "https://example.com/post/1", group: "post" },
    { id: 2, label: "Blog Post 2",    url: "https://example.com/post/2", group: "post" },
    { id: 3, label: "hashtag",        url: "https://example.com/tag/3",  group: "tag"  },
    { id: 4, label: "anotherHashtag", url: "https://example.com/tag/4",  group: "tag"  },
];
</code></pre>

<ul>
<li>Blog Post 1 links to Blog Post 2.</li>
<li>Blog Post 1 has a #hashtag.</li>
<li>Both 1 &amp; 2 share #anotherHashtag.</li>
</ul>

<pre><code class="language-js">const links = [
    { source: 1, target: 2 },
    { source: 3, target: 1 },
    { source: 4, target: 1 },
    { source: 4, target: 2 },
];
</code></pre>

<p>Here's how to create a list of nodes and their links.  You will need to edit it for your own blog's peculiarities.</p>

<pre><code class="language-php">&lt;?php 
// Load WordPress environment
require_once( "wp-load.php" );

//  Set up arrays for nodes and links
$nodes = array();
$links = array();

//  ID of the Post
$main_post_id = 12345;

//  Get the Post's details
$main_post_url   = get_permalink( $main_post_id );
$main_post_title = get_the_title( $main_post_id );

//  Function to add new nodes
function add_item_to_nodes( &amp;$nodes, $id, $label, $url, $group ) {
    $nodes[] = [ 
        "id"    =&gt; $id, 
        "label" =&gt; $label, 
        "url"   =&gt; $url, 
        "group" =&gt; $group
    ];

}

//  Function to add new relationships
function add_relationship( &amp;$links, $source, $target ) {
    $links[] = [
        "source" =&gt; $source,
        "target" =&gt; $target
    ];
}

//  Add Post to the nodes
add_item_to_nodes( $nodes, $main_post_id, $main_post_title, $main_post_url, "post" );

//  Get the tags of the Post
$main_post_tags = get_the_tags( $main_post_id );

//  Add the tags as nodes, and create links to main Post
foreach( $main_post_tags as $tag ) {
    $id   = $tag-&gt;term_id;
    $name = $tag-&gt;name;

    //  Add the node
    add_item_to_nodes( $nodes, $id, $name, "https://shkspr.mobi/blog/tag/" . $name, "tag" );
    //  Add the relationship
    add_relationship( $links, $id, $main_post_id );
}

//  Get all the posts which link to this one, oldest first
$the_query = new WP_Query(
    array(
        's'              =&gt; $main_post_url,
        'post_type'      =&gt; 'post',
        "posts_per_page" =&gt; "-1",
        "order"          =&gt; "ASC"
    )
);

//  Nothing to do if there are no inbound links
if ( $the_query-&gt;have_posts() ) {
    //  Loop through the posts
    while ( $the_query-&gt;have_posts() ) {
        //  Set up the query
        $the_query-&gt;the_post();
        $post_id = get_the_ID();
        $title = esc_html( get_the_title() );
        $url   = get_the_permalink();

        //  Add the node
        add_item_to_nodes( $nodes, $post_id, $title, $url, "post" );
        //  Add the relationship
        add_relationship( $links, $post_id, $main_post_id );

        //  Get the tags of the Post
        $post_tags = get_the_tags( $post_id );

        //  Add the tags as nodes, and create links to main Post
        foreach($post_tags as $tag) {

            $id   = $tag-&gt;term_id;
            $name = $tag-&gt;name;

            //  Add the node
            add_item_to_nodes( $nodes, $id, $name, "https://shkspr.mobi/blog/tag/" . $name, "tag" );
            //  Add the relationship
            add_relationship( $links, $id, $post_id );
        }

    }
}

//  Get all the internal links from this post
//  Render the post as HTML
$content = apply_filters( "the_content", get_the_content( null, false, $ID ) );

//  Load it into HTML
$dom = new DOMDocument();
libxml_use_internal_errors( true );
$dom-&gt;loadHTML( $content );
libxml_clear_errors();

//  Get any &lt;a href="…" which starts with https://shkspr.mobi/blog/
$internal_links = [];
foreach ( $dom-&gt;getElementsByTagName( "a" ) as $anchor ) {
    $href = $anchor-&gt;getAttribute( "href" );
    if (preg_match('/^https:\/\/shkspr\.mobi\/blog\/\d{4}$/', $href)) {
        $internal_links[] = $href;
    }
}

//  Loop through the internal links, get their hashtags
foreach ( $internal_links as $url ) {
    $post_id = url_to_postid( $url );
    //  Get the Post's details
    $post_title = get_the_title( $id );

    //  Add the node
    add_item_to_nodes( $nodes, $post_id, $post_title, $url, "post" );
    //  Add the relationship
    add_relationship($links, $main_post_id, $post_id );

    //  Get the tags of the Post
    $post_tags = get_the_tags( $post_id );

    //  Add the tags as nodes, and create links to main Post
    foreach( $post_tags as $tag ) {
        $id   = $tag-&gt;term_id;
        $name = $tag-&gt;name;

        //  Add the node
        add_item_to_nodes( $nodes, $id, $name, "https://shkspr.mobi/blog/tag/" . $name, "tag" );
        //  Add the relationship
        add_relationship( $links, $id, $post_id );
    }
}

//  Deduplicate the nodes and links
$nodes_unique = array_unique( $nodes, SORT_REGULAR );
$links_unique = array_unique( $links, SORT_REGULAR );

//  Put them in the keyless format that D3 expects
$nodes_output = array();
$links_output = array();

foreach ( $nodes_unique as $node ) {
    $nodes_output[] = $node;
}

foreach ( $links_unique as $link ) {
    $links_output[] = $link;
}

//  Return the JSON
echo json_encode( $nodes_output, JSON_PRETTY_PRINT );
echo "\n";
echo json_encode( $links_output, JSON_PRETTY_PRINT );
</code></pre>

<h2 id="creating-a-force-directed-svg"><a href="https://shkspr.mobi/blog/2025/01/graphing-the-connections-between-my-blog-posts/#creating-a-force-directed-svg">Creating a Force Directed SVG</a></h2>

<p>Once the data are spat out, you can include them in a web-page. Here's a basic example:</p>

<pre><code class="language-html">&lt;!DOCTYPE html&gt;
&lt;html lang="en"&gt;
    &lt;head&gt;
        &lt;meta charset="UTF-8"&gt;
        &lt;meta name="viewport" content="width=device-width, initial-scale=1.0"&gt;
        &lt;title&gt;Force Directed Graph&lt;/title&gt;
        &lt;script src="https://d3js.org/d3.v7.min.js"&gt;&lt;/script&gt;
    &lt;/head&gt;
    &lt;body&gt;
        &lt;svg width="800" height="600"&gt;
            &lt;defs&gt;
                &lt;marker id="arrowhead" markerWidth="10" markerHeight="7" refX="10" refY="3.5" orient="auto" fill="#999"&gt;
                &lt;path d="M0,0 L10,3.5 L0,7 Z"&gt;&lt;/path&gt;
                &lt;/marker&gt;
            &lt;/defs&gt;
        &lt;/svg&gt;
        &lt;script&gt;
</code></pre>

<pre><code class="language-js">            const nodes = [];
            const links = [];

            const width  = 800;
            const height = 600;

            const svg = d3.select("svg")
                .attr( "width",  width  )
                .attr( "height", height );

            const simulation = d3.forceSimulation( nodes )
                .force( "link",   d3.forceLink( links ).id( d =&gt; d.id ).distance( 100 ) )
                .force( "charge", d3.forceManyBody().strength( -300 ) )
                .force( "center", d3.forceCenter( width / 2, height / 2 ) );

            //  Run simulation with simple animation
            simulation.on("tick", () =&gt; {
                link
                    .attr("x1", d =&gt; d.source.x)
                    .attr("y1", d =&gt; d.source.y)
                    .attr("x2", d =&gt; d.target.x)
                    .attr("y2", d =&gt; d.target.y);   node
                    .attr("transform", d =&gt; `translate(${d.x},${d.y})`);
            });

            // Draw links
            const link = svg.selectAll( ".link" )
                .data(links)
                .enter().append("line")
                .attr( "stroke", "#999" )
                .attr( "stroke-width", 2 )
                .attr( "x1", d =&gt; d.source.x )
                .attr( "y1", d =&gt; d.source.y )
                .attr( "x2", d =&gt; d.target.x )
                .attr( "y2", d =&gt; d.target.y )
                .attr( "marker-end", "url(#arrowhead)" );

            //  Draw nodes
            const node = svg.selectAll( ".node" )
                .data( nodes )
                .enter().append( "g" )
                .attr( "class", "node" )
                .attr( "transform", d =&gt; `translate(${d.x},${d.y})` )
                .call(d3.drag() //  Make nodes draggable
                    .on( "start", dragStarted )
                    .on( "drag",  dragged )
                    .on( "end",   dragEnded ) 
                );

            //  Add hyperlink
            node.append("a")
            .attr( "xlink:href", d =&gt; d.url ) //    Link to the node's URL
            .attr( "target", "_blank" ) //  Open in a new tab
            .each(function (d) {
                const a = d3.select(this);
                //  Different shapes for posts and tags
                if ( d.group === "post" ) {
                    a.append("circle")
                        .attr("r", 10)
                        .attr("fill", "blue");
                } else if ( d.group === "tag" ) {
                    //  White background rectangle
                    a.append("rect")
                            .attr("width", 20)
                            .attr("height", 20)
                            .attr("x", -10)
                            .attr("y", -10)
                            .attr("fill", "white"); 
                    // Red octothorpe
                    a.append("path")
                            .attr("d", "M-10,-5 H10 M-10,5 H10 M-5,-10 V10 M5,-10 V10") 
                            .attr("stroke", "red")
                            .attr("stroke-width", 2)
                            .attr("fill", "none");
                }
                //  Text label
                a.append( "text")
                    .attr( "dy", 4 )
                    .attr( "x", d =&gt; ( d.group === "post" ? 12 : 14 ) )
                    .attr( "fill", "black" )
                    .style("font-size", "12px" )
                    .text( d.label );
            });

            //  Standard helper functions to make nodes draggable
            function dragStarted( event, d ) {
                if ( !event.active ) simulation.alphaTarget(0.3).restart();
                d.fx = d.x;
                d.fy = d.y;
            }
            function dragged( event, d ) {
                d.fx = event.x;
                d.fy = event.y;
            }
            function dragEnded( event, d ) {
                if (!event.active) simulation.alphaTarget(0);
                d.fx = null;
                d.fy = null;
            }
</code></pre>

<pre><code class="language-html">        &lt;/script&gt;
    &lt;/body&gt;
&lt;/html&gt;
</code></pre>

<h2 id="next-steps"><a href="https://shkspr.mobi/blog/2025/01/graphing-the-connections-between-my-blog-posts/#next-steps">Next Steps</a></h2>

<p>It needs a bit of cleaning up if I want to turn it into a WordPress plugin. It might be nice to make it a static SVG rather than relying on JavaScript. And the general æsthetic needs a bit of work.</p>

<p>Perhaps I could make it 3D like my <a href="https://shkspr.mobi/blog/2023/04/msc-dissertation-exploring-the-visualisation-of-hierarchical-cybersecurity-data-within-the-metaverse/">MSc Dissertation</a>?</p>

<p>But I'm pretty happy with that for an afternoon hack!</p>

<p>You can <a href="https://gitlab.com/edent/blog-theme/-/blob/master/includes/graph.php">get the code</a> if you want to play.</p>
<img src="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/info/okgo.php?ID=55159&HTTP_REFERER=RSS" alt="" width="1" height="1" loading="eager">]]></content:encoded>
					
					<wfw:commentRss>https://shkspr.mobi/blog/2025/01/graphing-the-connections-between-my-blog-posts/feed/</wfw:commentRss>
			<slash:comments>3</slash:comments>
		
		
			</item>
		<item>
		<title><![CDATA[Order WordPress Posts by Most Comments]]></title>
		<link>https://shkspr.mobi/blog/2024/12/order-wordpress-posts-by-most-comments/</link>
					<comments>https://shkspr.mobi/blog/2024/12/order-wordpress-posts-by-most-comments/#respond</comments>
				<dc:creator><![CDATA[@edent]]></dc:creator>
		<pubDate>Thu, 12 Dec 2024 12:34:42 +0000</pubDate>
				<category><![CDATA[/etc/]]></category>
		<category><![CDATA[blogging]]></category>
		<category><![CDATA[php]]></category>
		<category><![CDATA[WordPress]]></category>
		<guid isPermaLink="false">https://shkspr.mobi/blog/?p=54404</guid>

					<description><![CDATA[I take great delight in seeing people reply to my blog posts.  I use WebMentions to collect replies from social media and other sites. But which of my posts has the most comments? Here&#039;s a snipped to stick in your functions.php file. It allows you to add ?comment-order to any WordPress URl and have the posts with the most comments on top.  //  Add ordering by comments add_action( &#039;pre_get_posts&#039;, …]]></description>
										<content:encoded><![CDATA[<p>I take great delight in seeing people reply to my blog posts.  I use WebMentions to collect replies from social media and other sites. But which of my posts has the most comments? Here's a snipped to stick in your <code>functions.php</code> file. It allows you to add <code>?comment-order</code> to any WordPress URl and have the posts with the most comments on top.</p>

<pre><code class="language-php">//  Add ordering by comments
add_action( 'pre_get_posts', 'pre_get_posts_by_comments' );
function pre_get_posts_by_comments( $query ) {
    //  Do nothing if the post_status parameter in the URL is not "comment-order"
    if ( ! isset( $_GET['comment-order'] ) ) {
        return;
    }

    $query-&gt;set( "orderby", "comment_count" );  //  Default: date
    $query-&gt;set( "order", "DESC" ); //  Biggest first
}
</code></pre>

<p>This makes use of <a href="https://developer.wordpress.org/reference/hooks/pre_get_posts/">the <code>pre_get_posts</code> hook</a> to rewrite the posts query. That means it works on most WordPress pages.</p>

<p>For example:</p>

<ul>
<li>My homepage <a href="https://shkspr.mobi/blog/?comment-order">https://shkspr.mobi/blog/?comment-order</a></li>
<li>Posts with a specific tag <a href="https://shkspr.mobi/blog/tag/blockchain/?comment-order">https://shkspr.mobi/blog/tag/blockchain/?comment-order</a></li>
<li>Dates <a href="https://shkspr.mobi/blog/2012/?comment-order">https://shkspr.mobi/blog/2012/?comment-order</a></li>
</ul>

<p>Did you find this post useful? Please leave a comment here!</p>
<img src="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/info/okgo.php?ID=54404&HTTP_REFERER=RSS" alt="" width="1" height="1" loading="eager">]]></content:encoded>
					
					<wfw:commentRss>https://shkspr.mobi/blog/2024/12/order-wordpress-posts-by-most-comments/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title><![CDATA[WordPress - Sic Transit Gloria Mundi]]></title>
		<link>https://shkspr.mobi/blog/2024/10/wordpress-sic-transit-gloria-mundi/</link>
					<comments>https://shkspr.mobi/blog/2024/10/wordpress-sic-transit-gloria-mundi/#comments</comments>
				<dc:creator><![CDATA[@edent]]></dc:creator>
		<pubDate>Mon, 21 Oct 2024 11:34:59 +0000</pubDate>
				<category><![CDATA[/etc/]]></category>
		<category><![CDATA[blogging]]></category>
		<category><![CDATA[WordPress]]></category>
		<category><![CDATA[wpdrama]]></category>
		<guid isPermaLink="false">https://shkspr.mobi/blog/?p=53533</guid>

					<description><![CDATA[Why do so many vastly-wealthy tech personalities go mad?  My ideal job involves being employed by a millionaire tech-bro. Just before they get on stage, or moments before they file a lawsuit, or an instant before they publish their thought leadership - I will appear to them. I will be dressed in rags, body smeared with excrement, weeping sores blotching my face. I will sidle up to them, lean…]]></description>
										<content:encoded><![CDATA[<p>Why do so many vastly-wealthy tech personalities go mad?</p>

<p>My ideal job involves being employed by a millionaire tech-bro. Just before they get on stage, or moments before they file a lawsuit, or an instant before they publish their thought leadership - I will appear to them. I will be dressed in rags, body smeared with excrement, weeping sores blotching my face. I will sidle up to them, lean down, and whisper into their ear "<i lang="la">Sic Transit Gloria Mundi!</i>"</p>

<p>This used to be the way, of course. When a new Pope or Emperor was paraded through the streets, the people cheered and the acolytes coo'd. But someone would chant at them in Latin, "Thus passes all Earthly glory."</p>

<p>The sun sets on every empire. Each god-king was eventually proved to be mortal. The evil that men do lives after them and the good is oft interred within their bones. And so on. We know this. No one is perfect all the time. Every genius has a moment of idiocy.</p>

<p>For small projects, it makes sense to have only one gaffer. All the work passes through him. Too many cooks poison the well.</p>

<p>As projects get bigger, one person doesn't scale. It is functionally impossible to know everything, see everything, please everyone. Yet the sole arbiter remains. We (almost-jokingly) call this position BDFL. The Benevolent Dictator For Life.</p>

<p>But BDFL only works if the D is genuinely B. Otherwise the FL becomes <abbr title="Fuck My Life">FML</abbr>.</p>

<p>It must be psychologically difficult being responsible for a mega-project. I certainly couldn't do it. If you're wasting time reading this blog post, <em>you</em> almost certainly couldn't do it.  I like my friends to challenge my occasional missteps. I want people to be somewhat honest to my face. I can't imagine what it would do to my ego to receive endless praise. Of course I'd tune out the negative voices.</p>

<p>This isn't to excuse their excesses. Nor to fully demystify their demagoguery. I just want them to have good mental health so their public meltdowns don't reverberate through the æther, infecting us all with their psychic fallout.</p>

<p>*<em>sigh</em>* I'd rather not be blogging about blogging.</p>
<img src="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/info/okgo.php?ID=53533&HTTP_REFERER=RSS" alt="" width="1" height="1" loading="eager">]]></content:encoded>
					
					<wfw:commentRss>https://shkspr.mobi/blog/2024/10/wordpress-sic-transit-gloria-mundi/feed/</wfw:commentRss>
			<slash:comments>3</slash:comments>
		
		
			</item>
		<item>
		<title><![CDATA[Tending To My Digital Garden]]></title>
		<link>https://shkspr.mobi/blog/2024/10/tending-to-my-digital-garden/</link>
					<comments>https://shkspr.mobi/blog/2024/10/tending-to-my-digital-garden/#comments</comments>
				<dc:creator><![CDATA[@edent]]></dc:creator>
		<pubDate>Sat, 12 Oct 2024 11:34:11 +0000</pubDate>
				<category><![CDATA[/etc/]]></category>
		<category><![CDATA[blogging]]></category>
		<guid isPermaLink="false">https://shkspr.mobi/blog/?p=52801</guid>

					<description><![CDATA[I&#039;ve written over 3,000 blog posts throughout the years. This blog has become a repository of my thoughts, feelings, experiments, hopes, and creations.  It has also become outdated, buggy, and suffers from link-rot.  So, every day, I tend to my digital garden. I go in to old posts and check that the links are still pointing somewhere relevant. Are the embeds still live or do they need replacing?  …]]></description>
										<content:encoded><![CDATA[<p>I've written over 3,000 blog posts throughout the years. This blog has become a repository of my thoughts, feelings, experiments, hopes, and creations.</p>

<p>It has also become outdated, buggy, and suffers from link-rot.</p>

<p>So, every day, I tend to my digital garden. I <a href="https://shkspr.mobi/blog/on-this-day/">go in to old posts</a> and check that the links are still pointing somewhere relevant. Are the embeds still live or do they need replacing?  Has my writing somehow been mangled by me buggering about with CSS?</p>

<p>Oh, I could use automated tools. But when a spammer takes over a domain I've linked to, they rarely send an HTTP 410 code.</p>

<p>Sometimes the work is delightful - finding a prescient post from a decade ago. Sometimes it is frustrating - being unable to find a vital-but-long-dead link. And sometimes it is sad - seeing how much or how little the world has changed.</p>

<p>But, mostly, it is meditative. We do our best to fight against decay, but entropy always wins in the end. Every link eventually withers and every truth is eroded by time.  Nevertheless, we continue.</p>
<img src="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/info/okgo.php?ID=52801&HTTP_REFERER=RSS" alt="" width="1" height="1" loading="eager">]]></content:encoded>
					
					<wfw:commentRss>https://shkspr.mobi/blog/2024/10/tending-to-my-digital-garden/feed/</wfw:commentRss>
			<slash:comments>4</slash:comments>
		
		
			</item>
		<item>
		<title><![CDATA[Replacing Twitter Embeds With Images]]></title>
		<link>https://shkspr.mobi/blog/2024/08/replacing-twitter-embeds-with-images/</link>
					<comments>https://shkspr.mobi/blog/2024/08/replacing-twitter-embeds-with-images/#comments</comments>
				<dc:creator><![CDATA[@edent]]></dc:creator>
		<pubDate>Mon, 19 Aug 2024 11:34:18 +0000</pubDate>
				<category><![CDATA[/etc/]]></category>
		<category><![CDATA[api]]></category>
		<category><![CDATA[blogging]]></category>
		<category><![CDATA[twitter]]></category>
		<guid isPermaLink="false">https://shkspr.mobi/blog/?p=51289</guid>

					<description><![CDATA[I logged into Twitter using a fresh account last week. No followers, no preferences set. The default experience was an unending slurry of racism and porn.  I don&#039;t care to use Twitter any more. Whatever good that was there is now drowned in a cess-pit of violent filth.  I still have a lot of Tweets embedded on this blog. Using WordPress, it was easy to paste in a link and have it converted to an…]]></description>
										<content:encoded><![CDATA[<p>I logged into Twitter using a fresh account last week. No followers, no preferences set. The default experience was an unending slurry of racism and porn.  I don't care to use Twitter any more. Whatever good that was there is now drowned in a cess-pit of violent filth.</p>

<p>I still have a lot of Tweets embedded on this blog. Using WordPress, it was easy to paste in a link and have it converted to an embed. But I don't want to direct people to a dangerous site.</p>

<p>So here's a somewhat automated way to replace embedded Tweets with screenshots.</p>

<p><a href="https://github.com/edent/Tweet2Img">Shut up and show me the code!</a></p>

<h2 id="demo"><a href="https://shkspr.mobi/blog/2024/08/replacing-twitter-embeds-with-images/#demo">Demo</a></h2>

<img src="https://shkspr.mobi/blog/wp-content/uploads/2013/11/400929884786417664.webp" alt="Screenshot from Twitter. 2013-11-14T09:50:42.000Z. Terence Eden is on Mastodon (@edent). iOS only! Why not mobile web? MT @SCAS999: Our new app could help save the life of a person suffering cardiac arrest http://t.co/65OaiQ3W78. Reply 2013-11-14T10:15:08.000Z. South Central Ambulance Service (@SCAS999). @edent We'd love a mobile web version. Are you able to help? The app was done for free! Google link here http://t.co/R3xVkLHi3A" width="549" height="385" class="aligncenter size-full wp-image-51330">

<h2 id="use-the-embed-platform"><a href="https://shkspr.mobi/blog/2024/08/replacing-twitter-embeds-with-images/#use-the-embed-platform">Use the Embed Platform</a></h2>

<p>Take the ID of the Tweet you want to convert. Add it on to the end of an embed URl like this - <a href="https://platform.twitter.com/embed/Tweet.html?dnt=true&amp;embedId=twitter-widget-0&amp;frame=false&amp;hideCard=false&amp;hideThread=true&amp;lang=en&amp;theme=light&amp;width=550px&amp;id=1092852483033055232">https://platform.twitter.com/embed/Tweet.html?dnt=true&amp;embedId=twitter-widget-0&amp;frame=false&amp;hideCard=false&amp;hideThread=true&amp;lang=en&amp;theme=light&amp;width=550px&amp;id=1092852483033055232</a></p>

<p>Let's make that a bit more readable:</p>

<pre><code class="language-_">https://platform.twitter.com/embed/Tweet.html?
   hideCard=false
  &amp;hideThread=true
  &amp;lang=en
  &amp;theme=light
  &amp;width=550px
  &amp;id=1092852483033055232
</code></pre>

<p>You can change whether to show a card (the attached image or link), show the preceding message in the thread or not, what UI language to show, dark or light mode, and how wide you want the embed to be.</p>

<h2 id="use-selenium-to-automate-the-screenshot"><a href="https://shkspr.mobi/blog/2024/08/replacing-twitter-embeds-with-images/#use-selenium-to-automate-the-screenshot">Use Selenium to automate the screenshot</a></h2>

<p>Using Python, we can use Selenium's Chrome Webdriver to open pages, find elements, and take screenshots:</p>

<pre><code class="language-python">import time

import io
from PIL import Image

from selenium import webdriver 
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.by import By

#   Chrome's headless options
chrome_options = Options()
chrome_options.add_argument('--headless=new')
chrome_options.add_argument('--window-size=1920,1080')

#   Turn off everything
chrome_options.add_argument("--disable-gpu")
chrome_options.add_argument("--no-sandbox")
chrome_options.add_argument("--disable-dev-shm-usage")
chrome_options.add_argument("--disable-extensions")
chrome_options.add_argument("--disable-infobars")
chrome_options.add_argument("--disable-logging")
chrome_options.add_argument("--log-level=3")

#   Start Chrome
driver = webdriver.Chrome(options=chrome_options)

#   Open the page
driver.get("https://platform.twitter.com/embed/Tweet.html?dnt=true&amp;embedId=twitter-widget-0&amp;frame=false&amp;hideCard=false&amp;hideThread=true&amp;lang=en&amp;theme=light&amp;width=550px&amp;id=1092852483033055232")

#   Twitter is slow!
time.sleep(5)

#   Get the Tweet
tweet = driver.find_element(By.TAG_NAME, "article")
#   Use the parent element for more padding
tweet = driver.execute_script("return arguments[0].parentNode;", tweet)

#   Save as an image
print("Save")
image_binary = tweet.screenshot_as_png
img = Image.open(io.BytesIO(image_binary))
img.save("tweet.png")
</code></pre>

<h2 id="get-the-alt-text"><a href="https://shkspr.mobi/blog/2024/08/replacing-twitter-embeds-with-images/#get-the-alt-text">Get the alt text</a></h2>

<p>Accessibility is important. Getting the text of the Tweet is as simple as:</p>

<pre><code class="language-python">#   Get the alt text
alt = tweet.text
</code></pre>

<p>But that retrieves <em>all</em> the text - including things like "Copy link to post" and "Read more on X" - because Twitter doesn't believe in semantic HTML.  There's also no way to easily get the number of likes and retweets - which might be useful information.</p>

<p>If there are images in the post, it's useful to get their alt text. This is simpler:</p>

<pre><code class="language-python">images = tweet.find_elements(By.TAG_NAME, "img")
print("\nAlt text of images:")
for img in images:
    alt_text = img.get_attribute("alt")
    if alt_text:
        print(alt_text)
</code></pre>

<h2 id="use-the-api"><a href="https://shkspr.mobi/blog/2024/08/replacing-twitter-embeds-with-images/#use-the-api">Use The API</a></h2>

<p>There is a better way to get all the text, alt text, and metadata. Use the hidden syndication API!  But that's a blog post for another time…</p>

<h2 id="get-the-code"><a href="https://shkspr.mobi/blog/2024/08/replacing-twitter-embeds-with-images/#get-the-code">Get the Code</a></h2>

<p><a href="https://github.com/edent/Tweet2Img">Grab the code from GitHub</a> - and if it is useful to you, please star the repo or leave a friendly comment.</p>

<p>Of course, if we can use the API to get the pure data, perhaps it is possible to make some lovely semantic HTML rather than an image...? 😉</p>
<img src="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/info/okgo.php?ID=51289&HTTP_REFERER=RSS" alt="" width="1" height="1" loading="eager">]]></content:encoded>
					
					<wfw:commentRss>https://shkspr.mobi/blog/2024/08/replacing-twitter-embeds-with-images/feed/</wfw:commentRss>
			<slash:comments>3</slash:comments>
		
		
			</item>
		<item>
		<title><![CDATA[Corporate Blogging is Hard; Open a GitHub Issue Instead]]></title>
		<link>https://shkspr.mobi/blog/2024/07/corporate-blogging-is-hard-open-a-github-issue-instead/</link>
					<comments>https://shkspr.mobi/blog/2024/07/corporate-blogging-is-hard-open-a-github-issue-instead/#comments</comments>
				<dc:creator><![CDATA[@edent]]></dc:creator>
		<pubDate>Fri, 26 Jul 2024 11:34:02 +0000</pubDate>
				<category><![CDATA[/etc/]]></category>
		<category><![CDATA[blogging]]></category>
		<category><![CDATA[github]]></category>
		<category><![CDATA[marketing]]></category>
		<guid isPermaLink="false">https://shkspr.mobi/blog/?p=29202</guid>

					<description><![CDATA[(Inspired by this conversation between Jukesie and Himal)  Lots of companies encourage their staff to blog. It&#039;s free PR! It makes them look like they&#039;re on the cutting edge of technology! It helps with recruitment!  It can also be a corporate nightmare. What if the developer says something stupid? What if it accidentally reveals something top secret? What if the CEO doesn&#039;t like it?  And so,…]]></description>
										<content:encoded><![CDATA[<p>(Inspired by <a href="https://bsky.app/profile/himal.bsky.social/post/3kxwv4wfgnr2r">this conversation between Jukesie and Himal</a>)</p>

<p>Lots of companies encourage their staff to blog. It's free PR! It makes them look like they're on the cutting edge of technology! It helps with recruitment!</p>

<p>It can also be a corporate nightmare. What if the developer says something stupid? What if it accidentally reveals something top secret? What if the CEO doesn't like it?</p>

<p>And so, gradually, any free-wheelin' developer blog gradually succumbs to the tender mercies of the comms team. It starts with help editing and clarifying the writing. Then it turns into massaging the tone-of-voice. Before too long you have to wait for a slot on the corporate comms grid. And then you need seven levels of sign-off just to post something anodyne like "5 reasons we switched to Rust".</p>

<p><em>*sigh*</em></p>

<p>So, maybe once or twice, I cheated.</p>

<p>Rather than writing a blog post called "An interesting issue configuring GLaDOS with YAML" I opened an issue on our public GitHub repo.</p>

<p>Oh, sure, it wasn't as "bloggy". And I still made sure not to discuss anything secret or sensitive. For some of the more contentious issues, I ran it by a few trusted colleagues first. But, for all intents and purposes, it was a blog post. Nerds got to reply in the comments, people shared it on HN, people quoted it on social media.</p>

<p>I've since left that sort of restrictive environment. I still see my old team occasionally publishing a useful blog post. But I keep my eyes on their GitHub issues to see the <em>real</em> story!</p>

<p>What's the moral here?</p>

<p>Should you bypass your employer's strict social media policy? No, absolutely not. 
Should you ensure that your GitHub issues are well-formatted, interesting to read, and useful to others? Yes.</p>
<img src="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/info/okgo.php?ID=29202&HTTP_REFERER=RSS" alt="" width="1" height="1" loading="eager">]]></content:encoded>
					
					<wfw:commentRss>https://shkspr.mobi/blog/2024/07/corporate-blogging-is-hard-open-a-github-issue-instead/feed/</wfw:commentRss>
			<slash:comments>3</slash:comments>
		
		
			</item>
		<item>
		<title><![CDATA[An end to daily blogging]]></title>
		<link>https://shkspr.mobi/blog/2024/07/an-end-to-daily-blogging/</link>
					<comments>https://shkspr.mobi/blog/2024/07/an-end-to-daily-blogging/#comments</comments>
				<dc:creator><![CDATA[@edent]]></dc:creator>
		<pubDate>Fri, 05 Jul 2024 11:34:36 +0000</pubDate>
				<category><![CDATA[/etc/]]></category>
		<category><![CDATA[blogging]]></category>
		<category><![CDATA[meta]]></category>
		<guid isPermaLink="false">https://shkspr.mobi/blog/?p=51043</guid>

					<description><![CDATA[If you explore this blog&#039;s archives, you&#039;ll see that I&#039;ve been blogging continuously every day since the start of 2020.  Before that, I was blogging every month since mid-2008.  Today, I am very hungover. Although I usually write a bunch of posts a few days and weeks in advance, I find myself looking at my publishing calendar and seeing it blissfully empty.  Part of the reason I blog is that I…]]></description>
										<content:encoded><![CDATA[<p>If you <a href="https://shkspr.mobi/blog/2024/07/an-end-to-daily-blogging/#edent_calendar_widget-2" onclick="openDeets()">explore this blog's archives</a>, you'll see that I've been blogging continuously every day since the start of 2020.  Before that, I was blogging every month since mid-2008.</p>

<p>Today, I am <em>very</em> hungover. Although I usually write a bunch of posts a few days and weeks in advance, I find myself looking at my publishing calendar and seeing it blissfully empty.</p>

<p>Part of the reason I blog is that I truly want to learn something new every day - and I want to share that knowledge. Whether it's a cool programming trick, and interesting book, or thoughts on the world - I enjoy lifelong learning.</p>

<p>But I am tired. Also hungover - but mostly tired.  Perhaps it is a little burn-out. Perhaps it is a little disillusionment with the state of the tech world.  Perhaps I'm spending too much of my energies elsewhere.  Either way, despite the little dopamine hit I get when my posts go viral, or when new friends leave comments, I think now is the perfect time to throttle back.</p>

<p>So, this is the 3,094th post that I've published on this site - and it is likely to be the last one for a little while.  I'll still be writing when the mood strikes.  And I'll still be <a href="https://mastodon.social/@Edent">wittering away on Mastodon</a>.</p>

<p>Thank you for reading, commenting, and sharing.</p>

<p>Be seeing you!</p>

<img src="https://shkspr.mobi/blog/wp-content/uploads/2024/07/seeing-you.gif" alt="Gif from the Prisoner. Be Seeing You." width="398" height="298" class="aligncenter size-full wp-image-51052">

<script>function openDeets() { document.getElementsByTagName("details")[0].setAttribute("open", "open");}</script>
<img src="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/info/okgo.php?ID=51043&HTTP_REFERER=RSS" alt="" width="1" height="1" loading="eager">]]></content:encoded>
					
					<wfw:commentRss>https://shkspr.mobi/blog/2024/07/an-end-to-daily-blogging/feed/</wfw:commentRss>
			<slash:comments>13</slash:comments>
		
		
			</item>
		<item>
		<title><![CDATA[It was twenty years ago today]]></title>
		<link>https://shkspr.mobi/blog/2024/05/it-was-twenty-years-ago-today/</link>
					<comments>https://shkspr.mobi/blog/2024/05/it-was-twenty-years-ago-today/#comments</comments>
				<dc:creator><![CDATA[@edent]]></dc:creator>
		<pubDate>Sat, 11 May 2024 11:34:45 +0000</pubDate>
				<category><![CDATA[/etc/]]></category>
		<category><![CDATA[blogging]]></category>
		<category><![CDATA[meta]]></category>
		<guid isPermaLink="false">https://shkspr.mobi/blog/?p=50534</guid>

					<description><![CDATA[I wrote my first public blog post on 2004-05-11.  I immediately followed it up with a brief review of my BlackBerry.    I kept up the blogging for a few months, then it trickled off. I preferred posting on Usenet and other primitive forms of social media. But, by 2007, I was back to blogging on my own site again, and I never really stopped.  This blog fluctuates between being a diary, an excuse…]]></description>
										<content:encoded><![CDATA[<p>I wrote <a href="https://web.archive.org/web/20231125015446/https://terryeden.blogspot.com/2004/05/in-begggggininggggg.html">my first public blog post</a> on 2004-05-11<sup id="fnref:import"><a href="https://shkspr.mobi/blog/2024/05/it-was-twenty-years-ago-today/#fn:import" class="footnote-ref" title="Since then, I've imported it to this site" role="doc-noteref">0</a></sup>.  I immediately followed it up with <a href="https://web.archive.org/web/20231125002337/https://terryeden.blogspot.com/2004/05/i-also-have-blackberry.html">a brief review of my BlackBerry</a><sup id="fnref:bb"><a href="https://shkspr.mobi/blog/2024/05/it-was-twenty-years-ago-today/#fn:bb" class="footnote-ref" title="If memory serves, I found the BlackBerry in a colleague's drawer, asked to borrow it, then used social engineering to get the IT team to set it up for me. Fun times!" role="doc-noteref">1</a></sup>.</p>

<img src="https://shkspr.mobi/blog/wp-content/uploads/2024/05/I-power-blogger.webp" alt="Heavily pixellated image saying &quot;I Power Blogger&quot;." width="1024" height="349" class="aligncenter size-full wp-image-50538">

<p>I kept up the blogging for a few months, then it trickled off. I preferred posting on Usenet and other primitive forms of social media. But, by 2007, I was <a href="https://shkspr.mobi/blog/2007/11/">back to blogging on my own site again</a>, and I never really stopped.  This blog fluctuates between being a diary, an excuse to rant, and technical writing. It's <em>my</em> site and I can do whatever I want with it. That's rather freeing.</p>

<p>I have an "<a href="https://shkspr.mobi/blog/on-this-day/">On This Day</a>" feature of my blog. Every morning I check what I was writing about on this day in years gone by. I find it informative and meditative to see how much I've grown<sup id="fnref:old"><a href="https://shkspr.mobi/blog/2024/05/it-was-twenty-years-ago-today/#fn:old" class="footnote-ref" title="And how little I've changed." role="doc-noteref">2</a></sup> and what topics I keep returning to.</p>

<p>I'm not big on milestones or anniversaries. But it does feel rather nice to know that I started something a few decades ago that is still a going concern.</p>

<p>Here's a little treat to thank you for reading:</p>

<iframe title="Sgt. Pepper's Lonely Hearts Club Band" width="620" height="465" src="https://www.youtube.com/embed/m8yaP_woyRc?list=PLre6LI5yV64jpqaPuI4MXW-C-HAxBznH1" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen=""></iframe>

<div id="footnotes" role="doc-endnotes">
<hr aria-label="Footnotes">
<ol start="0">

<li id="fn:import">
<p>Since then, <a href="https://shkspr.mobi/blog/2004/05/in-the-begggggininggggg/">I've imported it to this site</a>&nbsp;<a href="https://shkspr.mobi/blog/2024/05/it-was-twenty-years-ago-today/#fnref:import" class="footnote-backref" role="doc-backlink">↩︎</a></p>
</li>

<li id="fn:bb">
<p>If memory serves, I found the BlackBerry in a colleague's drawer, asked to borrow it, then used social engineering to get the IT team to set it up for me. Fun times!&nbsp;<a href="https://shkspr.mobi/blog/2024/05/it-was-twenty-years-ago-today/#fnref:bb" class="footnote-backref" role="doc-backlink">↩︎</a></p>
</li>

<li id="fn:old">
<p>And how little I've changed.&nbsp;<a href="https://shkspr.mobi/blog/2024/05/it-was-twenty-years-ago-today/#fnref:old" class="footnote-backref" role="doc-backlink">↩︎</a></p>
</li>

</ol>
</div>
<img src="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/info/okgo.php?ID=50534&HTTP_REFERER=RSS" alt="" width="1" height="1" loading="eager">]]></content:encoded>
					
					<wfw:commentRss>https://shkspr.mobi/blog/2024/05/it-was-twenty-years-ago-today/feed/</wfw:commentRss>
			<slash:comments>6</slash:comments>
		
		
			</item>
		<item>
		<title><![CDATA[]]></title>
		<link>https://shkspr.mobi/blog/2024/05/49911/</link>
					<comments>https://shkspr.mobi/blog/2024/05/49911/#comments</comments>
				<dc:creator><![CDATA[@edent]]></dc:creator>
		<pubDate>Thu, 02 May 2024 11:34:51 +0000</pubDate>
				<category><![CDATA[/etc/]]></category>
		<category><![CDATA[blogging]]></category>
		<category><![CDATA[HTML]]></category>
		<category><![CDATA[IndieWebCamp]]></category>
		<guid isPermaLink="false">https://shkspr.mobi/blog/?p=49911</guid>

					<description><![CDATA[While attending IndieWebCamp in Brighton a few weeks ago, a bunch of us were talking about blogging. What is post? What should it contain? What&#039;s optional?  Someone (probably Jeremy Keith) said:  A blog post doesn&#039;t need a title.  In a literal sense, he was wrong. The HTML specification makes it clear that the &#60;title&#62; element is mandatory. All documents have title.  But, in a practical sense, he…]]></description>
										<content:encoded><![CDATA[<p>While attending IndieWebCamp in Brighton a few weeks ago, a bunch of us were talking about blogging. What is post? What should it contain? What's optional?</p>

<p>Someone (probably <a href="https://adactio.com/">Jeremy Keith</a>) said:</p>

<blockquote><p>A blog post doesn't need a title.</p></blockquote>

<p>In a literal sense, he was wrong. The <a href="https://html.spec.whatwg.org/multipage/semantics.html#the-head-element">HTML specification</a> makes it clear that the <code>&lt;title&gt;</code> element is mandatory. All documents have title.</p>

<p>But, in a practical sense, he was right. This blog post has an empty <code>&lt;h1&gt;</code> element - the document might be semantically invalid, it might reduce accessibility, but the <em>post</em> is still available.</p>

<p>A blog post can be a plain text document uploaded to a server. It can be an image hosted on a social network. It can be a voice note shared with your friends.</p>

<p>Title, dates, comments, links, and text are all <em>optional</em>.</p>

<p>No one is policing this.</p>

<p>Go create something which doesn't fit properly with the rest of the world.</p>
<img src="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/info/okgo.php?ID=49911&HTTP_REFERER=RSS" alt="" width="1" height="1" loading="eager">]]></content:encoded>
					
					<wfw:commentRss>https://shkspr.mobi/blog/2024/05/49911/feed/</wfw:commentRss>
			<slash:comments>12</slash:comments>
		
		
			</item>
		<item>
		<title><![CDATA[3,000 blog posts!]]></title>
		<link>https://shkspr.mobi/blog/2024/04/3000-blog-posts/</link>
					<comments>https://shkspr.mobi/blog/2024/04/3000-blog-posts/#comments</comments>
				<dc:creator><![CDATA[@edent]]></dc:creator>
		<pubDate>Tue, 02 Apr 2024 11:34:53 +0000</pubDate>
				<category><![CDATA[/etc/]]></category>
		<category><![CDATA[blog]]></category>
		<category><![CDATA[blogging]]></category>
		<category><![CDATA[WordPress]]></category>
		<guid isPermaLink="false">https://shkspr.mobi/blog/?p=46084</guid>

					<description><![CDATA[This is the 3,000th blog post I&#039;ve published on this site! Bloody hell!  I first started a blog on Blogger.com in 2004 - twenty years ago. Like all blogs, I managed half a dozen posts before I forgot about it.  Cut to 2007 and I decided to launch shkspr.mobi as a weird site dedicated to rendering Shakespeare&#039;s plays in txt spk. Judging by Archive.org I was still using Blogger.  By 2008 I was…]]></description>
										<content:encoded><![CDATA[<p>This is the 3,000th blog post I've published on this site! Bloody hell!</p>

<p>I first started <a href="https://shkspr.mobi/blog/2004/?order=ASC">a blog on Blogger.com in 2004 - twenty years ago</a>. Like all blogs, I managed half a dozen posts before I forgot about it.</p>

<p>Cut to 2007 and I decided to launch <a href="https://shkspr.mobi/blog/2007/11/shksprmobi-goes-live/"><code>shkspr.mobi</code></a> as a weird site dedicated to rendering Shakespeare's plays in txt spk. Judging by <a href="https://web.archive.org/web/20090122051921/http://shkspr.mobi/blog/">Archive.org</a> I was still using Blogger.</p>

<p>By <a href="https://shkspr.mobi/blog/2008/?order=ASC">2008</a> I was blogging most months. And then I never really stopped.  In early <a href="https://shkspr.mobi/blog/2009/05/playing-with-wordpress/">2009 I switched to WordPress</a> which led me down the path of developing my own theme and plugins.</p>

<p>Along the way, I've added <a href="https://shkspr.mobi/blog/2023/02/necroposting-blogging-from-before-you-started-blogging/">necroposts</a> - blog posts from work blogs which have since become defunct, or letters that I wrote to magazines when I was a kid.</p>

<p>I've also been quite liberal with my use of <a href="https://shkspr.mobi/blog/tag/retropost/">retroposts</a> - posts written far in advance and then published once the dust has settled. There are a few more of those in the pipeline.</p>

<p>As of today, there are about 6,500 pieces of media in my library - taking up 2.3GB.</p>

<p>Last year I hit <a href="https://shkspr.mobi/blog/2023/06/12000-comments/">12,000 comments</a> - now I'm on about 13,500.</p>

<p>Since 2009, when I first turned on WordPress stats, my blog has been visited 8.5 million times.</p>

<p>According to <a href="https://wpguru.co.uk/2018/03/how-to-retrieve-the-total-word-count-from-all-posts-in-wordpress/">some code I copy and pasted</a> the total wordcount across all my posts is...</p>

<p>1,553,953 words.</p>

<p><em>Bloody hell!</em></p>

<p>So, because bloggers like nothing more than writing about blogging, I thought I'd do a little look behind my process.</p>

<h2 id="how-i-write"><a href="https://shkspr.mobi/blog/2024/04/3000-blog-posts/#how-i-write">How I write</a></h2>

<p>I don't write every day. But I do write <em>most</em> days. I usually write title and scraps of ideas, then leave them to ferment<sup id="fnref:ferment"><a href="https://shkspr.mobi/blog/2024/04/3000-blog-posts/#fn:ferment" class="footnote-ref" title="Or foment." role="doc-noteref">0</a></sup>.  They usually start out as social-media posts which have got a little traction.</p>

<p>When I do write a post, I'll quite often leave it in a half-finished state and come back to it later. I usually have several blog posts on the go at any one time. It is incredibly rare that I'll write something and publish it that same day. And it is only occasionally that I publish something in the same week it was written.</p>

<p>I schedule and reschedule as the mood takes me.</p>

<p>I write in MarkDown using the classic editor. I'm too stuck in my ways to switch to Gutenberg blocks.</p>

<h2 id="why-i-write"><a href="https://shkspr.mobi/blog/2024/04/3000-blog-posts/#why-i-write">Why I write</a></h2>

<p>Because I enjoy it.</p>

<p>Someone once asked me how I managed to <a href="https://shkspr.mobi/blog/library/">read so many books</a> and the answer is simple - because I prefer reading books to doing other things.</p>

<p>I could spend more time playing video games, or learning to solder, or cooking from scratch, or brewing beer, or drinking beer, or any of a thousand hobbies<sup id="fnref:beer"><a href="https://shkspr.mobi/blog/2024/04/3000-blog-posts/#fn:beer" class="footnote-ref" title="Mostly beer related, TBF." role="doc-noteref">1</a></sup>. But reading and writing are what I prefer doing.</p>

<p>Oh, sure, there's a thrill when a post goes viral. Or when someone you admire says something nice about the writing.  Or when someone leaves a comment on an old blog saying how I helped them. Or when <a href="https://shkspr.mobi/blog/citations/">I'm cited in academic research</a>.</p>

<p>But the reason I write is to get all the ideas out of my head<sup id="fnref:🤯"><a href="https://shkspr.mobi/blog/2024/04/3000-blog-posts/#fn:🤯" class="footnote-ref" title="And there are a lot of ideas in there. Not all of them good. Very few of them sensible. But all rattling around. Once written down, they no longer crowd my thoughts." role="doc-noteref">2</a></sup>. I blog daily because, deep down, I want to learn something new and surprising every day - and I want to share that with anyone who happens to pass by.</p>

<h2 id="what-ive-learned"><a href="https://shkspr.mobi/blog/2024/04/3000-blog-posts/#what-ive-learned">What I've learned</a></h2>

<p>It is trite, but the blog posts I expected to be lauded were mostly flops. The half-arsed ones often do well<sup id="fnref:🤪"><a href="https://shkspr.mobi/blog/2024/04/3000-blog-posts/#fn:🤪" class="footnote-ref" title="There is a lesson here which I refuse to learn!" role="doc-noteref">3</a></sup>.</p>

<p>People can't read. I'll write as clearly as I can and some yutz will misunderstand me<sup id="fnref:deliberate"><a href="https://shkspr.mobi/blog/2024/04/3000-blog-posts/#fn:deliberate" class="footnote-ref" title="Perhaps deliberately. Perhaps not. But you can't tell the difference." role="doc-noteref">4</a></sup>.</p>

<p>Some people are incapable of understanding hyperbole. Perhaps it is a feature of British English which isn't well understood around the world?</p>

<p>Americans don't like swearing in blog posts and get a bit po-faced about it<sup id="fnref:swearing"><a href="https://shkspr.mobi/blog/2024/04/3000-blog-posts/#fn:swearing" class="footnote-ref" title="The nice thing about swearing is that it simultaneously shows you are both funny and clever!" role="doc-noteref">5</a></sup>.</p>

<p>My predictions rarely come true, my opinions fluctuate, and my spelling remains poor.</p>

<p>I'm nothing without <a href="https://web.archive.org/web/20240403222815/https://mymisanthropicmusings.org.uk/">my unpaid editor</a>.</p>

<h2 id="thank-you-for-reading"><a href="https://shkspr.mobi/blog/2024/04/3000-blog-posts/#thank-you-for-reading">Thank you for reading</a></h2>

<p>I mostly write for me. But it is lovely to know that people all around the world occasionally stop by to read.  It is very sweet of you.</p>

<p>If you've enjoyed my writing or ever found it useful, please go off and start your own blog. Write whatever nonsense comes into <em>your</em> head.</p>

<div id="footnotes" role="doc-endnotes">
<hr aria-label="Footnotes">
<ol start="0">

<li id="fn:ferment">
<p>Or foment.&nbsp;<a href="https://shkspr.mobi/blog/2024/04/3000-blog-posts/#fnref:ferment" class="footnote-backref" role="doc-backlink">↩︎</a></p>
</li>

<li id="fn:beer">
<p>Mostly beer related, TBF.&nbsp;<a href="https://shkspr.mobi/blog/2024/04/3000-blog-posts/#fnref:beer" class="footnote-backref" role="doc-backlink">↩︎</a></p>
</li>

<li id="fn:🤯">
<p>And there are a <em>lot</em> of ideas in there. Not all of them good. Very few of them sensible. But all rattling around. Once written down, they no longer crowd my thoughts.&nbsp;<a href="https://shkspr.mobi/blog/2024/04/3000-blog-posts/#fnref:🤯" class="footnote-backref" role="doc-backlink">↩︎</a></p>
</li>

<li id="fn:🤪">
<p>There is a lesson here which I <em>refuse</em> to learn!&nbsp;<a href="https://shkspr.mobi/blog/2024/04/3000-blog-posts/#fnref:🤪" class="footnote-backref" role="doc-backlink">↩︎</a></p>
</li>

<li id="fn:deliberate">
<p>Perhaps deliberately. Perhaps not. But you can't tell the difference.&nbsp;<a href="https://shkspr.mobi/blog/2024/04/3000-blog-posts/#fnref:deliberate" class="footnote-backref" role="doc-backlink">↩︎</a></p>
</li>

<li id="fn:swearing">
<p>The nice thing about swearing is that it simultaneously shows you are both funny <em>and</em> clever!&nbsp;<a href="https://shkspr.mobi/blog/2024/04/3000-blog-posts/#fnref:swearing" class="footnote-backref" role="doc-backlink">↩︎</a></p>
</li>

</ol>
</div>
<img src="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/info/okgo.php?ID=46084&HTTP_REFERER=RSS" alt="" width="1" height="1" loading="eager">]]></content:encoded>
					
					<wfw:commentRss>https://shkspr.mobi/blog/2024/04/3000-blog-posts/feed/</wfw:commentRss>
			<slash:comments>9</slash:comments>
		
		
			</item>
		<item>
		<title><![CDATA[Caboom! Comment Anywhere, Bring Onto Own Media]]></title>
		<link>https://shkspr.mobi/blog/2024/03/caboom-comment-anywhere-bring-onto-own-media/</link>
					<comments>https://shkspr.mobi/blog/2024/03/caboom-comment-anywhere-bring-onto-own-media/#respond</comments>
				<dc:creator><![CDATA[@edent]]></dc:creator>
		<pubDate>Thu, 21 Mar 2024 12:34:02 +0000</pubDate>
				<category><![CDATA[/etc/]]></category>
		<category><![CDATA[blogging]]></category>
		<category><![CDATA[IndieWebCamp]]></category>
		<guid isPermaLink="false">https://shkspr.mobi/blog/?p=49892</guid>

					<description><![CDATA[In the IndieWeb movement there&#039;s a concept of &#34;POSSE&#34; - Publish Once, Simultaneously Syndicate Elsewhere.  You should publish your words, pictures, songs, reviews on your own site. And then you can choose to share them out to where your audience is. Perhaps that&#039;s posting the link on Facebook, or a copy of a photo on Instagram, or sharing the episode on YouTube.  There&#039;s no shame in meeting your…]]></description>
										<content:encoded><![CDATA[<p>In the IndieWeb movement there's a concept of "POSSE" - <a href="https://indieweb.org/POSSE">Publish Once, Simultaneously Syndicate Elsewhere</a>.</p>

<p>You should publish your words, pictures, songs, reviews on your <em>own</em> site. And then you can choose to share them out to where your audience is. Perhaps that's posting the link on Facebook, or a copy of a photo on Instagram, or sharing the episode on YouTube.</p>

<p>There's no shame in meeting your audience where they are - but the canonical version should be somewhere you control.</p>

<p>But what about the conversation? Social media is about socialising. People who comment on LinkedIn can't see the comments on Mastodon and vice versa.</p>

<p>This is where the concept of "CABOOM!" (exclamation optional) comes in<sup id="fnref:liz"><a href="https://shkspr.mobi/blog/2024/03/caboom-comment-anywhere-bring-onto-own-media/#fn:liz" class="footnote-ref" title="As suggested by my wife" role="doc-noteref">0</a></sup>. As far as practical, you should bring the various comment threads together so everyone can see them<sup id="fnref:backfeed"><a href="https://shkspr.mobi/blog/2024/03/caboom-comment-anywhere-bring-onto-own-media/#fn:backfeed" class="footnote-ref" title="The technical term is, apparently, backfeed - which sounds a little too like backwash for my liking!" role="doc-noteref">1</a></sup>.</p>

<p>There are a few ways to do this - choose whichever is right for you.</p>

<h2 id="do-nothing"><a href="https://shkspr.mobi/blog/2024/03/caboom-comment-anywhere-bring-onto-own-media/#do-nothing">Do Nothing</a></h2>

<p>Let people self-discover that you syndicate. Perhaps they'll stumble upon a bunch of people discussing your post over on Slashdot.  That's fine, but a little hard work.</p>

<h2 id="point-to-your-syndication-sites"><a href="https://shkspr.mobi/blog/2024/03/caboom-comment-anywhere-bring-onto-own-media/#point-to-your-syndication-sites">Point to your syndication sites</a></h2>

<p>At the bottom of your post, you can say "Click here to read the comments on MySpace!"</p>

<h2 id="find-other-sources"><a href="https://shkspr.mobi/blog/2024/03/caboom-comment-anywhere-bring-onto-own-media/#find-other-sources">Find other sources</a></h2>

<p>Did someone share your post to HackerNews? Add a link on your page to the orange-site so that others can read what's being said.</p>

<h2 id="convert-comments-to-webmentions"><a href="https://shkspr.mobi/blog/2024/03/caboom-comment-anywhere-bring-onto-own-media/#convert-comments-to-webmentions">Convert comments to WebMentions</a></h2>

<p>Using a service like <a href="https://brid.gy/">Brid.gy</a> you can "upgrade" external comments to <a href="https://www.w3.org/TR/webmention/">WebMentions</a>.  They will appear on your post as normal comments, but with a link back to their original location.</p>

<h2 id="abuse-apis"><a href="https://shkspr.mobi/blog/2024/03/caboom-comment-anywhere-bring-onto-own-media/#abuse-apis">(Ab)use APIs</a></h2>

<p>Services like Reddit have APIs. You could pull in a complete comment thread and present it on your own site.</p>

<h2 id="make-backups"><a href="https://shkspr.mobi/blog/2024/03/caboom-comment-anywhere-bring-onto-own-media/#make-backups">Make backups</a></h2>

<p>Some of those external sites are going to break one day. Take a backup using <a href="https://archive.org">Archive.org</a> or snap a screenshots.  Perhaps link to those if the original goes away.</p>

<h2 id="be-mindful-of-others-feelings"><a href="https://shkspr.mobi/blog/2024/03/caboom-comment-anywhere-bring-onto-own-media/#be-mindful-of-others-feelings">Be mindful of others' feelings</a></h2>

<p>I've previously discussed <a href="https://shkspr.mobi/blog/2022/12/the-ethics-of-syndicating-comments-using-webmentions/">the ethics of syndicating comments using WebMentions</a>. Not everyone wants their comment copied back to your site. Some people regularly delete comments. Or they may have valid copyright concerns.  Just because someone has written something in public, it doesn't mean it is in the public domain. Be kind to your commenters and respect their wishes.</p>

<h2 id="have-fun"><a href="https://shkspr.mobi/blog/2024/03/caboom-comment-anywhere-bring-onto-own-media/#have-fun">Have fun</a></h2>

<p>There is literally no-one policing this. If you don't want to do it, that's fine. If you want to give it your best effort, that's also fine.</p>

<div id="footnotes" role="doc-endnotes">
<hr aria-label="Footnotes">
<ol start="0">

<li id="fn:liz">
<p>As suggested by <a href="https://web.archive.org/web/20240403222815/https://mymisanthropicmusings.org.uk/">my wife</a>&nbsp;<a href="https://shkspr.mobi/blog/2024/03/caboom-comment-anywhere-bring-onto-own-media/#fnref:liz" class="footnote-backref" role="doc-backlink">↩︎</a></p>
</li>

<li id="fn:backfeed">
<p>The technical term is, apparently, <a href="https://indieweb.org/backfeed">backfeed</a> - which sounds a little too like backwash for my liking!&nbsp;<a href="https://shkspr.mobi/blog/2024/03/caboom-comment-anywhere-bring-onto-own-media/#fnref:backfeed" class="footnote-backref" role="doc-backlink">↩︎</a></p>
</li>

</ol>
</div>
<img src="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/info/okgo.php?ID=49892&HTTP_REFERER=RSS" alt="" width="1" height="1" loading="eager">]]></content:encoded>
					
					<wfw:commentRss>https://shkspr.mobi/blog/2024/03/caboom-comment-anywhere-bring-onto-own-media/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title><![CDATA[A library of all my book reviews]]></title>
		<link>https://shkspr.mobi/blog/2023/12/a-library-of-all-my-book-reviews/</link>
					<comments>https://shkspr.mobi/blog/2023/12/a-library-of-all-my-book-reviews/#respond</comments>
				<dc:creator><![CDATA[@edent]]></dc:creator>
		<pubDate>Fri, 29 Dec 2023 12:34:02 +0000</pubDate>
				<category><![CDATA[/etc/]]></category>
		<category><![CDATA[blogging]]></category>
		<category><![CDATA[books]]></category>
		<category><![CDATA[meta]]></category>
		<category><![CDATA[WordPress]]></category>
		<guid isPermaLink="false">https://shkspr.mobi/blog/?p=48984</guid>

					<description><![CDATA[One of the things I love about having a database-backed blog like WordPress is that&#039;s it opens up a delightful range of possibilities for displaying content.  I&#039;ve read and reviewed around 300 books over the last few years. So I wrote a scrap of code which goes through all my book reviews, grabs their cover and rating, and displays them in a nice grid. You can visit it at shkspr.mobi/blog/library …]]></description>
										<content:encoded><![CDATA[<p>One of the things I love about having a database-backed blog like WordPress is that's it opens up a delightful range of possibilities for displaying content.</p>

<p>I've read and reviewed around 300 books over the last few years. So I wrote a scrap of code which goes through all my book reviews, grabs their cover and rating, and displays them in a nice grid. You can visit it at <a href="https://shkspr.mobi/blog/library">shkspr.mobi/blog/library</a></p>

<p>You know how when you're at a friend's home you rummage through their bookshelf? That's the same idea here. You get to poke through the stacks and, hopefully, pick out something entertaining to read. Or, perhaps, you'll be warned off something truly dreadful.</p>

<p><a href="https://shkspr.mobi/blog/library"><img src="https://shkspr.mobi/blog/wp-content/uploads/2023/12/Library-fs8.png" alt="A grid of books with their titles and star ratings." width="606" height="926" class="aligncenter size-full wp-image-48986"></a></p>

<p>All <a href="https://gitlab.com/edent/blog-theme/-/blob/master/includes/library.php?ref_type=heads">the code for my blog is open source</a> - and I'd be delighted if you did something interesting with it.</p>

<p>This was <em>heavily</em> inspired by <a href="https://daverupert.com/bookshelf/">Dave Rupert's bookshelf</a>.</p>
<img src="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/info/okgo.php?ID=48984&HTTP_REFERER=RSS" alt="" width="1" height="1" loading="eager">]]></content:encoded>
					
					<wfw:commentRss>https://shkspr.mobi/blog/2023/12/a-library-of-all-my-book-reviews/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title><![CDATA[Displaying internal linkbacks on WordPress]]></title>
		<link>https://shkspr.mobi/blog/2023/10/displaying-internal-linkbacks-on-wordpress/</link>
					<comments>https://shkspr.mobi/blog/2023/10/displaying-internal-linkbacks-on-wordpress/#comments</comments>
				<dc:creator><![CDATA[@edent]]></dc:creator>
		<pubDate>Sun, 15 Oct 2023 11:34:58 +0000</pubDate>
				<category><![CDATA[/etc/]]></category>
		<category><![CDATA[blogging]]></category>
		<category><![CDATA[HowTo]]></category>
		<category><![CDATA[php]]></category>
		<category><![CDATA[WordPress]]></category>
		<guid isPermaLink="false">https://shkspr.mobi/blog/?p=48355</guid>

					<description><![CDATA[I have written a lot of blog posts. In some of those posts I link to other posts on my site.  What&#039;s the easiest way of displaying those internal incoming links?  Here&#039;s what it looks like:    Code  All we need to do is search WordPress for the URl of the current page. Loop through the results. Then display those links.  $the_query = new WP_Query(     array(         &#039;s&#039; =&#62; get_the_permalink(), // …]]></description>
										<content:encoded><![CDATA[<p>I have written a <em>lot</em> of blog posts. In some of those posts I link to <em>other</em> posts on my site.  What's the easiest way of displaying those internal incoming links?</p>

<p>Here's what it looks like:</p>

<img src="https://shkspr.mobi/blog/wp-content/uploads/2023/10/What-links-here-fs8.png" alt="Screenshot of my website. The headline says &quot;What links here from around this site.&quot; Underneath are three links." width="800" height="400" class="aligncenter size-full wp-image-48357">

<h2 id="code"><a href="https://shkspr.mobi/blog/2023/10/displaying-internal-linkbacks-on-wordpress/#code">Code</a></h2>

<p>All we need to do is <a href="https://developer.wordpress.org/reference/classes/wp_query/#search-parameters">search WordPress</a> for the URl of the current page. Loop through the results. Then display those links.</p>

<pre><code class="language-php">$the_query = new WP_Query(
    array(
        's' =&gt; get_the_permalink(), // This post
        'post_type' =&gt; 'post',      // Only posts, not pages
        "posts_per_page" =&gt; "-1",   // Get all results
        "order" =&gt; "ASC"            // Oldest first
    )
);

//  Nothing to do if there are no inbound links
if ( !$the_query-&gt;have_posts() ) {
    return;
}

//  Loop through the posts
while ( $the_query-&gt;have_posts() ) {
    //  Set it up
    $the_query-&gt;the_post();
    $id            = get_the_ID();
    $title         = esc_html( get_the_title() );
    $url           = get_the_permalink();
    $comment_date  = get_the_date( "c" );
    $comment_year  = get_the_date( "Y" );

    //  Get the thumbnail
    if ( has_post_thumbnail( $id) ) {
        $thumb = get_the_post_thumbnail( $rid, 'full', 
            array( "class"   =&gt; "related-post-img",
                   "loading" =&gt; "lazy" //   Lazy loading and other attributes
            ) 
        );
    } else {
        $thumb = null;
    }

    //  Create the HTML for the incoming link
    echo &lt;&lt;&lt;EOT
    &lt;li&gt;
        &lt;a href="$url"&gt;
            $thumb
            $title
        &lt;/a&gt;
        &lt;time datetime="$comment_date"&gt;$comment_year&lt;/time&gt;
    &lt;/li&gt;
    EOT;

}
</code></pre>

<p>In order to reduce duplicates, you may also want to <a href="https://plugins.svn.wordpress.org/no-self-ping/tags/1.1.5/no-self-pings.php">disable self-ping</a>.  Here's some code you can stick in your <code>functions.php</code>:</p>

<pre><code class="language-php">function no_self_ping( &amp;$links ) {

    $home = esc_url( home_url() );

    // Process each link in the content and remove if it matches the current site URL
    foreach ( $links as $l =&gt; $link ) {
        if ( 0 === strpos( $link, $home ) ) {
            unset( $links[ $l ] );
        }
    }
}
add_action( 'pre_ping', 'no_self_ping' );
</code></pre>
<img src="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/info/okgo.php?ID=48355&HTTP_REFERER=RSS" alt="" width="1" height="1" loading="eager">]]></content:encoded>
					
					<wfw:commentRss>https://shkspr.mobi/blog/2023/10/displaying-internal-linkbacks-on-wordpress/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
			</item>
		<item>
		<title><![CDATA[Use WP CLI to find all blog posts without a featured image - two methods]]></title>
		<link>https://shkspr.mobi/blog/2023/10/use-wp-cli-to-find-all-blog-posts-without-a-featured-image-two-methods/</link>
					<comments>https://shkspr.mobi/blog/2023/10/use-wp-cli-to-find-all-blog-posts-without-a-featured-image-two-methods/#respond</comments>
				<dc:creator><![CDATA[@edent]]></dc:creator>
		<pubDate>Tue, 10 Oct 2023 11:34:15 +0000</pubDate>
				<category><![CDATA[/etc/]]></category>
		<category><![CDATA[blogging]]></category>
		<category><![CDATA[command line]]></category>
		<category><![CDATA[HowTo]]></category>
		<category><![CDATA[WordPress]]></category>
		<guid isPermaLink="false">https://shkspr.mobi/blog/?p=47413</guid>

					<description><![CDATA[This uses the wp shell command. It gives you an interactive prompt into which you can do various WordPress &#34;things&#34;.  One small annoyance is that it doesn&#039;t like multi-line entry. It treats every hit of the enter key as &#34;plz run the codez&#34; - so, at the end of this blog post, I&#039;ve put the commands in copy-n-paste format.  Once you&#039;ve installed WP CLIP, go to the command line and run wp shell.…]]></description>
										<content:encoded><![CDATA[<p>This uses <a href="https://developer.wordpress.org/cli/commands/shell/">the <code>wp shell</code> command</a>. It gives you an interactive prompt into which you can do various WordPress "things".</p>

<p>One small annoyance is that it doesn't like multi-line entry. It treats every hit of the enter key as "plz run the codez" - so, at the end of this blog post, I've put the commands in copy-n-paste format.</p>

<p>Once you've installed WP CLIP, go to the command line and run <code>wp shell</code>. You'll be greeted with an interactive prompt <code>wp&gt;</code></p>

<h2 id="method-one-quick-search"><a href="https://shkspr.mobi/blog/2023/10/use-wp-cli-to-find-all-blog-posts-without-a-featured-image-two-methods/#method-one-quick-search">Method One - Quick Search</a></h2>

<p>This command constructs a query which gets <em>all</em> posts, which have been published, where there is no thumbnail ID.  Note - this isn't quite the same as not having a featured image.</p>

<pre><code class="language-php">$args = array(
    'post_type'     =&gt; 'post',
    'post_status'   =&gt; array('publish'),
    'posts_per_page'=&gt; -1,
    'meta_query'    =&gt; array(
        array(
            'key' =&gt; '_thumbnail_id',
            'compare' =&gt; 'NOT EXISTS'
        )
    ),
);
</code></pre>

<p>Next we run that query. It will dump quite a lot of information into the screen, we'll format it shortly.</p>

<pre><code class="language-php">$query = new WP_Query( $args );
</code></pre>

<p>Finally, we loop through all the posts the query has found and print them out in a nice format.</p>

<pre><code class="language-php">$posts = $query-&gt;posts;
foreach ( $posts as $post ) {
    echo $post-&gt;post_date . " " . $post-&gt;guid . " " . $post-&gt;post_title . "\n";
}
</code></pre>

<p>That will give you the date, link, and title of every post where there is no featured image set.</p>

<h3 id="copy-and-paste-snippet"><a href="https://shkspr.mobi/blog/2023/10/use-wp-cli-to-find-all-blog-posts-without-a-featured-image-two-methods/#copy-and-paste-snippet">Copy and Paste Snippet</a></h3>

<pre><code class="language-php">$args = array( 'post_type' =&gt; 'post', 'post_status' =&gt; array('publish'), 'posts_per_page'=&gt; -1,  'meta_query' =&gt; array(       array( 'key' =&gt; '_thumbnail_id', 'value' =&gt; '?', 'compare' =&gt; 'NOT EXISTS' ) ),);
$query = new WP_Query( $args );
$posts = $query-&gt;posts;
foreach ( $posts as $post ) { echo $post-&gt;post_date . " " . $post-&gt;guid . " " . $post-&gt;post_title . "\n"; }
</code></pre>

<h2 id="method-two-full-search"><a href="https://shkspr.mobi/blog/2023/10/use-wp-cli-to-find-all-blog-posts-without-a-featured-image-two-methods/#method-two-full-search">Method Two - Full Search</a></h2>

<p>Sometimes the WordPress database can get a little confused. It will say there is a post thumbnail, but when you try to retrieve it, there's nothing there!</p>

<p>This method is slightly slower if you have lots of posts. It goes through every single post and checks whether the featured image can be retrieved. If not it will let you know.</p>

<pre><code class="language-php">$args = array(
    'post_type'     =&gt; 'post',
    'post_status'   =&gt; array('publish'),
    'posts_per_page'=&gt; -1,
);

$posts = get_posts( $args );

foreach ( $posts =&gt; $post) {
    if( get_the_post_thumbnail( $post ) == "" ) {
        echo $post-&gt;post_date . " " . $post-&gt;guid . " " . $post-&gt;post_title . "\n"; }
}
</code></pre>

<h3 id="copy-and-paste-snippet"><a href="https://shkspr.mobi/blog/2023/10/use-wp-cli-to-find-all-blog-posts-without-a-featured-image-two-methods/#copy-and-paste-snippet">Copy and Paste Snippet</a></h3>

<pre><code class="language-php">foreach (get_posts( array( 'post_type' =&gt; 'post', 'post_status' =&gt; array('publish'), 'posts_per_page' =&gt; -1,) ) as $post) { if(get_the_post_thumbnail($post)== "") { echo $post-&gt;post_date . " " . $post-&gt;guid . " " . $post-&gt;post_title . "\n"; } }
</code></pre>
<img src="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/info/okgo.php?ID=47413&HTTP_REFERER=RSS" alt="" width="1" height="1" loading="eager">]]></content:encoded>
					
					<wfw:commentRss>https://shkspr.mobi/blog/2023/10/use-wp-cli-to-find-all-blog-posts-without-a-featured-image-two-methods/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
