<?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>Fri, 23 Jan 2026 07:21:24 +0000</lastBuildDate>
	<language>en-GB</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.9.4</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[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>2</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>
</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>22</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>
		<item>
		<title><![CDATA[The minimal-div minimal-span philosophy of this blog]]></title>
		<link>https://shkspr.mobi/blog/2023/10/the-minimal-div-minimal-span-philosophy-of-this-blog/</link>
					<comments>https://shkspr.mobi/blog/2023/10/the-minimal-div-minimal-span-philosophy-of-this-blog/#comments</comments>
				<dc:creator><![CDATA[@edent]]></dc:creator>
		<pubDate>Sun, 01 Oct 2023 11:34:42 +0000</pubDate>
				<category><![CDATA[/etc/]]></category>
		<category><![CDATA[blogging]]></category>
		<category><![CDATA[HTML]]></category>
		<guid isPermaLink="false">https://shkspr.mobi/blog/?p=47294</guid>

					<description><![CDATA[If you&#039;ve ever learned Mandarin Chinese, you&#039;ll know about &#34;measure words&#34;. They&#039;re the sort of thing that trip up all new learners of the language.  While 个 (gè) can be used as a generic measure word, using it everywhere makes you sound like an idiot (according to my old teacher). So you learn to use 个 for people, 包 for packets, and 根 for things which are long and thin.  English has a similar con…]]></description>
										<content:encoded><![CDATA[<p>If you've ever learned Mandarin Chinese, you'll know about "measure words". They're the sort of thing that trip up all new learners of the language.  While 个 (gè) can be used as a generic measure word, using it everywhere makes you sound like an idiot (according to my old teacher). So you learn to use 个 for people, 包 for packets, and 根 for things which are long and thin.</p>

<p>English has a similar construct. You might say "one <em>bunch</em> of flowers" or "two <em>glasses</em> of wine" or "three <em>bowls</em> of soup".</p>

<p>You <em>could</em> say "one <em>thing</em> of flowers" or "two <em>things</em> of wines" or "three <em>things</em> of soups" but the measure words give much needed context and semantics.</p>

<p>If you get it wrong and said to a publican "four mugs of beer please" they'd <em>probably</em> know what you meant but it could be a bit confusing.</p>

<p>And isn't that very much like HTML?</p>

<p>The language of the web gives us semantic elements for our markup. When you use <code>&lt;button&gt;</code> to draw a button on screen, the browser knows exactly what to expect, how to display the content, and what it should do.  A search engine can extract meaning from the page. Users of assistive technology can be told that they're on a button. Everything is lovely!</p>

<p>You don't have to do that, of course. You could use <code>&lt;div class="button" onclick="something()"&gt;</code> - with enough CSS and JS you'll have something which looks and acts more-or-less like a button. But you'll lose all the semantics which make life easier for browsers, search engines, assistive technologies, and anything else that a user uses to interact with your site.</p>

<p>HTML has <em>dozens</em> of semantic elements. There's <code>&lt;address&gt;</code> for contact details, <code>&lt;time&gt;</code> for dates and times, <code>&lt;nav&gt;</code> for navigation elements.</p>

<p>There are two main "generic" elements. <code>&lt;div&gt;</code> for blocks of stuff, and <code>&lt;span&gt;</code> for a short run of text.  I find that most modern websites over-use these elements. I want to reiterate, there's nothing illegal or immoral about doing so; the web police aren't going to take you to gaol.  I personally think that writing semantic HTML is easier to maintain, easier to understand, easier for accessibility, and easier for automatically extracting meaning.</p>

<p>So, for a while now, I've been slowly working on my blog's theme in order to remove as many <code>&lt;div&gt;</code>s and <code>&lt;span&gt;</code>s as possible. I started out with a couple of hundred of each. I'm now down to about 35 of each - depending on which page you're on.</p>

<p>Here are some of the problems I found.</p>

<h2 id="semantic-wrapping-is-complicated"><a href="https://shkspr.mobi/blog/2023/10/the-minimal-div-minimal-span-philosophy-of-this-blog/#semantic-wrapping-is-complicated">Semantic wrapping is complicated</a></h2>

<p>I use <a href="https://schema.org/">Schema.org</a> microdata in my HTML. For example, when a user has left a comment, I might indicate their name by using <code>&lt;span itemprop="name"&gt;Juliet Capulet&lt;/span&gt;</code></p>

<p>Or, in the heading of a post I might use</p>

<pre><code class="language-html">This post has
&lt;span itemprop="https://schema.org/commentCount"&gt;3&lt;/span&gt; comments and is
&lt;span itemprop="https://schema.org/wordCount"&gt;400&lt;/span&gt; words long
</code></pre>

<p>Because there's no HTML element like <code>&lt;commentcount&gt;</code> or <code>&lt;wordcount&gt;</code>, I have to wrap those numbers in <em>something</em> if I want to indicate the semantic content behind them.  I could "cheat" and use something like <code>&lt;var&gt;</code> or <code>&lt;output&gt;</code> but they're as semantically irrelevant as <code>&lt;span&gt;</code>.</p>

<p>Similarly, it's hard to do <a href="https://shkspr.mobi/blog/2022/04/semantic-comments-for-wordpress/">semantic comments for WordPress</a>.</p>

<h2 id="some-things-are-just-things"><a href="https://shkspr.mobi/blog/2023/10/the-minimal-div-minimal-span-philosophy-of-this-blog/#some-things-are-just-things">Some things are just things</a></h2>

<p>I have a large calendar at the bottom of every page showing my archives. Each calendar is its own "thing" and so is wrapped in a <code>&lt;div&gt;</code> which controls its style and layout. That group of calendars is also its own thing - <a href="https://front-end.social/@mia/111025056866652533">because <code>&lt;details&gt;</code> elements are weird</a>.</p>

<p>They're inside a widget - which itself is inside of an <code>&lt;aside&gt;</code>.</p>

<p>I was using a <code>&lt;table&gt;</code> layout for them, but it wasn't flexible and it ballooned the size of the DOM. Perhaps I should treat them as lists? At least then they'd be easier to skip?</p>

<h2 id="wordpress-defaults"><a href="https://shkspr.mobi/blog/2023/10/the-minimal-div-minimal-span-philosophy-of-this-blog/#wordpress-defaults">WordPress defaults</a></h2>

<p>All built in widgets in WordPress take the following form:</p>

<pre><code class="language-html">&lt;h2 class="widget-title"&gt;...&lt;/h2&gt;
&lt;div&gt;
   ...
&lt;/div&gt;
</code></pre>

<p>I don't think they <em>need</em> that extra <code>&lt;div&gt;</code>, although I can see why it might be necessary for styling.</p>

<p>Similarly, <a href="https://developer.wordpress.org/reference/functions/wp_nav_menu/"><code>wp_nav_menu()</code></a> is encased in a <code>&lt;div&gt;</code> by default - although that can be removed.</p>

<h2 id="whats-next"><a href="https://shkspr.mobi/blog/2023/10/the-minimal-div-minimal-span-philosophy-of-this-blog/#whats-next">What's Next?</a></h2>

<p>I'm going to continue hacking away out of a sense of masochism. Perhaps the only person who will notice (other than me) is someone accidentally viewing the source of this page.</p>

<!-- MADE YOU LOOK! -->

<p>But I think it's worth it. There's that <a href="https://www.cnbc.com/2018/05/10/how-steve-jobs-developed-his-design-philosophy-for-apple.html">story about how the original Mac circuit board was laid out</a> so that, despite never being seen by normal people, it was part of the overall æsthetic.  And I think that's what I'm going for - something that I can be satisfied with.</p>
<img src="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/info/okgo.php?ID=47294&HTTP_REFERER=RSS" alt="" width="1" height="1" loading="eager">]]></content:encoded>
					
					<wfw:commentRss>https://shkspr.mobi/blog/2023/10/the-minimal-div-minimal-span-philosophy-of-this-blog/feed/</wfw:commentRss>
			<slash:comments>8</slash:comments>
		
		
			</item>
		<item>
		<title><![CDATA[Who reads my blog?]]></title>
		<link>https://shkspr.mobi/blog/2023/09/who-reads-my-blog/</link>
					<comments>https://shkspr.mobi/blog/2023/09/who-reads-my-blog/#comments</comments>
				<dc:creator><![CDATA[@edent]]></dc:creator>
		<pubDate>Sun, 10 Sep 2023 11:34:41 +0000</pubDate>
				<category><![CDATA[/etc/]]></category>
		<category><![CDATA[blogging]]></category>
		<category><![CDATA[meta]]></category>
		<guid isPermaLink="false">https://shkspr.mobi/blog/?p=46751</guid>

					<description><![CDATA[Hello! Thank you for reading what I write. Sorry to ask, but… who are you?  I was chatting to a friend about what it is like running a blog, finding new topics, keeping up with a daily schedule, moderating comments, etc. And they asked, quite reasonably, &#34;who are your readers?&#34;  And, honestly, I have very little idea! The only analytics I have on here is basic WordPress JetPack statistics. I can s…]]></description>
										<content:encoded><![CDATA[<p>Hello! Thank you for reading what I write. Sorry to ask, but… <em>who are you?</em></p>

<p>I was chatting to a friend about what it is like running a blog, finding new topics, keeping up with a daily schedule, moderating comments, etc. And they asked, quite reasonably, "who are your readers?"</p>

<p>And, honestly, I have very little idea! The only analytics I have on here is basic WordPress JetPack statistics. I can see which posts are popular. I get a sense of which countries you are in. If you leave a comment, I know the name you choose to give. If you link back to my posts, I know what your website is.</p>

<p>But, that's pretty much it. I don't know your age, height, gender, dietary needs, or the date of your last colonoscopy. Frankly, that's the way I like it! I write, you read. You don't have to pay me in fiat, crypto, or your personal data.</p>

<p>It is <em>nice</em> if you subscribe and/or share my posts. But there's never going to be a big pop-up asking for your email address, or trying to make you sign in with a social network. I don't need to know <em>anything</em> about you.</p>

<p>And yet… I'm curious!</p>

<p>About 500 of you subscribe by email. A few thousand of you subscribe by RSS. Even more come via links from social media.</p>

<p>If you're comfortable, I would love it if you <a href="https://shkspr.mobi/blog/2023/09/who-reads-my-blog/#reply-title">dropped a note in the comments of this post</a>.
You can either just say "hello", or you can tell me why you read, or what you like, or what you'd like to see more of. Leave a link to your blog, if you have one. Or just say what part of the world you're from.</p>

<p>So, who are you?</p>
<img src="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/info/okgo.php?ID=46751&HTTP_REFERER=RSS" alt="" width="1" height="1" loading="eager">]]></content:encoded>
					
					<wfw:commentRss>https://shkspr.mobi/blog/2023/09/who-reads-my-blog/feed/</wfw:commentRss>
			<slash:comments>144</slash:comments>
		
		
			</item>
		<item>
		<title><![CDATA[Build your own "On This Day" page for WordPress]]></title>
		<link>https://shkspr.mobi/blog/2023/07/build-your-own-on-this-day-page-for-wordpress/</link>
					<comments>https://shkspr.mobi/blog/2023/07/build-your-own-on-this-day-page-for-wordpress/#comments</comments>
				<dc:creator><![CDATA[@edent]]></dc:creator>
		<pubDate>Wed, 12 Jul 2023 11:34:10 +0000</pubDate>
				<category><![CDATA[/etc/]]></category>
		<category><![CDATA[blogging]]></category>
		<category><![CDATA[HowTo]]></category>
		<category><![CDATA[php]]></category>
		<category><![CDATA[plugin]]></category>
		<category><![CDATA[WordPress]]></category>
		<guid isPermaLink="false">https://shkspr.mobi/blog/?p=46182</guid>

					<description><![CDATA[I blog. A lot. Too much really. One of the things I like to do is see what I was rambling on about this time last year. And the year before that. And so on.  So, here&#039;s my On This Day page and here&#039;s how I built it.  WARNING Extremely quick and dirty code ahead!  This allows you to add a shortcode like [ edent_on_this_day ] to a page and have it auto generate a list of posts you published on this …]]></description>
										<content:encoded><![CDATA[<p>I blog. A <em>lot</em>. Too much really. One of the things I like to do is see what I was rambling on about this time last year. And the year before that. And so on.</p>

<p>So, here's my <a href="https://shkspr.mobi/blog/on-this-day/">On This Day page</a> and here's how I built it.</p>

<p><strong>WARNING</strong> Extremely quick and dirty code ahead!</p>

<p>This allows you to add a shortcode like <code>[ edent_on_this_day ]</code> to a page and have it auto generate a list of posts you published on this day in previous years.  You may need to exclude that page from your cache.</p>

<p>Add these functions to your theme or to a new plugin:</p>

<pre><code class="language-php">function edent_on_this_day_shortcode() {
    $today = getdate();
    $args = array(
        'date_query' =&gt; array(
            array(
                'month' =&gt; $today['mon'],
                'day'   =&gt; $today['mday'],
            ),
        ),
    );
    $query = new WP_Query( $args );
    $posts = $query-&gt;get_posts();

    $today = getdate();
    $pubDate =  date("D, d M Y") . " 00:00:00 GMT";

    $output  = "&lt;h2&gt;From the " . date("jS \of F") . " archives&lt;/h2&gt;";
    $output .= "&lt;ul&gt;";

    foreach($posts as $post) {
        $title = $post-&gt;post_title;
        $id    = $post-&gt;ID;
        $link  = get_permalink($id);
        $date  = $post-&gt;post_date;
        $postDate = date("D, d M Y") . " " . date("h:i:s O", strtotime($date));
        // $thumb = get_the_post_thumbnail($id, 'full');

        $archive = "" . date("Y", strtotime($date)) . ": ";

        //  Only add an item if it is before *this year*
        if (
            intval(date("Y", strtotime($date))) &lt;  intval($today['year'])
        ) {
            $output .= '&lt;li&gt;&lt;a href="' . htmlspecialchars($link) .'"&gt;';
            $output .= html_entity_decode($archive . $title) . '&lt;/a&gt;&lt;/li&gt;';
        }
    }
    $output .= "&lt;/ul&gt;";
    return $output;
}

//  Set up the shortcode
function edent_on_this_day_shortcode_init() {
    add_shortcode( 'edent_on_this_day', 'edent_on_this_day_shortcode' );
}
add_action( 'init', 'edent_on_this_day_shortcode_init' );
</code></pre>

<p>I really can't be bothered to deal with WordPress's complicated plugin publishing system - so feel free to copy the above with or without attribution.</p>

<p>I originally built this as an RSS feed - but decided recently that a regular HTML page was more useful. If you spot any bugs, <a href="https://github.com/edent/WordPress-On-This-Day-Plugin">you can contribute on GitHub</a>.</p>

<p>Enjoy!</p>
<img src="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/info/okgo.php?ID=46182&HTTP_REFERER=RSS" alt="" width="1" height="1" loading="eager">]]></content:encoded>
					
					<wfw:commentRss>https://shkspr.mobi/blog/2023/07/build-your-own-on-this-day-page-for-wordpress/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
			</item>
	</channel>
</rss>
