Starting a TIL
While thinking about moving Soloist Systems to a personal blog, I came back around to the idea of the digital garden, a personal online space, similar to a blog, but with a focus on the ongoing, iterative process of learning and knowledge building. I want to leverage the way I have been using LogSeq to save personal notes about technologies and philosophy in a way that I can share to benefit others.
This led me to the concept of a TIL (today I learned…), which serves as a bite-sized learn-in-public piece of information I can push daily in the form of Markdown. HUGO strikes me as a perfect technology to build this. I want to put focus on the minimal styling and fast page loading. I might leverage technologies I have previously worked with on LinuxCreative.com, such as the search plugin I vendored. Additionally, this LogSeq plugin looked interesting upon a quick web search of others with similar ideas.
Here are some goals I have for the project:
- Built with HUGO
- Under 200 lines of CSS (inspired by Bear Blog)
- 200-word soft limit on posts. Heavy use of tags and semantic linking.
Some unanswered questions:
- Should I have a TIL collection and a posts collection (for the legacy posts)?
- Should this live at wesleysinks.com?
- Should I create a newsletter with a weekly or monthly digest or something?
- Listmonk might work well for this #docker
- Should I direct LinuxCreative traffic here and just make this my one thing? I try to do too many things.
- How will this be navigated? How can I avoid throwing the stream at users rather than the garden?
- Homepage: tag cloud? (limit to top 50 visible, include search filter) TIL Homepage Idea
- Tag cloud can be sorted by most recent or TIL count, default TIL count
TIL Homepage Idea
- Render a nice looking tag cloud sorted by tag count by default. The hugo creator has implemented this, so this is a good place to start.
- Include more data in the DOM to sort by with JavaScript (dropdown) – namely, by last modified (Gemini generated):
- Front-matter should have a lastmod.
1---
2title: My TIL Entry
3date: 2024-08-01
4lastmod: 2025-08-06 # Manually set or update this value
5tags: ["hugo", "javascript", "frontend"]
6---
- Gitinfo is a nice-to-have and can help. (optional) config.yaml
1enableGitInfo: true
2
3# prioritize git commit date
4frontmatter:
5lastmod:
6 - lastmod
7 - :git
8 - date
9 - publishDate
- In your Hugo template (e.g.,
layouts/index.htmlor a partial used on your homepage), you’ll iterate through your tags and embed the necessary data directly into the tag’s DOM element:
1<input type="search" id="tagSearch" placeholder="Search tags...">
2
3<select id="sortTags">
4 <option value="count">Sort by Count</option>
5 <option value="recent">Sort by Most Recent</option>
6</select>
7
8<div id="tagCloud">
9 {{ range $.Site.Taxonomies.tags.ByCount.Reverse }}
10 {{ $name := .Name }}
11 {{ $lastmod := "" }}
12 {{ with $.Site.GetPage (printf "/%s/%s" "tags" $name) }}
13 {{ $lastmod = .Lastmod.Format "2006-01-02T15:04:05Z" }}
14 {{ end }}
15 <a href="/tags/{{ $name | urlize }}/"
16 class="tag-item"
17 data-term="{{ $name }}"
18 data-count="{{ .Count }}"
19 data-lastmod="{{ $lastmod }}">
20 {{ $name }} ({{ .Count }})
21 </a>
22 {{ end }}
23</div>
Explanation:
range $.Site.Taxonomies.tags: Iterates through each unique tag across your site.$name: The actual tag string (e.g., “hugo”, “javascript”).$taxonomy.Count: The number of pages associated with that tag.$.Site.GetPage (printf "/%s/%s" "tags" $name): This attempts to retrieve the page associated with the specific tag. You’ll likely need to create these pages for the lastmod to be directly associated. You could achieve this by creating_index.mdfiles for each tag (e.g.,content/tags/hugo/_index.md) if you want custom metadata for the tag itself..Lastmod.Format "2006-01-02T15:04:05Z": Formats thelastmoddate into a standard format (ISO 8601) that JavaScript can easily parse into aDateobject.data-term,data-count,data-lastmod: These are custom data attributes that store the tag’s information directly on the HTML element, making it accessible to JavaScript.
Now your JavaScript can simply grab these elements, sort them based on the
data-*attributes, and reorder them in the DOM.
1document.addEventListener('DOMContentLoaded', () => {
2 const tagCloudContainer = document.getElementById('tagCloud');
3 const sortDropdown = document.getElementById('sortTags');
4 // Get the search input
5 const tagSearchInput = document.getElementById('tagSearch');
6
7 function sortAndRenderTags(sortBy) {
8 const tags = Array.from(tagCloudContainer.children);
9 let sortedTags = [...tags];
10
11 if (sortBy === 'count') {
12 sortedTags.sort((a, b) =>
13 parseInt(b.dataset.count) - parseInt(a.dataset.count)
14 );
15 } else if (sortBy === 'recent') {
16 sortedTags.sort((a, b) =>
17 new Date(b.dataset.lastmod) - new Date(a.dataset.lastmod)
18 );
19 }
20
21 sortedTags.forEach(tag => tagCloudContainer.appendChild(tag));
22 }
23
24 function filterTags(searchTerm) {
25 const tags = Array.from(tagCloudContainer.children);
26 const lowerCaseSearchTerm = searchTerm.toLowerCase();
27
28 // consider using tag.hidden instead for performance reasons
29 tags.forEach(tag => {
30 const tagName = tag.dataset.term.toLowerCase();
31 if (tagName.includes(lowerCaseSearchTerm)) {
32 tag.style.display = 'inline-block';
33 // tag.hidden = false;
34 } else {
35 tag.style.display = 'none';
36 // tag.hidden = true;
37 }
38 });
39 }
40
41 sortDropdown.addEventListener('change', (event) => {
42 sortAndRenderTags(event.target.value);
43 });
44
45 tagSearchInput.addEventListener('input', (event) => {
46 filterTags(event.target.value);
47 });
48
49 // Add the keydown event listener for the Enter key
50 tagSearchInput.addEventListener('keydown', (event) => {
51 if (event.key === 'Enter') {
52 // Prevent potential form submission if within a form.
53 event.preventDefault();
54 // Remove focus from the input field.
55 event.target.blur();
56 }
57 });
58});