Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
W
www
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
1
Issues
1
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
PLN
www
Commits
346abc3d
Commit
346abc3d
authored
Apr 11, 2025
by
PLN (Algolia)
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat(poems): Improvements
parent
426231d8
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
321 additions
and
83 deletions
+321
-83
utils.js
next/lib/utils.js
+20
-19
index.js
next/pages/index.js
+10
-10
poesie.js
next/pages/poesie.js
+102
-45
[id].js
next/pages/poesie/[id].js
+37
-7
utils.module.css
next/styles/utils.module.css
+152
-2
No files found.
next/lib/utils.js
View file @
346abc3d
...
...
@@ -12,25 +12,26 @@ function getAllContentData(name, sorted = false) {
// Get file names under /content/{name}
const
contentDirectory
=
getContentDirectory
(
name
);
const
fileNames
=
fs
.
readdirSync
(
contentDirectory
);
// console.log(`gACD: total ${name}(s): ${fileNames.length}`);
const
allContentData
=
fileNames
.
map
((
fileName
)
=>
{
// Remove ".md" from file name to get id
const
id
=
fileName
.
replace
(
/
\.
md$/
,
""
);
// Read markdown file as string
const
fullPath
=
path
.
join
(
contentDirectory
,
fileName
);
const
fileContents
=
fs
.
readFileSync
(
fullPath
,
"utf8"
);
// Use gray-matter to parse the post metadata section
const
matterResult
=
matter
(
fileContents
);
// Combine the data with the id
return
{
id
,
...
matterResult
.
data
,
};
});
const
allContentData
=
fileNames
.
filter
(
fileName
=>
fileName
.
endsWith
(
'.md'
))
// Keep only .md files
.
map
((
fileName
)
=>
{
// Remove ".md" from file name to get id
const
id
=
fileName
.
replace
(
/
\.
md$/
,
""
);
// Read markdown file as string
const
fullPath
=
path
.
join
(
contentDirectory
,
fileName
);
const
fileContents
=
fs
.
readFileSync
(
fullPath
,
"utf8"
);
// Use gray-matter to parse the post metadata section
const
matterResult
=
matter
(
fileContents
);
// Combine the data with the id
return
{
id
,
...
matterResult
.
data
,
};
});
return
sorted
?
allContentData
.
sort
((
a
,
b
)
=>
{
...
...
next/pages/index.js
View file @
346abc3d
...
...
@@ -43,8 +43,8 @@ export default function Home({ posts, talks }) {
</p>
</section>
<section className={`${utilStyles.headingMd} ${utilStyles.padding1px}`}>
<Link href="
/
parvagues
/
" className={utilStyles.sectionLink}
legacyBehavior
>
…code into <em>music</em
>
<Link href="
/
parvagues
/
" className={utilStyles.sectionLink}>
<span>…code into <em>music</em></span
>
</Link>
<p className="
description
">
As <Link href="
/
parvagues
/
" className={utilStyles.inlineLink}>ParVagues</Link>, I write patterns that
...
...
@@ -52,8 +52,8 @@ export default function Home({ posts, talks }) {
</p>
</section>
<section className={`${utilStyles.headingMd} ${utilStyles.padding1px}`}>
<Link href="
/
hydra
/
" className={utilStyles.sectionLink}
legacyBehavior
>
…code into <em>animated pixelscapes</em
>
<Link href="
/
hydra
/
" className={utilStyles.sectionLink}>
<span>…code into <em>animated pixelscapes</em></span
>
</Link>
<p className="
description
">
Using <Link href="
/
hydra
/
" className={utilStyles.inlineLink}>Hydra</Link>, I create animations that
...
...
@@ -61,16 +61,16 @@ export default function Home({ posts, talks }) {
</p>
</section>
<section className={`${utilStyles.headingMd} ${utilStyles.padding1px}`}>
<Link href="
/
poesie
/
" className={utilStyles.sectionLink}
legacyBehavior
>
…words into <em>thoughts</em
>
<Link href="
/
poesie
/
" className={utilStyles.sectionLink}>
<span>…words into <em>thoughts</em></span
>
</Link>
<p className="
description
">
A selection of texts scattered across note books and note apps.
</p>
</section>
<section className={`${utilStyles.headingMd} ${utilStyles.padding1px}`}>
<Link href="
/
talks
/
" className={utilStyles.sectionLink}
legacyBehavior
>
…ideas into <em>talks</em
>
<Link href="
/
talks
/
" className={utilStyles.sectionLink}>
<span>…ideas into <em>talks</em></span
>
</Link>
<p className="
description
">
I <Link href="
/
talks
/
" className={utilStyles.inlineLink}>speak</Link> about topics I care about: from
...
...
@@ -82,8 +82,8 @@ export default function Home({ posts, talks }) {
<ul className={utilStyles.list}>
{talks.map(({ id, title, description }) => (
<li className={utilStyles.listItem} key={id}>
<Link href={`/talks#${id}`} className={utilStyles.listItemLink}
legacyBehavior
>
{title}
<Link href={`/talks#${id}`} className={utilStyles.listItemLink}>
<span>{title}</span>
</Link>
{description && (
<small className={utilStyles.lightText}>{description}</small>
...
...
next/pages/poesie.js
View file @
346abc3d
...
...
@@ -23,7 +23,8 @@ export default function Poems({ poems }) {
// Get unique languages and tags
const
languages
=
[
'all'
,
...
new
Set
(
poems
.
map
(
p
=>
p
.
language
||
'unknown'
))];
const
tags
=
[
'all'
,
...
new
Set
(
poems
.
flatMap
(
p
=>
p
.
tags
||
[]))];
const
allTags
=
[...
new
Set
(
poems
.
flatMap
(
p
=>
p
.
tags
||
[]))];
const
tags
=
[
'all'
,
...
allTags
];
// Filter and sort poems
const
filteredPoems
=
poems
...
...
@@ -47,57 +48,113 @@ export default function Poems({ poems }) {
return
(
<
Layout
>
<
Head
>
<
title
>
Poems
<
/title
>
<
title
>
Poems
|
Words
into
thoughts
<
/title
>
<
meta
name
=
"description"
content
=
"A collection of poems and thoughts"
/>
<
meta
name
=
"viewport"
content
=
"width=device-width, initial-scale=1"
/>
<
/Head
>
<
div
className
=
{
utilStyles
.
headingMd
}
>
<
h1
>
Words
into
thoughts
<
/h1
>
<
p
>
A
selection
of
texts
scattered
across
note
books
and
note
apps
.
<
/p
>
<
div
className
=
{
utilStyles
.
filters
}
>
<
div
>
<
label
>
Sort
by
:
<
/label
>
<
select
value
=
{
sortBy
}
onChange
=
{(
e
)
=>
setSortBy
(
e
.
target
.
value
)}
>
<
option
value
=
"date"
>
Date
<
/option
>
<
option
value
=
"wordcount"
>
Word
count
<
/option
>
<
/select
>
<
select
value
=
{
sortOrder
}
onChange
=
{(
e
)
=>
setSortOrder
(
e
.
target
.
value
)}
>
<
option
value
=
"desc"
>
Descending
<
/option
>
<
option
value
=
"asc"
>
Ascending
<
/option
>
<
/select
>
<
/div
>
<
div
>
<
label
>
Language
:
<
/label
>
<
select
value
=
{
filterLanguage
}
onChange
=
{(
e
)
=>
setFilterLanguage
(
e
.
target
.
value
)}
>
{
languages
.
map
(
lang
=>
(
<
option
key
=
{
lang
}
value
=
{
lang
}
>
{
lang
}
<
/option
>
))}
<
/select
>
<
/div
>
<
div
>
<
label
>
Tag
:
<
/label
>
<
select
value
=
{
filterTags
}
onChange
=
{(
e
)
=>
setFilterTags
(
e
.
target
.
value
)}
>
{
tags
.
map
(
tag
=>
(
<
option
key
=
{
tag
}
value
=
{
tag
}
>
{
tag
}
<
/option
>
))}
<
/select
>
<
div
className
=
{
utilStyles
.
filtersContainer
}
>
<
div
className
=
{
utilStyles
.
filtersGroup
}
>
<
div
className
=
{
utilStyles
.
filterItem
}
>
<
span
className
=
{
utilStyles
.
filterLabel
}
>
Sort
by
<
/span
>
<
select
className
=
{
utilStyles
.
filterSelect
}
value
=
{
sortBy
}
onChange
=
{(
e
)
=>
setSortBy
(
e
.
target
.
value
)}
aria
-
label
=
"Sort poems by"
>
<
option
value
=
"date"
>
Date
<
/option
>
<
option
value
=
"wordcount"
>
Word
count
<
/option
>
<
/select
>
<
/div
>
<
div
className
=
{
utilStyles
.
filterItem
}
>
<
span
className
=
{
utilStyles
.
filterLabel
}
>
Order
<
/span
>
<
select
className
=
{
utilStyles
.
filterSelect
}
value
=
{
sortOrder
}
onChange
=
{(
e
)
=>
setSortOrder
(
e
.
target
.
value
)}
aria
-
label
=
"Sort order"
>
<
option
value
=
"desc"
>
Newest
first
<
/option
>
<
option
value
=
"asc"
>
Oldest
first
<
/option
>
<
/select
>
<
/div
>
<
div
className
=
{
utilStyles
.
filterItem
}
>
<
span
className
=
{
utilStyles
.
filterLabel
}
>
Language
<
/span
>
<
select
className
=
{
utilStyles
.
filterSelect
}
value
=
{
filterLanguage
}
onChange
=
{(
e
)
=>
setFilterLanguage
(
e
.
target
.
value
)}
aria
-
label
=
"Filter by language"
>
{
languages
.
map
(
lang
=>
(
<
option
key
=
{
lang
}
value
=
{
lang
}
>
{
lang
===
'all'
?
'All languages'
:
lang
}
<
/option
>
))}
<
/select
>
<
/div
>
<
div
className
=
{
utilStyles
.
filterItem
}
>
<
span
className
=
{
utilStyles
.
filterLabel
}
>
Tags
<
/span
>
<
select
className
=
{
utilStyles
.
filterSelect
}
value
=
{
filterTags
}
onChange
=
{(
e
)
=>
setFilterTags
(
e
.
target
.
value
)}
aria
-
label
=
"Filter by tag"
>
{
tags
.
map
(
tag
=>
(
<
option
key
=
{
tag
}
value
=
{
tag
}
>
{
tag
===
'all'
?
'All tags'
:
tag
}
<
/option
>
))}
<
/select
>
<
/div
>
<
/div
>
<
/div
>
<
ul
className
=
{
utilStyles
.
list
}
>
{
sortedPoems
.
map
(({
id
,
title
,
date
,
language
,
tags
})
=>
(
<
li
className
=
{
utilStyles
.
listItem
}
key
=
{
id
}
>
<
Link
href
=
{
`/poesie/
${
id
}
`
}
className
=
{
utilStyles
.
listItemLink
}
legacyBehavior
>
{
title
}
<
/Link
>
<
small
className
=
{
utilStyles
.
lightText
}
>
<
Date
dateString
=
{
date
}
/
>
{
language
&&
` •
${
language
}
`
}
{
tags
&&
tags
.
length
>
0
&&
` •
${
tags
.
join
(
', '
)}
`
}
<
/small
>
<
/li
>
))}
<
/ul
>
<
div
className
=
{
utilStyles
.
list
}
>
{
sortedPoems
.
length
===
0
?
(
<
p
>
No
poems
match
your
current
filters
.
Try
adjusting
your
selection
.
<
/p
>
)
:
(
sortedPoems
.
map
(({
id
,
title
,
date
,
language
,
tags
})
=>
(
<
div
className
=
{
utilStyles
.
poemCard
}
key
=
{
id
}
>
<
Link
href
=
{
`/poesie/
${
id
}
`
}
className
=
{
utilStyles
.
listItemLink
}
>
{
title
}
<
/Link
>
<
div
className
=
{
utilStyles
.
poemMeta
}
>
{
date
&&
(
<
span
className
=
{
utilStyles
.
poemMetaItem
}
>
<
Date
dateString
=
{
date
}
/
>
<
/span
>
)}
{
language
&&
(
<
span
className
=
{
utilStyles
.
poemMetaItem
}
>
{
language
}
<
/span
>
)}
<
/div
>
{
tags
&&
tags
.
length
>
0
&&
(
<
div
className
=
{
utilStyles
.
tagList
}
>
{
tags
.
map
(
tag
=>
(
<
span
key
=
{
tag
}
className
=
{
utilStyles
.
tag
}
>
{
tag
}
<
/span
>
))}
<
/div
>
)}
<
/div
>
))
)}
<
/div
>
<
/div
>
<
/Layout
>
);
...
...
next/pages/poesie/[id].js
View file @
346abc3d
...
...
@@ -3,6 +3,7 @@ import Layout from '@/components/layout';
import
Date
from
'@/components/date'
;
import
utilStyles
from
'@/styles/utils.module.css'
;
import
{
getPoemData
,
getAllPoemIds
}
from
'@/lib/poems'
;
import
Link
from
'next/link'
;
export
async
function
getStaticProps
({
params
})
{
const
poemData
=
await
getPoemData
(
params
.
id
);
...
...
@@ -26,14 +27,43 @@ export default function Poem({ poemData }) {
<
Layout
>
<
Head
>
<
title
>
{
poemData
.
title
}
<
/title
>
<
meta
name
=
"description"
content
=
{
`
${
poemData
.
title
}
- Poem by PLN`
}
/
>
<
meta
name
=
"viewport"
content
=
"width=device-width, initial-scale=1"
/>
<
/Head
>
<
article
>
<
h1
className
=
{
utilStyles
.
headingXl
}
>
{
poemData
.
title
}
<
/h1
>
<
div
className
=
{
utilStyles
.
lightText
}
>
<
Date
dateString
=
{
poemData
.
date
}
/
>
{
poemData
.
language
&&
` •
${
poemData
.
language
}
`
}
{
poemData
.
tags
&&
poemData
.
tags
.
length
>
0
&&
` •
${
poemData
.
tags
.
join
(
', '
)}
`
}
<
/div
>
<
Link
href
=
"/poesie"
className
=
{
utilStyles
.
backLink
}
>
←
Back
to
poems
<
/Link
>
<
article
className
=
{
utilStyles
.
poemContainer
}
>
<
header
>
<
h1
className
=
{
utilStyles
.
headingXl
}
>
{
poemData
.
title
}
<
/h1
>
<
div
className
=
{
utilStyles
.
poemMeta
}
>
{
poemData
.
date
&&
(
<
span
className
=
{
utilStyles
.
poemMetaItem
}
>
<
Date
dateString
=
{
poemData
.
date
}
/
>
<
/span
>
)}
{
poemData
.
language
&&
(
<
span
className
=
{
utilStyles
.
poemMetaItem
}
>
{
poemData
.
language
}
<
/span
>
)}
<
/div
>
{
poemData
.
tags
&&
poemData
.
tags
.
length
>
0
&&
(
<
div
className
=
{
utilStyles
.
tagList
}
>
{
poemData
.
tags
.
map
(
tag
=>
(
<
span
key
=
{
tag
}
className
=
{
utilStyles
.
tag
}
>
{
tag
}
<
/span
>
))}
<
/div
>
)}
<
/header
>
<
div
className
=
{
utilStyles
.
poemContent
}
dangerouslySetInnerHTML
=
{{
__html
:
poemData
.
contentHtml
}}
...
...
next/styles/utils.module.css
View file @
346abc3d
...
...
@@ -65,20 +65,140 @@
border-radius
:
4px
;
}
.poemCard
{
background-color
:
#fff
;
border-radius
:
8px
;
box-shadow
:
0
2px
8px
rgba
(
0
,
0
,
0
,
0.1
);
padding
:
1.5rem
;
margin-bottom
:
1.5rem
;
transition
:
transform
0.2s
ease-in-out
,
box-shadow
0.2s
ease-in-out
;
}
.poemCard
:hover
{
transform
:
translateY
(
-2px
);
box-shadow
:
0
4px
12px
rgba
(
0
,
0
,
0
,
0.15
);
}
.poemMeta
{
display
:
flex
;
flex-wrap
:
wrap
;
gap
:
0.5rem
;
margin-top
:
0.5rem
;
color
:
#666
;
font-size
:
0.9rem
;
}
.poemMetaItem
{
display
:
inline-flex
;
align-items
:
center
;
}
.poemMetaItem
:not
(
:last-child
)
::after
{
content
:
"•"
;
margin-left
:
0.5rem
;
opacity
:
0.6
;
}
.tagList
{
display
:
flex
;
flex-wrap
:
wrap
;
gap
:
0.5rem
;
margin-top
:
0.75rem
;
}
.tag
{
background-color
:
#f0f0f0
;
border-radius
:
12px
;
padding
:
0.25rem
0.75rem
;
font-size
:
0.85rem
;
color
:
#555
;
transition
:
background-color
0.2s
;
}
.tag
:hover
{
background-color
:
#e0e0e0
;
}
.filtersContainer
{
background-color
:
#f8f9fa
;
border-radius
:
8px
;
padding
:
1.5rem
;
margin-bottom
:
2rem
;
}
.filtersGroup
{
display
:
flex
;
flex-direction
:
column
;
gap
:
1rem
;
}
@media
(
min-width
:
768px
)
{
.filtersGroup
{
flex-direction
:
row
;
flex-wrap
:
wrap
;
}
}
.filterItem
{
display
:
flex
;
align-items
:
center
;
flex-wrap
:
wrap
;
gap
:
0.5rem
;
}
.filterLabel
{
font-weight
:
600
;
color
:
#333
;
min-width
:
80px
;
}
.filterSelect
{
padding
:
0.5rem
0.75rem
;
border
:
1px
solid
#ddd
;
border-radius
:
4px
;
background-color
:
white
;
min-width
:
120px
;
font-size
:
0.9rem
;
color
:
#333
;
transition
:
border-color
0.2s
,
box-shadow
0.2s
;
}
.filterSelect
:focus
{
border-color
:
#0070f3
;
box-shadow
:
0
0
0
2px
rgba
(
0
,
112
,
243
,
0.2
);
outline
:
none
;
}
.filterSelect
:hover
{
border-color
:
#999
;
}
.poemContent
{
margin-top
:
2rem
;
line-height
:
1.8
;
white-space
:
pre-wrap
;
font-size
:
1.1rem
;
color
:
#333
;
max-width
:
70ch
;
}
@media
(
max-width
:
768px
)
{
.poemContent
{
font-size
:
1rem
;
line-height
:
1.7
;
}
}
.poemContent
p
{
margin
:
1rem
0
;
margin
:
1
.5
rem
0
;
}
.poemContent
img
{
max-width
:
100%
;
height
:
auto
;
margin
:
1rem
0
;
margin
:
1.5rem
0
;
border-radius
:
4px
;
box-shadow
:
0
2px
8px
rgba
(
0
,
0
,
0
,
0.1
);
}
.listItemLink
{
...
...
@@ -115,3 +235,33 @@
.inlineLink
:hover
{
opacity
:
0.8
;
}
.poemContainer
{
max-width
:
800px
;
margin
:
2rem
auto
;
padding
:
2rem
;
background-color
:
#fff
;
border-radius
:
8px
;
box-shadow
:
0
2px
10px
rgba
(
0
,
0
,
0
,
0.1
);
}
.backLink
{
display
:
inline-block
;
margin
:
1rem
0
;
color
:
#666
;
text-decoration
:
none
;
font-size
:
0.9rem
;
transition
:
color
0.2s
;
}
.backLink
:hover
{
color
:
#0070f3
;
text-decoration
:
underline
;
}
@media
(
max-width
:
768px
)
{
.poemContainer
{
padding
:
1.5rem
;
margin
:
1rem
auto
;
}
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment