Skip to content

Commit 371e4d1

Browse files
committed
Blog implementation
1 parent 7f2b6ad commit 371e4d1

19 files changed

Lines changed: 985 additions & 68 deletions

File tree

Gemfile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ gem "devise_invitable", "~> 2.0"
5757

5858
gem "dotenv", "~> 3.1"
5959

60+
gem "redcarpet", "~> 3.6"
61+
6062
group :development, :test do
6163
# See https://guides.rubyonrails.org/debugging_rails_applications.html#debugging-with-the-debug-gem
6264
gem "debug", platforms: %i[ mri windows ], require: "debug/prelude"

Gemfile.lock

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,6 @@ GEM
209209
matrix (0.4.3)
210210
memory_profiler (1.1.0)
211211
mini_mime (1.1.5)
212-
mini_portile2 (2.8.9)
213212
minitest (6.0.2)
214213
drb (~> 2.0)
215214
prism (~> 1.5)
@@ -230,9 +229,6 @@ GEM
230229
net-protocol
231230
net-ssh (7.3.0)
232231
nio4r (2.7.5)
233-
nokogiri (1.19.1)
234-
mini_portile2 (~> 2.8.2)
235-
racc (~> 1.4)
236232
nokogiri (1.19.1-aarch64-linux-gnu)
237233
racc (~> 1.4)
238234
nokogiri (1.19.1-arm64-darwin)
@@ -319,6 +315,7 @@ GEM
319315
erb
320316
psych (>= 4.0.0)
321317
tsort
318+
redcarpet (3.6.1)
322319
regexp_parser (2.11.3)
323320
reline (0.6.3)
324321
io-console (~> 0.5)
@@ -405,8 +402,6 @@ GEM
405402
fugit (~> 1.11)
406403
railties (>= 7.1)
407404
thor (>= 1.3.1)
408-
sqlite3 (2.9.0)
409-
mini_portile2 (~> 2.8.0)
410405
sqlite3 (2.9.0-aarch64-linux-gnu)
411406
sqlite3 (2.9.0-arm64-darwin)
412407
sqlite3 (2.9.0-x86_64-linux-gnu)
@@ -422,7 +417,6 @@ GEM
422417
railties (>= 6.0.0)
423418
stringio (3.2.0)
424419
thor (1.5.0)
425-
thruster (0.1.18)
426420
thruster (0.1.18-aarch64-linux)
427421
thruster (0.1.18-arm64-darwin)
428422
thruster (0.1.18-x86_64-linux)
@@ -457,7 +451,7 @@ GEM
457451
PLATFORMS
458452
aarch64-linux
459453
arm64-darwin-23
460-
ruby
454+
arm64-darwin-25
461455
x86_64-linux
462456

463457
DEPENDENCIES
@@ -486,6 +480,7 @@ DEPENDENCIES
486480
rack-mini-profiler
487481
rails (~> 8.1.2)
488482
rails_admin (~> 3.0)
483+
redcarpet (~> 3.6)
489484
rspec-rails
490485
rubocop-rails-omakase
491486
rubocop-rspec

README.md

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515

1616
1. **Pure Rails:** No proprietary DSLs or "magic". If you know Rails, you know this starter.
1717
2. **Readability > Cleverness:** Standard patterns over complex abstractions.
18-
3. **Batteries Included:** Authentication, Multi-tenancy, and UI components are pre-configured.
18+
3. **Batteries Included:** Authentication, Multi-tenancy, Blog, and UI components are pre-configured.
1919

2020
## Why this starter?
2121

@@ -63,6 +63,34 @@ Overhaul the UI via the `AppSettings` singleton. No CSS hunting required. All se
6363

6464
---
6565

66+
## Blog Engine
67+
68+
A file-based markdown blog is included out of the box. Posts live in `content/blog/` as `.md` files with frontmatter:
69+
70+
```yaml
71+
---
72+
title: My Post
73+
summary: A brief description
74+
date: 2024-01-15
75+
author: Author Name
76+
category: Tutorial
77+
published: true
78+
---
79+
80+
# My Post
81+
82+
Your content here...
83+
```
84+
85+
**Features:**
86+
- Markdown rendering with syntax highlighting for code blocks
87+
- Categories and pagination
88+
- RSS feed at `/blog/feed`
89+
- SEO meta tags (Open Graph, Twitter Cards)
90+
- HTTP caching for fast page loads
91+
92+
---
93+
6694
## ⚡ Quick Start
6795

6896
```bash
Lines changed: 263 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,263 @@
1+
.blog-layout {
2+
display: flex;
3+
flex-direction: column;
4+
min-height: 100vh;
5+
}
6+
7+
.blog-layout main {
8+
flex: 1;
9+
}
10+
11+
.blog-layout .footer {
12+
margin-top: auto;
13+
}
14+
15+
.blog-header {
16+
text-align: center;
17+
padding: 3rem 0 3rem;
18+
}
19+
20+
.blog-title {
21+
font-size: 2.5rem;
22+
font-weight: 700;
23+
margin-bottom: 0.5rem;
24+
}
25+
26+
.blog-subtitle {
27+
font-size: 1.125rem;
28+
color: var(--tblr-muted);
29+
}
30+
31+
.blog-post {
32+
max-width: 680px;
33+
margin: 0 auto 3rem;
34+
padding-bottom: 2rem;
35+
border-bottom: 1px solid var(--tblr-border-color);
36+
}
37+
38+
.blog-post:last-child {
39+
border-bottom: none;
40+
}
41+
42+
.blog-post-title {
43+
font-size: 1.75rem;
44+
font-weight: 600;
45+
margin-bottom: 0.5rem;
46+
line-height: 1.3;
47+
}
48+
49+
.blog-post-title a {
50+
color: inherit;
51+
text-decoration: none;
52+
}
53+
54+
.blog-post-title a:hover {
55+
color: var(--tblr-primary);
56+
}
57+
58+
.blog-post-meta {
59+
font-size: 0.875rem;
60+
color: var(--tblr-muted);
61+
margin-bottom: 1rem;
62+
}
63+
64+
.blog-post-summary {
65+
font-size: 1.125rem;
66+
color: var(--tblr-secondary);
67+
line-height: 1.6;
68+
margin-bottom: 0;
69+
}
70+
71+
.blog-newsletter {
72+
max-width: 500px;
73+
margin: 3rem auto;
74+
padding: 2rem;
75+
background: var(--tblr-light);
76+
border-radius: 8px;
77+
text-align: center;
78+
}
79+
80+
.blog-newsletter h4 {
81+
margin-bottom: 0.5rem;
82+
}
83+
84+
.blog-newsletter p {
85+
color: var(--tblr-muted);
86+
margin-bottom: 1rem;
87+
}
88+
89+
.blog-article {
90+
max-width: 680px;
91+
margin: 0 auto;
92+
padding: 0 1rem;
93+
}
94+
95+
.blog-article-header {
96+
text-align: center;
97+
margin-bottom: 2rem;
98+
padding-bottom: 2rem;
99+
}
100+
101+
.blog-category {
102+
display: inline-block;
103+
font-size: 0.75rem;
104+
text-transform: uppercase;
105+
letter-spacing: 0.05em;
106+
color: var(--tblr-primary);
107+
font-weight: 600;
108+
margin-bottom: 1rem;
109+
}
110+
111+
.blog-article-title {
112+
font-size: 2.5rem;
113+
font-weight: 700;
114+
line-height: 1.2;
115+
margin-bottom: 1rem;
116+
}
117+
118+
.blog-article-meta {
119+
font-size: 1rem;
120+
color: var(--tblr-muted);
121+
}
122+
123+
.blog-article-body {
124+
font-size: 1.25rem;
125+
line-height: 1.7;
126+
color: var(--tblr-body-color);
127+
}
128+
129+
.blog-article-body p {
130+
margin-bottom: 1.5rem;
131+
}
132+
133+
.blog-article-body h2 {
134+
font-size: 1.75rem;
135+
font-weight: 600;
136+
margin-top: 2.5rem;
137+
margin-bottom: 1rem;
138+
}
139+
140+
.blog-article-body h3 {
141+
font-size: 1.375rem;
142+
font-weight: 600;
143+
margin-top: 2rem;
144+
margin-bottom: 0.75rem;
145+
}
146+
147+
.blog-article-body ul, .blog-article-body ol {
148+
margin-bottom: 1.5rem;
149+
padding-left: 1.5rem;
150+
}
151+
152+
.blog-article-body li {
153+
margin-bottom: 0.5rem;
154+
}
155+
156+
.blog-article-body pre {
157+
background: #f6f8fa;
158+
border: 1px solid #d0d7de;
159+
border-radius: 8px;
160+
padding: 1rem;
161+
overflow-x: auto;
162+
margin-bottom: 1.5rem;
163+
font-size: 0.875rem;
164+
}
165+
166+
.blog-article-body pre code {
167+
background: transparent;
168+
padding: 0;
169+
font-size: inherit;
170+
}
171+
172+
.blog-article-body code {
173+
font-family: var(--tblr-font-monospace);
174+
font-size: 0.9em;
175+
background: #f6f8fa;
176+
padding: 0.125rem 0.25rem;
177+
border-radius: 4px;
178+
color: #24292f;
179+
}
180+
181+
.blog-article-body blockquote {
182+
border-left: 3px solid var(--tblr-primary);
183+
padding-left: 1rem;
184+
margin: 1.5rem 0;
185+
font-style: italic;
186+
color: var(--tblr-secondary);
187+
}
188+
189+
.blog-share {
190+
max-width: 680px;
191+
margin: 3rem auto;
192+
padding: 1.5rem;
193+
background: var(--tblr-light);
194+
border-radius: 8px;
195+
text-align: center;
196+
}
197+
198+
.blog-share h5 {
199+
margin-bottom: 0.5rem;
200+
}
201+
202+
.blog-share p {
203+
color: var(--tblr-muted);
204+
margin-bottom: 1rem;
205+
font-size: 0.9375rem;
206+
}
207+
208+
.blog-back {
209+
display: block;
210+
text-align: center;
211+
margin-top: 3rem;
212+
color: var(--tblr-muted);
213+
text-decoration: none;
214+
}
215+
216+
.blog-back:hover {
217+
color: var(--tblr-primary);
218+
}
219+
220+
.blog-categories-header {
221+
text-align: center;
222+
padding: 3rem 0 2rem;
223+
margin-bottom: 3rem;
224+
}
225+
226+
.blog-categories-title {
227+
font-size: 2.5rem;
228+
font-weight: 700;
229+
margin-bottom: 0.5rem;
230+
}
231+
232+
.blog-categories-subtitle {
233+
font-size: 1.125rem;
234+
color: var(--tblr-muted);
235+
}
236+
237+
.blog-category-card {
238+
max-width: 400px;
239+
margin: 0 auto 2rem;
240+
padding: 1.5rem;
241+
border: 1px solid var(--tblr-border-color);
242+
border-radius: 8px;
243+
text-decoration: none;
244+
color: inherit;
245+
transition: all 0.2s ease;
246+
}
247+
248+
.blog-category-card:hover {
249+
border-color: var(--tblr-primary);
250+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
251+
transform: translateY(-2px);
252+
}
253+
254+
.blog-category-name {
255+
font-size: 1.25rem;
256+
font-weight: 600;
257+
margin-bottom: 0.5rem;
258+
}
259+
260+
.blog-category-count {
261+
font-size: 0.875rem;
262+
color: var(--tblr-muted);
263+
}

0 commit comments

Comments
 (0)