diff options
author | Frédéric Guillot <fred@miniflux.net> | 2019-11-17 19:44:12 -0800 |
---|---|---|
committer | Frédéric Guillot <fred@miniflux.net> | 2019-11-17 20:10:44 -0800 |
commit | fad9ad2be4fc800f8710e2a498cc8f536af8827c (patch) | |
tree | 8bc4f134b63f100442850bd9eb443f03fffcf7e6 | |
parent | 15fe9c20df7eaab4c1e10461f1a9965eeaf85f0f (diff) |
Display list of feeds per category
-rw-r--r-- | locale/translations.go | 36 | ||||
-rw-r--r-- | locale/translations/de_DE.json | 2 | ||||
-rw-r--r-- | locale/translations/en_US.json | 2 | ||||
-rw-r--r-- | locale/translations/es_ES.json | 2 | ||||
-rw-r--r-- | locale/translations/fr_FR.json | 2 | ||||
-rw-r--r-- | locale/translations/it_IT.json | 2 | ||||
-rw-r--r-- | locale/translations/nl_NL.json | 2 | ||||
-rw-r--r-- | locale/translations/pl_PL.json | 2 | ||||
-rw-r--r-- | locale/translations/ru_RU.json | 2 | ||||
-rw-r--r-- | locale/translations/zh_CN.json | 2 | ||||
-rw-r--r-- | storage/feed.go | 42 | ||||
-rw-r--r-- | template/common.go | 57 | ||||
-rw-r--r-- | template/html/categories.html | 9 | ||||
-rw-r--r-- | template/html/category_entries.html | 3 | ||||
-rw-r--r-- | template/html/category_feeds.html | 34 | ||||
-rw-r--r-- | template/html/common/feed_list.html | 56 | ||||
-rw-r--r-- | template/html/edit_category.html | 5 | ||||
-rw-r--r-- | template/html/feeds.html | 55 | ||||
-rw-r--r-- | template/views.go | 116 | ||||
-rw-r--r-- | ui/category_feeds.go | 52 | ||||
-rw-r--r-- | ui/category_update.go | 2 | ||||
-rw-r--r-- | ui/ui.go | 1 |
22 files changed, 345 insertions, 141 deletions
diff --git a/locale/translations.go b/locale/translations.go index b0e09b3..fa04cde 100644 --- a/locale/translations.go +++ b/locale/translations.go @@ -76,6 +76,7 @@ var translations = map[string]string{ "page.starred.title": "Lesezeichen", "page.categories.title": "Kategorien", "page.categories.no_feed": "Kein Abonnement.", + "page.categories.feeds": "Siehe Abonnements", "page.categories.feed_count": [ "Es gibt %d Abonnement.", "Es gibt %d Abonnements." @@ -175,6 +176,7 @@ var translations = map[string]string{ "alert.no_category_entry": "Es befindet sich kein Artikel in dieser Kategorie.", "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.", "alert.no_history": "Es existiert zur Zeit kein Verlauf.", "alert.feed_error": "Es gibt ein Problem mit diesem Abonnement", "alert.no_search_result": "Es gibt kein Ergebnis für diese Suche.", @@ -379,6 +381,7 @@ var translations = map[string]string{ "page.starred.title": "Starred", "page.categories.title": "Categories", "page.categories.no_feed": "No feed.", + "page.categories.feeds": "See subscriptions", "page.categories.feed_count": [ "There is %d feed.", "There are %d feeds." @@ -478,6 +481,7 @@ var translations = map[string]string{ "alert.no_category_entry": "There are no articles in this category.", "alert.no_feed_entry": "There are no articles for this feed.", "alert.no_feed": "You don't have any subscriptions.", + "alert.no_feed_in_category": "There is no subscription for this category.", "alert.no_history": "There is no history at the moment.", "alert.feed_error": "There is a problem with this feed", "alert.no_search_result": "There are no results for this search.", @@ -662,6 +666,7 @@ var translations = map[string]string{ "page.starred.title": "Marcadores", "page.categories.title": "Categorias", "page.categories.no_feed": "No fuente.", + "page.categories.feeds": "Ver suscripciones", "page.categories.feed_count": [ "Hay %d fuente.", "Hay %d fuentes." @@ -761,6 +766,7 @@ var translations = map[string]string{ "alert.no_category_entry": "No hay artículos en esta categoria.", "alert.no_feed_entry": "No hay artículos para esta fuente.", "alert.no_feed": "No tienes suscripciones.", + "alert.no_feed_in_category": "No hay suscripción para esta categoría.", "alert.no_history": "No hay historial en este momento.", "alert.feed_error": "Hay un problema con esta fuente.", "alert.no_search_result": "No hay resultados para esta búsqueda.", @@ -945,6 +951,7 @@ var translations = map[string]string{ "page.starred.title": "Favoris", "page.categories.title": "Catégories", "page.categories.no_feed": "Aucun abonnement.", + "page.categories.feeds": "Voir les abonnements", "page.categories.feed_count": [ "Il y a %d abonnement.", "Il y a %d abonnements." @@ -1044,6 +1051,7 @@ var translations = map[string]string{ "alert.no_category_entry": "Il n'y a aucun article dans cette catégorie.", "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.", "alert.no_history": "Il n'y a aucun historique pour le moment.", "alert.feed_error": "Il y a un problème avec cet abonnement", "alert.no_search_result": "Il n'y a aucun résultat pour cette recherche.", @@ -1248,6 +1256,7 @@ var translations = map[string]string{ "page.starred.title": "Preferiti", "page.categories.title": "Categorie", "page.categories.no_feed": "Nessun feed.", + "page.categories.feeds": "Vedi abbonamenti", "page.categories.feed_count": [ "C'è %d feed.", "Ci sono %d feed." @@ -1347,6 +1356,7 @@ var translations = map[string]string{ "alert.no_category_entry": "Questa categoria non contiene alcun articolo.", "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.", "alert.no_history": "La tua cronologia al momento è vuota.", "alert.feed_error": "Sembra ci sia un problema con questo feed", "alert.no_search_result": "La ricerca non ha prodotto risultati.", @@ -1531,6 +1541,7 @@ var translations = map[string]string{ "page.starred.title": "Favorieten", "page.categories.title": "Categorieën", "page.categories.no_feed": "Geen feeds.", + "page.categories.feeds": "Zie abonnementen", "page.categories.feed_count": [ "Er is %d feed.", "Er zijn %d feeds." @@ -1630,6 +1641,7 @@ var translations = map[string]string{ "alert.no_category_entry": "Deze categorie bevat geen feeds.", "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.", "alert.no_history": "Geschiedenis is op dit moment leeg.", "alert.feed_error": "Er is een probleem met deze feed", "alert.no_search_result": "Er is geen resultaat voor deze zoekopdracht.", @@ -1832,6 +1844,7 @@ var translations = map[string]string{ "page.starred.title": "Oznaczone gwiazdką", "page.categories.title": "Kategorie", "page.categories.no_feed": "Brak kanałów.", + "page.categories.feeds": "Zobacz subskrypcje", "page.categories.feed_count": [ "Jest %d kanał.", "Są %d kanały.", @@ -1933,6 +1946,7 @@ var translations = map[string]string{ "alert.no_category_entry": "W tej kategorii nie ma żadnych artykułów", "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.", "alert.no_history": "Obecnie nie ma żadnej historii.", "alert.feed_error": "Z tym kanałem jest problem", "alert.no_search_result": "Brak wyników dla tego wyszukiwania.", @@ -2141,6 +2155,7 @@ var translations = map[string]string{ "page.starred.title": "Избранное", "page.categories.title": "Категории", "page.categories.no_feed": "Нет подписок.", + "page.categories.feeds": "Посмотреть подписку", "page.categories.feed_count": [ "Есть %d подписка.", "Есть %d подписки.", @@ -2242,6 +2257,7 @@ var translations = map[string]string{ "alert.no_category_entry": "В этой категории нет статей.", "alert.no_feed_entry": "В этой подписке отсутствуют статьи.", "alert.no_feed": "У вас нет ни одной подписки.", + "alert.no_feed_in_category": "Для этой категории нет подписки.", "alert.no_history": "Истории пока нет.", "alert.feed_error": "С этой подпиской есть проблема", "alert.no_search_result": "Нет результатов для данного поискового запроса.", @@ -2432,6 +2448,7 @@ var translations = map[string]string{ "page.starred.title": "星标", "page.categories.title": "分类", "page.categories.no_feed": "没有源", + "page.categories.feeds": "查看订阅", "page.categories.feed_count": [ "有 %d 个源" ], @@ -2532,6 +2549,7 @@ var translations = map[string]string{ "alert.no_history": "目前没有历史", "alert.feed_error": "该源存在问题", "alert.no_search_result": "该搜索没有结果", + "alert.no_feed_in_category": "没有该类别的订阅。", "alert.no_unread_entry": "目前没有未读文章", "alert.no_user": "您是目前仅有的用户", "alert.account_unlinked": "您的外部帐户现已解除关联!", @@ -2656,13 +2674,13 @@ var translations = map[string]string{ } var translationsChecksums = map[string]string{ - "de_DE": "ec2dd4be11e4bb29efaa6cd124a1edd2e5271889d31d7fda92781be014388387", - "en_US": "8010481cea76d28aad37c00fb0f481514f81c1581a6172b4a4ad17ad61e2eee0", - "es_ES": "be58c4452068277826022931d86bd561fe150250756a6254985de5aa6d8129b7", - "fr_FR": "4d3fa6084994a7b3121dd9c1f3baf8c1b0e519f6012aeaa805e5132ec1eaa60e", - "it_IT": "3246b020b7ea01f762f19c9ee2825605b0e42f6ffdd34fd6306193597650f8d7", - "nl_NL": "c58d1100bcd345824086d0df255381f7789379d0e2b95e146be009ad82e0aa5f", - "pl_PL": "b438c4119ed5685950293f5c3c629a940c6475ca243c53f65879dfca6fc8cdd6", - "ru_RU": "5b42510b54c678563791010f4c1fad1a024fa7029d268667bbfde1e7a1f02d88", - "zh_CN": "30f72b341911682877bb86b0e82e9127be833625502ae41e9530447bb2f27de3", + "de_DE": "08618fb4a57b1d427ec627ac6c46958bbe16b262a588aa640ad5be8b5e15562d", + "en_US": "a611f133d106bb896bbe15df036838f33b7c7848abbf15829e918233e91783eb", + "es_ES": "f47862bcd9af07d96510c3c24f89f0718f1325cde375954babe73766c62a6eca", + "fr_FR": "0aca382d06630935b905452960a8dceac6f062cd5ebe2967b3837006a282f171", + "it_IT": "60716683ca6d6e311154900508c0f8c389664096f3ab41eb9dd745914d497034", + "nl_NL": "516c91a2be0f0b5c09e0183d61e53bfce2c8f60b8a5cb06593f0170ad6043f99", + "pl_PL": "1fcf9422514fc7ebac57355a50c37d146fad6d19b879ba0dad29c49dfb20e00a", + "ru_RU": "e701297d7c1b456dda066877bc5c6d18c9523f96eff3b4d53d13c6e6bf8f84a6", + "zh_CN": "4da796ef2fdaf1898d2a17be6668b8308daebc97185bf69a698809067856c6ce", } diff --git a/locale/translations/de_DE.json b/locale/translations/de_DE.json index 83325e0..4960836 100644 --- a/locale/translations/de_DE.json +++ b/locale/translations/de_DE.json @@ -71,6 +71,7 @@ "page.starred.title": "Lesezeichen", "page.categories.title": "Kategorien", "page.categories.no_feed": "Kein Abonnement.", + "page.categories.feeds": "Siehe Abonnements", "page.categories.feed_count": [ "Es gibt %d Abonnement.", "Es gibt %d Abonnements." @@ -170,6 +171,7 @@ "alert.no_category_entry": "Es befindet sich kein Artikel in dieser Kategorie.", "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.", "alert.no_history": "Es existiert zur Zeit kein Verlauf.", "alert.feed_error": "Es gibt ein Problem mit diesem Abonnement", "alert.no_search_result": "Es gibt kein Ergebnis für diese Suche.", diff --git a/locale/translations/en_US.json b/locale/translations/en_US.json index c86e3e2..2153243 100644 --- a/locale/translations/en_US.json +++ b/locale/translations/en_US.json @@ -71,6 +71,7 @@ "page.starred.title": "Starred", "page.categories.title": "Categories", "page.categories.no_feed": "No feed.", + "page.categories.feeds": "See subscriptions", "page.categories.feed_count": [ "There is %d feed.", "There are %d feeds." @@ -170,6 +171,7 @@ "alert.no_category_entry": "There are no articles in this category.", "alert.no_feed_entry": "There are no articles for this feed.", "alert.no_feed": "You don't have any subscriptions.", + "alert.no_feed_in_category": "There is no subscription for this category.", "alert.no_history": "There is no history at the moment.", "alert.feed_error": "There is a problem with this feed", "alert.no_search_result": "There are no results for this search.", diff --git a/locale/translations/es_ES.json b/locale/translations/es_ES.json index ba8f076..de2912a 100644 --- a/locale/translations/es_ES.json +++ b/locale/translations/es_ES.json @@ -71,6 +71,7 @@ "page.starred.title": "Marcadores", "page.categories.title": "Categorias", "page.categories.no_feed": "No fuente.", + "page.categories.feeds": "Ver suscripciones", "page.categories.feed_count": [ "Hay %d fuente.", "Hay %d fuentes." @@ -170,6 +171,7 @@ "alert.no_category_entry": "No hay artículos en esta categoria.", "alert.no_feed_entry": "No hay artículos para esta fuente.", "alert.no_feed": "No tienes suscripciones.", + "alert.no_feed_in_category": "No hay suscripción para esta categoría.", "alert.no_history": "No hay historial en este momento.", "alert.feed_error": "Hay un problema con esta fuente.", "alert.no_search_result": "No hay resultados para esta búsqueda.", diff --git a/locale/translations/fr_FR.json b/locale/translations/fr_FR.json index 052913c..9df25d6 100644 --- a/locale/translations/fr_FR.json +++ b/locale/translations/fr_FR.json @@ -71,6 +71,7 @@ "page.starred.title": "Favoris", "page.categories.title": "Catégories", "page.categories.no_feed": "Aucun abonnement.", + "page.categories.feeds": "Voir les abonnements", "page.categories.feed_count": [ "Il y a %d abonnement.", "Il y a %d abonnements." @@ -170,6 +171,7 @@ "alert.no_category_entry": "Il n'y a aucun article dans cette catégorie.", "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.", "alert.no_history": "Il n'y a aucun historique pour le moment.", "alert.feed_error": "Il y a un problème avec cet abonnement", "alert.no_search_result": "Il n'y a aucun résultat pour cette recherche.", diff --git a/locale/translations/it_IT.json b/locale/translations/it_IT.json index 60daa20..9d84f96 100644 --- a/locale/translations/it_IT.json +++ b/locale/translations/it_IT.json @@ -71,6 +71,7 @@ "page.starred.title": "Preferiti", "page.categories.title": "Categorie", "page.categories.no_feed": "Nessun feed.", + "page.categories.feeds": "Vedi abbonamenti", "page.categories.feed_count": [ "C'è %d feed.", "Ci sono %d feed." @@ -170,6 +171,7 @@ "alert.no_category_entry": "Questa categoria non contiene alcun articolo.", "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.", "alert.no_history": "La tua cronologia al momento è vuota.", "alert.feed_error": "Sembra ci sia un problema con questo feed", "alert.no_search_result": "La ricerca non ha prodotto risultati.", diff --git a/locale/translations/nl_NL.json b/locale/translations/nl_NL.json index 87e2e08..0765a7d 100644 --- a/locale/translations/nl_NL.json +++ b/locale/translations/nl_NL.json @@ -71,6 +71,7 @@ "page.starred.title": "Favorieten", "page.categories.title": "Categorieën", "page.categories.no_feed": "Geen feeds.", + "page.categories.feeds": "Zie abonnementen", "page.categories.feed_count": [ "Er is %d feed.", "Er zijn %d feeds." @@ -170,6 +171,7 @@ "alert.no_category_entry": "Deze categorie bevat geen feeds.", "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.", "alert.no_history": "Geschiedenis is op dit moment leeg.", "alert.feed_error": "Er is een probleem met deze feed", "alert.no_search_result": "Er is geen resultaat voor deze zoekopdracht.", diff --git a/locale/translations/pl_PL.json b/locale/translations/pl_PL.json index 9fe141b..413945b 100644 --- a/locale/translations/pl_PL.json +++ b/locale/translations/pl_PL.json @@ -71,6 +71,7 @@ "page.starred.title": "Oznaczone gwiazdką", "page.categories.title": "Kategorie", "page.categories.no_feed": "Brak kanałów.", + "page.categories.feeds": "Zobacz subskrypcje", "page.categories.feed_count": [ "Jest %d kanał.", "Są %d kanały.", @@ -172,6 +173,7 @@ "alert.no_category_entry": "W tej kategorii nie ma żadnych artykułów", "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.", "alert.no_history": "Obecnie nie ma żadnej historii.", "alert.feed_error": "Z tym kanałem jest problem", "alert.no_search_result": "Brak wyników dla tego wyszukiwania.", diff --git a/locale/translations/ru_RU.json b/locale/translations/ru_RU.json index 27ed092..03ba8cb 100644 --- a/locale/translations/ru_RU.json +++ b/locale/translations/ru_RU.json @@ -71,6 +71,7 @@ "page.starred.title": "Избранное", "page.categories.title": "Категории", "page.categories.no_feed": "Нет подписок.", + "page.categories.feeds": "Посмотреть подписку", "page.categories.feed_count": [ "Есть %d подписка.", "Есть %d подписки.", @@ -172,6 +173,7 @@ "alert.no_category_entry": "В этой категории нет статей.", "alert.no_feed_entry": "В этой подписке отсутствуют статьи.", "alert.no_feed": "У вас нет ни одной подписки.", + "alert.no_feed_in_category": "Для этой категории нет подписки.", "alert.no_history": "Истории пока нет.", "alert.feed_error": "С этой подпиской есть проблема", "alert.no_search_result": "Нет результатов для данного поискового запроса.", diff --git a/locale/translations/zh_CN.json b/locale/translations/zh_CN.json index 8748695..da0febc 100644 --- a/locale/translations/zh_CN.json +++ b/locale/translations/zh_CN.json @@ -71,6 +71,7 @@ "page.starred.title": "星标", "page.categories.title": "分类", "page.categories.no_feed": "没有源", + "page.categories.feeds": "查看订阅", "page.categories.feed_count": [ "有 %d 个源" ], @@ -171,6 +172,7 @@ "alert.no_history": "目前没有历史", "alert.feed_error": "该源存在问题", "alert.no_search_result": "该搜索没有结果", + "alert.no_feed_in_category": "没有该类别的订阅。", "alert.no_unread_entry": "目前没有未读文章", "alert.no_user": "您是目前仅有的用户", "alert.account_unlinked": "您的外部帐户现已解除关联!", diff --git a/storage/feed.go b/storage/feed.go index f6ad021..6db3488 100644 --- a/storage/feed.go +++ b/storage/feed.go @@ -139,7 +139,6 @@ func (s *Storage) Feeds(userID int64) (model.Feeds, error) { // FeedsWithCounters returns all feeds of the given user with counters of read and unread entries. func (s *Storage) FeedsWithCounters(userID int64) (model.Feeds, error) { - feeds := make(model.Feeds, 0) query := ` SELECT f.id, @@ -166,7 +165,43 @@ func (s *Storage) FeedsWithCounters(userID int64) (model.Feeds, error) { f.user_id=$1 ORDER BY f.parsing_error_count DESC, unread_count DESC, lower(f.title) ASC ` - rows, err := s.db.Query(query, userID) + return s.fetchFeedsWithCounters(query, userID) +} + +// FeedsByCategoryWithCounters returns all feeds of the given user/category with counters of read and unread entries. +func (s *Storage) FeedsByCategoryWithCounters(userID, categoryID int64) (model.Feeds, error) { + query := ` + SELECT + f.id, + f.feed_url, + f.site_url, + f.title, + f.etag_header, + f.last_modified_header, + f.user_id, + f.checked_at at time zone u.timezone, + f.parsing_error_count, f.parsing_error_msg, + f.scraper_rules, f.rewrite_rules, f.crawler, f.user_agent, + f.username, f.password, f.disabled, + f.category_id, c.title as category_title, + fi.icon_id, + u.timezone, + (SELECT count(*) FROM entries WHERE entries.feed_id=f.id AND status='unread') as unread_count, + (SELECT count(*) FROM entries WHERE entries.feed_id=f.id AND status='read') as read_count + FROM feeds f + LEFT JOIN categories c ON c.id=f.category_id + LEFT JOIN feed_icons fi ON fi.feed_id=f.id + LEFT JOIN users u ON u.id=f.user_id + WHERE + f.user_id=$1 AND f.category_id=$2 + ORDER BY f.parsing_error_count DESC, unread_count DESC, lower(f.title) ASC + ` + return s.fetchFeedsWithCounters(query, userID, categoryID) +} + +func (s *Storage) fetchFeedsWithCounters(query string, args ...interface{}) (model.Feeds, error) { + feeds := make(model.Feeds, 0) + rows, err := s.db.Query(query, args...) if err != nil { return nil, fmt.Errorf(`store: unable to fetch feeds: %v`, err) } @@ -176,7 +211,7 @@ func (s *Storage) FeedsWithCounters(userID int64) (model.Feeds, error) { var feed model.Feed var iconID interface{} var tz string - feed.Category = &model.Category{UserID: userID} + feed.Category = &model.Category{} err := rows.Scan( &feed.ID, @@ -213,6 +248,7 @@ func (s *Storage) FeedsWithCounters(userID int64) (model.Feeds, error) { } feed.CheckedAt = timezone.Convert(tz, feed.CheckedAt) + feed.Category.UserID = feed.UserID feeds = append(feeds, &feed) } diff --git a/template/common.go b/template/common.go index 574e4e1..2539cad 100644 --- a/template/common.go +++ b/template/common.go @@ -22,6 +22,62 @@ var templateCommonMap = map[string]string{ </div> </div> {{ end }}`, + "feed_list": `{{ define "feed_list" }} + <div class="items"> + {{ range .feeds }} + <article class="item {{ if ne .ParsingErrorCount 0 }}feed-parsing-error{{ end }}"> + <div class="item-header"> + <span class="item-title"> + {{ if .Icon }} + <img src="{{ route "icon" "iconID" .Icon.IconID }}" width="16" height="16" loading="lazy" alt="{{ .Title }}"> + {{ end }} + {{ if .Disabled }} 🚫 {{ end }} + <a href="{{ route "feedEntries" "feedID" .ID }}">{{ .Title }}</a> + </span> + <span class="feed-entries-counter"> + (<span title="{{ t "page.feeds.unread_counter" }}">{{ .UnreadCount }}</span>/<span title="{{ t "page.feeds.read_counter" }}">{{ .ReadCount }}</span>) + </span> + <span class="category"> + <a href="{{ route "categoryEntries" "categoryID" .Category.ID }}">{{ .Category.Title }}</a> + </span> + </div> + <div class="item-meta"> + <ul> + <li> + <a href="{{ .SiteURL }}" title="{{ .SiteURL }}" target="_blank" rel="noopener noreferrer" referrerpolicy="no-referrer" data-original-link="true">{{ domain .SiteURL }}</a> + </li> + <li> + {{ t "page.feeds.last_check" }} <time datetime="{{ isodate .CheckedAt }}" title="{{ isodate .CheckedAt }}">{{ elapsed $.user.Timezone .CheckedAt }}</time> + </li> + </ul> + <ul> + <li> + <a href="{{ route "refreshFeed" "feedID" .ID }}">{{ t "menu.refresh_feed" }}</a> + </li> + <li> + <a href="{{ route "editFeed" "feedID" .ID }}">{{ t "menu.edit_feed" }}</a> + </li> + <li> + <a href="#" + data-confirm="true" + data-label-question="{{ t "confirm.question" }}" + data-label-yes="{{ t "confirm.yes" }}" + data-label-no="{{ t "confirm.no" }}" + data-label-loading="{{ t "confirm.loading" }}" + data-url="{{ route "removeFeed" "feedID" .ID }}">{{ t "action.remove" }}</a> + </li> + </ul> + </div> + {{ if ne .ParsingErrorCount 0 }} + <div class="parsing-error"> + <strong title="{{ .ParsingErrorMsg }}" class="parsing-error-count">{{ plural "page.feeds.error_count" .ParsingErrorCount .ParsingErrorCount }}</strong> + - <small class="parsing-error-message">{{ .ParsingErrorMsg }}</small> + </div> + {{ end }} + </article> + {{ end }} + </div> +{{ end }}`, "item_meta": `{{ define "item_meta" }} <div class="item-meta"> <ul> @@ -274,6 +330,7 @@ var templateCommonMap = map[string]string{ var templateCommonMapChecksums = map[string]string{ "entry_pagination": "4faa91e2eae150c5e4eab4d258e039dfdd413bab7602f0009360e6d52898e353", + "feed_list": "7b7ea2c7df07d048c83d86237d5b5e41bddce561273c652d9265950093ca261b", "item_meta": "34deb081a054f2948ad808bdb2c8603d6ab00c58f2f50c4ead0b47ae092888eb", "layout": "010e31c9dde88cb429b21f4b0c24bb3769043a3ef1ef4a57100314f5910c8725", "pagination": "3386e90c6e1230311459e9a484629bc5d5bf39514a75ef2e73bbbc61142f7abb", diff --git a/template/html/categories.html b/template/html/categories.html index b534ba1..4be3c2f 100644 --- a/template/html/categories.html +++ b/template/html/categories.html @@ -20,18 +20,13 @@ <span class="item-title"> <a href="{{ route "categoryEntries" "categoryID" .ID }}">{{ .Title }}</a> </span> + (<span title="{{ if eq .FeedCount 0 }}{{ t "page.categories.no_feed" }}{{ else }}{{ plural "page.categories.feed_count" .FeedCount .FeedCount }}{{ end }}">{{ .FeedCount }}</span>) </div> <div class="item-meta"> <ul> <li> - {{ if eq .FeedCount 0 }} - {{ t "page.categories.no_feed" }} - {{ else }} - {{ plural "page.categories.feed_count" .FeedCount .FeedCount }} - {{ end }} + <a href="{{ route "categoryFeeds" "categoryID" .ID }}">{{ t "page.categories.feeds" }}</a> </li> - </ul> - <ul> <li> <a href="{{ route "editCategory" "categoryID" .ID }}">{{ t "menu.edit_category" }}</a> </li> diff --git a/template/html/category_entries.html b/template/html/category_entries.html index 48bf205..90687fb 100644 --- a/template/html/category_entries.html +++ b/template/html/category_entries.html @@ -24,6 +24,9 @@ <a href="{{ route "categoryEntries" "categoryID" .category.ID }}">{{ t "menu.show_only_unread_entries" }}</a> </li> {{ end }} + <li> + <a href="{{ route "categoryFeeds" "categoryID" .category.ID }}">{{ t "menu.feeds" }}</a> + </li> </ul> </section> diff --git a/template/html/category_feeds.html b/template/html/category_feeds.html new file mode 100644 index 0000000..b903256 --- /dev/null +++ b/template/html/category_feeds.html @@ -0,0 +1,34 @@ +{{ define "title"}}{{ .category.Title }} > {{ t "page.feeds.title" }} ({{ .total }}){{ end }} + +{{ define "content"}} +<section class="page-header"> + <h1>{{ .category.Title }} > {{ t "page.feeds.title" }} ({{ .total }})</h1> + <ul> + <li> + <a href="{{ route "categories" }}">{{ t "menu.categories" }}</a> + </li> + <li> + <a href="{{ route "editCategory" "categoryID" .category.ID }}">{{ t "menu.edit_category" }}</a> + </li> + {{ if eq .total 0 }} + <li> + <a href="#" + data-confirm="true" + data-label-question="{{ t "confirm.question" }}" + data-label-yes="{{ t "confirm.yes" }}" + data-label-no="{{ t "confirm.no" }}" + data-label-loading="{{ t "confirm.loading" }}" + data-redirect-url="{{ route "categories" }}" + data-url="{{ route "removeCategory" "categoryID" .category.ID }}">{{ t "action.remove" }}</a> + </li> + {{ end }} + </ul> +</section> + +{{ if not .feeds }} + <p class="alert">{{ t "alert.no_feed_in_category" }}</p> +{{ else }} + {{ template "feed_list" dict "user" .user "feeds" .feeds "ParsingErrorCount" .ParsingErrorCount }} +{{ end }} + +{{ end }} diff --git a/template/html/common/feed_list.html b/template/html/common/feed_list.html new file mode 100644 index 0000000..cb80a1f --- /dev/null +++ b/template/html/common/feed_list.html @@ -0,0 +1,56 @@ +{{ define "feed_list" }} + <div class="items"> + {{ range .feeds }} + <article class="item {{ if ne .ParsingErrorCount 0 }}feed-parsing-error{{ end }}"> + <div class="item-header"> + <span class="item-title"> + {{ if .Icon }} + <img src="{{ route "icon" "iconID" .Icon.IconID }}" width="16" height="16" loading="lazy" alt="{{ .Title }}"> + {{ end }} + {{ if .Disabled }} 🚫 {{ end }} + <a href="{{ route "feedEntries" "feedID" .ID }}">{{ .Title }}</a> + </span> + <span class="feed-entries-counter"> + (<span title="{{ t "page.feeds.unread_counter" }}">{{ .UnreadCount }}</span>/<span title="{{ t "page.feeds.read_counter" }}">{{ .ReadCount }}</span>) + </span> + <span class="category"> + <a href="{{ route "categoryEntries" "categoryID" .Category.ID }}">{{ .Category.Title }}</a> + </span> + </div> + <div class="item-meta"> + <ul> + <li> + <a href="{{ .SiteURL }}" title="{{ .SiteURL }}" target="_blank" rel="noopener noreferrer" referrerpolicy="no-referrer" data-original-link="true">{{ domain .SiteURL }}</a> + </li> + <li> + {{ t "page.feeds.last_check" }} <time datetime="{{ isodate .CheckedAt }}" title="{{ isodate .CheckedAt }}">{{ elapsed $.user.Timezone .CheckedAt }}</time> + </li> + </ul> + <ul> + <li> + <a href="{{ route "refreshFeed" "feedID" .ID }}">{{ t "menu.refresh_feed" }}</a> + </li> + <li> + <a href="{{ route "editFeed" "feedID" .ID }}">{{ t "menu.edit_feed" }}</a> + </li> + <li> + <a href="#" + data-confirm="true" + data-label-question="{{ t "confirm.question" }}" + data-label-yes="{{ t "confirm.yes" }}" + data-label-no="{{ t "confirm.no" }}" + data-label-loading="{{ t "confirm.loading" }}" + data-url="{{ route "removeFeed" "feedID" .ID }}">{{ t "action.remove" }}</a> + </li> + </ul> + </div> + {{ if ne .ParsingErrorCount 0 }} + <div class="parsing-error"> + <strong title="{{ .ParsingErrorMsg }}" class="parsing-error-count">{{ plural "page.feeds.error_count" .ParsingErrorCount .ParsingErrorCount }}</strong> + - <small class="parsing-error-message">{{ .ParsingErrorMsg }}</small> + </div> + {{ end }} + </article> + {{ end }} + </div> +{{ end }}
\ No newline at end of file diff --git a/template/html/edit_category.html b/template/html/edit_category.html index 6b21e46..3506e45 100644 --- a/template/html/edit_category.html +++ b/template/html/edit_category.html @@ -8,6 +8,9 @@ <a href="{{ route "categories" }}">{{ t "menu.categories" }}</a> </li> <li> + <a href="{{ route "categoryFeeds" "categoryID" .category.ID }}">{{ t "menu.feeds" }}</a> + </li> + <li> <a href="{{ route "createCategory" }}">{{ t "menu.create_category" }}</a> </li> </ul> @@ -24,7 +27,7 @@ <input type="text" name="title" id="form-title" value="{{ .form.Title }}" required autofocus> <div class="buttons"> - <button type="submit" class="button button-primary" data-label-loading="{{ t "form.submit.saving" }}">{{ t "action.update" }}</button> {{ t "action.or" }} <a href="{{ route "categories" }}">{{ t "action.cancel" }}</a> + <button type="submit" class="button button-primary" data-label-loading="{{ t "form.submit.saving" }}">{{ t "action.update" }}</button> </div> </form> {{ end }} diff --git a/template/html/feeds.html b/template/html/feeds.html index 7d4a428..e4d24bf 100644 --- a/template/html/feeds.html +++ b/template/html/feeds.html @@ -22,60 +22,7 @@ {{ if not .feeds }} <p class="alert">{{ t "alert.no_feed" }}</p> {{ else }} - <div class="items"> - {{ range .feeds }} - <article class="item {{ if ne .ParsingErrorCount 0 }}feed-parsing-error{{ end }}"> - <div class="item-header"> - <span class="item-title"> - {{ if .Icon }} - <img src="{{ route "icon" "iconID" .Icon.IconID }}" width="16" height="16" loading="lazy" alt="{{ .Title }}"> - {{ end }} - {{ if .Disabled }} 🚫 {{ end }} - <a href="{{ route "feedEntries" "feedID" .ID }}">{{ .Title }}</a> - </span> - <span class="feed-entries-counter"> - (<span title="{{ t "page.feeds.unread_counter" }}">{{ .UnreadCount }}</span>/<span title="{{ t "page.feeds.read_counter" }}">{{ .ReadCount }}</span>) - </span> - <span class="category"> - <a href="{{ route "categoryEntries" "categoryID" .Category.ID }}">{{ .Category.Title }}</a> - </span> - </div> - <div class="item-meta"> - <ul> - <li> - <a href="{{ .SiteURL }}" title="{{ .SiteURL }}" target="_blank" rel="noopener noreferrer" referrerpolicy="no-referrer" data-original-link="true">{{ domain .SiteURL }}</a> - </li> - <li> - {{ t "page.feeds.last_check" }} <time datetime="{{ isodate .CheckedAt }}" title="{{ isodate .CheckedAt }}">{{ elapsed $.user.Timezone .CheckedAt }}</time> - </li> - </ul> - <ul> - <li> - <a href="{{ route "refreshFeed" "feedID" .ID }}">{{ t "menu.refresh_feed" }}</a> - </li> - <li> - <a href="{{ route "editFeed" "feedID" .ID }}">{{ t "menu.edit_feed" }}</a> - </li> - <li> - <a href="#" - data-confirm="true" - data-label-question="{{ t "confirm.question" }}" - data-label-yes="{{ t "confirm.yes" }}" - data-label-no="{{ t "confirm.no" }}" - data-label-loading="{{ t "confirm.loading" }}" - data-url="{{ route "removeFeed" "feedID" .ID }}">{{ t "action.remove" }}</a> - </li> - </ul> - </div> - {{ if ne .ParsingErrorCount 0 }} - <div class="parsing-error"> - <strong title="{{ .ParsingErrorMsg }}" class="parsing-error-count">{{ plural "page.feeds.error_count" .ParsingErrorCount .ParsingErrorCount }}</strong> - - <small class="parsing-error-message">{{ .ParsingErrorMsg }}</small> - </div> - {{ end }} - </article> - {{ end }} - </div> + {{ template "feed_list" dict "user" .user "feeds" .feeds "ParsingErrorCount" .ParsingErrorCount }} {{ end }} {{ end }} diff --git a/template/views.go b/template/views.go index 788e2f5..c4fadb4 100644 --- a/template/views.go +++ b/template/views.go @@ -151,18 +151,13 @@ var templateViewsMap = map[string]string{ <span class="item-title"> <a href="{{ route "categoryEntries" "categoryID" .ID }}">{{ .Title }}</a> </span> + (<span title="{{ if eq .FeedCount 0 }}{{ t "page.categories.no_feed" }}{{ else }}{{ plural "page.categories.feed_count" .FeedCount .FeedCount }}{{ end }}">{{ .FeedCount }}</span>) </div> <div class="item-meta"> <ul> <li> - {{ if eq .FeedCount 0 }} - {{ t "page.categories.no_feed" }} - {{ else }} - {{ plural "page.categories.feed_count" .FeedCount .FeedCount }} - {{ end }} + <a href="{{ route "categoryFeeds" "categoryID" .ID }}">{{ t "page.categories.feeds" }}</a> </li> - </ul> - <ul> <li> <a href="{{ route "editCategory" "categoryID" .ID }}">{{ t "menu.edit_category" }}</a> </li> @@ -212,6 +207,9 @@ var templateViewsMap = map[string]string{ <a href="{{ route "categoryEntries" "categoryID" .category.ID }}">{{ t "menu.show_only_unread_entries" }}</a> </li> {{ end }} + <li> + <a href="{{ route "categoryFeeds" "categoryID" .category.ID }}">{{ t "menu.feeds" }}</a> + </li> </ul> </section> @@ -254,6 +252,41 @@ var templateViewsMap = map[string]string{ {{ end }} `, + "category_feeds": `{{ define "title"}}{{ .category.Title }} > {{ t "page.feeds.title" }} ({{ .total }}){{ end }} + +{{ define "content"}} +<section class="page-header"> + <h1>{{ .category.Title }} > {{ t "page.feeds.title" }} ({{ .total }})</h1> + <ul> + <li> + <a href="{{ route "categories" }}">{{ t "menu.categories" }}</a> + </li> + <li> + <a href="{{ route "editCategory" "categoryID" .category.ID }}">{{ t "menu.edit_category" }}</a> + </li> + {{ if eq .total 0 }} + <li> + <a href="#" + data-confirm="true" + data-label-question="{{ t "confirm.question" }}" + data-label-yes="{{ t "confirm.yes" }}" + data-label-no="{{ t "confirm.no" }}" + data-label-loading="{{ t "confirm.loading" }}" + data-redirect-url="{{ route "categories" }}" + data-url="{{ route "removeCategory" "categoryID" .category.ID }}">{{ t "action.remove" }}</a> + </li> + {{ end }} + </ul> +</section> + +{{ if not .feeds }} + <p class="alert">{{ t "alert.no_feed_in_category" }}</p> +{{ else }} + {{ template "feed_list" dict "user" .user "feeds" .feeds "ParsingErrorCount" .ParsingErrorCount }} +{{ end }} + +{{ end }} +`, "choose_subscription": `{{ define "title"}}{{ t "page.add_feed.title" }}{{ end }} {{ define "content"}} @@ -367,6 +400,9 @@ var templateViewsMap = map[string]string{ <a href="{{ route "categories" }}">{{ t "menu.categories" }}</a> </li> <li> + <a href="{{ route "categoryFeeds" "categoryID" .category.ID }}">{{ t "menu.feeds" }}</a> + </li> + <li> <a href="{{ route "createCategory" }}">{{ t "menu.create_category" }}</a> </li> </ul> @@ -383,7 +419,7 @@ var templateViewsMap = map[string]string{ <input type="text" name="title" id="form-title" value="{{ .form.Title }}" required autofocus> <div class="buttons"> - <button type="submit" class="button button-primary" data-label-loading="{{ t "form.submit.saving" }}">{{ t "action.update" }}</button> {{ t "action.or" }} <a href="{{ route "categories" }}">{{ t "action.cancel" }}</a> + <button type="submit" class="button button-primary" data-label-loading="{{ t "form.submit.saving" }}">{{ t "action.update" }}</button> </div> </form> {{ end }} @@ -778,60 +814,7 @@ var templateViewsMap = map[string]string{ {{ if not .feeds }} <p class="alert">{{ t "alert.no_feed" }}</p> {{ else }} - <div class="items"> - {{ range .feeds }} - <article class="item {{ if ne .ParsingErrorCount 0 }}feed-parsing-error{{ end }}"> - <div class="item-header"> - <span class="item-title"> - {{ if .Icon }} - <img src="{{ route "icon" "iconID" .Icon.IconID }}" width="16" height="16" loading="lazy" alt="{{ .Title }}"> - {{ end }} - {{ if .Disabled }} 🚫 {{ end }} - <a href="{{ route "feedEntries" "feedID" .ID }}">{{ .Title }}</a> - </span> - <span class="feed-entries-counter"> - (<span title="{{ t "page.feeds.unread_counter" }}">{{ .UnreadCount }}</span>/<span title="{{ t "page.feeds.read_counter" }}">{{ .ReadCount }}</span>) - </span> - <span class="category"> - <a href="{{ route "categoryEntries" "categoryID" .Category.ID }}">{{ .Category.Title }}</a> - </span> - </div> - <div class="item-meta"> - <ul> - <li> - <a href="{{ .SiteURL }}" title="{{ .SiteURL }}" target="_blank" rel="noopener noreferrer" referrerpolicy="no-referrer" data-original-link="true">{{ domain .SiteURL }}</a> - </li> - <li> - {{ t "page.feeds.last_check" }} <time datetime="{{ isodate .CheckedAt }}" title="{{ isodate .CheckedAt }}">{{ elapsed $.user.Timezone .CheckedAt }}</time> - </li> - </ul> - <ul> - <li> - <a href="{{ route "refreshFeed" "feedID" .ID }}">{{ t "menu.refresh_feed" }}</a> - </li> - <li> - <a href="{{ route "editFeed" "feedID" .ID }}">{{ t "menu.edit_feed" }}</a> - </li> - <li> - <a href="#" - data-confirm="true" - data-label-question="{{ t "confirm.question" }}" - data-label-yes="{{ t "confirm.yes" }}" - data-label-no="{{ t "confirm.no" }}" - data-label-loading="{{ t "confirm.loading" }}" - data-url="{{ route "removeFeed" "feedID" .ID }}">{{ t "action.remove" }}</a> - </li> - </ul> - </div> - {{ if ne .ParsingErrorCount 0 }} - <div class="parsing-error"> - <strong title="{{ .ParsingErrorMsg }}" class="parsing-error-count">{{ plural "page.feeds.error_count" .ParsingErrorCount .ParsingErrorCount }}</strong> - - <small class="parsing-error-message">{{ .ParsingErrorMsg }}</small> - </div> - {{ end }} - </article> - {{ end }} - </div> + {{ template "feed_list" dict "user" .user "feeds" .feeds "ParsingErrorCount" .ParsingErrorCount }} {{ end }} {{ end }} @@ -1362,17 +1345,18 @@ var templateViewsMapChecksums = map[string]string{ "about": "4035658497363d7af7f79be83190404eb21ec633fe8ec636bdfc219d9fc78cfc", "add_subscription": "a0f1d2bc02b6adc83dbeae593f74d9b936102cd6dd73302cdbec2137cafdcdd9", "bookmark_entries": "65588da78665699dd3f287f68325e9777d511f1a57fee4131a5bb6d00bb68df8", - "categories": "642ee3cddbd825ee6ab5a77caa0d371096b55de0f1bd4ae3055b8c8a70507d8d", - "category_entries": "3ec30d2cb97f29514ff61898a4f23d2aa73a24b3468b6d410b1c2d18c8808927", + "categories": "2c5dd0ed6355bd5acc393bbf6117d20458b5581aab82036008324f6bbbe2af75", + "category_entries": "dee7b9cd60c6c46f01dd4289940679df31c1fce28ce4aa7249fa459023e1eeb4", + "category_feeds": "527c2ffbc4fcec775071424ba1022ae003525dba53a28cc41f48fb7b30aa984b", "choose_subscription": "33c04843d7c1b608d034e605e52681822fc6d79bc6b900c04915dd9ebae584e2", "create_category": "6b22b5ce51abf4e225e23a79f81be09a7fb90acb265e93a8faf9446dff74018d", "create_user": "9b73a55233615e461d1f07d99ad1d4d3b54532588ab960097ba3e090c85aaf3a", - "edit_category": "daf073d2944a180ce5aaeb80b597eb69597a50dff55a9a1d6cf7938b48d768cb", + "edit_category": "b1c0b38f1b714c5d884edcd61e5b5295a5f1c8b71c469b35391e4dcc97cc6d36", "edit_feed": "34aa0d668b3ea1a1b5fa480c20cebeae729b37010af3bb915d2a9eed73d3b996", "edit_user": "c692db9de1a084c57b93e95a14b041d39bf489846cbb91fc982a62b72b77062a", "entry": "24aeba26ef9a51ce585ca5c4af090f1de7d7bfd7f1e3ff1b63af520e2afa76bd", "feed_entries": "9c70b82f55e4b311eff20be1641733612e3c1b406ce8010861e4c417d97b6dcc", - "feeds": "f11ba1c45cf3966843ddc406d96e048fc8f2235428e10111a1660a141ea2c42f", + "feeds": "fa06cd1e1e3fec79132386972c640a2fe91237f5dba572389d5f45be74545f25", "history_entries": "87e17d39de70eb3fdbc4000326283be610928758eae7924e4b08dcb446f3b6a9", "import": "5eb56cecaa4d369b9acc991a82be7617710c551089a2e99d34ce8b6e5c37df0a", "integrations": "6104ff6ff3ac3c1ae5e850c78250aab6e99e2342a337589f3848459fa333766a", diff --git a/ui/category_feeds.go b/ui/category_feeds.go new file mode 100644 index 0000000..202fc3e --- /dev/null +++ b/ui/category_feeds.go @@ -0,0 +1,52 @@ +// Copyright 2019 Frédéric Guillot. All rights reserved. +// Use of this source code is governed by the Apache 2.0 +// license that can be found in the LICENSE file. + +package ui // import "miniflux.app/ui" + +import ( + "net/http" + + "miniflux.app/http/request" + "miniflux.app/http/response/html" + "miniflux.app/ui/session" + "miniflux.app/ui/view" +) + +func (h *handler) showCategoryFeedsPage(w http.ResponseWriter, r *http.Request) { + user, err := h.store.UserByID(request.UserID(r)) + if err != nil { + html.ServerError(w, r, err) + return + } + + categoryID := request.RouteInt64Param(r, "categoryID") + category, err := h.store.Category(request.UserID(r), categoryID) + if err != nil { + html.ServerError(w, r, err) + return + } + + if category == nil { + html.NotFound(w, r) + return + } + + feeds, err := h.store.FeedsByCategoryWithCounters(user.ID, categoryID) + if err != nil { + html.ServerError(w, r, err) + return + } + + sess := session.New(h.store, request.SessionID(r)) + view := view.New(h.tpl, r, sess) + view.Set("category", category) + view.Set("feeds", feeds) + view.Set("total", len(feeds)) + view.Set("menu", "categories") + view.Set("user", user) + view.Set("countUnread", h.store.CountUnreadEntries(user.ID)) + view.Set("countErrorFeeds", h.store.CountErrorFeeds(user.ID)) + + html.OK(w, r, view.Render("category_feeds")) +} diff --git a/ui/category_update.go b/ui/category_update.go index 026f991..591063a 100644 --- a/ui/category_update.go +++ b/ui/category_update.go @@ -66,5 +66,5 @@ func (h *handler) updateCategory(w http.ResponseWriter, r *http.Request) { return } - html.Redirect(w, r, route.Path(h.router, "categories")) + html.Redirect(w, r, route.Path(h.router, "categoryFeeds", "categoryID", categoryID)) } @@ -74,6 +74,7 @@ func Serve(router *mux.Router, store *storage.Storage, pool *worker.Pool, feedHa uiRouter.HandleFunc("/categories", handler.showCategoryListPage).Name("categories").Methods("GET") uiRouter.HandleFunc("/category/create", handler.showCreateCategoryPage).Name("createCategory").Methods("GET") uiRouter.HandleFunc("/category/save", handler.saveCategory).Name("saveCategory").Methods("POST") + uiRouter.HandleFunc("/category/{categoryID}/feeds", handler.showCategoryFeedsPage).Name("categoryFeeds").Methods("GET") uiRouter.HandleFunc("/category/{categoryID}/entries", handler.showCategoryEntriesPage).Name("categoryEntries").Methods("GET") uiRouter.HandleFunc("/category/{categoryID}/entries/all", handler.showCategoryEntriesAllPage).Name("categoryEntriesAll").Methods("GET") uiRouter.HandleFunc("/category/{categoryID}/edit", handler.showEditCategoryPage).Name("editCategory").Methods("GET") |