我使用Miniflux作为我的 feed 阅读器已经有几年了。它是有意简约的,因此缺乏某些功能。我非常怀念的一件事是能够按照条目阅读时间等各种标准对提要条目进行排序。值得庆幸的是,Miniflux 允许用户通过设置注入自定义 JavaScript 代码来自定义 UI。所以我把这个掌握在自己手中:
以下是不同排序选项的含义:
- 默认
- 未应用自定义排序。默认为 Miniflux 设置中指定的排序顺序。
- 随机的
- 随机排序。每次刷新都会发生变化。
- 网址
- 按条目 URL 的字母顺序排序。对于查找重复条目很有用。
- 最古老的
- 按条目发布日期排序,最旧的排在前面。
- 最新
- 按条目发布日期排序,最新的排在前面。
- 最长
- 按条目预计阅读时间排序,最长的排在前面。阅读时间未知的条目排序在最后。
- 最短
- 按条目预计阅读时间排序,最短的排在前面。阅读时间未知的条目排序在最后。
- 最少
- 按 Feed 名称分组,按 Feed 中的条目数排序,最少的在前。条目按默认排序顺序排序。
- 最多
- 按 Feed 名称分组,按 Feed 中的条目数排序,最先排序。条目按默认排序顺序排序。
- 喂养
- 按 Feed 名称分组和排序,条目按默认排序顺序排序。
- 类别
- 按类别名称然后提要名称进行分组和排序,条目按默认排序顺序排序。
排序选项不会出现在“历史记录”页面上。按提要和类别排序的选项不会出现在提要特定页面上。按类别排序的选项不会出现在类别特定页面上。
此外,为任何页面选择的选项都会记住在浏览器的本地存储中,因此当您返回页面时,您会发现它按您上次选择的相同选项排序。
这是自定义 JavaScript:
( function () { " use strict " ; function entryFeed ( e ) { return e . querySelector ( " .item-meta-info-title a " ). getAttribute ( " title " ); } function entryCategory ( e ) { return e . querySelector ( " .category " ). innerText ; } function entryTime ( e ) { return e . querySelector ( " .item-meta-info-timestamp time " ). dateTime ; } function entryUrl ( e ) { return e . querySelector ( " .item-meta-icons-external-url a " ) . href . split ( " :// " )[ 1 ]; } function entryReadingTime ( e ) { const ertE = e . querySelector ( " .item-meta-info-reading-time " ); return ertE == null ? null : parseInt ( ertE . innerText ); } function sortEntriesBy ( entries , f , comparator ) { return entries . map (( e ) => [ f ( e ), e ]) . sort ( comparator ) . map (([ _ , e ]) => e ); } function sortEntries ( entries , sortMode ) { if ( entries . length < 2 ) { return entries ; } switch ( sortMode ) { case " random " : return sortEntriesBy ( entries , ( _ ) => Math . random ()); case " url " : return sortEntriesBy ( entries , entryUrl ); case " fewest " : return Array . from ( Map . groupBy ( entries , entryFeed ). values ()) . sort (( a , b ) => a . length > b . length ) . flat (); case " most " : return Array . from ( Map . groupBy ( entries , entryFeed ). values ()) . sort (( a , b ) => a . length < b . length ) . flat (); case " oldest " : return sortEntriesBy ( entries , entryTime ); case " newest " : return sortEntriesBy ( entries , entryTime ). reverse (); case " shortest " : return sortEntriesBy ( entries , entryReadingTime , ([ a ], [ b ]) => a == null ? 1 : b == null ? - 1 : a - b , ); case " longest " : return sortEntriesBy ( entries , entryReadingTime , ([ a ], [ b ]) => a == null ? 1 : b == null ? - 1 : b - a , ); case " feed " : return sortEntriesBy ( entries , entryFeed ); case " category " : return sortEntriesBy ( entries , ( e ) => entryCategory ( e ) + " - " + entryFeed ( e ), ); default : return entries ; } } function setupSortModeSelector () { const entries = Array . from ( document . querySelectorAll ( " .items .entry-item:not(:has(.item-title a[href^='/history'])) " , ), ); if ( entries . length < 2 ) { return ; } const path = window . location . pathname ; const lsKey = path + " -sortmode " ; const sortMode = localStorage . getItem ( lsKey ); const ph = document . querySelector ( " .page-header " ); const sortOpts = document . createElement ( " select " ); sortOpts . id = " sortmode " ; const options = [ " Default " , " Random " , " URL " , " Oldest " , " Newest " , " Longest " , " Shortest " , ]; if ( path . startsWith ( " /unread " )) { options . push ( " Fewest " , " Most " , " Feed " , " Category " ); } else if ( path . startsWith ( " /category " )) { options . push ( " Fewest " , " Most " , " Feed " ); } for ( const opt of options ) { const optE = document . createElement ( " option " ); optE . text = opt ; optE . value = opt . toLowerCase (); if ( optE . value === sortMode ) { optE . selected = true ; } sortOpts . appendChild ( optE ); } ph . insertBefore ( sortOpts , ph . querySelector ( " #page-header-title " )); sortOpts . onchange = function ( event ) { const sortMode = event . target . value ; sortEntries ( entries , sortMode ). forEach (( e , i ) => ( e . style . order = i )); localStorage . setItem ( lsKey , sortMode ); }; if ( sortMode != null ) { sortEntries ( entries , sortMode ). forEach (( e , i ) => ( e . style . order = i )); } } document . addEventListener ( " DOMContentLoaded " , function () { setupSortModeSelector (); }); })();
就是这样!这对我来说非常有效,我希望这对其他人也有帮助。
点赞、分享或评论Mastodon上的这篇文章。
原文: https://notes.abhinavsarkar.net/2025/customizing-miniflux