diff --git a/next/kmp/browser/src/commonMain/kotlin/org/dweb_browser/browser/web/model/BrowserViewModel.kt b/next/kmp/browser/src/commonMain/kotlin/org/dweb_browser/browser/web/model/BrowserViewModel.kt index a5af3efdf..4517ff450 100644 --- a/next/kmp/browser/src/commonMain/kotlin/org/dweb_browser/browser/web/model/BrowserViewModel.kt +++ b/next/kmp/browser/src/commonMain/kotlin/org/dweb_browser/browser/web/model/BrowserViewModel.kt @@ -46,6 +46,7 @@ import org.dweb_browser.helper.clamp import org.dweb_browser.helper.compose.compositionChainOf import org.dweb_browser.helper.encodeURIComponent import org.dweb_browser.helper.format +import org.dweb_browser.helper.humanTrim import org.dweb_browser.helper.isDwebDeepLink import org.dweb_browser.helper.isTrimEndSlashEqual import org.dweb_browser.helper.platform.toByteArray @@ -356,7 +357,7 @@ class BrowserViewModel( * 否:将 url 进行判断封装,符合条件后,判断当前界面是否是 BrowserWebPage,然后进行搜索操作 */ fun doIOSearchUrl(searchText: String) = lifecycleScope.launch { - val text = searchText.trim().trim('\u200B').trim() + val text = searchText.humanTrim() if (text.isDwebDeepLink()) { browserNMM.nativeFetch(text) return@launch diff --git a/next/kmp/browser/src/commonMain/kotlin/org/dweb_browser/browser/web/ui/BrowserSearchPanel.kt b/next/kmp/browser/src/commonMain/kotlin/org/dweb_browser/browser/web/ui/BrowserSearchPanel.kt index 65e8deb8a..f7f886c70 100644 --- a/next/kmp/browser/src/commonMain/kotlin/org/dweb_browser/browser/web/ui/BrowserSearchPanel.kt +++ b/next/kmp/browser/src/commonMain/kotlin/org/dweb_browser/browser/web/ui/BrowserSearchPanel.kt @@ -40,6 +40,7 @@ import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.input.VisualTransformation import kotlinx.coroutines.delay import org.dweb_browser.browser.web.model.BrowserViewModel @@ -129,7 +130,10 @@ class BrowserSearchPanel(val viewModel: BrowserViewModel) { lineLimits = TextFieldLineLimits.SingleLine, textStyle = LocalTextStyle.current.copy(color = searchFieldColors.focusedTextColor), cursorBrush = SolidColor(searchFieldColors.cursorColor), - keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search), + keyboardOptions = KeyboardOptions( + imeAction = ImeAction.Search, + keyboardType = KeyboardType.Uri, + ), onKeyboardAction = { focusManager.clearFocus() suggestionActions.firstOrNull()?.invoke() diff --git a/next/kmp/browser/src/commonMain/kotlin/org/dweb_browser/browser/web/ui/search/SearchSuggestion.kt b/next/kmp/browser/src/commonMain/kotlin/org/dweb_browser/browser/web/ui/search/SearchSuggestion.kt index a446cc65b..13c3e1112 100644 --- a/next/kmp/browser/src/commonMain/kotlin/org/dweb_browser/browser/web/ui/search/SearchSuggestion.kt +++ b/next/kmp/browser/src/commonMain/kotlin/org/dweb_browser/browser/web/ui/search/SearchSuggestion.kt @@ -37,11 +37,13 @@ import androidx.compose.ui.zIndex import kotlinx.coroutines.CancellationException import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.cancel +import kotlinx.coroutines.delay import kotlinx.coroutines.job import kotlinx.coroutines.launch import org.dweb_browser.browser.BrowserI18nResource import org.dweb_browser.browser.web.model.LocalBrowserViewModel import org.dweb_browser.browser.web.model.page.BrowserWebPage +import org.dweb_browser.helper.humanTrim internal enum class TabId { Chat, @@ -93,17 +95,22 @@ internal fun SearchSuggestion( } DisposableEffect(searchText) { web3Searcher?.cancel(CancellationException("Cancel search")) - web3Searcher = when { - searchText.isEmpty() -> null - else -> { - val parentScope = viewModel.browserNMM.getRuntimeScope() - Web3Searcher( - coroutineContext = parentScope.coroutineContext + SupervisorJob(parentScope.coroutineContext.job), - searchText = searchText - ) + val job = scope.launch { + delay(150)// 防抖 + val keyword = searchText.humanTrim() + web3Searcher = when { + keyword.isEmpty() -> null + else -> { + val parentScope = viewModel.browserNMM.getRuntimeScope() + Web3Searcher( + coroutineContext = parentScope.coroutineContext + SupervisorJob(parentScope.coroutineContext.job), + searchText = keyword + ) + } } } onDispose { + job.cancel() web3Searcher?.cancel(CancellationException("Cancel search")) web3Searcher = null } diff --git a/next/kmp/browser/src/commonMain/kotlin/org/dweb_browser/browser/web/ui/search/Web3Searcher.kt b/next/kmp/browser/src/commonMain/kotlin/org/dweb_browser/browser/web/ui/search/Web3Searcher.kt index 149d4f26c..330b068ac 100644 --- a/next/kmp/browser/src/commonMain/kotlin/org/dweb_browser/browser/web/ui/search/Web3Searcher.kt +++ b/next/kmp/browser/src/commonMain/kotlin/org/dweb_browser/browser/web/ui/search/Web3Searcher.kt @@ -19,8 +19,8 @@ import org.dweb_browser.core.std.dns.httpFetch import org.dweb_browser.helper.Once import org.dweb_browser.helper.commonConsumeEachArrayRange import org.dweb_browser.helper.hexString +import org.dweb_browser.helper.isNoProtocolWebUrl import org.dweb_browser.helper.isWebUrl -import org.dweb_browser.helper.isWebUrlOrWithoutProtocol import org.dweb_browser.helper.toWebUrl import org.dweb_browser.helper.utf8String import org.dweb_browser.pure.crypto.hash.sha256 @@ -31,8 +31,18 @@ import kotlin.coroutines.CoroutineContext internal class Web3Searcher( override val coroutineContext: CoroutineContext, - val searchText: String, + val searchTexts: List, ) : CoroutineScope { + constructor(coroutineContext: CoroutineContext, searchText: String) : this( + coroutineContext, when { + searchText.contains(' ') -> listOf( + searchText.replace(Regex("\\s+"), "-"), + searchText.replace(Regex("\\s+"), ""), + ) + + else -> listOf(searchText) + } + ) /** * 这是新语法,如果你的IDE报错: @@ -118,27 +128,30 @@ internal class Web3Searcher( // 创建一个 Semaphore 来限制并发数为 5 val semaphore = Semaphore(5) flow { - if (searchText.isWebUrl()) { - emit(searchText) - return@flow - } - if (searchText.isWebUrlOrWithoutProtocol()) { - emit("https://$searchText") - emit("https://dweb.$searchText") - } - flow { - emit("com") - emit("org") - emit("net") - }.collect { top -> - emit("https://$searchText.$top") - emit("https://www.$searchText.$top") - emit("https://dweb.$searchText.$top") - emit("https://dweb-$searchText.$top") - emit("https://$searchText-dweb.$top") + searchTexts.forEach { searchText -> + when { + searchText.isWebUrl() -> emit(searchText) + searchText.isNoProtocolWebUrl() -> { + emit("https://$searchText") + emit("https://dweb.$searchText") + } + + else -> { + flow { + emit("com") + emit("org") + emit("net") + }.collect { top -> + emit("https://$searchText.$top") + emit("https://www.$searchText.$top") + emit("https://dweb.$searchText.$top") + emit("https://dweb-$searchText.$top") + emit("https://$searchText-dweb.$top") + } + } + } } - } - .collect { originHref -> + }.collect { originHref -> launch { semaphore.withPermit { val originUrl = originHref.toWebUrl() ?: return@withPermit diff --git a/next/kmp/helper/src/commonMain/kotlin/org/dweb_browser/helper/stringHelper.kt b/next/kmp/helper/src/commonMain/kotlin/org/dweb_browser/helper/stringHelper.kt index 6690235f9..88d29de7a 100644 --- a/next/kmp/helper/src/commonMain/kotlin/org/dweb_browser/helper/stringHelper.kt +++ b/next/kmp/helper/src/commonMain/kotlin/org/dweb_browser/helper/stringHelper.kt @@ -1,3 +1,32 @@ package org.dweb_browser.helper -public fun String.removeInvisibleChars(): String = replace(Regex("[\\p{C}\\p{Z}&&[^\\p{Zs}]]"), "") \ No newline at end of file +public fun String.removeInvisibleChars(): String = replace(Regex("[\\p{C}\\p{Z}&&[^\\p{Zs}]]"), "") + +public fun String.humanTrim(): String = trim( + // 零宽字符: + '\u200B',//零宽空格 (ZWSP) + '\u200C',//零宽非连接符 (ZWNJ) + '\u200D',//零宽连接符 (ZWJ) + + // 常见的空白字符: + + '\u0020',// 普通空格 (Space) + '\u00A0',// 不间断空格 (Non-breaking space) + '\u1680',// 赡养符 (Ogham space mark) + '\u2000',// 到 \u200A 一系列空格(各种宽度的空格) +// 格式控制字符(这些字符主要用于控制文本格式,通常在现代文本中不再使用,但在某些场景中仍然可能出现): + + '\u200B',// 零宽空格(Zero Width Space) + '\u200C',// 零宽非连接符(Zero Width Non-Joiner) + '\u200D',// 零宽连接符(Zero Width Joiner) + '\u200E',// 左到右标记(Left-to-Right Mark, LRM) + '\u200F',// 右到左标记(Right-to-Left Mark, RLM) + '\u202A',// 到 \u202E 一些文本方向控制字符(如:右到左方向标记、强制方向标记等) +// 换行符与回车符: + + '\u000A',// 换行符(Line Feed, LF) + '\u000D',// 回车符(Carriage Return, CR) + '\u2028',// 行分隔符(Line Separator) + '\u2029',// 段分隔符(Paragraph Separator) + +) \ No newline at end of file