This commit is contained in:
Horis 2023-04-14 22:14:04 +08:00
parent 3e0938a2c9
commit 8fa1056af1
23 changed files with 116 additions and 65 deletions

View File

@ -232,7 +232,7 @@ fun Book.isSameNameAuthor(other: Any?): Boolean {
fun Book.getExportFileName(suffix: String): String {
val jsStr = AppConfig.bookExportFileName
if (jsStr.isNullOrBlank()) {
return "${name} 作者:${getRealAuthor()}.$suffix"
return "$name 作者:${getRealAuthor()}.$suffix"
}
val bindings = SimpleBindings()
bindings["name"] = name

View File

@ -35,7 +35,15 @@ class OkHttpStreamFetcher(private val url: GlideUrl, private val options: Option
@Volatile
private var call: Call? = null
companion object {
val failUrl = hashSetOf<String>()
}
override fun loadData(priority: Priority, callback: DataFetcher.DataCallback<in InputStream>) {
if (failUrl.contains(url.toStringUrl())) {
callback.onLoadFailed(NoStackTraceException("跳过加载失败的图片"))
return
}
val loadOnlyWifi = options.get(OkHttpModelLoader.loadOnlyWifiOption) ?: false
if (loadOnlyWifi && !appCtx.isWifiConnect) {
callback.onLoadFailed(NoStackTraceException("只在wifi加载图片"))
@ -96,6 +104,7 @@ class OkHttpStreamFetcher(private val url: GlideUrl, private val options: Option
callback?.onDataReady(stream)
}
} else {
failUrl.add(url.toStringUrl())
callback?.onLoadFailed(HttpException(response.message, response.code))
}
}

View File

@ -22,13 +22,11 @@ data class Authorization(
return "$username:$password"
}
constructor(serverID: Long?): this("","") {
serverID ?: throw WebDavException("Unexpected server ID")
appDb.serverDao.get(serverID)?.getWebDavConfig()?.run {
data = Credentials.basic(username, password, charset)
} ?: throw WebDavException("Unexpected WebDav Authorization")
}
constructor(serverID: Long) : this(
appDb.serverDao.get(serverID)?.getWebDavConfig()
?: throw WebDavException("Unexpected WebDav Authorization")
)
constructor(webDavConfig: WebDavConfig): this(webDavConfig.username, webDavConfig.password)
constructor(webDavConfig: WebDavConfig) : this(webDavConfig.username, webDavConfig.password)
}

View File

@ -39,7 +39,7 @@ open class WebDav(
companion object {
fun fromPath(path: String): WebDav {
val id = AnalyzeUrl(path).serverID
val id = AnalyzeUrl(path).serverID ?: throw WebDavException("没有serverID")
val authorization = Authorization(id)
return WebDav(path, authorization)
}
@ -386,6 +386,16 @@ open class WebDav(
private fun checkResult(response: Response) {
if (!response.isSuccessful) {
val body = response.body?.string()
if (response.code == 401) {
val headers = response.headers("WWW-Authenticate")
val supportBasicAuth = headers.any {
it.startsWith("Basic", ignoreCase = true)
}
if (!supportBasicAuth) {
AppLog.put("服务器不支持BasicAuth认证")
}
}
if (response.message.isNotBlank() || body.isNullOrBlank()) {
throw WebDavException("${url}\n${response.code}:${response.message}")
}

View File

@ -14,10 +14,10 @@ class ServerConfigViewModel(application: Application): BaseViewModel(application
//mServer不为空可能是旋转屏幕界面重新创建,不用更新数据
if (mServer != null) return
execute {
if (id != null) {
mServer = appDb.serverDao.get(id)
mServer = if (id != null) {
appDb.serverDao.get(id)
} else {
mServer = Server()
Server()
}
}.onSuccess {
onSuccess.invoke()

View File

@ -98,6 +98,10 @@ class HttpServer(port: Int) : NanoHTTPD(port) {
)
} else {
try {
val data = returnData.data
if (data is List<*> && data.size > 3000) {
throw OutOfMemoryError()
}
newFixedLengthResponse(GSON.toJson(returnData))
} catch (e: OutOfMemoryError) {
val path = FileUtils.getPath(
@ -106,7 +110,7 @@ class HttpServer(port: Int) : NanoHTTPD(port) {
"bookSources.json"
)
val file = FileUtils.createFileIfNotExist(path)
BufferedWriter(FileWriter(file)).use {
BufferedWriter(FileWriter(file), 128 * 1024).use {
GSON.toJson(returnData, it)
}
val fis = FileInputStream(file)

View File

@ -4,7 +4,6 @@
"ComponentPublicInstance": true,
"ComputedRef": true,
"EffectScope": true,
"ElLoading": true,
"ElMessage": true,
"InjectionKey": true,
"PropType": true,

View File

@ -1,6 +1,6 @@
{
"compilerOptions": {
"types": ["element-plus/global", "@element-plus/icons-vue","@vueuse/shared", "vite/client"],
"types": ["@element-plus/icons-vue", "@vueuse/shared", "vite/client"],
"target": "ESNext",
"useDefineForClassFields": true,
"module": "ESNext",
@ -10,9 +10,9 @@
"skipLibCheck": true,
"noEmit": true,
"paths": {
"@/*":["./src/*"],
"@api":["./src/api"],
"@utils/*":["./src/utils/*"]
"@/*": ["./src/*"],
"@api": ["./src/api"],
"@utils/*": ["./src/utils/*"]
}
},
//"exclude": ["node_modules", "dist"],

View File

@ -21,7 +21,8 @@
"hotkeys-js": "^3.10.2",
"pinia": "^2.0.34",
"vue": "^3.2.47",
"vue-router": "^4.1.6"
"vue-router": "^4.1.6",
"vue3-virtual-scroll-list": "^0.2.1"
},
"devDependencies": {
"@vitejs/plugin-vue": "^4.1.0",
@ -36,4 +37,4 @@
"unplugin-vue-components": "^0.24.1",
"vite": "^4.2.1"
}
}
}

View File

@ -4,7 +4,7 @@ const SECOND = 1000;
const ajax = axios.create({
baseURL: import.meta.env.VITE_API || location.origin,
timeout: 5 * SECOND,
timeout: 120 * SECOND,
});
export default ajax;

View File

@ -1,10 +1,6 @@
@import './kbd.css';
@import './code.css';
::-webkit-scrollbar {
width: 0;
height: 0;
}
body {
padding: 0;
margin: 0;

View File

@ -5,7 +5,6 @@
export {}
declare global {
const EffectScope: typeof import('vue')['EffectScope']
const ElLoading: typeof import('element-plus/es')['ElLoading']
const ElMessage: typeof import('element-plus/es')['ElMessage']
const acceptHMRUpdate: typeof import('pinia')['acceptHMRUpdate']
const computed: typeof import('vue')['computed']

View File

@ -17,7 +17,6 @@ declare module '@vue/runtime-core' {
ElDialog: typeof import('element-plus/es')['ElDialog']
ElForm: typeof import('element-plus/es')['ElForm']
ElFormItem: typeof import('element-plus/es')['ElFormItem']
ElIcon: typeof import('element-plus/es')['ElIcon']
ElInput: typeof import('element-plus/es')['ElInput']
ElInputNumber: typeof import('element-plus/es')['ElInputNumber']
ElLink: typeof import('element-plus/es')['ElLink']
@ -36,6 +35,7 @@ declare module '@vue/runtime-core' {
RouterView: typeof import('vue-router')['RouterView']
SourceDebug: typeof import('./components/SourceDebug.vue')['default']
SourceHelp: typeof import('./components/SourceHelp.vue')['default']
SourceItem: typeof import('./components/SourceItem.vue')['default']
SourceJson: typeof import('./components/SourceJson.vue')['default']
SourceList: typeof import('./components/SourceList.vue')['default']
SourceTabForm: typeof import('./components/SourceTabForm.vue')['default']

View File

@ -4,7 +4,7 @@
<div
class="book"
v-for="book in props.books"
:key="book.noteUrl"
:key="book.bookUrl"
@click="handleClick(book)"
>
<div class="cover-img">
@ -24,7 +24,7 @@
</div>
<div class="tags" v-show="props.isSearch">
<el-tag
v-for="tag in book.kind.split(',').slice(0, 2)"
v-for="tag in book.kind?.split(',').slice(0, 2)"
:key="tag"
>
{{ tag }}

View File

@ -52,4 +52,8 @@ const isBookSource = computed(() => {
});
</script>
<style lang="scss" scoped></style>
<style lang="scss" scoped>
:deep(#debug-text) {
height: calc(100vh - 45px - 36px - 5px);
}
</style>

View File

@ -0,0 +1,21 @@
<template>
<el-checkbox
size="large"
border
:label="source"
:class="{ error: errorPushSources.includes(source) }"
@change="handleSourceClick(source)"
:key="source.bookSourceUrl"
>
{{ source.bookSourceName || source.sourceName }}
</el-checkbox>
</template>
<script setup>
const { source } = defineProps(['source'])
const store = useSourceStore()
const { errorPushSources } = storeToRefs(store)
const handleSourceClick = source => {
store.changeCurrentSource(source)
}
</script>

View File

@ -1,5 +1,6 @@
<template>
<el-input
id="source-json"
v-model="sourceString"
type="textarea"
placeholder="这里输出序列化的JSON数据,可直接导入'阅读'APP"
@ -33,8 +34,11 @@ watchEffect(async () => {
}
});
</script>
<style>
.el-input {
<style scoped>
:deep(.el-input) {
width: 100%;
}
:deep(#source-json) {
height: calc(100vh - 50px);
}
</style>

View File

@ -8,7 +8,7 @@
<div class="tool">
<el-button @click="importSourceFile" :icon="Folder"> 打开 </el-button>
<el-button
:disabled="sourceSelect.length === 0"
:disabled="sourcesFiltered.length === 0"
@click="outExport"
:icon="Download"
>
@ -29,35 +29,28 @@
>
</div>
<el-checkbox-group id="source-list" v-model="sourceSelect">
<el-checkbox
v-for="source in sourcesFiltered"
size="large"
border
:label="source"
:class="{ error: errorPushSources.includes(source) }"
@click="handleSourceClick(source)"
:key="source.bookSourceName"
>
{{ source.bookSourceName || source.sourceName }}
</el-checkbox>
<virtual-list
style="height: 100%; overflow-y: auto; overflow-x: hidden;"
:data-key="'bookSourceUrl'"
:data-sources="sourcesFiltered"
:data-component="SourceItem"
:estimate-size="45"
/>
</el-checkbox-group>
</template>
<script setup>
import { Folder, Delete, Download, Search } from "@element-plus/icons-vue";
import { isSourceContains } from "../utils/souce";
import VirtualList from 'vue3-virtual-scroll-list';
import SourceItem from "./SourceItem.vue";
const store = useSourceStore();
const sourceSelect = ref([]);
const searchKey = ref("");
const { sources, errorPushSources } = storeToRefs(store);
const { sources } = storeToRefs(store);
const isBookSource = computed(() => {
return /bookSource/.test(window.location.href);
});
const handleSourceClick = (source) => {
store.changeCurrentSource(source);
};
const deleteSelectSources = () => {
store.deleteSources(sourceSelect.value);
sourceSelect.value = [];
@ -104,7 +97,7 @@ const importSourceFile = () => {
};
const outExport = () => {
const exportFile = document.createElement("a");
let sources = store.sources,
let sources = sourceSelect.value.length === 0 ? sourcesFiltered.value : sourceSelect.value,
sourceType = isBookSource.value ? "BookSource" : "RssSource";
exportFile.download = `${sourceType}_${Date()
@ -122,15 +115,13 @@ const outExport = () => {
<style lang="scss" scoped>
.tool {
display: flex;
padding: 4px 0;
justify-content: space-between;
margin: 4px 0;
justify-content: center;
}
#source-list {
padding-top: 6px;
height: calc(100vh - 112px - 20px);
overflow-y: auto;
overflow-x: hidden;
margin-top: 6px;
height: calc(100vh - 112px - 7px);
:deep(.el-checkbox) {
margin-bottom: 4px;

View File

@ -69,7 +69,12 @@ const { currentSource } = storeToRefs(store);
<style lang="scss" scoped>
:deep(.el-tab-pane) {
height: calc(100vh - 40px);
height: calc(100vh - 55px);
padding-top: 15px;
padding-right: 5px;
overflow-y: auto;
}
:deep(.el-tabs__header) {
margin: 0;
}
</style>

View File

@ -29,4 +29,8 @@ const tabData = ref([
]);
</script>
<style lang="scss" scoped></style>
<style lang="scss" scoped>
:deep(.el-tabs__header) {
margin-bottom: 5px;
}
</style>

View File

@ -72,6 +72,11 @@ import { isInvaildSource } from "../utils/souce";
const store = useSourceStore();
const pull = () => {
const loadingMsg = ElMessage({
message: '加载中……',
showClose: true,
duration: 0
})
API.getSources().then(({ data }) => {
if (data.isSuccess) {
store.changeTabName("editList");
@ -86,7 +91,7 @@ const pull = () => {
type: "error",
});
}
});
}).finally(() => loadingMsg.close());
};
const push = () => {

View File

@ -552,11 +552,11 @@ id: "deleteUrl",
id: "enabledExplore",
type: "Boolean",
},
{
title: "启用段评",
id: "enabledReview",
type: "Boolean",
},
// {
// title: "启用段评",
// id: "enabledReview",
// type: "Boolean",
// },
{
title: "Cookie",
id: "enabledCookieJar",

View File

@ -30,6 +30,7 @@ if (/bookSource/i.test(location.href)) {
margin-left: 20px;
}
.right {
flex: 1;
width: 360px;
margin-right: 20px;
}