From 69d7cd1da644b43d1768fa0a2ff78a0f83f2b3c3 Mon Sep 17 00:00:00 2001 From: Nathan Upchurch Date: Sun, 4 Feb 2024 19:36:44 -0600 Subject: [PATCH] Implement mastodon comments --- _data/metadata.js | 2 + _includes/footer.njk | 7 +- _includes/layouts/post.njk | 5 +- _includes/mastodonComments.njk | 94 ++++++++++++++++++ _includes/metadata.njk | 3 +- content/blog/cowsay_2024-01-02.md | 1 + content/blog/let-us-waffle.md | 1 + content/blog/new-kmines-themes.md | 1 + content/blog/offline.md | 1 + content/blog/patience.md | 1 + content/blog/underrated-apps-qownnotes.md | 1 + ...an-and-alternative-diets-in-foodservice.md | 1 + content/index.njk | 2 +- eleventy.config.js | 2 + public/css/dropcap.css | 2 +- public/css/index.css | 99 ++++++++++++++++--- public/js/main.js | 4 + public/js/webComponents/card.js | 33 +++++++ public/js/webComponents/comment.js | 76 ++++++++++++++ public/js/webComponents/profilePic.js | 40 ++++++++ 20 files changed, 354 insertions(+), 22 deletions(-) create mode 100644 _includes/mastodonComments.njk create mode 100644 public/js/main.js create mode 100644 public/js/webComponents/card.js create mode 100644 public/js/webComponents/comment.js create mode 100644 public/js/webComponents/profilePic.js diff --git a/_data/metadata.js b/_data/metadata.js index f77f534..65a3f6a 100644 --- a/_data/metadata.js +++ b/_data/metadata.js @@ -13,6 +13,8 @@ module.exports = { copyrightNotice: "© Nathan Upchurch 2022 - 2024", defaultPostImageURL: "/img/vasilina-sirotina-1NMPvajSt9Q-unsplash_copy.avif", defaultPostImageAlt: "The default post image: a close picture of the dark green leaves of a plant.", + mastodonHost: "lounge.town", + mastodonUser: "nathanu", postlistHeaderText: "Latest Posts", socialLinks: [ { diff --git a/_includes/footer.njk b/_includes/footer.njk index ec14a44..0786d35 100644 --- a/_includes/footer.njk +++ b/_includes/footer.njk @@ -1,12 +1,13 @@ diff --git a/_includes/layouts/post.njk b/_includes/layouts/post.njk index 8b1d6cc..3f0c42b 100644 --- a/_includes/layouts/post.njk +++ b/_includes/layouts/post.njk @@ -3,6 +3,7 @@ layout: layouts/base.njk --- {# Only include the syntax highlighter CSS on blog posts #} {%- css %}{% include "public/css/code.css" %}{% endcss %} +

{{ title }}

{{ content | safe }} - +
+{% include "mastodonComments.njk" %} +

Read Next

{% include "nextLast.njk" %} diff --git a/_includes/mastodonComments.njk b/_includes/mastodonComments.njk new file mode 100644 index 0000000..5cb75ec --- /dev/null +++ b/_includes/mastodonComments.njk @@ -0,0 +1,94 @@ +{% if mastodon_id %} +
+

Comments

+
+
+

Loading comments...

+
+
+ + + + +{% endif %} diff --git a/_includes/metadata.njk b/_includes/metadata.njk index d7c47b9..ffca936 100644 --- a/_includes/metadata.njk +++ b/_includes/metadata.njk @@ -4,6 +4,7 @@ + - + diff --git a/content/blog/cowsay_2024-01-02.md b/content/blog/cowsay_2024-01-02.md index 0dca51b..0afb71b 100644 --- a/content/blog/cowsay_2024-01-02.md +++ b/content/blog/cowsay_2024-01-02.md @@ -7,6 +7,7 @@ tags: synopsis: An ASCII cow postulates on the state of science education in the modern world. imageURL: /img/cowsayOfTheDay.avif imageAlt: An ASCII cow with a thought bubble containing the word wut +mastodon_id: "111688829907363670" --- As a big-old nerd, I spend a lot of time in the terminal on my computer. When you spend a lot of time somewhere, you want it to be comfortable. As a part of making my terminal more homey, I've set it up to give me a random quote each time I start a new session, delivered, of course, by a cow. Here's today's cowsay of the day: diff --git a/content/blog/let-us-waffle.md b/content/blog/let-us-waffle.md index b4c58a8..5de3ff5 100644 --- a/content/blog/let-us-waffle.md +++ b/content/blog/let-us-waffle.md @@ -8,6 +8,7 @@ tags: synopsis: Tools like cooked.wiki let us strip away the cruft from online recipes. Is this necessarily a good thing? imageURL: /img/pexels-brigitte-tohm-378008_compressed.webp imageAlt: An oddly rectangular waffle covered in raspberries. It actually looks quite dry and not very nice. Hopefully there's some syrup on the side! +mastodon_id: "111812478768090324" --- So, about this [cooked.wiki](https://cooked.wiki) thing, believe me when I say I take my fair share in our collective frustration as I find myself skimming through a hugoesque tome on Brayden and Braxlynne’s wiggly teeth in order to reach the ingredients for “Keighleigh’s Extra Easy No-Bake Ten Minute Palmiers (*So Delicious You’ll Snort the Crumbs!*),” but I must admit that the endless complaining about it puts me out a bit. Here’s the thing; as someone who writes for his own personal blog, who plans to someday publish a recipe or two, *the waffling is the point.* diff --git a/content/blog/new-kmines-themes.md b/content/blog/new-kmines-themes.md index 9421892..5e85d32 100644 --- a/content/blog/new-kmines-themes.md +++ b/content/blog/new-kmines-themes.md @@ -8,6 +8,7 @@ tags: synopsis: My first KDE contribution! Two new high-contrast KMines themes that will arrive with Plasma 6. imageURL: /img/kmines_dark.webp imageAlt: A screenshot of the KMines game window showing a new dark theme. +mastodon_id: "111794936518292495" --- ## Why KMines? Minesweeper is a tragically underrated puzzle game. While I recall examining the mysterious array of gray squares as a child, it wasn't until adulthood that I took the time to learn the rules of the game. Despite my late start, however, I still count minesweeper as a classic. These days, good minesweeper clones are hard to come by. I settled on GNOME's [Mines](https://wiki.gnome.org/Apps/Mines) for a while, but as the look of GTK applications on my QT-based [KDE Plasma Desktop](https://kde.org/plasma-desktop/) sets my teeth on edge, I ditched it for [KMines](https://apps.kde.org/kmines/) in short order. While I enjoyed the game, I found the themes shipped with KMines a bit dated, so I thought I'd make my own. diff --git a/content/blog/offline.md b/content/blog/offline.md index 9806b02..69643a5 100644 --- a/content/blog/offline.md +++ b/content/blog/offline.md @@ -7,6 +7,7 @@ tags: synopsis: A conversation with a colleague caused me to consider the consequences of online-only tooling. imageURL: /img/kenny-eliason-uq5RMAZdZG4-unsplash.webp imageAlt: A server rack in the dark with colorful cables draped between the ports of servers and switches. +mastodon_id: "111268603361637013" --- As part of a project investigating a potential new piece of software, I've been speaking with colleagues and contractors to determine which features they rely on to do their day-to-day tasks, as well as discover any wish-list items for a new platform. In one of these discussions with a colleague, we had covered her relatively simple use case, and moved on to discuss potential features that might be useful. At this juncture, she mentioned, somewhat apologetically, that should a particular workflow be translated to a new platform, it was important to be able to access data and documents offline. diff --git a/content/blog/patience.md b/content/blog/patience.md index 1049a61..d9ca127 100644 --- a/content/blog/patience.md +++ b/content/blog/patience.md @@ -8,6 +8,7 @@ tags: synopsis: Learning about patience through an incense-making miscalculation. imageURL: /img/dragons_blood_incense_copy.avif imageAlt: A small piece of a coreless, Japanese-style incense stick burning in a black cast-iron burner. +mastodon_id: "111732713202024407" --- Some time ago, maybe a year or so, I extruded a batch of incense sticks from some ingredients I thought might go well together: sandalwood, cinnamon, dragon's blood resin, a touch of Hojari frankincense for acidity, and some tonka bean for sweetness, if I recall correctly. After leaving the sticks to dry overnight, I was disappointed to see that they didn't stay lit; the stick would shrink behind the ember, and it would fizzle out in short order. Even worse, the little scent I was able to detect during the short burn was terrible: acrid and smoky. Dejected, I put the sticks away, returning to attempt to burn a small fragment every few days or so before I lost interest entirely. A few months later, the tube of crooked red incense sticks caught my eye, and I once again attempted to burn a stick. To my surprise, it stayed lit throughout the entire burn. The fragrance had transformed also, from leafy-campfire to a simple, warm, slightly sweet, and medicinal fragrance. While this was enough of an improvement to encourage me to light one every now and then, I remained disappointed that the fragrance was so far from what I'd hoped to achieve. After half-heartedly burning each stick in the little plastic tube that housed them over a period of weeks, the tube disappeared into a basket on the shelf beneath my coffee table amidst a mess of bundled cables and game-controllers, never to be seen again – until just a few days ago. diff --git a/content/blog/underrated-apps-qownnotes.md b/content/blog/underrated-apps-qownnotes.md index 0420d90..08ec2fc 100644 --- a/content/blog/underrated-apps-qownnotes.md +++ b/content/blog/underrated-apps-qownnotes.md @@ -8,6 +8,7 @@ tags: - Underrated Apps imageURL: /img/qownnotes.webp imageAlt: A screenshot of QOwnNotes showing a note subfolder panel beside markdown editor and preview panels. +mastodon_id: "110862579682916657" --- [![A screenshot of QOwnNotes showing a note subfolder panel beside markdown editor and preview panels.](/img/qownnotes.webp "QOwnNotes running on EndeavourOS / KDE Plasma")](/img/qownnotes.webp) diff --git a/content/blog/vegan-and-alternative-diets-in-foodservice.md b/content/blog/vegan-and-alternative-diets-in-foodservice.md index 3e214e4..8c32eb2 100644 --- a/content/blog/vegan-and-alternative-diets-in-foodservice.md +++ b/content/blog/vegan-and-alternative-diets-in-foodservice.md @@ -8,6 +8,7 @@ tags: synopsis: Breaking down the alternative-diet restaurant experience to offer some perspective and advice to foodservice professionals and proprietors. imageURL: /img/k8-sWEpcc0Rm0U-unsplash.webp imageAlt: An overhead view of a restaurant interior showing guests sitting at tables with white tablecloths, eating pastries. +mastodon_id: "111173763912666764" --- I've been a vegan for close to a decade. From washing dishes, slinging cocktails, and pouring latte art, to recipe development, hiring, compliance, and multi-location operations, I've also been around the block a few times when it comes to foodservice. So when I tell you that the vast majority of foodservice establishments of any stripe are utter and complete complete nightmares for people with alternative diets, I hope you'll take me at my word. diff --git a/content/index.njk b/content/index.njk index c1747ff..b612c50 100644 --- a/content/index.njk +++ b/content/index.njk @@ -13,5 +13,5 @@ numberOfLatestPostsToShow: 5 {% set morePosts = postsCount - numberOfLatestPostsToShow %} {% if morePosts > 0 %} -

See {{ morePosts }} more post{% if morePosts != 1 %}s{% endif %} in the blog.

+See {{ morePosts }} more post{% if morePosts != 1 %}s{% endif %} in the blog » {% endif %} diff --git a/eleventy.config.js b/eleventy.config.js index ca0cb07..abd0344 100644 --- a/eleventy.config.js +++ b/eleventy.config.js @@ -78,6 +78,8 @@ module.exports = function(eleventyConfig) { eleventyConfig.addPassthroughCopy({ 'public/xsl/*': "/xsl/" }); eleventyConfig.addPassthroughCopy({ 'public/img/*': "/img/" }); eleventyConfig.addPassthroughCopy({ 'public/robots.txt': "/" }); + eleventyConfig.addPassthroughCopy({ 'public/js/*': "/js/" }); + eleventyConfig.addPassthroughCopy({ 'public/js/webComponents/*': "/js/webComponents" }); // Copying so that basic.xsl can use it eleventyConfig.addPassthroughCopy({ 'public/css/index.css': "/css/index.css" }); eleventyConfig.addPassthroughCopy({ 'public/css/webfonts/*': "/css/webfonts/" }); diff --git a/public/css/dropcap.css b/public/css/dropcap.css index de171b4..7723a5a 100644 --- a/public/css/dropcap.css +++ b/public/css/dropcap.css @@ -1,4 +1,4 @@ -main > p:not(.nodropcap):first-of-type:first-letter { +main > article > p:not(.nodropcap):first-of-type:first-letter { float: left; font-size: 4rem; padding: .5rem .5rem .5rem .5rem; diff --git a/public/css/index.css b/public/css/index.css index f21b120..ca20f74 100644 --- a/public/css/index.css +++ b/public/css/index.css @@ -24,7 +24,7 @@ --icon-filter: none; /* Corners */ - --corner-radius: .3rem; + --border-radius: .3rem; /* Space & Size */ --syntax-tab-size: 2; @@ -32,6 +32,7 @@ --single-gap: 1rem; --double-gap: 2rem; --triple-gap: 3rem; + --quad-gap: 4rem; /* Transitions */ --transition-normal: all .3s; @@ -49,11 +50,28 @@ --weight-heavy: 500; --weight-normal: 300; + /* Links */ + --link-decoration-thickness: .1rem; + /* Borders */ --border-nav: 1px solid var(--text-color); --border-nav-currentpage: 20px solid var(--contrast-color); --border-nav-hover: 20px solid var(--text-color); --border-thin: 1px solid var(--color-gray-20); + + /* Shadow */ + --box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2); + + /* Components */ + --wc-card-background-color: var(--card-color); + --wc-card-border-radius: var(--border-radius); + --wc-card-box-shadow: var(--box-shadow); + --wc-link-color: var(--text-color); + --wc-link-decoration-color: var(--contrast-color); + --wc-link-decoration-thickness: var(--link-decoration-thickness); + --wc-comment-text-margin: auto auto auto 4rem; + --wc-profile-pic-size: 3rem; + --wc-profile-pic-border-radius: 10rem; } @media (prefers-color-scheme: dark) { @@ -64,7 +82,7 @@ --contrast-color: #04c49e; /* --text-color is assigned to --color-gray-_ above */ - --text-color-link: var(--contrast-color); + --text-color-link: var(--text-color); --background-color: #15202b; --logo-filter: none; @@ -96,6 +114,7 @@ body { background-color: var(--background-color); color: var(--text-color); font-family: var(--font-family); + font-size: 13px; font-variant-Ligatures: normal; font-weight: var(--weight-normal); margin: 0 auto; @@ -109,7 +128,7 @@ body { } a { text-decoration-color: var(--contrast-color); - text-decoration-thickness: .1rem; + text-decoration-thickness: var(--link-decoration-thickness); transition: var(--transition-normal); } /* https://www.a11yproject.com/posts/how-to-hide-content/ */ @@ -123,8 +142,18 @@ a { width: 1px; } footer { + margin-top: var(--triple-gap); padding: var(--single-gap); - border-top: var(--border-thin); +} +footer .copyright-notice { + padding-right: var(--single-gap); +} +footer .webring { + display: inline-block; + padding-right: var(--single-gap); +} +footer p { + font-size: var(--font-s); } h1, h2, h3 { color: var(--text-color); @@ -140,7 +169,7 @@ h1 { h2 { font-size: var(--font-xl); font-weight: var(--weight-extraheavy); - margin-top: var(--double-gap); + margin: var(--quad-gap) auto 0 auto; } h3 { font-size: var(--font-l); @@ -167,7 +196,7 @@ p, li { } figure { margin: 0; - padding: var(--single-gap) 0 var(--single-gap) 0; + padding: var(--single-gap) 0 0 0; width: 100%; } figure > a > img { @@ -182,6 +211,17 @@ figcaption { .page-block { margin-bottom: var(--triple-gap); } +.big-link { + width: 100%; + padding: var(--half-gap); + border: var(--border-nav); + border-radius: var(--border-radius); + margin: var(--single-gap) auto var(--single-gap) auto; + transition: var(--transition-normal); +} +.big-link:hover { + border-color: var(--contrast-color); +} a[href]:not(.icon-button) { color: var(--text-color-link); } @@ -194,9 +234,8 @@ a[href]:active:not(.icon-button) { } .links-nextprev { list-style: none; - border-top: var(--border-thin); - padding: var(--triple-gap) 0 var(--single-gap) 0; - margin-top: var(--triple-gap); + padding: 0 0 var(--single-gap) 0; + margin-top: var(--single-gap); } table { @@ -207,6 +246,27 @@ table th { padding-right: 1em; } +/* Comments */ +.comment-ingress { + margin-bottom: var(--double-gap); +} +#comment-section h2 { + margin: var(--quad-gap) auto 0 auto; +} +wc-comment::part(author-link) { + font-size: var(--font-n); + font-weight: var(--weight-extraheavy); + text-decoration: none; +} +wc-comment::part(main) { + margin-bottom: var(--double-gap); +} +wc-comment::part(publish-date) { + font-weight: var(--weight-heavy); + font-size: var(--font-s); + margin-top: -.25rem; +} + /* Code Fences */ pre, code { @@ -233,7 +293,6 @@ code { /* Header */ header { align-items: end; - border-bottom: var(--border-thin); display: flex; flex-wrap: wrap; gap: 1em .5em; @@ -331,17 +390,17 @@ nav ul { .postlist-item { align-items: flex-start; background-color: var(--card-color); - border-radius: var(--corner-radius); + border-radius: var(--border-radius); box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2); display: flex; flex-flow: row nowrap; justify-content: flex-start; - margin-bottom: 1em; + margin-bottom: var(--double-gap); padding: var(--single-gap) 1.1rem var(--single-gap) 1.1rem; width: 100%; } .post-image-container { - border-radius: var(--corner-radius); + border-radius: var(--border-radius); margin-right: var(--single-gap); max-height: 15rem; overflow: hidden; @@ -421,9 +480,14 @@ a.post-tag:hover { align-self: center; } +/* Article / Post */ +.post h2 { + font-size: var(--font-l); +} + /* Post Metadata */ .post-metadata { - margin-bottom: var(--triple-gap); + margin-bottom: var(--double-gap); margin-top: var(--single-gap); padding: 0 0 0 .4rem; } @@ -481,7 +545,7 @@ h2 + .header-anchor { margin-bottom: var(--single-gap); } h3, .post-copy a h3 { - font-size: 1rem; + font-size: 1.25rem; } /* Header */ @@ -494,6 +558,11 @@ h2 + .header-anchor { margin-top: var(--single-gap); } + /* Footer */ + footer .webring { + display: block; + } + /* Nav */ .nav { flex-flow: row wrap; diff --git a/public/js/main.js b/public/js/main.js new file mode 100644 index 0000000..9b2097c --- /dev/null +++ b/public/js/main.js @@ -0,0 +1,4 @@ +import './webComponents/card.js'; +import './webComponents/profilePic.js'; +import './webComponents/speechBubble.js'; +import './webComponents/comment.js'; diff --git a/public/js/webComponents/card.js b/public/js/webComponents/card.js new file mode 100644 index 0000000..36c58c6 --- /dev/null +++ b/public/js/webComponents/card.js @@ -0,0 +1,33 @@ +const template = document.createElement('template'); + +template.innerHTML = ` + + +
+ +
+` + +class card extends HTMLElement { + constructor() { + super(); + + this._shadowRoot = this.attachShadow({ 'mode': 'open' }); + this._shadowRoot.appendChild(template.content.cloneNode(true)); + } +} + +window.customElements.define('wc-card', card); diff --git a/public/js/webComponents/comment.js b/public/js/webComponents/comment.js new file mode 100644 index 0000000..2befd81 --- /dev/null +++ b/public/js/webComponents/comment.js @@ -0,0 +1,76 @@ +const template = document.createElement('template'); + +template.innerHTML = ` + + +
+
+
+ +
+
+

+ says: +

+

+
+
+
+
+
+` + +class comment extends HTMLElement { + constructor() { + super(); + + this._shadowRoot = this.attachShadow({ 'mode': 'open' }); + this._shadowRoot.appendChild(template.content.cloneNode(true)); + this.$comment = this._shadowRoot.querySelector('#commentContainer'); + } + + static get observedAttributes() { + return ['author_name', 'author_url', 'avatar_url', 'comment_content', 'publish_date']; + } + + attributeChangedCallback(name, oldVal, newVal) { + if (oldVal != newVal) { + this[name] = newVal; + this.render(); + } + } + + render() { + this.$comment.querySelector('#author-link').innerHTML = this.author_name; + this.$comment.querySelector('#author-link').href = this.author_url; + this.$comment.querySelector('wc-profile-pic').setAttribute('url', this.avatar_url) + this.$comment.querySelector('#comment').innerHTML = this.comment_content; + this.$comment.querySelector('#publish-date').innerHTML = this.publish_date; + } +} + +window.customElements.define('wc-comment', comment); diff --git a/public/js/webComponents/profilePic.js b/public/js/webComponents/profilePic.js new file mode 100644 index 0000000..76b42ee --- /dev/null +++ b/public/js/webComponents/profilePic.js @@ -0,0 +1,40 @@ +const template = document.createElement('template'); + +template.innerHTML = ` + + + +` + +class profilePic extends HTMLElement { + constructor() { + super(); + + this._shadowRoot = this.attachShadow({ 'mode': 'open' }); + this._shadowRoot.appendChild(template.content.cloneNode(true)); + this.$profilePic = this._shadowRoot.querySelector('#profilePic'); + } + + static get observedAttributes() { + return ['url']; + } + + attributeChangedCallback(name, oldVal, newVal) { + if (oldVal != newVal) { + this[name] = newVal; + this.render(); + } + } + + render() { + this.url ? this.$profilePic.src = this.url : null; + } +} + +window.customElements.define('wc-profile-pic', profilePic);