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. 40
      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. 128
      app/src/main/assets/web/uploadBook/js/common.js
  20. 85
      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. 60
      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. 6
      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. 193
      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. 11
      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. 6
      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. 25
      app/src/main/java/io/legado/app/ui/book/source/manage/BookSourceActivity.kt
  73. 8
      app/src/main/java/io/legado/app/ui/book/source/manage/BookSourceAdapter.kt
  74. 34
      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. 18
      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. 28
      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. 3
      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. 3
      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">
<img src="https://img.shields.io/badge/-微信关注“开源阅读”公众号-orange.svg" alt="#" align="right">
</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">
<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 / 开源阅读
<br>
<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>
[![](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),也许里面就有你要的答案。
# Function-主要功能 [![](https://img.shields.io/badge/-Function-F5F5F5.svg)](#Function-主要功能-)
1. 自定义书源,自己设置规则,抓取网页数据,规则简单易懂,软件内有规则说明。
2. 列表书架,网格书架自由切换。
3. 书源规则支持搜索及发现,所有找书看书功能全部自定义,找书更方便。
4. 订阅内容,可以订阅想看的任何内容,看你想看
5. 支持替换净化,去除广告替换内容很方便。
6. 支持本地TXT、EPUB阅读,手动浏览,智能扫描。
7. 支持高度自定义阅读界面,切换字体、颜色、背景、行距、段距、加粗、简繁转换等。
8. 支持多种翻页模式,覆盖、仿真、滑动、滚动等。
<details><summary>English</summary>
1. Online reading from a variety of sources.<br>
2. Local reading of downloaded content.<br>
3. A configurable reader with multiple viewers, reading directions and other settings. <br>
4. Categories to organize your library.<br>
5. Light and dark themes.<br>
6. Schedule updating your library for new chapters.<br>
7. read offline or to your desired cloud service
</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">
<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/)
* [\#Beta](https://kunfei.lanzoui.com/b0f810h4b)
#### IOS-苹果
* 准备中
* 准备中(No release) - [Github](https://github.com/gedoor/YueDuFlutter)
<a href="#readme">
<img src="https://img.shields.io/badge/-返回顶部-orange.svg" alt="#" align="right">
@ -53,7 +67,9 @@ Legado / 开源阅读
#### 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-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
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())
android {
compileSdkVersion 30
buildToolsVersion '30.0.3'
compileSdkVersion 31
buildToolsVersion '31.0.0'
kotlinOptions {
jvmTarget = "11"
}
@ -34,7 +34,7 @@ android {
defaultConfig {
applicationId "io.legado.app"
minSdkVersion 21
targetSdkVersion 30
targetSdkVersion 31
versionCode gitCommits
versionName version
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
@ -130,24 +130,24 @@ dependencies {
//kotlin
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version")
//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-android:$coroutines_version")
//androidX
implementation('androidx.core:core-ktx:1.7.0')
implementation('androidx.appcompat:appcompat:1.3.1')
implementation('androidx.core:core-ktx:1.6.0')
implementation("androidx.activity:activity-ktx:1.3.1")
implementation("androidx.fragment:fragment-ktx:1.3.6")
implementation('androidx.activity:activity-ktx:1.4.0')
implementation('androidx.fragment:fragment-ktx:1.3.6')
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.viewpager2:viewpager2:1.0.0')
implementation('com.google.android.material:material:1.4.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('com.jakewharton.timber:timber:5.0.1')
@ -155,8 +155,8 @@ dependencies {
//media
implementation("androidx.media:media:1.4.1")
def exoplayer_version = '2.15.0'
implementation "com.google.android.exoplayer:exoplayer-core:$exoplayer_version"
implementation "com.google.android.exoplayer:extension-okhttp:$exoplayer_version"
implementation("com.google.android.exoplayer:exoplayer-core:$exoplayer_version")
implementation("com.google.android.exoplayer:extension-okhttp:$exoplayer_version")
//Splitties
def splitties_version = '3.0.0'
@ -169,7 +169,7 @@ dependencies {
implementation("androidx.lifecycle:lifecycle-common-java8:$lifecycle_version")
//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-ktx:$room_version")
kapt("androidx.room:room-compiler:$room_version")

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

@ -10,7 +10,7 @@
"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)}} }}",
"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": [
{
"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 by DuShuYuan
* 朗读语速调节添加微调按钮
**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>
</html>
<script src="js/jquery-1.4.2.min.js"></script>
<script src="js/langSwich.js"></script>
<script src="js/swfupload.js"></script>
<script src="js/common.js"></script>

@ -7,14 +7,13 @@ var config = {
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: "../addLocalBook",
fileLimitSize: 500 * 1024 * 1024
}
fileLimitSize: 500 * 1024 * 1024,
};
var file = {
"inQueue": [], //已经在队列里面的文件,包括 HTML5上传和 Flash上传的
"clientHaveFiles": [] // 客户端已经存在的文件列表
}
inQueue: [], //已经在队列里面的文件,包括 HTML5上传和 Flash上传的
clientHaveFiles: [], // 客户端已经存在的文件列表
};
/**
* HTML5 flash 公用所有文件对象集合
@ -22,24 +21,6 @@ var file = {
*/
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) {
var KB = 1024;
@ -65,18 +46,23 @@ function substr_string(name) {
var preLen = pre.length;
var preStart = preLen - 10;
//前面10个 + 后面5个
var fileName = pre.substr(0, 10) + "...." + pre.substr(preStart > 4 ? -4 : -preStart, 4) + suffix;
return fileName
var fileName =
pre.substr(0, 10) +
"...." +
pre.substr(preStart > 4 ? -4 : -preStart, 4) +
suffix;
return fileName;
}
function checkFile(file) {
if (file.size > config.fileLimitSize) {
return jsonLang.t11;
}
if (!file.name || !file.name.toLowerCase().match('(' + config.fileTypes + ')$')) {
if (
!file.name ||
!file.name.toLowerCase().match("(" + config.fileTypes + ")$")
) {
return jsonLang.t12;
}
@ -95,36 +81,32 @@ function checkFile(file) {
* @param int type 0 是swf 上传的1 是html5上传的
*/
function fileQueued(file, type) {
var size = 0, fid = file.id, name = "";
var size = 0,
fid = file.id,
name = "";
type = type || 0;
if (file != undefined) {
//计算文件大小 单位MB
size = countFileSize(file.size);
name = substr_string(file.name)
name = substr_string(file.name);
//创建要插入的元素
// "<tr id='tr_'"+fid+"><td><div class='bh-poStion'><h1>"+name+"</h1>"+
// "<div class='bh-tip bh-tip3' id='progress_bar_"+fid+"'>"+
// "<span id='progress_bar_span_"+fid+"'></span></div>"+
// "</div></td><td><span class='bh-M'>"+size+"</span></td><td><div class='bh-link' id='handle_button_"+fid+"'>"+
// "<a href='javascript:void(0)' onclick=userCancelUpload('"+fid+"',"+type+")>取消</a></div></td></tr>";
var HTML = '<li id="tr_"' + fid + '>' +
'<dl class="grybg">' +
'<dt>' + name + '</dt>' +
'<dd>' + size + '</dd>' +
'<dd id="handle_button_' + fid + '" onclick=userCancelUpload("' + fid + '",' + type + ') class="orange">' +
'<span id="progress_bar_span_' + fid + '">0%</span> ' + jsonLang.t9 + '</dd>' +
'</dl>' +
'<div class="jdt"><p id="progress_bar_p_' + fid + '" ></p></div>' +
'</li>';
jQuery("#tableStyle").append(HTML);
var li = document.createElement("li");
li.id = `tr_${fid}`;
li.innerHTML = `<dl class="grybg">
<dt>${name}</dt>
<dd>${size}</dd>
<dd id="handle_button_${fid}" onclick=userCancelUpload("${fid}",${type}) class="orange">
<span id="progress_bar_span_${fid}">0%</span> ${jsonLang.t9}
</dd>
</dl>
<div class="jdt"><p id="progress_bar_p_${fid}" ></p></div>`;
var table = document.getElementById("tableStyle");
table.appendChild(li);
//保存falsh_id,为上传做准备
//global_flash_id.push(file.id);
//更改背景颜色
changeTrBackGroundColor()
changeTrBackGroundColor();
}
}
@ -138,44 +120,47 @@ function changeTrBackGroundColor() {
}
}
//上传时返回的状态
function uploadProgress(file, bytesLoaded, bytesTotal) {
if (!$("#progress_bar_p_" + file.id).hasClass("orange")) {
$("#progress_bar_p_" + file.id).addClass("orange");
var progressClass = document.getElementById(
"progress_bar_p_" + file.id
).classList;
if (!progressClass.contains("orange")) {
progressClass.add("orange");
}
$("#progress_bar_p_" + file.id).css("width", (bytesLoaded / bytesTotal) * 100 + "%");
$("#progress_bar_span_" + file.id).html(parseInt((bytesLoaded / bytesTotal) * 100) + "%");
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) {
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) {
if (type == 0) {
SWFFuns.cancelUpload(file_id);
} else {
HTML5Funs.cancelUpload(file_id);
}
$("#handle_button_" + file_id).html(jsonLang.t14).removeClass("orange").addClass("gray");
var element = document.getElementById("handle_button_" + file_id);
element.innerHTML = jsonLang.t14;
element.classList.remove("orange");
element.classList.add("gray");
//如果已经上传一部分了
if ($("#progress_bar_p_" + file_id).hasClass("orange")) {
$("#progress_bar_p_" + file_id).removeClass("orange");
$("#progress_bar_p_" + file_id).addClass("gray");
var progressElement = document.getElementById("progress_bar_p_" + file_id);
if (progressElement.classList.contains("orange")) {
progressElement.classList.remove("orange");
progressElement.classList.add("gray");
}
}
/**
* 通过文件名称 从全局的文件列表中获取文件对象
*
@ -191,14 +176,12 @@ function getFileFomeFilesUpload(filename) {
return null;
}
/**
* 往全局的 上传列表添加一个数据
*/
function addFileToFilesUpload(file) {
if (typeof file == "string") {
filesUpload.push({ "name": file })
filesUpload.push({ name: file });
return true;
} else if (typeof file == "object") {
filesUpload.push(file);
@ -212,7 +195,6 @@ function addFileToFilesUpload(file) {
* 往全局的 上传列表添加一个数据
*/
function updateFileToFilesUpload(file) {
var len = filesUpload.length;
for (var i = 0; i < len; i++) {
if (filesUpload[i].name == file.name) {
@ -244,13 +226,11 @@ function findObjectKey(object, fid) {
* @return 删除后的数组 其实数组是引用类型可以不返回
*/
function removeFileFromFilesUpload(files, fid) {
var filesUploadKey = -1;
filesUploadKey = findObjectKey(files, fid);
//从全局文件中移除
if (filesUploadKey > -1)
files.splice(filesUploadKey, 1);
if (filesUploadKey > -1) files.splice(filesUploadKey, 1);
return files;
}

@ -9,44 +9,43 @@ var currUploadfile = {}; //当前上传的文件对象
var uploadQueue = []; //上传队列集合
var isUploading = false; //是否正在上传
var XHR
var XHR;
try {
XHR = new XMLHttpRequest();
} catch (e) {}
(function (isSupportFileUpload) {
//不支持拖拽上传,或者 不支持FormData ,显示WiFi表示
if (!isSupportFileUpload) {
$("#drag_area").addClass("wf_wifi");
$("#drag_area h3").html("");
document.getElementById("drag_area").classList.add("wf_wifi");
document.getElementById("drag_area").innerHTML = "";
return;
//更换样式
} else {
$("#drag_area").addClass("wf_normal");
document.getElementById("drag_area").classList.add("wf_normal");
}
addEvent();
/**
* 添加事件
*/
function addEvent() {
var dropArea = $('#drag_area h3')[0];
dropArea.addEventListener('dragover', handleDragOver, false);
dropArea.addEventListener('dragleave', handleDragLeave, false);
dropArea.addEventListener('drop', handleDrop, false);
var dropArea = document.getElementById("drag_area");
dropArea.addEventListener("dragover", handleDragOver, false);
dropArea.addEventListener("dragleave", handleDragLeave, false);
dropArea.addEventListener("drop", handleDrop, false);
}
/**
* 松开拖拽文件的处理进行上传
*/
function handleDrop(evt) {
evt.stopPropagation();
evt.preventDefault();
$('#drag_area').addClass('wf_normal').removeClass('wf_active');
var classList = document.getElementById("drag_area").classList;
classList.add("wf_normal");
classList.remove("wf_active");
console.log("Drop");
isDragOver = false;
@ -61,7 +60,7 @@ try {
file.id = fileNumberPex + fileNumber;
//检测文件
msg = checkFile(file)
msg = checkFile(file);
//文件可以通过
if (!msg) {
//添加全局
@ -71,7 +70,7 @@ try {
//在页面进行展示
fileQueued(file, 1);
} else {
errorMsgs.push(msg)
errorMsgs.push(msg);
}
}
@ -79,7 +78,6 @@ try {
//只选择做一个进行上传
if (len == 1) {
alert(errorMsgs[0]);
} else {
a1 = len;
a2 = len - errorMsgs.length;
@ -90,35 +88,33 @@ try {
}
//拿出第一个,进行上传
if (!isUploading && uploadQueue.length > 0) uploadFiles(uploadQueue.shift());
if (!isUploading && uploadQueue.length > 0)
uploadFiles(uploadQueue.shift());
}
function handleDragOver(evt) {
evt.stopPropagation();
evt.preventDefault();
//防止多次DOM操作
if (!isDragOver) {
console.log("Over");
$('#drag_area').addClass('wf_active').removeClass('wf_normal');
var classList = document.getElementById("drag_area").classList;
classList.add("wf_active");
classList.remove("wf_normal");
isDragOver = true;
}
}
function handleDragLeave(evt) {
console.log("DragLeave");
evt.stopPropagation();
evt.preventDefault();
isDragOver = false;
$('#drag_area').addClass('wf_normal').removeClass('wf_active');
var classList = document.getElementById("drag_area").classList;
classList.add("wf_normal");
classList.remove("wf_active");
}
function uploadFiles(file) {
//正在上传
isUploading = true;
@ -146,23 +142,21 @@ try {
XHR.open("POST", config.url);
XHR.send(fd);
XHR.onreadystatechange = function () {
//只要上传完成不管成功失败
if (XHR.readyState == 4) {
console.log("onreadystatechange ", XHR.status, +new Date());
if (XHR.status == 200) {
uploadSuccess(currUploadfile, {}, XHR.status)
uploadSuccess(currUploadfile, {}, XHR.status);
} else {
uploadError()
uploadError();
}
//进行下一个上传
nextUpload()
nextUpload();
}
};
}
};
}
//请求完成,无论失败或成功
@ -176,7 +170,7 @@ try {
//在请求发送或接收数据期间,在服务器指定的时间间隔触发。
function progress(evt) {
uploadProgress(currUploadfile, evt.loaded || evt.position, evt.total)
uploadProgress(currUploadfile, evt.loaded || evt.position, evt.total);
}
//在请求被取消时触发,例如,在调用 abort() 方法时。
@ -188,7 +182,7 @@ try {
function error(evt) {
//终止ajax请求
XHR.abort();
uploadError()
uploadError();
nextUpload();
}
@ -204,19 +198,22 @@ try {
uploadFiles(uploadQueue.shift());
} else {
//米有正在上传的了
currUploadfile = {}
currUploadfile = {};
}
}
//上传出错误了,比如断网,
function uploadError() {
//移除全局变量中的,上传出错的
removeFileFromFilesUpload(filesUpload, currUploadfile.id)
removeFileFromFilesUpload(filesUpload, currUploadfile.id);
var file = currUploadfile;
$("#handle_button_" + file.id).replaceWith("<dd>" + jsonLang.t8 + "</dd>")
var dd = document.createElement("dd")
dd.innerHTML = jsonLang.t8
document
.getElementById("handle_button_" + file.id)
.replaceWith(dd);
}
//对外部注册的函数
var HTML5Funs = {
/**
@ -224,25 +221,21 @@ try {
* @param string fid 文件的Id
*/
cancelUpload: function (fid) {
var filesUploadKey = -1;
var uploadQueueKey = -1;
//从全局中删除文件
removeFileFromFilesUpload(filesUpload, fid)
removeFileFromFilesUpload(filesUpload, fid);
//如果是正在上传的,AJAX取消
if (currUploadfile.id == fid) {
XHR.abort();
} else {
//从上传队列中移除
removeFileFromFilesUpload(uploadQueue, fid)
}
}
removeFileFromFilesUpload(uploadQueue, fid);
}
},
};
window.HTML5Funs = HTML5Funs;
})("FormData" in window && "ondrop" in document.body)
})("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);

@ -11,24 +11,43 @@ var jsonZh;//中文
* 语言包
*/
jsonEn = {
t0: "WiFi Upload", t1: "Documents", t2: "Name", t3: "Size", t4: "Operation",
t5: "Supported Format:TXT、EPUB、UMD", t6: "Drag here and Drop to upload",
t0: "WiFi Upload",
t1: "Documents",
t2: "Name",
t3: "Size",
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"
t8: "Failed",
t9: "Cancel",
t10: "Done",
t11: "One of books is over 500MB",
t12: "Bad file mode",
t13: "File already exists",
t14: "Canceled",
};
jsonZh = {
t0: "传书", t1: "文档", t2: "名字", t3: "大小", t4: "操作",
t5: "支持类型:TXT、EPUB、UMD", t6: "拖至此处上传",
t0: "传书",
t1: "文档",
t2: "名字",
t3: "大小",
t4: "操作",
t5: "支持类型:TXT、EPUB、UMD",
t6: "拖至此处上传",
t7: "你选择了[txt]个文件,我们只能上传其中的[txt]。\n请选择 TXT、EPUB、UMD 类型的文件,文件的名字不能重复。",
t8: "失败", t9: "取消", t10: "完成", t11: "上传文件不能大于500MB", t12: "无效的文件格式",
t13: "文件已存在", t14: "已取消"
t8: "失败",
t9: "取消",
t10: "完成",
t11: "上传文件不能大于500MB",
t12: "无效的文件格式",
t13: "文件已存在",
t14: "已取消",
};
if (navigator.language) {
language = navigator.language;
}
else {
} else {
language = navigator.browserLanguage;
}
language = language.toLowerCase(language);
@ -38,10 +57,10 @@ language = language.toLowerCase(language);
*/
function langSwich(s) {
switch (s) {
case 'en-us':
case "en-us":
_strReplace(jsonEn);
break;
case 'zh-cn':
case "zh-cn":
_strReplace(jsonZh);
break;
default:
@ -52,7 +71,10 @@ function langSwich(s) {
function _strReplace(d) {
window.jsonLang = d; //全局变量在其他js文件中会遇到,勿删。
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,18 +84,19 @@ function _strReplace(d) {
* replace 待插入的文本 支持变量或数组
*/
var isArray = function (obj) {
return Object.prototype.toString.call(obj) === '[object Array]';
}
return Object.prototype.toString.call(obj) === "[object Array]";
};
function stringReplace(content, replace) {
var str = content;
if (!str) { return null; }
if (!str) {
return null;
}
if (isArray(replace)) {
strs = str.split("[txt]");
count = strs.length - 1;
var string = '';
var string = "";
for (var i = 0; i < count; i++) {
string = string + strs[i] + replace[i];
}
@ -84,6 +107,5 @@ function stringReplace(content, replace) {
return string;
}
//执行
langSwich(language);

@ -116,8 +116,9 @@ function uploadError(file, errorCode, message) {
removeFileFromFilesUpload(filesUpload, file.id)
errorMessage = jsonLang.t8;
$("#handle_button_" + file.id).replaceWith("<dd>" + errorMessage + "</dd>")
var dd = document.createElement("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?)
final override fun onCreateOptionsMenu(menu: Menu?): Boolean {
return menu?.let {
final override fun onCreateOptionsMenu(menu: Menu): Boolean {
return menu.let {
val bool = onCompatCreateOptionsMenu(it)
it.applyTint(this, toolBarTheme)
bool
} ?: super.onCreateOptionsMenu(menu)
}
}
override fun onMenuOpened(featureId: Int, menu: Menu): Boolean {

@ -106,6 +106,9 @@ object AppConst {
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"
}

@ -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 exportUseReplace = "exportUseReplace"
const val useZhLayout = "useZhLayout"
const val fullScreenGesturesSupport = "fullScreenGesturesSupport"
const val brightness = "brightness"
const val nightBrightness = "nightBrightness"
const val expandTextMenu = "expandTextMenu"

@ -16,8 +16,11 @@ interface TxtTocRuleDao {
@get:Query("select * from txtTocRules where enable = 1 order by serialNumber")
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")
val lastOrderNum: Int
val maxOrder: Int
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insert(vararg rule: TxtTocRule)

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

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

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

@ -1,6 +1,7 @@
package io.legado.app.data.entities
import androidx.room.Entity
import androidx.room.Ignore
import androidx.room.PrimaryKey
@ -12,4 +13,9 @@ data class TxtTocRule(
var rule: String = "",
var serialNumber: Int = -1,
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 fullScreenGesturesSupport: Boolean
get() = appCtx.getPrefBoolean(PreferKey.fullScreenGesturesSupport, false)
val doublePageHorizontal: Boolean
get() = appCtx.getPrefBoolean(PreferKey.doublePageHorizontal, true)

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

@ -15,7 +15,7 @@ object DirectLinkUpload {
private const val downloadUrlRuleKey = "directLinkDownloadUrlRule"
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()
if (url.isNullOrBlank()) {
throw NoStackTraceException("上传url未配置")

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

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

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

@ -3,6 +3,7 @@ package io.legado.app.help.http
import io.legado.app.constant.AppConst
import io.legado.app.help.AppConfig
import io.legado.app.utils.EncodingDetect
import io.legado.app.utils.GSON
import io.legado.app.utils.UTF8BOMFighter
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.suspendCancellableCoroutine
@ -10,7 +11,9 @@ import kotlinx.coroutines.withContext
import okhttp3.*
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.RequestBody.Companion.asRequestBody
import okhttp3.RequestBody.Companion.toRequestBody
import java.io.File
import java.io.IOException
import java.nio.charset.Charset
import kotlin.coroutines.resume
@ -152,9 +155,22 @@ fun Request.Builder.postMultipart(type: String?, form: Map<String, Any>) {
when (val value = it.value) {
is Map<*, *> -> {
val fileName = value["fileName"] as String
val file = value["file"] as ByteArray
val file = value["file"]
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)
}
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 okhttp3.*
import java.io.IOException
import timber.log.Timber
class CronetInterceptor(private val cookieJar: CookieJar?) : Interceptor {
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
val original: Request = chain.request()
//Cronet未初始化
@ -29,20 +28,18 @@ class CronetInterceptor(private val cookieJar: CookieJar?) : Interceptor {
response
} ?: chain.proceed(original)
} catch (e: Exception) {
//不能抛出错误,抛出错误会导致应用崩溃
//遇到Cronet处理有问题时的情况,如证书过期等等,回退到okhttp处理
if (e.message.toString().contains("ERR_CERT_", true)
|| e.message.toString().contains("ERR_SSL_", true)
if (!e.message.toString().contains("ERR_CERT_", true)
&& !e.message.toString().contains("ERR_SSL_", true)
) {
chain.proceed(original)
} else {
throw e
Timber.e(e)
}
chain.proceed(original)
}
}
@Throws(IOException::class)
private fun proceedWithCronet(request: Request, call: Call): Response? {
val callback = CronetRequestCallback(request, call)
buildRequest(request, callback)?.let {

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

@ -4,6 +4,7 @@ import android.content.Context
import android.net.Uri
import androidx.documentfile.provider.DocumentFile
import io.legado.app.R
import io.legado.app.constant.AppLog
import io.legado.app.constant.PreferKey
import io.legado.app.data.appDb
import io.legado.app.help.DefaultData
@ -15,6 +16,7 @@ import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.withContext
import splitties.init.appCtx
import java.io.File
import java.io.FileOutputStream
import java.util.concurrent.TimeUnit
@ -51,7 +53,8 @@ object Backup {
Coroutine.async {
backup(context, context.getPrefString(PreferKey.backupPath) ?: "", true)
}.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) {
if (list.isNotEmpty()) {
val json = GSON.toJson(list)
FileUtils.createFileIfNotExist(path + File.separator + fileName).writeText(json)
val file = FileUtils.createFileIfNotExist(path + File.separator + fileName)
FileOutputStream(file).use {
GSON.writeToOutputStream(it, list)
}
}
}

@ -52,7 +52,6 @@ object ReadBook : CoroutineScope by MainScope() {
durChapterIndex = book.durChapterIndex
durChapterPos = book.durChapterPos
isLocalBook = book.origin == BookType.local
chapterSize = book.totalChapterNum
clearTextChapter()
callBack?.upMenuView()
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) {
if (book.origin == BookType.local) {
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) {
url(urlNoQuery)
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.BitmapFactory
import android.net.Uri
import android.text.TextUtils
import io.legado.app.data.entities.Book
import io.legado.app.data.entities.BookChapter
import io.legado.app.help.BookHelp
import io.legado.app.utils.FileUtils
import io.legado.app.utils.HtmlFormatter
import io.legado.app.utils.MD5Utils
import io.legado.app.utils.externalFiles
import io.legado.app.utils.*
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 org.jsoup.Jsoup
import org.jsoup.nodes.Element
import org.jsoup.select.Elements
import splitties.init.appCtx
import timber.log.Timber
import java.io.File
@ -28,10 +30,29 @@ class EpubFile(var book: Book) {
companion object {
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
private fun getEFile(book: Book): EpubFile {
BookHelp.getEpubFile(book)
getFile(book)
if (eFile == null || eFile?.book?.bookUrl != book.bookUrl) {
eFile = EpubFile(book)
//对于Epub文件默认不启用替换
@ -105,12 +126,9 @@ class EpubFile(var book: Book) {
/*重写epub文件解析代码,直接读出压缩包文件生成Resources给epublib,这样的好处是可以逐一修改某些文件的格式错误*/
private fun readEpub(): EpubBook? {
try {
val file = BookHelp.getEpubFile(book)
val file = getFile(book)
//通过懒加载读取epub
return EpubReader().readEpubLazy(ZipFile(file), "utf-8")
} catch (e: Exception) {
Timber.e(e)
}
@ -119,26 +137,45 @@ class EpubFile(var book: Book) {
private fun getContent(chapter: BookChapter): String? {
/*获取当前章节文本*/
return getChildChapter(chapter, chapter.url)
}
private fun getChildChapter(chapter: BookChapter, href: String): String? {
epubBook?.let {
val body = Jsoup.parse(String(it.resources.getByHref(href).data, mCharset)).body()
if (chapter.url == href) {
epubBook?.let { epubBook ->
val nextUrl = chapter.getVariable("nextUrl")
val startFragmentId = chapter.startFragmentId
val endFragmentId = chapter.endFragmentId
val elements = Elements()
var isChapter = false
/*一些书籍依靠href索引的resource会包含多个章节,需要依靠fragmentId来截取到当前章节的内容*/
/*注:这里较大增加了内容加载的时间,所以首次获取内容后可存储到本地cache,减少重复加载*/
if (!startFragmentId.isNullOrBlank())
for (res in epubBook.contents) {
if (chapter.url.substringBeforeLast("#") == res.href) {
elements.add(getBody(res, startFragmentId, endFragmentId))
isChapter = true
} else if (isChapter) {
if (nextUrl.isNullOrBlank() || res.href == nextUrl.substringBeforeLast("#")) {
break
}
elements.add(getBody(res, startFragmentId, endFragmentId))
}
}
var html = elements.outerHtml()
val tag = Book.rubyTag
if (book.getDelTag(tag)) {
html = html.replace("<ruby>\\s?([\\u4e00-\\u9fa5])\\s?.*?</ruby>".toRegex(), "$1")
}
return HtmlFormatter.formatKeepImg(html)
}
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)
}
if (!endFragmentId.isNullOrBlank() && endFragmentId != startFragmentId) {
body.getElementById(endFragmentId)?.nextElementSiblings()?.remove()
}
/*选择去除正文中的H标签,部分书籍标题与阅读标题重复待优化*/
var tag = Book.hTag
val tag = Book.hTag
if (book.getDelTag(tag)) {
body.getElementsByTag("h1").remove()
body.getElementsByTag("h2").remove()
@ -149,24 +186,10 @@ class EpubFile(var book: Book) {
//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()
if (book.getDelTag(tag)) {
html = html.replace("<ruby>\\s?([\\u4e00-\\u9fa5])\\s?.*?</ruby>".toRegex(), "$1")
}
return HtmlFormatter.formatKeepImg(html)
}
return null
val children = body.children()
children.select("script").remove()
children.select("style").remove()
return body
}
private fun getImage(href: String): InputStream? {
@ -198,7 +221,14 @@ class EpubFile(var book: Book) {
private fun getChapterList(): ArrayList<BookChapter> {
val chapterList = ArrayList<BookChapter>()
epubBook?.tableOfContents?.allUniqueResources?.forEachIndexed { index, resource ->
epubBook?.let { eBook ->
val refs = eBook.tableOfContents.tocReferences
if (refs == null || refs.isEmpty()) {
val spineReferences = eBook.spine.spineReferences
var i = 0
val size = spineReferences.size
while (i < size) {
val resource = spineReferences[i].resource
var title = resource.title
if (TextUtils.isEmpty(title)) {
try {
@ -209,23 +239,98 @@ class EpubFile(var book: Book) {
title = elements[0].text()
}
} catch (e: IOException) {
Timber.e(e)
e.printStackTrace()
}
}
val chapter = BookChapter()
chapter.index = index
chapter.index = i
chapter.bookUrl = book.bookUrl
chapter.url = resource.href
if (index == 0 && title.isEmpty()) {
if (i == 0 && title.isEmpty()) {
chapter.title = "封面"
} else {
chapter.title = title
}
chapterList.add(chapter)
i++
}
} else {
parseFirstPage(chapterList, refs)
parseMenu(chapterList, refs, 0)
for (i in chapterList.indices) {
chapterList[i].index = i
}
}
}
book.latestChapterTitle = chapterList.lastOrNull()?.title
book.totalChapterNum = chapterList.size
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
}
fun getContext(book: Book, chapter: BookChapter): String? {
fun getContent(book: Book, chapter: BookChapter): String? {
return when {
book.isEpub() -> {
EpubFile.getContent(book, chapter)
@ -151,11 +151,10 @@ object LocalBook {
bookFile.delete()
}
if (book.isEpub()) {
val bookFile = BookHelp.getEpubFile(book).parentFile
val bookFile = EpubFile.getFile(book).parentFile
if (bookFile != null && bookFile.exists()) {
FileUtils.delete(bookFile, true)
}
}
if (deleteOriginal) {

@ -56,7 +56,12 @@ class SearchModel(private val scope: CoroutineScope, private val callBack: CallB
if (searchGroup.isBlank()) {
bookSourceList.addAll(appDb.bookSourceDao.allEnabled)
} 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 {
searchPage++
@ -74,12 +79,13 @@ class SearchModel(private val scope: CoroutineScope, private val callBack: CallB
}
searchIndex++
val source = bookSourceList[searchIndex]
searchPool?.let { searchPool ->
val task = WebBook.searchBook(
scope,
source,
searchKey,
searchPage,
context = searchPool!!
context = searchPool
).timeout(30000L)
.onSuccess(searchPool) {
onSuccess(searchId, it)
@ -89,6 +95,7 @@ class SearchModel(private val scope: CoroutineScope, private val callBack: CallB
}
tasks.add(task)
}
}
@Synchronized
private fun onSuccess(searchId: Long, items: ArrayList<SearchBook>) {

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

@ -84,7 +84,7 @@ class ArrangeBookActivity : VMBaseActivity<ActivityArrangeBookBinding, ArrangeBo
adapter.revertSelection()
}
override fun onClickMainAction() {
override fun onClickSelectBarMainAction() {
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,11 +20,9 @@ class ChangeSourceAdapter(
context: Context,
val viewModel: ChangeSourceViewModel,
val callBack: CallBack
) :
DiffRecyclerAdapter<SearchBook, ItemChangeSourceBinding>(context) {
) : DiffRecyclerAdapter<SearchBook, ItemChangeSourceBinding>(context) {
override val diffItemCallback: DiffUtil.ItemCallback<SearchBook>
get() = object : DiffUtil.ItemCallback<SearchBook>() {
override val diffItemCallback = object : DiffUtil.ItemCallback<SearchBook>() {
override fun areItemsTheSame(oldItem: SearchBook, newItem: SearchBook): Boolean {
return oldItem.bookUrl == newItem.bookUrl
}

@ -116,7 +116,12 @@ class ChangeSourceViewModel(application: Application) : BaseViewModel(applicatio
if (searchGroup.isBlank()) {
bookSourceList.addAll(appDb.bookSourceDao.allEnabled)
} 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)
initSearchPool()

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

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

@ -72,7 +72,7 @@ class ImportBookActivity : VMBaseActivity<ActivityImportBookBinding, ImportBookV
override fun onCompatOptionsItemSelected(item: MenuItem): Boolean {
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_import_file_name -> alertImportFileName()
}
@ -98,7 +98,7 @@ class ImportBookActivity : VMBaseActivity<ActivityImportBookBinding, ImportBookV
}
@SuppressLint("NotifyDataSetChanged")
override fun onClickMainAction() {
override fun onClickSelectBarMainAction() {
viewModel.addToBookshelf(adapter.selectedUris) {
adapter.notifyDataSetChanged()
}
@ -133,7 +133,7 @@ class ImportBookActivity : VMBaseActivity<ActivityImportBookBinding, ImportBookV
when {
lastPath.isNullOrEmpty() -> {
binding.tvEmptyMsg.visible()
selectFolder.launch(null)
selectFolder.launch()
}
lastPath.isContentScheme() -> {
val rootUri = Uri.parse(lastPath)
@ -141,7 +141,7 @@ class ImportBookActivity : VMBaseActivity<ActivityImportBookBinding, ImportBookV
val doc = DocumentFile.fromTreeUri(this, rootUri)
if (doc == null || doc.name.isNullOrEmpty()) {
binding.tvEmptyMsg.visible()
selectFolder.launch(null)
selectFolder.launch()
} else {
subDocs.clear()
rootDoc = FileDoc.fromDocumentFile(doc)
@ -149,12 +149,12 @@ class ImportBookActivity : VMBaseActivity<ActivityImportBookBinding, ImportBookV
}
}.onFailure {
binding.tvEmptyMsg.visible()
selectFolder.launch(null)
selectFolder.launch()
}
}
Build.VERSION.SDK_INT > Build.VERSION_CODES.Q -> {
binding.tvEmptyMsg.visible()
selectFolder.launch(null)
selectFolder.launch()
}
else -> initRootPath(lastPath)
}
@ -172,7 +172,7 @@ class ImportBookActivity : VMBaseActivity<ActivityImportBookBinding, ImportBookV
upPath()
}.onFailure {
binding.tvEmptyMsg.visible()
selectFolder.launch(null)
selectFolder.launch()
}
}
.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.BookProgress
import io.legado.app.data.entities.BookSource
import io.legado.app.help.BookHelp
import io.legado.app.help.IntentData
import io.legado.app.help.ReadBookConfig
import io.legado.app.help.ReadTipConfig
@ -266,6 +267,9 @@ class ReadBookActivity : BaseReadBookActivity(),
TextDialog(ReadBook.curTextChapter?.getContent())
)
R.id.menu_update_toc -> ReadBook.book?.let {
if (it.isEpub()) {
BookHelp.clearCache(it)
}
loadChapterList(it)
}
R.id.menu_enable_replace -> ReadBook.book?.let {
@ -416,6 +420,12 @@ class ReadBookActivity : BaseReadBookActivity(),
autoPageStop()
return true
}
if (getPrefBoolean("disableReturnKey")) {
if (menuLayoutIsVisible) {
finish()
}
return true
}
}
}
}

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

@ -117,7 +117,7 @@ class BgTextConfigDialog : BaseDialogFragment(R.layout.dialog_read_bg_text) {
ivBg.setImageResource(R.drawable.ic_image)
ivBg.setColorFilter(primaryTextColor)
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) {
execute {
if (id == null) {
id = arguments?.getLong("id")
val httpTTS = id?.let {
return@let appDb.httpTTSDao.get(it)
val argumentId = arguments?.getLong("id")
if (argumentId != null && argumentId != 0L) {
id = argumentId
return@execute appDb.httpTTSDao.get(argumentId)
}
return@execute httpTTS
}
return@execute null
}.onSuccess {

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

@ -64,7 +64,9 @@ class ReadAloudDialog : BaseDialogFragment(R.layout.dialog_read_aloud) {
ivStop.setColorFilter(textColor)
ivTimer.setColorFilter(textColor)
tvTimer.setTextColor(textColor)
ivTtsSpeechReduce.setColorFilter(textColor)
tvTtsSpeed.setTextColor(textColor)
ivTtsSpeechAdd.setColorFilter(textColor)
ivCatalog.setColorFilter(textColor)
tvCatalog.setTextColor(textColor)
ivMainMenu.setColorFilter(textColor)
@ -112,6 +114,16 @@ class ReadAloudDialog : BaseDialogFragment(R.layout.dialog_read_aloud) {
seekTtsSpeechRate.isEnabled = !isChecked
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.setOnSeekBarChangeListener(object : SeekBarChangeListener {

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

@ -317,7 +317,7 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
}
val textPage = relativePage(relativePos)
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()) {
if (textChar.isTouch(x)) {
touched.invoke(

@ -102,20 +102,18 @@ class ReadView(context: Context, attrs: AttributeSet) :
setWillNotDraw(false)
upPageAnim()
}
setRect9x()
}
fun setRect9x() {
val edge = if (AppConfig.fullScreenGesturesSupport) 200f else 0f
tlRect.set(0f + edge, 0f, width * 0.33f, height * 0.33f)
tlRect.set(0f, 0f, width * 0.33f, 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)
mlRect.set(0f + edge, height * 0.33f, width * 0.33f, height * 0.66f)
trRect.set(width * 0.36f, 0f, width.toFloat(), height * 0.33f)
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)
mrRect.set(width * 0.66f, height * 0.33f, width - 0f - edge, height * 0.66f)
blRect.set(0f + edge, height * 0.66f, width * 0.33f, height - 10f - edge)
bcRect.set(width * 0.33f, height * 0.66f, width * 0.66f, height - 0f - edge)
brRect.set(width * 0.66f, height * 0.66f, width - 0f - edge, height - 0f - edge)
mrRect.set(width * 0.66f, height * 0.33f, width.toFloat(), height * 0.66f)
blRect.set(0f, height * 0.66f, width * 0.33f, height.toFloat())
bcRect.set(width * 0.33f, height * 0.66f, width * 0.66f, height.toFloat())
brRect.set(width * 0.66f, height * 0.66f, width.toFloat(), height.toFloat())
}
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)
if (!pressDown) return true
pressDown = false
@ -220,6 +218,17 @@ class ReadView(context: Context, attrs: AttributeSet) :
}
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
}

@ -17,6 +17,8 @@ data class TextLine(
) {
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) {
lineTop = ChapterProvider.paddingTop + durY
@ -38,7 +40,10 @@ data class TextLine(
return textChars.size
}
fun isTouch(y: Float, relativeOffset: Float): Boolean {
return y > lineTop + relativeOffset && y < lineBottom + relativeOffset
fun isTouch(x: Float, y: Float, relativeOffset: Float): Boolean {
return y > lineTop + relativeOffset
&& y < lineBottom + relativeOffset
&& x >= lineStart
&& x <= lineEnd
}
}

@ -591,7 +591,7 @@ object ChapterProvider {
* 更新绘制尺寸
*/
fun upLayout() {
doublePage = viewWidth > viewHeight
doublePage = (viewWidth > viewHeight || appCtx.isPad)
&& ReadBook.pageAnim() != 3
&& AppConfig.doublePageHorizontal
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.ui.qrcode.QrCodeResult
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.showDialogFragment
import io.legado.app.utils.toastOnUi
@ -137,7 +138,7 @@ class BookSourceDebugActivity : VMBaseActivity<ActivitySourceDebugBinding, BookS
override fun onCompatOptionsItemSelected(item: MenuItem): Boolean {
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_book_src -> showDialogFragment(TextDialog(viewModel.bookSrc))
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_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_qr -> shareWithQr(
GSON.toJson(getSource()),

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

@ -19,7 +19,7 @@ import io.legado.app.lib.theme.backgroundColor
import io.legado.app.model.Debug
import io.legado.app.ui.login.SourceLoginActivity
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.invisible
import io.legado.app.utils.startActivity
@ -28,7 +28,7 @@ import io.legado.app.utils.visible
class BookSourceAdapter(context: Context, val callBack: CallBack) :
RecyclerAdapter<BookSource, ItemBookSourceBinding>(context),
Callback {
ItemTouchCallback.Callback {
private val selected = linkedSetOf<BookSource>()
@ -110,7 +110,6 @@ class BookSourceAdapter(context: Context, val callBack: CallBack) :
override fun registerListener(holder: ItemViewHolder, binding: ItemBookSourceBinding) {
binding.apply {
swtEnabled.setOnCheckedChangeListener { view, checked ->
if (view.isPressed) {
getItem(holder.layoutPosition)?.let {
if (view.isPressed) {
it.enabled = checked
@ -118,9 +117,7 @@ class BookSourceAdapter(context: Context, val callBack: CallBack) :
}
}
}
}
cbBookSource.setOnCheckedChangeListener { view, checked ->
if (view.isPressed) {
getItem(holder.layoutPosition)?.let {
if (view.isPressed) {
if (checked) {
@ -132,7 +129,6 @@ class BookSourceAdapter(context: Context, val callBack: CallBack) :
}
}
}
}
ivEdit.setOnClickListener {
getItem(holder.layoutPosition)?.let {
callBack.edit(it)

@ -1,15 +1,14 @@
package io.legado.app.ui.book.source.manage
import android.app.Application
import android.content.Intent
import android.text.TextUtils
import androidx.core.content.FileProvider
import io.legado.app.base.BaseViewModel
import io.legado.app.constant.AppConst
import io.legado.app.constant.AppPattern
import io.legado.app.data.appDb
import io.legado.app.data.entities.BookSource
import io.legado.app.utils.*
import java.io.File
import java.io.FileOutputStream
class BookSourceViewModel(application: Application) : BaseViewModel(application) {
@ -33,8 +32,8 @@ class BookSourceViewModel(application: Application) : BaseViewModel(application)
}
}
fun del(bookSource: BookSource) {
execute { appDb.bookSourceDao.delete(bookSource) }
fun del(vararg sources: BookSource) {
execute { appDb.bookSourceDao.delete(*sources) }
}
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 {
appDb.bookSourceDao.delete(*sources.toTypedArray())
val path = "${context.filesDir}/shareBookSource.json"
FileUtils.delete(path)
val file = FileUtils.createFileWithReplace(path)
FileOutputStream(file).use {
GSON.writeToOutputStream(it, sources)
}
}
fun shareSelection(sources: List<BookSource>, success: ((intent: Intent) -> Unit)) {
execute {
val tmpSharePath = "${context.filesDir}/shareBookSource.json"
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
file
}.onSuccess {
success.invoke(it)
}.onError {

@ -2,6 +2,7 @@ package io.legado.app.ui.book.toc
import android.content.Context
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import io.legado.app.R
import io.legado.app.base.adapter.ItemViewHolder
import io.legado.app.base.adapter.RecyclerAdapter
@ -16,6 +17,22 @@ class ChapterListAdapter(context: Context, val callback: Callback) :
RecyclerAdapter<BookChapter, ItemChapterListBinding>(context) {
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 {
return ItemChapterListBinding.inflate(inflater, parent, false)

@ -27,18 +27,16 @@ import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlin.math.min
class ChapterListFragment : VMBaseFragment<TocViewModel>(R.layout.fragment_chapter_list),
ChapterListAdapter.Callback,
TocViewModel.ChapterListCallBack {
override val viewModel by activityViewModels<TocViewModel>()
private val binding by viewBinding(FragmentChapterListBinding::bind)
private val mLayoutManager by lazy { UpLinearLayoutManager(requireContext()) }
private val adapter by lazy { ChapterListAdapter(requireContext(), this) }
private var durChapterIndex = 0
private lateinit var mLayoutManager: UpLinearLayoutManager
private var tocFlowJob: Job? = null
private var scrollToDurChapter = false
override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) = binding.run {
viewModel.chapterCallBack = this@ChapterListFragment
@ -56,14 +54,15 @@ class ChapterListFragment : VMBaseFragment<TocViewModel>(R.layout.fragment_chapt
}
private fun initRecyclerView() {
mLayoutManager = UpLinearLayoutManager(requireContext())
binding.recyclerView.layoutManager = mLayoutManager
binding.recyclerView.addItemDecoration(VerticalDivider(requireContext()))
binding.recyclerView.adapter = adapter
}
private fun initView() = binding.run {
ivChapterTop.setOnClickListener { mLayoutManager.scrollToPositionWithOffset(0, 0) }
ivChapterTop.setOnClickListener {
mLayoutManager.scrollToPositionWithOffset(0, 0)
}
ivChapterBottom.setOnClickListener {
if (adapter.itemCount > 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)
else -> appDb.bookChapterDao.flowSearch(viewModel.bookUrl, searchKey)
}.collect {
adapter.setItems(it)
if (searchKey.isNullOrBlank() && !scrollToDurChapter) {
if (!(searchKey.isNullOrBlank() && it.isEmpty())) {
adapter.setItems(it, adapter.diffCallBack)
if (searchKey.isNullOrBlank() && mLayoutManager.findFirstVisibleItemPosition() < 0) {
mLayoutManager.scrollToPositionWithOffset(durChapterIndex, 0)
scrollToDurChapter = true
}
}
}
}
@ -125,7 +125,7 @@ class ChapterListFragment : VMBaseFragment<TocViewModel>(R.layout.fragment_chapt
get() = viewModel.bookData.value?.isLocalBook() == true
override fun durChapterIndex(): Int {
return min(durChapterIndex, adapter.itemCount)
return durChapterIndex
}
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.lib.theme.accentColor
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.gone
import io.legado.app.utils.showDialogFragment
import io.legado.app.utils.viewbindingdelegate.viewBinding
import io.legado.app.utils.visible
@ -80,6 +82,7 @@ class TocActivity : VMBaseActivity<ActivityChapterListBinding, TocViewModel>() {
putExtra("chapterPos", it.durChapterPos)
})
}
R.id.menu_log -> showDialogFragment<AppLogDialog>()
}
return super.onCompatOptionsItemSelected(item)
}

@ -7,7 +7,7 @@ import androidx.activity.result.contract.ActivityResultContract
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)
.putExtra("bookUrl", input)
}

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

@ -37,6 +37,9 @@ class OtherConfigFragment : BasePreferenceFragment(),
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
putPrefBoolean(PreferKey.processText, isProcessTextEnabled())
addPreferencesFromResource(R.xml.pref_config_other)
if (AppConfig.isGooglePlay) {
preferenceScreen.removePreferenceRecursively("Cronet")
}
upPreferenceSummary(PreferKey.userAgent, AppConfig.userAgent)
upPreferenceSummary(PreferKey.preDownloadNum, AppConfig.preDownloadNum.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.utils.getJsonArray
import io.legado.app.utils.isContentScheme
import io.legado.app.utils.launch
import io.legado.app.utils.toastOnUi
import io.legado.app.utils.viewbindingdelegate.viewBinding
import java.io.File
@ -79,7 +80,17 @@ class HandleFileActivity :
alert(title) {
items(selectList) { _, item, _ ->
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))
10 -> checkPermissions {
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 file = intent.getStringExtra("fileKey")?.let {
IntentData.get<ByteArray>(it)
IntentData.get<Any>(it)
}
val contentType = intent.getStringExtra("contentType")
if (fileName != null && file != null && contentType != null) {

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

@ -8,6 +8,7 @@ import io.legado.app.base.BaseViewModel
import io.legado.app.constant.AppLog
import io.legado.app.help.DirectLinkUpload
import io.legado.app.utils.FileUtils
import io.legado.app.utils.GSON
import io.legado.app.utils.isContentScheme
import io.legado.app.utils.writeBytes
@ -20,7 +21,7 @@ class HandleFileViewModel(application: Application) : BaseViewModel(application)
fun upload(
fileName: String,
file: ByteArray,
file: Any,
contentType: String,
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 {
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()) {
val doc = DocumentFile.fromTreeUri(context, uri)!!
doc.findFile(fileName)?.delete()
val newDoc = doc.createFile("", fileName)
newDoc!!.writeBytes(context, data)
newDoc!!.writeBytes(context, bytes)
newDoc.uri
} else {
val file = File(uri.path!!)
val newFile = FileUtils.createFileIfNotExist(file, fileName)
newFile.writeBytes(data)
newFile.writeBytes(bytes)
Uri.fromFile(newFile)
}
}.onError {

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

@ -1,6 +1,7 @@
package io.legado.app.ui.main.bookshelf
import android.app.Application
import com.google.gson.stream.JsonWriter
import io.legado.app.R
import io.legado.app.base.BaseViewModel
import io.legado.app.data.appDb
@ -14,6 +15,9 @@ import io.legado.app.model.webBook.WebBook
import io.legado.app.utils.*
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.isActive
import java.io.File
import java.io.FileOutputStream
import java.io.OutputStreamWriter
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 {
val exportList = arrayListOf<Map<String, String?>>()
books?.forEach {
books?.let {
val path = "${context.filesDir}/books.json"
FileUtils.delete(path)
val file = FileUtils.createFileWithReplace(path)
@Suppress("BlockingMethodInNonBlockingContext")
FileOutputStream(file).use { out ->
val writer = JsonWriter(OutputStreamWriter(out, "UTF-8"))
writer.setIndent(" ")
writer.beginArray()
books.forEach {
val bookMap = hashMapOf<String, String?>()
bookMap["name"] = it.name
bookMap["author"] = it.author
bookMap["intro"] = it.getDisplayIntro()
exportList.add(bookMap)
GSON.toJson(bookMap, bookMap::class.java, writer)
}
GSON.toJson(exportList)
writer.endArray()
writer.close()
}
file
} ?: throw NoStackTraceException("书籍不能为空")
}.onSuccess {
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 fragmentMap = hashMapOf<Long, BooksFragment>()
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>
get() {
val fragment = fragmentMap[selectedGroup.groupId]
val fragment = fragmentMap[groupId]
return fragment?.getBooks() ?: emptyList()
}
@ -60,8 +60,8 @@ class BookshelfFragment1 : BaseBookshelfFragment(R.layout.fragment_bookshelf),
initBookGroupData()
}
private val selectedGroup: BookGroup
get() = bookGroups[tabLayout.selectedTabPosition]
private val selectedGroup: BookGroup?
get() = bookGroups.getOrNull(tabLayout.selectedTabPosition)
private fun initView() {
binding.viewPagerBookshelf.setEdgeEffectColor(primaryColor)
@ -113,8 +113,10 @@ class BookshelfFragment1 : BaseBookshelfFragment(R.layout.fragment_bookshelf),
}
override fun onTabReselected(tab: TabLayout.Tab) {
fragmentMap[selectedGroup.groupId]?.let {
toastOnUi("${selectedGroup.groupName}(${it.getBooksCount()})")
selectedGroup?.let { group ->
fragmentMap[group.groupId]?.let {
toastOnUi("${group.groupName}(${it.getBooksCount()})")
}
}
}
@ -125,7 +127,7 @@ class BookshelfFragment1 : BaseBookshelfFragment(R.layout.fragment_bookshelf),
}
override fun gotoTop() {
fragmentMap[selectedGroup.groupId]?.gotoTop()
fragmentMap[groupId]?.gotoTop()
}
private inner class TabFragmentPageAdapter(fm: FragmentManager) :

@ -11,8 +11,7 @@ abstract class BaseBooksAdapter<VH : RecyclerView.ViewHolder>(
val callBack: CallBack
) : RecyclerView.Adapter<VH>() {
val diffItemCallback: DiffUtil.ItemCallback<Book>
get() = object : DiffUtil.ItemCallback<Book>() {
val diffItemCallback = object : DiffUtil.ItemCallback<Book>() {
override fun areItemsTheSame(oldItem: Book, newItem: Book): Boolean {
return oldItem.name == newItem.name

@ -40,6 +40,7 @@ class ExploreFragment : VMBaseFragment<ExploreViewModel>(R.layout.fragment_explo
private val searchView: SearchView by lazy {
binding.titleBar.findViewById(R.id.search_view)
}
private val diffItemCallBack = ExploreDiffItemCallBack()
private val groups = linkedSetOf<String>()
private var exploreFlowJob: Job? = null
private var groupsMenu: SubMenu? = null
@ -127,7 +128,7 @@ class ExploreFragment : VMBaseFragment<ExploreViewModel>(R.layout.fragment_explo
}
exploreFlow.collect {
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.help.AppConfig
import io.legado.app.help.ThemeConfig
import io.legado.app.lib.dialogs.selector
import io.legado.app.lib.theme.primaryColor
import io.legado.app.service.WebService
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?) {
putPrefBoolean(PreferKey.webService, WebService.isRun)
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) {
findPreference<SwitchPreference>(PreferKey.webService)?.let {
it.isChecked = WebService.isRun
@ -138,5 +151,6 @@ class MyFragment : BaseFragment(R.layout.fragment_my_config) {
return super.onPreferenceTreeClick(preference)
}
}
}

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

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

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

@ -125,7 +125,7 @@ class RssSourceEditActivity :
}
}
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_share_str -> share(GSON.toJson(getRssSource()))
R.id.menu_share_qr -> shareWithQr(

@ -1,7 +1,6 @@
package io.legado.app.ui.rss.source.manage
import android.annotation.SuppressLint
import android.content.Intent
import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
@ -93,7 +92,7 @@ class RssSourceActivity : VMBaseActivity<ActivityRssSourceBinding, RssSourceView
initSearchView()
initGroupFlow()
upSourceFlow()
initViewEvent()
initSelectActionBar()
}
override fun onCompatCreateOptionsMenu(menu: Menu): Boolean {
@ -115,11 +114,8 @@ class RssSourceActivity : VMBaseActivity<ActivityRssSourceBinding, RssSourceView
allowExtensions = arrayOf("txt", "json")
}
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_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_help -> showHelp()
else -> if (item.groupId == R.id.source_group) {
@ -134,17 +130,17 @@ class RssSourceActivity : VMBaseActivity<ActivityRssSourceBinding, RssSourceView
when (item?.itemId) {
R.id.menu_enable_selection -> viewModel.enableSelection(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_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
}
@ -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() {
launch {
appDb.rssSourceDao.flowGroup().collect {
@ -207,20 +210,13 @@ class RssSourceActivity : VMBaseActivity<ActivityRssSourceBinding, RssSourceView
adapter.revertSelection()
}
override fun onClickMainAction() {
override fun onClickSelectBarMainAction() {
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() {
alert(titleResource = R.string.draw, messageResource = R.string.sure_del) {
okButton { viewModel.delSelection(adapter.selection) }
okButton { viewModel.del(*adapter.selection.toTypedArray()) }
noButton()
}
}

@ -36,8 +36,7 @@ class RssSourceAdapter(context: Context, val callBack: CallBack) :
return selection.sortedBy { it.customOrder }
}
val diffItemCallback: DiffUtil.ItemCallback<RssSource>
get() = object : DiffUtil.ItemCallback<RssSource>() {
val diffItemCallback = object : DiffUtil.ItemCallback<RssSource>() {
override fun areItemsTheSame(oldItem: RssSource, newItem: RssSource): Boolean {
return oldItem.sourceUrl == newItem.sourceUrl

@ -1,15 +1,13 @@
package io.legado.app.ui.rss.source.manage
import android.app.Application
import android.content.Intent
import android.text.TextUtils
import androidx.core.content.FileProvider
import io.legado.app.base.BaseViewModel
import io.legado.app.constant.AppConst
import io.legado.app.data.appDb
import io.legado.app.data.entities.RssSource
import io.legado.app.help.DefaultData
import io.legado.app.utils.*
import java.io.File
class RssSourceViewModel(application: Application) : BaseViewModel(application) {
@ -33,8 +31,8 @@ class RssSourceViewModel(application: Application) : BaseViewModel(application)
}
}
fun del(rssSource: RssSource) {
execute { appDb.rssSourceDao.delete(rssSource) }
fun del(vararg rssSource: RssSource) {
execute { appDb.rssSourceDao.delete(*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 {
appDb.rssSourceDao.delete(*sources.toTypedArray())
}
}
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)
val path = "${context.filesDir}/shareRssSource.json"
FileUtils.delete(path)
val file = FileUtils.createFileWithReplace(path)
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
file
}.onSuccess {
success.invoke(it)
}.onError {

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

@ -14,7 +14,7 @@ class EditTextPreference(context: Context, attrs: AttributeSet) :
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)
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)
val v = Preference.bindView<ImageView>(
context,

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

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

Loading…
Cancel
Save