Skip to content

Commit 8446a0c

Browse files
authored
Add RSS feed for notes (#44)
I want to add media log updates into the feed as well but can do that separate
1 parent d7194f7 commit 8446a0c

File tree

18 files changed

+468
-39
lines changed

18 files changed

+468
-39
lines changed

app/Livewire/Notes/NotesIndexPage.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ class NotesIndexPage extends Component
1515
public function render(): View|Factory
1616
{
1717
return view('livewire.notes.notes-index-page', [
18-
'notes' => Note::where('visible', true)->orderBy('published_at', 'desc')->simplePaginate(100),
18+
'notes' => Note::where('visible', true)->orderBy('published_at', 'desc')->simplePaginate(1000),
1919
]);
2020
}
2121
}

app/Models/Note.php

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@
55
use Illuminate\Database\Eloquent\Factories\HasFactory;
66
use Illuminate\Database\Eloquent\Model;
77
use Illuminate\Support\Str;
8+
use Spatie\Feed\Feedable;
9+
use Spatie\Feed\FeedItem;
810

9-
class Note extends Model
11+
class Note extends Model implements Feedable
1012
{
1113
use HasFactory;
1214

@@ -15,7 +17,7 @@ class Note extends Model
1517
'title',
1618
'lead',
1719
'content',
18-
'hidden',
20+
'visible',
1921
'published_at',
2022
];
2123

@@ -53,4 +55,57 @@ public function publicationDate(): string
5355
{
5456
return $this->published_at->format('Y F j');
5557
}
58+
59+
public function toFeedItem(): FeedItem
60+
{
61+
$fullPost = view('components.notes.prose', ['note' => $this])->render();
62+
$fullPost = preg_replace('/<!--.*?-->/s', '', $fullPost);
63+
64+
return FeedItem::create()
65+
->id($this->slug)
66+
->title($this->rssTitle())
67+
->summary($this->rssSummary())
68+
->updated($this->published_at)
69+
->link(route('notes.show', $this->slug))
70+
->authorName('David Harting')
71+
->authorEmail('connect@davidharting.com');
72+
}
73+
74+
private function rssTitle(): string
75+
{
76+
if ($this->title) {
77+
return $this->title;
78+
}
79+
80+
if ($this->lead) {
81+
// Should I truncate this?
82+
return $this->lead;
83+
}
84+
85+
return 'Untitled note';
86+
}
87+
88+
/**
89+
* I'm including full test of the post in the RSS feed
90+
* rather than just a summary and link
91+
*/
92+
private function rssSummary(): string
93+
{
94+
$fullContent = Str::of('');
95+
96+
// Do not include title
97+
// Because that will already be visible as the title of the post in the RSS reader
98+
99+
if ($this->lead) {
100+
$fullContent = $fullContent->append('<p><i>');
101+
$fullContent = $fullContent->append($this->lead);
102+
$fullContent = $fullContent->append('</i></p>');
103+
}
104+
105+
if ($this->content) {
106+
$fullContent = $fullContent->append($this->content);
107+
}
108+
109+
return $fullContent;
110+
}
56111
}

app/Queries/FeedQuery.php

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
namespace App\Queries;
4+
5+
use App\Models\Note;
6+
7+
class FeedQuery
8+
{
9+
/**
10+
* Create a new class instance.
11+
*/
12+
public function __construct()
13+
{
14+
//
15+
}
16+
17+
public static function execute()
18+
{
19+
return Note::where('visible', true)->orderBy('published_at', 'desc')->limit(50)->get();
20+
}
21+
}

composer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@
1818
"laravel/tinker": "^2.10.1",
1919
"league/csv": "^9.22.0",
2020
"livewire/livewire": "^3.6",
21-
"mailersend/laravel-driver": "^2.7"
21+
"mailersend/laravel-driver": "^2.7",
22+
"spatie/laravel-feed": "^4.4"
2223
},
2324
"require-dev": {
2425
"fakerphp/faker": "^1.24.1",

composer.lock

Lines changed: 93 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

config/feed.php

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
<?php
2+
3+
return [
4+
'feeds' => [
5+
'main' => [
6+
/*
7+
* Here you can specify which class and method will return
8+
* the items that should appear in the feed. For example:
9+
* [App\Model::class, 'getAllFeedItems']
10+
*
11+
* You can also pass an argument to that method. Note that their key must be the name of the parameter:
12+
* [App\Model::class, 'getAllFeedItems', 'parameterName' => 'argument']
13+
*/
14+
'items' => [App\Queries\FeedQuery::class, 'execute'],
15+
16+
/*
17+
* The feed will be available on this url.
18+
*/
19+
'url' => 'feed',
20+
21+
'title' => 'David Harting',
22+
'description' => 'Notes from David Harting',
23+
'language' => 'en-US',
24+
25+
/*
26+
* The image to display for the feed. For Atom feeds, this is displayed as
27+
* a banner/logo; for RSS and JSON feeds, it's displayed as an icon.
28+
* An empty value omits the image attribute from the feed.
29+
*/
30+
'image' => 'https://www.davidharting.com/favicon.ico',
31+
32+
/*
33+
* The format of the feed. Acceptable values are 'rss', 'atom', or 'json'.
34+
*/
35+
'format' => 'atom',
36+
37+
/*
38+
* The view that will render the feed.
39+
*/
40+
'view' => 'feed::atom',
41+
42+
/*
43+
* The mime type to be used in the <link> tag. Set to an empty string to automatically
44+
* determine the correct value.
45+
*/
46+
'type' => '',
47+
48+
/*
49+
* The content type for the feed response. Set to an empty string to automatically
50+
* determine the correct value.
51+
*/
52+
'contentType' => '',
53+
],
54+
],
55+
];

phpunit.xml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,9 @@
2424
<env name="DB_CONNECTION" value="pgsql"/>
2525
<env name="DB_DATABASE" value="laravel"/>
2626
<env name="MAIL_MAILER" value="array"/>
27-
<env name="QUEUE_CONNECTION" value="sync"/>
27+
<env name="QUEUE_CONNECTION" value="sync"/>
2828
<env name="SESSION_DRIVER" value="array"/>
2929
<env name="TELESCOPE_ENABLED" value="false"/>
30+
<env name="APP_URL" value="http://davidharting-dot-com.test" />
3031
</php>
3132
</phpunit>

public/vendor/feed/atom.xsl

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
3+
xmlns:atom="http://www.w3.org/2005/Atom">
4+
<xsl:output method="html" version="1.0" encoding="UTF-8" indent="yes"/>
5+
<xsl:template match="/">
6+
<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
7+
<head>
8+
<title>
9+
RSS Feed | <xsl:value-of select="/atom:feed/atom:title"/>
10+
</title>
11+
<meta charset="utf-8"/>
12+
<meta http-equiv="content-type" content="text/html; charset=utf-8"/>
13+
<meta name="viewport" content="width=device-width, initial-scale=1"/>
14+
<link rel="stylesheet" href="/vendor/feed/style.css"/>
15+
</head>
16+
<body>
17+
<main class="layout-content">
18+
<h1 class="flex items-start">
19+
<!-- https://commons.wikimedia.org/wiki/File:Feed-icon.svg -->
20+
<svg xmlns="http://www.w3.org/2000/svg" version="1.1"
21+
class="mr-5"
22+
style="flex-shrink: 0; width: 1em; height: 1em;"
23+
viewBox="0 0 256 256">
24+
<defs>
25+
<linearGradient x1="0.085" y1="0.085" x2="0.915" y2="0.915"
26+
id="RSSg">
27+
<stop offset="0.0" stop-color="#E3702D"/>
28+
<stop offset="0.1071" stop-color="#EA7D31"/>
29+
<stop offset="0.3503" stop-color="#F69537"/>
30+
<stop offset="0.5" stop-color="#FB9E3A"/>
31+
<stop offset="0.7016" stop-color="#EA7C31"/>
32+
<stop offset="0.8866" stop-color="#DE642B"/>
33+
<stop offset="1.0" stop-color="#D95B29"/>
34+
</linearGradient>
35+
</defs>
36+
<rect width="256" height="256" rx="55" ry="55" x="0" y="0"
37+
fill="#CC5D15"/>
38+
<rect width="246" height="246" rx="50" ry="50" x="5" y="5"
39+
fill="#F49C52"/>
40+
<rect width="236" height="236" rx="47" ry="47" x="10" y="10"
41+
fill="url(#RSSg)"/>
42+
<circle cx="68" cy="189" r="24" fill="#FFF"/>
43+
<path
44+
d="M160 213h-34a82 82 0 0 0 -82 -82v-34a116 116 0 0 1 116 116z"
45+
fill="#FFF"/>
46+
<path
47+
d="M184 213A140 140 0 0 0 44 73 V 38a175 175 0 0 1 175 175z"
48+
fill="#FFF"/>
49+
</svg>
50+
RSS Feed
51+
</h1>
52+
<h2><xsl:value-of select="/atom:feed/atom:title"/></h2>
53+
<p>
54+
<xsl:value-of select="/atom:feed/atom:subtitle"/>
55+
</p>
56+
<hr/>
57+
<xsl:for-each select="/atom:feed/atom:entry">
58+
<div class="post">
59+
<div class="title">
60+
<a>
61+
<xsl:attribute name="href">
62+
<xsl:value-of select="atom:link/@href"/>
63+
</xsl:attribute>
64+
<xsl:value-of select="atom:title"/>
65+
</a>
66+
</div>
67+
68+
<div class="summary">
69+
<xsl:value-of select="atom:summary" disable-output-escaping="yes"/>
70+
</div>
71+
72+
<div class="published-info">
73+
Published on
74+
<xsl:value-of select="substring(atom:updated, 0, 11)" /> by <xsl:value-of select="atom:author/atom:name"/>
75+
</div>
76+
</div>
77+
</xsl:for-each>
78+
</main>
79+
</body>
80+
</html>
81+
</xsl:template>
82+
</xsl:stylesheet>

0 commit comments

Comments
 (0)