From de7bfae28e589ec12ea2f400c12a96b6e8f09d08 Mon Sep 17 00:00:00 2001 From: Romain de Laage Date: Thu, 4 Apr 2024 19:44:27 +0200 Subject: [PATCH] ui: add tag entries page --- internal/locale/translations/de_DE.json | 1 + internal/locale/translations/el_EL.json | 1 + internal/locale/translations/en_US.json | 1 + internal/locale/translations/es_ES.json | 1 + internal/locale/translations/fi_FI.json | 1 + internal/locale/translations/fr_FR.json | 1 + internal/locale/translations/hi_IN.json | 1 + internal/locale/translations/id_ID.json | 1 + internal/locale/translations/it_IT.json | 1 + internal/locale/translations/ja_JP.json | 1 + internal/locale/translations/nl_NL.json | 1 + internal/locale/translations/pl_PL.json | 1 + internal/locale/translations/pt_BR.json | 1 + internal/locale/translations/ru_RU.json | 1 + internal/locale/translations/tr_TR.json | 1 + internal/locale/translations/uk_UA.json | 1 + internal/locale/translations/zh_CN.json | 1 + internal/locale/translations/zh_TW.json | 1 + internal/storage/entry_pagination_builder.go | 9 ++ internal/storage/entry_query_builder.go | 2 +- internal/template/functions.go | 6 +- internal/template/templates/views/entry.html | 2 +- .../template/templates/views/tag_entries.html | 65 ++++++++++++++ internal/ui/entry_tag.go | 90 +++++++++++++++++++ internal/ui/tag_entries.go | 64 +++++++++++++ internal/ui/tag_entries_all.go | 64 +++++++++++++ internal/ui/ui.go | 5 ++ 27 files changed, 321 insertions(+), 4 deletions(-) create mode 100644 internal/template/templates/views/tag_entries.html create mode 100644 internal/ui/entry_tag.go create mode 100644 internal/ui/tag_entries.go create mode 100644 internal/ui/tag_entries_all.go diff --git a/internal/locale/translations/de_DE.json b/internal/locale/translations/de_DE.json index cc60035a0eb..c49a27f6289 100644 --- a/internal/locale/translations/de_DE.json +++ b/internal/locale/translations/de_DE.json @@ -258,6 +258,7 @@ "alert.no_bookmark": "Es existiert derzeit kein Lesezeichen.", "alert.no_category": "Es ist keine Kategorie vorhanden.", "alert.no_category_entry": "Es befindet sich kein Artikel in dieser Kategorie.", + "alert.no_tag_entry": "Es gibt keine Artikel, die diesem Tag entsprechen.", "alert.no_feed_entry": "Es existiert kein Artikel für dieses Abonnement.", "alert.no_feed": "Es sind keine Abonnements vorhanden.", "alert.no_feed_in_category": "Für diese Kategorie gibt es kein Abonnement.", diff --git a/internal/locale/translations/el_EL.json b/internal/locale/translations/el_EL.json index 822821633db..94d74149cf3 100644 --- a/internal/locale/translations/el_EL.json +++ b/internal/locale/translations/el_EL.json @@ -258,6 +258,7 @@ "alert.no_bookmark": "Δεν υπάρχει σελιδοδείκτης αυτή τη στιγμή.", "alert.no_category": "Δεν υπάρχει κατηγορία.", "alert.no_category_entry": "Δεν υπάρχουν άρθρα σε αυτήν την κατηγορία.", + "alert.no_tag_entry": "Δεν υπάρχουν αντικείμενα που να ταιριάζουν με αυτή την ετικέτα.", "alert.no_feed_entry": "Δεν υπάρχουν άρθρα για αυτήν τη ροή.", "alert.no_feed": "Δεν έχετε συνδρομές.", "alert.no_feed_in_category": "Δεν υπάρχει συνδρομή για αυτήν την κατηγορία.", diff --git a/internal/locale/translations/en_US.json b/internal/locale/translations/en_US.json index 97e58fe5a2b..77b7377867b 100644 --- a/internal/locale/translations/en_US.json +++ b/internal/locale/translations/en_US.json @@ -258,6 +258,7 @@ "alert.no_bookmark": "There are no starred entries.", "alert.no_category": "There is no category.", "alert.no_category_entry": "There are no entries in this category.", + "alert.no_tag_entry": "There are no entries matching this tag.", "alert.no_feed_entry": "There are no entries for this feed.", "alert.no_feed": "You don’t have any feeds.", "alert.no_feed_in_category": "There is no feed for this category.", diff --git a/internal/locale/translations/es_ES.json b/internal/locale/translations/es_ES.json index 914d8994220..d4669b39562 100644 --- a/internal/locale/translations/es_ES.json +++ b/internal/locale/translations/es_ES.json @@ -258,6 +258,7 @@ "alert.no_bookmark": "No hay marcador en este momento.", "alert.no_category": "No hay categoría.", "alert.no_category_entry": "No hay artículos en esta categoría.", + "alert.no_tag_entry": "No hay artículos con esta etiqueta.", "alert.no_feed_entry": "No hay artículos para esta fuente.", "alert.no_feed": "No tienes fuentes.", "alert.no_feed_in_category": "No hay fuentes para esta categoría.", diff --git a/internal/locale/translations/fi_FI.json b/internal/locale/translations/fi_FI.json index 41bd2e50d04..6b2f2fca8f6 100644 --- a/internal/locale/translations/fi_FI.json +++ b/internal/locale/translations/fi_FI.json @@ -258,6 +258,7 @@ "alert.no_bookmark": "Tällä hetkellä ei ole kirjanmerkkiä.", "alert.no_category": "Ei ole kategoriaa.", "alert.no_category_entry": "Tässä kategoriassa ei ole artikkeleita.", + "alert.no_tag_entry": "Tätä tunnistetta vastaavia merkintöjä ei ole.", "alert.no_feed_entry": "Tässä syötteessä ei ole artikkeleita.", "alert.no_feed": "Sinulla ei ole tilauksia.", "alert.no_feed_in_category": "Tälle kategorialle ei ole tilausta.", diff --git a/internal/locale/translations/fr_FR.json b/internal/locale/translations/fr_FR.json index d616eb3eb3a..fb2982fafba 100644 --- a/internal/locale/translations/fr_FR.json +++ b/internal/locale/translations/fr_FR.json @@ -258,6 +258,7 @@ "alert.no_bookmark": "Il n'y a aucun favoris pour le moment.", "alert.no_category": "Il n'y a aucune catégorie.", "alert.no_category_entry": "Il n'y a aucun article dans cette catégorie.", + "alert.no_tag_entry": "Il n'y a aucun article correspondant à ce tag.", "alert.no_feed_entry": "Il n'y a aucun article pour cet abonnement.", "alert.no_feed": "Vous n'avez aucun abonnement.", "alert.no_feed_in_category": "Il n'y a pas d'abonnement pour cette catégorie.", diff --git a/internal/locale/translations/hi_IN.json b/internal/locale/translations/hi_IN.json index 199c66af1a6..ef7c0c043eb 100644 --- a/internal/locale/translations/hi_IN.json +++ b/internal/locale/translations/hi_IN.json @@ -258,6 +258,7 @@ "alert.no_bookmark": "इस समय कोई बुकमार्क नहीं है", "alert.no_category": "कोई श्रेणी नहीं है।", "alert.no_category_entry": "इस श्रेणी में कोई विषय-वस्तु नहीं है।", + "alert.no_tag_entry": "इस टैग से मेल खाती कोई प्रविष्टियाँ नहीं हैं।", "alert.no_feed_entry": "इस फ़ीड के लिए कोई विषय-वस्तु नहीं है।", "alert.no_feed": "आपके पास कोई सदस्यता नहीं है।", "alert.no_feed_in_category": "इस श्रेणी के लिए कोई सदस्यता नहीं है।", diff --git a/internal/locale/translations/id_ID.json b/internal/locale/translations/id_ID.json index ee06ac61f34..3f1e3cd3ade 100644 --- a/internal/locale/translations/id_ID.json +++ b/internal/locale/translations/id_ID.json @@ -248,6 +248,7 @@ "alert.no_bookmark": "Tidak ada markah.", "alert.no_category": "Tidak ada kategori.", "alert.no_category_entry": "Tidak ada artikel di kategori ini.", + "alert.no_tag_entry": "Tidak ada entri yang cocok dengan tag ini.", "alert.no_feed_entry": "Tidak ada artikel di umpan ini.", "alert.no_feed": "Anda tidak memiliki langganan.", "alert.no_feed_in_category": "Tidak ada langganan untuk kategori ini.", diff --git a/internal/locale/translations/it_IT.json b/internal/locale/translations/it_IT.json index 6e808a02053..e9385c7c320 100644 --- a/internal/locale/translations/it_IT.json +++ b/internal/locale/translations/it_IT.json @@ -258,6 +258,7 @@ "alert.no_bookmark": "Nessun preferito disponibile.", "alert.no_category": "Nessuna categoria disponibile.", "alert.no_category_entry": "Questa categoria non contiene alcun articolo.", + "alert.no_tag_entry": "Non ci sono voci corrispondenti a questo tag.", "alert.no_feed_entry": "Questo feed non contiene alcun articolo.", "alert.no_feed": "Nessun feed disponibile.", "alert.no_feed_in_category": "Non esiste un abbonamento per questa categoria.", diff --git a/internal/locale/translations/ja_JP.json b/internal/locale/translations/ja_JP.json index 8c767b55162..efd2d37dca8 100644 --- a/internal/locale/translations/ja_JP.json +++ b/internal/locale/translations/ja_JP.json @@ -248,6 +248,7 @@ "alert.no_bookmark": "現在星付きはありません。", "alert.no_category": "カテゴリが存在しません。", "alert.no_category_entry": "このカテゴリには記事がありません。", + "alert.no_tag_entry": "このタグに一致するエントリーはありません。", "alert.no_feed_entry": "このフィードには記事がありません。", "alert.no_feed": "何も購読していません。", "alert.no_feed_in_category": "このカテゴリには購読中のフィードがありません。", diff --git a/internal/locale/translations/nl_NL.json b/internal/locale/translations/nl_NL.json index d47510e9518..d9141819e67 100644 --- a/internal/locale/translations/nl_NL.json +++ b/internal/locale/translations/nl_NL.json @@ -258,6 +258,7 @@ "alert.no_bookmark": "Er zijn op dit moment geen favorieten.", "alert.no_category": "Er zijn geen categorieën.", "alert.no_category_entry": "Deze categorie bevat geen feeds.", + "alert.no_tag_entry": "Er zijn geen items die overeenkomen met deze tag.", "alert.no_feed_entry": "Er zijn geen artikelen in deze feed.", "alert.no_feed": "Je hebt nog geen feeds geabboneerd staan.", "alert.no_feed_in_category": "Er is geen abonnement voor deze categorie.", diff --git a/internal/locale/translations/pl_PL.json b/internal/locale/translations/pl_PL.json index 32f3e4f8ceb..3b39301d204 100644 --- a/internal/locale/translations/pl_PL.json +++ b/internal/locale/translations/pl_PL.json @@ -268,6 +268,7 @@ "alert.no_bookmark": "Obecnie nie ma żadnych zakładek.", "alert.no_category": "Nie ma żadnej kategorii!", "alert.no_category_entry": "W tej kategorii nie ma żadnych artykułów", + "alert.no_tag_entry": "Nie ma wpisów pasujących do tego tagu.", "alert.no_feed_entry": "Nie ma artykułu dla tego kanału.", "alert.no_feed": "Nie masz żadnej subskrypcji.", "alert.no_feed_in_category": "Nie ma subskrypcji dla tej kategorii.", diff --git a/internal/locale/translations/pt_BR.json b/internal/locale/translations/pt_BR.json index 56861a3a080..7c639f5ba5a 100644 --- a/internal/locale/translations/pt_BR.json +++ b/internal/locale/translations/pt_BR.json @@ -258,6 +258,7 @@ "alert.no_bookmark": "Não há favorito neste momento.", "alert.no_category": "Não há categoria.", "alert.no_category_entry": "Não há itens nesta categoria.", + "alert.no_tag_entry": "Não há itens que correspondam a esta etiqueta.", "alert.no_feed_entry": "Não há itens nessa fonte.", "alert.no_feed": "Não há inscrições.", "alert.no_feed_in_category": "Não há inscrições nessa categoria.", diff --git a/internal/locale/translations/ru_RU.json b/internal/locale/translations/ru_RU.json index 69c139f0183..6327c09f480 100644 --- a/internal/locale/translations/ru_RU.json +++ b/internal/locale/translations/ru_RU.json @@ -268,6 +268,7 @@ "alert.no_bookmark": "Избранное отсутствует.", "alert.no_category": "Категории отсутствуют.", "alert.no_category_entry": "В этой категории нет статей.", + "alert.no_tag_entry": "Нет записей, соответствующих этому тегу.", "alert.no_feed_entry": "В этой подписке отсутствуют статьи.", "alert.no_feed": "У вас нет ни одной подписки.", "alert.no_feed_in_category": "Для этой категории нет подписки.", diff --git a/internal/locale/translations/tr_TR.json b/internal/locale/translations/tr_TR.json index 15fe4c8cf13..4fc999a251f 100644 --- a/internal/locale/translations/tr_TR.json +++ b/internal/locale/translations/tr_TR.json @@ -18,6 +18,7 @@ "alert.no_bookmark": "Yıldızlanmış makale yok.", "alert.no_category": "Hiç kategori yok.", "alert.no_category_entry": "Bu kategoride hiç makele yok.", + "alert.no_tag_entry": "Bu etiketle eşleşen hiçbir giriş yok.", "alert.no_feed": "Hiç beslemeniz yok.", "alert.no_feed_entry": "Bu besleme için makele yok.", "alert.no_feed_in_category": "Bu kategori için besleme yok.", diff --git a/internal/locale/translations/uk_UA.json b/internal/locale/translations/uk_UA.json index eeb305d73b4..832a14710c2 100644 --- a/internal/locale/translations/uk_UA.json +++ b/internal/locale/translations/uk_UA.json @@ -268,6 +268,7 @@ "alert.no_bookmark": "Наразі закладки відсутні.", "alert.no_category": "Немає категорії.", "alert.no_category_entry": "У цій категорії немає записів.", + "alert.no_tag_entry": "Немає записів, що відповідають цьому тегу.", "alert.no_feed_entry": "У цій стрічці немає записів.", "alert.no_feed": "У вас немає підписок.", "alert.no_feed_in_category": "У цій категорії немає підписок.", diff --git a/internal/locale/translations/zh_CN.json b/internal/locale/translations/zh_CN.json index dc1079c420f..b32a270a52e 100644 --- a/internal/locale/translations/zh_CN.json +++ b/internal/locale/translations/zh_CN.json @@ -248,6 +248,7 @@ "alert.no_bookmark": "目前没有收藏", "alert.no_category": "目前没有分类", "alert.no_category_entry": "该分类下没有文章", + "alert.no_tag_entry": "没有与此标签匹配的条目。", "alert.no_feed_entry": "该源中没有文章", "alert.no_feed": "目前没有源", "alert.no_history": "目前没有历史", diff --git a/internal/locale/translations/zh_TW.json b/internal/locale/translations/zh_TW.json index 4b4316f3b0e..39504b73c78 100644 --- a/internal/locale/translations/zh_TW.json +++ b/internal/locale/translations/zh_TW.json @@ -248,6 +248,7 @@ "alert.no_bookmark": "目前沒有收藏", "alert.no_category": "目前沒有分類", "alert.no_category_entry": "該分類下沒有文章", + "alert.no_tag_entry": "沒有與此標籤相符的條目。", "alert.no_feed_entry": "該Feed中沒有文章", "alert.no_feed": "目前沒有Feed", "alert.no_history": "目前沒有歷史", diff --git a/internal/storage/entry_pagination_builder.go b/internal/storage/entry_pagination_builder.go index bab478d392d..9779f245eaf 100644 --- a/internal/storage/entry_pagination_builder.go +++ b/internal/storage/entry_pagination_builder.go @@ -58,6 +58,15 @@ func (e *EntryPaginationBuilder) WithStatus(status string) { } } +func (e *EntryPaginationBuilder) WithTags(tags []string) { + if len(tags) > 0 { + for _, tag := range tags { + e.conditions = append(e.conditions, fmt.Sprintf("LOWER($%d) = ANY(LOWER(e.tags::text)::text[])", len(e.args)+1)) + e.args = append(e.args, tag) + } + } +} + // WithGloballyVisible adds global visibility to the condition. func (e *EntryPaginationBuilder) WithGloballyVisible() { e.conditions = append(e.conditions, "not c.hide_globally") diff --git a/internal/storage/entry_query_builder.go b/internal/storage/entry_query_builder.go index 680fbedb25e..9ab26738508 100644 --- a/internal/storage/entry_query_builder.go +++ b/internal/storage/entry_query_builder.go @@ -160,7 +160,7 @@ func (e *EntryQueryBuilder) WithStatuses(statuses []string) *EntryQueryBuilder { func (e *EntryQueryBuilder) WithTags(tags []string) *EntryQueryBuilder { if len(tags) > 0 { for _, cat := range tags { - e.conditions = append(e.conditions, fmt.Sprintf("$%d = ANY(e.tags)", len(e.args)+1)) + e.conditions = append(e.conditions, fmt.Sprintf("LOWER($%d) = ANY(LOWER(e.tags::text)::text[])", len(e.args)+1)) e.args = append(e.args, cat) } } diff --git a/internal/template/functions.go b/internal/template/functions.go index 54e787cf50d..cfbfc53d785 100644 --- a/internal/template/functions.go +++ b/internal/template/functions.go @@ -8,6 +8,7 @@ import ( "html/template" "math" "net/mail" + "net/url" "slices" "strings" "time" @@ -91,8 +92,9 @@ func (f *funcMap) Map() template.FuncMap { "nonce": func() string { return crypto.GenerateRandomStringHex(16) }, - "deRef": func(i *int) int { return *i }, - "duration": duration, + "deRef": func(i *int) int { return *i }, + "duration": duration, + "urlEncode": url.PathEscape, // These functions are overrode at runtime after the parsing. "elapsed": func(timezone string, t time.Time) string { diff --git a/internal/template/templates/views/entry.html b/internal/template/templates/views/entry.html index 4284f097013..7b745d46396 100644 --- a/internal/template/templates/views/entry.html +++ b/internal/template/templates/views/entry.html @@ -135,7 +135,7 @@

{{ if .entry.Tags }} {{ end }}