ソースを参照

fix: 列表简介兜底剥离书源状态面板

listIntro 覆盖不到的书(URL 添加、本地导入、搜索缓存已过期没回填到的
老书)仍会在书架显示详情简介里的状态面板。

HtmlFormatter 已有的 metaLineRegex 对这类行无效: 正则锚在标签词上,
而这些行是"📡 当前服务:"这种图标开头的, 且标签词无法穷举。改按形状
判断 —— 图标 + 短标签(<=8字) + 冒号 + 短值(<=40字) 的整行, 以及纯符号
的分隔行。

只在列表侧生效: 新增 formatIntroText 走这套过滤, 书架/卡片/瀑布流
用它, formatDisplayText 原样不动, 详情页不受影响。

测试含三条反证: formatDisplayText 必须留着面板、无冒号的图标行不能
误删、标签后跟长正文时不能整行吞掉。
5151561 3 ヶ月 前
親
コミット
fc0a51e4ae

+ 1 - 1
app/src/main/java/io/legado/app/ui/main/bookshelf/BookItem.kt

@@ -765,7 +765,7 @@ fun BookItem(
                 }
                 }
                 val intro = remember(book.intro, introMaxLines) {
                 val intro = remember(book.intro, introMaxLines) {
                     val text = if (introMaxLines == Int.MAX_VALUE) {
                     val text = if (introMaxLines == Int.MAX_VALUE) {
-                        HtmlFormatter.formatDisplayText(book.intro)
+                        HtmlFormatter.formatIntroText(book.intro)
                     } else {
                     } else {
                         HtmlFormatter.formatSummaryText(book.intro)
                         HtmlFormatter.formatSummaryText(book.intro)
                     }
                     }

+ 24 - 2
app/src/main/java/io/legado/app/utils/HtmlFormatter.kt

@@ -40,6 +40,13 @@ object HtmlFormatter {
     private val introLabelRegex =
     private val introLabelRegex =
         "^(?:[【\\[((](?:$introLabels)[】\\]))]|(?:$introLabels)\\s*[::])\\s*".toRegex()
         "^(?:[【\\[((](?:$introLabels)[】\\]))]|(?:$introLabels)\\s*[::])\\s*".toRegex()
 
 
+    //聚合类书源常把服务/登录状态排版进简介, 统一是"图标 + 短标签:值"的整行, 标签词无法穷举
+    private const val lineIcons = "(?:\\p{So}[\\uFE0F\\u200D\\s\\u200E]*)+"
+    private val iconMetaLineRegex = "^$lineIcons[^::\\s]{1,8}\\s*[::]\\s*.{0,40}$".toRegex()
+
+    //只有符号没有文字的分隔行/占位行
+    private val decorationLineRegex = "^[^\\p{L}\\p{N}]+$".toRegex()
+
     fun format(html: String?, otherRegex: Regex = otherHtmlRegex): String =
     fun format(html: String?, otherRegex: Regex = otherHtmlRegex): String =
         format(html, otherRegex, "  ")
         format(html, otherRegex, "  ")
 
 
@@ -78,11 +85,26 @@ object HtmlFormatter {
     }
     }
 
 
     /**
     /**
-     * 与 [formatDisplayText] 同样清洗, 但压成单行摘要。
+     * 书架/列表用的简介: 在 [formatDisplayText] 之上再丢掉书源排版进简介的状态面板,
+     * 即"📡 当前服务:xxx"这类图标开头的整行, 以及纯符号的分隔行。
+     * 详情页不做这一步 —— 那里是书源和用户交互的地方(登录提示等), 状态面板有用。
+     */
+    fun formatIntroText(html: String?): String {
+        return formatDisplayText(html)
+            .lineSequence()
+            .map { it.trim(*blankChars) }
+            .filterNot {
+                it.isEmpty() || decorationLineRegex.matches(it) || iconMetaLineRegex.matches(it)
+            }
+            .joinToString("\n") { PARAGRAPH_INDENT + it }
+    }
+
+    /**
+     * 与 [formatIntroText] 同样清洗, 但压成单行摘要。
      * 列表/卡片只显示一两行并 ellipsis, 保留换行会让首段之后的内容被直接截断。
      * 列表/卡片只显示一两行并 ellipsis, 保留换行会让首段之后的内容被直接截断。
      */
      */
     fun formatSummaryText(html: String?): String {
     fun formatSummaryText(html: String?): String {
-        return formatDisplayText(html)
+        return formatIntroText(html)
             .lineSequence()
             .lineSequence()
             .map { it.trim(*blankChars) }
             .map { it.trim(*blankChars) }
             .filterNot { it.isEmpty() }
             .filterNot { it.isEmpty() }

+ 48 - 0
app/src/test/java/io/legado/app/utils/HtmlFormatterTest.kt

@@ -67,4 +67,52 @@ class HtmlFormatterTest {
     fun formatSummaryText_dropsLeadingIndentOfSingleParagraph() {
     fun formatSummaryText_dropsLeadingIndentOfSingleParagraph() {
         assertEquals("普通简介", HtmlFormatter.formatSummaryText("  普通简介"))
         assertEquals("普通简介", HtmlFormatter.formatSummaryText("  普通简介"))
     }
     }
+
+    //聚合书源实际写进详情简介的状态面板
+    private val aggregatedSourceIntro = """
+        📡 当前服务:https://v1.example.cf
+        🔑 账号状态:⚠️ 未登录 | 点击右上角 🔖 登录
+        🏷 数据来源:百度
+        ⚙️ 访问模式:服务器网络
+        📖 阅读至:第一章 陨落的天才
+        ❇️───────❇️───────❇️
+        📚 最新章节: 《某某传》番外(下)
+        ⏳ 更新时间: 2018-09-19 12:21:32
+        📝 书籍字数: 532万
+        🚩 书籍状态: 完结
+        ‎
+        📖 书籍简介:
+        这里是属于斗气的世界,没有花俏艳丽的魔法。
+        新书等级制度:斗者,斗师,大斗师,斗灵。
+    """.trimIndent().replace("\n", "<br>")
+
+    @Test
+    fun formatIntroText_dropsSourceStatusPanel() {
+        assertEquals(
+            "  这里是属于斗气的世界,没有花俏艳丽的魔法。\n  新书等级制度:斗者,斗师,大斗师,斗灵。",
+            HtmlFormatter.formatIntroText(aggregatedSourceIntro)
+        )
+    }
+
+    @Test
+    fun formatDisplayText_keepsSourceStatusPanel() {
+        val result = HtmlFormatter.formatDisplayText(aggregatedSourceIntro)
+
+        assertTrue(result.contains("当前服务"))
+        assertTrue(result.contains("点击右上角"))
+    }
+
+    @Test
+    fun formatIntroText_keepsIconLineWithoutLabel() {
+        assertEquals(
+            "  🔥 这本书已经完结了, 放心入坑",
+            HtmlFormatter.formatIntroText("🔥 这本书已经完结了, 放心入坑")
+        )
+    }
+
+    @Test
+    fun formatIntroText_keepsLongTextAfterIconLabel() {
+        val long = "本书讲述了一个少年从家族没落到重回巅峰的故事, 其中有热血也有温情, 值得一读再读。"
+        assertTrue(HtmlFormatter.formatIntroText("📖 简介:$long").contains(long))
+    }
 }
 }