Write blog post list component

This commit is contained in:
2025-10-02 12:12:59 +02:00
parent 7432a46ae2
commit 278f51ce2e
12 changed files with 205 additions and 100 deletions

View File

@@ -1,18 +1,20 @@
Class { Class {
#name : #TBApplicationRootComponent, #name : 'TBApplicationRootComponent',
#superclass : #WAComponent, #superclass : 'WAComponent',
#instVars : [ #instVars : [
'main' 'main'
], ],
#category : #'TinyBlog-Components' #category : 'TinyBlog-Components-Components',
#package : 'TinyBlog-Components',
#tag : 'Components'
} }
{ #category : #testing } { #category : 'testing' }
TBApplicationRootComponent class >> canBeRoot [ TBApplicationRootComponent class >> canBeRoot [
^ true ^ true
] ]
{ #category : #initialization } { #category : 'initialization' }
TBApplicationRootComponent class >> initialize [ TBApplicationRootComponent class >> initialize [
"self initialize" "self initialize"
| app | | app |
@@ -23,23 +25,29 @@ TBApplicationRootComponent class >> initialize [
addLibrary: SBSDeploymentLibrary addLibrary: SBSDeploymentLibrary
] ]
{ #category : #rendering } { #category : 'rendering' }
TBApplicationRootComponent >> children [ TBApplicationRootComponent >> children [
^ { main } ^ { main }
] ]
{ #category : #initialization } { #category : 'initialization' }
TBApplicationRootComponent >> initialize [ TBApplicationRootComponent >> initialize [
super initialize. super initialize.
main := TBScreenComponent new main := TBPostsListComponent new
] ]
{ #category : #rendering } { #category : 'initialization' }
TBApplicationRootComponent >> main: aComponent [
main := aComponent
]
{ #category : 'rendering' }
TBApplicationRootComponent >> renderContentOn: html [ TBApplicationRootComponent >> renderContentOn: html [
html render: main html render: main
] ]
{ #category : #rendering } { #category : 'rendering' }
TBApplicationRootComponent >> updateRoot: anHtmlRoot [ TBApplicationRootComponent >> updateRoot: anHtmlRoot [
super updateRoot: anHtmlRoot. super updateRoot: anHtmlRoot.
anHtmlRoot beHtml5. anHtmlRoot beHtml5.

View File

@@ -0,0 +1,27 @@
Class {
#name : 'TBHeaderComponent',
#superclass : 'SBSComponent',
#category : 'TinyBlog-Components-Components',
#package : 'TinyBlog-Components',
#tag : 'Components'
}
{ #category : 'rendering' }
TBHeaderComponent >> renderBrandOn: html [
html navigationBarBrand
url: self application url;
with: 'TinyBlog'
]
{ #category : 'rendering' }
TBHeaderComponent >> renderContentOn: html [
| bar |
bar := html navigationBar.
bar background beLight.
bar with: [
html container: [
self renderBrandOn: html
]
]
]

View File

@@ -0,0 +1,43 @@
Class {
#name : 'TBPostComponent',
#superclass : 'SBSComponent',
#instVars : [
'post'
],
#category : 'TinyBlog-Components-Components',
#package : 'TinyBlog-Components',
#tag : 'Components'
}
{ #category : 'initialization' }
TBPostComponent >> date [
^ post date
]
{ #category : 'initialization' }
TBPostComponent >> initialize [
super initialize.
post := TBPost new
]
{ #category : 'initialization' }
TBPostComponent >> post: aPost [
post := aPost
]
{ #category : 'initialization' }
TBPostComponent >> renderContentOn: html [
html heading level: 2; with: self title.
html heading level: 6; with: self date.
html text: self text
]
{ #category : 'initialization' }
TBPostComponent >> text [
^ post text
]
{ #category : 'initialization' }
TBPostComponent >> title [
^ post title
]

View File

@@ -0,0 +1,41 @@
Class {
#name : 'TBPostsListComponent',
#superclass : 'TBScreenComponent',
#instVars : [
'postComponents'
],
#category : 'TinyBlog-Components-Components',
#package : 'TinyBlog-Components',
#tag : 'Components'
}
{ #category : 'initialization' }
TBPostsListComponent >> children [
^ self postComponents, super children
]
{ #category : 'initialization' }
TBPostsListComponent >> initialize [
super initialize.
postComponents := OrderedCollection new
]
{ #category : 'initialization' }
TBPostsListComponent >> postComponents [
postComponents := self readSelectedPosts
collect: [ :each | TBPostComponent new post: each ].
^ postComponents
]
{ #category : 'initialization' }
TBPostsListComponent >> readSelectedPosts [
^ self blog allVisibleBlogPosts
]
{ #category : 'rendering' }
TBPostsListComponent >> renderContentOn: html [
super renderContentOn: html.
html container: [
self postComponents do: [ :p |
html render: p ] ]
]

View File

@@ -1,36 +1,38 @@
Class { Class {
#name : #TBScreenComponent, #name : 'TBScreenComponent',
#superclass : #WAComponent, #superclass : 'SBSRootComponent',
#instVars : [ #instVars : [
'header' 'header'
], ],
#category : #'TinyBlog-Components' #category : 'TinyBlog-Components-Components',
#package : 'TinyBlog-Components',
#tag : 'Components'
} }
{ #category : #acccessing } { #category : 'acccessing' }
TBScreenComponent >> blog [ TBScreenComponent >> blog [
"Return the current blog. In the future we will ask the "Return the current blog. In the future we will ask the
session to return the blog of the currently logged in user." session to return the blog of the currently logged in user."
^ TBBlog current ^ TBBlog current
] ]
{ #category : #acccessing } { #category : 'acccessing' }
TBScreenComponent >> children [ TBScreenComponent >> children [
^ { header } ^ { header }
] ]
{ #category : #initialization } { #category : 'initialization' }
TBScreenComponent >> createHeaderComponent [ TBScreenComponent >> createHeaderComponent [
^ TBHeaderComponent new ^ TBHeaderComponent new
] ]
{ #category : #initialization } { #category : 'initialization' }
TBScreenComponent >> initialize [ TBScreenComponent >> initialize [
super initialize. super initialize.
header := self createHeaderComponent header := self createHeaderComponent
] ]
{ #category : #acccessing } { #category : 'acccessing' }
TBScreenComponent >> renderContentOn: html [ TBScreenComponent >> renderContentOn: html [
html render: header html render: header
] ]

View File

@@ -0,0 +1 @@
Package { #name : 'TinyBlog-Components' }

View File

@@ -1,13 +1,14 @@
Class { Class {
#name : #TBBlog, #name : 'TBBlog',
#superclass : #Object, #superclass : 'Object',
#instVars : [ #instVars : [
'posts' 'posts'
], ],
#category : #TinyBlog #category : 'TinyBlog',
#package : 'TinyBlog'
} }
{ #category : #demos } { #category : 'demos' }
TBBlog class >> createDemoPosts [ TBBlog class >> createDemoPosts [
"TBBlog createDemoPosts" "TBBlog createDemoPosts"
self current self current
@@ -29,7 +30,7 @@ TBBlog class >> createDemoPosts [
category: 'Pharo') visible: true) category: 'Pharo') visible: true)
] ]
{ #category : #initialization } { #category : 'initialization' }
TBBlog class >> current [ TBBlog class >> current [
"answer the instance of the TBRepository" "answer the instance of the TBRepository"
^ self selectAll ^ self selectAll
@@ -37,70 +38,70 @@ TBBlog class >> current [
ifEmpty: [ self new save ] ifEmpty: [ self new save ]
] ]
{ #category : #initialization } { #category : 'initialization' }
TBBlog class >> initialize [ TBBlog class >> initialize [
self reset self reset
] ]
{ #category : #reading } { #category : 'reading' }
TBBlog class >> initializeVoyageOnMemoryDB [ TBBlog class >> initializeVoyageOnMemoryDB [
VOMemoryRepository new enableSingleton VOMemoryRepository new enableSingleton
] ]
{ #category : #reading } { #category : 'reading' }
TBBlog class >> isVoyageRoot [ TBBlog class >> isVoyageRoot [
"Indicates that instances of this class are top level documents in noSQL databases" "Indicates that instances of this class are top level documents in noSQL databases"
^ true ^ true
] ]
{ #category : #initialization } { #category : 'initialization' }
TBBlog class >> reset [ TBBlog class >> reset [
self initializeVoyageOnMemoryDB. self initializeVoyageOnMemoryDB.
] ]
{ #category : #reading } { #category : 'reading' }
TBBlog >> allBlogPosts [ TBBlog >> allBlogPosts [
^ posts ^ posts
] ]
{ #category : #reading } { #category : 'reading' }
TBBlog >> allBlogPostsFromCategory: aCategory [ TBBlog >> allBlogPostsFromCategory: aCategory [
^ posts select: [ :p | p category = aCategory ] ^ posts select: [ :p | p category = aCategory ]
] ]
{ #category : #reading } { #category : 'reading' }
TBBlog >> allCategories [ TBBlog >> allCategories [
^ (self allBlogPosts collect: [ :p | p category ]) asSet ^ (self allBlogPosts collect: [ :p | p category ]) asSet
] ]
{ #category : #reading } { #category : 'reading' }
TBBlog >> allVisibleBlogPosts [ TBBlog >> allVisibleBlogPosts [
^ posts select: [ :p | p isVisible ] ^ posts select: [ :p | p isVisible ]
] ]
{ #category : #reading } { #category : 'reading' }
TBBlog >> allVisibleBlogPostsFromCategory: aCategory [ TBBlog >> allVisibleBlogPostsFromCategory: aCategory [
^ posts select: [ :p | p category = aCategory and: [ p isVisible ] ] ^ posts select: [ :p | p category = aCategory and: [ p isVisible ] ]
] ]
{ #category : #initialization } { #category : 'initialization' }
TBBlog >> initialize [ TBBlog >> initialize [
super initialize. super initialize.
posts := OrderedCollection new posts := OrderedCollection new
] ]
{ #category : #deleting } { #category : 'deleting' }
TBBlog >> removeAllPosts [ TBBlog >> removeAllPosts [
posts := OrderedCollection new. posts := OrderedCollection new.
self save self save
] ]
{ #category : #initialization } { #category : 'initialization' }
TBBlog >> size [ TBBlog >> size [
^ posts size ^ posts size
] ]
{ #category : #writing } { #category : 'writing' }
TBBlog >> writeBlogPost: aPost [ TBBlog >> writeBlogPost: aPost [
"Add the blog post in database." "Add the blog post in database."
self allBlogPosts add: aPost. self allBlogPosts add: aPost.

View File

@@ -1,16 +1,18 @@
Class { Class {
#name : #TBBlogTest, #name : 'TBBlogTest',
#superclass : #TestCase, #superclass : 'TestCase',
#instVars : [ #instVars : [
'blog', 'blog',
'post', 'post',
'first', 'first',
'previousRepository' 'previousRepository'
], ],
#category : #'TinyBlog-Tests' #category : 'TinyBlog-Tests',
#package : 'TinyBlog',
#tag : 'Tests'
} }
{ #category : #running } { #category : 'running' }
TBBlogTest >> setUp [ TBBlogTest >> setUp [
previousRepository := VORepository current. previousRepository := VORepository current.
VORepository setRepository: VOMemoryRepository new. VORepository setRepository: VOMemoryRepository new.
@@ -21,60 +23,60 @@ TBBlogTest >> setUp [
post := (TBPost title: 'Another title' text: 'Another text' category: 'Second Category') beVisible post := (TBPost title: 'Another title' text: 'Another text' category: 'Second Category') beVisible
] ]
{ #category : #running } { #category : 'running' }
TBBlogTest >> tearDown [ TBBlogTest >> tearDown [
VORepository setRepository: previousRepository VORepository setRepository: previousRepository
] ]
{ #category : #tests } { #category : 'tests' }
TBBlogTest >> testAddBlogPost [ TBBlogTest >> testAddBlogPost [
blog writeBlogPost: post. blog writeBlogPost: post.
self assert: blog size equals: 2 self assert: blog size equals: 2
] ]
{ #category : #tests } { #category : 'tests' }
TBBlogTest >> testAllBlogPosts [ TBBlogTest >> testAllBlogPosts [
blog writeBlogPost: post. blog writeBlogPost: post.
self assert: blog allBlogPosts size equals: 2 self assert: blog allBlogPosts size equals: 2
] ]
{ #category : #tests } { #category : 'tests' }
TBBlogTest >> testAllBlogPostsFromCategory [ TBBlogTest >> testAllBlogPostsFromCategory [
self assert: (blog allBlogPostsFromCategory: 'First Category') self assert: (blog allBlogPostsFromCategory: 'First Category')
size equals: 1 size equals: 1
] ]
{ #category : #tests } { #category : 'tests' }
TBBlogTest >> testAllCategories [ TBBlogTest >> testAllCategories [
blog writeBlogPost: post. blog writeBlogPost: post.
self assert: blog allCategories size equals: 2 self assert: blog allCategories size equals: 2
] ]
{ #category : #tests } { #category : 'tests' }
TBBlogTest >> testAllVisibleBlogPosts [ TBBlogTest >> testAllVisibleBlogPosts [
blog writeBlogPost: post. blog writeBlogPost: post.
self assert: blog allVisibleBlogPosts size equals: 1 self assert: blog allVisibleBlogPosts size equals: 1
] ]
{ #category : #tests } { #category : 'tests' }
TBBlogTest >> testAllVisibleBlogPostsFromCategory [ TBBlogTest >> testAllVisibleBlogPostsFromCategory [
blog writeBlogPost: post. blog writeBlogPost: post.
self assert: (blog allVisibleBlogPostsFromCategory: 'First Category') size equals: 0. self assert: (blog allVisibleBlogPostsFromCategory: 'First Category') size equals: 0.
self assert: (blog allVisibleBlogPostsFromCategory: 'Second Category') size equals: 1. self assert: (blog allVisibleBlogPostsFromCategory: 'Second Category') size equals: 1.
] ]
{ #category : #tests } { #category : 'tests' }
TBBlogTest >> testRemoveAllBlogPosts [ TBBlogTest >> testRemoveAllBlogPosts [
blog removeAllPosts. blog removeAllPosts.
self assert: blog size equals: 0 self assert: blog size equals: 0
] ]
{ #category : #tests } { #category : 'tests' }
TBBlogTest >> testSize [ TBBlogTest >> testSize [
self assert: blog size equals: 1 self assert: blog size equals: 1
] ]
{ #category : #tests } { #category : 'tests' }
TBBlogTest >> testUnclassifiedBlogPosts [ TBBlogTest >> testUnclassifiedBlogPosts [
self assert: (blog allBlogPosts select: [ :p | p isUnclassified ]) size equals: 0 self assert: (blog allBlogPosts select: [ :p | p isUnclassified ]) size equals: 0
] ]

View File

@@ -1,23 +0,0 @@
Class {
#name : #TBHeaderComponent,
#superclass : #WAComponent,
#category : #'TinyBlog-Components'
}
{ #category : #rendering }
TBHeaderComponent >> renderBrandOn: html [
html tbsNavbarHeader: [
html tbsNavbarBrand
url: self application url;
with: 'TinyBlog'
]
]
{ #category : #rendering }
TBHeaderComponent >> renderContentOn: html [
html tbsNavbar beDefault; with: [
html tbsContainer: [
self renderBrandOn: html
]
]
]

View File

@@ -1,6 +1,6 @@
Class { Class {
#name : #TBPost, #name : 'TBPost',
#superclass : #Object, #superclass : 'Object',
#instVars : [ #instVars : [
'title', 'title',
'text', 'text',
@@ -8,10 +8,11 @@ Class {
'category', 'category',
'visible' 'visible'
], ],
#category : #TinyBlog #category : 'TinyBlog',
#package : 'TinyBlog'
} }
{ #category : #'instance creation' } { #category : 'instance creation' }
TBPost class >> title: aTitle text: aText [ TBPost class >> title: aTitle text: aText [
^ self new ^ self new
title: aTitle; title: aTitle;
@@ -19,48 +20,48 @@ TBPost class >> title: aTitle text: aText [
yourself yourself
] ]
{ #category : #'instance creation' } { #category : 'instance creation' }
TBPost class >> title: aTitle text: aText category: aCategory [ TBPost class >> title: aTitle text: aText category: aCategory [
^ (self title: aTitle text: aText) ^ (self title: aTitle text: aText)
category: aCategory; category: aCategory;
yourself yourself
] ]
{ #category : #'as yet unclassified' } { #category : 'as yet unclassified' }
TBPost class >> unclassifiedTag [ TBPost class >> unclassifiedTag [
^ 'Unclassified' ^ 'Unclassified'
] ]
{ #category : #action } { #category : 'action' }
TBPost >> beVisible [ TBPost >> beVisible [
self visible: true self visible: true
] ]
{ #category : #accessing } { #category : 'accessing' }
TBPost >> category [ TBPost >> category [
^ category ^ category
] ]
{ #category : #accessing } { #category : 'accessing' }
TBPost >> category: anObject [ TBPost >> category: anObject [
category := anObject category := anObject
] ]
{ #category : #accessing } { #category : 'accessing' }
TBPost >> date [ TBPost >> date [
^ date ^ date
] ]
{ #category : #accessing } { #category : 'accessing' }
TBPost >> date: anObject [ TBPost >> date: anObject [
date := anObject date := anObject
] ]
{ #category : #initialization } { #category : 'initialization' }
TBPost >> initialize [ TBPost >> initialize [
super initialize. super initialize.
self category: self class unclassifiedTag. self category: self class unclassifiedTag.
@@ -68,52 +69,52 @@ TBPost >> initialize [
self notVisible. self notVisible.
] ]
{ #category : #testing } { #category : 'testing' }
TBPost >> isUnclassified [ TBPost >> isUnclassified [
^ self category = self class unclassifiedTag ^ self category = self class unclassifiedTag
] ]
{ #category : #testing } { #category : 'testing' }
TBPost >> isVisible [ TBPost >> isVisible [
^ self visible ^ self visible
] ]
{ #category : #action } { #category : 'action' }
TBPost >> notVisible [ TBPost >> notVisible [
self visible: false self visible: false
] ]
{ #category : #accessing } { #category : 'accessing' }
TBPost >> text [ TBPost >> text [
^ text ^ text
] ]
{ #category : #accessing } { #category : 'accessing' }
TBPost >> text: anObject [ TBPost >> text: anObject [
text := anObject text := anObject
] ]
{ #category : #accessing } { #category : 'accessing' }
TBPost >> title [ TBPost >> title [
^ title ^ title
] ]
{ #category : #accessing } { #category : 'accessing' }
TBPost >> title: anObject [ TBPost >> title: anObject [
title := anObject title := anObject
] ]
{ #category : #accessing } { #category : 'accessing' }
TBPost >> visible [ TBPost >> visible [
^ visible ^ visible
] ]
{ #category : #accessing } { #category : 'accessing' }
TBPost >> visible: anObject [ TBPost >> visible: anObject [
visible := anObject visible := anObject

View File

@@ -1,10 +1,12 @@
Class { Class {
#name : #TBPostTest, #name : 'TBPostTest',
#superclass : #TestCase, #superclass : 'TestCase',
#category : #'TinyBlog-Tests' #category : 'TinyBlog-Tests',
#package : 'TinyBlog',
#tag : 'Tests'
} }
{ #category : #tests } { #category : 'tests' }
TBPostTest >> testPostIsCreatedCorrectly [ TBPostTest >> testPostIsCreatedCorrectly [
| post | | post |
@@ -16,7 +18,7 @@ TBPostTest >> testPostIsCreatedCorrectly [
self assert: post text equals: 'TinyBlog is a small blog engine made with Pharo.'. self assert: post text equals: 'TinyBlog is a small blog engine made with Pharo.'.
] ]
{ #category : #tests } { #category : 'tests' }
TBPostTest >> testWithoutCategoryIsUnclassified [ TBPostTest >> testWithoutCategoryIsUnclassified [
| post | | post |

View File

@@ -1 +1 @@
Package { #name : #TinyBlog } Package { #name : 'TinyBlog' }