Merge branch 'master' into search-menu

pull/1463/head
Jason Yao 3 years ago committed by GitHub
commit d60c08090c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 42
      README.md
  2. 26
      app/build.gradle
  3. 5
      app/src/main/AndroidManifest.xml
  4. 2
      app/src/main/assets/defaultData/httpTTS.json
  5. 34
      app/src/main/assets/updateLog.md
  6. 5
      app/src/main/assets/web/.idea/.gitignore
  7. 8
      app/src/main/assets/web/.idea/modules.xml
  8. 6
      app/src/main/assets/web/.idea/vcs.xml
  9. BIN
      app/src/main/assets/web/__MACOSX/._assets
  10. BIN
      app/src/main/assets/web/__MACOSX/._images
  11. BIN
      app/src/main/assets/web/__MACOSX/._index.html
  12. BIN
      app/src/main/assets/web/__MACOSX/assets/._.DS_Store
  13. BIN
      app/src/main/assets/web/__MACOSX/assets/._css
  14. BIN
      app/src/main/assets/web/__MACOSX/assets/._js
  15. BIN
      app/src/main/assets/web/__MACOSX/assets/css/._.DS_Store
  16. BIN
      app/src/main/assets/web/__MACOSX/assets/js/._.DS_Store
  17. BIN
      app/src/main/assets/web/__MACOSX/assets/js/._dist.js
  18. 1
      app/src/main/assets/web/uploadBook/index.html
  19. 324
      app/src/main/assets/web/uploadBook/js/common.js
  20. 467
      app/src/main/assets/web/uploadBook/js/html5_fun.js
  21. 154
      app/src/main/assets/web/uploadBook/js/jquery-1.4.2.min.js
  22. 118
      app/src/main/assets/web/uploadBook/js/langSwich.js
  23. 5
      app/src/main/assets/web/uploadBook/js/swf_fun.js
  24. 6
      app/src/main/java/io/legado/app/base/BaseActivity.kt
  25. 3
      app/src/main/java/io/legado/app/constant/AppConst.kt
  26. 2
      app/src/main/java/io/legado/app/constant/AppPattern.kt
  27. 1
      app/src/main/java/io/legado/app/constant/PreferKey.kt
  28. 5
      app/src/main/java/io/legado/app/data/dao/TxtTocRuleDao.kt
  29. 4
      app/src/main/java/io/legado/app/data/entities/BookSource.kt
  30. 1
      app/src/main/java/io/legado/app/data/entities/Bookmark.kt
  31. 1
      app/src/main/java/io/legado/app/data/entities/ReplaceRule.kt
  32. 8
      app/src/main/java/io/legado/app/data/entities/TxtTocRule.kt
  33. 3
      app/src/main/java/io/legado/app/help/AppConfig.kt
  34. 45
      app/src/main/java/io/legado/app/help/BookHelp.kt
  35. 2
      app/src/main/java/io/legado/app/help/DirectLinkUpload.kt
  36. 18
      app/src/main/java/io/legado/app/help/JsExtensions.kt
  37. 8
      app/src/main/java/io/legado/app/help/http/BackstageWebView.kt
  38. 3
      app/src/main/java/io/legado/app/help/http/HttpHelper.kt
  39. 20
      app/src/main/java/io/legado/app/help/http/OkHttpUtils.kt
  40. 15
      app/src/main/java/io/legado/app/help/http/cronet/CronetInterceptor.kt
  41. 2
      app/src/main/java/io/legado/app/help/http/cronet/CronetLoader.kt
  42. 11
      app/src/main/java/io/legado/app/help/storage/Backup.kt
  43. 13
      app/src/main/java/io/legado/app/model/ReadBook.kt
  44. 2
      app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeUrl.kt
  45. 245
      app/src/main/java/io/legado/app/model/localBook/EpubFile.kt
  46. 5
      app/src/main/java/io/legado/app/model/localBook/LocalBook.kt
  47. 37
      app/src/main/java/io/legado/app/model/webBook/SearchModel.kt
  48. 44
      app/src/main/java/io/legado/app/service/BaseReadAloudService.kt
  49. 2
      app/src/main/java/io/legado/app/ui/book/arrange/ArrangeBookActivity.kt
  50. 52
      app/src/main/java/io/legado/app/ui/book/changecover/DiffCallBack.kt
  51. 22
      app/src/main/java/io/legado/app/ui/book/changesource/ChangeSourceAdapter.kt
  52. 7
      app/src/main/java/io/legado/app/ui/book/changesource/ChangeSourceViewModel.kt
  53. 2
      app/src/main/java/io/legado/app/ui/book/group/GroupEditDialog.kt
  54. 2
      app/src/main/java/io/legado/app/ui/book/info/edit/BookInfoEditActivity.kt
  55. 14
      app/src/main/java/io/legado/app/ui/book/local/ImportBookActivity.kt
  56. 198
      app/src/main/java/io/legado/app/ui/book/local/rule/TxtTocRuleActivity.kt
  57. 191
      app/src/main/java/io/legado/app/ui/book/local/rule/TxtTocRuleAdapter.kt
  58. 86
      app/src/main/java/io/legado/app/ui/book/local/rule/TxtTocRuleViewModel.kt
  59. 10
      app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt
  60. 10
      app/src/main/java/io/legado/app/ui/book/read/ReadBookViewModel.kt
  61. 2
      app/src/main/java/io/legado/app/ui/book/read/config/BgTextConfigDialog.kt
  62. 8
      app/src/main/java/io/legado/app/ui/book/read/config/HttpTtsEditViewModel.kt
  63. 4
      app/src/main/java/io/legado/app/ui/book/read/config/MoreConfigDialog.kt
  64. 12
      app/src/main/java/io/legado/app/ui/book/read/config/ReadAloudDialog.kt
  65. 2
      app/src/main/java/io/legado/app/ui/book/read/config/TocRegexViewModel.kt
  66. 2
      app/src/main/java/io/legado/app/ui/book/read/page/ContentTextView.kt
  67. 29
      app/src/main/java/io/legado/app/ui/book/read/page/ReadView.kt
  68. 9
      app/src/main/java/io/legado/app/ui/book/read/page/entities/TextLine.kt
  69. 2
      app/src/main/java/io/legado/app/ui/book/read/page/provider/ChapterProvider.kt
  70. 3
      app/src/main/java/io/legado/app/ui/book/source/debug/BookSourceDebugActivity.kt
  71. 2
      app/src/main/java/io/legado/app/ui/book/source/edit/BookSourceEditActivity.kt
  72. 27
      app/src/main/java/io/legado/app/ui/book/source/manage/BookSourceActivity.kt
  73. 30
      app/src/main/java/io/legado/app/ui/book/source/manage/BookSourceAdapter.kt
  74. 36
      app/src/main/java/io/legado/app/ui/book/source/manage/BookSourceViewModel.kt
  75. 17
      app/src/main/java/io/legado/app/ui/book/toc/ChapterListAdapter.kt
  76. 20
      app/src/main/java/io/legado/app/ui/book/toc/ChapterListFragment.kt
  77. 3
      app/src/main/java/io/legado/app/ui/book/toc/TocActivity.kt
  78. 2
      app/src/main/java/io/legado/app/ui/book/toc/TocActivityResult.kt
  79. 31
      app/src/main/java/io/legado/app/ui/config/BackupConfigFragment.kt
  80. 3
      app/src/main/java/io/legado/app/ui/config/OtherConfigFragment.kt
  81. 17
      app/src/main/java/io/legado/app/ui/document/HandleFileActivity.kt
  82. 4
      app/src/main/java/io/legado/app/ui/document/HandleFileContract.kt
  83. 15
      app/src/main/java/io/legado/app/ui/document/HandleFileViewModel.kt
  84. 8
      app/src/main/java/io/legado/app/ui/main/bookshelf/BaseBookshelfFragment.kt
  85. 38
      app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfViewModel.kt
  86. 16
      app/src/main/java/io/legado/app/ui/main/bookshelf/style1/BookshelfFragment1.kt
  87. 19
      app/src/main/java/io/legado/app/ui/main/bookshelf/style2/BaseBooksAdapter.kt
  88. 3
      app/src/main/java/io/legado/app/ui/main/explore/ExploreFragment.kt
  89. 14
      app/src/main/java/io/legado/app/ui/main/my/MyFragment.kt
  90. 3
      app/src/main/java/io/legado/app/ui/qrcode/QrCodeActivity.kt
  91. 2
      app/src/main/java/io/legado/app/ui/qrcode/QrCodeResult.kt
  92. 4
      app/src/main/java/io/legado/app/ui/replace/ReplaceRuleActivity.kt
  93. 2
      app/src/main/java/io/legado/app/ui/rss/source/edit/RssSourceEditActivity.kt
  94. 44
      app/src/main/java/io/legado/app/ui/rss/source/manage/RssSourceActivity.kt
  95. 19
      app/src/main/java/io/legado/app/ui/rss/source/manage/RssSourceAdapter.kt
  96. 30
      app/src/main/java/io/legado/app/ui/rss/source/manage/RssSourceViewModel.kt
  97. 4
      app/src/main/java/io/legado/app/ui/widget/SelectActionBar.kt
  98. 2
      app/src/main/java/io/legado/app/ui/widget/prefs/EditTextPreference.kt
  99. 2
      app/src/main/java/io/legado/app/ui/widget/prefs/IconListPreference.kt
  100. 2
      app/src/main/java/io/legado/app/ui/widget/prefs/NameListPreference.kt
  101. Some files were not shown because too many files have changed in this diff Show More

@ -1,15 +1,16 @@
[![icon_android](https://github.com/gedoor/gedoor.github.io/blob/master/images/icon_android.png)](https://play.google.com/store/apps/details?id=io.legado.play.release)
<a href="https://data.newrank.cn/m/s.html?s=NykyOzI9MS5LNQ%3D%3D" target="_blank"> <a href="https://data.newrank.cn/m/s.html?s=NykyOzI9MS5LNQ%3D%3D" target="_blank">
<img src="https://img.shields.io/badge/-微信关注“开源阅读”公众号-orange.svg" alt="#" align="right"> <img src="https://img.shields.io/badge/-微信关注“开源阅读”公众号-orange.svg" alt="#" align="right">
</a> </a>
[![Downloads](https://img.shields.io/github/downloads/gedoor/legado/total.svg)](https://github.com/gedoor/legado/releases/latest) [![Telegram](https://img.shields.io/badge/Telegram-channel-blue)](https://t.me/legado_channels)
<div align="center"> <div align="center">
<img width="125" height="125" src="https://github.com/gedoor/legado/raw/master/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png" alt="legado"/> <img width="125" height="125" src="https://github.com/gedoor/legado/raw/master/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png" alt="legado"/>
Legado / 开源阅读 Legado / 开源阅读
<br> <br>
<a href="https://gedoor.github.io" target="_blank">gedoor.github.io</a> / <a href="https://www.legado.top/" target="_blank">legado.top</a> <a href="https://gedoor.github.io" target="_blank">gedoor.github.io</a> / <a href="https://www.legado.top/" target="_blank">legado.top</a>
<br>
Legado is a free and open source novel reader for Android.
</div> </div>
[![](https://img.shields.io/badge/-Contents:-696969.svg)](#contents) [![](https://img.shields.io/badge/-Function-F5F5F5.svg)](#Function-主要功能-) [![](https://img.shields.io/badge/-Download-F5F5F5.svg)](#Download-下载-) [![](https://img.shields.io/badge/-Community-F5F5F5.svg)](#Community-交流社区-) [![](https://img.shields.io/badge/-API-F5F5F5.svg)](#API-) [![](https://img.shields.io/badge/-Other-F5F5F5.svg)](#Other-其他-) [![](https://img.shields.io/badge/-Grateful-F5F5F5.svg)](#Grateful-感谢-) [![](https://img.shields.io/badge/-Interface-F5F5F5.svg)](#Interface-界面-) [![](https://img.shields.io/badge/-Contents:-696969.svg)](#contents) [![](https://img.shields.io/badge/-Function-F5F5F5.svg)](#Function-主要功能-) [![](https://img.shields.io/badge/-Download-F5F5F5.svg)](#Download-下载-) [![](https://img.shields.io/badge/-Community-F5F5F5.svg)](#Community-交流社区-) [![](https://img.shields.io/badge/-API-F5F5F5.svg)](#API-) [![](https://img.shields.io/badge/-Other-F5F5F5.svg)](#Other-其他-) [![](https://img.shields.io/badge/-Grateful-F5F5F5.svg)](#Grateful-感谢-) [![](https://img.shields.io/badge/-Interface-F5F5F5.svg)](#Interface-界面-)
@ -20,15 +21,27 @@ Legado / 开源阅读
>看看 [官方帮助文档](https://www.yuque.com/legado/wiki),也许里面就有你要的答案。 >看看 [官方帮助文档](https://www.yuque.com/legado/wiki),也许里面就有你要的答案。
# Function-主要功能 [![](https://img.shields.io/badge/-Function-F5F5F5.svg)](#Function-主要功能-) # Function-主要功能 [![](https://img.shields.io/badge/-Function-F5F5F5.svg)](#Function-主要功能-)
1. 自定义书源,自己设置规则,抓取网页数据,规则简单易懂,软件内有规则说明。 <details><summary>English</summary>
2. 列表书架,网格书架自由切换。 1. Online reading from a variety of sources.<br>
3. 书源规则支持搜索及发现,所有找书看书功能全部自定义,找书更方便。 2. Local reading of downloaded content.<br>
4. 订阅内容,可以订阅想看的任何内容,看你想看 3. A configurable reader with multiple viewers, reading directions and other settings. <br>
5. 支持替换净化,去除广告替换内容很方便。 4. Categories to organize your library.<br>
6. 支持本地TXT、EPUB阅读,手动浏览,智能扫描。 5. Light and dark themes.<br>
7. 支持高度自定义阅读界面,切换字体、颜色、背景、行距、段距、加粗、简繁转换等。 6. Schedule updating your library for new chapters.<br>
8. 支持多种翻页模式,覆盖、仿真、滑动、滚动等。 7. read offline or to your desired cloud service
9. 软件开源,持续优化,无广告。 </details>
<details><summary>中文</summary>
1.自定义书源,自己设置规则,抓取网页数据,规则简单易懂,软件内有规则说明。<br>
2.列表书架,网格书架自由切换。<br>
3.书源规则支持搜索及发现,所有找书看书功能全部自定义,找书更方便。<br>
4.订阅内容,可以订阅想看的任何内容,看你想看<br>
5.支持替换净化,去除广告替换内容很方便。<br>
6.支持本地TXT、EPUB阅读,手动浏览,智能扫描。<br>
7.支持高度自定义阅读界面,切换字体、颜色、背景、行距、段距、加粗、简繁转换等。<br>
8.支持多种翻页模式,覆盖、仿真、滑动、滚动等。<br>
9.软件开源,持续优化,无广告。
</details>
<a href="#readme"> <a href="#readme">
<img src="https://img.shields.io/badge/-返回顶部-orange.svg" alt="#" align="right"> <img src="https://img.shields.io/badge/-返回顶部-orange.svg" alt="#" align="right">
@ -42,8 +55,9 @@ Legado / 开源阅读
* [Jsdelivr](https://cdn.jsdelivr.net/gh/gedoor/legado@release/) * [Jsdelivr](https://cdn.jsdelivr.net/gh/gedoor/legado@release/)
* [\#Beta](https://kunfei.lanzoui.com/b0f810h4b) * [\#Beta](https://kunfei.lanzoui.com/b0f810h4b)
#### IOS-苹果 #### IOS-苹果
* 准备中 * 准备中(No release) - [Github](https://github.com/gedoor/YueDuFlutter)
<a href="#readme"> <a href="#readme">
<img src="https://img.shields.io/badge/-返回顶部-orange.svg" alt="#" align="right"> <img src="https://img.shields.io/badge/-返回顶部-orange.svg" alt="#" align="right">
@ -53,7 +67,9 @@ Legado / 开源阅读
#### Telegram #### Telegram
[![Telegram-group](https://img.shields.io/badge/Telegram-%E7%BE%A4%E7%BB%84-blue)](https://t.me/yueduguanfang) [![Telegram-channel](https://img.shields.io/badge/Telegram-%E9%A2%91%E9%81%93-blue)](https://t.me/legado_channels) [![Telegram-group](https://img.shields.io/badge/Telegram-%E7%BE%A4%E7%BB%84-blue)](https://t.me/yueduguanfang) [![Telegram-channel](https://img.shields.io/badge/Telegram-%E9%A2%91%E9%81%93-blue)](https://t.me/legado_channels)
[![Telegram-group](https://img.shields.io/badge/Telegram-group-blue)](https://t.me/yueduguanfang) [![Telegram-channel](https://img.shields.io/badge/Telegram-channel-blue)](https://t.me/legado_channels)
#### Discord
[![Discord](https://img.shields.io/discord/560731361414086666?color=%235865f2&label=Discord)](https://discord.gg/VtUfRyzRXn)
#### Other #### Other
https://www.yuque.com/legado/wiki/community https://www.yuque.com/legado/wiki/community

@ -14,8 +14,8 @@ def version = "3." + releaseTime()
def gitCommits = Integer.parseInt('git rev-list --count HEAD'.execute([], project.rootDir).text.trim()) def gitCommits = Integer.parseInt('git rev-list --count HEAD'.execute([], project.rootDir).text.trim())
android { android {
compileSdkVersion 30 compileSdkVersion 31
buildToolsVersion '30.0.3' buildToolsVersion '31.0.0'
kotlinOptions { kotlinOptions {
jvmTarget = "11" jvmTarget = "11"
} }
@ -34,7 +34,7 @@ android {
defaultConfig { defaultConfig {
applicationId "io.legado.app" applicationId "io.legado.app"
minSdkVersion 21 minSdkVersion 21
targetSdkVersion 30 targetSdkVersion 31
versionCode gitCommits versionCode gitCommits
versionName version versionName version
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
@ -130,24 +130,24 @@ dependencies {
//kotlin //kotlin
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version") implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version")
//Kotlin反射 //Kotlin反射
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" implementation("org.jetbrains.kotlin:kotlin-reflect:$kotlin_version")
// //
def coroutines_version = '1.5.1' def coroutines_version = '1.5.2'
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version")
//androidX //androidX
implementation('androidx.core:core-ktx:1.7.0')
implementation('androidx.appcompat:appcompat:1.3.1') implementation('androidx.appcompat:appcompat:1.3.1')
implementation('androidx.core:core-ktx:1.6.0') implementation('androidx.activity:activity-ktx:1.4.0')
implementation("androidx.activity:activity-ktx:1.3.1") implementation('androidx.fragment:fragment-ktx:1.3.6')
implementation("androidx.fragment:fragment-ktx:1.3.6")
implementation('androidx.preference:preference-ktx:1.1.1') implementation('androidx.preference:preference-ktx:1.1.1')
implementation('androidx.constraintlayout:constraintlayout:2.1.0') implementation('androidx.constraintlayout:constraintlayout:2.1.1')
implementation('androidx.swiperefreshlayout:swiperefreshlayout:1.1.0') implementation('androidx.swiperefreshlayout:swiperefreshlayout:1.1.0')
implementation('androidx.viewpager2:viewpager2:1.0.0') implementation('androidx.viewpager2:viewpager2:1.0.0')
implementation('com.google.android.material:material:1.4.0') implementation('com.google.android.material:material:1.4.0')
implementation('com.google.android.flexbox:flexbox:3.0.0') implementation('com.google.android.flexbox:flexbox:3.0.0')
implementation('com.google.code.gson:gson:2.8.8') implementation('com.google.code.gson:gson:2.8.9')
implementation('androidx.webkit:webkit:1.4.0') implementation('androidx.webkit:webkit:1.4.0')
implementation('com.jakewharton.timber:timber:5.0.1') implementation('com.jakewharton.timber:timber:5.0.1')
@ -155,8 +155,8 @@ dependencies {
//media //media
implementation("androidx.media:media:1.4.1") implementation("androidx.media:media:1.4.1")
def exoplayer_version = '2.15.0' def exoplayer_version = '2.15.0'
implementation "com.google.android.exoplayer:exoplayer-core:$exoplayer_version" implementation("com.google.android.exoplayer:exoplayer-core:$exoplayer_version")
implementation "com.google.android.exoplayer:extension-okhttp:$exoplayer_version" implementation("com.google.android.exoplayer:extension-okhttp:$exoplayer_version")
//Splitties //Splitties
def splitties_version = '3.0.0' def splitties_version = '3.0.0'
@ -169,7 +169,7 @@ dependencies {
implementation("androidx.lifecycle:lifecycle-common-java8:$lifecycle_version") implementation("androidx.lifecycle:lifecycle-common-java8:$lifecycle_version")
//room //room
def room_version = '2.3.0' def room_version = '2.4.0-beta02'
implementation("androidx.room:room-runtime:$room_version") implementation("androidx.room:room-runtime:$room_version")
implementation("androidx.room:room-ktx:$room_version") implementation("androidx.room:room-ktx:$room_version")
kapt("androidx.room:room-compiler:$room_version") kapt("androidx.room:room-compiler:$room_version")

@ -236,6 +236,11 @@
android:name=".ui.rss.source.manage.RssSourceActivity" android:name=".ui.rss.source.manage.RssSourceActivity"
android:launchMode="singleTop" android:launchMode="singleTop"
android:screenOrientation="behind" /> android:screenOrientation="behind" />
<!-- txt目录规则管理 -->
<activity
android:name=".ui.book.local.rule.TxtTocRuleActivity"
android:launchMode="singleTop"
android:screenOrientation="behind" />
<!-- 替换规则界面 --> <!-- 替换规则界面 -->
<activity <activity
android:name=".ui.replace.ReplaceRuleActivity" android:name=".ui.replace.ReplaceRuleActivity"

@ -10,7 +10,7 @@
"name": "2.阿里云语音", "name": "2.阿里云语音",
"url": "https://nls-gateway.cn-shanghai.aliyuncs.com/stream/v1/tts,{\"method\": \"POST\",\"body\": {\"appkey\":\"{{source.getLoginInfoMap().get('AppKey')}}\",\"text\":\"{{speakText}}\",\"format\":\"mp3\",\"volume\":100,\"speech_rate\":{{String((speakSpeed) * 20 - 400)}} }}", "url": "https://nls-gateway.cn-shanghai.aliyuncs.com/stream/v1/tts,{\"method\": \"POST\",\"body\": {\"appkey\":\"{{source.getLoginInfoMap().get('AppKey')}}\",\"text\":\"{{speakText}}\",\"format\":\"mp3\",\"volume\":100,\"speech_rate\":{{String((speakSpeed) * 20 - 400)}} }}",
"contentType": "audio/mpeg", "contentType": "audio/mpeg",
"loginUrl": "var loginInfo = source.getLoginInfoMap();\nvar accessKeyId = loginInfo.get('AccessKeyId');\nvar accessKeySecret = loginInfo.get('AccessKeySecret');\nvar timestamp = java.timeFormatUTC(new Date().getTime(), \"YYYY-MM-dd'T'HH:mm:ss'Z'\", 0);\nvar aly = new JavaImporter(Packages.javax.crypto.Mac, Packages.javax.crypto.spec.SecretKeySpec, Packages.javax.xml.bind.DatatypeConverter, Packages.java.net.URLEncoder, Packages.java.lang.String, Packages.android.util.Base64);\nwith (aly) {\n function percentEncode(value) {\n return URLEncoder.encode(value, \"UTF-8\").replace(\"+\", \"%20\")\n .replace(\"*\", \"%2A\").replace(\"%7E\", \"~\")\n }\n\n function sign(stringToSign, accessKeySecret) {\n var mac = Mac.getInstance('HmacSHA1');\n mac.init(new SecretKeySpec(String(accessKeySecret + '&').getBytes(\"UTF-8\"), \"HmacSHA1\"));\n var signData = mac.doFinal(String(stringToSign).getBytes(\"UTF-8\"));\n var signBase64 = Base64.encodeToString(signData, Base64.NO_WRAP);\n var signUrlEncode = percentEncode(signBase64);\n return signUrlEncode;\n }\n}\nvar query = 'AccessKeyId=' + accessKeyId + '&Action=CreateToken&Format=JSON&RegionId=cn-shanghai&SignatureMethod=HMAC-SHA1&SignatureNonce=' + java.randomUUID() + '&SignatureVersion=1.0&Timestamp=' + percentEncode(timestamp) + '&Version=2019-02-28';\nvar signStr = sign('GET&' + percentEncode('/') + '&' + percentEncode(query), accessKeySecret);\nvar queryStringWithSign = \"Signature=\" + signStr + \"&\" + query;\nvar body = java.ajax('http://nls-meta.cn-shanghai.aliyuncs.com/?' + queryStringWithSign)\nvar res = JSON.parse(body)\nif (res.Message) {\n throw new Error(res.Message)\n}\nvar header = { \"X-NLS-Token\": res.Token.Id };\nsource.putLoginHeader(JSON.stringify(header))", "loginUrl": "var loginInfo = source.getLoginInfoMap();\nvar accessKeyId = loginInfo.get('AccessKeyId');\nvar accessKeySecret = loginInfo.get('AccessKeySecret');\nvar timestamp = java.timeFormatUTC(new Date().getTime(), \"yyyy-MM-dd'T'HH:mm:ss'Z'\", 0);\nvar aly = new JavaImporter(Packages.javax.crypto.Mac, Packages.javax.crypto.spec.SecretKeySpec, Packages.javax.xml.bind.DatatypeConverter, Packages.java.net.URLEncoder, Packages.java.lang.String, Packages.android.util.Base64);\nwith (aly) {\n function percentEncode(value) {\n return URLEncoder.encode(value, \"UTF-8\").replace(\"+\", \"%20\")\n .replace(\"*\", \"%2A\").replace(\"%7E\", \"~\")\n }\n\n function sign(stringToSign, accessKeySecret) {\n var mac = Mac.getInstance('HmacSHA1');\n mac.init(new SecretKeySpec(String(accessKeySecret + '&').getBytes(\"UTF-8\"), \"HmacSHA1\"));\n var signData = mac.doFinal(String(stringToSign).getBytes(\"UTF-8\"));\n var signBase64 = Base64.encodeToString(signData, Base64.NO_WRAP);\n var signUrlEncode = percentEncode(signBase64);\n return signUrlEncode;\n }\n}\nvar query = 'AccessKeyId=' + accessKeyId + '&Action=CreateToken&Format=JSON&RegionId=cn-shanghai&SignatureMethod=HMAC-SHA1&SignatureNonce=' + java.randomUUID() + '&SignatureVersion=1.0&Timestamp=' + percentEncode(timestamp) + '&Version=2019-02-28';\nvar signStr = sign('GET&' + percentEncode('/') + '&' + percentEncode(query), accessKeySecret);\nvar queryStringWithSign = \"Signature=\" + signStr + \"&\" + query;\nvar body = java.ajax('http://nls-meta.cn-shanghai.aliyuncs.com/?' + queryStringWithSign)\nvar res = JSON.parse(body)\nif (res.Message) {\n throw new Error(res.Message)\n}\nvar header = { \"X-NLS-Token\": res.Token.Id };\nsource.putLoginHeader(JSON.stringify(header))",
"loginUi": [ "loginUi": [
{ {
"name": "AppKey", "name": "AppKey",

@ -11,10 +11,42 @@
* 正文出现缺字漏字、内容缺失、排版错乱等情况,有可能是净化规则或简繁转换出现问题。 * 正文出现缺字漏字、内容缺失、排版错乱等情况,有可能是净化规则或简繁转换出现问题。
* 漫画源看书显示乱码,**阅读与其他软件的源并不通用**,请导入阅读的支持的漫画源! * 漫画源看书显示乱码,**阅读与其他软件的源并不通用**,请导入阅读的支持的漫画源!
**2021/10/26** **2021/12/10**
* 修复全面屏手势会触发翻页的bug
* js添加java.logType(*),打印变量类型,方便调试时查看
**2021/12/10**
* 朗读出错不弹出朗读界面的时候可以长按朗读按钮进入朗读界面切换朗读引擎,这个有很多人不知道
* 修复cronet访问出错时应用崩溃的bug
* 修复一些epub目录不全或内容不全的问题
* 修复横屏双页时文字选择的问题
* 电脑硬盘坏了还好资料恢复出来了,还是要经常备份比较好
**2021/11/27**
* 更新到SDK31,android 12
* 修复在线朗读引擎新建会替换之前已有的bug
* 修复目录界面自动跳转到顶部bug
* 修复阿里云在线朗读引擎模板中获取时间的兼容性问题
**2021/11/20**
* 修复部分平板双页问题
* 修复书源太多不能导出和备份的问题
* 给txt目录规则添加了一个入口,在书源管理的菜单里面
**2021/11/13**
* 修复没有目录时进入阅读界面不自动更新目录的bug
* 使用系统文件夹选择器出错时自动打开应用文件夹选择器,部分系统文件夹选择器被阉割了
**2021/11/02**
* 修复朗读错误时提示不消失的bug * 修复朗读错误时提示不消失的bug
* 修复滚动阅读选择文字错位bug by DuShuYuan * 修复滚动阅读选择文字错位bug by DuShuYuan
* 朗读语速调节添加微调按钮
**2021/10/24** **2021/10/24**

@ -0,0 +1,5 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/web.iml" filepath="$PROJECT_DIR$/.idea/web.iml" />
</modules>
</component>
</project>

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$/../../../../.." vcs="Git" />
</component>
</project>

@ -35,7 +35,6 @@
</body> </body>
</html> </html>
<script src="js/jquery-1.4.2.min.js"></script>
<script src="js/langSwich.js"></script> <script src="js/langSwich.js"></script>
<script src="js/swfupload.js"></script> <script src="js/swfupload.js"></script>
<script src="js/common.js"></script> <script src="js/common.js"></script>

@ -4,17 +4,16 @@
//全局的配置文件 //全局的配置文件
var config = { var config = {
fileTypes: "txt|epub|umd", //允许上传的文件格式 "txt|epub" // |doc|docx|wps|xls|xlsx|et|ppt|pptx|dps fileTypes: "txt|epub|umd", //允许上传的文件格式 "txt|epub" // |doc|docx|wps|xls|xlsx|et|ppt|pptx|dps
//url : "http://"+location.host+"?action=addBook",//"http://localhost/t/post.php",// //url : "http://"+location.host+"?action=addBook",//"http://localhost/t/post.php",//
url: "../addLocalBook", url: "../addLocalBook",
fileLimitSize: 500 * 1024 * 1024 fileLimitSize: 500 * 1024 * 1024,
};
}
var file = { var file = {
"inQueue": [], //已经在队列里面的文件,包括 HTML5上传和 Flash上传的 inQueue: [], //已经在队列里面的文件,包括 HTML5上传和 Flash上传的
"clientHaveFiles": [] // 客户端已经存在的文件列表 clientHaveFiles: [], // 客户端已经存在的文件列表
} };
/** /**
* HTML5 flash 公用所有文件对象集合 * HTML5 flash 公用所有文件对象集合
@ -22,71 +21,58 @@ var file = {
*/ */
var filesUpload = []; // var filesUpload = []; //
$.ajax({
url: "http://" + location.host + '?action=getBooksList&t=' + (+new Date()),//"http://localhost/t/t.php",//
async: false,//同步获取数据
dataType: "json",
success: function (data) {
try {
var dataLen = data.length;
if (dataLen > 0) {
for (var i = 0; i < dataLen; i++) {
filesUpload.push(data[i]);
}
}
} catch (e) { }
}
})
//统计文件大小 //统计文件大小
function countFileSize(fileSize) { function countFileSize(fileSize) {
var KB = 1024; var KB = 1024;
var MB = 1024 * 1024; var MB = 1024 * 1024;
if (KB >= fileSize) { if (KB >= fileSize) {
return fileSize + "B"; return fileSize + "B";
} else if (MB >= fileSize) { } else if (MB >= fileSize) {
return (fileSize / KB).toFixed(2) + "KB"; return (fileSize / KB).toFixed(2) + "KB";
} else { } else {
return (fileSize / MB).toFixed(2) + "MB"; return (fileSize / MB).toFixed(2) + "MB";
} }
} }
//如果文件太长进行截取 //如果文件太长进行截取
function substr_string(name) { function substr_string(name) {
var maxLen = 15; var maxLen = 15;
var len = name.length; var len = name.length;
if (len < 17) return name; if (len < 17) return name;
var lastIndex = name.lastIndexOf("."); var lastIndex = name.lastIndexOf(".");
var suffix = name.substr(lastIndex); var suffix = name.substr(lastIndex);
var pre = name.substr(0, lastIndex); var pre = name.substr(0, lastIndex);
var preLen = pre.length; var preLen = pre.length;
var preStart = preLen - 10; var preStart = preLen - 10;
//前面10个 + 后面5个 //前面10个 + 后面5个
var fileName = pre.substr(0, 10) + "...." + pre.substr(preStart > 4 ? -4 : -preStart, 4) + suffix; var fileName =
return fileName pre.substr(0, 10) +
"...." +
pre.substr(preStart > 4 ? -4 : -preStart, 4) +
suffix;
return fileName;
} }
function checkFile(file) { function checkFile(file) {
if (file.size > config.fileLimitSize) {
if (file.size > config.fileLimitSize) { return jsonLang.t11;
return jsonLang.t11; }
}
if (
if (!file.name || !file.name.toLowerCase().match('(' + config.fileTypes + ')$')) { !file.name ||
return jsonLang.t12; !file.name.toLowerCase().match("(" + config.fileTypes + ")$")
} ) {
return jsonLang.t12;
var len = filesUpload.length; }
for (var i = 0; i < len; i++) {
if (filesUpload[i].name == file.name) { var len = filesUpload.length;
return jsonLang.t13; for (var i = 0; i < len; i++) {
} if (filesUpload[i].name == file.name) {
} return jsonLang.t13;
return null; }
}
return null;
} }
/** /**
@ -95,146 +81,142 @@ function checkFile(file) {
* @param int type 0 是swf 上传的1 是html5上传的 * @param int type 0 是swf 上传的1 是html5上传的
*/ */
function fileQueued(file, type) { function fileQueued(file, type) {
var size = 0, fid = file.id, name = ""; var size = 0,
type = type || 0; fid = file.id,
name = "";
if (file != undefined) { type = type || 0;
//计算文件大小 单位MB
size = countFileSize(file.size); if (file != undefined) {
name = substr_string(file.name) //计算文件大小 单位MB
//创建要插入的元素 size = countFileSize(file.size);
// "<tr id='tr_'"+fid+"><td><div class='bh-poStion'><h1>"+name+"</h1>"+ name = substr_string(file.name);
// "<div class='bh-tip bh-tip3' id='progress_bar_"+fid+"'>"+ //创建要插入的元素
// "<span id='progress_bar_span_"+fid+"'></span></div>"+ var li = document.createElement("li");
// "</div></td><td><span class='bh-M'>"+size+"</span></td><td><div class='bh-link' id='handle_button_"+fid+"'>"+ li.id = `tr_${fid}`;
// "<a href='javascript:void(0)' onclick=userCancelUpload('"+fid+"',"+type+")>取消</a></div></td></tr>"; li.innerHTML = `<dl class="grybg">
<dt>${name}</dt>
var HTML = '<li id="tr_"' + fid + '>' + <dd>${size}</dd>
'<dl class="grybg">' + <dd id="handle_button_${fid}" onclick=userCancelUpload("${fid}",${type}) class="orange">
'<dt>' + name + '</dt>' + <span id="progress_bar_span_${fid}">0%</span> ${jsonLang.t9}
'<dd>' + size + '</dd>' + </dd>
'<dd id="handle_button_' + fid + '" onclick=userCancelUpload("' + fid + '",' + type + ') class="orange">' + </dl>
'<span id="progress_bar_span_' + fid + '">0%</span> ' + jsonLang.t9 + '</dd>' + <div class="jdt"><p id="progress_bar_p_${fid}" ></p></div>`;
'</dl>' + var table = document.getElementById("tableStyle");
'<div class="jdt"><p id="progress_bar_p_' + fid + '" ></p></div>' + table.appendChild(li);
'</li>'; //保存falsh_id,为上传做准备
//global_flash_id.push(file.id);
//更改背景颜色
jQuery("#tableStyle").append(HTML); changeTrBackGroundColor();
//保存falsh_id,为上传做准备 }
//global_flash_id.push(file.id);
//更改背景颜色
changeTrBackGroundColor()
}
} }
function changeTrBackGroundColor() { function changeTrBackGroundColor() {
var getTr = document.getElementById("tableStyle").getElementsByTagName("dl"); var getTr = document.getElementById("tableStyle").getElementsByTagName("dl");
trNum = getTr.length; trNum = getTr.length;
for (var i = 0; i < trNum; i++) { for (var i = 0; i < trNum; i++) {
if (i % 2 == 0) { if (i % 2 == 0) {
getTr[i].style.backgroundColor = "#f3f3f3"; getTr[i].style.backgroundColor = "#f3f3f3";
} }
} }
} }
//上传时返回的状态 //上传时返回的状态
function uploadProgress(file, bytesLoaded, bytesTotal) { function uploadProgress(file, bytesLoaded, bytesTotal) {
if (!$("#progress_bar_p_" + file.id).hasClass("orange")) { var progressClass = document.getElementById(
$("#progress_bar_p_" + file.id).addClass("orange"); "progress_bar_p_" + file.id
} ).classList;
$("#progress_bar_p_" + file.id).css("width", (bytesLoaded / bytesTotal) * 100 + "%"); if (!progressClass.contains("orange")) {
$("#progress_bar_span_" + file.id).html(parseInt((bytesLoaded / bytesTotal) * 100) + "%"); progressClass.add("orange");
}
document.getElementById("progress_bar_p_" + file.id).style.width =
(bytesLoaded / bytesTotal) * 100 + "%";
document.getElementById("progress_bar_span_" + file.id).innerHTML =
parseInt((bytesLoaded / bytesTotal) * 100) + "%";
} }
//上传成功 //上传成功
function uploadSuccess(file, serverData, res) { function uploadSuccess(file, serverData, res) {
var id = "handle_button_" + file.id; var id = "handle_button_" + file.id;
$("#" + id).replaceWith("<dd>" + jsonLang.t10 + "</dd>") var dd = document.createElement("dd");
dd.innerHTML = jsonLang.t10;
document.getElementById(id).replaceWith(dd);
} }
//取消上传 //取消上传
function userCancelUpload(file_id, type) { function userCancelUpload(file_id, type) {
if (type == 0) {
if (type == 0) { SWFFuns.cancelUpload(file_id);
SWFFuns.cancelUpload(file_id); } else {
} else { HTML5Funs.cancelUpload(file_id);
HTML5Funs.cancelUpload(file_id); }
} var element = document.getElementById("handle_button_" + file_id);
element.innerHTML = jsonLang.t14;
$("#handle_button_" + file_id).html(jsonLang.t14).removeClass("orange").addClass("gray"); element.classList.remove("orange");
//如果已经上传一部分了 element.classList.add("gray");
if ($("#progress_bar_p_" + file_id).hasClass("orange")) { //如果已经上传一部分了
$("#progress_bar_p_" + file_id).removeClass("orange"); var progressElement = document.getElementById("progress_bar_p_" + file_id);
$("#progress_bar_p_" + file_id).addClass("gray"); if (progressElement.classList.contains("orange")) {
} progressElement.classList.remove("orange");
progressElement.classList.add("gray");
}
} }
/** /**
* 通过文件名称 从全局的文件列表中获取文件对象 * 通过文件名称 从全局的文件列表中获取文件对象
* *
*/ */
function getFileFomeFilesUpload(filename) { function getFileFomeFilesUpload(filename) {
var len = filesUpload.length; var len = filesUpload.length;
for (var i = 0; i < len; i++) { for (var i = 0; i < len; i++) {
if (filesUpload[i].name == filename) { if (filesUpload[i].name == filename) {
return filesUpload[i]; return filesUpload[i];
} }
} }
return null; return null;
} }
/** /**
* 往全局的 上传列表添加一个数据 * 往全局的 上传列表添加一个数据
*/ */
function addFileToFilesUpload(file) { function addFileToFilesUpload(file) {
if (typeof file == "string") {
if (typeof file == "string") { filesUpload.push({ name: file });
filesUpload.push({ "name": file }) return true;
return true; } else if (typeof file == "object") {
} else if (typeof file == "object") { filesUpload.push(file);
filesUpload.push(file); return true;
return true; }
}
return false;
return false;
} }
/** /**
* 往全局的 上传列表添加一个数据 * 往全局的 上传列表添加一个数据
*/ */
function updateFileToFilesUpload(file) { function updateFileToFilesUpload(file) {
var len = filesUpload.length;
var len = filesUpload.length; for (var i = 0; i < len; i++) {
for (var i = 0; i < len; i++) { if (filesUpload[i].name == file.name) {
if (filesUpload[i].name == file.name) { filesUpload[i] = file;
filesUpload[i] = file; return true;
return true; }
} }
}
return false;
return false;
} }
/** /**
* 查找在数组中的位置 * 查找在数组中的位置
*/ */
function findObjectKey(object, fid) { function findObjectKey(object, fid) {
var len = object.length; var len = object.length;
for (var i = 0; i < len; i++) { for (var i = 0; i < len; i++) {
if (object[i].id == fid) { if (object[i].id == fid) {
return i; return i;
} }
} }
return -1; return -1;
} }
/** /**
@ -244,13 +226,11 @@ function findObjectKey(object, fid) {
* @return 删除后的数组 其实数组是引用类型可以不返回 * @return 删除后的数组 其实数组是引用类型可以不返回
*/ */
function removeFileFromFilesUpload(files, fid) { function removeFileFromFilesUpload(files, fid) {
var filesUploadKey = -1;
var filesUploadKey = -1; filesUploadKey = findObjectKey(files, fid);
//从全局文件中移除
filesUploadKey = findObjectKey(files, fid); if (filesUploadKey > -1) files.splice(filesUploadKey, 1);
//从全局文件中移除
if (filesUploadKey > -1)
files.splice(filesUploadKey, 1);
return files; return files;
} }

@ -1,248 +1,241 @@
/** /**
* 处理拖拽上传 * 处理拖拽上传
*/ */
var isDragOver = false;//拖拽触发点 var isDragOver = false; //拖拽触发点
var fileNumber = -1; //上传文件编号 var fileNumber = -1; //上传文件编号
var fileNumberPex = "zyFileUpload_"; //编号前缀 var fileNumberPex = "zyFileUpload_"; //编号前缀
var currUploadfile = {}; //当前上传的文件对象 var currUploadfile = {}; //当前上传的文件对象
var uploadQueue = [];//上传队列集合 var uploadQueue = []; //上传队列集合
var isUploading = false;//是否正在上传 var isUploading = false; //是否正在上传
var XHR var XHR;
try { try {
XHR = new XMLHttpRequest(); XHR = new XMLHttpRequest();
} catch (e) { } } catch (e) {}
(function (isSupportFileUpload) { (function (isSupportFileUpload) {
//不支持拖拽上传,或者 不支持FormData ,显示WiFi表示
//不支持拖拽上传,或者 不支持FormData ,显示WiFi表示 if (!isSupportFileUpload) {
if (!isSupportFileUpload) { document.getElementById("drag_area").classList.add("wf_wifi");
$("#drag_area").addClass("wf_wifi"); document.getElementById("drag_area").innerHTML = "";
$("#drag_area h3").html(""); return;
return; //更换样式
//更换样式 } else {
} else { document.getElementById("drag_area").classList.add("wf_normal");
$("#drag_area").addClass("wf_normal"); }
}
addEvent();
addEvent(); /**
* 添加事件
/** */
* 添加事件 function addEvent() {
*/ var dropArea = document.getElementById("drag_area");
function addEvent() { dropArea.addEventListener("dragover", handleDragOver, false);
var dropArea = $('#drag_area h3')[0]; dropArea.addEventListener("dragleave", handleDragLeave, false);
dropArea.addEventListener('dragover', handleDragOver, false); dropArea.addEventListener("drop", handleDrop, false);
dropArea.addEventListener('dragleave', handleDragLeave, false); }
dropArea.addEventListener('drop', handleDrop, false);
} /**
* 松开拖拽文件的处理进行上传
/** */
* 松开拖拽文件的处理进行上传 function handleDrop(evt) {
*/ evt.stopPropagation();
function handleDrop(evt) { evt.preventDefault();
var classList = document.getElementById("drag_area").classList;
evt.stopPropagation(); classList.add("wf_normal");
evt.preventDefault(); classList.remove("wf_active");
$('#drag_area').addClass('wf_normal').removeClass('wf_active');
console.log("Drop");
console.log("Drop"); isDragOver = false;
isDragOver = false;
var file = {};
var file = {}; var errorMsgs = [];
var errorMsgs = []; var len = evt.dataTransfer.files.length;
var len = evt.dataTransfer.files.length;
for (var i = 0; i < len; i++) {
for (var i = 0; i < len; i++) { fileNumber++;
fileNumber++; file = evt.dataTransfer.files[i];
file = evt.dataTransfer.files[i]; file.id = fileNumberPex + fileNumber;
file.id = fileNumberPex + fileNumber;
//检测文件
//检测文件 msg = checkFile(file);
msg = checkFile(file) //文件可以通过
//文件可以通过 if (!msg) {
if (!msg) { //添加全局
//添加全局 filesUpload.push(file);
filesUpload.push(file); //添加上传队列
//添加上传队列 uploadQueue.push(file);
uploadQueue.push(file); //在页面进行展示
//在页面进行展示 fileQueued(file, 1);
fileQueued(file, 1); } else {
} else { errorMsgs.push(msg);
errorMsgs.push(msg) }
} }
}
if (errorMsgs.length > 0) {
if (errorMsgs.length > 0) { //只选择做一个进行上传
//只选择做一个进行上传 if (len == 1) {
if (len == 1) { alert(errorMsgs[0]);
alert(errorMsgs[0]); } else {
a1 = len;
} else { a2 = len - errorMsgs.length;
a1 = len;
a2 = len - errorMsgs.length; var replaceArr = new Array(a1, a2);
alert(stringReplace(jsonLang.t7, replaceArr));
var replaceArr = new Array(a1, a2); }
alert(stringReplace(jsonLang.t7, replaceArr)); }
}
} //拿出第一个,进行上传
if (!isUploading && uploadQueue.length > 0)
//拿出第一个,进行上传 uploadFiles(uploadQueue.shift());
if (!isUploading && uploadQueue.length > 0) uploadFiles(uploadQueue.shift()); }
}
function handleDragOver(evt) {
evt.stopPropagation();
function handleDragOver(evt) { evt.preventDefault();
//防止多次DOM操作
evt.stopPropagation(); if (!isDragOver) {
evt.preventDefault(); console.log("Over");
//防止多次DOM操作 var classList = document.getElementById("drag_area").classList;
if (!isDragOver) { classList.add("wf_active");
console.log("Over"); classList.remove("wf_normal");
$('#drag_area').addClass('wf_active').removeClass('wf_normal'); isDragOver = true;
isDragOver = true; }
} }
function handleDragLeave(evt) {
} console.log("DragLeave");
evt.stopPropagation();
function handleDragLeave(evt) { evt.preventDefault();
isDragOver = false;
console.log("DragLeave"); var classList = document.getElementById("drag_area").classList;
evt.stopPropagation(); classList.add("wf_normal");
evt.preventDefault(); classList.remove("wf_active");
isDragOver = false; }
$('#drag_area').addClass('wf_normal').removeClass('wf_active');
} function uploadFiles(file) {
//正在上传
isUploading = true;
//设置上传的数据
function uploadFiles(file) { var reader = new FileReader();
//正在上传 reader.readAsDataURL(file);
isUploading = true; reader.onload = function (e) {
//设置上传的数据 var data = e.target.result;
var reader = new FileReader(); var fd = new FormData();
reader.readAsDataURL(file); fd.append("fileName", file.name);
reader.onload = function (e) { fd.append("fileData", data);
var data = e.target.result; //设置当前的上传对象
var fd = new FormData(); currUploadfile = file;
fd.append("fileName", file.name);
fd.append("fileData", data); if (XHR.readyState > 0) {
//设置当前的上传对象 XHR = new XMLHttpRequest();
currUploadfile = file; }
if (XHR.readyState > 0) { XHR.upload.addEventListener("progress", progress, false);
XHR = new XMLHttpRequest(); XHR.upload.addEventListener("load", requestLoad, false);
} XHR.upload.addEventListener("error", error, false);
XHR.upload.addEventListener("abort", abort, false);
XHR.upload.addEventListener("progress", progress, false); XHR.upload.addEventListener("loadend", loadend, false);
XHR.upload.addEventListener("load", requestLoad, false); XHR.upload.addEventListener("loadstart", loadstart, false);
XHR.upload.addEventListener("error", error, false); XHR.open("POST", config.url);
XHR.upload.addEventListener("abort", abort, false); XHR.send(fd);
XHR.upload.addEventListener("loadend", loadend, false); XHR.onreadystatechange = function () {
XHR.upload.addEventListener("loadstart", loadstart, false); //只要上传完成不管成功失败
XHR.open("POST", config.url); if (XHR.readyState == 4) {
XHR.send(fd); console.log("onreadystatechange ", XHR.status, +new Date());
XHR.onreadystatechange = function () {
if (XHR.status == 200) {
//只要上传完成不管成功失败 uploadSuccess(currUploadfile, {}, XHR.status);
if (XHR.readyState == 4) { } else {
console.log("onreadystatechange ", XHR.status, +new Date()); uploadError();
}
if (XHR.status == 200) {
uploadSuccess(currUploadfile, {}, XHR.status) //进行下一个上传
} else { nextUpload();
uploadError() }
} };
};
//进行下一个上传 }
nextUpload()
} //请求完成,无论失败或成功
}; function loadend(evt) {
} console.log("loadend", +new Date(), evt);
}
} //请求开始
function loadstart(evt) {
//请求完成,无论失败或成功 console.log("loadstart", evt);
function loadend(evt) { }
console.log("loadend", +new Date(), evt);
} //在请求发送或接收数据期间,在服务器指定的时间间隔触发。
//请求开始 function progress(evt) {
function loadstart(evt) { uploadProgress(currUploadfile, evt.loaded || evt.position, evt.total);
console.log("loadstart", evt); }
}
//在请求被取消时触发,例如,在调用 abort() 方法时。
//在请求发送或接收数据期间,在服务器指定的时间间隔触发。 function abort(evt) {
function progress(evt) { console.log("abort", evt);
uploadProgress(currUploadfile, evt.loaded || evt.position, evt.total) }
}
//在请求失败时触发。
//在请求被取消时触发,例如,在调用 abort() 方法时。 function error(evt) {
function abort(evt) { //终止ajax请求
console.log("abort", evt); XHR.abort();
} uploadError();
nextUpload();
//在请求失败时触发。 }
function error(evt) {
//终止ajax请求 //在请求成功完成时触发。
XHR.abort(); function requestLoad(evt) {
uploadError() console.log("requestLoad", +new Date(), evt);
nextUpload(); }
}
//进行下一个上传
//在请求成功完成时触发。 function nextUpload() {
function requestLoad(evt) { isUploading = false;
console.log("requestLoad", +new Date(), evt); if (uploadQueue.length > 0) {
} uploadFiles(uploadQueue.shift());
} else {
//进行下一个上传 //米有正在上传的了
function nextUpload() { currUploadfile = {};
isUploading = false; }
if (uploadQueue.length > 0) { }
uploadFiles(uploadQueue.shift());
} else { //上传出错误了,比如断网,
//米有正在上传的了 function uploadError() {
currUploadfile = {} //移除全局变量中的,上传出错的
} removeFileFromFilesUpload(filesUpload, currUploadfile.id);
} var file = currUploadfile;
var dd = document.createElement("dd")
//上传出错误了,比如断网, dd.innerHTML = jsonLang.t8
function uploadError() { document
//移除全局变量中的,上传出错的 .getElementById("handle_button_" + file.id)
removeFileFromFilesUpload(filesUpload, currUploadfile.id) .replaceWith(dd);
var file = currUploadfile; }
$("#handle_button_" + file.id).replaceWith("<dd>" + jsonLang.t8 + "</dd>")
} //对外部注册的函数
var HTML5Funs = {
/**
//对外部注册的函数 * 取消上传
var HTML5Funs = { * @param string fid 文件的Id
/** */
* 取消上传 cancelUpload: function (fid) {
* @param string fid 文件的Id var filesUploadKey = -1;
*/ var uploadQueueKey = -1;
cancelUpload: function (fid) {
//从全局中删除文件
var filesUploadKey = -1; removeFileFromFilesUpload(filesUpload, fid);
var uploadQueueKey = -1;
//如果是正在上传的,AJAX取消
if (currUploadfile.id == fid) {
//从全局中删除文件 XHR.abort();
removeFileFromFilesUpload(filesUpload, fid) } else {
//从上传队列中移除
//如果是正在上传的,AJAX取消 removeFileFromFilesUpload(uploadQueue, fid);
if (currUploadfile.id == fid) { }
XHR.abort(); },
} else { };
//从上传队列中移除
removeFileFromFilesUpload(uploadQueue, fid) window.HTML5Funs = HTML5Funs;
} })("FormData" in window && "ondrop" in document.body);
}
}
window.HTML5Funs = HTML5Funs;
})("FormData" in window && "ondrop" in document.body)

@ -1,154 +0,0 @@
/*!
* jQuery JavaScript Library v1.4.2
* http://jquery.com/
*
* Copyright 2010, John Resig
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*
* Includes Sizzle.js
* http://sizzlejs.com/
* Copyright 2010, The Dojo Foundation
* Released under the MIT, BSD, and GPL Licenses.
*
* Date: Sat Feb 13 22:33:48 2010 -0500
*/
(function(A,w){function ma(){if(!c.isReady){try{s.documentElement.doScroll("left")}catch(a){setTimeout(ma,1);return}c.ready()}}function Qa(a,b){b.src?c.ajax({url:b.src,async:false,dataType:"script"}):c.globalEval(b.text||b.textContent||b.innerHTML||"");b.parentNode&&b.parentNode.removeChild(b)}function X(a,b,d,f,e,j){var i=a.length;if(typeof b==="object"){for(var o in b)X(a,o,b[o],f,e,d);return a}if(d!==w){f=!j&&f&&c.isFunction(d);for(o=0;o<i;o++)e(a[o],b,f?d.call(a[o],o,e(a[o],b)):d,j);return a}return i?
e(a[0],b):w}function J(){return(new Date).getTime()}function Y(){return false}function Z(){return true}function na(a,b,d){d[0].type=a;return c.event.handle.apply(b,d)}function oa(a){var b,d=[],f=[],e=arguments,j,i,o,k,n,r;i=c.data(this,"events");if(!(a.liveFired===this||!i||!i.live||a.button&&a.type==="click")){a.liveFired=this;var u=i.live.slice(0);for(k=0;k<u.length;k++){i=u[k];i.origType.replace(O,"")===a.type?f.push(i.selector):u.splice(k--,1)}j=c(a.target).closest(f,a.currentTarget);n=0;for(r=
j.length;n<r;n++)for(k=0;k<u.length;k++){i=u[k];if(j[n].selector===i.selector){o=j[n].elem;f=null;if(i.preType==="mouseenter"||i.preType==="mouseleave")f=c(a.relatedTarget).closest(i.selector)[0];if(!f||f!==o)d.push({elem:o,handleObj:i})}}n=0;for(r=d.length;n<r;n++){j=d[n];a.currentTarget=j.elem;a.data=j.handleObj.data;a.handleObj=j.handleObj;if(j.handleObj.origHandler.apply(j.elem,e)===false){b=false;break}}return b}}function pa(a,b){return"live."+(a&&a!=="*"?a+".":"")+b.replace(/\./g,"`").replace(/ /g,
"&")}function qa(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function ra(a,b){var d=0;b.each(function(){if(this.nodeName===(a[d]&&a[d].nodeName)){var f=c.data(a[d++]),e=c.data(this,f);if(f=f&&f.events){delete e.handle;e.events={};for(var j in f)for(var i in f[j])c.event.add(this,j,f[j][i],f[j][i].data)}}})}function sa(a,b,d){var f,e,j;b=b&&b[0]?b[0].ownerDocument||b[0]:s;if(a.length===1&&typeof a[0]==="string"&&a[0].length<512&&b===s&&!ta.test(a[0])&&(c.support.checkClone||!ua.test(a[0]))){e=
true;if(j=c.fragments[a[0]])if(j!==1)f=j}if(!f){f=b.createDocumentFragment();c.clean(a,b,f,d)}if(e)c.fragments[a[0]]=j?f:1;return{fragment:f,cacheable:e}}function K(a,b){var d={};c.each(va.concat.apply([],va.slice(0,b)),function(){d[this]=a});return d}function wa(a){return"scrollTo"in a&&a.document?a:a.nodeType===9?a.defaultView||a.parentWindow:false}var c=function(a,b){return new c.fn.init(a,b)},Ra=A.jQuery,Sa=A.$,s=A.document,T,Ta=/^[^<]*(<[\w\W]+>)[^>]*$|^#([\w-]+)$/,Ua=/^.[^:#\[\.,]*$/,Va=/\S/,
Wa=/^(\s|\u00A0)+|(\s|\u00A0)+$/g,Xa=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,P=navigator.userAgent,xa=false,Q=[],L,$=Object.prototype.toString,aa=Object.prototype.hasOwnProperty,ba=Array.prototype.push,R=Array.prototype.slice,ya=Array.prototype.indexOf;c.fn=c.prototype={init:function(a,b){var d,f;if(!a)return this;if(a.nodeType){this.context=this[0]=a;this.length=1;return this}if(a==="body"&&!b){this.context=s;this[0]=s.body;this.selector="body";this.length=1;return this}if(typeof a==="string")if((d=Ta.exec(a))&&
(d[1]||!b))if(d[1]){f=b?b.ownerDocument||b:s;if(a=Xa.exec(a))if(c.isPlainObject(b)){a=[s.createElement(a[1])];c.fn.attr.call(a,b,true)}else a=[f.createElement(a[1])];else{a=sa([d[1]],[f]);a=(a.cacheable?a.fragment.cloneNode(true):a.fragment).childNodes}return c.merge(this,a)}else{if(b=s.getElementById(d[2])){if(b.id!==d[2])return T.find(a);this.length=1;this[0]=b}this.context=s;this.selector=a;return this}else if(!b&&/^\w+$/.test(a)){this.selector=a;this.context=s;a=s.getElementsByTagName(a);return c.merge(this,
a)}else return!b||b.jquery?(b||T).find(a):c(b).find(a);else if(c.isFunction(a))return T.ready(a);if(a.selector!==w){this.selector=a.selector;this.context=a.context}return c.makeArray(a,this)},selector:"",jquery:"1.4.2",length:0,size:function(){return this.length},toArray:function(){return R.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this.slice(a)[0]:this[a]},pushStack:function(a,b,d){var f=c();c.isArray(a)?ba.apply(f,a):c.merge(f,a);f.prevObject=this;f.context=this.context;if(b===
"find")f.selector=this.selector+(this.selector?" ":"")+d;else if(b)f.selector=this.selector+"."+b+"("+d+")";return f},each:function(a,b){return c.each(this,a,b)},ready:function(a){c.bindReady();if(c.isReady)a.call(s,c);else Q&&Q.push(a);return this},eq:function(a){return a===-1?this.slice(a):this.slice(a,+a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(R.apply(this,arguments),"slice",R.call(arguments).join(","))},map:function(a){return this.pushStack(c.map(this,
function(b,d){return a.call(b,d,b)}))},end:function(){return this.prevObject||c(null)},push:ba,sort:[].sort,splice:[].splice};c.fn.init.prototype=c.fn;c.extend=c.fn.extend=function(){var a=arguments[0]||{},b=1,d=arguments.length,f=false,e,j,i,o;if(typeof a==="boolean"){f=a;a=arguments[1]||{};b=2}if(typeof a!=="object"&&!c.isFunction(a))a={};if(d===b){a=this;--b}for(;b<d;b++)if((e=arguments[b])!=null)for(j in e){i=a[j];o=e[j];if(a!==o)if(f&&o&&(c.isPlainObject(o)||c.isArray(o))){i=i&&(c.isPlainObject(i)||
c.isArray(i))?i:c.isArray(o)?[]:{};a[j]=c.extend(f,i,o)}else if(o!==w)a[j]=o}return a};c.extend({noConflict:function(a){A.$=Sa;if(a)A.jQuery=Ra;return c},isReady:false,ready:function(){if(!c.isReady){if(!s.body)return setTimeout(c.ready,13);c.isReady=true;if(Q){for(var a,b=0;a=Q[b++];)a.call(s,c);Q=null}c.fn.triggerHandler&&c(s).triggerHandler("ready")}},bindReady:function(){if(!xa){xa=true;if(s.readyState==="complete")return c.ready();if(s.addEventListener){s.addEventListener("DOMContentLoaded",
L,false);A.addEventListener("load",c.ready,false)}else if(s.attachEvent){s.attachEvent("onreadystatechange",L);A.attachEvent("onload",c.ready);var a=false;try{a=A.frameElement==null}catch(b){}s.documentElement.doScroll&&a&&ma()}}},isFunction:function(a){return $.call(a)==="[object Function]"},isArray:function(a){return $.call(a)==="[object Array]"},isPlainObject:function(a){if(!a||$.call(a)!=="[object Object]"||a.nodeType||a.setInterval)return false;if(a.constructor&&!aa.call(a,"constructor")&&!aa.call(a.constructor.prototype,
"isPrototypeOf"))return false;var b;for(b in a);return b===w||aa.call(a,b)},isEmptyObject:function(a){for(var b in a)return false;return true},error:function(a){throw a;},parseJSON:function(a){if(typeof a!=="string"||!a)return null;a=c.trim(a);if(/^[\],:{}\s]*$/.test(a.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,"@").replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,"]").replace(/(?:^|:|,)(?:\s*\[)+/g,"")))return A.JSON&&A.JSON.parse?A.JSON.parse(a):(new Function("return "+
a))();else c.error("Invalid JSON: "+a)},noop:function(){},globalEval:function(a){if(a&&Va.test(a)){var b=s.getElementsByTagName("head")[0]||s.documentElement,d=s.createElement("script");d.type="text/javascript";if(c.support.scriptEval)d.appendChild(s.createTextNode(a));else d.text=a;b.insertBefore(d,b.firstChild);b.removeChild(d)}},nodeName:function(a,b){return a.nodeName&&a.nodeName.toUpperCase()===b.toUpperCase()},each:function(a,b,d){var f,e=0,j=a.length,i=j===w||c.isFunction(a);if(d)if(i)for(f in a){if(b.apply(a[f],
d)===false)break}else for(;e<j;){if(b.apply(a[e++],d)===false)break}else if(i)for(f in a){if(b.call(a[f],f,a[f])===false)break}else for(d=a[0];e<j&&b.call(d,e,d)!==false;d=a[++e]);return a},trim:function(a){return(a||"").replace(Wa,"")},makeArray:function(a,b){b=b||[];if(a!=null)a.length==null||typeof a==="string"||c.isFunction(a)||typeof a!=="function"&&a.setInterval?ba.call(b,a):c.merge(b,a);return b},inArray:function(a,b){if(b.indexOf)return b.indexOf(a);for(var d=0,f=b.length;d<f;d++)if(b[d]===
a)return d;return-1},merge:function(a,b){var d=a.length,f=0;if(typeof b.length==="number")for(var e=b.length;f<e;f++)a[d++]=b[f];else for(;b[f]!==w;)a[d++]=b[f++];a.length=d;return a},grep:function(a,b,d){for(var f=[],e=0,j=a.length;e<j;e++)!d!==!b(a[e],e)&&f.push(a[e]);return f},map:function(a,b,d){for(var f=[],e,j=0,i=a.length;j<i;j++){e=b(a[j],j,d);if(e!=null)f[f.length]=e}return f.concat.apply([],f)},guid:1,proxy:function(a,b,d){if(arguments.length===2)if(typeof b==="string"){d=a;a=d[b];b=w}else if(b&&
!c.isFunction(b)){d=b;b=w}if(!b&&a)b=function(){return a.apply(d||this,arguments)};if(a)b.guid=a.guid=a.guid||b.guid||c.guid++;return b},uaMatch:function(a){a=a.toLowerCase();a=/(webkit)[ \/]([\w.]+)/.exec(a)||/(opera)(?:.*version)?[ \/]([\w.]+)/.exec(a)||/(msie) ([\w.]+)/.exec(a)||!/compatible/.test(a)&&/(mozilla)(?:.*? rv:([\w.]+))?/.exec(a)||[];return{browser:a[1]||"",version:a[2]||"0"}},browser:{}});P=c.uaMatch(P);if(P.browser){c.browser[P.browser]=true;c.browser.version=P.version}if(c.browser.webkit)c.browser.safari=
true;if(ya)c.inArray=function(a,b){return ya.call(b,a)};T=c(s);if(s.addEventListener)L=function(){s.removeEventListener("DOMContentLoaded",L,false);c.ready()};else if(s.attachEvent)L=function(){if(s.readyState==="complete"){s.detachEvent("onreadystatechange",L);c.ready()}};(function(){c.support={};var a=s.documentElement,b=s.createElement("script"),d=s.createElement("div"),f="script"+J();d.style.display="none";d.innerHTML=" <link/><table></table><a href='/a' style='color:red;float:left;opacity:.55;'>a</a><input type='checkbox'/>";
var e=d.getElementsByTagName("*"),j=d.getElementsByTagName("a")[0];if(!(!e||!e.length||!j)){c.support={leadingWhitespace:d.firstChild.nodeType===3,tbody:!d.getElementsByTagName("tbody").length,htmlSerialize:!!d.getElementsByTagName("link").length,style:/red/.test(j.getAttribute("style")),hrefNormalized:j.getAttribute("href")==="/a",opacity:/^0.55$/.test(j.style.opacity),cssFloat:!!j.style.cssFloat,checkOn:d.getElementsByTagName("input")[0].value==="on",optSelected:s.createElement("select").appendChild(s.createElement("option")).selected,
parentNode:d.removeChild(d.appendChild(s.createElement("div"))).parentNode===null,deleteExpando:true,checkClone:false,scriptEval:false,noCloneEvent:true,boxModel:null};b.type="text/javascript";try{b.appendChild(s.createTextNode("window."+f+"=1;"))}catch(i){}a.insertBefore(b,a.firstChild);if(A[f]){c.support.scriptEval=true;delete A[f]}try{delete b.test}catch(o){c.support.deleteExpando=false}a.removeChild(b);if(d.attachEvent&&d.fireEvent){d.attachEvent("onclick",function k(){c.support.noCloneEvent=
false;d.detachEvent("onclick",k)});d.cloneNode(true).fireEvent("onclick")}d=s.createElement("div");d.innerHTML="<input type='radio' name='radiotest' checked='checked'/>";a=s.createDocumentFragment();a.appendChild(d.firstChild);c.support.checkClone=a.cloneNode(true).cloneNode(true).lastChild.checked;c(function(){var k=s.createElement("div");k.style.width=k.style.paddingLeft="1px";s.body.appendChild(k);c.boxModel=c.support.boxModel=k.offsetWidth===2;s.body.removeChild(k).style.display="none"});a=function(k){var n=
s.createElement("div");k="on"+k;var r=k in n;if(!r){n.setAttribute(k,"return;");r=typeof n[k]==="function"}return r};c.support.submitBubbles=a("submit");c.support.changeBubbles=a("change");a=b=d=e=j=null}})();c.props={"for":"htmlFor","class":"className",readonly:"readOnly",maxlength:"maxLength",cellspacing:"cellSpacing",rowspan:"rowSpan",colspan:"colSpan",tabindex:"tabIndex",usemap:"useMap",frameborder:"frameBorder"};var G="jQuery"+J(),Ya=0,za={};c.extend({cache:{},expando:G,noData:{embed:true,object:true,
applet:true},data:function(a,b,d){if(!(a.nodeName&&c.noData[a.nodeName.toLowerCase()])){a=a==A?za:a;var f=a[G],e=c.cache;if(!f&&typeof b==="string"&&d===w)return null;f||(f=++Ya);if(typeof b==="object"){a[G]=f;e[f]=c.extend(true,{},b)}else if(!e[f]){a[G]=f;e[f]={}}a=e[f];if(d!==w)a[b]=d;return typeof b==="string"?a[b]:a}},removeData:function(a,b){if(!(a.nodeName&&c.noData[a.nodeName.toLowerCase()])){a=a==A?za:a;var d=a[G],f=c.cache,e=f[d];if(b){if(e){delete e[b];c.isEmptyObject(e)&&c.removeData(a)}}else{if(c.support.deleteExpando)delete a[c.expando];
else a.removeAttribute&&a.removeAttribute(c.expando);delete f[d]}}}});c.fn.extend({data:function(a,b){if(typeof a==="undefined"&&this.length)return c.data(this[0]);else if(typeof a==="object")return this.each(function(){c.data(this,a)});var d=a.split(".");d[1]=d[1]?"."+d[1]:"";if(b===w){var f=this.triggerHandler("getData"+d[1]+"!",[d[0]]);if(f===w&&this.length)f=c.data(this[0],a);return f===w&&d[1]?this.data(d[0]):f}else return this.trigger("setData"+d[1]+"!",[d[0],b]).each(function(){c.data(this,
a,b)})},removeData:function(a){return this.each(function(){c.removeData(this,a)})}});c.extend({queue:function(a,b,d){if(a){b=(b||"fx")+"queue";var f=c.data(a,b);if(!d)return f||[];if(!f||c.isArray(d))f=c.data(a,b,c.makeArray(d));else f.push(d);return f}},dequeue:function(a,b){b=b||"fx";var d=c.queue(a,b),f=d.shift();if(f==="inprogress")f=d.shift();if(f){b==="fx"&&d.unshift("inprogress");f.call(a,function(){c.dequeue(a,b)})}}});c.fn.extend({queue:function(a,b){if(typeof a!=="string"){b=a;a="fx"}if(b===
w)return c.queue(this[0],a);return this.each(function(){var d=c.queue(this,a,b);a==="fx"&&d[0]!=="inprogress"&&c.dequeue(this,a)})},dequeue:function(a){return this.each(function(){c.dequeue(this,a)})},delay:function(a,b){a=c.fx?c.fx.speeds[a]||a:a;b=b||"fx";return this.queue(b,function(){var d=this;setTimeout(function(){c.dequeue(d,b)},a)})},clearQueue:function(a){return this.queue(a||"fx",[])}});var Aa=/[\n\t]/g,ca=/\s+/,Za=/\r/g,$a=/href|src|style/,ab=/(button|input)/i,bb=/(button|input|object|select|textarea)/i,
cb=/^(a|area)$/i,Ba=/radio|checkbox/;c.fn.extend({attr:function(a,b){return X(this,a,b,true,c.attr)},removeAttr:function(a){return this.each(function(){c.attr(this,a,"");this.nodeType===1&&this.removeAttribute(a)})},addClass:function(a){if(c.isFunction(a))return this.each(function(n){var r=c(this);r.addClass(a.call(this,n,r.attr("class")))});if(a&&typeof a==="string")for(var b=(a||"").split(ca),d=0,f=this.length;d<f;d++){var e=this[d];if(e.nodeType===1)if(e.className){for(var j=" "+e.className+" ",
i=e.className,o=0,k=b.length;o<k;o++)if(j.indexOf(" "+b[o]+" ")<0)i+=" "+b[o];e.className=c.trim(i)}else e.className=a}return this},removeClass:function(a){if(c.isFunction(a))return this.each(function(k){var n=c(this);n.removeClass(a.call(this,k,n.attr("class")))});if(a&&typeof a==="string"||a===w)for(var b=(a||"").split(ca),d=0,f=this.length;d<f;d++){var e=this[d];if(e.nodeType===1&&e.className)if(a){for(var j=(" "+e.className+" ").replace(Aa," "),i=0,o=b.length;i<o;i++)j=j.replace(" "+b[i]+" ",
" ");e.className=c.trim(j)}else e.className=""}return this},toggleClass:function(a,b){var d=typeof a,f=typeof b==="boolean";if(c.isFunction(a))return this.each(function(e){var j=c(this);j.toggleClass(a.call(this,e,j.attr("class"),b),b)});return this.each(function(){if(d==="string")for(var e,j=0,i=c(this),o=b,k=a.split(ca);e=k[j++];){o=f?o:!i.hasClass(e);i[o?"addClass":"removeClass"](e)}else if(d==="undefined"||d==="boolean"){this.className&&c.data(this,"__className__",this.className);this.className=
this.className||a===false?"":c.data(this,"__className__")||""}})},hasClass:function(a){a=" "+a+" ";for(var b=0,d=this.length;b<d;b++)if((" "+this[b].className+" ").replace(Aa," ").indexOf(a)>-1)return true;return false},val:function(a){if(a===w){var b=this[0];if(b){if(c.nodeName(b,"option"))return(b.attributes.value||{}).specified?b.value:b.text;if(c.nodeName(b,"select")){var d=b.selectedIndex,f=[],e=b.options;b=b.type==="select-one";if(d<0)return null;var j=b?d:0;for(d=b?d+1:e.length;j<d;j++){var i=
e[j];if(i.selected){a=c(i).val();if(b)return a;f.push(a)}}return f}if(Ba.test(b.type)&&!c.support.checkOn)return b.getAttribute("value")===null?"on":b.value;return(b.value||"").replace(Za,"")}return w}var o=c.isFunction(a);return this.each(function(k){var n=c(this),r=a;if(this.nodeType===1){if(o)r=a.call(this,k,n.val());if(typeof r==="number")r+="";if(c.isArray(r)&&Ba.test(this.type))this.checked=c.inArray(n.val(),r)>=0;else if(c.nodeName(this,"select")){var u=c.makeArray(r);c("option",this).each(function(){this.selected=
c.inArray(c(this).val(),u)>=0});if(!u.length)this.selectedIndex=-1}else this.value=r}})}});c.extend({attrFn:{val:true,css:true,html:true,text:true,data:true,width:true,height:true,offset:true},attr:function(a,b,d,f){if(!a||a.nodeType===3||a.nodeType===8)return w;if(f&&b in c.attrFn)return c(a)[b](d);f=a.nodeType!==1||!c.isXMLDoc(a);var e=d!==w;b=f&&c.props[b]||b;if(a.nodeType===1){var j=$a.test(b);if(b in a&&f&&!j){if(e){b==="type"&&ab.test(a.nodeName)&&a.parentNode&&c.error("type property can't be changed");
a[b]=d}if(c.nodeName(a,"form")&&a.getAttributeNode(b))return a.getAttributeNode(b).nodeValue;if(b==="tabIndex")return(b=a.getAttributeNode("tabIndex"))&&b.specified?b.value:bb.test(a.nodeName)||cb.test(a.nodeName)&&a.href?0:w;return a[b]}if(!c.support.style&&f&&b==="style"){if(e)a.style.cssText=""+d;return a.style.cssText}e&&a.setAttribute(b,""+d);a=!c.support.hrefNormalized&&f&&j?a.getAttribute(b,2):a.getAttribute(b);return a===null?w:a}return c.style(a,b,d)}});var O=/\.(.*)$/,db=function(a){return a.replace(/[^\w\s\.\|`]/g,
function(b){return"\\"+b})};c.event={add:function(a,b,d,f){if(!(a.nodeType===3||a.nodeType===8)){if(a.setInterval&&a!==A&&!a.frameElement)a=A;var e,j;if(d.handler){e=d;d=e.handler}if(!d.guid)d.guid=c.guid++;if(j=c.data(a)){var i=j.events=j.events||{},o=j.handle;if(!o)j.handle=o=function(){return typeof c!=="undefined"&&!c.event.triggered?c.event.handle.apply(o.elem,arguments):w};o.elem=a;b=b.split(" ");for(var k,n=0,r;k=b[n++];){j=e?c.extend({},e):{handler:d,data:f};if(k.indexOf(".")>-1){r=k.split(".");
k=r.shift();j.namespace=r.slice(0).sort().join(".")}else{r=[];j.namespace=""}j.type=k;j.guid=d.guid;var u=i[k],z=c.event.special[k]||{};if(!u){u=i[k]=[];if(!z.setup||z.setup.call(a,f,r,o)===false)if(a.addEventListener)a.addEventListener(k,o,false);else a.attachEvent&&a.attachEvent("on"+k,o)}if(z.add){z.add.call(a,j);if(!j.handler.guid)j.handler.guid=d.guid}u.push(j);c.event.global[k]=true}a=null}}},global:{},remove:function(a,b,d,f){if(!(a.nodeType===3||a.nodeType===8)){var e,j=0,i,o,k,n,r,u,z=c.data(a),
C=z&&z.events;if(z&&C){if(b&&b.type){d=b.handler;b=b.type}if(!b||typeof b==="string"&&b.charAt(0)==="."){b=b||"";for(e in C)c.event.remove(a,e+b)}else{for(b=b.split(" ");e=b[j++];){n=e;i=e.indexOf(".")<0;o=[];if(!i){o=e.split(".");e=o.shift();k=new RegExp("(^|\\.)"+c.map(o.slice(0).sort(),db).join("\\.(?:.*\\.)?")+"(\\.|$)")}if(r=C[e])if(d){n=c.event.special[e]||{};for(B=f||0;B<r.length;B++){u=r[B];if(d.guid===u.guid){if(i||k.test(u.namespace)){f==null&&r.splice(B--,1);n.remove&&n.remove.call(a,u)}if(f!=
null)break}}if(r.length===0||f!=null&&r.length===1){if(!n.teardown||n.teardown.call(a,o)===false)Ca(a,e,z.handle);delete C[e]}}else for(var B=0;B<r.length;B++){u=r[B];if(i||k.test(u.namespace)){c.event.remove(a,n,u.handler,B);r.splice(B--,1)}}}if(c.isEmptyObject(C)){if(b=z.handle)b.elem=null;delete z.events;delete z.handle;c.isEmptyObject(z)&&c.removeData(a)}}}}},trigger:function(a,b,d,f){var e=a.type||a;if(!f){a=typeof a==="object"?a[G]?a:c.extend(c.Event(e),a):c.Event(e);if(e.indexOf("!")>=0){a.type=
e=e.slice(0,-1);a.exclusive=true}if(!d){a.stopPropagation();c.event.global[e]&&c.each(c.cache,function(){this.events&&this.events[e]&&c.event.trigger(a,b,this.handle.elem)})}if(!d||d.nodeType===3||d.nodeType===8)return w;a.result=w;a.target=d;b=c.makeArray(b);b.unshift(a)}a.currentTarget=d;(f=c.data(d,"handle"))&&f.apply(d,b);f=d.parentNode||d.ownerDocument;try{if(!(d&&d.nodeName&&c.noData[d.nodeName.toLowerCase()]))if(d["on"+e]&&d["on"+e].apply(d,b)===false)a.result=false}catch(j){}if(!a.isPropagationStopped()&&
f)c.event.trigger(a,b,f,true);else if(!a.isDefaultPrevented()){f=a.target;var i,o=c.nodeName(f,"a")&&e==="click",k=c.event.special[e]||{};if((!k._default||k._default.call(d,a)===false)&&!o&&!(f&&f.nodeName&&c.noData[f.nodeName.toLowerCase()])){try{if(f[e]){if(i=f["on"+e])f["on"+e]=null;c.event.triggered=true;f[e]()}}catch(n){}if(i)f["on"+e]=i;c.event.triggered=false}}},handle:function(a){var b,d,f,e;a=arguments[0]=c.event.fix(a||A.event);a.currentTarget=this;b=a.type.indexOf(".")<0&&!a.exclusive;
if(!b){d=a.type.split(".");a.type=d.shift();f=new RegExp("(^|\\.)"+d.slice(0).sort().join("\\.(?:.*\\.)?")+"(\\.|$)")}e=c.data(this,"events");d=e[a.type];if(e&&d){d=d.slice(0);e=0;for(var j=d.length;e<j;e++){var i=d[e];if(b||f.test(i.namespace)){a.handler=i.handler;a.data=i.data;a.handleObj=i;i=i.handler.apply(this,arguments);if(i!==w){a.result=i;if(i===false){a.preventDefault();a.stopPropagation()}}if(a.isImmediatePropagationStopped())break}}}return a.result},props:"altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode layerX layerY metaKey newValue offsetX offsetY originalTarget pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "),
fix:function(a){if(a[G])return a;var b=a;a=c.Event(b);for(var d=this.props.length,f;d;){f=this.props[--d];a[f]=b[f]}if(!a.target)a.target=a.srcElement||s;if(a.target.nodeType===3)a.target=a.target.parentNode;if(!a.relatedTarget&&a.fromElement)a.relatedTarget=a.fromElement===a.target?a.toElement:a.fromElement;if(a.pageX==null&&a.clientX!=null){b=s.documentElement;d=s.body;a.pageX=a.clientX+(b&&b.scrollLeft||d&&d.scrollLeft||0)-(b&&b.clientLeft||d&&d.clientLeft||0);a.pageY=a.clientY+(b&&b.scrollTop||
d&&d.scrollTop||0)-(b&&b.clientTop||d&&d.clientTop||0)}if(!a.which&&(a.charCode||a.charCode===0?a.charCode:a.keyCode))a.which=a.charCode||a.keyCode;if(!a.metaKey&&a.ctrlKey)a.metaKey=a.ctrlKey;if(!a.which&&a.button!==w)a.which=a.button&1?1:a.button&2?3:a.button&4?2:0;return a},guid:1E8,proxy:c.proxy,special:{ready:{setup:c.bindReady,teardown:c.noop},live:{add:function(a){c.event.add(this,a.origType,c.extend({},a,{handler:oa}))},remove:function(a){var b=true,d=a.origType.replace(O,"");c.each(c.data(this,
"events").live||[],function(){if(d===this.origType.replace(O,""))return b=false});b&&c.event.remove(this,a.origType,oa)}},beforeunload:{setup:function(a,b,d){if(this.setInterval)this.onbeforeunload=d;return false},teardown:function(a,b){if(this.onbeforeunload===b)this.onbeforeunload=null}}}};var Ca=s.removeEventListener?function(a,b,d){a.removeEventListener(b,d,false)}:function(a,b,d){a.detachEvent("on"+b,d)};c.Event=function(a){if(!this.preventDefault)return new c.Event(a);if(a&&a.type){this.originalEvent=
a;this.type=a.type}else this.type=a;this.timeStamp=J();this[G]=true};c.Event.prototype={preventDefault:function(){this.isDefaultPrevented=Z;var a=this.originalEvent;if(a){a.preventDefault&&a.preventDefault();a.returnValue=false}},stopPropagation:function(){this.isPropagationStopped=Z;var a=this.originalEvent;if(a){a.stopPropagation&&a.stopPropagation();a.cancelBubble=true}},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=Z;this.stopPropagation()},isDefaultPrevented:Y,isPropagationStopped:Y,
isImmediatePropagationStopped:Y};var Da=function(a){var b=a.relatedTarget;try{for(;b&&b!==this;)b=b.parentNode;if(b!==this){a.type=a.data;c.event.handle.apply(this,arguments)}}catch(d){}},Ea=function(a){a.type=a.data;c.event.handle.apply(this,arguments)};c.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(a,b){c.event.special[a]={setup:function(d){c.event.add(this,b,d&&d.selector?Ea:Da,a)},teardown:function(d){c.event.remove(this,b,d&&d.selector?Ea:Da)}}});if(!c.support.submitBubbles)c.event.special.submit=
{setup:function(){if(this.nodeName.toLowerCase()!=="form"){c.event.add(this,"click.specialSubmit",function(a){var b=a.target,d=b.type;if((d==="submit"||d==="image")&&c(b).closest("form").length)return na("submit",this,arguments)});c.event.add(this,"keypress.specialSubmit",function(a){var b=a.target,d=b.type;if((d==="text"||d==="password")&&c(b).closest("form").length&&a.keyCode===13)return na("submit",this,arguments)})}else return false},teardown:function(){c.event.remove(this,".specialSubmit")}};
if(!c.support.changeBubbles){var da=/textarea|input|select/i,ea,Fa=function(a){var b=a.type,d=a.value;if(b==="radio"||b==="checkbox")d=a.checked;else if(b==="select-multiple")d=a.selectedIndex>-1?c.map(a.options,function(f){return f.selected}).join("-"):"";else if(a.nodeName.toLowerCase()==="select")d=a.selectedIndex;return d},fa=function(a,b){var d=a.target,f,e;if(!(!da.test(d.nodeName)||d.readOnly)){f=c.data(d,"_change_data");e=Fa(d);if(a.type!=="focusout"||d.type!=="radio")c.data(d,"_change_data",
e);if(!(f===w||e===f))if(f!=null||e){a.type="change";return c.event.trigger(a,b,d)}}};c.event.special.change={filters:{focusout:fa,click:function(a){var b=a.target,d=b.type;if(d==="radio"||d==="checkbox"||b.nodeName.toLowerCase()==="select")return fa.call(this,a)},keydown:function(a){var b=a.target,d=b.type;if(a.keyCode===13&&b.nodeName.toLowerCase()!=="textarea"||a.keyCode===32&&(d==="checkbox"||d==="radio")||d==="select-multiple")return fa.call(this,a)},beforeactivate:function(a){a=a.target;c.data(a,
"_change_data",Fa(a))}},setup:function(){if(this.type==="file")return false;for(var a in ea)c.event.add(this,a+".specialChange",ea[a]);return da.test(this.nodeName)},teardown:function(){c.event.remove(this,".specialChange");return da.test(this.nodeName)}};ea=c.event.special.change.filters}s.addEventListener&&c.each({focus:"focusin",blur:"focusout"},function(a,b){function d(f){f=c.event.fix(f);f.type=b;return c.event.handle.call(this,f)}c.event.special[b]={setup:function(){this.addEventListener(a,
d,true)},teardown:function(){this.removeEventListener(a,d,true)}}});c.each(["bind","one"],function(a,b){c.fn[b]=function(d,f,e){if(typeof d==="object"){for(var j in d)this[b](j,f,d[j],e);return this}if(c.isFunction(f)){e=f;f=w}var i=b==="one"?c.proxy(e,function(k){c(this).unbind(k,i);return e.apply(this,arguments)}):e;if(d==="unload"&&b!=="one")this.one(d,f,e);else{j=0;for(var o=this.length;j<o;j++)c.event.add(this[j],d,i,f)}return this}});c.fn.extend({unbind:function(a,b){if(typeof a==="object"&&
!a.preventDefault)for(var d in a)this.unbind(d,a[d]);else{d=0;for(var f=this.length;d<f;d++)c.event.remove(this[d],a,b)}return this},delegate:function(a,b,d,f){return this.live(b,d,f,a)},undelegate:function(a,b,d){return arguments.length===0?this.unbind("live"):this.die(b,null,d,a)},trigger:function(a,b){return this.each(function(){c.event.trigger(a,b,this)})},triggerHandler:function(a,b){if(this[0]){a=c.Event(a);a.preventDefault();a.stopPropagation();c.event.trigger(a,b,this[0]);return a.result}},
toggle:function(a){for(var b=arguments,d=1;d<b.length;)c.proxy(a,b[d++]);return this.click(c.proxy(a,function(f){var e=(c.data(this,"lastToggle"+a.guid)||0)%d;c.data(this,"lastToggle"+a.guid,e+1);f.preventDefault();return b[e].apply(this,arguments)||false}))},hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)}});var Ga={focus:"focusin",blur:"focusout",mouseenter:"mouseover",mouseleave:"mouseout"};c.each(["live","die"],function(a,b){c.fn[b]=function(d,f,e,j){var i,o=0,k,n,r=j||this.selector,
u=j?this:c(this.context);if(c.isFunction(f)){e=f;f=w}for(d=(d||"").split(" ");(i=d[o++])!=null;){j=O.exec(i);k="";if(j){k=j[0];i=i.replace(O,"")}if(i==="hover")d.push("mouseenter"+k,"mouseleave"+k);else{n=i;if(i==="focus"||i==="blur"){d.push(Ga[i]+k);i+=k}else i=(Ga[i]||i)+k;b==="live"?u.each(function(){c.event.add(this,pa(i,r),{data:f,selector:r,handler:e,origType:i,origHandler:e,preType:n})}):u.unbind(pa(i,r),e)}}return this}});c.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error".split(" "),
function(a,b){c.fn[b]=function(d){return d?this.bind(b,d):this.trigger(b)};if(c.attrFn)c.attrFn[b]=true});A.attachEvent&&!A.addEventListener&&A.attachEvent("onunload",function(){for(var a in c.cache)if(c.cache[a].handle)try{c.event.remove(c.cache[a].handle.elem)}catch(b){}});(function(){function a(g){for(var h="",l,m=0;g[m];m++){l=g[m];if(l.nodeType===3||l.nodeType===4)h+=l.nodeValue;else if(l.nodeType!==8)h+=a(l.childNodes)}return h}function b(g,h,l,m,q,p){q=0;for(var v=m.length;q<v;q++){var t=m[q];
if(t){t=t[g];for(var y=false;t;){if(t.sizcache===l){y=m[t.sizset];break}if(t.nodeType===1&&!p){t.sizcache=l;t.sizset=q}if(t.nodeName.toLowerCase()===h){y=t;break}t=t[g]}m[q]=y}}}function d(g,h,l,m,q,p){q=0;for(var v=m.length;q<v;q++){var t=m[q];if(t){t=t[g];for(var y=false;t;){if(t.sizcache===l){y=m[t.sizset];break}if(t.nodeType===1){if(!p){t.sizcache=l;t.sizset=q}if(typeof h!=="string"){if(t===h){y=true;break}}else if(k.filter(h,[t]).length>0){y=t;break}}t=t[g]}m[q]=y}}}var f=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,
e=0,j=Object.prototype.toString,i=false,o=true;[0,0].sort(function(){o=false;return 0});var k=function(g,h,l,m){l=l||[];var q=h=h||s;if(h.nodeType!==1&&h.nodeType!==9)return[];if(!g||typeof g!=="string")return l;for(var p=[],v,t,y,S,H=true,M=x(h),I=g;(f.exec(""),v=f.exec(I))!==null;){I=v[3];p.push(v[1]);if(v[2]){S=v[3];break}}if(p.length>1&&r.exec(g))if(p.length===2&&n.relative[p[0]])t=ga(p[0]+p[1],h);else for(t=n.relative[p[0]]?[h]:k(p.shift(),h);p.length;){g=p.shift();if(n.relative[g])g+=p.shift();
t=ga(g,t)}else{if(!m&&p.length>1&&h.nodeType===9&&!M&&n.match.ID.test(p[0])&&!n.match.ID.test(p[p.length-1])){v=k.find(p.shift(),h,M);h=v.expr?k.filter(v.expr,v.set)[0]:v.set[0]}if(h){v=m?{expr:p.pop(),set:z(m)}:k.find(p.pop(),p.length===1&&(p[0]==="~"||p[0]==="+")&&h.parentNode?h.parentNode:h,M);t=v.expr?k.filter(v.expr,v.set):v.set;if(p.length>0)y=z(t);else H=false;for(;p.length;){var D=p.pop();v=D;if(n.relative[D])v=p.pop();else D="";if(v==null)v=h;n.relative[D](y,v,M)}}else y=[]}y||(y=t);y||k.error(D||
g);if(j.call(y)==="[object Array]")if(H)if(h&&h.nodeType===1)for(g=0;y[g]!=null;g++){if(y[g]&&(y[g]===true||y[g].nodeType===1&&E(h,y[g])))l.push(t[g])}else for(g=0;y[g]!=null;g++)y[g]&&y[g].nodeType===1&&l.push(t[g]);else l.push.apply(l,y);else z(y,l);if(S){k(S,q,l,m);k.uniqueSort(l)}return l};k.uniqueSort=function(g){if(B){i=o;g.sort(B);if(i)for(var h=1;h<g.length;h++)g[h]===g[h-1]&&g.splice(h--,1)}return g};k.matches=function(g,h){return k(g,null,null,h)};k.find=function(g,h,l){var m,q;if(!g)return[];
for(var p=0,v=n.order.length;p<v;p++){var t=n.order[p];if(q=n.leftMatch[t].exec(g)){var y=q[1];q.splice(1,1);if(y.substr(y.length-1)!=="\\"){q[1]=(q[1]||"").replace(/\\/g,"");m=n.find[t](q,h,l);if(m!=null){g=g.replace(n.match[t],"");break}}}}m||(m=h.getElementsByTagName("*"));return{set:m,expr:g}};k.filter=function(g,h,l,m){for(var q=g,p=[],v=h,t,y,S=h&&h[0]&&x(h[0]);g&&h.length;){for(var H in n.filter)if((t=n.leftMatch[H].exec(g))!=null&&t[2]){var M=n.filter[H],I,D;D=t[1];y=false;t.splice(1,1);if(D.substr(D.length-
1)!=="\\"){if(v===p)p=[];if(n.preFilter[H])if(t=n.preFilter[H](t,v,l,p,m,S)){if(t===true)continue}else y=I=true;if(t)for(var U=0;(D=v[U])!=null;U++)if(D){I=M(D,t,U,v);var Ha=m^!!I;if(l&&I!=null)if(Ha)y=true;else v[U]=false;else if(Ha){p.push(D);y=true}}if(I!==w){l||(v=p);g=g.replace(n.match[H],"");if(!y)return[];break}}}if(g===q)if(y==null)k.error(g);else break;q=g}return v};k.error=function(g){throw"Syntax error, unrecognized expression: "+g;};var n=k.selectors={order:["ID","NAME","TAG"],match:{ID:/#((?:[\w\u00c0-\uFFFF-]|\\.)+)/,
CLASS:/\.((?:[\w\u00c0-\uFFFF-]|\\.)+)/,NAME:/\[name=['"]*((?:[\w\u00c0-\uFFFF-]|\\.)+)['"]*\]/,ATTR:/\[\s*((?:[\w\u00c0-\uFFFF-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/,TAG:/^((?:[\w\u00c0-\uFFFF\*-]|\\.)+)/,CHILD:/:(only|nth|last|first)-child(?:\((even|odd|[\dn+-]*)\))?/,POS:/:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^-]|$)/,PSEUDO:/:((?:[\w\u00c0-\uFFFF-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/},leftMatch:{},attrMap:{"class":"className","for":"htmlFor"},attrHandle:{href:function(g){return g.getAttribute("href")}},
relative:{"+":function(g,h){var l=typeof h==="string",m=l&&!/\W/.test(h);l=l&&!m;if(m)h=h.toLowerCase();m=0;for(var q=g.length,p;m<q;m++)if(p=g[m]){for(;(p=p.previousSibling)&&p.nodeType!==1;);g[m]=l||p&&p.nodeName.toLowerCase()===h?p||false:p===h}l&&k.filter(h,g,true)},">":function(g,h){var l=typeof h==="string";if(l&&!/\W/.test(h)){h=h.toLowerCase();for(var m=0,q=g.length;m<q;m++){var p=g[m];if(p){l=p.parentNode;g[m]=l.nodeName.toLowerCase()===h?l:false}}}else{m=0;for(q=g.length;m<q;m++)if(p=g[m])g[m]=
l?p.parentNode:p.parentNode===h;l&&k.filter(h,g,true)}},"":function(g,h,l){var m=e++,q=d;if(typeof h==="string"&&!/\W/.test(h)){var p=h=h.toLowerCase();q=b}q("parentNode",h,m,g,p,l)},"~":function(g,h,l){var m=e++,q=d;if(typeof h==="string"&&!/\W/.test(h)){var p=h=h.toLowerCase();q=b}q("previousSibling",h,m,g,p,l)}},find:{ID:function(g,h,l){if(typeof h.getElementById!=="undefined"&&!l)return(g=h.getElementById(g[1]))?[g]:[]},NAME:function(g,h){if(typeof h.getElementsByName!=="undefined"){var l=[];
h=h.getElementsByName(g[1]);for(var m=0,q=h.length;m<q;m++)h[m].getAttribute("name")===g[1]&&l.push(h[m]);return l.length===0?null:l}},TAG:function(g,h){return h.getElementsByTagName(g[1])}},preFilter:{CLASS:function(g,h,l,m,q,p){g=" "+g[1].replace(/\\/g,"")+" ";if(p)return g;p=0;for(var v;(v=h[p])!=null;p++)if(v)if(q^(v.className&&(" "+v.className+" ").replace(/[\t\n]/g," ").indexOf(g)>=0))l||m.push(v);else if(l)h[p]=false;return false},ID:function(g){return g[1].replace(/\\/g,"")},TAG:function(g){return g[1].toLowerCase()},
CHILD:function(g){if(g[1]==="nth"){var h=/(-?)(\d*)n((?:\+|-)?\d*)/.exec(g[2]==="even"&&"2n"||g[2]==="odd"&&"2n+1"||!/\D/.test(g[2])&&"0n+"+g[2]||g[2]);g[2]=h[1]+(h[2]||1)-0;g[3]=h[3]-0}g[0]=e++;return g},ATTR:function(g,h,l,m,q,p){h=g[1].replace(/\\/g,"");if(!p&&n.attrMap[h])g[1]=n.attrMap[h];if(g[2]==="~=")g[4]=" "+g[4]+" ";return g},PSEUDO:function(g,h,l,m,q){if(g[1]==="not")if((f.exec(g[3])||"").length>1||/^\w/.test(g[3]))g[3]=k(g[3],null,null,h);else{g=k.filter(g[3],h,l,true^q);l||m.push.apply(m,
g);return false}else if(n.match.POS.test(g[0])||n.match.CHILD.test(g[0]))return true;return g},POS:function(g){g.unshift(true);return g}},filters:{enabled:function(g){return g.disabled===false&&g.type!=="hidden"},disabled:function(g){return g.disabled===true},checked:function(g){return g.checked===true},selected:function(g){return g.selected===true},parent:function(g){return!!g.firstChild},empty:function(g){return!g.firstChild},has:function(g,h,l){return!!k(l[3],g).length},header:function(g){return/h\d/i.test(g.nodeName)},
text:function(g){return"text"===g.type},radio:function(g){return"radio"===g.type},checkbox:function(g){return"checkbox"===g.type},file:function(g){return"file"===g.type},password:function(g){return"password"===g.type},submit:function(g){return"submit"===g.type},image:function(g){return"image"===g.type},reset:function(g){return"reset"===g.type},button:function(g){return"button"===g.type||g.nodeName.toLowerCase()==="button"},input:function(g){return/input|select|textarea|button/i.test(g.nodeName)}},
setFilters:{first:function(g,h){return h===0},last:function(g,h,l,m){return h===m.length-1},even:function(g,h){return h%2===0},odd:function(g,h){return h%2===1},lt:function(g,h,l){return h<l[3]-0},gt:function(g,h,l){return h>l[3]-0},nth:function(g,h,l){return l[3]-0===h},eq:function(g,h,l){return l[3]-0===h}},filter:{PSEUDO:function(g,h,l,m){var q=h[1],p=n.filters[q];if(p)return p(g,l,h,m);else if(q==="contains")return(g.textContent||g.innerText||a([g])||"").indexOf(h[3])>=0;else if(q==="not"){h=
h[3];l=0;for(m=h.length;l<m;l++)if(h[l]===g)return false;return true}else k.error("Syntax error, unrecognized expression: "+q)},CHILD:function(g,h){var l=h[1],m=g;switch(l){case "only":case "first":for(;m=m.previousSibling;)if(m.nodeType===1)return false;if(l==="first")return true;m=g;case "last":for(;m=m.nextSibling;)if(m.nodeType===1)return false;return true;case "nth":l=h[2];var q=h[3];if(l===1&&q===0)return true;h=h[0];var p=g.parentNode;if(p&&(p.sizcache!==h||!g.nodeIndex)){var v=0;for(m=p.firstChild;m;m=
m.nextSibling)if(m.nodeType===1)m.nodeIndex=++v;p.sizcache=h}g=g.nodeIndex-q;return l===0?g===0:g%l===0&&g/l>=0}},ID:function(g,h){return g.nodeType===1&&g.getAttribute("id")===h},TAG:function(g,h){return h==="*"&&g.nodeType===1||g.nodeName.toLowerCase()===h},CLASS:function(g,h){return(" "+(g.className||g.getAttribute("class"))+" ").indexOf(h)>-1},ATTR:function(g,h){var l=h[1];g=n.attrHandle[l]?n.attrHandle[l](g):g[l]!=null?g[l]:g.getAttribute(l);l=g+"";var m=h[2];h=h[4];return g==null?m==="!=":m===
"="?l===h:m==="*="?l.indexOf(h)>=0:m==="~="?(" "+l+" ").indexOf(h)>=0:!h?l&&g!==false:m==="!="?l!==h:m==="^="?l.indexOf(h)===0:m==="$="?l.substr(l.length-h.length)===h:m==="|="?l===h||l.substr(0,h.length+1)===h+"-":false},POS:function(g,h,l,m){var q=n.setFilters[h[2]];if(q)return q(g,l,h,m)}}},r=n.match.POS;for(var u in n.match){n.match[u]=new RegExp(n.match[u].source+/(?![^\[]*\])(?![^\(]*\))/.source);n.leftMatch[u]=new RegExp(/(^(?:.|\r|\n)*?)/.source+n.match[u].source.replace(/\\(\d+)/g,function(g,
h){return"\\"+(h-0+1)}))}var z=function(g,h){g=Array.prototype.slice.call(g,0);if(h){h.push.apply(h,g);return h}return g};try{Array.prototype.slice.call(s.documentElement.childNodes,0)}catch(C){z=function(g,h){h=h||[];if(j.call(g)==="[object Array]")Array.prototype.push.apply(h,g);else if(typeof g.length==="number")for(var l=0,m=g.length;l<m;l++)h.push(g[l]);else for(l=0;g[l];l++)h.push(g[l]);return h}}var B;if(s.documentElement.compareDocumentPosition)B=function(g,h){if(!g.compareDocumentPosition||
!h.compareDocumentPosition){if(g==h)i=true;return g.compareDocumentPosition?-1:1}g=g.compareDocumentPosition(h)&4?-1:g===h?0:1;if(g===0)i=true;return g};else if("sourceIndex"in s.documentElement)B=function(g,h){if(!g.sourceIndex||!h.sourceIndex){if(g==h)i=true;return g.sourceIndex?-1:1}g=g.sourceIndex-h.sourceIndex;if(g===0)i=true;return g};else if(s.createRange)B=function(g,h){if(!g.ownerDocument||!h.ownerDocument){if(g==h)i=true;return g.ownerDocument?-1:1}var l=g.ownerDocument.createRange(),m=
h.ownerDocument.createRange();l.setStart(g,0);l.setEnd(g,0);m.setStart(h,0);m.setEnd(h,0);g=l.compareBoundaryPoints(Range.START_TO_END,m);if(g===0)i=true;return g};(function(){var g=s.createElement("div"),h="script"+(new Date).getTime();g.innerHTML="<a name='"+h+"'/>";var l=s.documentElement;l.insertBefore(g,l.firstChild);if(s.getElementById(h)){n.find.ID=function(m,q,p){if(typeof q.getElementById!=="undefined"&&!p)return(q=q.getElementById(m[1]))?q.id===m[1]||typeof q.getAttributeNode!=="undefined"&&
q.getAttributeNode("id").nodeValue===m[1]?[q]:w:[]};n.filter.ID=function(m,q){var p=typeof m.getAttributeNode!=="undefined"&&m.getAttributeNode("id");return m.nodeType===1&&p&&p.nodeValue===q}}l.removeChild(g);l=g=null})();(function(){var g=s.createElement("div");g.appendChild(s.createComment(""));if(g.getElementsByTagName("*").length>0)n.find.TAG=function(h,l){l=l.getElementsByTagName(h[1]);if(h[1]==="*"){h=[];for(var m=0;l[m];m++)l[m].nodeType===1&&h.push(l[m]);l=h}return l};g.innerHTML="<a href='#'></a>";
if(g.firstChild&&typeof g.firstChild.getAttribute!=="undefined"&&g.firstChild.getAttribute("href")!=="#")n.attrHandle.href=function(h){return h.getAttribute("href",2)};g=null})();s.querySelectorAll&&function(){var g=k,h=s.createElement("div");h.innerHTML="<p class='TEST'></p>";if(!(h.querySelectorAll&&h.querySelectorAll(".TEST").length===0)){k=function(m,q,p,v){q=q||s;if(!v&&q.nodeType===9&&!x(q))try{return z(q.querySelectorAll(m),p)}catch(t){}return g(m,q,p,v)};for(var l in g)k[l]=g[l];h=null}}();
(function(){var g=s.createElement("div");g.innerHTML="<div class='test e'></div><div class='test'></div>";if(!(!g.getElementsByClassName||g.getElementsByClassName("e").length===0)){g.lastChild.className="e";if(g.getElementsByClassName("e").length!==1){n.order.splice(1,0,"CLASS");n.find.CLASS=function(h,l,m){if(typeof l.getElementsByClassName!=="undefined"&&!m)return l.getElementsByClassName(h[1])};g=null}}})();var E=s.compareDocumentPosition?function(g,h){return!!(g.compareDocumentPosition(h)&16)}:
function(g,h){return g!==h&&(g.contains?g.contains(h):true)},x=function(g){return(g=(g?g.ownerDocument||g:0).documentElement)?g.nodeName!=="HTML":false},ga=function(g,h){var l=[],m="",q;for(h=h.nodeType?[h]:h;q=n.match.PSEUDO.exec(g);){m+=q[0];g=g.replace(n.match.PSEUDO,"")}g=n.relative[g]?g+"*":g;q=0;for(var p=h.length;q<p;q++)k(g,h[q],l);return k.filter(m,l)};c.find=k;c.expr=k.selectors;c.expr[":"]=c.expr.filters;c.unique=k.uniqueSort;c.text=a;c.isXMLDoc=x;c.contains=E})();var eb=/Until$/,fb=/^(?:parents|prevUntil|prevAll)/,
gb=/,/;R=Array.prototype.slice;var Ia=function(a,b,d){if(c.isFunction(b))return c.grep(a,function(e,j){return!!b.call(e,j,e)===d});else if(b.nodeType)return c.grep(a,function(e){return e===b===d});else if(typeof b==="string"){var f=c.grep(a,function(e){return e.nodeType===1});if(Ua.test(b))return c.filter(b,f,!d);else b=c.filter(b,f)}return c.grep(a,function(e){return c.inArray(e,b)>=0===d})};c.fn.extend({find:function(a){for(var b=this.pushStack("","find",a),d=0,f=0,e=this.length;f<e;f++){d=b.length;
c.find(a,this[f],b);if(f>0)for(var j=d;j<b.length;j++)for(var i=0;i<d;i++)if(b[i]===b[j]){b.splice(j--,1);break}}return b},has:function(a){var b=c(a);return this.filter(function(){for(var d=0,f=b.length;d<f;d++)if(c.contains(this,b[d]))return true})},not:function(a){return this.pushStack(Ia(this,a,false),"not",a)},filter:function(a){return this.pushStack(Ia(this,a,true),"filter",a)},is:function(a){return!!a&&c.filter(a,this).length>0},closest:function(a,b){if(c.isArray(a)){var d=[],f=this[0],e,j=
{},i;if(f&&a.length){e=0;for(var o=a.length;e<o;e++){i=a[e];j[i]||(j[i]=c.expr.match.POS.test(i)?c(i,b||this.context):i)}for(;f&&f.ownerDocument&&f!==b;){for(i in j){e=j[i];if(e.jquery?e.index(f)>-1:c(f).is(e)){d.push({selector:i,elem:f});delete j[i]}}f=f.parentNode}}return d}var k=c.expr.match.POS.test(a)?c(a,b||this.context):null;return this.map(function(n,r){for(;r&&r.ownerDocument&&r!==b;){if(k?k.index(r)>-1:c(r).is(a))return r;r=r.parentNode}return null})},index:function(a){if(!a||typeof a===
"string")return c.inArray(this[0],a?c(a):this.parent().children());return c.inArray(a.jquery?a[0]:a,this)},add:function(a,b){a=typeof a==="string"?c(a,b||this.context):c.makeArray(a);b=c.merge(this.get(),a);return this.pushStack(qa(a[0])||qa(b[0])?b:c.unique(b))},andSelf:function(){return this.add(this.prevObject)}});c.each({parent:function(a){return(a=a.parentNode)&&a.nodeType!==11?a:null},parents:function(a){return c.dir(a,"parentNode")},parentsUntil:function(a,b,d){return c.dir(a,"parentNode",
d)},next:function(a){return c.nth(a,2,"nextSibling")},prev:function(a){return c.nth(a,2,"previousSibling")},nextAll:function(a){return c.dir(a,"nextSibling")},prevAll:function(a){return c.dir(a,"previousSibling")},nextUntil:function(a,b,d){return c.dir(a,"nextSibling",d)},prevUntil:function(a,b,d){return c.dir(a,"previousSibling",d)},siblings:function(a){return c.sibling(a.parentNode.firstChild,a)},children:function(a){return c.sibling(a.firstChild)},contents:function(a){return c.nodeName(a,"iframe")?
a.contentDocument||a.contentWindow.document:c.makeArray(a.childNodes)}},function(a,b){c.fn[a]=function(d,f){var e=c.map(this,b,d);eb.test(a)||(f=d);if(f&&typeof f==="string")e=c.filter(f,e);e=this.length>1?c.unique(e):e;if((this.length>1||gb.test(f))&&fb.test(a))e=e.reverse();return this.pushStack(e,a,R.call(arguments).join(","))}});c.extend({filter:function(a,b,d){if(d)a=":not("+a+")";return c.find.matches(a,b)},dir:function(a,b,d){var f=[];for(a=a[b];a&&a.nodeType!==9&&(d===w||a.nodeType!==1||!c(a).is(d));){a.nodeType===
1&&f.push(a);a=a[b]}return f},nth:function(a,b,d){b=b||1;for(var f=0;a;a=a[d])if(a.nodeType===1&&++f===b)break;return a},sibling:function(a,b){for(var d=[];a;a=a.nextSibling)a.nodeType===1&&a!==b&&d.push(a);return d}});var Ja=/ jQuery\d+="(?:\d+|null)"/g,V=/^\s+/,Ka=/(<([\w:]+)[^>]*?)\/>/g,hb=/^(?:area|br|col|embed|hr|img|input|link|meta|param)$/i,La=/<([\w:]+)/,ib=/<tbody/i,jb=/<|&#?\w+;/,ta=/<script|<object|<embed|<option|<style/i,ua=/checked\s*(?:[^=]|=\s*.checked.)/i,Ma=function(a,b,d){return hb.test(d)?
a:b+"></"+d+">"},F={option:[1,"<select multiple='multiple'>","</select>"],legend:[1,"<fieldset>","</fieldset>"],thead:[1,"<table>","</table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],col:[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"],area:[1,"<map>","</map>"],_default:[0,"",""]};F.optgroup=F.option;F.tbody=F.tfoot=F.colgroup=F.caption=F.thead;F.th=F.td;if(!c.support.htmlSerialize)F._default=[1,"div<div>","</div>"];c.fn.extend({text:function(a){if(c.isFunction(a))return this.each(function(b){var d=
c(this);d.text(a.call(this,b,d.text()))});if(typeof a!=="object"&&a!==w)return this.empty().append((this[0]&&this[0].ownerDocument||s).createTextNode(a));return c.text(this)},wrapAll:function(a){if(c.isFunction(a))return this.each(function(d){c(this).wrapAll(a.call(this,d))});if(this[0]){var b=c(a,this[0].ownerDocument).eq(0).clone(true);this[0].parentNode&&b.insertBefore(this[0]);b.map(function(){for(var d=this;d.firstChild&&d.firstChild.nodeType===1;)d=d.firstChild;return d}).append(this)}return this},
wrapInner:function(a){if(c.isFunction(a))return this.each(function(b){c(this).wrapInner(a.call(this,b))});return this.each(function(){var b=c(this),d=b.contents();d.length?d.wrapAll(a):b.append(a)})},wrap:function(a){return this.each(function(){c(this).wrapAll(a)})},unwrap:function(){return this.parent().each(function(){c.nodeName(this,"body")||c(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,true,function(a){this.nodeType===1&&this.appendChild(a)})},
prepend:function(){return this.domManip(arguments,true,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,false,function(b){this.parentNode.insertBefore(b,this)});else if(arguments.length){var a=c(arguments[0]);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,false,function(b){this.parentNode.insertBefore(b,
this.nextSibling)});else if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,c(arguments[0]).toArray());return a}},remove:function(a,b){for(var d=0,f;(f=this[d])!=null;d++)if(!a||c.filter(a,[f]).length){if(!b&&f.nodeType===1){c.cleanData(f.getElementsByTagName("*"));c.cleanData([f])}f.parentNode&&f.parentNode.removeChild(f)}return this},empty:function(){for(var a=0,b;(b=this[a])!=null;a++)for(b.nodeType===1&&c.cleanData(b.getElementsByTagName("*"));b.firstChild;)b.removeChild(b.firstChild);
return this},clone:function(a){var b=this.map(function(){if(!c.support.noCloneEvent&&!c.isXMLDoc(this)){var d=this.outerHTML,f=this.ownerDocument;if(!d){d=f.createElement("div");d.appendChild(this.cloneNode(true));d=d.innerHTML}return c.clean([d.replace(Ja,"").replace(/=([^="'>\s]+\/)>/g,'="$1">').replace(V,"")],f)[0]}else return this.cloneNode(true)});if(a===true){ra(this,b);ra(this.find("*"),b.find("*"))}return b},html:function(a){if(a===w)return this[0]&&this[0].nodeType===1?this[0].innerHTML.replace(Ja,
""):null;else if(typeof a==="string"&&!ta.test(a)&&(c.support.leadingWhitespace||!V.test(a))&&!F[(La.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(Ka,Ma);try{for(var b=0,d=this.length;b<d;b++)if(this[b].nodeType===1){c.cleanData(this[b].getElementsByTagName("*"));this[b].innerHTML=a}}catch(f){this.empty().append(a)}}else c.isFunction(a)?this.each(function(e){var j=c(this),i=j.html();j.empty().append(function(){return a.call(this,e,i)})}):this.empty().append(a);return this},replaceWith:function(a){if(this[0]&&
this[0].parentNode){if(c.isFunction(a))return this.each(function(b){var d=c(this),f=d.html();d.replaceWith(a.call(this,b,f))});if(typeof a!=="string")a=c(a).detach();return this.each(function(){var b=this.nextSibling,d=this.parentNode;c(this).remove();b?c(b).before(a):c(d).append(a)})}else return this.pushStack(c(c.isFunction(a)?a():a),"replaceWith",a)},detach:function(a){return this.remove(a,true)},domManip:function(a,b,d){function f(u){return c.nodeName(u,"table")?u.getElementsByTagName("tbody")[0]||
u.appendChild(u.ownerDocument.createElement("tbody")):u}var e,j,i=a[0],o=[],k;if(!c.support.checkClone&&arguments.length===3&&typeof i==="string"&&ua.test(i))return this.each(function(){c(this).domManip(a,b,d,true)});if(c.isFunction(i))return this.each(function(u){var z=c(this);a[0]=i.call(this,u,b?z.html():w);z.domManip(a,b,d)});if(this[0]){e=i&&i.parentNode;e=c.support.parentNode&&e&&e.nodeType===11&&e.childNodes.length===this.length?{fragment:e}:sa(a,this,o);k=e.fragment;if(j=k.childNodes.length===
1?(k=k.firstChild):k.firstChild){b=b&&c.nodeName(j,"tr");for(var n=0,r=this.length;n<r;n++)d.call(b?f(this[n],j):this[n],n>0||e.cacheable||this.length>1?k.cloneNode(true):k)}o.length&&c.each(o,Qa)}return this}});c.fragments={};c.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){c.fn[a]=function(d){var f=[];d=c(d);var e=this.length===1&&this[0].parentNode;if(e&&e.nodeType===11&&e.childNodes.length===1&&d.length===1){d[b](this[0]);
return this}else{e=0;for(var j=d.length;e<j;e++){var i=(e>0?this.clone(true):this).get();c.fn[b].apply(c(d[e]),i);f=f.concat(i)}return this.pushStack(f,a,d.selector)}}});c.extend({clean:function(a,b,d,f){b=b||s;if(typeof b.createElement==="undefined")b=b.ownerDocument||b[0]&&b[0].ownerDocument||s;for(var e=[],j=0,i;(i=a[j])!=null;j++){if(typeof i==="number")i+="";if(i){if(typeof i==="string"&&!jb.test(i))i=b.createTextNode(i);else if(typeof i==="string"){i=i.replace(Ka,Ma);var o=(La.exec(i)||["",
""])[1].toLowerCase(),k=F[o]||F._default,n=k[0],r=b.createElement("div");for(r.innerHTML=k[1]+i+k[2];n--;)r=r.lastChild;if(!c.support.tbody){n=ib.test(i);o=o==="table"&&!n?r.firstChild&&r.firstChild.childNodes:k[1]==="<table>"&&!n?r.childNodes:[];for(k=o.length-1;k>=0;--k)c.nodeName(o[k],"tbody")&&!o[k].childNodes.length&&o[k].parentNode.removeChild(o[k])}!c.support.leadingWhitespace&&V.test(i)&&r.insertBefore(b.createTextNode(V.exec(i)[0]),r.firstChild);i=r.childNodes}if(i.nodeType)e.push(i);else e=
c.merge(e,i)}}if(d)for(j=0;e[j];j++)if(f&&c.nodeName(e[j],"script")&&(!e[j].type||e[j].type.toLowerCase()==="text/javascript"))f.push(e[j].parentNode?e[j].parentNode.removeChild(e[j]):e[j]);else{e[j].nodeType===1&&e.splice.apply(e,[j+1,0].concat(c.makeArray(e[j].getElementsByTagName("script"))));d.appendChild(e[j])}return e},cleanData:function(a){for(var b,d,f=c.cache,e=c.event.special,j=c.support.deleteExpando,i=0,o;(o=a[i])!=null;i++)if(d=o[c.expando]){b=f[d];if(b.events)for(var k in b.events)e[k]?
c.event.remove(o,k):Ca(o,k,b.handle);if(j)delete o[c.expando];else o.removeAttribute&&o.removeAttribute(c.expando);delete f[d]}}});var kb=/z-?index|font-?weight|opacity|zoom|line-?height/i,Na=/alpha\([^)]*\)/,Oa=/opacity=([^)]*)/,ha=/float/i,ia=/-([a-z])/ig,lb=/([A-Z])/g,mb=/^-?\d+(?:px)?$/i,nb=/^-?\d/,ob={position:"absolute",visibility:"hidden",display:"block"},pb=["Left","Right"],qb=["Top","Bottom"],rb=s.defaultView&&s.defaultView.getComputedStyle,Pa=c.support.cssFloat?"cssFloat":"styleFloat",ja=
function(a,b){return b.toUpperCase()};c.fn.css=function(a,b){return X(this,a,b,true,function(d,f,e){if(e===w)return c.curCSS(d,f);if(typeof e==="number"&&!kb.test(f))e+="px";c.style(d,f,e)})};c.extend({style:function(a,b,d){if(!a||a.nodeType===3||a.nodeType===8)return w;if((b==="width"||b==="height")&&parseFloat(d)<0)d=w;var f=a.style||a,e=d!==w;if(!c.support.opacity&&b==="opacity"){if(e){f.zoom=1;b=parseInt(d,10)+""==="NaN"?"":"alpha(opacity="+d*100+")";a=f.filter||c.curCSS(a,"filter")||"";f.filter=
Na.test(a)?a.replace(Na,b):b}return f.filter&&f.filter.indexOf("opacity=")>=0?parseFloat(Oa.exec(f.filter)[1])/100+"":""}if(ha.test(b))b=Pa;b=b.replace(ia,ja);if(e)f[b]=d;return f[b]},css:function(a,b,d,f){if(b==="width"||b==="height"){var e,j=b==="width"?pb:qb;function i(){e=b==="width"?a.offsetWidth:a.offsetHeight;f!=="border"&&c.each(j,function(){f||(e-=parseFloat(c.curCSS(a,"padding"+this,true))||0);if(f==="margin")e+=parseFloat(c.curCSS(a,"margin"+this,true))||0;else e-=parseFloat(c.curCSS(a,
"border"+this+"Width",true))||0})}a.offsetWidth!==0?i():c.swap(a,ob,i);return Math.max(0,Math.round(e))}return c.curCSS(a,b,d)},curCSS:function(a,b,d){var f,e=a.style;if(!c.support.opacity&&b==="opacity"&&a.currentStyle){f=Oa.test(a.currentStyle.filter||"")?parseFloat(RegExp.$1)/100+"":"";return f===""?"1":f}if(ha.test(b))b=Pa;if(!d&&e&&e[b])f=e[b];else if(rb){if(ha.test(b))b="float";b=b.replace(lb,"-$1").toLowerCase();e=a.ownerDocument.defaultView;if(!e)return null;if(a=e.getComputedStyle(a,null))f=
a.getPropertyValue(b);if(b==="opacity"&&f==="")f="1"}else if(a.currentStyle){d=b.replace(ia,ja);f=a.currentStyle[b]||a.currentStyle[d];if(!mb.test(f)&&nb.test(f)){b=e.left;var j=a.runtimeStyle.left;a.runtimeStyle.left=a.currentStyle.left;e.left=d==="fontSize"?"1em":f||0;f=e.pixelLeft+"px";e.left=b;a.runtimeStyle.left=j}}return f},swap:function(a,b,d){var f={};for(var e in b){f[e]=a.style[e];a.style[e]=b[e]}d.call(a);for(e in b)a.style[e]=f[e]}});if(c.expr&&c.expr.filters){c.expr.filters.hidden=function(a){var b=
a.offsetWidth,d=a.offsetHeight,f=a.nodeName.toLowerCase()==="tr";return b===0&&d===0&&!f?true:b>0&&d>0&&!f?false:c.curCSS(a,"display")==="none"};c.expr.filters.visible=function(a){return!c.expr.filters.hidden(a)}}var sb=J(),tb=/<script(.|\s)*?\/script>/gi,ub=/select|textarea/i,vb=/color|date|datetime|email|hidden|month|number|password|range|search|tel|text|time|url|week/i,N=/=\?(&|$)/,ka=/\?/,wb=/(\?|&)_=.*?(&|$)/,xb=/^(\w+:)?\/\/([^\/?#]+)/,yb=/%20/g,zb=c.fn.load;c.fn.extend({load:function(a,b,d){if(typeof a!==
"string")return zb.call(this,a);else if(!this.length)return this;var f=a.indexOf(" ");if(f>=0){var e=a.slice(f,a.length);a=a.slice(0,f)}f="GET";if(b)if(c.isFunction(b)){d=b;b=null}else if(typeof b==="object"){b=c.param(b,c.ajaxSettings.traditional);f="POST"}var j=this;c.ajax({url:a,type:f,dataType:"html",data:b,complete:function(i,o){if(o==="success"||o==="notmodified")j.html(e?c("<div />").append(i.responseText.replace(tb,"")).find(e):i.responseText);d&&j.each(d,[i.responseText,o,i])}});return this},
serialize:function(){return c.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?c.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||ub.test(this.nodeName)||vb.test(this.type))}).map(function(a,b){a=c(this).val();return a==null?null:c.isArray(a)?c.map(a,function(d){return{name:b.name,value:d}}):{name:b.name,value:a}}).get()}});c.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),
function(a,b){c.fn[b]=function(d){return this.bind(b,d)}});c.extend({get:function(a,b,d,f){if(c.isFunction(b)){f=f||d;d=b;b=null}return c.ajax({type:"GET",url:a,data:b,success:d,dataType:f})},getScript:function(a,b){return c.get(a,null,b,"script")},getJSON:function(a,b,d){return c.get(a,b,d,"json")},post:function(a,b,d,f){if(c.isFunction(b)){f=f||d;d=b;b={}}return c.ajax({type:"POST",url:a,data:b,success:d,dataType:f})},ajaxSetup:function(a){c.extend(c.ajaxSettings,a)},ajaxSettings:{url:location.href,
global:true,type:"GET",contentType:"application/x-www-form-urlencoded",processData:true,async:true,xhr:A.XMLHttpRequest&&(A.location.protocol!=="file:"||!A.ActiveXObject)?function(){return new A.XMLHttpRequest}:function(){try{return new A.ActiveXObject("Microsoft.XMLHTTP")}catch(a){}},accepts:{xml:"application/xml, text/xml",html:"text/html",script:"text/javascript, application/javascript",json:"application/json, text/javascript",text:"text/plain",_default:"*/*"}},lastModified:{},etag:{},ajax:function(a){function b(){e.success&&
e.success.call(k,o,i,x);e.global&&f("ajaxSuccess",[x,e])}function d(){e.complete&&e.complete.call(k,x,i);e.global&&f("ajaxComplete",[x,e]);e.global&&!--c.active&&c.event.trigger("ajaxStop")}function f(q,p){(e.context?c(e.context):c.event).trigger(q,p)}var e=c.extend(true,{},c.ajaxSettings,a),j,i,o,k=a&&a.context||e,n=e.type.toUpperCase();if(e.data&&e.processData&&typeof e.data!=="string")e.data=c.param(e.data,e.traditional);if(e.dataType==="jsonp"){if(n==="GET")N.test(e.url)||(e.url+=(ka.test(e.url)?
"&":"?")+(e.jsonp||"callback")+"=?");else if(!e.data||!N.test(e.data))e.data=(e.data?e.data+"&":"")+(e.jsonp||"callback")+"=?";e.dataType="json"}if(e.dataType==="json"&&(e.data&&N.test(e.data)||N.test(e.url))){j=e.jsonpCallback||"jsonp"+sb++;if(e.data)e.data=(e.data+"").replace(N,"="+j+"$1");e.url=e.url.replace(N,"="+j+"$1");e.dataType="script";A[j]=A[j]||function(q){o=q;b();d();A[j]=w;try{delete A[j]}catch(p){}z&&z.removeChild(C)}}if(e.dataType==="script"&&e.cache===null)e.cache=false;if(e.cache===
false&&n==="GET"){var r=J(),u=e.url.replace(wb,"$1_="+r+"$2");e.url=u+(u===e.url?(ka.test(e.url)?"&":"?")+"_="+r:"")}if(e.data&&n==="GET")e.url+=(ka.test(e.url)?"&":"?")+e.data;e.global&&!c.active++&&c.event.trigger("ajaxStart");r=(r=xb.exec(e.url))&&(r[1]&&r[1]!==location.protocol||r[2]!==location.host);if(e.dataType==="script"&&n==="GET"&&r){var z=s.getElementsByTagName("head")[0]||s.documentElement,C=s.createElement("script");C.src=e.url;if(e.scriptCharset)C.charset=e.scriptCharset;if(!j){var B=
false;C.onload=C.onreadystatechange=function(){if(!B&&(!this.readyState||this.readyState==="loaded"||this.readyState==="complete")){B=true;b();d();C.onload=C.onreadystatechange=null;z&&C.parentNode&&z.removeChild(C)}}}z.insertBefore(C,z.firstChild);return w}var E=false,x=e.xhr();if(x){e.username?x.open(n,e.url,e.async,e.username,e.password):x.open(n,e.url,e.async);try{if(e.data||a&&a.contentType)x.setRequestHeader("Content-Type",e.contentType);if(e.ifModified){c.lastModified[e.url]&&x.setRequestHeader("If-Modified-Since",
c.lastModified[e.url]);c.etag[e.url]&&x.setRequestHeader("If-None-Match",c.etag[e.url])}r||x.setRequestHeader("X-Requested-With","XMLHttpRequest");x.setRequestHeader("Accept",e.dataType&&e.accepts[e.dataType]?e.accepts[e.dataType]+", */*":e.accepts._default)}catch(ga){}if(e.beforeSend&&e.beforeSend.call(k,x,e)===false){e.global&&!--c.active&&c.event.trigger("ajaxStop");x.abort();return false}e.global&&f("ajaxSend",[x,e]);var g=x.onreadystatechange=function(q){if(!x||x.readyState===0||q==="abort"){E||
d();E=true;if(x)x.onreadystatechange=c.noop}else if(!E&&x&&(x.readyState===4||q==="timeout")){E=true;x.onreadystatechange=c.noop;i=q==="timeout"?"timeout":!c.httpSuccess(x)?"error":e.ifModified&&c.httpNotModified(x,e.url)?"notmodified":"success";var p;if(i==="success")try{o=c.httpData(x,e.dataType,e)}catch(v){i="parsererror";p=v}if(i==="success"||i==="notmodified")j||b();else c.handleError(e,x,i,p);d();q==="timeout"&&x.abort();if(e.async)x=null}};try{var h=x.abort;x.abort=function(){x&&h.call(x);
g("abort")}}catch(l){}e.async&&e.timeout>0&&setTimeout(function(){x&&!E&&g("timeout")},e.timeout);try{x.send(n==="POST"||n==="PUT"||n==="DELETE"?e.data:null)}catch(m){c.handleError(e,x,null,m);d()}e.async||g();return x}},handleError:function(a,b,d,f){if(a.error)a.error.call(a.context||a,b,d,f);if(a.global)(a.context?c(a.context):c.event).trigger("ajaxError",[b,a,f])},active:0,httpSuccess:function(a){try{return!a.status&&location.protocol==="file:"||a.status>=200&&a.status<300||a.status===304||a.status===
1223||a.status===0}catch(b){}return false},httpNotModified:function(a,b){var d=a.getResponseHeader("Last-Modified"),f=a.getResponseHeader("Etag");if(d)c.lastModified[b]=d;if(f)c.etag[b]=f;return a.status===304||a.status===0},httpData:function(a,b,d){var f=a.getResponseHeader("content-type")||"",e=b==="xml"||!b&&f.indexOf("xml")>=0;a=e?a.responseXML:a.responseText;e&&a.documentElement.nodeName==="parsererror"&&c.error("parsererror");if(d&&d.dataFilter)a=d.dataFilter(a,b);if(typeof a==="string")if(b===
"json"||!b&&f.indexOf("json")>=0)a=c.parseJSON(a);else if(b==="script"||!b&&f.indexOf("javascript")>=0)c.globalEval(a);return a},param:function(a,b){function d(i,o){if(c.isArray(o))c.each(o,function(k,n){b||/\[\]$/.test(i)?f(i,n):d(i+"["+(typeof n==="object"||c.isArray(n)?k:"")+"]",n)});else!b&&o!=null&&typeof o==="object"?c.each(o,function(k,n){d(i+"["+k+"]",n)}):f(i,o)}function f(i,o){o=c.isFunction(o)?o():o;e[e.length]=encodeURIComponent(i)+"="+encodeURIComponent(o)}var e=[];if(b===w)b=c.ajaxSettings.traditional;
if(c.isArray(a)||a.jquery)c.each(a,function(){f(this.name,this.value)});else for(var j in a)d(j,a[j]);return e.join("&").replace(yb,"+")}});var la={},Ab=/toggle|show|hide/,Bb=/^([+-]=)?([\d+-.]+)(.*)$/,W,va=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]];c.fn.extend({show:function(a,b){if(a||a===0)return this.animate(K("show",3),a,b);else{a=0;for(b=this.length;a<b;a++){var d=c.data(this[a],"olddisplay");
this[a].style.display=d||"";if(c.css(this[a],"display")==="none"){d=this[a].nodeName;var f;if(la[d])f=la[d];else{var e=c("<"+d+" />").appendTo("body");f=e.css("display");if(f==="none")f="block";e.remove();la[d]=f}c.data(this[a],"olddisplay",f)}}a=0;for(b=this.length;a<b;a++)this[a].style.display=c.data(this[a],"olddisplay")||"";return this}},hide:function(a,b){if(a||a===0)return this.animate(K("hide",3),a,b);else{a=0;for(b=this.length;a<b;a++){var d=c.data(this[a],"olddisplay");!d&&d!=="none"&&c.data(this[a],
"olddisplay",c.css(this[a],"display"))}a=0;for(b=this.length;a<b;a++)this[a].style.display="none";return this}},_toggle:c.fn.toggle,toggle:function(a,b){var d=typeof a==="boolean";if(c.isFunction(a)&&c.isFunction(b))this._toggle.apply(this,arguments);else a==null||d?this.each(function(){var f=d?a:c(this).is(":hidden");c(this)[f?"show":"hide"]()}):this.animate(K("toggle",3),a,b);return this},fadeTo:function(a,b,d){return this.filter(":hidden").css("opacity",0).show().end().animate({opacity:b},a,d)},
animate:function(a,b,d,f){var e=c.speed(b,d,f);if(c.isEmptyObject(a))return this.each(e.complete);return this[e.queue===false?"each":"queue"](function(){var j=c.extend({},e),i,o=this.nodeType===1&&c(this).is(":hidden"),k=this;for(i in a){var n=i.replace(ia,ja);if(i!==n){a[n]=a[i];delete a[i];i=n}if(a[i]==="hide"&&o||a[i]==="show"&&!o)return j.complete.call(this);if((i==="height"||i==="width")&&this.style){j.display=c.css(this,"display");j.overflow=this.style.overflow}if(c.isArray(a[i])){(j.specialEasing=
j.specialEasing||{})[i]=a[i][1];a[i]=a[i][0]}}if(j.overflow!=null)this.style.overflow="hidden";j.curAnim=c.extend({},a);c.each(a,function(r,u){var z=new c.fx(k,j,r);if(Ab.test(u))z[u==="toggle"?o?"show":"hide":u](a);else{var C=Bb.exec(u),B=z.cur(true)||0;if(C){u=parseFloat(C[2]);var E=C[3]||"px";if(E!=="px"){k.style[r]=(u||1)+E;B=(u||1)/z.cur(true)*B;k.style[r]=B+E}if(C[1])u=(C[1]==="-="?-1:1)*u+B;z.custom(B,u,E)}else z.custom(B,u,"")}});return true})},stop:function(a,b){var d=c.timers;a&&this.queue([]);
this.each(function(){for(var f=d.length-1;f>=0;f--)if(d[f].elem===this){b&&d[f](true);d.splice(f,1)}});b||this.dequeue();return this}});c.each({slideDown:K("show",1),slideUp:K("hide",1),slideToggle:K("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"}},function(a,b){c.fn[a]=function(d,f){return this.animate(b,d,f)}});c.extend({speed:function(a,b,d){var f=a&&typeof a==="object"?a:{complete:d||!d&&b||c.isFunction(a)&&a,duration:a,easing:d&&b||b&&!c.isFunction(b)&&b};f.duration=c.fx.off?0:typeof f.duration===
"number"?f.duration:c.fx.speeds[f.duration]||c.fx.speeds._default;f.old=f.complete;f.complete=function(){f.queue!==false&&c(this).dequeue();c.isFunction(f.old)&&f.old.call(this)};return f},easing:{linear:function(a,b,d,f){return d+f*a},swing:function(a,b,d,f){return(-Math.cos(a*Math.PI)/2+0.5)*f+d}},timers:[],fx:function(a,b,d){this.options=b;this.elem=a;this.prop=d;if(!b.orig)b.orig={}}});c.fx.prototype={update:function(){this.options.step&&this.options.step.call(this.elem,this.now,this);(c.fx.step[this.prop]||
c.fx.step._default)(this);if((this.prop==="height"||this.prop==="width")&&this.elem.style)this.elem.style.display="block"},cur:function(a){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null))return this.elem[this.prop];return(a=parseFloat(c.css(this.elem,this.prop,a)))&&a>-10000?a:parseFloat(c.curCSS(this.elem,this.prop))||0},custom:function(a,b,d){function f(j){return e.step(j)}this.startTime=J();this.start=a;this.end=b;this.unit=d||this.unit||"px";this.now=this.start;
this.pos=this.state=0;var e=this;f.elem=this.elem;if(f()&&c.timers.push(f)&&!W)W=setInterval(c.fx.tick,13)},show:function(){this.options.orig[this.prop]=c.style(this.elem,this.prop);this.options.show=true;this.custom(this.prop==="width"||this.prop==="height"?1:0,this.cur());c(this.elem).show()},hide:function(){this.options.orig[this.prop]=c.style(this.elem,this.prop);this.options.hide=true;this.custom(this.cur(),0)},step:function(a){var b=J(),d=true;if(a||b>=this.options.duration+this.startTime){this.now=
this.end;this.pos=this.state=1;this.update();this.options.curAnim[this.prop]=true;for(var f in this.options.curAnim)if(this.options.curAnim[f]!==true)d=false;if(d){if(this.options.display!=null){this.elem.style.overflow=this.options.overflow;a=c.data(this.elem,"olddisplay");this.elem.style.display=a?a:this.options.display;if(c.css(this.elem,"display")==="none")this.elem.style.display="block"}this.options.hide&&c(this.elem).hide();if(this.options.hide||this.options.show)for(var e in this.options.curAnim)c.style(this.elem,
e,this.options.orig[e]);this.options.complete.call(this.elem)}return false}else{e=b-this.startTime;this.state=e/this.options.duration;a=this.options.easing||(c.easing.swing?"swing":"linear");this.pos=c.easing[this.options.specialEasing&&this.options.specialEasing[this.prop]||a](this.state,e,0,1,this.options.duration);this.now=this.start+(this.end-this.start)*this.pos;this.update()}return true}};c.extend(c.fx,{tick:function(){for(var a=c.timers,b=0;b<a.length;b++)a[b]()||a.splice(b--,1);a.length||
c.fx.stop()},stop:function(){clearInterval(W);W=null},speeds:{slow:600,fast:200,_default:400},step:{opacity:function(a){c.style(a.elem,"opacity",a.now)},_default:function(a){if(a.elem.style&&a.elem.style[a.prop]!=null)a.elem.style[a.prop]=(a.prop==="width"||a.prop==="height"?Math.max(0,a.now):a.now)+a.unit;else a.elem[a.prop]=a.now}}});if(c.expr&&c.expr.filters)c.expr.filters.animated=function(a){return c.grep(c.timers,function(b){return a===b.elem}).length};c.fn.offset="getBoundingClientRect"in s.documentElement?
function(a){var b=this[0];if(a)return this.each(function(e){c.offset.setOffset(this,a,e)});if(!b||!b.ownerDocument)return null;if(b===b.ownerDocument.body)return c.offset.bodyOffset(b);var d=b.getBoundingClientRect(),f=b.ownerDocument;b=f.body;f=f.documentElement;return{top:d.top+(self.pageYOffset||c.support.boxModel&&f.scrollTop||b.scrollTop)-(f.clientTop||b.clientTop||0),left:d.left+(self.pageXOffset||c.support.boxModel&&f.scrollLeft||b.scrollLeft)-(f.clientLeft||b.clientLeft||0)}}:function(a){var b=
this[0];if(a)return this.each(function(r){c.offset.setOffset(this,a,r)});if(!b||!b.ownerDocument)return null;if(b===b.ownerDocument.body)return c.offset.bodyOffset(b);c.offset.initialize();var d=b.offsetParent,f=b,e=b.ownerDocument,j,i=e.documentElement,o=e.body;f=(e=e.defaultView)?e.getComputedStyle(b,null):b.currentStyle;for(var k=b.offsetTop,n=b.offsetLeft;(b=b.parentNode)&&b!==o&&b!==i;){if(c.offset.supportsFixedPosition&&f.position==="fixed")break;j=e?e.getComputedStyle(b,null):b.currentStyle;
k-=b.scrollTop;n-=b.scrollLeft;if(b===d){k+=b.offsetTop;n+=b.offsetLeft;if(c.offset.doesNotAddBorder&&!(c.offset.doesAddBorderForTableAndCells&&/^t(able|d|h)$/i.test(b.nodeName))){k+=parseFloat(j.borderTopWidth)||0;n+=parseFloat(j.borderLeftWidth)||0}f=d;d=b.offsetParent}if(c.offset.subtractsBorderForOverflowNotVisible&&j.overflow!=="visible"){k+=parseFloat(j.borderTopWidth)||0;n+=parseFloat(j.borderLeftWidth)||0}f=j}if(f.position==="relative"||f.position==="static"){k+=o.offsetTop;n+=o.offsetLeft}if(c.offset.supportsFixedPosition&&
f.position==="fixed"){k+=Math.max(i.scrollTop,o.scrollTop);n+=Math.max(i.scrollLeft,o.scrollLeft)}return{top:k,left:n}};c.offset={initialize:function(){var a=s.body,b=s.createElement("div"),d,f,e,j=parseFloat(c.curCSS(a,"marginTop",true))||0;c.extend(b.style,{position:"absolute",top:0,left:0,margin:0,border:0,width:"1px",height:"1px",visibility:"hidden"});b.innerHTML="<div style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;'><div></div></div><table style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;' cellpadding='0' cellspacing='0'><tr><td></td></tr></table>";
a.insertBefore(b,a.firstChild);d=b.firstChild;f=d.firstChild;e=d.nextSibling.firstChild.firstChild;this.doesNotAddBorder=f.offsetTop!==5;this.doesAddBorderForTableAndCells=e.offsetTop===5;f.style.position="fixed";f.style.top="20px";this.supportsFixedPosition=f.offsetTop===20||f.offsetTop===15;f.style.position=f.style.top="";d.style.overflow="hidden";d.style.position="relative";this.subtractsBorderForOverflowNotVisible=f.offsetTop===-5;this.doesNotIncludeMarginInBodyOffset=a.offsetTop!==j;a.removeChild(b);
c.offset.initialize=c.noop},bodyOffset:function(a){var b=a.offsetTop,d=a.offsetLeft;c.offset.initialize();if(c.offset.doesNotIncludeMarginInBodyOffset){b+=parseFloat(c.curCSS(a,"marginTop",true))||0;d+=parseFloat(c.curCSS(a,"marginLeft",true))||0}return{top:b,left:d}},setOffset:function(a,b,d){if(/static/.test(c.curCSS(a,"position")))a.style.position="relative";var f=c(a),e=f.offset(),j=parseInt(c.curCSS(a,"top",true),10)||0,i=parseInt(c.curCSS(a,"left",true),10)||0;if(c.isFunction(b))b=b.call(a,
d,e);d={top:b.top-e.top+j,left:b.left-e.left+i};"using"in b?b.using.call(a,d):f.css(d)}};c.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),d=this.offset(),f=/^body|html$/i.test(b[0].nodeName)?{top:0,left:0}:b.offset();d.top-=parseFloat(c.curCSS(a,"marginTop",true))||0;d.left-=parseFloat(c.curCSS(a,"marginLeft",true))||0;f.top+=parseFloat(c.curCSS(b[0],"borderTopWidth",true))||0;f.left+=parseFloat(c.curCSS(b[0],"borderLeftWidth",true))||0;return{top:d.top-
f.top,left:d.left-f.left}},offsetParent:function(){return this.map(function(){for(var a=this.offsetParent||s.body;a&&!/^body|html$/i.test(a.nodeName)&&c.css(a,"position")==="static";)a=a.offsetParent;return a})}});c.each(["Left","Top"],function(a,b){var d="scroll"+b;c.fn[d]=function(f){var e=this[0],j;if(!e)return null;if(f!==w)return this.each(function(){if(j=wa(this))j.scrollTo(!a?f:c(j).scrollLeft(),a?f:c(j).scrollTop());else this[d]=f});else return(j=wa(e))?"pageXOffset"in j?j[a?"pageYOffset":
"pageXOffset"]:c.support.boxModel&&j.document.documentElement[d]||j.document.body[d]:e[d]}});c.each(["Height","Width"],function(a,b){var d=b.toLowerCase();c.fn["inner"+b]=function(){return this[0]?c.css(this[0],d,false,"padding"):null};c.fn["outer"+b]=function(f){return this[0]?c.css(this[0],d,false,f?"margin":"border"):null};c.fn[d]=function(f){var e=this[0];if(!e)return f==null?null:this;if(c.isFunction(f))return this.each(function(j){var i=c(this);i[d](f.call(this,j,i[d]()))});return"scrollTo"in
e&&e.document?e.document.compatMode==="CSS1Compat"&&e.document.documentElement["client"+b]||e.document.body["client"+b]:e.nodeType===9?Math.max(e.documentElement["client"+b],e.body["scroll"+b],e.documentElement["scroll"+b],e.body["offset"+b],e.documentElement["offset"+b]):f===w?c.css(e,d):this.css(d,typeof f==="string"?f:f+"px")}});A.jQuery=A.$=c})(window);

@ -3,33 +3,52 @@
* 切换语言环境 addby zhongweikang@zhangyue.com 2015/09/23 * 切换语言环境 addby zhongweikang@zhangyue.com 2015/09/23
*/ */
var language;//当前语言 var language; //当前语言
var jsonEn;//英文 var jsonEn; //英文
var jsonZh;//中文 var jsonZh; //中文
/** /**
* 语言包 * 语言包
*/ */
jsonEn = { jsonEn = {
t0: "WiFi Upload", t1: "Documents", t2: "Name", t3: "Size", t4: "Operation", t0: "WiFi Upload",
t5: "Supported Format:TXT、EPUB、UMD", t6: "Drag here and Drop to upload", t1: "Documents",
t7: "You choosed [txt] files ,we only can upload [txt] of them。\nPlease choose TXT、EPUB、UMD files, file name cannot be repeated.", t2: "Name",
t8: "Failed", t9: "Cancel", t10: "Done", t11: "One of books is over 500MB", t12: "Bad file mode", t3: "Size",
t13: "File already exists", t14: "Canceled" t4: "Operation",
t5: "Supported Format:TXT、EPUB、UMD",
t6: "Drag here and Drop to upload",
t7: "You choosed [txt] files ,we only can upload [txt] of them。\nPlease choose TXT、EPUB、UMD files, file name cannot be repeated.",
t8: "Failed",
t9: "Cancel",
t10: "Done",
t11: "One of books is over 500MB",
t12: "Bad file mode",
t13: "File already exists",
t14: "Canceled",
}; };
jsonZh = { jsonZh = {
t0: "传书", t1: "文档", t2: "名字", t3: "大小", t4: "操作", t0: "传书",
t5: "支持类型:TXT、EPUB、UMD", t6: "拖至此处上传", t1: "文档",
t7: "你选择了[txt]个文件,我们只能上传其中的[txt]。\n请选择 TXT、EPUB、UMD 类型的文件,文件的名字不能重复。", t2: "名字",
t8: "失败", t9: "取消", t10: "完成", t11: "上传文件不能大于500MB", t12: "无效的文件格式", t3: "大小",
t13: "文件已存在", t14: "已取消" t4: "操作",
t5: "支持类型:TXT、EPUB、UMD",
t6: "拖至此处上传",
t7: "你选择了[txt]个文件,我们只能上传其中的[txt]。\n请选择 TXT、EPUB、UMD 类型的文件,文件的名字不能重复。",
t8: "失败",
t9: "取消",
t10: "完成",
t11: "上传文件不能大于500MB",
t12: "无效的文件格式",
t13: "文件已存在",
t14: "已取消",
}; };
if (navigator.language) { if (navigator.language) {
language = navigator.language; language = navigator.language;
} } else {
else { language = navigator.browserLanguage;
language = navigator.browserLanguage;
} }
language = language.toLowerCase(language); language = language.toLowerCase(language);
@ -37,23 +56,26 @@ language = language.toLowerCase(language);
* 切换语言 * 切换语言
*/ */
function langSwich(s) { function langSwich(s) {
switch (s) { switch (s) {
case 'en-us': case "en-us":
_strReplace(jsonEn); _strReplace(jsonEn);
break; break;
case 'zh-cn': case "zh-cn":
_strReplace(jsonZh); _strReplace(jsonZh);
break; break;
default: default:
_strReplace(jsonEn); _strReplace(jsonEn);
break; break;
} }
} }
function _strReplace(d) { function _strReplace(d) {
window.jsonLang = d;//全局变量在其他js文件中会遇到,勿删。 window.jsonLang = d; //全局变量在其他js文件中会遇到,勿删。
for (var i in jsonLang) { for (var i in jsonLang) {
$('[data-js=' + i + ']').html(d[i]) var element = document.querySelector("[data-js=" + i + "]");
if (element) {
element.innerHTML = d[i];
} }
}
} }
/** /**
@ -62,28 +84,28 @@ function _strReplace(d) {
* replace 待插入的文本 支持变量或数组 * replace 待插入的文本 支持变量或数组
*/ */
var isArray = function (obj) { var isArray = function (obj) {
return Object.prototype.toString.call(obj) === '[object Array]'; return Object.prototype.toString.call(obj) === "[object Array]";
} };
function stringReplace(content, replace) { function stringReplace(content, replace) {
var str = content;
if (!str) {
return null;
}
var str = content; if (isArray(replace)) {
if (!str) { return null; } strs = str.split("[txt]");
count = strs.length - 1;
if (isArray(replace)) {
strs = str.split("[txt]");
count = strs.length - 1;
var string = ''; var string = "";
for (var i = 0; i < count; i++) { for (var i = 0; i < count; i++) {
string = string + strs[i] + replace[i]; string = string + strs[i] + replace[i];
}
string = string + strs[count];
} else {
string = str.replace(replace, "[txt]");
} }
return string; string = string + strs[count];
} else {
string = str.replace(replace, "[txt]");
}
return string;
} }
//执行 //执行
langSwich(language); langSwich(language);

@ -116,8 +116,9 @@ function uploadError(file, errorCode, message) {
removeFileFromFilesUpload(filesUpload, file.id) removeFileFromFilesUpload(filesUpload, file.id)
errorMessage = jsonLang.t8; errorMessage = jsonLang.t8;
var dd = document.createElement("dd")
$("#handle_button_" + file.id).replaceWith("<dd>" + errorMessage + "</dd>") dd.innerHTML = errorMessage
document.getElementById("handle_button_" + file.id).replaceWith(dd)
} }

@ -98,12 +98,12 @@ abstract class BaseActivity<VB : ViewBinding>(
abstract fun onActivityCreated(savedInstanceState: Bundle?) abstract fun onActivityCreated(savedInstanceState: Bundle?)
final override fun onCreateOptionsMenu(menu: Menu?): Boolean { final override fun onCreateOptionsMenu(menu: Menu): Boolean {
return menu?.let { return menu.let {
val bool = onCompatCreateOptionsMenu(it) val bool = onCompatCreateOptionsMenu(it)
it.applyTint(this, toolBarTheme) it.applyTint(this, toolBarTheme)
bool bool
} ?: super.onCreateOptionsMenu(menu) }
} }
override fun onMenuOpened(featureId: Int, menu: Menu): Boolean { override fun onMenuOpened(featureId: Int, menu: Menu): Boolean {

@ -106,6 +106,9 @@ object AppConst {
var versionName: String = "" var versionName: String = ""
) )
/**
* The authority of a FileProvider defined in a <provider> element in your app's manifest.
*/
const val authority = BuildConfig.APPLICATION_ID + ".fileProvider" const val authority = BuildConfig.APPLICATION_ID + ".fileProvider"
} }

@ -29,5 +29,5 @@ object AppPattern {
/** /**
* 不发音段落判断 * 不发音段落判断
*/ */
val notReadAloudRegex = Regex("^(\\s|\\p{C}|\\p{P}|\\p{Z}|\\p{S})+\$") val notReadAloudRegex = Regex("^(\\s|\\p{C}|\\p{P}|\\p{Z}|\\p{S})+$")
} }

@ -85,7 +85,6 @@ object PreferKey {
const val exportCharset = "exportCharset" const val exportCharset = "exportCharset"
const val exportUseReplace = "exportUseReplace" const val exportUseReplace = "exportUseReplace"
const val useZhLayout = "useZhLayout" const val useZhLayout = "useZhLayout"
const val fullScreenGesturesSupport = "fullScreenGesturesSupport"
const val brightness = "brightness" const val brightness = "brightness"
const val nightBrightness = "nightBrightness" const val nightBrightness = "nightBrightness"
const val expandTextMenu = "expandTextMenu" const val expandTextMenu = "expandTextMenu"

@ -16,8 +16,11 @@ interface TxtTocRuleDao {
@get:Query("select * from txtTocRules where enable = 1 order by serialNumber") @get:Query("select * from txtTocRules where enable = 1 order by serialNumber")
val enabled: List<TxtTocRule> val enabled: List<TxtTocRule>
@get:Query("select ifNull(min(serialNumber), 0) from txtTocRules")
val minOrder: Int
@get:Query("select ifNull(max(serialNumber), 0) from txtTocRules") @get:Query("select ifNull(max(serialNumber), 0) from txtTocRules")
val lastOrderNum: Int val maxOrder: Int
@Insert(onConflict = OnConflictStrategy.REPLACE) @Insert(onConflict = OnConflictStrategy.REPLACE)
fun insert(vararg rule: TxtTocRule) fun insert(vararg rule: TxtTocRule)

@ -19,10 +19,10 @@ import timber.log.Timber
indices = [(Index(value = ["bookSourceUrl"], unique = false))] indices = [(Index(value = ["bookSourceUrl"], unique = false))]
) )
data class BookSource( data class BookSource(
var bookSourceName: String = "", // 名称
var bookSourceGroup: String? = null, // 分组
@PrimaryKey @PrimaryKey
var bookSourceUrl: String = "", // 地址,包括 http/https var bookSourceUrl: String = "", // 地址,包括 http/https
var bookSourceName: String = "", // 名称
var bookSourceGroup: String? = null, // 分组
var bookSourceType: Int = BookType.default, // 类型,0 文本,1 音频, 3 图片 var bookSourceType: Int = BookType.default, // 类型,0 文本,1 音频, 3 图片
var bookUrlPattern: String? = null, // 详情页url正则 var bookUrlPattern: String? = null, // 详情页url正则
var customOrder: Int = 0, // 手动排序编号 var customOrder: Int = 0, // 手动排序编号

@ -21,5 +21,4 @@ data class Bookmark(
var chapterName: String = "", var chapterName: String = "",
var bookText: String = "", var bookText: String = "",
var content: String = "" var content: String = ""
) : Parcelable ) : Parcelable

@ -29,7 +29,6 @@ data class ReplaceRule(
var order: Int = 0 var order: Int = 0
) : Parcelable { ) : Parcelable {
override fun equals(other: Any?): Boolean { override fun equals(other: Any?): Boolean {
if (other is ReplaceRule) { if (other is ReplaceRule) {
return other.id == id return other.id == id

@ -1,6 +1,7 @@
package io.legado.app.data.entities package io.legado.app.data.entities
import androidx.room.Entity import androidx.room.Entity
import androidx.room.Ignore
import androidx.room.PrimaryKey import androidx.room.PrimaryKey
@ -12,4 +13,9 @@ data class TxtTocRule(
var rule: String = "", var rule: String = "",
var serialNumber: Int = -1, var serialNumber: Int = -1,
var enable: Boolean = true var enable: Boolean = true
) ) {
@Ignore
constructor() : this(name = "")
}

@ -255,9 +255,6 @@ object AppConfig : SharedPreferences.OnSharedPreferenceChangeListener {
val replaceEnableDefault get() = appCtx.getPrefBoolean(PreferKey.replaceEnableDefault, true) val replaceEnableDefault get() = appCtx.getPrefBoolean(PreferKey.replaceEnableDefault, true)
val fullScreenGesturesSupport: Boolean
get() = appCtx.getPrefBoolean(PreferKey.fullScreenGesturesSupport, false)
val doublePageHorizontal: Boolean val doublePageHorizontal: Boolean
get() = appCtx.getPrefBoolean(PreferKey.doublePageHorizontal, true) get() = appCtx.getPrefBoolean(PreferKey.doublePageHorizontal, true)

@ -1,6 +1,5 @@
package io.legado.app.help package io.legado.app.help
import android.net.Uri
import io.legado.app.constant.AppPattern import io.legado.app.constant.AppPattern
import io.legado.app.constant.EventBus import io.legado.app.constant.EventBus
import io.legado.app.data.appDb import io.legado.app.data.appDb
@ -24,9 +23,9 @@ import kotlin.math.min
@Suppress("unused") @Suppress("unused")
object BookHelp { object BookHelp {
private const val cacheFolderName = "book_cache" val downloadDir: File = appCtx.externalFiles
const val cacheFolderName = "book_cache"
private const val cacheImageFolderName = "images" private const val cacheImageFolderName = "images"
private val downloadDir: File = appCtx.externalFiles
private val downloadImages = CopyOnWriteArraySet<String>() private val downloadImages = CopyOnWriteArraySet<String>()
fun clearCache() { fun clearCache() {
@ -58,25 +57,7 @@ object BookHelp {
} }
} }
fun getEpubFile(book: Book): File { fun saveContent(
val file = downloadDir.getFile(cacheFolderName, book.getFolderName(), "index.epubx")
if (!file.exists()) {
val input = if (book.bookUrl.isContentScheme()) {
val uri = Uri.parse(book.bookUrl)
appCtx.contentResolver.openInputStream(uri)
} else {
File(book.bookUrl).inputStream()
}
if (input != null) {
FileUtils.writeInputStream(file, input)
}
}
return file
}
suspend fun saveContent(
bookSource: BookSource,
book: Book, book: Book,
bookChapter: BookChapter, bookChapter: BookChapter,
content: String content: String
@ -89,6 +70,15 @@ object BookHelp {
book.getFolderName(), book.getFolderName(),
bookChapter.getFileName(), bookChapter.getFileName(),
).writeText(content) ).writeText(content)
}
suspend fun saveContent(
bookSource: BookSource,
book: Book,
bookChapter: BookChapter,
content: String
) {
saveContent(book, bookChapter, content)
//保存图片 //保存图片
content.split("\n").forEach { content.split("\n").forEach {
val matcher = AppPattern.imgPattern.matcher(it) val matcher = AppPattern.imgPattern.matcher(it)
@ -200,16 +190,11 @@ object BookHelp {
*/ */
fun getContent(book: Book, bookChapter: BookChapter): String? { fun getContent(book: Book, bookChapter: BookChapter): String? {
if (book.isLocalTxt() || book.isUmd()) { if (book.isLocalTxt() || book.isUmd()) {
return LocalBook.getContext(book, bookChapter) return LocalBook.getContent(book, bookChapter)
} else if (book.isEpub() && !hasContent(book, bookChapter)) { } else if (book.isEpub() && !hasContent(book, bookChapter)) {
val string = LocalBook.getContext(book, bookChapter) val string = LocalBook.getContent(book, bookChapter)
string?.let { string?.let {
FileUtils.createFileIfNotExist( saveContent(book, bookChapter, it)
downloadDir,
cacheFolderName,
book.getFolderName(),
bookChapter.getFileName(),
).writeText(it)
} }
return string return string
} else { } else {

@ -15,7 +15,7 @@ object DirectLinkUpload {
private const val downloadUrlRuleKey = "directLinkDownloadUrlRule" private const val downloadUrlRuleKey = "directLinkDownloadUrlRule"
private const val summaryKey = "directSummary" private const val summaryKey = "directSummary"
suspend fun upLoad(fileName: String, file: ByteArray, contentType: String): String { suspend fun upLoad(fileName: String, file: Any, contentType: String): String {
val url = getUploadUrl() val url = getUploadUrl()
if (url.isNullOrBlank()) { if (url.isNullOrBlank()) {
throw NoStackTraceException("上传url未配置") throw NoStackTraceException("上传url未配置")

@ -15,7 +15,6 @@ import io.legado.app.utils.*
import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.async import kotlinx.coroutines.async
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import org.apache.commons.lang3.time.DateFormatUtils
import org.jsoup.Connection import org.jsoup.Connection
import org.jsoup.Jsoup import org.jsoup.Jsoup
import splitties.init.appCtx import splitties.init.appCtx
@ -25,6 +24,7 @@ import java.io.ByteArrayOutputStream
import java.io.File import java.io.File
import java.net.URLEncoder import java.net.URLEncoder
import java.nio.charset.Charset import java.nio.charset.Charset
import java.text.SimpleDateFormat
import java.util.* import java.util.*
import java.util.zip.ZipEntry import java.util.zip.ZipEntry
import java.util.zip.ZipInputStream import java.util.zip.ZipInputStream
@ -233,7 +233,10 @@ interface JsExtensions {
*/ */
fun timeFormatUTC(time: Long, format: String, sh: Int): String? { fun timeFormatUTC(time: Long, format: String, sh: Int): String? {
val utc = SimpleTimeZone(sh, "UTC") val utc = SimpleTimeZone(sh, "UTC")
return DateFormatUtils.format(Date(time), format, utc, null) return SimpleDateFormat(format, Locale.getDefault()).run {
timeZone = utc
format(Date(time))
}
} }
/** /**
@ -487,6 +490,17 @@ interface JsExtensions {
return msg return msg
} }
/**
* 输出对象类型
*/
fun logType(any: Any?) {
if (any == null) {
log("null")
} else {
log(any.javaClass.name)
}
}
/** /**
* 生成UUID * 生成UUID
*/ */

@ -3,6 +3,7 @@ package io.legado.app.help.http
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import android.util.AndroidRuntimeException
import android.webkit.CookieManager import android.webkit.CookieManager
import android.webkit.WebSettings import android.webkit.WebSettings
import android.webkit.WebView import android.webkit.WebView
@ -51,7 +52,11 @@ class BackstageWebView(
} }
} }
runOnUI { runOnUI {
load() try {
load()
} catch (error: Throwable) {
block.cancel(error)
}
} }
} }
@ -59,6 +64,7 @@ class BackstageWebView(
return encode ?: "utf-8" return encode ?: "utf-8"
} }
@Throws(AndroidRuntimeException::class)
private fun load() { private fun load() {
val webView = createWebView() val webView = createWebView()
mWebView = webView mWebView = webView

@ -3,7 +3,6 @@ package io.legado.app.help.http
import io.legado.app.help.AppConfig import io.legado.app.help.AppConfig
import io.legado.app.help.http.cronet.CronetInterceptor import io.legado.app.help.http.cronet.CronetInterceptor
import io.legado.app.help.http.cronet.CronetLoader import io.legado.app.help.http.cronet.CronetLoader
import io.legado.app.help.http.cronet.cronetEngine
import okhttp3.ConnectionSpec import okhttp3.ConnectionSpec
import okhttp3.Credentials import okhttp3.Credentials
import okhttp3.Interceptor import okhttp3.Interceptor
@ -44,7 +43,7 @@ val okHttpClient: OkHttpClient by lazy {
.build() .build()
chain.proceed(request) chain.proceed(request)
}) })
if (AppConfig.isCronet && CronetLoader.install() && cronetEngine != null) { if (AppConfig.isCronet && CronetLoader.install() && !AppConfig.isGooglePlay) {
builder.addInterceptor(CronetInterceptor(null)) builder.addInterceptor(CronetInterceptor(null))
} }
builder.build() builder.build()

@ -3,6 +3,7 @@ package io.legado.app.help.http
import io.legado.app.constant.AppConst import io.legado.app.constant.AppConst
import io.legado.app.help.AppConfig import io.legado.app.help.AppConfig
import io.legado.app.utils.EncodingDetect import io.legado.app.utils.EncodingDetect
import io.legado.app.utils.GSON
import io.legado.app.utils.UTF8BOMFighter import io.legado.app.utils.UTF8BOMFighter
import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.suspendCancellableCoroutine
@ -10,7 +11,9 @@ import kotlinx.coroutines.withContext
import okhttp3.* import okhttp3.*
import okhttp3.HttpUrl.Companion.toHttpUrl import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.MediaType.Companion.toMediaType import okhttp3.MediaType.Companion.toMediaType
import okhttp3.RequestBody.Companion.asRequestBody
import okhttp3.RequestBody.Companion.toRequestBody import okhttp3.RequestBody.Companion.toRequestBody
import java.io.File
import java.io.IOException import java.io.IOException
import java.nio.charset.Charset import java.nio.charset.Charset
import kotlin.coroutines.resume import kotlin.coroutines.resume
@ -152,9 +155,22 @@ fun Request.Builder.postMultipart(type: String?, form: Map<String, Any>) {
when (val value = it.value) { when (val value = it.value) {
is Map<*, *> -> { is Map<*, *> -> {
val fileName = value["fileName"] as String val fileName = value["fileName"] as String
val file = value["file"] as ByteArray val file = value["file"]
val mediaType = (value["contentType"] as? String)?.toMediaType() val mediaType = (value["contentType"] as? String)?.toMediaType()
val requestBody = file.toRequestBody(mediaType) val requestBody = when (file) {
is File -> {
file.asRequestBody(mediaType)
}
is ByteArray -> {
file.toRequestBody(mediaType)
}
is String -> {
file.toRequestBody(mediaType)
}
else -> {
GSON.toJson(file).toRequestBody(mediaType)
}
}
multipartBody.addFormDataPart(it.key, fileName, requestBody) multipartBody.addFormDataPart(it.key, fileName, requestBody)
} }
else -> multipartBody.addFormDataPart(it.key, it.value.toString()) else -> multipartBody.addFormDataPart(it.key, it.value.toString())

@ -2,11 +2,10 @@ package io.legado.app.help.http.cronet
import io.legado.app.help.http.CookieStore import io.legado.app.help.http.CookieStore
import okhttp3.* import okhttp3.*
import java.io.IOException import timber.log.Timber
class CronetInterceptor(private val cookieJar: CookieJar?) : Interceptor { class CronetInterceptor(private val cookieJar: CookieJar?) : Interceptor {
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response { override fun intercept(chain: Interceptor.Chain): Response {
val original: Request = chain.request() val original: Request = chain.request()
//Cronet未初始化 //Cronet未初始化
@ -29,20 +28,18 @@ class CronetInterceptor(private val cookieJar: CookieJar?) : Interceptor {
response response
} ?: chain.proceed(original) } ?: chain.proceed(original)
} catch (e: Exception) { } catch (e: Exception) {
//不能抛出错误,抛出错误会导致应用崩溃
//遇到Cronet处理有问题时的情况,如证书过期等等,回退到okhttp处理 //遇到Cronet处理有问题时的情况,如证书过期等等,回退到okhttp处理
if (e.message.toString().contains("ERR_CERT_", true) if (!e.message.toString().contains("ERR_CERT_", true)
|| e.message.toString().contains("ERR_SSL_", true) && !e.message.toString().contains("ERR_SSL_", true)
) { ) {
chain.proceed(original) Timber.e(e)
} else {
throw e
} }
chain.proceed(original)
} }
} }
@Throws(IOException::class)
private fun proceedWithCronet(request: Request, call: Call): Response? { private fun proceedWithCronet(request: Request, call: Call): Response? {
val callback = CronetRequestCallback(request, call) val callback = CronetRequestCallback(request, call)
buildRequest(request, callback)?.let { buildRequest(request, callback)?.let {

@ -261,7 +261,7 @@ object CronetLoader : CronetEngine.Builder.LibraryLoader() {
downloadTempFile: File, downloadTempFile: File,
destSuccessFile: File destSuccessFile: File
) { ) {
if (download) { if (download || AppConfig.isGooglePlay) {
return return
} }
download = true download = true

@ -4,6 +4,7 @@ import android.content.Context
import android.net.Uri import android.net.Uri
import androidx.documentfile.provider.DocumentFile import androidx.documentfile.provider.DocumentFile
import io.legado.app.R import io.legado.app.R
import io.legado.app.constant.AppLog
import io.legado.app.constant.PreferKey import io.legado.app.constant.PreferKey
import io.legado.app.data.appDb import io.legado.app.data.appDb
import io.legado.app.help.DefaultData import io.legado.app.help.DefaultData
@ -15,6 +16,7 @@ import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import splitties.init.appCtx import splitties.init.appCtx
import java.io.File import java.io.File
import java.io.FileOutputStream
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
@ -51,7 +53,8 @@ object Backup {
Coroutine.async { Coroutine.async {
backup(context, context.getPrefString(PreferKey.backupPath) ?: "", true) backup(context, context.getPrefString(PreferKey.backupPath) ?: "", true)
}.onError { }.onError {
appCtx.toastOnUi(R.string.autobackup_fail) AppLog.put("备份出错\n${it.localizedMessage}", it)
appCtx.toastOnUi(appCtx.getString(R.string.autobackup_fail, it.localizedMessage))
} }
} }
} }
@ -112,8 +115,10 @@ object Backup {
private fun writeListToJson(list: List<Any>, fileName: String, path: String) { private fun writeListToJson(list: List<Any>, fileName: String, path: String) {
if (list.isNotEmpty()) { if (list.isNotEmpty()) {
val json = GSON.toJson(list) val file = FileUtils.createFileIfNotExist(path + File.separator + fileName)
FileUtils.createFileIfNotExist(path + File.separator + fileName).writeText(json) FileOutputStream(file).use {
GSON.writeToOutputStream(it, list)
}
} }
} }

@ -52,7 +52,6 @@ object ReadBook : CoroutineScope by MainScope() {
durChapterIndex = book.durChapterIndex durChapterIndex = book.durChapterIndex
durChapterPos = book.durChapterPos durChapterPos = book.durChapterPos
isLocalBook = book.origin == BookType.local isLocalBook = book.origin == BookType.local
chapterSize = book.totalChapterNum
clearTextChapter() clearTextChapter()
callBack?.upMenuView() callBack?.upMenuView()
callBack?.upPageAnim() callBack?.upPageAnim()
@ -63,6 +62,18 @@ object ReadBook : CoroutineScope by MainScope() {
} }
} }
fun upData(book: Book) {
ReadBook.book = book
chapterSize = appDb.bookChapterDao.getChapterCount(book.bookUrl)
if (durChapterIndex != book.durChapterIndex) {
durChapterIndex = book.durChapterIndex
durChapterPos = book.durChapterPos
clearTextChapter()
}
callBack?.upMenuView()
upWebBook(book)
}
fun upWebBook(book: Book) { fun upWebBook(book: Book) {
if (book.origin == BookType.local) { if (book.origin == BookType.local) {
bookSource = null bookSource = null

@ -481,7 +481,7 @@ class AnalyzeUrl(
/** /**
* 上传文件 * 上传文件
*/ */
suspend fun upload(fileName: String, file: ByteArray, contentType: String): StrResponse { suspend fun upload(fileName: String, file: Any, contentType: String): StrResponse {
return getProxyClient(proxy).newCallStrResponse(retry) { return getProxyClient(proxy).newCallStrResponse(retry) {
url(urlNoQuery) url(urlNoQuery)
val bodyMap = GSON.fromJsonObject<HashMap<String, Any>>(body)!! val bodyMap = GSON.fromJsonObject<HashMap<String, Any>>(body)!!

@ -2,17 +2,19 @@ package io.legado.app.model.localBook
import android.graphics.Bitmap import android.graphics.Bitmap
import android.graphics.BitmapFactory import android.graphics.BitmapFactory
import android.net.Uri
import android.text.TextUtils import android.text.TextUtils
import io.legado.app.data.entities.Book import io.legado.app.data.entities.Book
import io.legado.app.data.entities.BookChapter import io.legado.app.data.entities.BookChapter
import io.legado.app.help.BookHelp import io.legado.app.help.BookHelp
import io.legado.app.utils.FileUtils import io.legado.app.utils.*
import io.legado.app.utils.HtmlFormatter
import io.legado.app.utils.MD5Utils
import io.legado.app.utils.externalFiles
import me.ag2s.epublib.domain.EpubBook import me.ag2s.epublib.domain.EpubBook
import me.ag2s.epublib.domain.Resource
import me.ag2s.epublib.domain.TOCReference
import me.ag2s.epublib.epub.EpubReader import me.ag2s.epublib.epub.EpubReader
import org.jsoup.Jsoup import org.jsoup.Jsoup
import org.jsoup.nodes.Element
import org.jsoup.select.Elements
import splitties.init.appCtx import splitties.init.appCtx
import timber.log.Timber import timber.log.Timber
import java.io.File import java.io.File
@ -28,10 +30,29 @@ class EpubFile(var book: Book) {
companion object { companion object {
private var eFile: EpubFile? = null private var eFile: EpubFile? = null
fun getFile(book: Book): File {
val file = BookHelp.downloadDir.getFile(
BookHelp.cacheFolderName,
book.getFolderName(),
"index.epubx"
)
if (!file.exists()) {
val input = if (book.bookUrl.isContentScheme()) {
val uri = Uri.parse(book.bookUrl)
appCtx.contentResolver.openInputStream(uri)
} else {
File(book.bookUrl).inputStream()
}
if (input != null) {
FileUtils.writeInputStream(file, input)
}
}
return file
}
@Synchronized @Synchronized
private fun getEFile(book: Book): EpubFile { private fun getEFile(book: Book): EpubFile {
BookHelp.getEpubFile(book) getFile(book)
if (eFile == null || eFile?.book?.bookUrl != book.bookUrl) { if (eFile == null || eFile?.book?.bookUrl != book.bookUrl) {
eFile = EpubFile(book) eFile = EpubFile(book)
//对于Epub文件默认不启用替换 //对于Epub文件默认不启用替换
@ -105,12 +126,9 @@ class EpubFile(var book: Book) {
/*重写epub文件解析代码,直接读出压缩包文件生成Resources给epublib,这样的好处是可以逐一修改某些文件的格式错误*/ /*重写epub文件解析代码,直接读出压缩包文件生成Resources给epublib,这样的好处是可以逐一修改某些文件的格式错误*/
private fun readEpub(): EpubBook? { private fun readEpub(): EpubBook? {
try { try {
val file = getFile(book)
val file = BookHelp.getEpubFile(book)
//通过懒加载读取epub //通过懒加载读取epub
return EpubReader().readEpubLazy(ZipFile(file), "utf-8") return EpubReader().readEpubLazy(ZipFile(file), "utf-8")
} catch (e: Exception) { } catch (e: Exception) {
Timber.e(e) Timber.e(e)
} }
@ -119,48 +137,27 @@ class EpubFile(var book: Book) {
private fun getContent(chapter: BookChapter): String? { private fun getContent(chapter: BookChapter): String? {
/*获取当前章节文本*/ /*获取当前章节文本*/
return getChildChapter(chapter, chapter.url) epubBook?.let { epubBook ->
} val nextUrl = chapter.getVariable("nextUrl")
val startFragmentId = chapter.startFragmentId
private fun getChildChapter(chapter: BookChapter, href: String): String? { val endFragmentId = chapter.endFragmentId
epubBook?.let { val elements = Elements()
val body = Jsoup.parse(String(it.resources.getByHref(href).data, mCharset)).body() var isChapter = false
/*一些书籍依靠href索引的resource会包含多个章节,需要依靠fragmentId来截取到当前章节的内容*/
if (chapter.url == href) { /*注:这里较大增加了内容加载的时间,所以首次获取内容后可存储到本地cache,减少重复加载*/
val startFragmentId = chapter.startFragmentId for (res in epubBook.contents) {
val endFragmentId = chapter.endFragmentId if (chapter.url.substringBeforeLast("#") == res.href) {
/*一些书籍依靠href索引的resource会包含多个章节,需要依靠fragmentId来截取到当前章节的内容*/ elements.add(getBody(res, startFragmentId, endFragmentId))
/*注:这里较大增加了内容加载的时间,所以首次获取内容后可存储到本地cache,减少重复加载*/ isChapter = true
if (!startFragmentId.isNullOrBlank()) } else if (isChapter) {
body.getElementById(startFragmentId)?.previousElementSiblings()?.remove() if (nextUrl.isNullOrBlank() || res.href == nextUrl.substringBeforeLast("#")) {
if (!endFragmentId.isNullOrBlank() && endFragmentId != startFragmentId) break
body.getElementById(endFragmentId)?.nextElementSiblings()?.remove() }
} elements.add(getBody(res, startFragmentId, endFragmentId))
}
/*选择去除正文中的H标签,部分书籍标题与阅读标题重复待优化*/
var tag = Book.hTag
if (book.getDelTag(tag)) {
body.getElementsByTag("h1").remove()
body.getElementsByTag("h2").remove()
body.getElementsByTag("h3").remove()
body.getElementsByTag("h4").remove()
body.getElementsByTag("h5").remove()
body.getElementsByTag("h6").remove()
//body.getElementsMatchingOwnText(chapter.title)?.remove()
}
/*选择去除正文中的img标签,目前图片支持效果待优化*/
tag = Book.imgTag
if (book.getDelTag(tag)) {
body.getElementsByTag("img").remove()
} }
val elements = body.children()
elements.select("script").remove()
elements.select("style").remove()
/*选择去除正文中的ruby标签,目前注释支持效果待优化*/
tag = Book.rubyTag
var html = elements.outerHtml() var html = elements.outerHtml()
val tag = Book.rubyTag
if (book.getDelTag(tag)) { if (book.getDelTag(tag)) {
html = html.replace("<ruby>\\s?([\\u4e00-\\u9fa5])\\s?.*?</ruby>".toRegex(), "$1") html = html.replace("<ruby>\\s?([\\u4e00-\\u9fa5])\\s?.*?</ruby>".toRegex(), "$1")
} }
@ -169,6 +166,32 @@ class EpubFile(var book: Book) {
return null return null
} }
private fun getBody(res: Resource, startFragmentId: String?, endFragmentId: String?): Element {
val body = Jsoup.parse(String(res.data, mCharset)).body()
if (!startFragmentId.isNullOrBlank()) {
body.getElementById(startFragmentId)?.previousElementSiblings()?.remove()
}
if (!endFragmentId.isNullOrBlank() && endFragmentId != startFragmentId) {
body.getElementById(endFragmentId)?.nextElementSiblings()?.remove()
}
/*选择去除正文中的H标签,部分书籍标题与阅读标题重复待优化*/
val tag = Book.hTag
if (book.getDelTag(tag)) {
body.getElementsByTag("h1").remove()
body.getElementsByTag("h2").remove()
body.getElementsByTag("h3").remove()
body.getElementsByTag("h4").remove()
body.getElementsByTag("h5").remove()
body.getElementsByTag("h6").remove()
//body.getElementsMatchingOwnText(chapter.title)?.remove()
}
val children = body.children()
children.select("script").remove()
children.select("style").remove()
return body
}
private fun getImage(href: String): InputStream? { private fun getImage(href: String): InputStream? {
val abHref = href.replace("../", "") val abHref = href.replace("../", "")
return epubBook?.resources?.getByHref(abHref)?.inputStream return epubBook?.resources?.getByHref(abHref)?.inputStream
@ -198,34 +221,116 @@ class EpubFile(var book: Book) {
private fun getChapterList(): ArrayList<BookChapter> { private fun getChapterList(): ArrayList<BookChapter> {
val chapterList = ArrayList<BookChapter>() val chapterList = ArrayList<BookChapter>()
epubBook?.tableOfContents?.allUniqueResources?.forEachIndexed { index, resource -> epubBook?.let { eBook ->
var title = resource.title val refs = eBook.tableOfContents.tocReferences
if (TextUtils.isEmpty(title)) { if (refs == null || refs.isEmpty()) {
try { val spineReferences = eBook.spine.spineReferences
val doc = var i = 0
Jsoup.parse(String(resource.data, mCharset)) val size = spineReferences.size
val elements = doc.getElementsByTag("title") while (i < size) {
if (elements.size > 0) { val resource = spineReferences[i].resource
title = elements[0].text() var title = resource.title
if (TextUtils.isEmpty(title)) {
try {
val doc =
Jsoup.parse(String(resource.data, mCharset))
val elements = doc.getElementsByTag("title")
if (elements.size > 0) {
title = elements[0].text()
}
} catch (e: IOException) {
e.printStackTrace()
}
} }
} catch (e: IOException) { val chapter = BookChapter()
Timber.e(e) chapter.index = i
chapter.bookUrl = book.bookUrl
chapter.url = resource.href
if (i == 0 && title.isEmpty()) {
chapter.title = "封面"
} else {
chapter.title = title
}
chapterList.add(chapter)
i++
} }
}
val chapter = BookChapter()
chapter.index = index
chapter.bookUrl = book.bookUrl
chapter.url = resource.href
if (index == 0 && title.isEmpty()) {
chapter.title = "封面"
} else { } else {
chapter.title = title parseFirstPage(chapterList, refs)
parseMenu(chapterList, refs, 0)
for (i in chapterList.indices) {
chapterList[i].index = i
}
} }
chapterList.add(chapter)
} }
book.latestChapterTitle = chapterList.lastOrNull()?.title book.latestChapterTitle = chapterList.lastOrNull()?.title
book.totalChapterNum = chapterList.size book.totalChapterNum = chapterList.size
return chapterList return chapterList
} }
/*获取书籍起始页内容。部分书籍第一章之前存在封面,引言,扉页等内容*/
/*tile获取不同书籍风格杂乱,格式化处理待优化*/
private var durIndex = 0
private fun parseFirstPage(
chapterList: ArrayList<BookChapter>,
refs: List<TOCReference>?
) {
val contents = epubBook?.contents
if (epubBook == null || contents == null || refs == null) return
var i = 0
durIndex = 0
while (i < contents.size) {
val content = contents[i]
if (!content.mediaType.toString().contains("htm")) continue
/*检索到第一章href停止*/
if (refs[0].completeHref == content.href) break
val chapter = BookChapter()
var title = content.title
if (TextUtils.isEmpty(title)) {
val elements = Jsoup.parse(
String(epubBook!!.resources.getByHref(content.href).data, mCharset)
).getElementsByTag("title")
title =
if (elements.size > 0 && elements[0].text().isNotBlank())
elements[0].text()
else
"--卷首--"
}
chapter.bookUrl = book.bookUrl
chapter.title = title
chapter.url = content.href
chapter.startFragmentId =
if (content.href.substringAfter("#") == content.href) null
else content.href.substringAfter("#")
chapterList.lastOrNull()?.endFragmentId = chapter.startFragmentId
chapterList.lastOrNull()?.putVariable("nextUrl", chapter.url)
chapterList.add(chapter)
durIndex++
i++
}
}
private fun parseMenu(
chapterList: ArrayList<BookChapter>,
refs: List<TOCReference>?,
level: Int
) {
refs?.forEach { ref ->
if (ref.resource != null) {
val chapter = BookChapter()
chapter.bookUrl = book.bookUrl
chapter.title = ref.title
chapter.url = ref.completeHref
chapter.startFragmentId = ref.fragmentId
chapterList.lastOrNull()?.endFragmentId = chapter.startFragmentId
chapterList.lastOrNull()?.putVariable("nextUrl", chapter.url)
chapterList.add(chapter)
durIndex++
}
if (ref.children != null && ref.children.isNotEmpty()) {
parseMenu(chapterList, ref.children, level + 1)
}
}
}
} }

@ -43,7 +43,7 @@ object LocalBook {
return chapters return chapters
} }
fun getContext(book: Book, chapter: BookChapter): String? { fun getContent(book: Book, chapter: BookChapter): String? {
return when { return when {
book.isEpub() -> { book.isEpub() -> {
EpubFile.getContent(book, chapter) EpubFile.getContent(book, chapter)
@ -151,11 +151,10 @@ object LocalBook {
bookFile.delete() bookFile.delete()
} }
if (book.isEpub()) { if (book.isEpub()) {
val bookFile = BookHelp.getEpubFile(book).parentFile val bookFile = EpubFile.getFile(book).parentFile
if (bookFile != null && bookFile.exists()) { if (bookFile != null && bookFile.exists()) {
FileUtils.delete(bookFile, true) FileUtils.delete(bookFile, true)
} }
} }
if (deleteOriginal) { if (deleteOriginal) {

@ -56,7 +56,12 @@ class SearchModel(private val scope: CoroutineScope, private val callBack: CallB
if (searchGroup.isBlank()) { if (searchGroup.isBlank()) {
bookSourceList.addAll(appDb.bookSourceDao.allEnabled) bookSourceList.addAll(appDb.bookSourceDao.allEnabled)
} else { } else {
bookSourceList.addAll(appDb.bookSourceDao.getEnabledByGroup(searchGroup)) val sources = appDb.bookSourceDao.getEnabledByGroup(searchGroup)
if (sources.isEmpty()) {
bookSourceList.addAll(appDb.bookSourceDao.allEnabled)
} else {
bookSourceList.addAll(sources)
}
} }
} else { } else {
searchPage++ searchPage++
@ -74,20 +79,22 @@ class SearchModel(private val scope: CoroutineScope, private val callBack: CallB
} }
searchIndex++ searchIndex++
val source = bookSourceList[searchIndex] val source = bookSourceList[searchIndex]
val task = WebBook.searchBook( searchPool?.let { searchPool ->
scope, val task = WebBook.searchBook(
source, scope,
searchKey, source,
searchPage, searchKey,
context = searchPool!! searchPage,
).timeout(30000L) context = searchPool
.onSuccess(searchPool) { ).timeout(30000L)
onSuccess(searchId, it) .onSuccess(searchPool) {
} onSuccess(searchId, it)
.onFinally(searchPool) { }
onFinally(searchId) .onFinally(searchPool) {
} onFinally(searchId)
tasks.add(task) }
tasks.add(task)
}
} }
@Synchronized @Synchronized

@ -44,10 +44,16 @@ abstract class BaseReadAloudService : BaseService(),
} }
} }
private lateinit var audioManager: AudioManager private val audioManager: AudioManager by lazy {
private var mFocusRequest: AudioFocusRequestCompat? = null getSystemService(Context.AUDIO_SERVICE) as AudioManager
private var broadcastReceiver: BroadcastReceiver? = null }
private lateinit var mediaSessionCompat: MediaSessionCompat private val mFocusRequest: AudioFocusRequestCompat by lazy {
MediaHelp.getFocusRequest(this)
}
private val mediaSessionCompat: MediaSessionCompat by lazy {
MediaSessionCompat(this, "readAloud")
}
private var audioFocusLossTransient = false
internal val contentList = arrayListOf<String>() internal val contentList = arrayListOf<String>()
internal var nowSpeak: Int = 0 internal var nowSpeak: Int = 0
internal var readAloudNumber: Int = 0 internal var readAloudNumber: Int = 0
@ -55,13 +61,18 @@ abstract class BaseReadAloudService : BaseService(),
internal var pageIndex = 0 internal var pageIndex = 0
private var dsJob: Job? = null private var dsJob: Job? = null
private val broadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
if (AudioManager.ACTION_AUDIO_BECOMING_NOISY == intent.action) {
pauseReadAloud(true)
}
}
}
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
isRun = true isRun = true
pause = false pause = false
audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager
mFocusRequest = MediaHelp.getFocusRequest(this)
mediaSessionCompat = MediaSessionCompat(this, "readAloud")
initMediaSession() initMediaSession()
initBroadcastReceiver() initBroadcastReceiver()
upNotification() upNotification()
@ -258,13 +269,6 @@ abstract class BaseReadAloudService : BaseService(),
* 断开耳机监听 * 断开耳机监听
*/ */
private fun initBroadcastReceiver() { private fun initBroadcastReceiver() {
broadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
if (AudioManager.ACTION_AUDIO_BECOMING_NOISY == intent.action) {
pauseReadAloud(true)
}
}
}
val intentFilter = IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY) val intentFilter = IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY)
registerReceiver(broadcastReceiver, intentFilter) registerReceiver(broadcastReceiver, intentFilter)
} }
@ -275,14 +279,19 @@ abstract class BaseReadAloudService : BaseService(),
override fun onAudioFocusChange(focusChange: Int) { override fun onAudioFocusChange(focusChange: Int) {
when (focusChange) { when (focusChange) {
AudioManager.AUDIOFOCUS_GAIN -> { AudioManager.AUDIOFOCUS_GAIN -> {
// 重新获得焦点, 可做恢复播放,恢复后台音量的操作 AppLog.put("重新获得焦点, 恢复播放")
audioFocusLossTransient = false
if (!pause) resumeReadAloud() if (!pause) resumeReadAloud()
} }
AudioManager.AUDIOFOCUS_LOSS -> { AudioManager.AUDIOFOCUS_LOSS -> {
// 永久丢失焦点除非重新主动获取,这种情况是被其他播放器抢去了焦点, 为避免与其他播放器混音,可将音乐暂停 AppLog.put("永久丢失焦点")
if (audioFocusLossTransient) {
pauseReadAloud(true)
}
} }
AudioManager.AUDIOFOCUS_LOSS_TRANSIENT -> { AudioManager.AUDIOFOCUS_LOSS_TRANSIENT -> {
// 暂时丢失焦点,这种情况是被其他应用申请了短暂的焦点,可压低后台音量 AppLog.put("暂时丢失焦点, 暂停播放")
audioFocusLossTransient = true
if (!pause) pauseReadAloud(false) if (!pause) pauseReadAloud(false)
} }
AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK -> { AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK -> {
@ -356,4 +365,5 @@ abstract class BaseReadAloudService : BaseService(),
stopSelf() stopSelf()
} }
} }
} }

@ -84,7 +84,7 @@ class ArrangeBookActivity : VMBaseActivity<ActivityArrangeBookBinding, ArrangeBo
adapter.revertSelection() adapter.revertSelection()
} }
override fun onClickMainAction() { override fun onClickSelectBarMainAction() {
selectGroup(groupRequestCode, 0) selectGroup(groupRequestCode, 0)
} }

@ -1,52 +0,0 @@
package io.legado.app.ui.book.changecover
import android.os.Bundle
import androidx.recyclerview.widget.DiffUtil
import io.legado.app.data.entities.SearchBook
class DiffCallBack(private val oldItems: List<SearchBook>, private val newItems: List<SearchBook>) :
DiffUtil.Callback() {
override fun getOldListSize(): Int {
return oldItems.size
}
override fun getNewListSize(): Int {
return newItems.size
}
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
val oldItem = oldItems[oldItemPosition]
val newItem = newItems[newItemPosition]
return oldItem.bookUrl == newItem.bookUrl
}
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
val oldItem = oldItems[oldItemPosition]
val newItem = newItems[newItemPosition]
if (oldItem.originName != newItem.originName) {
return false
}
if (oldItem.coverUrl != newItem.coverUrl) {
return false
}
return true
}
override fun getChangePayload(oldItemPosition: Int, newItemPosition: Int): Any? {
val oldItem = oldItems[oldItemPosition]
val newItem = newItems[newItemPosition]
val payload = Bundle()
if (oldItem.originName != newItem.originName) {
payload.putString("name", newItem.originName)
}
if (oldItem.coverUrl != newItem.coverUrl) {
payload.putString("coverUrl", newItem.coverUrl)
}
if (payload.isEmpty) {
return null
}
return payload
}
}

@ -20,22 +20,20 @@ class ChangeSourceAdapter(
context: Context, context: Context,
val viewModel: ChangeSourceViewModel, val viewModel: ChangeSourceViewModel,
val callBack: CallBack val callBack: CallBack
) : ) : DiffRecyclerAdapter<SearchBook, ItemChangeSourceBinding>(context) {
DiffRecyclerAdapter<SearchBook, ItemChangeSourceBinding>(context) {
override val diffItemCallback: DiffUtil.ItemCallback<SearchBook> override val diffItemCallback = object : DiffUtil.ItemCallback<SearchBook>() {
get() = object : DiffUtil.ItemCallback<SearchBook>() { override fun areItemsTheSame(oldItem: SearchBook, newItem: SearchBook): Boolean {
override fun areItemsTheSame(oldItem: SearchBook, newItem: SearchBook): Boolean { return oldItem.bookUrl == newItem.bookUrl
return oldItem.bookUrl == newItem.bookUrl }
}
override fun areContentsTheSame(oldItem: SearchBook, newItem: SearchBook): Boolean {
return oldItem.originName == newItem.originName
&& oldItem.getDisplayLastChapterTitle() == newItem.getDisplayLastChapterTitle()
}
override fun areContentsTheSame(oldItem: SearchBook, newItem: SearchBook): Boolean {
return oldItem.originName == newItem.originName
&& oldItem.getDisplayLastChapterTitle() == newItem.getDisplayLastChapterTitle()
} }
}
override fun getViewBinding(parent: ViewGroup): ItemChangeSourceBinding { override fun getViewBinding(parent: ViewGroup): ItemChangeSourceBinding {
return ItemChangeSourceBinding.inflate(inflater, parent, false) return ItemChangeSourceBinding.inflate(inflater, parent, false)
} }

@ -116,7 +116,12 @@ class ChangeSourceViewModel(application: Application) : BaseViewModel(applicatio
if (searchGroup.isBlank()) { if (searchGroup.isBlank()) {
bookSourceList.addAll(appDb.bookSourceDao.allEnabled) bookSourceList.addAll(appDb.bookSourceDao.allEnabled)
} else { } else {
bookSourceList.addAll(appDb.bookSourceDao.getEnabledByGroup(searchGroup)) val sources = appDb.bookSourceDao.getEnabledByGroup(searchGroup)
if (sources.isEmpty()) {
bookSourceList.addAll(appDb.bookSourceDao.allEnabled)
} else {
bookSourceList.addAll(sources)
}
} }
searchStateData.postValue(true) searchStateData.postValue(true)
initSearchPool() initSearchPool()

@ -55,7 +55,7 @@ class GroupEditDialog() : BaseDialogFragment(R.layout.dialog_book_group_edit) {
} }
binding.run { binding.run {
ivCover.onClick { ivCover.onClick {
selectImage.launch(null) selectImage.launch()
} }
btnCancel.onClick { btnCancel.onClick {
dismiss() dismiss()

@ -58,7 +58,7 @@ class BookInfoEditActivity :
} }
} }
tvSelectCover.setOnClickListener { tvSelectCover.setOnClickListener {
selectCover.launch(null) selectCover.launch()
} }
tvRefreshCover.setOnClickListener { tvRefreshCover.setOnClickListener {
viewModel.book?.customCoverUrl = tieCoverUrl.text?.toString() viewModel.book?.customCoverUrl = tieCoverUrl.text?.toString()

@ -72,7 +72,7 @@ class ImportBookActivity : VMBaseActivity<ActivityImportBookBinding, ImportBookV
override fun onCompatOptionsItemSelected(item: MenuItem): Boolean { override fun onCompatOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) { when (item.itemId) {
R.id.menu_select_folder -> selectFolder.launch(null) R.id.menu_select_folder -> selectFolder.launch()
R.id.menu_scan_folder -> scanFolder() R.id.menu_scan_folder -> scanFolder()
R.id.menu_import_file_name -> alertImportFileName() R.id.menu_import_file_name -> alertImportFileName()
} }
@ -98,7 +98,7 @@ class ImportBookActivity : VMBaseActivity<ActivityImportBookBinding, ImportBookV
} }
@SuppressLint("NotifyDataSetChanged") @SuppressLint("NotifyDataSetChanged")
override fun onClickMainAction() { override fun onClickSelectBarMainAction() {
viewModel.addToBookshelf(adapter.selectedUris) { viewModel.addToBookshelf(adapter.selectedUris) {
adapter.notifyDataSetChanged() adapter.notifyDataSetChanged()
} }
@ -133,7 +133,7 @@ class ImportBookActivity : VMBaseActivity<ActivityImportBookBinding, ImportBookV
when { when {
lastPath.isNullOrEmpty() -> { lastPath.isNullOrEmpty() -> {
binding.tvEmptyMsg.visible() binding.tvEmptyMsg.visible()
selectFolder.launch(null) selectFolder.launch()
} }
lastPath.isContentScheme() -> { lastPath.isContentScheme() -> {
val rootUri = Uri.parse(lastPath) val rootUri = Uri.parse(lastPath)
@ -141,7 +141,7 @@ class ImportBookActivity : VMBaseActivity<ActivityImportBookBinding, ImportBookV
val doc = DocumentFile.fromTreeUri(this, rootUri) val doc = DocumentFile.fromTreeUri(this, rootUri)
if (doc == null || doc.name.isNullOrEmpty()) { if (doc == null || doc.name.isNullOrEmpty()) {
binding.tvEmptyMsg.visible() binding.tvEmptyMsg.visible()
selectFolder.launch(null) selectFolder.launch()
} else { } else {
subDocs.clear() subDocs.clear()
rootDoc = FileDoc.fromDocumentFile(doc) rootDoc = FileDoc.fromDocumentFile(doc)
@ -149,12 +149,12 @@ class ImportBookActivity : VMBaseActivity<ActivityImportBookBinding, ImportBookV
} }
}.onFailure { }.onFailure {
binding.tvEmptyMsg.visible() binding.tvEmptyMsg.visible()
selectFolder.launch(null) selectFolder.launch()
} }
} }
Build.VERSION.SDK_INT > Build.VERSION_CODES.Q -> { Build.VERSION.SDK_INT > Build.VERSION_CODES.Q -> {
binding.tvEmptyMsg.visible() binding.tvEmptyMsg.visible()
selectFolder.launch(null) selectFolder.launch()
} }
else -> initRootPath(lastPath) else -> initRootPath(lastPath)
} }
@ -172,7 +172,7 @@ class ImportBookActivity : VMBaseActivity<ActivityImportBookBinding, ImportBookV
upPath() upPath()
}.onFailure { }.onFailure {
binding.tvEmptyMsg.visible() binding.tvEmptyMsg.visible()
selectFolder.launch(null) selectFolder.launch()
} }
} }
.request() .request()

@ -0,0 +1,198 @@
package io.legado.app.ui.book.local.rule
import android.annotation.SuppressLint
import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
import androidx.activity.viewModels
import androidx.recyclerview.widget.ItemTouchHelper
import com.google.android.material.snackbar.Snackbar
import io.legado.app.R
import io.legado.app.base.VMBaseActivity
import io.legado.app.data.appDb
import io.legado.app.data.entities.TxtTocRule
import io.legado.app.databinding.ActivityTxtTocRuleBinding
import io.legado.app.databinding.DialogEditTextBinding
import io.legado.app.databinding.DialogTocRegexEditBinding
import io.legado.app.lib.dialogs.alert
import io.legado.app.lib.theme.primaryColor
import io.legado.app.ui.widget.SelectActionBar
import io.legado.app.ui.widget.recycler.DragSelectTouchHelper
import io.legado.app.ui.widget.recycler.ItemTouchCallback
import io.legado.app.ui.widget.recycler.VerticalDivider
import io.legado.app.utils.ACache
import io.legado.app.utils.setEdgeEffectColor
import io.legado.app.utils.snackbar
import io.legado.app.utils.splitNotBlank
import io.legado.app.utils.viewbindingdelegate.viewBinding
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
class TxtTocRuleActivity : VMBaseActivity<ActivityTxtTocRuleBinding, TxtTocRuleViewModel>(),
TxtTocRuleAdapter.CallBack,
SelectActionBar.CallBack {
override val viewModel: TxtTocRuleViewModel by viewModels()
override val binding: ActivityTxtTocRuleBinding by viewBinding(ActivityTxtTocRuleBinding::inflate)
private val adapter: TxtTocRuleAdapter by lazy {
TxtTocRuleAdapter(this, this)
}
private val importTocRuleKey = "tocRuleUrl"
override fun onActivityCreated(savedInstanceState: Bundle?) {
initView()
initBottomActionBar()
initData()
}
private fun initView() = binding.run {
recyclerView.setEdgeEffectColor(primaryColor)
recyclerView.addItemDecoration(VerticalDivider(this@TxtTocRuleActivity))
recyclerView.adapter = adapter
// When this page is opened, it is in selection mode
val dragSelectTouchHelper =
DragSelectTouchHelper(adapter.dragSelectCallback).setSlideArea(16, 50)
dragSelectTouchHelper.attachToRecyclerView(binding.recyclerView)
dragSelectTouchHelper.activeSlideSelect()
// Note: need judge selection first, so add ItemTouchHelper after it.
val itemTouchCallback = ItemTouchCallback(adapter)
itemTouchCallback.isCanDrag = true
ItemTouchHelper(itemTouchCallback).attachToRecyclerView(binding.recyclerView)
}
private fun initBottomActionBar() {
binding.selectActionBar.setMainActionText(R.string.delete)
binding.selectActionBar.setCallBack(this)
}
private fun initData() {
launch {
appDb.txtTocRuleDao.observeAll().collect { tocRules ->
adapter.setItems(tocRules)
upCountView()
}
}
}
override fun onCompatCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.txt_toc_regex, menu)
return super.onCompatCreateOptionsMenu(menu)
}
override fun onCompatOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.menu_add -> edit(TxtTocRule())
R.id.menu_default -> viewModel.importDefault()
R.id.menu_import -> showImportDialog()
}
return super.onCompatOptionsItemSelected(item)
}
override fun del(source: TxtTocRule) {
viewModel.del(source)
}
override fun edit(source: TxtTocRule) {
alert(titleResource = R.string.txt_toc_regex) {
val alertBinding = DialogTocRegexEditBinding.inflate(layoutInflater)
alertBinding.apply {
tvRuleName.setText(source.name)
tvRuleRegex.setText(source.rule)
}
customView { alertBinding.root }
okButton {
alertBinding.apply {
source.name = tvRuleName.text.toString()
source.rule = tvRuleRegex.text.toString()
viewModel.save(source)
}
}
cancelButton()
}
}
override fun onClickSelectBarMainAction() {
delSourceDialog()
}
override fun revertSelection() {
adapter.revertSelection()
}
override fun selectAll(selectAll: Boolean) {
if (selectAll) {
adapter.selectAll()
} else {
adapter.revertSelection()
}
}
override fun update(vararg source: TxtTocRule) {
viewModel.update(*source)
}
override fun toTop(source: TxtTocRule) {
viewModel.toTop(source)
}
override fun toBottom(source: TxtTocRule) {
viewModel.toBottom(source)
}
override fun upOrder() {
viewModel.upOrder()
}
override fun upCountView() {
binding.selectActionBar
.upCountView(adapter.selection.size, adapter.itemCount)
}
private fun delSourceDialog() {
alert(titleResource = R.string.draw, messageResource = R.string.sure_del) {
okButton { viewModel.del(*adapter.selection.toTypedArray()) }
noButton()
}
}
@SuppressLint("InflateParams")
private fun showImportDialog() {
val aCache = ACache.get(this, cacheDir = false)
val defaultUrl = "https://gitee.com/fisher52/YueDuJson/raw/master/myTxtChapterRule.json"
val cacheUrls: MutableList<String> = aCache
.getAsString(importTocRuleKey)
?.splitNotBlank(",")
?.toMutableList()
?: mutableListOf()
if (!cacheUrls.contains(defaultUrl)) {
cacheUrls.add(0, defaultUrl)
}
alert(titleResource = R.string.import_on_line) {
val alertBinding = DialogEditTextBinding.inflate(layoutInflater).apply {
editView.hint = "url"
editView.setFilterValues(cacheUrls)
editView.delCallBack = {
cacheUrls.remove(it)
aCache.put(importTocRuleKey, cacheUrls.joinToString(","))
}
}
customView { alertBinding.root }
okButton {
val text = alertBinding.editView.text?.toString()
text?.let {
if (!cacheUrls.contains(it)) {
cacheUrls.add(0, it)
aCache.put(importTocRuleKey, cacheUrls.joinToString(","))
}
Snackbar.make(binding.root, R.string.importing, Snackbar.LENGTH_INDEFINITE)
.show()
viewModel.importOnLine(it) { msg ->
binding.root.snackbar(msg)
}
}
}
cancelButton()
}
}
}

@ -0,0 +1,191 @@
package io.legado.app.ui.book.local.rule
import android.content.Context
import android.os.Bundle
import android.view.View
import android.view.ViewGroup
import android.widget.PopupMenu
import androidx.core.os.bundleOf
import androidx.recyclerview.widget.RecyclerView
import io.legado.app.R
import io.legado.app.base.adapter.ItemViewHolder
import io.legado.app.base.adapter.RecyclerAdapter
import io.legado.app.data.entities.TxtTocRule
import io.legado.app.databinding.ItemTxtTocRuleBinding
import io.legado.app.lib.theme.backgroundColor
import io.legado.app.ui.widget.recycler.DragSelectTouchHelper
import io.legado.app.ui.widget.recycler.ItemTouchCallback
import io.legado.app.utils.ColorUtils
class TxtTocRuleAdapter(context: Context, private val callBack: CallBack) :
RecyclerAdapter<TxtTocRule, ItemTxtTocRuleBinding>(context),
ItemTouchCallback.Callback {
private val selected = linkedSetOf<TxtTocRule>()
val selection: List<TxtTocRule>
get() {
val selection = arrayListOf<TxtTocRule>()
getItems().map {
if (selected.contains(it)) {
selection.add(it)
}
}
return selection.sortedBy { it.serialNumber }
}
override fun getViewBinding(parent: ViewGroup): ItemTxtTocRuleBinding {
return ItemTxtTocRuleBinding.inflate(inflater, parent, false)
}
override fun convert(
holder: ItemViewHolder,
binding: ItemTxtTocRuleBinding,
item: TxtTocRule,
payloads: MutableList<Any>
) {
binding.run {
val bundle = payloads.getOrNull(0) as? Bundle
if (bundle == null) {
root.setBackgroundColor(ColorUtils.withAlpha(context.backgroundColor, 0.5f))
cbSource.text = item.name
swtEnabled.isChecked = item.enable
cbSource.isChecked = selected.contains(item)
} else {
bundle.keySet().map {
when (it) {
"selected" -> cbSource.isChecked = selected.contains(item)
}
}
}
}
}
override fun registerListener(holder: ItemViewHolder, binding: ItemTxtTocRuleBinding) {
binding.cbSource.setOnCheckedChangeListener { buttonView, isChecked ->
getItem(holder.layoutPosition)?.let {
if (buttonView.isPressed) {
if (isChecked) {
selected.add(it)
} else {
selected.remove(it)
}
callBack.upCountView()
}
}
}
binding.swtEnabled.setOnCheckedChangeListener { buttonView, isChecked ->
getItem(holder.layoutPosition)?.let {
if (buttonView.isPressed) {
it.enable = isChecked
}
}
}
binding.ivEdit.setOnClickListener {
getItem(holder.layoutPosition)?.let {
callBack.edit(it)
}
}
binding.ivMenuMore.setOnClickListener {
showMenu(it, holder.layoutPosition)
}
}
private fun showMenu(view: View, position: Int) {
val source = getItem(position) ?: return
val popupMenu = PopupMenu(context, view)
popupMenu.inflate(R.menu.txt_toc_rule_item)
popupMenu.setOnMenuItemClickListener { menuItem ->
when (menuItem.itemId) {
R.id.menu_top -> callBack.toTop(source)
R.id.menu_bottom -> callBack.toBottom(source)
R.id.menu_del -> callBack.del(source)
}
true
}
popupMenu.show()
}
fun selectAll() {
getItems().forEach {
selected.add(it)
}
notifyItemRangeChanged(0, itemCount, bundleOf(Pair("selected", null)))
callBack.upCountView()
}
fun revertSelection() {
getItems().forEach {
if (selected.contains(it)) {
selected.remove(it)
} else {
selected.add(it)
}
}
notifyItemRangeChanged(0, itemCount, bundleOf(Pair("selected", null)))
callBack.upCountView()
}
override fun swap(srcPosition: Int, targetPosition: Int): Boolean {
val srcItem = getItem(srcPosition)
val targetItem = getItem(targetPosition)
if (srcItem != null && targetItem != null) {
if (srcItem.serialNumber == targetItem.serialNumber) {
callBack.upOrder()
} else {
val srcOrder = srcItem.serialNumber
srcItem.serialNumber = targetItem.serialNumber
targetItem.serialNumber = srcOrder
movedItems.add(srcItem)
movedItems.add(targetItem)
}
}
swapItem(srcPosition, targetPosition)
return true
}
private val movedItems = hashSetOf<TxtTocRule>()
override fun onClearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) {
if (movedItems.isNotEmpty()) {
callBack.update(*movedItems.toTypedArray())
movedItems.clear()
}
}
val dragSelectCallback: DragSelectTouchHelper.Callback =
object : DragSelectTouchHelper.AdvanceCallback<TxtTocRule>(Mode.ToggleAndReverse) {
override fun currentSelectedId(): MutableSet<TxtTocRule> {
return selected
}
override fun getItemId(position: Int): TxtTocRule {
return getItem(position)!!
}
override fun updateSelectState(position: Int, isSelected: Boolean): Boolean {
getItem(position)?.let {
if (isSelected) {
selected.add(it)
} else {
selected.remove(it)
}
notifyItemChanged(position, bundleOf(Pair("selected", null)))
callBack.upCountView()
return true
}
return false
}
}
interface CallBack {
fun del(source: TxtTocRule)
fun edit(source: TxtTocRule)
fun update(vararg source: TxtTocRule)
fun toTop(source: TxtTocRule)
fun toBottom(source: TxtTocRule)
fun upOrder()
fun upCountView()
}
}

@ -0,0 +1,86 @@
package io.legado.app.ui.book.local.rule
import android.app.Application
import io.legado.app.base.BaseViewModel
import io.legado.app.data.appDb
import io.legado.app.data.entities.TxtTocRule
import io.legado.app.help.DefaultData
import io.legado.app.help.http.newCallResponseBody
import io.legado.app.help.http.okHttpClient
import io.legado.app.help.http.text
import io.legado.app.utils.GSON
import io.legado.app.utils.fromJsonArray
class TxtTocRuleViewModel(app: Application) : BaseViewModel(app) {
fun save(txtTocRule: TxtTocRule) {
execute {
appDb.txtTocRuleDao.insert(txtTocRule)
}
}
fun del(vararg txtTocRule: TxtTocRule) {
execute {
appDb.txtTocRuleDao.delete(*txtTocRule)
}
}
fun update(vararg txtTocRule: TxtTocRule) {
execute {
appDb.txtTocRuleDao.update(*txtTocRule)
}
}
fun importDefault() {
execute {
DefaultData.importDefaultTocRules()
}
}
fun importOnLine(url: String, finally: (msg: String) -> Unit) {
execute {
okHttpClient.newCallResponseBody {
url(url)
}.text("utf-8").let { json ->
GSON.fromJsonArray<TxtTocRule>(json)?.let {
appDb.txtTocRuleDao.insert(*it.toTypedArray())
}
}
}.onSuccess {
finally("导入成功")
}.onError {
finally("导入失败")
}
}
fun toTop(vararg rules: TxtTocRule) {
execute {
val minOrder = appDb.txtTocRuleDao.minOrder - 1
rules.forEachIndexed { index, source ->
source.serialNumber = minOrder - index
}
appDb.txtTocRuleDao.update(*rules)
}
}
fun toBottom(vararg sources: TxtTocRule) {
execute {
val maxOrder = appDb.txtTocRuleDao.maxOrder + 1
sources.forEachIndexed { index, source ->
source.serialNumber = maxOrder + index
}
appDb.txtTocRuleDao.update(*sources)
}
}
fun upOrder() {
execute {
val sources = appDb.txtTocRuleDao.all
for ((index: Int, source: TxtTocRule) in sources.withIndex()) {
source.serialNumber = index + 1
}
appDb.txtTocRuleDao.update(*sources.toTypedArray())
}
}
}

@ -21,6 +21,7 @@ import io.legado.app.data.entities.Book
import io.legado.app.data.entities.BookChapter import io.legado.app.data.entities.BookChapter
import io.legado.app.data.entities.BookProgress import io.legado.app.data.entities.BookProgress
import io.legado.app.data.entities.BookSource import io.legado.app.data.entities.BookSource
import io.legado.app.help.BookHelp
import io.legado.app.help.IntentData import io.legado.app.help.IntentData
import io.legado.app.help.ReadBookConfig import io.legado.app.help.ReadBookConfig
import io.legado.app.help.ReadTipConfig import io.legado.app.help.ReadTipConfig
@ -266,6 +267,9 @@ class ReadBookActivity : BaseReadBookActivity(),
TextDialog(ReadBook.curTextChapter?.getContent()) TextDialog(ReadBook.curTextChapter?.getContent())
) )
R.id.menu_update_toc -> ReadBook.book?.let { R.id.menu_update_toc -> ReadBook.book?.let {
if (it.isEpub()) {
BookHelp.clearCache(it)
}
loadChapterList(it) loadChapterList(it)
} }
R.id.menu_enable_replace -> ReadBook.book?.let { R.id.menu_enable_replace -> ReadBook.book?.let {
@ -416,6 +420,12 @@ class ReadBookActivity : BaseReadBookActivity(),
autoPageStop() autoPageStop()
return true return true
} }
if (getPrefBoolean("disableReturnKey")) {
if (menuLayoutIsVisible) {
finish()
}
return true
}
} }
} }
} }

@ -69,16 +69,8 @@ class ReadBookViewModel(application: Application) : BaseViewModel(application) {
} }
syncBookProgress(book) syncBookProgress(book)
} else { } else {
ReadBook.book = book ReadBook.upData(book)
if (ReadBook.durChapterIndex != book.durChapterIndex) {
ReadBook.durChapterIndex = book.durChapterIndex
ReadBook.durChapterPos = book.durChapterPos
ReadBook.clearTextChapter()
}
ReadBook.callBack?.upMenuView()
ReadBook.upWebBook(book)
isInitFinish = true isInitFinish = true
ReadBook.chapterSize = appDb.bookChapterDao.getChapterCount(book.bookUrl)
if (ReadBook.chapterSize == 0) { if (ReadBook.chapterSize == 0) {
if (book.tocUrl.isEmpty()) { if (book.tocUrl.isEmpty()) {
loadBookInfo(book) loadBookInfo(book)

@ -117,7 +117,7 @@ class BgTextConfigDialog : BaseDialogFragment(R.layout.dialog_read_bg_text) {
ivBg.setImageResource(R.drawable.ic_image) ivBg.setImageResource(R.drawable.ic_image)
ivBg.setColorFilter(primaryTextColor) ivBg.setColorFilter(primaryTextColor)
root.setOnClickListener { root.setOnClickListener {
selectBgImage.launch(null) selectBgImage.launch()
} }
} }
} }

@ -16,11 +16,11 @@ class HttpTtsEditViewModel(app: Application) : BaseViewModel(app) {
fun initData(arguments: Bundle?, success: (httpTTS: HttpTTS) -> Unit) { fun initData(arguments: Bundle?, success: (httpTTS: HttpTTS) -> Unit) {
execute { execute {
if (id == null) { if (id == null) {
id = arguments?.getLong("id") val argumentId = arguments?.getLong("id")
val httpTTS = id?.let { if (argumentId != null && argumentId != 0L) {
return@let appDb.httpTTSDao.get(it) id = argumentId
return@execute appDb.httpTTSDao.get(argumentId)
} }
return@execute httpTTS
} }
return@execute null return@execute null
}.onSuccess { }.onSuccess {

@ -17,7 +17,6 @@ import io.legado.app.lib.theme.bottomBackground
import io.legado.app.lib.theme.primaryColor import io.legado.app.lib.theme.primaryColor
import io.legado.app.model.ReadBook import io.legado.app.model.ReadBook
import io.legado.app.ui.book.read.ReadBookActivity import io.legado.app.ui.book.read.ReadBookActivity
import io.legado.app.ui.book.read.page.ReadView
import io.legado.app.ui.book.read.page.provider.ChapterProvider import io.legado.app.ui.book.read.page.provider.ChapterProvider
import io.legado.app.utils.dp import io.legado.app.utils.dp
import io.legado.app.utils.getPrefBoolean import io.legado.app.utils.getPrefBoolean
@ -138,9 +137,6 @@ class MoreConfigDialog : DialogFragment() {
"clickRegionalConfig" -> { "clickRegionalConfig" -> {
(activity as? ReadBookActivity)?.showClickRegionalConfig() (activity as? ReadBookActivity)?.showClickRegionalConfig()
} }
"fullScreenGesturesSupport" -> {
((activity as? ReadBookActivity)?.findViewById(R.id.read_view) as ReadView).setRect9x()
}
} }
return super.onPreferenceTreeClick(preference) return super.onPreferenceTreeClick(preference)
} }

@ -64,7 +64,9 @@ class ReadAloudDialog : BaseDialogFragment(R.layout.dialog_read_aloud) {
ivStop.setColorFilter(textColor) ivStop.setColorFilter(textColor)
ivTimer.setColorFilter(textColor) ivTimer.setColorFilter(textColor)
tvTimer.setTextColor(textColor) tvTimer.setTextColor(textColor)
ivTtsSpeechReduce.setColorFilter(textColor)
tvTtsSpeed.setTextColor(textColor) tvTtsSpeed.setTextColor(textColor)
ivTtsSpeechAdd.setColorFilter(textColor)
ivCatalog.setColorFilter(textColor) ivCatalog.setColorFilter(textColor)
tvCatalog.setTextColor(textColor) tvCatalog.setTextColor(textColor)
ivMainMenu.setColorFilter(textColor) ivMainMenu.setColorFilter(textColor)
@ -112,6 +114,16 @@ class ReadAloudDialog : BaseDialogFragment(R.layout.dialog_read_aloud) {
seekTtsSpeechRate.isEnabled = !isChecked seekTtsSpeechRate.isEnabled = !isChecked
upTtsSpeechRate() upTtsSpeechRate()
} }
ivTtsSpeechReduce.setOnClickListener {
seekTtsSpeechRate.progress = AppConfig.ttsSpeechRate - 1
AppConfig.ttsSpeechRate = AppConfig.ttsSpeechRate - 1
upTtsSpeechRate()
}
ivTtsSpeechAdd.setOnClickListener {
seekTtsSpeechRate.progress = AppConfig.ttsSpeechRate + 1
AppConfig.ttsSpeechRate = AppConfig.ttsSpeechRate + 1
upTtsSpeechRate()
}
//设置保存的默认值 //设置保存的默认值
seekTtsSpeechRate.progress = AppConfig.ttsSpeechRate seekTtsSpeechRate.progress = AppConfig.ttsSpeechRate
seekTtsSpeechRate.setOnSeekBarChangeListener(object : SeekBarChangeListener { seekTtsSpeechRate.setOnSeekBarChangeListener(object : SeekBarChangeListener {

@ -16,7 +16,7 @@ class TocRegexViewModel(application: Application) : BaseViewModel(application) {
fun saveRule(rule: TxtTocRule) { fun saveRule(rule: TxtTocRule) {
execute { execute {
if (rule.serialNumber < 0) { if (rule.serialNumber < 0) {
rule.serialNumber = appDb.txtTocRuleDao.lastOrderNum + 1 rule.serialNumber = appDb.txtTocRuleDao.maxOrder + 1
} }
appDb.txtTocRuleDao.insert(rule) appDb.txtTocRuleDao.insert(rule)
} }

@ -317,7 +317,7 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
} }
val textPage = relativePage(relativePos) val textPage = relativePage(relativePos)
for ((lineIndex, textLine) in textPage.textLines.withIndex()) { for ((lineIndex, textLine) in textPage.textLines.withIndex()) {
if (textLine.isTouch(y, relativeOffset)) { if (textLine.isTouch(x, y, relativeOffset)) {
for ((charIndex, textChar) in textLine.textChars.withIndex()) { for ((charIndex, textChar) in textLine.textChars.withIndex()) {
if (textChar.isTouch(x)) { if (textChar.isTouch(x)) {
touched.invoke( touched.invoke(

@ -102,20 +102,18 @@ class ReadView(context: Context, attrs: AttributeSet) :
setWillNotDraw(false) setWillNotDraw(false)
upPageAnim() upPageAnim()
} }
setRect9x()
} }
fun setRect9x() { fun setRect9x() {
val edge = if (AppConfig.fullScreenGesturesSupport) 200f else 0f tlRect.set(0f, 0f, width * 0.33f, height * 0.33f)
tlRect.set(0f + edge, 0f, width * 0.33f, height * 0.33f)
tcRect.set(width * 0.33f, 0f, width * 0.66f, height * 0.33f) tcRect.set(width * 0.33f, 0f, width * 0.66f, height * 0.33f)
trRect.set(width * 0.36f, 0f, width - 0f - edge, height * 0.33f) trRect.set(width * 0.36f, 0f, width.toFloat(), height * 0.33f)
mlRect.set(0f + edge, height * 0.33f, width * 0.33f, height * 0.66f) mlRect.set(0f, height * 0.33f, width * 0.33f, height * 0.66f)
mcRect.set(width * 0.33f, height * 0.33f, width * 0.66f, height * 0.66f) mcRect.set(width * 0.33f, height * 0.33f, width * 0.66f, height * 0.66f)
mrRect.set(width * 0.66f, height * 0.33f, width - 0f - edge, height * 0.66f) mrRect.set(width * 0.66f, height * 0.33f, width.toFloat(), height * 0.66f)
blRect.set(0f + edge, height * 0.66f, width * 0.33f, height - 10f - edge) blRect.set(0f, height * 0.66f, width * 0.33f, height.toFloat())
bcRect.set(width * 0.33f, height * 0.66f, width * 0.66f, height - 0f - edge) bcRect.set(width * 0.33f, height * 0.66f, width * 0.66f, height.toFloat())
brRect.set(width * 0.66f, height * 0.66f, width - 0f - edge, height - 0f - edge) brRect.set(width * 0.66f, height * 0.66f, width.toFloat(), height.toFloat())
} }
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
@ -203,7 +201,7 @@ class ReadView(context: Context, attrs: AttributeSet) :
} }
} }
} }
MotionEvent.ACTION_CANCEL, MotionEvent.ACTION_UP -> { MotionEvent.ACTION_UP -> {
removeCallbacks(longPressRunnable) removeCallbacks(longPressRunnable)
if (!pressDown) return true if (!pressDown) return true
pressDown = false pressDown = false
@ -220,6 +218,17 @@ class ReadView(context: Context, attrs: AttributeSet) :
} }
pressOnTextSelected = false pressOnTextSelected = false
} }
MotionEvent.ACTION_CANCEL -> {
removeCallbacks(longPressRunnable)
if (!pressDown) return true
pressDown = false
if (isTextSelected) {
callBack.showTextActionMenu()
} else if (isMove) {
pageDelegate?.onTouch(event)
}
pressOnTextSelected = false
}
} }
return true return true
} }

@ -17,6 +17,8 @@ data class TextLine(
) { ) {
val charSize: Int get() = textChars.size val charSize: Int get() = textChars.size
val lineStart: Float get() = textChars.firstOrNull()?.start ?: 0f
val lineEnd: Float get() = textChars.lastOrNull()?.end ?: 0f
fun upTopBottom(durY: Float, textPaint: TextPaint) { fun upTopBottom(durY: Float, textPaint: TextPaint) {
lineTop = ChapterProvider.paddingTop + durY lineTop = ChapterProvider.paddingTop + durY
@ -38,7 +40,10 @@ data class TextLine(
return textChars.size return textChars.size
} }
fun isTouch(y: Float, relativeOffset: Float): Boolean { fun isTouch(x: Float, y: Float, relativeOffset: Float): Boolean {
return y > lineTop + relativeOffset && y < lineBottom + relativeOffset return y > lineTop + relativeOffset
&& y < lineBottom + relativeOffset
&& x >= lineStart
&& x <= lineEnd
} }
} }

@ -591,7 +591,7 @@ object ChapterProvider {
* 更新绘制尺寸 * 更新绘制尺寸
*/ */
fun upLayout() { fun upLayout() {
doublePage = viewWidth > viewHeight doublePage = (viewWidth > viewHeight || appCtx.isPad)
&& ReadBook.pageAnim() != 3 && ReadBook.pageAnim() != 3
&& AppConfig.doublePageHorizontal && AppConfig.doublePageHorizontal
if (viewWidth > 0 && viewHeight > 0) { if (viewWidth > 0 && viewHeight > 0) {

@ -14,6 +14,7 @@ import io.legado.app.lib.theme.accentColor
import io.legado.app.lib.theme.primaryColor import io.legado.app.lib.theme.primaryColor
import io.legado.app.ui.qrcode.QrCodeResult import io.legado.app.ui.qrcode.QrCodeResult
import io.legado.app.ui.widget.dialog.TextDialog import io.legado.app.ui.widget.dialog.TextDialog
import io.legado.app.utils.launch
import io.legado.app.utils.setEdgeEffectColor import io.legado.app.utils.setEdgeEffectColor
import io.legado.app.utils.showDialogFragment import io.legado.app.utils.showDialogFragment
import io.legado.app.utils.toastOnUi import io.legado.app.utils.toastOnUi
@ -137,7 +138,7 @@ class BookSourceDebugActivity : VMBaseActivity<ActivitySourceDebugBinding, BookS
override fun onCompatOptionsItemSelected(item: MenuItem): Boolean { override fun onCompatOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) { when (item.itemId) {
R.id.menu_scan -> qrCodeResult.launch(null) R.id.menu_scan -> qrCodeResult.launch()
R.id.menu_search_src -> showDialogFragment(TextDialog(viewModel.searchSrc)) R.id.menu_search_src -> showDialogFragment(TextDialog(viewModel.searchSrc))
R.id.menu_book_src -> showDialogFragment(TextDialog(viewModel.bookSrc)) R.id.menu_book_src -> showDialogFragment(TextDialog(viewModel.bookSrc))
R.id.menu_toc_src -> showDialogFragment(TextDialog(viewModel.tocSrc)) R.id.menu_toc_src -> showDialogFragment(TextDialog(viewModel.tocSrc))

@ -112,7 +112,7 @@ class BookSourceEditActivity :
} }
R.id.menu_copy_source -> sendToClip(GSON.toJson(getSource())) R.id.menu_copy_source -> sendToClip(GSON.toJson(getSource()))
R.id.menu_paste_source -> viewModel.pasteSource { upRecyclerView(it) } R.id.menu_paste_source -> viewModel.pasteSource { upRecyclerView(it) }
R.id.menu_qr_code_camera -> qrCodeResult.launch(null) R.id.menu_qr_code_camera -> qrCodeResult.launch()
R.id.menu_share_str -> share(GSON.toJson(getSource())) R.id.menu_share_str -> share(GSON.toJson(getSource()))
R.id.menu_share_qr -> shareWithQr( R.id.menu_share_qr -> shareWithQr(
GSON.toJson(getSource()), GSON.toJson(getSource()),

@ -1,7 +1,6 @@
package io.legado.app.ui.book.source.manage package io.legado.app.ui.book.source.manage
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
@ -28,6 +27,7 @@ import io.legado.app.lib.theme.primaryTextColor
import io.legado.app.model.CheckSource import io.legado.app.model.CheckSource
import io.legado.app.model.Debug import io.legado.app.model.Debug
import io.legado.app.ui.association.ImportBookSourceDialog import io.legado.app.ui.association.ImportBookSourceDialog
import io.legado.app.ui.book.local.rule.TxtTocRuleActivity
import io.legado.app.ui.book.source.debug.BookSourceDebugActivity import io.legado.app.ui.book.source.debug.BookSourceDebugActivity
import io.legado.app.ui.book.source.edit.BookSourceEditActivity import io.legado.app.ui.book.source.edit.BookSourceEditActivity
import io.legado.app.ui.document.HandleFileContract import io.legado.app.ui.document.HandleFileContract
@ -120,16 +120,14 @@ class BookSourceActivity : VMBaseActivity<ActivityBookSourceBinding, BookSourceV
override fun onCompatOptionsItemSelected(item: MenuItem): Boolean { override fun onCompatOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) { when (item.itemId) {
R.id.menu_add_book_source -> startActivity<BookSourceEditActivity>() R.id.menu_add_book_source -> startActivity<BookSourceEditActivity>()
R.id.menu_import_qr -> qrResult.launch(null) R.id.menu_import_qr -> qrResult.launch()
R.id.menu_share_source -> viewModel.shareSelection(adapter.selection) {
startActivity(Intent.createChooser(it, getString(R.string.share_selected_source)))
}
R.id.menu_group_manage -> showDialogFragment<GroupManageDialog>() R.id.menu_group_manage -> showDialogFragment<GroupManageDialog>()
R.id.menu_import_local -> importDoc.launch { R.id.menu_import_local -> importDoc.launch {
mode = HandleFileContract.FILE mode = HandleFileContract.FILE
allowExtensions = arrayOf("txt", "json") allowExtensions = arrayOf("txt", "json")
} }
R.id.menu_import_onLine -> showImportDialog() R.id.menu_import_onLine -> showImportDialog()
R.id.menu_text_toc_rule -> startActivity<TxtTocRuleActivity>()
R.id.menu_sort_manual -> { R.id.menu_sort_manual -> {
item.isChecked = true item.isChecked = true
sortCheck(Sort.Default) sortCheck(Sort.Default)
@ -308,9 +306,9 @@ class BookSourceActivity : VMBaseActivity<ActivityBookSourceBinding, BookSourceV
adapter.revertSelection() adapter.revertSelection()
} }
override fun onClickMainAction() { override fun onClickSelectBarMainAction() {
alert(titleResource = R.string.draw, messageResource = R.string.sure_del) { alert(titleResource = R.string.draw, messageResource = R.string.sure_del) {
okButton { viewModel.delSelection(adapter.selection) } okButton { viewModel.del(*adapter.selection.toTypedArray()) }
noButton() noButton()
} }
} }
@ -333,13 +331,14 @@ class BookSourceActivity : VMBaseActivity<ActivityBookSourceBinding, BookSourceV
R.id.menu_bottom_sel -> viewModel.bottomSource(*adapter.selection.toTypedArray()) R.id.menu_bottom_sel -> viewModel.bottomSource(*adapter.selection.toTypedArray())
R.id.menu_add_group -> selectionAddToGroups() R.id.menu_add_group -> selectionAddToGroups()
R.id.menu_remove_group -> selectionRemoveFromGroups() R.id.menu_remove_group -> selectionRemoveFromGroups()
R.id.menu_export_selection -> exportDir.launch { R.id.menu_export_selection -> viewModel.saveToFile(adapter.selection) { file ->
mode = HandleFileContract.EXPORT exportDir.launch {
fileData = Triple( mode = HandleFileContract.EXPORT
"bookSource.json", fileData = Triple("bookSource.json", file, "application/json")
GSON.toJson(adapter.selection).toByteArray(), }
"application/json" }
) R.id.menu_share_source -> viewModel.saveToFile(adapter.selection) {
share(it)
} }
} }
return true return true

@ -19,7 +19,7 @@ import io.legado.app.lib.theme.backgroundColor
import io.legado.app.model.Debug import io.legado.app.model.Debug
import io.legado.app.ui.login.SourceLoginActivity import io.legado.app.ui.login.SourceLoginActivity
import io.legado.app.ui.widget.recycler.DragSelectTouchHelper import io.legado.app.ui.widget.recycler.DragSelectTouchHelper
import io.legado.app.ui.widget.recycler.ItemTouchCallback.Callback import io.legado.app.ui.widget.recycler.ItemTouchCallback
import io.legado.app.utils.ColorUtils import io.legado.app.utils.ColorUtils
import io.legado.app.utils.invisible import io.legado.app.utils.invisible
import io.legado.app.utils.startActivity import io.legado.app.utils.startActivity
@ -28,7 +28,7 @@ import io.legado.app.utils.visible
class BookSourceAdapter(context: Context, val callBack: CallBack) : class BookSourceAdapter(context: Context, val callBack: CallBack) :
RecyclerAdapter<BookSource, ItemBookSourceBinding>(context), RecyclerAdapter<BookSource, ItemBookSourceBinding>(context),
Callback { ItemTouchCallback.Callback {
private val selected = linkedSetOf<BookSource>() private val selected = linkedSetOf<BookSource>()
@ -110,26 +110,22 @@ class BookSourceAdapter(context: Context, val callBack: CallBack) :
override fun registerListener(holder: ItemViewHolder, binding: ItemBookSourceBinding) { override fun registerListener(holder: ItemViewHolder, binding: ItemBookSourceBinding) {
binding.apply { binding.apply {
swtEnabled.setOnCheckedChangeListener { view, checked -> swtEnabled.setOnCheckedChangeListener { view, checked ->
if (view.isPressed) { getItem(holder.layoutPosition)?.let {
getItem(holder.layoutPosition)?.let { if (view.isPressed) {
if (view.isPressed) { it.enabled = checked
it.enabled = checked callBack.update(it)
callBack.update(it)
}
} }
} }
} }
cbBookSource.setOnCheckedChangeListener { view, checked -> cbBookSource.setOnCheckedChangeListener { view, checked ->
if (view.isPressed) { getItem(holder.layoutPosition)?.let {
getItem(holder.layoutPosition)?.let { if (view.isPressed) {
if (view.isPressed) { if (checked) {
if (checked) { selected.add(it)
selected.add(it) } else {
} else { selected.remove(it)
selected.remove(it)
}
callBack.upCountView()
} }
callBack.upCountView()
} }
} }
} }

@ -1,15 +1,14 @@
package io.legado.app.ui.book.source.manage package io.legado.app.ui.book.source.manage
import android.app.Application import android.app.Application
import android.content.Intent
import android.text.TextUtils import android.text.TextUtils
import androidx.core.content.FileProvider
import io.legado.app.base.BaseViewModel import io.legado.app.base.BaseViewModel
import io.legado.app.constant.AppConst
import io.legado.app.constant.AppPattern import io.legado.app.constant.AppPattern
import io.legado.app.data.appDb import io.legado.app.data.appDb
import io.legado.app.data.entities.BookSource import io.legado.app.data.entities.BookSource
import io.legado.app.utils.* import io.legado.app.utils.*
import java.io.File
import java.io.FileOutputStream
class BookSourceViewModel(application: Application) : BaseViewModel(application) { class BookSourceViewModel(application: Application) : BaseViewModel(application) {
@ -33,8 +32,8 @@ class BookSourceViewModel(application: Application) : BaseViewModel(application)
} }
} }
fun del(bookSource: BookSource) { fun del(vararg sources: BookSource) {
execute { appDb.bookSourceDao.delete(bookSource) } execute { appDb.bookSourceDao.delete(*sources) }
} }
fun update(vararg bookSource: BookSource) { fun update(vararg bookSource: BookSource) {
@ -129,25 +128,16 @@ class BookSourceViewModel(application: Application) : BaseViewModel(application)
} }
} }
fun delSelection(sources: List<BookSource>) { @Suppress("BlockingMethodInNonBlockingContext")
fun saveToFile(sources: List<BookSource>, success: (file: File) -> Unit) {
execute { execute {
appDb.bookSourceDao.delete(*sources.toTypedArray()) val path = "${context.filesDir}/shareBookSource.json"
} FileUtils.delete(path)
} val file = FileUtils.createFileWithReplace(path)
FileOutputStream(file).use {
fun shareSelection(sources: List<BookSource>, success: ((intent: Intent) -> Unit)) { GSON.writeToOutputStream(it, sources)
execute { }
val tmpSharePath = "${context.filesDir}/shareBookSource.json" file
FileUtils.delete(tmpSharePath)
val intent = Intent(Intent.ACTION_SEND)
val file = FileUtils.createFileWithReplace(tmpSharePath)
file.writeText(GSON.toJson(sources))
val fileUri = FileProvider.getUriForFile(context, AppConst.authority, file)
intent.type = "text/*"
intent.putExtra(Intent.EXTRA_STREAM, fileUri)
intent.flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
intent
}.onSuccess { }.onSuccess {
success.invoke(it) success.invoke(it)
}.onError { }.onError {

@ -2,6 +2,7 @@ package io.legado.app.ui.book.toc
import android.content.Context import android.content.Context
import android.view.ViewGroup import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import io.legado.app.R import io.legado.app.R
import io.legado.app.base.adapter.ItemViewHolder import io.legado.app.base.adapter.ItemViewHolder
import io.legado.app.base.adapter.RecyclerAdapter import io.legado.app.base.adapter.RecyclerAdapter
@ -16,6 +17,22 @@ class ChapterListAdapter(context: Context, val callback: Callback) :
RecyclerAdapter<BookChapter, ItemChapterListBinding>(context) { RecyclerAdapter<BookChapter, ItemChapterListBinding>(context) {
val cacheFileNames = hashSetOf<String>() val cacheFileNames = hashSetOf<String>()
val diffCallBack = object : DiffUtil.ItemCallback<BookChapter>() {
override fun areItemsTheSame(oldItem: BookChapter, newItem: BookChapter): Boolean {
return oldItem.index == newItem.index
}
override fun areContentsTheSame(oldItem: BookChapter, newItem: BookChapter): Boolean {
return oldItem.bookUrl == newItem.bookUrl
&& oldItem.url == newItem.url
&& oldItem.isVip == newItem.isVip
&& oldItem.isPay == newItem.isPay
&& oldItem.title == newItem.title
&& oldItem.tag == newItem.tag
}
}
override fun getViewBinding(parent: ViewGroup): ItemChapterListBinding { override fun getViewBinding(parent: ViewGroup): ItemChapterListBinding {
return ItemChapterListBinding.inflate(inflater, parent, false) return ItemChapterListBinding.inflate(inflater, parent, false)

@ -27,18 +27,16 @@ import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import kotlin.math.min
class ChapterListFragment : VMBaseFragment<TocViewModel>(R.layout.fragment_chapter_list), class ChapterListFragment : VMBaseFragment<TocViewModel>(R.layout.fragment_chapter_list),
ChapterListAdapter.Callback, ChapterListAdapter.Callback,
TocViewModel.ChapterListCallBack { TocViewModel.ChapterListCallBack {
override val viewModel by activityViewModels<TocViewModel>() override val viewModel by activityViewModels<TocViewModel>()
private val binding by viewBinding(FragmentChapterListBinding::bind) private val binding by viewBinding(FragmentChapterListBinding::bind)
private val mLayoutManager by lazy { UpLinearLayoutManager(requireContext()) }
private val adapter by lazy { ChapterListAdapter(requireContext(), this) } private val adapter by lazy { ChapterListAdapter(requireContext(), this) }
private var durChapterIndex = 0 private var durChapterIndex = 0
private lateinit var mLayoutManager: UpLinearLayoutManager
private var tocFlowJob: Job? = null private var tocFlowJob: Job? = null
private var scrollToDurChapter = false
override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) = binding.run { override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) = binding.run {
viewModel.chapterCallBack = this@ChapterListFragment viewModel.chapterCallBack = this@ChapterListFragment
@ -56,14 +54,15 @@ class ChapterListFragment : VMBaseFragment<TocViewModel>(R.layout.fragment_chapt
} }
private fun initRecyclerView() { private fun initRecyclerView() {
mLayoutManager = UpLinearLayoutManager(requireContext())
binding.recyclerView.layoutManager = mLayoutManager binding.recyclerView.layoutManager = mLayoutManager
binding.recyclerView.addItemDecoration(VerticalDivider(requireContext())) binding.recyclerView.addItemDecoration(VerticalDivider(requireContext()))
binding.recyclerView.adapter = adapter binding.recyclerView.adapter = adapter
} }
private fun initView() = binding.run { private fun initView() = binding.run {
ivChapterTop.setOnClickListener { mLayoutManager.scrollToPositionWithOffset(0, 0) } ivChapterTop.setOnClickListener {
mLayoutManager.scrollToPositionWithOffset(0, 0)
}
ivChapterBottom.setOnClickListener { ivChapterBottom.setOnClickListener {
if (adapter.itemCount > 0) { if (adapter.itemCount > 0) {
mLayoutManager.scrollToPositionWithOffset(adapter.itemCount - 1, 0) mLayoutManager.scrollToPositionWithOffset(adapter.itemCount - 1, 0)
@ -112,10 +111,11 @@ class ChapterListFragment : VMBaseFragment<TocViewModel>(R.layout.fragment_chapt
searchKey.isNullOrBlank() -> appDb.bookChapterDao.flowByBook(viewModel.bookUrl) searchKey.isNullOrBlank() -> appDb.bookChapterDao.flowByBook(viewModel.bookUrl)
else -> appDb.bookChapterDao.flowSearch(viewModel.bookUrl, searchKey) else -> appDb.bookChapterDao.flowSearch(viewModel.bookUrl, searchKey)
}.collect { }.collect {
adapter.setItems(it) if (!(searchKey.isNullOrBlank() && it.isEmpty())) {
if (searchKey.isNullOrBlank() && !scrollToDurChapter) { adapter.setItems(it, adapter.diffCallBack)
mLayoutManager.scrollToPositionWithOffset(durChapterIndex, 0) if (searchKey.isNullOrBlank() && mLayoutManager.findFirstVisibleItemPosition() < 0) {
scrollToDurChapter = true mLayoutManager.scrollToPositionWithOffset(durChapterIndex, 0)
}
} }
} }
} }
@ -125,7 +125,7 @@ class ChapterListFragment : VMBaseFragment<TocViewModel>(R.layout.fragment_chapt
get() = viewModel.bookData.value?.isLocalBook() == true get() = viewModel.bookData.value?.isLocalBook() == true
override fun durChapterIndex(): Int { override fun durChapterIndex(): Int {
return min(durChapterIndex, adapter.itemCount) return durChapterIndex
} }
override fun openChapter(bookChapter: BookChapter) { override fun openChapter(bookChapter: BookChapter) {

@ -17,8 +17,10 @@ import io.legado.app.base.VMBaseActivity
import io.legado.app.databinding.ActivityChapterListBinding import io.legado.app.databinding.ActivityChapterListBinding
import io.legado.app.lib.theme.accentColor import io.legado.app.lib.theme.accentColor
import io.legado.app.lib.theme.primaryTextColor import io.legado.app.lib.theme.primaryTextColor
import io.legado.app.ui.about.AppLogDialog
import io.legado.app.utils.applyTint import io.legado.app.utils.applyTint
import io.legado.app.utils.gone import io.legado.app.utils.gone
import io.legado.app.utils.showDialogFragment
import io.legado.app.utils.viewbindingdelegate.viewBinding import io.legado.app.utils.viewbindingdelegate.viewBinding
import io.legado.app.utils.visible import io.legado.app.utils.visible
@ -80,6 +82,7 @@ class TocActivity : VMBaseActivity<ActivityChapterListBinding, TocViewModel>() {
putExtra("chapterPos", it.durChapterPos) putExtra("chapterPos", it.durChapterPos)
}) })
} }
R.id.menu_log -> showDialogFragment<AppLogDialog>()
} }
return super.onCompatOptionsItemSelected(item) return super.onCompatOptionsItemSelected(item)
} }

@ -7,7 +7,7 @@ import androidx.activity.result.contract.ActivityResultContract
class TocActivityResult : ActivityResultContract<String, Pair<Int, Int>?>() { class TocActivityResult : ActivityResultContract<String, Pair<Int, Int>?>() {
override fun createIntent(context: Context, input: String?): Intent { override fun createIntent(context: Context, input: String): Intent {
return Intent(context, TocActivity::class.java) return Intent(context, TocActivity::class.java)
.putExtra("bookUrl", input) .putExtra("bookUrl", input)
} }

@ -15,6 +15,7 @@ import androidx.preference.ListPreference
import androidx.preference.Preference import androidx.preference.Preference
import io.legado.app.R import io.legado.app.R
import io.legado.app.base.BasePreferenceFragment import io.legado.app.base.BasePreferenceFragment
import io.legado.app.constant.AppLog
import io.legado.app.constant.PreferKey import io.legado.app.constant.PreferKey
import io.legado.app.help.AppConfig import io.legado.app.help.AppConfig
import io.legado.app.help.LocalConfig import io.legado.app.help.LocalConfig
@ -47,8 +48,8 @@ class BackupConfigFragment : BasePreferenceFragment(),
} }
} }
} }
private val backupDir = registerForActivityResult(HandleFileContract()) { private val backupDir = registerForActivityResult(HandleFileContract()) { result ->
it.uri?.let { uri -> result.uri?.let { uri ->
if (uri.isContentScheme()) { if (uri.isContentScheme()) {
AppConfig.backupPath = uri.toString() AppConfig.backupPath = uri.toString()
Coroutine.async { Coroutine.async {
@ -56,7 +57,8 @@ class BackupConfigFragment : BasePreferenceFragment(),
}.onSuccess { }.onSuccess {
appCtx.toastOnUi(R.string.backup_success) appCtx.toastOnUi(R.string.backup_success)
}.onError { }.onError {
appCtx.toastOnUi(R.string.backup_fail) AppLog.put("备份出错\n${it.localizedMessage}", it)
appCtx.toastOnUi(getString(R.string.backup_fail, it.localizedMessage))
} }
} else { } else {
uri.path?.let { path -> uri.path?.let { path ->
@ -66,7 +68,8 @@ class BackupConfigFragment : BasePreferenceFragment(),
}.onSuccess { }.onSuccess {
appCtx.toastOnUi(R.string.backup_success) appCtx.toastOnUi(R.string.backup_success)
}.onError { }.onError {
appCtx.toastOnUi(R.string.backup_fail) AppLog.put("备份出错\n${it.localizedMessage}", it)
appCtx.toastOnUi(getString(R.string.backup_fail, it.localizedMessage))
} }
} }
} }
@ -120,7 +123,7 @@ class BackupConfigFragment : BasePreferenceFragment(),
upPreferenceSummary(PreferKey.webDavPassword, getPrefString(PreferKey.webDavPassword)) upPreferenceSummary(PreferKey.webDavPassword, getPrefString(PreferKey.webDavPassword))
upPreferenceSummary(PreferKey.backupPath, getPrefString(PreferKey.backupPath)) upPreferenceSummary(PreferKey.backupPath, getPrefString(PreferKey.backupPath))
findPreference<io.legado.app.ui.widget.prefs.Preference>("web_dav_restore") findPreference<io.legado.app.ui.widget.prefs.Preference>("web_dav_restore")
?.onLongClick = { restoreDir.launch(null) } ?.onLongClick { restoreDir.launch(); true }
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@ -203,11 +206,11 @@ class BackupConfigFragment : BasePreferenceFragment(),
override fun onPreferenceTreeClick(preference: Preference?): Boolean { override fun onPreferenceTreeClick(preference: Preference?): Boolean {
when (preference?.key) { when (preference?.key) {
PreferKey.backupPath -> selectBackupPath.launch(null) PreferKey.backupPath -> selectBackupPath.launch()
PreferKey.restoreIgnore -> restoreIgnore() PreferKey.restoreIgnore -> restoreIgnore()
"web_dav_backup" -> backup() "web_dav_backup" -> backup()
"web_dav_restore" -> restore() "web_dav_restore" -> restore()
"import_old" -> restoreOld.launch(null) "import_old" -> restoreOld.launch()
} }
return super.onPreferenceTreeClick(preference) return super.onPreferenceTreeClick(preference)
} }
@ -231,7 +234,7 @@ class BackupConfigFragment : BasePreferenceFragment(),
fun backup() { fun backup() {
val backupPath = AppConfig.backupPath val backupPath = AppConfig.backupPath
if (backupPath.isNullOrEmpty()) { if (backupPath.isNullOrEmpty()) {
backupDir.launch(null) backupDir.launch()
} else { } else {
if (backupPath.isContentScheme()) { if (backupPath.isContentScheme()) {
val uri = Uri.parse(backupPath) val uri = Uri.parse(backupPath)
@ -242,10 +245,11 @@ class BackupConfigFragment : BasePreferenceFragment(),
}.onSuccess { }.onSuccess {
appCtx.toastOnUi(R.string.backup_success) appCtx.toastOnUi(R.string.backup_success)
}.onError { }.onError {
appCtx.toastOnUi(R.string.backup_fail) AppLog.put("备份出错\n${it.localizedMessage}", it)
appCtx.toastOnUi(getString(R.string.backup_fail, it.localizedMessage))
} }
} else { } else {
backupDir.launch(null) backupDir.launch()
} }
} else { } else {
backupUsePermission(backupPath) backupUsePermission(backupPath)
@ -264,7 +268,8 @@ class BackupConfigFragment : BasePreferenceFragment(),
}.onSuccess { }.onSuccess {
appCtx.toastOnUi(R.string.backup_success) appCtx.toastOnUi(R.string.backup_success)
}.onError { }.onError {
appCtx.toastOnUi(R.string.backup_fail) AppLog.put("备份出错\n${it.localizedMessage}", it)
appCtx.toastOnUi(getString(R.string.backup_fail, it.localizedMessage))
} }
} }
.request() .request()
@ -296,13 +301,13 @@ class BackupConfigFragment : BasePreferenceFragment(),
Restore.restore(requireContext(), backupPath) Restore.restore(requireContext(), backupPath)
} }
} else { } else {
restoreDir.launch(null) restoreDir.launch()
} }
} else { } else {
restoreUsePermission(backupPath) restoreUsePermission(backupPath)
} }
} else { } else {
restoreDir.launch(null) restoreDir.launch()
} }
} }

@ -37,6 +37,9 @@ class OtherConfigFragment : BasePreferenceFragment(),
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
putPrefBoolean(PreferKey.processText, isProcessTextEnabled()) putPrefBoolean(PreferKey.processText, isProcessTextEnabled())
addPreferencesFromResource(R.xml.pref_config_other) addPreferencesFromResource(R.xml.pref_config_other)
if (AppConfig.isGooglePlay) {
preferenceScreen.removePreferenceRecursively("Cronet")
}
upPreferenceSummary(PreferKey.userAgent, AppConfig.userAgent) upPreferenceSummary(PreferKey.userAgent, AppConfig.userAgent)
upPreferenceSummary(PreferKey.preDownloadNum, AppConfig.preDownloadNum.toString()) upPreferenceSummary(PreferKey.preDownloadNum, AppConfig.preDownloadNum.toString())
upPreferenceSummary(PreferKey.threadCount, AppConfig.threadCount.toString()) upPreferenceSummary(PreferKey.threadCount, AppConfig.threadCount.toString())

@ -18,6 +18,7 @@ import io.legado.app.lib.permission.Permissions
import io.legado.app.lib.permission.PermissionsCompat import io.legado.app.lib.permission.PermissionsCompat
import io.legado.app.utils.getJsonArray import io.legado.app.utils.getJsonArray
import io.legado.app.utils.isContentScheme import io.legado.app.utils.isContentScheme
import io.legado.app.utils.launch
import io.legado.app.utils.toastOnUi import io.legado.app.utils.toastOnUi
import io.legado.app.utils.viewbindingdelegate.viewBinding import io.legado.app.utils.viewbindingdelegate.viewBinding
import java.io.File import java.io.File
@ -79,7 +80,17 @@ class HandleFileActivity :
alert(title) { alert(title) {
items(selectList) { _, item, _ -> items(selectList) { _, item, _ ->
when (item.value) { when (item.value) {
HandleFileContract.DIR -> selectDocTree.launch(null) HandleFileContract.DIR -> kotlin.runCatching {
selectDocTree.launch()
}.onFailure {
toastOnUi(R.string.open_sys_dir_picker_error)
checkPermissions {
FilePickerDialog.show(
supportFragmentManager,
mode = HandleFileContract.DIR
)
}
}
HandleFileContract.FILE -> selectDoc.launch(typesOfExtensions(allowExtensions)) HandleFileContract.FILE -> selectDoc.launch(typesOfExtensions(allowExtensions))
10 -> checkPermissions { 10 -> checkPermissions {
FilePickerDialog.show( FilePickerDialog.show(
@ -118,10 +129,10 @@ class HandleFileActivity :
} }
} }
private fun getFileData(): Triple<String, ByteArray, String>? { private fun getFileData(): Triple<String, Any, String>? {
val fileName = intent.getStringExtra("fileName") val fileName = intent.getStringExtra("fileName")
val file = intent.getStringExtra("fileKey")?.let { val file = intent.getStringExtra("fileKey")?.let {
IntentData.get<ByteArray>(it) IntentData.get<Any>(it)
} }
val contentType = intent.getStringExtra("contentType") val contentType = intent.getStringExtra("contentType")
if (fileName != null && file != null && contentType != null) { if (fileName != null && file != null && contentType != null) {

@ -11,7 +11,7 @@ import io.legado.app.utils.putJson
@Suppress("unused") @Suppress("unused")
class HandleFileContract : class HandleFileContract :
ActivityResultContract<HandleFileContract.HandleFileParam.() -> Unit, HandleFileContract.Result>() { ActivityResultContract<(HandleFileContract.HandleFileParam.() -> Unit)?, HandleFileContract.Result>() {
private var requestCode: Int = 0 private var requestCode: Int = 0
@ -55,7 +55,7 @@ class HandleFileContract :
var title: String? = null, var title: String? = null,
var allowExtensions: Array<String> = arrayOf(), var allowExtensions: Array<String> = arrayOf(),
var otherActions: ArrayList<SelectItem<Int>>? = null, var otherActions: ArrayList<SelectItem<Int>>? = null,
var fileData: Triple<String, ByteArray, String>? = null, var fileData: Triple<String, Any, String>? = null,
var requestCode: Int = 0 var requestCode: Int = 0
) )

@ -8,6 +8,7 @@ import io.legado.app.base.BaseViewModel
import io.legado.app.constant.AppLog import io.legado.app.constant.AppLog
import io.legado.app.help.DirectLinkUpload import io.legado.app.help.DirectLinkUpload
import io.legado.app.utils.FileUtils import io.legado.app.utils.FileUtils
import io.legado.app.utils.GSON
import io.legado.app.utils.isContentScheme import io.legado.app.utils.isContentScheme
import io.legado.app.utils.writeBytes import io.legado.app.utils.writeBytes
@ -20,7 +21,7 @@ class HandleFileViewModel(application: Application) : BaseViewModel(application)
fun upload( fun upload(
fileName: String, fileName: String,
file: ByteArray, file: Any,
contentType: String, contentType: String,
success: (url: String) -> Unit success: (url: String) -> Unit
) { ) {
@ -35,18 +36,24 @@ class HandleFileViewModel(application: Application) : BaseViewModel(application)
} }
} }
fun saveToLocal(uri: Uri, fileName: String, data: ByteArray, success: (uri: Uri) -> Unit) { fun saveToLocal(uri: Uri, fileName: String, data: Any, success: (uri: Uri) -> Unit) {
execute { execute {
val bytes = when (data) {
is File -> data.readBytes()
is ByteArray -> data
is String -> data.toByteArray()
else -> GSON.toJson(data).toByteArray()
}
return@execute if (uri.isContentScheme()) { return@execute if (uri.isContentScheme()) {
val doc = DocumentFile.fromTreeUri(context, uri)!! val doc = DocumentFile.fromTreeUri(context, uri)!!
doc.findFile(fileName)?.delete() doc.findFile(fileName)?.delete()
val newDoc = doc.createFile("", fileName) val newDoc = doc.createFile("", fileName)
newDoc!!.writeBytes(context, data) newDoc!!.writeBytes(context, bytes)
newDoc.uri newDoc.uri
} else { } else {
val file = File(uri.path!!) val file = File(uri.path!!)
val newFile = FileUtils.createFileIfNotExist(file, fileName) val newFile = FileUtils.createFileIfNotExist(file, fileName)
newFile.writeBytes(data) newFile.writeBytes(bytes)
Uri.fromFile(newFile) Uri.fromFile(newFile)
} }
}.onError { }.onError {

@ -82,14 +82,10 @@ abstract class BaseBookshelfFragment(layoutId: Int) : VMBaseFragment<BookshelfVi
R.id.menu_download -> startActivity<CacheActivity> { R.id.menu_download -> startActivity<CacheActivity> {
putExtra("groupId", groupId) putExtra("groupId", groupId)
} }
R.id.menu_export_bookshelf -> viewModel.exportBookshelf(books) { R.id.menu_export_bookshelf -> viewModel.exportBookshelf(books) { file ->
exportResult.launch { exportResult.launch {
mode = HandleFileContract.EXPORT mode = HandleFileContract.EXPORT
fileData = Triple( fileData = Triple("bookshelf.json", file, "application/json")
"bookshelf.json",
it.toByteArray(),
"application/json"
)
} }
} }
R.id.menu_import_bookshelf -> importBookshelfAlert(groupId) R.id.menu_import_bookshelf -> importBookshelfAlert(groupId)

@ -1,6 +1,7 @@
package io.legado.app.ui.main.bookshelf package io.legado.app.ui.main.bookshelf
import android.app.Application import android.app.Application
import com.google.gson.stream.JsonWriter
import io.legado.app.R import io.legado.app.R
import io.legado.app.base.BaseViewModel import io.legado.app.base.BaseViewModel
import io.legado.app.data.appDb import io.legado.app.data.appDb
@ -14,6 +15,9 @@ import io.legado.app.model.webBook.WebBook
import io.legado.app.utils.* import io.legado.app.utils.*
import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.isActive import kotlinx.coroutines.isActive
import java.io.File
import java.io.FileOutputStream
import java.io.OutputStreamWriter
class BookshelfViewModel(application: Application) : BaseViewModel(application) { class BookshelfViewModel(application: Application) : BaseViewModel(application) {
@ -66,19 +70,33 @@ class BookshelfViewModel(application: Application) : BaseViewModel(application)
} }
} }
fun exportBookshelf(books: List<Book>?, success: (json: String) -> Unit) { fun exportBookshelf(books: List<Book>?, success: (file: File) -> Unit) {
execute { execute {
val exportList = arrayListOf<Map<String, String?>>() books?.let {
books?.forEach { val path = "${context.filesDir}/books.json"
val bookMap = hashMapOf<String, String?>() FileUtils.delete(path)
bookMap["name"] = it.name val file = FileUtils.createFileWithReplace(path)
bookMap["author"] = it.author @Suppress("BlockingMethodInNonBlockingContext")
bookMap["intro"] = it.getDisplayIntro() FileOutputStream(file).use { out ->
exportList.add(bookMap) val writer = JsonWriter(OutputStreamWriter(out, "UTF-8"))
} writer.setIndent(" ")
GSON.toJson(exportList) writer.beginArray()
books.forEach {
val bookMap = hashMapOf<String, String?>()
bookMap["name"] = it.name
bookMap["author"] = it.author
bookMap["intro"] = it.getDisplayIntro()
GSON.toJson(bookMap, bookMap::class.java, writer)
}
writer.endArray()
writer.close()
}
file
} ?: throw NoStackTraceException("书籍不能为空")
}.onSuccess { }.onSuccess {
success(it) success(it)
}.onError {
context.toastOnUi("导出书籍出错\n${it.localizedMessage}")
} }
} }

@ -46,11 +46,11 @@ class BookshelfFragment1 : BaseBookshelfFragment(R.layout.fragment_bookshelf),
private val bookGroups = mutableListOf<BookGroup>() private val bookGroups = mutableListOf<BookGroup>()
private val fragmentMap = hashMapOf<Long, BooksFragment>() private val fragmentMap = hashMapOf<Long, BooksFragment>()
private var groupsFlowJob: Job? = null private var groupsFlowJob: Job? = null
override val groupId: Long get() = selectedGroup.groupId override val groupId: Long get() = selectedGroup?.groupId ?: 0
override val books: List<Book> override val books: List<Book>
get() { get() {
val fragment = fragmentMap[selectedGroup.groupId] val fragment = fragmentMap[groupId]
return fragment?.getBooks() ?: emptyList() return fragment?.getBooks() ?: emptyList()
} }
@ -60,8 +60,8 @@ class BookshelfFragment1 : BaseBookshelfFragment(R.layout.fragment_bookshelf),
initBookGroupData() initBookGroupData()
} }
private val selectedGroup: BookGroup private val selectedGroup: BookGroup?
get() = bookGroups[tabLayout.selectedTabPosition] get() = bookGroups.getOrNull(tabLayout.selectedTabPosition)
private fun initView() { private fun initView() {
binding.viewPagerBookshelf.setEdgeEffectColor(primaryColor) binding.viewPagerBookshelf.setEdgeEffectColor(primaryColor)
@ -113,8 +113,10 @@ class BookshelfFragment1 : BaseBookshelfFragment(R.layout.fragment_bookshelf),
} }
override fun onTabReselected(tab: TabLayout.Tab) { override fun onTabReselected(tab: TabLayout.Tab) {
fragmentMap[selectedGroup.groupId]?.let { selectedGroup?.let { group ->
toastOnUi("${selectedGroup.groupName}(${it.getBooksCount()})") fragmentMap[group.groupId]?.let {
toastOnUi("${group.groupName}(${it.getBooksCount()})")
}
} }
} }
@ -125,7 +127,7 @@ class BookshelfFragment1 : BaseBookshelfFragment(R.layout.fragment_bookshelf),
} }
override fun gotoTop() { override fun gotoTop() {
fragmentMap[selectedGroup.groupId]?.gotoTop() fragmentMap[groupId]?.gotoTop()
} }
private inner class TabFragmentPageAdapter(fm: FragmentManager) : private inner class TabFragmentPageAdapter(fm: FragmentManager) :

@ -11,18 +11,17 @@ abstract class BaseBooksAdapter<VH : RecyclerView.ViewHolder>(
val callBack: CallBack val callBack: CallBack
) : RecyclerView.Adapter<VH>() { ) : RecyclerView.Adapter<VH>() {
val diffItemCallback: DiffUtil.ItemCallback<Book> val diffItemCallback = object : DiffUtil.ItemCallback<Book>() {
get() = object : DiffUtil.ItemCallback<Book>() {
override fun areItemsTheSame(oldItem: Book, newItem: Book): Boolean { override fun areItemsTheSame(oldItem: Book, newItem: Book): Boolean {
return oldItem.name == newItem.name return oldItem.name == newItem.name
&& oldItem.author == newItem.author && oldItem.author == newItem.author
} }
override fun areContentsTheSame(oldItem: Book, newItem: Book): Boolean { override fun areContentsTheSame(oldItem: Book, newItem: Book): Boolean {
return when { return when {
oldItem.durChapterTime != newItem.durChapterTime -> false oldItem.durChapterTime != newItem.durChapterTime -> false
oldItem.name != newItem.name -> false oldItem.name != newItem.name -> false
oldItem.author != newItem.author -> false oldItem.author != newItem.author -> false
oldItem.durChapterTitle != newItem.durChapterTitle -> false oldItem.durChapterTitle != newItem.durChapterTitle -> false
oldItem.latestChapterTitle != newItem.latestChapterTitle -> false oldItem.latestChapterTitle != newItem.latestChapterTitle -> false

@ -40,6 +40,7 @@ class ExploreFragment : VMBaseFragment<ExploreViewModel>(R.layout.fragment_explo
private val searchView: SearchView by lazy { private val searchView: SearchView by lazy {
binding.titleBar.findViewById(R.id.search_view) binding.titleBar.findViewById(R.id.search_view)
} }
private val diffItemCallBack = ExploreDiffItemCallBack()
private val groups = linkedSetOf<String>() private val groups = linkedSetOf<String>()
private var exploreFlowJob: Job? = null private var exploreFlowJob: Job? = null
private var groupsMenu: SubMenu? = null private var groupsMenu: SubMenu? = null
@ -127,7 +128,7 @@ class ExploreFragment : VMBaseFragment<ExploreViewModel>(R.layout.fragment_explo
} }
exploreFlow.collect { exploreFlow.collect {
binding.tvEmptyMsg.isGone = it.isNotEmpty() || searchView.query.isNotEmpty() binding.tvEmptyMsg.isGone = it.isNotEmpty() || searchView.query.isNotEmpty()
adapter.setItems(it, ExploreDiffItemCallBack()) adapter.setItems(it, diffItemCallBack)
} }
} }
} }

@ -14,6 +14,7 @@ import io.legado.app.constant.PreferKey
import io.legado.app.databinding.FragmentMyConfigBinding import io.legado.app.databinding.FragmentMyConfigBinding
import io.legado.app.help.AppConfig import io.legado.app.help.AppConfig
import io.legado.app.help.ThemeConfig import io.legado.app.help.ThemeConfig
import io.legado.app.lib.dialogs.selector
import io.legado.app.lib.theme.primaryColor import io.legado.app.lib.theme.primaryColor
import io.legado.app.service.WebService import io.legado.app.service.WebService
import io.legado.app.ui.about.AboutActivity import io.legado.app.ui.about.AboutActivity
@ -65,6 +66,18 @@ class MyFragment : BaseFragment(R.layout.fragment_my_config) {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
putPrefBoolean(PreferKey.webService, WebService.isRun) putPrefBoolean(PreferKey.webService, WebService.isRun)
addPreferencesFromResource(R.xml.pref_main) addPreferencesFromResource(R.xml.pref_main)
findPreference<SwitchPreference>("webService")?.onLongClick {
if (!WebService.isRun) {
return@onLongClick false
}
context?.selector(arrayListOf("复制地址", "浏览器打开")) { _, i ->
when (i) {
0 -> context?.sendToClip(it.summary.toString())
1 -> context?.openUrl(it.summary.toString())
}
}
true
}
observeEventSticky<String>(EventBus.WEB_SERVICE) { observeEventSticky<String>(EventBus.WEB_SERVICE) {
findPreference<SwitchPreference>(PreferKey.webService)?.let { findPreference<SwitchPreference>(PreferKey.webService)?.let {
it.isChecked = WebService.isRun it.isChecked = WebService.isRun
@ -138,5 +151,6 @@ class MyFragment : BaseFragment(R.layout.fragment_my_config) {
return super.onPreferenceTreeClick(preference) return super.onPreferenceTreeClick(preference)
} }
} }
} }

@ -12,6 +12,7 @@ import io.legado.app.base.BaseActivity
import io.legado.app.databinding.ActivityQrcodeCaptureBinding import io.legado.app.databinding.ActivityQrcodeCaptureBinding
import io.legado.app.utils.QRCodeUtils import io.legado.app.utils.QRCodeUtils
import io.legado.app.utils.SelectImageContract import io.legado.app.utils.SelectImageContract
import io.legado.app.utils.launch
import io.legado.app.utils.readBytes import io.legado.app.utils.readBytes
import io.legado.app.utils.viewbindingdelegate.viewBinding import io.legado.app.utils.viewbindingdelegate.viewBinding
@ -41,7 +42,7 @@ class QrCodeActivity : BaseActivity<ActivityQrcodeCaptureBinding>(), OnScanResul
override fun onCompatOptionsItemSelected(item: MenuItem): Boolean { override fun onCompatOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) { when (item.itemId) {
R.id.action_choose_from_gallery -> selectQrImage.launch(null) R.id.action_choose_from_gallery -> selectQrImage.launch()
} }
return super.onCompatOptionsItemSelected(item) return super.onCompatOptionsItemSelected(item)
} }

@ -5,7 +5,7 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import androidx.activity.result.contract.ActivityResultContract import androidx.activity.result.contract.ActivityResultContract
class QrCodeResult : ActivityResultContract<Unit, String?>() { class QrCodeResult : ActivityResultContract<Unit?, String?>() {
override fun createIntent(context: Context, input: Unit?): Intent { override fun createIntent(context: Context, input: Unit?): Intent {
return Intent(context, QrCodeActivity::class.java) return Intent(context, QrCodeActivity::class.java)

@ -158,7 +158,7 @@ class ReplaceRuleActivity : VMBaseActivity<ActivityReplaceRuleBinding, ReplaceRu
adapter.revertSelection() adapter.revertSelection()
} }
override fun onClickMainAction() { override fun onClickSelectBarMainAction() {
delSourceDialog() delSourceDialog()
} }
@ -224,7 +224,7 @@ class ReplaceRuleActivity : VMBaseActivity<ActivityReplaceRuleBinding, ReplaceRu
mode = HandleFileContract.FILE mode = HandleFileContract.FILE
allowExtensions = arrayOf("txt", "json") allowExtensions = arrayOf("txt", "json")
} }
R.id.menu_import_qr -> qrCodeResult.launch(null) R.id.menu_import_qr -> qrCodeResult.launch()
R.id.menu_help -> showHelp() R.id.menu_help -> showHelp()
else -> if (item.groupId == R.id.replace_group) { else -> if (item.groupId == R.id.replace_group) {
searchView.setQuery("group:${item.title}", true) searchView.setQuery("group:${item.title}", true)

@ -125,7 +125,7 @@ class RssSourceEditActivity :
} }
} }
R.id.menu_copy_source -> sendToClip(GSON.toJson(getRssSource())) R.id.menu_copy_source -> sendToClip(GSON.toJson(getRssSource()))
R.id.menu_qr_code_camera -> qrCodeResult.launch(null) R.id.menu_qr_code_camera -> qrCodeResult.launch()
R.id.menu_paste_source -> viewModel.pasteSource { upRecyclerView(it) } R.id.menu_paste_source -> viewModel.pasteSource { upRecyclerView(it) }
R.id.menu_share_str -> share(GSON.toJson(getRssSource())) R.id.menu_share_str -> share(GSON.toJson(getRssSource()))
R.id.menu_share_qr -> shareWithQr( R.id.menu_share_qr -> shareWithQr(

@ -1,7 +1,6 @@
package io.legado.app.ui.rss.source.manage package io.legado.app.ui.rss.source.manage
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
@ -93,7 +92,7 @@ class RssSourceActivity : VMBaseActivity<ActivityRssSourceBinding, RssSourceView
initSearchView() initSearchView()
initGroupFlow() initGroupFlow()
upSourceFlow() upSourceFlow()
initViewEvent() initSelectActionBar()
} }
override fun onCompatCreateOptionsMenu(menu: Menu): Boolean { override fun onCompatCreateOptionsMenu(menu: Menu): Boolean {
@ -115,11 +114,8 @@ class RssSourceActivity : VMBaseActivity<ActivityRssSourceBinding, RssSourceView
allowExtensions = arrayOf("txt", "json") allowExtensions = arrayOf("txt", "json")
} }
R.id.menu_import_onLine -> showImportDialog() R.id.menu_import_onLine -> showImportDialog()
R.id.menu_import_qr -> qrCodeResult.launch(null) R.id.menu_import_qr -> qrCodeResult.launch()
R.id.menu_group_manage -> showDialogFragment<GroupManageDialog>() R.id.menu_group_manage -> showDialogFragment<GroupManageDialog>()
R.id.menu_share_source -> viewModel.shareSelection(adapter.selection) {
startActivity(Intent.createChooser(it, getString(R.string.share_selected_source)))
}
R.id.menu_import_default -> viewModel.importDefault() R.id.menu_import_default -> viewModel.importDefault()
R.id.menu_help -> showHelp() R.id.menu_help -> showHelp()
else -> if (item.groupId == R.id.source_group) { else -> if (item.groupId == R.id.source_group) {
@ -134,17 +130,17 @@ class RssSourceActivity : VMBaseActivity<ActivityRssSourceBinding, RssSourceView
when (item?.itemId) { when (item?.itemId) {
R.id.menu_enable_selection -> viewModel.enableSelection(adapter.selection) R.id.menu_enable_selection -> viewModel.enableSelection(adapter.selection)
R.id.menu_disable_selection -> viewModel.disableSelection(adapter.selection) R.id.menu_disable_selection -> viewModel.disableSelection(adapter.selection)
R.id.menu_del_selection -> viewModel.delSelection(adapter.selection)
R.id.menu_export_selection -> exportResult.launch {
mode = HandleFileContract.EXPORT
fileData = Triple(
"exportRssSource.json",
GSON.toJson(adapter.selection).toByteArray(),
"application/json"
)
}
R.id.menu_top_sel -> viewModel.topSource(*adapter.selection.toTypedArray()) R.id.menu_top_sel -> viewModel.topSource(*adapter.selection.toTypedArray())
R.id.menu_bottom_sel -> viewModel.bottomSource(*adapter.selection.toTypedArray()) R.id.menu_bottom_sel -> viewModel.bottomSource(*adapter.selection.toTypedArray())
R.id.menu_export_selection -> viewModel.saveToFile(adapter.selection) { file ->
exportResult.launch {
mode = HandleFileContract.EXPORT
fileData = Triple("exportRssSource.json", file, "application/json")
}
}
R.id.menu_share_source -> viewModel.saveToFile(adapter.selection) {
share(it)
}
} }
return true return true
} }
@ -183,6 +179,13 @@ class RssSourceActivity : VMBaseActivity<ActivityRssSourceBinding, RssSourceView
} }
} }
private fun initSelectActionBar() {
binding.selectActionBar.setMainActionText(R.string.delete)
binding.selectActionBar.inflateMenu(R.menu.rss_source_sel)
binding.selectActionBar.setOnMenuItemClickListener(this)
binding.selectActionBar.setCallBack(this)
}
private fun initGroupFlow() { private fun initGroupFlow() {
launch { launch {
appDb.rssSourceDao.flowGroup().collect { appDb.rssSourceDao.flowGroup().collect {
@ -207,20 +210,13 @@ class RssSourceActivity : VMBaseActivity<ActivityRssSourceBinding, RssSourceView
adapter.revertSelection() adapter.revertSelection()
} }
override fun onClickMainAction() { override fun onClickSelectBarMainAction() {
delSourceDialog() delSourceDialog()
} }
private fun initViewEvent() {
binding.selectActionBar.setMainActionText(R.string.delete)
binding.selectActionBar.inflateMenu(R.menu.rss_source_sel)
binding.selectActionBar.setOnMenuItemClickListener(this)
binding.selectActionBar.setCallBack(this)
}
private fun delSourceDialog() { private fun delSourceDialog() {
alert(titleResource = R.string.draw, messageResource = R.string.sure_del) { alert(titleResource = R.string.draw, messageResource = R.string.sure_del) {
okButton { viewModel.delSelection(adapter.selection) } okButton { viewModel.del(*adapter.selection.toTypedArray()) }
noButton() noButton()
} }
} }

@ -36,18 +36,17 @@ class RssSourceAdapter(context: Context, val callBack: CallBack) :
return selection.sortedBy { it.customOrder } return selection.sortedBy { it.customOrder }
} }
val diffItemCallback: DiffUtil.ItemCallback<RssSource> val diffItemCallback = object : DiffUtil.ItemCallback<RssSource>() {
get() = object : DiffUtil.ItemCallback<RssSource>() {
override fun areItemsTheSame(oldItem: RssSource, newItem: RssSource): Boolean { override fun areItemsTheSame(oldItem: RssSource, newItem: RssSource): Boolean {
return oldItem.sourceUrl == newItem.sourceUrl return oldItem.sourceUrl == newItem.sourceUrl
} }
override fun areContentsTheSame(oldItem: RssSource, newItem: RssSource): Boolean { override fun areContentsTheSame(oldItem: RssSource, newItem: RssSource): Boolean {
return oldItem.sourceName == newItem.sourceName return oldItem.sourceName == newItem.sourceName
&& oldItem.sourceGroup == newItem.sourceGroup && oldItem.sourceGroup == newItem.sourceGroup
&& oldItem.enabled == newItem.enabled && oldItem.enabled == newItem.enabled
} }
override fun getChangePayload(oldItem: RssSource, newItem: RssSource): Any? { override fun getChangePayload(oldItem: RssSource, newItem: RssSource): Any? {
val payload = Bundle() val payload = Bundle()

@ -1,15 +1,13 @@
package io.legado.app.ui.rss.source.manage package io.legado.app.ui.rss.source.manage
import android.app.Application import android.app.Application
import android.content.Intent
import android.text.TextUtils import android.text.TextUtils
import androidx.core.content.FileProvider
import io.legado.app.base.BaseViewModel import io.legado.app.base.BaseViewModel
import io.legado.app.constant.AppConst
import io.legado.app.data.appDb import io.legado.app.data.appDb
import io.legado.app.data.entities.RssSource import io.legado.app.data.entities.RssSource
import io.legado.app.help.DefaultData import io.legado.app.help.DefaultData
import io.legado.app.utils.* import io.legado.app.utils.*
import java.io.File
class RssSourceViewModel(application: Application) : BaseViewModel(application) { class RssSourceViewModel(application: Application) : BaseViewModel(application) {
@ -33,8 +31,8 @@ class RssSourceViewModel(application: Application) : BaseViewModel(application)
} }
} }
fun del(rssSource: RssSource) { fun del(vararg rssSource: RssSource) {
execute { appDb.rssSourceDao.delete(rssSource) } execute { appDb.rssSourceDao.delete(*rssSource) }
} }
fun update(vararg rssSource: RssSource) { fun update(vararg rssSource: RssSource) {
@ -71,25 +69,13 @@ class RssSourceViewModel(application: Application) : BaseViewModel(application)
} }
} }
fun delSelection(sources: List<RssSource>) { fun saveToFile(sources: List<RssSource>, success: (file: File) -> Unit) {
execute { execute {
appDb.rssSourceDao.delete(*sources.toTypedArray()) val path = "${context.filesDir}/shareRssSource.json"
} FileUtils.delete(path)
} val file = FileUtils.createFileWithReplace(path)
fun shareSelection(sources: List<RssSource>, success: ((intent: Intent) -> Unit)) {
execute {
val tmpSharePath = "${context.filesDir}/shareRssSource.json"
FileUtils.delete(tmpSharePath)
val intent = Intent(Intent.ACTION_SEND)
val file = FileUtils.createFileWithReplace(tmpSharePath)
file.writeText(GSON.toJson(sources)) file.writeText(GSON.toJson(sources))
val fileUri = FileProvider.getUriForFile(context, AppConst.authority, file) file
intent.type = "text/*"
intent.putExtra(Intent.EXTRA_STREAM, fileUri)
intent.flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
intent
}.onSuccess { }.onSuccess {
success.invoke(it) success.invoke(it)
}.onError { }.onError {

@ -42,7 +42,7 @@ class SelectActionBar @JvmOverloads constructor(
} }
} }
binding.btnRevertSelection.setOnClickListener { callBack?.revertSelection() } binding.btnRevertSelection.setOnClickListener { callBack?.revertSelection() }
binding.btnSelectActionMain.setOnClickListener { callBack?.onClickMainAction() } binding.btnSelectActionMain.setOnClickListener { callBack?.onClickSelectBarMainAction() }
binding.ivMenuMore.setOnClickListener { selMenu?.show() } binding.ivMenuMore.setOnClickListener { selMenu?.show() }
} }
@ -108,6 +108,6 @@ class SelectActionBar @JvmOverloads constructor(
fun revertSelection() fun revertSelection()
fun onClickMainAction() {} fun onClickSelectBarMainAction() {}
} }
} }

@ -14,7 +14,7 @@ class EditTextPreference(context: Context, attrs: AttributeSet) :
layoutResource = R.layout.view_preference layoutResource = R.layout.view_preference
} }
override fun onBindViewHolder(holder: PreferenceViewHolder?) { override fun onBindViewHolder(holder: PreferenceViewHolder) {
Preference.bindView<TextView>(context, holder, icon, title, summary, null, null) Preference.bindView<TextView>(context, holder, icon, title, summary, null, null)
super.onBindViewHolder(holder) super.onBindViewHolder(holder)
} }

@ -51,7 +51,7 @@ class IconListPreference(context: Context, attrs: AttributeSet) : ListPreference
} }
} }
override fun onBindViewHolder(holder: PreferenceViewHolder?) { override fun onBindViewHolder(holder: PreferenceViewHolder) {
super.onBindViewHolder(holder) super.onBindViewHolder(holder)
val v = Preference.bindView<ImageView>( val v = Preference.bindView<ImageView>(
context, context,

@ -23,7 +23,7 @@ class NameListPreference(context: Context, attrs: AttributeSet) : ListPreference
typedArray.recycle() typedArray.recycle()
} }
override fun onBindViewHolder(holder: PreferenceViewHolder?) { override fun onBindViewHolder(holder: PreferenceViewHolder) {
val v = Preference.bindView<TextView>( val v = Preference.bindView<TextView>(
context, context,
holder, holder,

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save