hyj 4 месяцев назад
Сommit
f39b72864a
100 измененных файлов с 19186 добавлено и 0 удалено
  1. 12 0
      .gitignore
  2. 10 0
      AppScope/app.json5
  3. 8 0
      AppScope/resources/base/element/string.json
  4. BIN
      AppScope/resources/base/media/background.png
  5. BIN
      AppScope/resources/base/media/foreground.png
  6. 7 0
      AppScope/resources/base/media/layered_image.json
  7. 60 0
      build-profile.json5
  8. 32 0
      code-linter.json5
  9. 6 0
      home/.gitignore
  10. 41 0
      home/build-profile.json5
  11. 6 0
      home/hvigorfile.ts
  12. 23 0
      home/obfuscation-rules.txt
  13. 10 0
      home/oh-package.json5
  14. 48 0
      home/src/main/ets/blocks/contents/meowCreditsRepos.ets
  15. 58 0
      home/src/main/ets/blocks/contents/meowCreditsUsers.ets
  16. 49 0
      home/src/main/ets/blocks/contents/meowWhatsNew.ets
  17. 1200 0
      home/src/main/ets/blocks/modules/meowAppSettings.ets
  18. 979 0
      home/src/main/ets/blocks/modules/meowBookmarks.ets
  19. 414 0
      home/src/main/ets/blocks/modules/meowDownloads.ets
  20. 395 0
      home/src/main/ets/blocks/modules/meowScratchingBoard.ets
  21. 178 0
      home/src/main/ets/blocks/modules/meowSuggestions.ets
  22. 340 0
      home/src/main/ets/blocks/modules/meowTabsHorizontal.ets
  23. 775 0
      home/src/main/ets/blocks/modules/meowTabsVertical.ets
  24. 1270 0
      home/src/main/ets/blocks/modules/meowTitleBar.ets
  25. 898 0
      home/src/main/ets/blocks/modules/meowWebView.ets
  26. 76 0
      home/src/main/ets/blocks/panels/meowAnimationManager.ets
  27. 151 0
      home/src/main/ets/blocks/panels/meowColorsManager.ets
  28. 165 0
      home/src/main/ets/blocks/panels/meowDebug.ets
  29. 72 0
      home/src/main/ets/blocks/panels/meowHomepageBackgroundManager.ets
  30. 371 0
      home/src/main/ets/blocks/panels/meowSEManager.ets
  31. 368 0
      home/src/main/ets/blocks/panels/meowUAManager.ets
  32. 124 0
      home/src/main/ets/components/HeaderSearch.ets
  33. 35 0
      home/src/main/ets/components/buttons/linysCapsuleButton.ets
  34. 39 0
      home/src/main/ets/components/buttons/linysCapsuleButtonWithText.ets
  35. 47 0
      home/src/main/ets/components/buttons/linysShowButton.ets
  36. 69 0
      home/src/main/ets/components/buttons/linysTimeoutButton.ets
  37. 40 0
      home/src/main/ets/components/buttons/linysTimeoutButtonWithText.ets
  38. 53 0
      home/src/main/ets/components/linysLockSlider.ets
  39. 99 0
      home/src/main/ets/components/linysPathTree.ets
  40. 54 0
      home/src/main/ets/components/linysProgress.ets
  41. 30 0
      home/src/main/ets/components/linysProgressInfo.ets
  42. 35 0
      home/src/main/ets/components/texts/linysLink.ets
  43. 21 0
      home/src/main/ets/components/texts/linysSymbol.ets
  44. 54 0
      home/src/main/ets/components/texts/linysText.ets
  45. 33 0
      home/src/main/ets/components/texts/linysTextSubtitleDivision.ets
  46. 21 0
      home/src/main/ets/components/texts/linysTextTitle.ets
  47. 23 0
      home/src/main/ets/components/toggles/linysLockToggle.ets
  48. 41 0
      home/src/main/ets/components/toggles/linysSwitchWithText.ets
  49. 47 0
      home/src/main/ets/dialogs/CustomDialogView.ets
  50. 38 0
      home/src/main/ets/dialogs/Dialog.ets
  51. 55 0
      home/src/main/ets/dialogs/contents/woofQR.ets
  52. 124 0
      home/src/main/ets/dialogs/contents/woofRecentFaultLogs.ets
  53. 173 0
      home/src/main/ets/dialogs/contents/woofUpdateHistory.ets
  54. 390 0
      home/src/main/ets/dialogs/managers/woofAdsBlocker.ets
  55. 200 0
      home/src/main/ets/dialogs/managers/woofCookies.ets
  56. 430 0
      home/src/main/ets/dialogs/managers/woofGeneralManage.ets
  57. 458 0
      home/src/main/ets/dialogs/managers/woofHistory.ets
  58. 149 0
      home/src/main/ets/dialogs/prompts/woofPromptFail.ets
  59. 54 0
      home/src/main/ets/dialogs/prompts/woofPromptOK.ets
  60. 303 0
      home/src/main/ets/dialogs/prompts/woofSelectBookmarksPath.ets
  61. 281 0
      home/src/main/ets/dialogs/prompts/woofSelectColor.ets
  62. 143 0
      home/src/main/ets/dialogs/prompts/woofWantDownload.ets
  63. 66 0
      home/src/main/ets/dialogs/prompts/woofWantJump.ets
  64. 77 0
      home/src/main/ets/dialogs/prompts/woofWantProtectedResources.ets
  65. 84 0
      home/src/main/ets/dialogs/quicks/woofQuickSE.ets
  66. 48 0
      home/src/main/ets/dialogs/woofControlFrame.ets
  67. 281 0
      home/src/main/ets/homeability/HomeAbility.ets
  68. 12 0
      home/src/main/ets/homebackupability/HomeBackupAbility.ets
  69. 844 0
      home/src/main/ets/hosts/bunch_of_bookmarks.ets
  70. 130 0
      home/src/main/ets/hosts/bunch_of_defaults.ets
  71. 443 0
      home/src/main/ets/hosts/bunch_of_downloads.ets
  72. 551 0
      home/src/main/ets/hosts/bunch_of_history.ets
  73. 261 0
      home/src/main/ets/hosts/bunch_of_history_index.ets
  74. 190 0
      home/src/main/ets/hosts/bunch_of_history_index_lite.ets
  75. 569 0
      home/src/main/ets/hosts/bunch_of_history_index_x_functions.ets
  76. 192 0
      home/src/main/ets/hosts/bunch_of_key_shortcuts.ets
  77. 86 0
      home/src/main/ets/hosts/bunch_of_search_engines.ets
  78. 390 0
      home/src/main/ets/hosts/bunch_of_settings.ets
  79. 448 0
      home/src/main/ets/hosts/bunch_of_tabs.ets
  80. 88 0
      home/src/main/ets/hosts/bunch_of_user_agents.ets
  81. 108 0
      home/src/main/ets/objects/HistoryDataSource.ets
  82. 606 0
      home/src/main/ets/pages/Index.ets
  83. 48 0
      home/src/main/ets/utils/any_concurrent_tools.ets
  84. 18 0
      home/src/main/ets/utils/clipboard_tools.ets
  85. 208 0
      home/src/main/ets/utils/color_tools.ets
  86. 57 0
      home/src/main/ets/utils/drag_drop_tools.ets
  87. 26 0
      home/src/main/ets/utils/environment_tools.ets
  88. 30 0
      home/src/main/ets/utils/html_tools.ets
  89. 187 0
      home/src/main/ets/utils/kv_store_tools.ets
  90. 65 0
      home/src/main/ets/utils/label_link_relation_tools.ets
  91. 21 0
      home/src/main/ets/utils/link_tools.ets
  92. 36 0
      home/src/main/ets/utils/performance_tools.ets
  93. 55 0
      home/src/main/ets/utils/permission_tools.ets
  94. 36 0
      home/src/main/ets/utils/preview_tools.ets
  95. 20 0
      home/src/main/ets/utils/print_tools.ets
  96. 11 0
      home/src/main/ets/utils/resource_tools.ets
  97. 19 0
      home/src/main/ets/utils/share_tools.ets
  98. 770 0
      home/src/main/ets/utils/storage_tools.ets
  99. 14 0
      home/src/main/ets/utils/ui_tools.ets
  100. 406 0
      home/src/main/ets/utils/url_tools.ets

+ 12 - 0
.gitignore

@@ -0,0 +1,12 @@
+/node_modules
+/oh_modules
+/local.properties
+/.idea
+**/build
+/.hvigor
+.cxx
+/.clangd
+/.clang-format
+/.clang-tidy
+**/.test
+/.appanalyzer

+ 10 - 0
AppScope/app.json5

@@ -0,0 +1,10 @@
+{
+  "app": {
+    "bundleName": "com.ytpm.jyllq",
+    "vendor": "example",
+    "versionCode": 1000000,
+    "versionName": "1.0.0",
+    "icon": "$media:layered_image",
+    "label": "$string:app_name"
+  }
+}

+ 8 - 0
AppScope/resources/base/element/string.json

@@ -0,0 +1,8 @@
+{
+  "string": [
+    {
+      "name": "app_name",
+      "value": "简阅浏览器"
+    }
+  ]
+}

BIN
AppScope/resources/base/media/background.png


BIN
AppScope/resources/base/media/foreground.png


+ 7 - 0
AppScope/resources/base/media/layered_image.json

@@ -0,0 +1,7 @@
+{
+  "layered-image":
+  {
+    "background" : "$media:background",
+    "foreground" : "$media:foreground"
+  }
+}

+ 60 - 0
build-profile.json5

@@ -0,0 +1,60 @@
+{
+  "app": {
+    "signingConfigs": [
+      {
+        "name": "default",
+        "type": "HarmonyOS",
+        "material": {
+          "storeFile": "E:/基础证书文件/basic.p12",
+          "storePassword": "000000187064E8729C1D71ED1096726C38C500D70E50269E07F24DA2FE4CAEA5BEF2D5644D143B18",
+          "keyAlias": "yt112233",
+          "keyPassword": "00000018DD456E3267E414F51A22E56BC1CBBD2C8849D113D83CEB3EABACC61AF27F4B3DBF515BDE",
+          "signAlg": "SHA256withECDSA",
+          "profile": "E:/浏览器调试证书Debug.p7b",
+          "certpath": "E:/基础证书文件/调试证书.cer"
+        }
+      }
+    ],
+    "products": [
+      {
+        "name": "default",
+        "signingConfig": "default",
+        "targetSdkVersion": "5.1.1(19)",
+        "compatibleSdkVersion": "5.0.3(15)",
+        "runtimeOS": "HarmonyOS",
+        "buildOption": {
+          "strictMode": {
+            "caseSensitiveCheck": true,
+            "useNormalizedOHMUrl": true
+          }
+        }
+      }
+    ],
+    "buildModeSet": [
+      {
+        "name": "debug",
+      },
+      {
+        "name": "release"
+      }
+    ]
+  },
+  "modules": [
+    {
+      "name": "home",
+      "srcPath": "./home",
+      "targets": [
+        {
+          "name": "default",
+          "applyToProducts": [
+            "default"
+          ]
+        }
+      ]
+    },
+    {
+      "name": "module_share",
+      "srcPath": "./shared/components/module_share",
+    }
+  ]
+}

+ 32 - 0
code-linter.json5

@@ -0,0 +1,32 @@
+{
+  "files": [
+    "**/*.ets"
+  ],
+  "ignore": [
+    "**/src/ohosTest/**/*",
+    "**/src/test/**/*",
+    "**/src/mock/**/*",
+    "**/node_modules/**/*",
+    "**/oh_modules/**/*",
+    "**/build/**/*",
+    "**/.preview/**/*"
+  ],
+  "ruleSet": [
+    "plugin:@performance/recommended",
+    "plugin:@typescript-eslint/recommended"
+  ],
+  "rules": {
+    "@security/no-unsafe-aes": "error",
+    "@security/no-unsafe-hash": "error",
+    "@security/no-unsafe-mac": "warn",
+    "@security/no-unsafe-dh": "error",
+    "@security/no-unsafe-dsa": "error",
+    "@security/no-unsafe-ecdsa": "error",
+    "@security/no-unsafe-rsa-encrypt": "error",
+    "@security/no-unsafe-rsa-sign": "error",
+    "@security/no-unsafe-rsa-key": "error",
+    "@security/no-unsafe-dsa-key": "error",
+    "@security/no-unsafe-dh-key": "error",
+    "@security/no-unsafe-3des": "error"
+  }
+}

+ 6 - 0
home/.gitignore

@@ -0,0 +1,6 @@
+/node_modules
+/oh_modules
+/.preview
+/build
+/.cxx
+/.test

+ 41 - 0
home/build-profile.json5

@@ -0,0 +1,41 @@
+{
+  "apiType": "stageMode",
+  "buildOption": {
+    "externalNativeOptions": {
+      "abiFilters": [
+        "arm64-v8a",
+        "x86_64"
+      ]
+    },
+    "sourceOption": {
+      "workers": [
+        './src/main/ets/workers/History_indexer.ets',
+        './src/main/ets/workers/History_index_loader.ets',
+        './src/main/ets/workers/History_index_saver.ets'
+      ]
+    },
+  },
+  "buildOptionSet": [
+    {
+      "name": "release",
+      "arkOptions": {
+        "obfuscation": {
+          "ruleOptions": {
+            "enable": false,
+            "files": [
+              "./obfuscation-rules.txt"
+            ]
+          }
+        }
+      }
+    },
+  ],
+  "targets": [
+    {
+      "name": "default"
+    },
+    {
+      "name": "ohosTest",
+    }
+  ]
+}

+ 6 - 0
home/hvigorfile.ts

@@ -0,0 +1,6 @@
+import { hapTasks } from '@ohos/hvigor-ohos-plugin';
+
+export default {
+    system: hapTasks,  /* Built-in plugin of Hvigor. It cannot be modified. */
+    plugins:[]         /* Custom plugin to extend the functionality of Hvigor. */
+}

+ 23 - 0
home/obfuscation-rules.txt

@@ -0,0 +1,23 @@
+# Define project specific obfuscation rules here.
+# You can include the obfuscation configuration files in the current module's build-profile.json5.
+#
+# For more details, see
+#   https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/source-obfuscation-V5
+
+# Obfuscation options:
+# -disable-obfuscation: disable all obfuscations
+# -enable-property-obfuscation: obfuscate the property names
+# -enable-toplevel-obfuscation: obfuscate the names in the global scope
+# -compact: remove unnecessary blank spaces and all line feeds
+# -remove-log: remove all console.* statements
+# -print-namecache: print the name cache that contains the mapping from the old names to new names
+# -apply-namecache: reuse the given cache file
+
+# Keep options:
+# -keep-property-name: specifies property names that you want to keep
+# -keep-global-name: specifies names that you want to keep in the global scope
+
+-enable-property-obfuscation
+-enable-toplevel-obfuscation
+-enable-filename-obfuscation
+-enable-export-obfuscation

+ 10 - 0
home/oh-package.json5

@@ -0,0 +1,10 @@
+{
+  "name": "home",
+  "version": "1.0.0",
+  "description": "Meow.",
+  "main": "",
+  "author": "",
+  "license": "",
+  "dependencies": {}
+}
+

+ 48 - 0
home/src/main/ets/blocks/contents/meowCreditsRepos.ets

@@ -0,0 +1,48 @@
+import linysText from '../../components/texts/linysText';
+import { animation_default } from '../../hosts/bunch_of_defaults';
+import { resource_to_string } from '../../utils/resource_tools';
+
+@Component
+struct meowCreditsRepos {
+  @State credits_repos_and_projs_resource: Resource = $r("app.string.Credits_repos_and_projs");
+  @State credits_repos_and_projs: string = resource_to_string(this.credits_repos_and_projs_resource);
+  @State credits_repos_and_projs_list: string[] = this.credits_repos_and_projs.split('\n');
+
+  build() {
+    Column({ space: 8 }) {
+      linysText({
+        text: $r('app.string.Credits'),
+        max_lines: 3
+      })
+
+      ForEach(this.credits_repos_and_projs_list, (text: string, index: number) => {
+        linysText({
+          text: text,
+          max_lines: 48
+        })
+          .opacity(0.8)
+      })
+    }
+    .alignItems(HorizontalAlign.Start)
+    .padding(10)
+    .borderRadius(13.5)
+    .backgroundColor($r('sys.color.comp_background_tertiary'))
+    .width("100%")
+    .animation(animation_default())
+    .onAreaChange(() => {
+      this.update_resource_string();
+    })
+  }
+
+  /**
+   * Convert the Resource to string again.
+   *
+   * Usually called to deal with the change of language.
+   * */
+  update_resource_string() {
+    this.credits_repos_and_projs = resource_to_string(this.credits_repos_and_projs_resource);
+    this.credits_repos_and_projs_list = this.credits_repos_and_projs.split('\n');
+  }
+}
+
+export default meowCreditsRepos;

+ 58 - 0
home/src/main/ets/blocks/contents/meowCreditsUsers.ets

@@ -0,0 +1,58 @@
+import linysText from '../../components/texts/linysText';
+import { animation_default } from '../../hosts/bunch_of_defaults';
+import { resource_to_string } from '../../utils/resource_tools';
+
+@Component
+struct meowCreditsUsers {
+  @State credits_devs_and_users_resource: Resource = $r("app.string.Credits_devs_and_users");
+  @State credits_devs_and_users: string = resource_to_string(this.credits_devs_and_users_resource);
+  @State credits_devs_and_users_list: string[] = this.credits_devs_and_users.split('\n');
+
+  build() {
+    Scroll() {
+      Column({ space: 8 }) {
+        linysText({
+          text: $r('app.string.Credits_issues'),
+          max_lines: 10
+        })
+
+        ForEach(this.credits_devs_and_users_list, (text: string, index: number) => {
+          linysText({
+            text: text,
+            max_lines: 48
+          })
+            .opacity(0.8)
+        })
+      }
+      .alignItems(HorizontalAlign.Start)
+      .padding(10)
+      .borderRadius(13.5)
+      .backgroundColor($r('sys.color.comp_background_tertiary'))
+      .width("100%")
+      .animation(animation_default())
+      .onAreaChange(() => {
+        this.update_resource_string();
+      })
+    }
+    // .nestedScroll({ scrollForward: NestedScrollMode.SELF_FIRST, scrollBackward: NestedScrollMode.SELF_FIRST })
+    .width("100%")
+    .height(180)
+    .scrollable(ScrollDirection.Vertical)
+    .edgeEffect(EdgeEffect.Spring)
+    .borderRadius(13.5)
+    .animation(animation_default())
+
+  }
+
+  /**
+   * Convert the Resource to string again.
+   *
+   * Usually called to deal with the change of language.
+   * */
+  update_resource_string() {
+    this.credits_devs_and_users = resource_to_string(this.credits_devs_and_users_resource);
+    this.credits_devs_and_users_list = this.credits_devs_and_users.split('\n');
+  }
+}
+
+export default meowCreditsUsers;

+ 49 - 0
home/src/main/ets/blocks/contents/meowWhatsNew.ets

@@ -0,0 +1,49 @@
+import linysText from '../../components/texts/linysText';
+import { animation_default } from '../../hosts/bunch_of_defaults';
+import { resource_to_string } from '../../utils/resource_tools';
+
+@Component
+struct meowWhatsNew {
+  @State new_things_resource: Resource = $r("app.string.Whats_new_content");
+  @State new_things: string = resource_to_string(this.new_things_resource);
+  @State new_things_list: string[] = this.new_things.split('\n');
+
+  build() {
+    Column({ space: 8 }) {
+      ForEach(this.new_things_list, (text: string, index: number) => {
+        Row({ space: 5 }) {
+          linysText({ text: (index + 1).toString() + '.' })
+          linysText({
+            text: text,
+            max_lines: 48
+          })
+            .opacity(0.8)
+            .layoutWeight(1)
+        }
+        // .width('90%')
+        .alignItems(VerticalAlign.Top)
+      })
+    }
+    .alignItems(HorizontalAlign.Start)
+    .padding(10)
+    .borderRadius(13.5)
+    .backgroundColor($r('sys.color.comp_background_tertiary'))
+    .width("100%")
+    .animation(animation_default())
+    .onAreaChange(() => {
+      this.update_resource_string();
+    })
+  }
+
+  /**
+   * Convert the Resource to string again.
+   *
+   * Usually called to deal with the change of language.
+   * */
+  update_resource_string() {
+    this.new_things = resource_to_string(this.new_things_resource);
+    this.new_things_list = this.new_things.split('\n');
+  }
+}
+
+export default meowWhatsNew;

+ 1200 - 0
home/src/main/ets/blocks/modules/meowAppSettings.ets

@@ -0,0 +1,1200 @@
+import {
+  animation_default,
+  capsule_bar_height,
+  click_effect_default,
+  fontSize_Extra,
+  fontSize_Large,
+  minimum_card_width,
+  url_default_blank
+} from '../../hosts/bunch_of_defaults';
+import linysTextTitle from '../../components/texts/linysTextTitle';
+import linysText from '../../components/texts/linysText';
+import bundleManager from '@ohos.bundle.bundleManager';
+import { ItemRestriction, SegmentButton, SegmentButtonOptions, SegmentButtonTextItem } from '@ohos.arkui.advanced.SegmentButton';
+import meowUAManager from '../panels/meowUAManager';
+import { bunch_of_user_agents } from '../../hosts/bunch_of_user_agents';
+import { bunch_of_tabs } from '../../hosts/bunch_of_tabs';
+import linysCapsuleButton from '../../components/buttons/linysCapsuleButton';
+import { bunch_of_history } from '../../hosts/bunch_of_history';
+import {
+  add_units_to_size,
+  arrayBuffer_2_pixelMap,
+  document_pick_to_text,
+  document_save_text,
+  export_everything,
+  get_folder_size_Sync,
+  import_everything,
+  sandbox_read_arrayBuffer_sync
+} from '../../utils/storage_tools';
+import woofHistory from '../../dialogs/managers/woofHistory';
+import { bunch_of_settings } from '../../hosts/bunch_of_settings';
+import meowSEManager from '../panels/meowSEManager';
+import linysLink from '../../components/texts/linysLink';
+import woofAdsBlocker from '../../dialogs/managers/woofAdsBlocker';
+import meowWhatsNew from '../contents/meowWhatsNew';
+import meowColorsManager from '../panels/meowColorsManager';
+import { BusinessError } from '@kit.BasicServicesKit';
+import meowCreditsRepos from '../contents/meowCreditsRepos';
+import meowCreditsUsers from '../contents/meowCreditsUsers';
+import woofCookies from '../../dialogs/managers/woofCookies';
+import woofGeneralManage from '../../dialogs/managers/woofGeneralManage';
+import { bunch_of_key_shortcuts } from '../../hosts/bunch_of_key_shortcuts';
+import meowAnimationManager from '../panels/meowAnimationManager';
+import linysLockSlider from '../../components/linysLockSlider';
+import linysTimeoutButton from '../../components/buttons/linysTimeoutButton';
+import { get_sandbox_folder_size } from '../../utils/any_concurrent_tools';
+// import woofRecentFaultLogs from '../../dialogs/contents/woofRecentFaultLogs';
+import { bunch_of_history_index } from '../../hosts/bunch_of_history_index';
+import {
+  fill_fake_history,
+  history_index_full_rebuild_worker,
+  history_index_load_from_disk_worker,
+  history_index_save_to_disk_worker
+} from '../../hosts/bunch_of_history_index_x_functions';
+import { bunch_of_history_index_lite } from '../../hosts/bunch_of_history_index_lite';
+import woofUpdateHistory from '../../dialogs/contents/woofUpdateHistory';
+import linysCapsuleButtonWithText from '../../components/buttons/linysCapsuleButtonWithText';
+import linysTimeoutButtonWithText from '../../components/buttons/linysTimeoutButtonWithText';
+import linysSwitchWithText from '../../components/toggles/linysSwitchWithText';
+import woofPromptOK from '../../dialogs/prompts/woofPromptOK';
+import linysTextSubtitleDivision from '../../components/texts/linysTextSubtitleDivision';
+import { setDarkColorMode, setLightColorMode } from '../../utils/color_tools';
+import { meowContext } from '../../utils/environment_tools';
+
+@Component
+struct meowAppSettings {
+  @Link @Watch('on_open_or_close_panel') showing_app_settings: boolean;
+  // Hosts and environment
+  @StorageLink('tabs_style') tabs_style: string = "";
+  @StorageLink('tabs_style_non_tablet_mode') tabs_style_non_tablet_mode: string = "";
+  @StorageLink('title_bar_position') title_bar_position: string = "";
+  @StorageLink('bunch_of_user_agents') bunch_of_user_agents: bunch_of_user_agents = new bunch_of_user_agents();
+  @StorageLink('bunch_of_tabs') bunch_of_tabs: bunch_of_tabs = new bunch_of_tabs(true);
+  @StorageLink('bunch_of_history') bunch_of_history: bunch_of_history = new bunch_of_history(true);
+  @StorageLink('bunch_of_settings') bunch_of_settings: bunch_of_settings = new bunch_of_settings(true);
+  @StorageLink('bunch_of_key_shortcuts') bunch_of_key_shortcuts: bunch_of_key_shortcuts = new bunch_of_key_shortcuts(true);
+  @StorageLink('bunch_of_history_index') bunch_of_history_index: bunch_of_history_index = new bunch_of_history_index();
+  @StorageLink('homepage_background') homepage_background: PixelMap | undefined = undefined;
+  // Colors
+  @StorageProp('color_current_primary') @Watch('on_color_change') color_current_primary: ResourceColor = $r('app.color.start_window_background');
+  @StorageProp('color_current_secondary') @Watch('on_color_change') color_current_secondary: ResourceColor = $r('app.color.block_color');
+  @StorageProp('color_current_font') @Watch('on_color_change') color_current_font: ResourceColor = $r('app.color.font_color_title');
+  // Generals
+  @StorageProp('screen_width') screen_width: number = 0;
+  @StorageProp('tablet_mode') tablet_mode: boolean = false;
+  // Information
+  @State version: string = "1.0.0";
+  @State history_length: number = this.bunch_of_history.get_history_this_month().length;
+  // Segment Button Options
+  @State title_bar_position_tabOptions: SegmentButtonOptions = SegmentButtonOptions.tab({
+    buttons: [{ text: '  󰀐  ' }, { text: '  󰃐  ' }],
+    selectedBackgroundColor: this.color_current_font,
+    selectedFontColor: this.color_current_primary
+  })
+  @State tabs_style_tabOptions: SegmentButtonOptions = SegmentButtonOptions.tab({
+    buttons: [{ text: $r('app.string.Settings_appearance_tabs_style_vertical') },
+      { text: $r('app.string.Settings_appearance_tabs_style_horizontal') }],
+    selectedBackgroundColor: this.color_current_font,
+    selectedFontColor: this.color_current_primary
+  })
+  @State start_up_page_tabOptions: SegmentButtonOptions = SegmentButtonOptions.tab({
+    buttons: [
+      { text: $r('app.string.Settings_start_up_new_tab') },
+      { text: $r('app.string.Settings_start_up_home') },
+      { text: $r('app.string.Settings_start_up_continue') }
+    ],
+    selectedBackgroundColor: this.color_current_font,
+    selectedFontColor: this.color_current_primary
+  })
+  @State left_or_right: SegmentButtonOptions = SegmentButtonOptions.tab({
+    buttons: [{ text: '󰃊' }, { text: '󰈱' }],
+    selectedBackgroundColor: this.color_current_font,
+    selectedFontColor: this.color_current_primary
+  })
+  // Settings
+  @StorageLink('DEV_MODE') DEV_MODE: boolean = false;
+  @State @Watch('on_title_bar_position_changed') title_bar_position_selected: number[] = [0];
+  @State @Watch('on_tabs_style_changed') tabs_style_selected: number[] = [0];
+  @State @Watch('on_tabs_style_non_tablet_mode_changed') tabs_style_selected_non_tablet_mode: number[] = [0];
+  @State @Watch('on_start_up_page_changed') start_up_page_selected: number[] = [0];
+  @State @Watch('on_prefer_hand_left_or_right_changed') left_or_right_selected: number[] = [0];
+  @StorageLink('max_bookmark_advice') max_bookmark_advice: number = 5;
+  @StorageLink('max_history_advice') max_history_advice: number = 5;
+  @StorageLink('sys_back_to_access_backward') sys_back_to_access_backward: boolean = false;
+  @StorageLink('resource_monitor') resource_monitor: boolean = true;
+  @StorageLink('web_force_dark_mode') web_force_dark_mode: boolean = false;
+  @StorageLink('use_adblock') use_adblock: boolean = true;
+  @StorageLink('collect_new_history') collect_new_history: boolean = true;
+  @StorageLink('intelligent_tracking_prevention') intelligent_tracking_prevention: boolean = false;
+  @StorageLink('disable_js') disable_js: boolean = true;
+  @StorageLink('disable_js_these_sites') disable_js_these_sites: string[] = [];
+  @StorageLink('disable_js_all_sites') disable_js_all_sites: boolean = false;
+  @StorageLink('disable_image') disable_image: boolean = true;
+  @StorageLink('disable_image_these_sites') disable_image_these_sites: string[] = [];
+  @StorageLink('disable_image_all_sites') disable_image_all_sites: boolean = false;
+  @StorageLink('continuation_auto_exit') continuation_auto_exit: boolean = false;
+  @StorageLink('continuation_auto_close_tab') continuation_auto_close_tab: boolean = true;
+  // Downloads
+  @StorageLink('direct_download') direct_download: boolean = false;
+  @StorageLink('direct_download_auto_open') direct_download_auto_open: boolean = false;
+  // Settings / Accessibility
+  @StorageLink('preferred_hand_left_or_right') preferred_hand_left_or_right: string = 'right';
+  @StorageLink('preferred_hand_reverse_settings_menu') preferred_hand_reverse_settings_menu: boolean = false;
+  @StorageLink('preferred_hand_reverse_tabs_panel') preferred_hand_reverse_tabs_panel: boolean = false;
+  @StorageLink('preferred_hand_reverse_homepage_shortcuts') preferred_hand_reverse_homepage_shortcuts: boolean = false;
+  // Edits
+  @State new_tab_url_edit: string = "";
+  @State home_url_edit: string = "";
+  @State lock_bookmark_suggestion_number: boolean = true;
+  @State lock_history_suggestion_number: boolean = true;
+  // Scroll menu
+  @State @Watch('scroll_settings_to') settings_scroll_to: number = 0;
+  @State settings_scroll_to_highlight_timeout: number = 0;
+  @State scroll_first: number = 0;
+  @State scroll_last: number = 0;
+  @State menu_text: string[] =
+    ['Settings_start_up', 'Settings_general', 'Settings_Experience', 'Settings_Accessibility', 'Settings_appearance',
+      'Settings_experience_history', 'Settings_storage', 'Settings_security', 'Settings_toolbox', 'Settings_about', 'Settings_update'];
+  // Dialogs
+  @StorageLink('universal_fail_prompt_desc_gateway') uni_fail_prompt_gateway: ResourceStr = "";
+  woofHistory_control: CustomDialogController = new CustomDialogController({
+    builder: woofHistory({ showing_settings: this.showing_app_settings }),
+    alignment: DialogAlignment.Center,
+    cornerRadius: 22,
+    // showInSubWindow: true,
+    width: "90%",
+  });
+  adsBlocker_control: CustomDialogController = new CustomDialogController({
+    builder: woofAdsBlocker(),
+    alignment: DialogAlignment.Center,
+    cornerRadius: 16,
+    // showInSubWindow: true,
+    width: "90%",
+  });
+  js_manage_control: CustomDialogController = new CustomDialogController({
+    builder: woofGeneralManage({
+      general_switch: this.disable_js,
+      general_on_all_sites_switch: this.disable_js_all_sites,
+      general_sites_list: this.disable_js_these_sites
+    }),
+    alignment: DialogAlignment.Center,
+    cornerRadius: 16,
+    // showInSubWindow: true,
+    width: "90%",
+  });
+  image_manage_control: CustomDialogController = new CustomDialogController({
+    builder: woofGeneralManage({
+      general_switch: this.disable_image,
+      general_sites_list: this.disable_image_these_sites,
+      general_on_all_sites_switch: this.disable_image_all_sites,
+      general_descriptions: [
+        $r('app.string.Settings_image_desc_1'),
+        $r('app.string.Settings_image_desc_2'),
+        $r('app.string.Settings_image_desc_3')],
+      general_tips: $r('app.string.Settings_image_already_disabled'),
+      general_title: $r('app.string.Settings_image_manage'),
+      general_switch_desc: $r('app.string.Settings_image_disable_image'),
+      general_subtitle_execute_on_these_sites: $r('app.string.Settings_image_some_sites'),
+      general_subtitle_execute_on_all_sites: $r('app.string.Settings_image_all_sites'),
+      general_switch_settings_id: 'disable_image',
+      general_sites_list_settings_id: 'disable_image_these_sites',
+      general_switch_all_sites_settings_id: 'disable_image_all_sites',
+    }),
+    alignment: DialogAlignment.Center,
+    cornerRadius: 16,
+    // showInSubWindow: true,
+    width: "90%",
+  });
+  cookies_control: CustomDialogController = new CustomDialogController({
+    builder: woofCookies(),
+    alignment: DialogAlignment.Center,
+    cornerRadius: 16,
+    // showInSubWindow: true,
+    width: "90%",
+  });
+
+  update_history: CustomDialogController = new CustomDialogController({
+    builder: woofUpdateHistory(),
+    alignment: DialogAlignment.Center,
+    cornerRadius: 16,
+    // showInSubWindow: true,
+    width: "90%",
+  });
+  woofSettingsImportOK_control: CustomDialogController = new CustomDialogController({
+    builder: woofPromptOK(),
+    alignment: DialogAlignment.Center,
+    cornerRadius: 16
+  })
+  woofEverythingImportOK_control: CustomDialogController = new CustomDialogController({
+    builder: woofPromptOK({
+      title: $r('app.string.Settings_toolbox_export_everything'),
+      desc: $r('app.string.Settings_toolbox_import_everything_ok')
+    }),
+    alignment: DialogAlignment.Center,
+    cornerRadius: 16
+  })
+  waterFlow_control: Scroller = new Scroller();
+  // Else
+  @State clear_web_cache_confirm: number = 0;
+  @State web_cache_size: number = 0;
+  @State history_index_size: number = 0;
+  @State version_clicked_times: number = 0;
+  // Status
+  @StorageLink('settings_init_retrieved') settings_init_retrieved: boolean = false;
+  @State calculating_web_cache_size: boolean = false;
+  @State calculating_history_index_size: boolean = false;
+  @StorageLink('reindexing_history') reindexing_history: boolean = false;
+  @StorageLink('reindexing_history_progress') reindexing_history_progress: number = 100;
+  @StorageLink('compressing_output_profile') compressing_output_profile: boolean = false;
+
+  build() {
+    Row() {
+      menuScrollVertical({
+        on_index_first: this.scroll_first,
+        on_index_last: this.scroll_last,
+        scroll_to_gateway: this.settings_scroll_to,
+        menu_text: this.menu_text,
+      })// Quick access on the left
+        .margin({ left: 15 })
+        .visibility(this.visibility_of_left_menu())
+        .animation(animation_default())
+
+      Column({ space: 10 }) {
+        WaterFlow({ scroller: this.waterFlow_control }) {
+          FlowItem() {
+            settingsCard({
+              title: $r('app.string.Settings_start_up'),
+              timeout: this.settings_scroll_to_highlight_timeout,
+              highlight: this.settings_scroll_to == 0
+            }) {
+              settingsSubCard({ timeout: this.settings_scroll_to_highlight_timeout, highlight: this.settings_scroll_to == 0 }) {
+                linysText({ text: $r('app.string.Settings_start_up_page') })
+                SegmentButton({
+                  options: this.start_up_page_tabOptions,
+                  selectedIndexes: this.start_up_page_selected
+                })
+              }
+            }
+          } // Start up
+          .width("100%")
+
+          FlowItem() {
+            settingsCard({
+              title: $r('app.string.Settings_general'),
+              timeout: this.settings_scroll_to_highlight_timeout, highlight: this.settings_scroll_to == 1
+            }) {
+              // UA
+              settingsSubCard({
+                icon: '󰐊',
+                title: $r('app.string.Index_surf_title'),
+                timeout: this.settings_scroll_to_highlight_timeout,
+                highlight: this.settings_scroll_to == 1
+              }) {
+
+                linysText({ text: $r('app.string.Settings_general_custom_search_engine') })
+                meowSEManager()
+
+                linysText({ text: $r('app.string.Settings_general_new_tab_url') })
+                TextInput({
+                  text: this.new_tab_url_edit,
+                  placeholder: url_default_blank(),
+                })
+                  .onChange((value) => {
+                    this.new_tab_url_edit = value;
+                  })
+                  .fontWeight(FontWeight.Regular)
+                  .fontColor(this.color_current_font)
+                  .caretColor(this.color_current_font)
+                  .selectedBackgroundColor(this.color_current_font)
+                  .onSubmit(() => {
+                    this.set_new_tab_url_and_kv_store();
+                  })
+                  .height(capsule_bar_height())
+
+                linysCapsuleButton({ text: "  󰀻  " })
+                  .visibility(this.new_tab_url_edit != this.bunch_of_tabs.new_tab_url ? Visibility.Visible : Visibility.None)
+                  .onClick(() => {
+                    this.set_new_tab_url_and_kv_store();
+                  })
+                  .alignSelf(ItemAlign.End)
+                  .animation(animation_default())
+
+                // Home URL
+                linysText({ text: $r('app.string.Settings_general_home_url') })
+                TextInput({
+                  text: this.home_url_edit,
+                  placeholder: url_default_blank(),
+                })
+                  .onChange((value) => {
+                    this.home_url_edit = value;
+                  })
+                  .fontWeight(FontWeight.Regular)
+                  .fontColor(this.color_current_font)
+                  .caretColor(this.color_current_font)
+                  .selectedBackgroundColor(this.color_current_font)
+                  .onSubmit(() => {
+                    this.set_home_url_and_kv_store();
+                  })
+                  .height(capsule_bar_height())
+
+                linysCapsuleButton({ text: "  󰀻  " })
+                  .visibility(this.home_url_edit != this.bunch_of_tabs.home_url ? Visibility.Visible : Visibility.None)
+                  .onClick(() => {
+                    this.set_home_url_and_kv_store();
+                  })
+                  .alignSelf(ItemAlign.End)
+                  .animation(animation_default())
+              } // Surf
+            }
+          } // General
+          .width("100%")
+
+          FlowItem() {
+            settingsCard({
+              title: $r('app.string.Settings_Experience'),
+              timeout: this.settings_scroll_to_highlight_timeout, highlight: this.settings_scroll_to == 2
+            }) {
+              settingsSubCard({
+                icon: '󰏯',
+                title: $r('app.string.Index_downloads_title'),
+                timeout: this.settings_scroll_to_highlight_timeout,
+                highlight: this.settings_scroll_to == 2
+              }) {
+                linysSwitchWithText({
+                  text: $r('app.string.Index_downloads_direct'),
+                  toggle_state: this.direct_download,
+                  onExecution: () => {
+                    this.bunch_of_settings.set('direct_download', this.direct_download);
+                  }
+                }) // Direct Download
+                linysSwitchWithText({
+                  text: $r('app.string.Index_downloads_direct_auto_open'),
+                  toggle_state: this.direct_download_auto_open,
+                  onExecution: () => {
+                    this.bunch_of_settings.set('direct_download_auto_open', this.direct_download_auto_open);
+                  }
+                }) // Direct Download auto open
+                  .opacity(this.direct_download ? 1 : 0.5)
+                  .animation(animation_default())
+              } // Downloads
+
+              settingsSubCard({
+                icon: '󰯏',
+                title: $r('app.string.Index_suggestions_title'),
+                timeout: this.settings_scroll_to_highlight_timeout,
+                highlight: this.settings_scroll_to == 2
+              }) {
+                linysText({ text: $r('app.string.Settings_experience_max_bookmark_advice'), max_lines: 2 })
+                linysLockSlider({
+                  slider_min: 0,
+                  slider_max: 20,
+                  slider_value: this.max_bookmark_advice,
+                  onTouchUp: () => {
+                    this.bunch_of_settings.set('max_bookmark_suggest', this.max_bookmark_advice);
+                  }
+                }) // Slider and display of max_bookmark_suggestions
+                linysText({ text: $r('app.string.Settings_experience_max_history_advice'), max_lines: 2 })
+                linysLockSlider({
+                  slider_min: 0,
+                  slider_max: 20,
+                  slider_value: this.max_history_advice,
+                  onTouchUp: () => {
+                    this.bunch_of_settings.set('max_history_suggest', this.max_history_advice);
+                  }
+                }) // Slider and display of max_history_suggestions
+              } // Suggestion
+
+              settingsSubCard({
+                icon: '󰇝',
+                title: $r('app.string.Title_advanced'),
+                timeout: this.settings_scroll_to_highlight_timeout,
+                highlight: this.settings_scroll_to == 2
+              }) {
+                linysSwitchWithText({
+                  text: $r('app.string.Settings_experience_use_adblock'),
+                  toggle_state: this.use_adblock,
+                  onExecution: () => {
+                    this.bunch_of_settings.set('use_adblock', this.use_adblock);
+                  }
+                }) // Use adblock
+                linysCapsuleButtonWithText({
+                  desc_text: $r('app.string.Settings_experience_manage_adblock'),
+                  button_text: "  󰀠  ",
+                  onExecution: () => {
+                    this.adsBlocker_control.open();
+                  }
+                })
+                linysSwitchWithText({
+                  text: $r('app.string.Settings_image_disable_image'),
+                  toggle_state: this.disable_image,
+                  onExecution: () => {
+                    this.bunch_of_settings.set('disable_image', this.disable_image);
+                  }
+                }) // Disable images
+                linysCapsuleButtonWithText({
+                  desc_text: $r('app.string.Settings_image_manage'),
+                  button_text: "  󰀠  ",
+                  onExecution: () => {
+                    this.image_manage_control.open();
+                  }
+                })
+              } // Advanced
+            }
+          } // Experience
+          .width("100%")
+
+          FlowItem() {
+            settingsCard({
+              title: $r('app.string.Settings_appearance'),
+              timeout: this.settings_scroll_to_highlight_timeout, highlight: this.settings_scroll_to == 4
+            }) {
+              settingsSubCard({ timeout: this.settings_scroll_to_highlight_timeout, highlight: this.settings_scroll_to == 4 }) {
+                linysSwitchWithText({
+                  text: $r('app.string.Settings_experience_web_force_dark_mode'),
+                  toggle_state: this.web_force_dark_mode,
+                  onExecution: () => {
+                    this.bunch_of_settings.set('web_force_dark_mode', this.web_force_dark_mode);
+                    if (this.web_force_dark_mode) {
+                      setDarkColorMode(meowContext())
+                    }else{
+                      setLightColorMode(meowContext())
+                    }
+                  }
+                }) // Force dark mode
+              }
+
+              settingsSubCard({
+                icon: '󰟠',
+                title: $r('app.string.Index_animation_title'),
+                timeout: this.settings_scroll_to_highlight_timeout,
+                highlight: this.settings_scroll_to == 4
+              }) {
+                meowAnimationManager() // Animation manager
+              } // Animation
+
+              settingsSubCard({
+                icon: '󰄥',
+                title: $r('app.string.Index_colors_title'),
+                timeout: this.settings_scroll_to_highlight_timeout,
+                highlight: this.settings_scroll_to == 4
+              }) {
+                meowColorsManager() // Colors manager
+              } // Colors
+            }
+          } // Appearance
+          .width("100%")
+
+          FlowItem() {
+            settingsCard({
+              title: $r('app.string.Settings_experience_history'),
+              timeout: this.settings_scroll_to_highlight_timeout, highlight: this.settings_scroll_to == 5
+            }) {
+              settingsSubCard({
+                icon: '󰏕',
+                title: $r('app.string.Settings_experience_history'),
+                timeout: this.settings_scroll_to_highlight_timeout,
+                highlight: this.settings_scroll_to == 5
+              }) {
+                linysSwitchWithText({
+                  text: $r('app.string.Settings_experience_history_record_new'),
+                  toggle_state: this.collect_new_history,
+                  onExecution: () => {
+                    this.bunch_of_settings.set('collect_new_history', this.collect_new_history);
+                  }
+                }) // Toggle History
+                linysCapsuleButtonWithText({
+                  desc_text: $r('app.string.Settings_experience_history_view'),
+                  button_text: "  󰀩  ",
+                  onExecution: () => {
+                    this.woofHistory_control.open();
+                  }
+                }) // View history
+              }
+            }
+          } // History
+          .width("100%")
+
+          FlowItem() {
+            settingsCard({
+              title: $r('app.string.Settings_storage'),
+              timeout: this.settings_scroll_to_highlight_timeout, highlight: this.settings_scroll_to == 6
+            }) {
+              settingsSubCard({
+                icon: '󰂼',
+                title: $r('app.string.Settings_experience_webview_storage'),
+                timeout: this.settings_scroll_to_highlight_timeout,
+                highlight: this.settings_scroll_to == 6
+              }) {
+                linysText({ text: $r('app.string.Settings_storage_clear_webview_desc'), is_description: true }) // Cache title
+                linysTextTitle({ text: add_units_to_size(this.web_cache_size) }) // data display
+                Row({ space: 12 }) {
+                  linysCapsuleButton({ text: '  󰃈  ' })
+                    .onClick(() => {
+                      this.refresh_web_cache_size();
+                    })
+                    .opacity(this.calculating_web_cache_size ? 0.4 : 1)
+                    .enabled(!this.calculating_web_cache_size)
+                    .animation(animation_default())
+
+                  linysTimeoutButton({
+                    dialogText: "确定是否删除缓存内容",
+                    text: '  󰀖  ',
+                    onExecution: () => {
+                      try {
+                        this.bunch_of_tabs.Tabs[0].controller.removeCache(true);
+                        console.log('[Meow][meowAppSettings] Clear webview cache!')
+                      } catch (error) {
+                        console.error(`[Meow][meowAppSettings] Clear webview cache.
+                      ErrorCode: ${(error as BusinessError).code},
+                      Message: ${(error as BusinessError).message}`);
+                      }
+                      this.refresh_web_cache_size();
+                    }
+                  })// Clear Cache
+                    .opacity(this.calculating_web_cache_size ? 0.4 : 1)
+                    .enabled(!this.calculating_web_cache_size)
+                    .animation(animation_default())
+                } // buttons
+                .justifyContent(this.preferred_hand_left_or_right == 'right' ? FlexAlign.End : FlexAlign.Start)
+                .animation(animation_default())
+                .width("100%")
+              } // Web cache
+            }
+          } // Storage
+          .width("100%")
+
+          FlowItem() {
+            settingsCard({
+              title: $r('app.string.Settings_security'),
+              timeout: this.settings_scroll_to_highlight_timeout, highlight: this.settings_scroll_to == 7
+            }) {
+              settingsSubCard({
+                icon: '󰠝',
+                title: $r('app.string.Settings_cookies'),
+                timeout: this.settings_scroll_to_highlight_timeout,
+                highlight: this.settings_scroll_to == 7
+              }) {
+                linysText({
+                  text: $r('app.string.intelligent_tracking_prevention_desc'),
+                  is_description: true
+                })
+                linysSwitchWithText({
+                  text: $r('app.string.intelligent_tracking_prevention'),
+                  toggle_state: this.intelligent_tracking_prevention,
+                  onExecution: () => {
+                    this.bunch_of_settings.set('intelligent_tracking_prevention', this.intelligent_tracking_prevention);
+                    for (let index = 0; index < this.bunch_of_tabs.Tabs.length; index++) {
+                      try {
+                        this.bunch_of_tabs.Tabs[index].controller.enableIntelligentTrackingPrevention(this.intelligent_tracking_prevention);
+                        console.log('[Meow][meowAppSettings] Intelligent Tracking Prevention now ' + this.intelligent_tracking_prevention.toString() + '!')
+                      } catch (e) {
+                        console.log('[Meow][meowAppSettings] Toggle Intelligent Tracking Prevention Failed! ' +
+                          'But perhaps this doesn\'t really matter?')
+                      }
+                    }
+                  }
+                }) // Intelligent tracking prevention
+
+                linysCapsuleButtonWithText({
+                  desc_text: $r('app.string.Settings_manage_cookies'),
+                  button_text: "  󰀩  ",
+                  onExecution: () => {
+                    this.cookies_control.open();
+                  }
+                }) // Cookie management
+
+              } // Cookies
+            }
+          } // Security
+          .width("100%")
+
+
+          FlowItem() {
+            settingsCard({
+              title: $r('app.string.Settings_about'),
+              timeout: this.settings_scroll_to_highlight_timeout, highlight: this.settings_scroll_to == 9
+            }) {
+              settingsSubCard({
+                icon: '󰕰',
+                title: $r('app.string.Settings_about'),
+                timeout: this.settings_scroll_to_highlight_timeout,
+                highlight: this.settings_scroll_to == 9
+              }) {
+                linysText({
+                  text:  "当前版本为" + " - " + this.version,
+                  max_lines: 3
+                })// Version Info
+                  .onClick(() => {
+                    this.version_clicked();
+                  })
+              }
+            }
+          } // About
+          .width("100%")
+
+        } // Bottom Bar of App Settings
+        .columnsTemplate("1fr ".repeat(Math.ceil(this.screen_width / minimum_card_width())))
+        .rowsGap(10)
+        .columnsGap(10)
+        .borderRadius(10)
+        .edgeEffect(EdgeEffect.Spring)
+        .scrollBar(BarState.Auto)
+        .width("100%")
+        .layoutWeight(1)
+        .animation(animation_default())
+        .onScrollIndex((first, last) => {
+          this.scroll_first = first;
+          this.scroll_last = last;
+        })
+      } // Main
+      .padding({ left: 15, right: 15 })
+      .layoutWeight(1)
+      .height("100%")
+      .animation(animation_default())
+
+      menuScrollVertical({
+        on_index_first: this.scroll_first,
+        on_index_last: this.scroll_last,
+        scroll_to_gateway: this.settings_scroll_to,
+        menu_text: this.menu_text,
+      })// Quick access on the right
+        .margin({ right: 15 })
+        .visibility(this.visibility_of_right_menu())
+        .animation(animation_default())
+    }
+    .height(this.showing_app_settings ? "75%" : 0)
+    .width("100%")
+    .animation(animation_default())
+    .onAppear(() => {
+      // console.log("[Meow][meowAppSettings] App Settings READY")
+      this.on_appear();
+      setInterval(() => {
+        // Deduce timeout for border highlights
+        if (this.settings_scroll_to_highlight_timeout < 0) {
+          this.settings_scroll_to_highlight_timeout = 0;
+        } else {
+          this.settings_scroll_to_highlight_timeout -= 100;
+        }
+        // console.log(this.settings_scroll_to_highlight_timeout.toString())
+      }, 100)
+    })
+  }
+
+  // Events
+
+  async on_appear() {
+    // Get app version info
+    bundleManager.getBundleInfoForSelf(bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION).then((bundleInfo) => {
+
+    })
+
+    // Get DEV_MODE
+    this.DEV_MODE = await this.bunch_of_settings.get('DEV_MODE') as boolean;
+
+    // Get settings of title bar position
+    this.title_bar_position = await this.bunch_of_settings.get('title_bar_position') as string;
+
+    // Get settings of tabs style
+    this.tabs_style = await this.bunch_of_settings.get('tabs_style') as string;
+
+    // Get settings of tabs style
+    this.tabs_style_non_tablet_mode = await this.bunch_of_settings.get('tabs_style_non_tablet_mode') as string;
+
+    // Get number of max bookmarks suggestions
+    this.max_bookmark_advice = await this.bunch_of_settings.get('max_bookmark_suggest') as number;
+
+    // Get number of max history suggestions
+    this.max_history_advice = await this.bunch_of_settings.get('max_history_suggest') as number;
+
+    // Get whether to continue to collect new history
+    this.collect_new_history = await this.bunch_of_settings.get('collect_new_history') as boolean;
+
+    // Get whether to continue to enable intelligent_tracking_prevention
+    this.intelligent_tracking_prevention = await this.bunch_of_settings.get('intelligent_tracking_prevention') as boolean;
+
+    // Get whether to use system back to access backward
+    this.sys_back_to_access_backward = await this.bunch_of_settings.get('sys_back_access_backward') as boolean;
+
+    // Get whether to enable forced webs dark mode
+    this.web_force_dark_mode = await this.bunch_of_settings.get('web_force_dark_mode') as boolean;
+
+    // Read webview cache
+    this.web_cache_size = await this.bunch_of_settings.get('webview_cache_size') as number;
+    if (this.web_cache_size == -1) {
+      this.web_cache_size = get_folder_size_Sync('/data/storage/el2/base/cache/web', true);
+      this.bunch_of_settings.set('webview_cache_size', this.web_cache_size);
+    }
+
+    // Set homepage background
+    let homepage_background_array_buffer = sandbox_read_arrayBuffer_sync('homepage_background_arrayBuffer');
+    // Set image for homepage
+    if (homepage_background_array_buffer) {
+      this.homepage_background = arrayBuffer_2_pixelMap(homepage_background_array_buffer);
+      console.log("[Meow][meowAppSettings] Finished loading homepage background!");
+    }
+
+    // Get settings of shortcuts on left or right
+    this.preferred_hand_left_or_right = await this.bunch_of_settings.get('preferred_hand_left_or_right') as string;
+    if (this.preferred_hand_left_or_right == 'left') {
+      this.left_or_right_selected = [0];
+    } else {
+      this.left_or_right_selected = [1];
+    }
+    // Get accessibility - reverse
+    this.preferred_hand_reverse_tabs_panel = await this.bunch_of_settings.get('preferred_hand_reverse_tabs_panel') as boolean;
+    this.preferred_hand_reverse_settings_menu = await this.bunch_of_settings.get('preferred_hand_reverse_settings_menu') as boolean;
+    this.preferred_hand_reverse_homepage_shortcuts = await this.bunch_of_settings.get('preferred_hand_reverse_homepage_shortcuts') as boolean;
+
+    // Get Homepage shortcut directory
+    let homepage_shortcuts_bookmarks_dir = await this.bunch_of_settings.get('homepage_shortcuts_bookmarks_dir') as string;
+    AppStorage.set('homepage_shortcuts_bookmarks_dir', homepage_shortcuts_bookmarks_dir);
+    // console.log("[Meow][meowAppSettings] Finished loading homepage shortcuts dir: '" + homepage_shortcuts_bookmarks_dir + "'.");
+
+    // Get whether to show resource monitor
+    this.resource_monitor = await this.bunch_of_settings.get('resource_monitor') as boolean;
+
+    // History indexer
+    this.history_index_size = await this.bunch_of_settings.get('history_index_size') as number;
+
+    // Downloads
+    this.direct_download = await this.bunch_of_settings.get('direct_download') as boolean;
+    this.direct_download_auto_open = await this.bunch_of_settings.get('direct_download_auto_open') as boolean;
+
+    // Continuation
+    this.continuation_auto_close_tab = await this.bunch_of_settings.get('continuation_auto_close_tab') as boolean;
+    this.continuation_auto_exit = await this.bunch_of_settings.get('continuation_auto_exit') as boolean;
+
+    // Tag status
+    this.settings_init_retrieved = true;
+  }
+
+  on_open_or_close_panel() {
+    if (this.showing_app_settings) {
+      // Open panel
+      this.on_panel_open();
+    } else {
+      // Close panel
+      this.on_panel_close();
+    }
+    // Get length properties
+    this.history_length = this.bunch_of_history.get_history_this_month().length;
+  }
+
+  on_panel_open() {
+    // Sync Data
+    if (this.title_bar_position == "top") {
+      this.title_bar_position_selected = [0];
+    } else {
+      this.title_bar_position_selected = [1];
+    }
+    if (this.tabs_style == "vertical") {
+      this.tabs_style_selected = [0];
+    } else {
+      this.tabs_style_selected = [1];
+    }
+    if (this.tabs_style_non_tablet_mode == "vertical") {
+      this.tabs_style_selected_non_tablet_mode = [0];
+    } else {
+      this.tabs_style_selected_non_tablet_mode = [1];
+    }
+    if (this.bunch_of_tabs.new_tab_url !== undefined) {
+      this.new_tab_url_edit = this.bunch_of_tabs.new_tab_url;
+    }
+    if (this.bunch_of_tabs.home_url !== undefined) {
+      this.home_url_edit = this.bunch_of_tabs.home_url;
+    }
+    if (this.bunch_of_tabs.start_up !== undefined) {
+      let start_up = this.bunch_of_tabs.start_up;
+      if (start_up == "new tab") {
+        this.start_up_page_selected = [0];
+      }
+      if (start_up == "home") {
+        this.start_up_page_selected = [1];
+      }
+      if (start_up == "continue") {
+        this.start_up_page_selected = [2];
+      }
+    }
+    // Calculate web cache size
+    // this.web_cache_size = get_folder_size_Sync('/data/storage/el2/base/cache/web/Cache', true)
+  }
+
+  on_panel_close() {
+
+  }
+
+  // On settings changed
+
+  on_title_bar_position_changed() {
+    let result: string = "";
+    if (this.title_bar_position_selected[0] == 0) {
+      result = 'top';
+    } else {
+      result = 'bottom';
+    }
+    this.title_bar_position = result;
+    this.bunch_of_settings.set('title_bar_position', result);
+  }
+
+  on_start_up_page_changed() {
+    let result: string = "";
+    if (this.start_up_page_selected[0] == 0) {
+      result = "new tab";
+    } else if (this.start_up_page_selected[0] == 1) {
+      result = "home";
+    } else {
+      result = "continue";
+      // Need modify after update
+    }
+    this.bunch_of_tabs.start_up = result;
+    // kv_store_put("start_up_option", result);
+    this.bunch_of_settings.set('start_up_option', result);
+  }
+
+  on_tabs_style_changed() {
+    let result: string = "";
+    if (this.tabs_style_selected[0] == 0) {
+      result = 'vertical';
+    } else {
+      result = 'horizontal';
+    }
+    this.tabs_style = result;
+    this.bunch_of_settings.set('tabs_style', result);
+  }
+
+  on_tabs_style_non_tablet_mode_changed() {
+    let result: string = "";
+    if (this.tabs_style_selected_non_tablet_mode[0] == 0) {
+      result = 'vertical';
+    } else {
+      result = 'horizontal';
+    }
+    this.tabs_style_non_tablet_mode = result;
+    this.bunch_of_settings.set('tabs_style_non_tablet_mode', result);
+  }
+
+  on_color_change() {
+    this.title_bar_position_tabOptions = SegmentButtonOptions.tab({
+      buttons: [{ text: '  󰀐  ' }, { text: '  󰃐  ' }] as ItemRestriction<SegmentButtonTextItem>,
+      selectedBackgroundColor: this.color_current_font,
+      selectedFontColor: this.color_current_primary
+    })
+    this.tabs_style_tabOptions = SegmentButtonOptions.tab({
+      buttons: [{ text: $r('app.string.Settings_appearance_tabs_style_vertical') },
+        { text: $r('app.string.Settings_appearance_tabs_style_horizontal') }] as ItemRestriction<SegmentButtonTextItem>,
+      selectedBackgroundColor: this.color_current_font,
+      selectedFontColor: this.color_current_primary
+    })
+    this.start_up_page_tabOptions = SegmentButtonOptions.tab({
+      buttons: [
+        { text: $r('app.string.Settings_start_up_new_tab') },
+        { text: $r('app.string.Settings_start_up_home') },
+        { text: $r('app.string.Settings_start_up_continue') }
+      ] as ItemRestriction<SegmentButtonTextItem>,
+      selectedBackgroundColor: this.color_current_font,
+      selectedFontColor: this.color_current_primary
+    })
+  }
+
+  // Events
+
+  on_prefer_hand_left_or_right_changed() {
+    if (this.left_or_right_selected[0] == 0) {
+      this.bunch_of_settings.set('preferred_hand_left_or_right', 'left');
+      this.preferred_hand_left_or_right = 'left';
+    } else {
+      this.bunch_of_settings.set('preferred_hand_left_or_right', 'right');
+      this.preferred_hand_left_or_right = 'right';
+    }
+  }
+
+  // Operations
+
+  set_new_tab_url_and_kv_store() {
+    this.bunch_of_tabs.new_tab_url = this.new_tab_url_edit;
+    // kv_store_put("new_tab_url", this.new_tab_url_edit);
+    this.bunch_of_settings.set('new_tab_url', this.new_tab_url_edit);
+  }
+
+  set_home_url_and_kv_store() {
+    this.bunch_of_tabs.home_url = this.home_url_edit;
+    // kv_store_put("home_url", this.home_url_edit);
+    this.bunch_of_settings.set('home_url', this.home_url_edit);
+  }
+
+  refresh_web_cache_size() {
+    this.calculating_web_cache_size = true;
+    get_sandbox_folder_size('/data/storage/el2/base/cache/web/', false).then(result => {
+      this.web_cache_size = result;
+      this.bunch_of_settings.set('webview_cache_size', this.web_cache_size);
+      this.calculating_web_cache_size = false;
+    })
+  }
+
+  refresh_history_index_size() {
+    this.calculating_history_index_size = true;
+    get_sandbox_folder_size('history-index', true).then(result => {
+      this.history_index_size = result;
+      this.bunch_of_settings.set('history_index_size', this.history_index_size);
+      this.calculating_history_index_size = false;
+    })
+  }
+
+  scroll_settings_to() {
+    if (this.settings_scroll_to < 0) {
+      return;
+    }
+    this.settings_scroll_to_highlight_timeout = 1200;
+    this.waterFlow_control.scrollToIndex(this.settings_scroll_to, true);
+  }
+
+  version_clicked() {
+    this.version_clicked_times += 1;
+    if (this.version_clicked_times == 7) {
+      // Enter Dev mode
+      this.version_clicked_times += 1;
+      this.bunch_of_settings.set('DEV_MODE', true);
+    }
+  }
+
+  // Kinda constants
+
+  // TODO: optimize duplicate code
+  visibility_of_left_menu() {
+    if (!this.tablet_mode) {
+      // Non tablet mode NO vertical menu!
+      return Visibility.None;
+    }
+    let status = this.preferred_hand_left_or_right == 'left';
+    if (this.preferred_hand_reverse_settings_menu) {
+      status = !status;
+    }
+    return status ? Visibility.Visible : Visibility.None;
+  }
+
+  visibility_of_right_menu() {
+    if (!this.tablet_mode) {
+      // Non tablet mode NO vertical menu!
+      return Visibility.None;
+    }
+    let status = this.preferred_hand_left_or_right == 'right';
+    if (this.preferred_hand_reverse_settings_menu) {
+      status = !status;
+    }
+    return status ? Visibility.Visible : Visibility.None;
+  }
+}
+
+export default meowAppSettings
+
+// Cards
+
+@Component
+struct settingsCard {
+  // Colors
+  @StorageProp('color_current_primary') color_current_primary: ResourceColor = $r('app.color.start_window_background');
+  @StorageProp('color_current_font') color_current_font: ResourceColor = $r('app.color.font_color_title');
+  // Contents
+  @BuilderParam contents: () => void;
+  @Prop title: ResourceStr = $r('app.string.Settings_start_up');
+  // Outline
+  @Prop highlight: boolean = false;
+  @Prop timeout: number = 0;
+
+  build() {
+    Column({ space: 10 }) {
+      Text(this.title)
+        .fontColor(this.color_current_font)
+        .fontSize(fontSize_Extra())
+        .fontWeight(FontWeight.Bold)
+        .opacity((!this.highlight && this.timeout > 0) ? 0.5 : 0.8)
+        .animation(animation_default())
+        .padding({ left: 5 })
+
+      Column({ space: 10 }) {
+        this.contents();
+      }
+      // .borderColor((this.highlight && this.timeout > 0) ? this.color_current_font : this.color_current_primary)
+      // .opacity((!this.highlight && this.timeout > 0) ? 0.5 : 1)
+      // .animation(animation_default())
+      // .padding(13)
+      .alignItems(HorizontalAlign.Start)
+      .justifyContent(FlexAlign.Start)
+      .width("100%")
+
+      // .borderRadius(10)
+      // .borderWidth(2)
+      // .backgroundColor(this.color_current_primary)
+    }
+    .alignItems(HorizontalAlign.Start)
+    .justifyContent(FlexAlign.Start)
+  }
+}
+
+@Component
+struct settingsSubCard {
+  // Colors
+  @StorageProp('color_current_primary') color_current_primary: ResourceColor = $r('app.color.start_window_background');
+  @StorageProp('color_current_font') color_current_font: ResourceColor = $r('app.color.font_color_title');
+  // Contents
+  @BuilderParam contents: () => void;
+  // Outline
+  @Prop highlight: boolean = false;
+  @Prop timeout: number = 0;
+  // Title
+  @Prop icon: ResourceStr | undefined = undefined;
+  @Prop title: ResourceStr | undefined = undefined;
+  // Stats
+  @State show: boolean = false;
+
+  build() {
+    Column() {
+      if (this.title) {
+        linysTextSubtitleDivision({ icon: this.icon, text: this.title }) // Title
+          .padding({ top: 12, left: 12, right: 12 })
+          .width('100%')
+          .clickEffect(click_effect_default())
+          .onClick(() => {
+            this.show = !this.show;
+          })
+      }
+      Column({ space: 12 }) {
+        this.contents();
+      }
+      .clip(true)
+      .padding({ top: 12, left: 12, right: 12 })
+      .alignItems(HorizontalAlign.Start)
+      .justifyContent(FlexAlign.Start)
+      .visibility((this.title == undefined || this.show) ? Visibility.Visible : Visibility.None)
+      .animation(animation_default())
+    }
+    .padding({ bottom: 12 })
+    .alignItems(HorizontalAlign.Start)
+    .justifyContent(FlexAlign.Start)
+    .borderColor((this.highlight && this.timeout > 0) ? this.color_current_font : this.color_current_primary)
+    .opacity((!this.highlight && this.timeout > 0) ? 0.5 : 1)
+    .animation(animation_default())
+    .width("100%")
+    .borderRadius(10)
+    .borderWidth(2)
+    .backgroundColor(this.color_current_primary)
+  }
+}
+
+// Jump Menu
+
+
+@Component
+struct menuScrollVertical {
+  @Link on_index_first: number;
+  @Link on_index_last: number;
+  @Link scroll_to_gateway: number;
+  @State menu_text: string[] = [];
+  @State button_Lengths: number[] = new Array(this.menu_text.length).fill(0);
+  // Colors
+  @StorageProp('color_current_primary') color_current_primary: ResourceColor = $r('app.color.start_window_background');
+  @StorageProp('color_current_secondary') color_current_secondary: ResourceColor = $r('app.color.block_color');
+  @StorageProp('color_current_font') color_current_font: ResourceColor = $r('app.color.font_color_title');
+
+  build() {
+    Scroll() {
+      Column() {
+        ForEach(this.menu_text, (item: string, index: number) => {
+          menuItem({
+            my_text: $r('app.string.'.concat(item)),
+            my_index: index,
+            on_index_first: this.on_index_first,
+            on_index_last: this.on_index_last,
+            scroll_to_gateway: this.scroll_to_gateway,
+          })
+            .onAreaChange((_old, n) => {
+              // Record button
+              this.button_Lengths[index] = n.width as number;
+            })
+            .backgroundColor(this.on_index_first <= index && index <= this.on_index_last ?
+            this.color_current_font : "transparent")
+            .borderRadius(this.border_radius(index))
+            .animation(animation_default())
+        })
+      }
+      .alignItems(HorizontalAlign.End)
+    }
+    .align(Alignment.Top)
+    .edgeEffect(EdgeEffect.Spring)
+    .height("100%")
+    .scrollable(ScrollDirection.Vertical)
+    .scrollBar(BarState.Off)
+  }
+
+  border_radius(index: number) {
+    if (index == this.on_index_first && index == this.on_index_last) {
+      // If I am the only one in range
+      return 10;
+    }
+
+    let topLeft = 0;
+    let topRight = 0;
+    let bottomLeft = 0;
+    let bottomRight = 0;
+
+    if (index == this.on_index_first) {
+      topLeft = 10;
+      topRight = 10;
+    }
+    if (index == this.on_index_last) {
+      bottomLeft = 10;
+      bottomRight = 10;
+    }
+
+    if (index + 1 < this.menu_text.length) {
+      if (this.button_Lengths[index] != this.button_Lengths[index + 1] &&
+        this.button_Lengths[index] + 10 > this.button_Lengths[index + 1]) {
+        bottomLeft = 10;
+      }
+    }
+
+    if (index - 1 >= 0) {
+      if (this.button_Lengths[index] != this.button_Lengths[index - 1] &&
+        this.button_Lengths[index] + 10 > this.button_Lengths[index - 1]) {
+        topLeft = 10;
+      }
+    }
+
+    let ra: BorderRadiuses = {
+      topLeft: topLeft,
+      topRight: topRight,
+      bottomLeft: bottomLeft,
+      bottomRight: bottomRight
+    };
+    return ra;
+  }
+}
+
+@Component
+struct menuItem {
+  @State my_index: number = 0;
+  @State my_text: ResourceStr = "";
+  @Link on_index_first: number;
+  @Link on_index_last: number;
+  @Link scroll_to_gateway: number;
+  // Colors
+  @StorageProp('color_current_primary') color_current_primary: ResourceColor = $r('app.color.start_window_background');
+  @StorageProp('color_current_secondary') color_current_secondary: ResourceColor = $r('app.color.block_color');
+  @StorageProp('color_current_font') color_current_font: ResourceColor = $r('app.color.font_color_title');
+
+  build() {
+    Row() {
+      Text(this.my_text)
+        .fontColor(this.on_index_first <= this.my_index && this.my_index <= this.on_index_last ?
+        this.color_current_secondary : this.color_current_font)
+        .fontSize(fontSize_Large() - 2)
+    }
+    .clickEffect(click_effect_default())
+    .padding(7)
+    .animation(animation_default())
+    .onClick(() => {
+      this.scroll_to_gateway = -1;
+      this.scroll_to_gateway = this.my_index;
+    })
+  }
+}

+ 979 - 0
home/src/main/ets/blocks/modules/meowBookmarks.ets

@@ -0,0 +1,979 @@
+import { bookmark, bunch_of_bookmarks, folder, unified_item } from '../../hosts/bunch_of_bookmarks';
+import { bunch_of_tabs } from '../../hosts/bunch_of_tabs';
+import { url_resource_to_meow } from '../../utils/url_tools';
+import linysSymbol from '../../components/texts/linysSymbol';
+import { document_pick_to_text, document_save_text, sandbox_read_text_sync, sandbox_save } from '../../utils/storage_tools';
+import { animation_default, capsule_bar_height, click_effect_default, fontSize_Large, fontSize_Normal } from '../../hosts/bunch_of_defaults';
+import { kv_store_get } from '../../utils/kv_store_tools';
+import linysShowButton from '../../components/buttons/linysShowButton';
+import linysCapsuleButton from '../../components/buttons/linysCapsuleButton';
+import woofSelectBookmarksPath from '../../dialogs/prompts/woofSelectBookmarksPath';
+import linysPathTree from '../../components/linysPathTree';
+import linysTimeoutButton from '../../components/buttons/linysTimeoutButton';
+import linysCapsuleButtonWithText from '../../components/buttons/linysCapsuleButtonWithText';
+
+@Component
+struct meowBookmarks {
+  // Hosts
+  @StorageLink('bunch_of_bookmarks') bunch_of_bookmarks: bunch_of_bookmarks = new bunch_of_bookmarks("Bookmarks~Meow");
+  @StorageLink('bunch_of_tabs') bunch_of_tabs: bunch_of_tabs = new bunch_of_tabs(true);
+  @State viewing_contents: unified_item[] = this.bunch_of_bookmarks.root.get_content();
+  @State @Watch('refresh_content_view') looking_at_path: string = "";
+  @State current_folder: folder | undefined = undefined;
+  // Environments
+  @StorageProp('tablet_mode') tablet_mode: boolean = false;
+  // UI control actions
+  @StorageLink('showing_tabs') showing_tabs: boolean = false;
+  @StorageLink('showing_bookmarks') showing_bookmarks: boolean = false;
+  @StorageLink('swapping_bookmarks') swapping_bookmarks: boolean = false;
+  bookmarks_scroller: Scroller = new Scroller();
+  @StorageLink('bookmarks_scroll_by') @Watch('scroll_bookmarks') bookmarks_scroll_by: number = 0;
+  @StorageLink('swapping_offset_timeout') swapping_offset_timeout: number = 0;
+  @State swapping_offset_upper: number = 0;
+  @State swapping_offset_downer: number = 0;
+  // Settings / Accessibility
+  @StorageLink('preferred_hand_left_or_right') preferred_hand_left_or_right: string = 'right';
+  @StorageLink('tabs_style') tabs_style: string = "";
+  @StorageLink('tabs_style_non_tablet_mode') tabs_style_non_tablet_mode: string = "";
+  @StorageLink('preferred_hand_reverse_tabs_panel') preferred_hand_reverse_tabs_panel: boolean = false;
+  // Input and Interactions
+  @StorageLink('bookmark_add_label') add_label: string = "";
+  @StorageLink('bookmark_add_link') add_link: string = "";
+  @State scroll_area_height: number = 0;
+  @State adding_folder: boolean = false;
+  @StorageLink('adding_bookmark') adding_bookmark: boolean = false;
+  @State showing_navigator: boolean = false;
+  @State importing_bookmarks: boolean = false;
+  @State exporting_bookmarks: boolean = false;
+  // Dialogs
+  @StorageLink('universal_fail_prompt_desc_gateway') uni_fail_prompt_gateway: ResourceStr = "";
+  // Colors
+  @StorageProp('color_current_primary') color_current_primary: ResourceColor = $r('app.color.start_window_background');
+  @StorageProp('color_current_secondary') color_current_secondary: ResourceColor = $r('app.color.block_color');
+  @StorageProp('color_current_font') color_current_font: ResourceColor = $r('app.color.font_color_title');
+  // Pan Gesture
+  pan_x: number = 0;
+
+  build() {
+    Column() {
+      Scroll(this.bookmarks_scroller) {
+        Column({ space: 5 }) {
+          Column() {
+            Column()
+              .height(this.swapping_offset_upper)
+            if (this.swapping_bookmarks) {
+              Column()
+                .height(Math.max(0, this.scroll_area_height - 5 - this.viewing_contents.length * (42 + 5)))
+            }
+          }
+
+          ForEach(this.viewing_contents, (_unified_item: unified_item, key: number) => {
+            bookmarkItem({
+              // Generals
+              my_index: key,
+              viewing_contents: this.viewing_contents, // For editing
+              looking_at_path: this.looking_at_path, // the path of now showing list (directory) on the panel
+              // To ask webViews to show
+              height_of_panel: this.scroll_area_height, // for animation params
+              parent_path: this.looking_at_path, // for get_my_path()
+            });
+          },)
+
+          Column()
+            .height(this.swapping_offset_downer)
+        }
+        .width("100%")
+      } // List of this directory
+      .direction(Direction.Rtl)
+      // .scrollBar((this.scroll_area_height - 5 - this.viewing_contents.length * (42 + 5) < 40) ? BarState.Off : BarState.On)
+      .align(Alignment.Bottom)
+      .edgeEffect(EdgeEffect.Spring)
+      .width("100%")
+      .layoutWeight(1)
+      .margin({ bottom: 10 })
+      .onAreaChange((_o, n) => {
+        this.scroll_area_height = n.height as number;
+        // Update height of scroll area
+        // So that bookmark buttons will know if they are out of visible area
+        // and reduce their appearance animation time.
+      })
+      .nestedScroll({scrollForward:NestedScrollMode.SELF_ONLY,scrollBackward:NestedScrollMode.SELF_ONLY})
+
+      Row({ space: this.tablet_mode ? 12 : 9 }) {
+        linysSymbol({ symbol_glyph_target: 'sys.symbol.arrow_left' })// Go back button
+          .onClick(() => {
+            this.go_back();
+          })
+          .visibility(this.looking_at_path == "" || this.swapping_bookmarks ? Visibility.None : this.visible_when_no_operating())
+          .animation(animation_default())
+
+        linysShowButton({
+          symbol_glyph_target: 'sys.symbol.swap',
+          show: this.swapping_bookmarks,
+          text: $r('app.string.Bookmarks_move_order')
+        })// Swap Button
+          .onClick(() => {
+            this.importing_bookmarks = false;
+            this.exporting_bookmarks = false;
+            this.adding_folder = false;
+            this.adding_bookmark = false;
+            this.swapping_bookmarks = !this.swapping_bookmarks;
+          })
+
+        Blank()
+          .visibility(this.preferred_hand_left_or_right == 'right' ? Visibility.Visible : Visibility.None)
+          .animation(animation_default())
+
+        Column() {
+          linysShowButton({
+            show: this.importing_bookmarks,
+            symbol_glyph_target: 'sys.symbol.arrow_down_and_rectangle_on_rectangle',
+            text: $r('app.string.Index_import_bookmarks_title')
+          })
+            .onClick(() => {
+              this.adding_folder = false;
+              this.adding_bookmark = false;
+              this.exporting_bookmarks = false;
+              this.importing_bookmarks = !this.importing_bookmarks;
+              // this.import_bookmarks_html();
+            })
+        } // Import bookmarks button
+        .alignItems(HorizontalAlign.End)
+        .visibility(this.swapping_bookmarks ? Visibility.None : Visibility.Visible)
+        .animation(animation_default())
+
+        linysShowButton({
+          show: this.exporting_bookmarks,
+          symbol_glyph_target: 'sys.symbol.arrow_right_up_and_square',
+          text: $r('app.string.Index_export_bookmarks_title')
+        })
+          .onClick(() => {
+            this.adding_folder = false;
+            this.adding_bookmark = false;
+            this.importing_bookmarks = false;
+            this.exporting_bookmarks = !this.exporting_bookmarks;
+            // this.export_bookmarks_html();
+          })// Export bookmarks button
+          .visibility(this.swapping_bookmarks ? Visibility.None : Visibility.Visible)
+          .animation(animation_default())
+        linysShowButton({
+          show: this.adding_folder,
+          symbol_glyph_target: 'sys.symbol.folder_badge_plus',
+          text: $r('app.string.Index_add_folder_title')
+        })
+          .onClick(() => {
+            this.adding_bookmark = false;
+            this.exporting_bookmarks = false;
+            this.importing_bookmarks = false;
+            this.add_label = "";
+            this.adding_folder = !this.adding_folder;
+          })// Add folder button
+          .visibility(this.swapping_bookmarks ? Visibility.None : Visibility.Visible)
+          .animation(animation_default())
+        linysShowButton({
+          show: this.adding_bookmark,
+          symbol_glyph_target: 'sys.symbol.plus_square',
+          text: $r('app.string.Index_add_bookmark_title')
+        })
+          .onClick(() => {
+            this.adding_folder = false;
+            this.exporting_bookmarks = false;
+            this.importing_bookmarks = false;
+            this.add_label = this.bunch_of_tabs.workingMainTab().title;
+            this.add_link = url_resource_to_meow(this.bunch_of_tabs.workingMainTab().url);
+            this.adding_bookmark = !this.adding_bookmark;
+          })// Add bookmark button
+          .visibility(this.swapping_bookmarks ? Visibility.None : Visibility.Visible)
+          .animation(animation_default())
+      } // Operation buttons
+      .constraintSize({ minHeight: 36 })
+      .width("100%")
+      .margin({ bottom: 5 })
+
+      Scroll() {
+        Column({ space: 10 }) {
+          Row({ space: 10 }) {
+            linysSymbol({ symbol_glyph_target: "sys.symbol.rename" })
+            TextInput({ text: this.add_label })
+              .onChange((value) => {
+                this.add_label = value;
+              })
+              .fontWeight(FontWeight.Regular)
+              .fontColor(this.color_current_font)
+              .caretColor(this.color_current_font)
+              .selectedBackgroundColor(this.color_current_font)
+              .height(capsule_bar_height())
+              .selectAll(true)
+              .layoutWeight(1)
+              .onSubmit(() => {
+                if (this.adding_folder) {
+                  this.new_folder();
+                }
+              })
+
+          } // Edit Label TextInput
+          .visibility(this.adding_folder || this.adding_bookmark ? Visibility.Visible : Visibility.None)
+          .animation(animation_default())
+
+          Row({ space: 10 }) {
+            linysSymbol({ symbol_glyph_target: "sys.symbol.paperclip" })
+            TextInput({ text: this.add_link })
+              .onChange((value) => {
+                this.add_link = value;
+              })
+              .fontWeight(FontWeight.Regular)
+              .fontColor(this.color_current_font)
+              .caretColor(this.color_current_font)
+              .selectedBackgroundColor(this.color_current_font)
+              .selectAll(true)
+              .layoutWeight(1)
+              .onSubmit(() => {
+                this.new_bookmark();
+              })
+              .height(capsule_bar_height())
+
+          } // Edit link TextInput
+          .visibility(this.adding_bookmark ? Visibility.Visible : Visibility.None)
+          .animation(animation_default())
+
+          linysCapsuleButtonWithText({
+            desc_text: this.exporting_bookmarks || this.importing_bookmarks ? $r('app.string.Index_select_file') : '',
+            button_text: this.adding_folder || this.adding_bookmark ? "  󰀓  " : "  󰃟  ",
+            onExecution: () => {
+              if (this.adding_folder) {
+                this.new_folder()
+              }
+              if (this.adding_bookmark) {
+                this.new_bookmark()
+              }
+              if (this.importing_bookmarks) {
+                this.import_bookmarks_html()
+              }
+              if (this.exporting_bookmarks) {
+                this.export_bookmarks_html()
+              }
+            }
+          })// Submit add button
+            .visibility(this.adding_folder || this.adding_bookmark || this.exporting_bookmarks ||
+            this.importing_bookmarks ? Visibility.Visible :
+            Visibility.None)
+            .animation(animation_default())
+
+        }
+        .width("100%")
+      } // Edit panel
+      .height(this.get_edit_area_height())
+      .animation(animation_default())
+      .scrollBar(BarState.Off)
+
+      Text(this.text_path())// Display of list menu quick jump
+        .fontColor(this.color_current_font)
+        .fontWeight(FontWeight.Bold)
+        .fontSize(fontSize_Normal())
+        .textAlign(this.preferred_hand_left_or_right == 'right' ? TextAlign.End : TextAlign.Start)
+        .width("100%")
+        .margin({ top: 5, bottom: 5 })
+        .opacity(0.7)
+        .animation(animation_default())
+        .clickEffect(click_effect_default())
+        .onClick(() => {
+          this.showing_navigator = !this.showing_navigator;
+        })
+
+      linysPathTree({
+        current_viewing_path: this.looking_at_path,
+      })// Path indicator
+        .height(this.showing_navigator ? undefined : 0)
+        .animation(animation_default())
+
+    } // List of directory, Operation buttons and edit TextInputs
+    .justifyContent(FlexAlign.End)
+    .width("100%")
+    .height("100%")
+    .onAppear(() => {
+      // console.log("[Meow][meowBookmarks] Bookmarks READY")
+      this.on_appear();
+    })
+    .gesture(
+      PanGesture({ direction: PanDirection.Left | PanDirection.Right })
+        .onActionStart(() => {
+          // console.info('Pan start');
+          this.pan_x = 0;
+        })
+        .onActionUpdate((e) => {
+          this.pan_x += e.offsetX;
+          // console.log(this.pan_x.toString())
+        })
+        .onActionEnd(() => {
+          // this.positionX = this.offsetX;
+          // this.positionY = this.offsetY;
+          // console.info('Pan end');
+          // console.info('Pan end timeStamp is: ' + event.timestamp);
+          if (this.current_tabs_style() == 'vertical') {
+            if (this.pan_x < 0) {
+              // ← CLose if Left align
+              if (!this.is_right_align()) {
+                this.showing_bookmarks = false;
+              }
+            } else {
+              // → Switch to Tabs
+              this.showing_bookmarks = false;
+              this.showing_tabs = true;
+            }
+          } else {
+            // Directly Close if Horizontal tab
+            if (this.pan_x > 0 && this.is_right_align()) {
+              // →
+              this.showing_bookmarks = false;
+            }
+            if (this.pan_x < 0 && !this.is_right_align()) {
+              // →
+              this.showing_bookmarks = false;
+            }
+          }
+        })
+    )
+  }
+
+  // Event
+
+  async on_appear() {
+    let result: string = sandbox_read_text_sync('html_bookmarks.html');
+    if (result == "undefined") {
+      // Check old kv_store
+      result = await kv_store_get("html_bookmarks") as string;
+      sandbox_save('html_bookmarks.html', result);
+    }
+    if (result == "undefined") {
+      // First use bookmarks
+    } else {
+      let html_bookmarks = result;
+      this.bunch_of_bookmarks.import_html(html_bookmarks, true);
+      this.refresh_content_view();
+    }
+    // Get bookmarks
+    setInterval(() => {
+      if (this.swapping_offset_timeout == 100) {
+        animateTo(animation_default(), () => {
+          this.swapping_offset_upper = 0;
+          this.swapping_offset_downer = 0;
+        })
+      }
+      this.swapping_offset_timeout -= 100;
+      if (this.swapping_offset_timeout <= 0) {
+        this.swapping_offset_timeout = 0;
+      }
+    }, 100)
+  }
+
+  // Misc
+
+  get_edit_area_height() {
+    let area_height = 0;
+    area_height += this.adding_bookmark ? 145 : 0;
+    area_height += this.adding_folder ? 100 : 0;
+    area_height += this.importing_bookmarks || this.exporting_bookmarks ? 54 : 0;
+    return area_height;
+  }
+
+  refresh_content_view() {
+    if (this.looking_at_path == "") {
+      // Go to root
+      this.viewing_contents = this.bunch_of_bookmarks.root.get_content();
+    } else {
+      // Go to a directory
+      this.current_folder = this.bunch_of_bookmarks.get_folder(this.looking_at_path);
+      if (this.current_folder !== undefined) {
+        this.viewing_contents = this.current_folder.get_content();
+      }
+    }
+    // this.bookmarks_scroller.scrollTo({ xOffset: 0, yOffset: this.scroll_area_height * 0.75, animation: false });
+  }
+
+  visible_when_no_operating() {
+    if (this.exporting_bookmarks || this.adding_folder || this.adding_bookmark || this.importing_bookmarks) {
+      return Visibility.None;
+    } else {
+      return Visibility.Visible;
+    }
+  }
+
+  // Operations
+
+  scroll_bookmarks() {
+    if (this.bookmarks_scroll_by == 0) {
+      return;
+    }
+    // this.swapping_offset_timeout = 300;
+    if (this.bookmarks_scroll_by > 0) {
+      this.swapping_offset_downer += this.bookmarks_scroll_by;
+      this.bookmarks_scroller.scrollBy(0, this.bookmarks_scroll_by);
+    } else {
+      this.swapping_offset_upper -= this.bookmarks_scroll_by;
+    }
+    this.bookmarks_scroll_by = 0;
+  }
+
+  new_folder() {
+    if (this.add_label == "") {
+      // Cannot add for no label.
+      this.uni_fail_prompt_gateway = $r('app.string.Fail_desc_no_name');
+      return;
+    }
+    let result = this.bunch_of_bookmarks.add_folder(new folder(this.add_label), this.looking_at_path);
+
+    if (!result) {
+      // If add failed
+      // Perhaps due to a name crash
+      this.uni_fail_prompt_gateway = $r("app.string.Fail_desc_folder_current_name_crash");
+      return;
+    }
+
+    this.adding_folder = false;
+    this.refresh_content_view();
+    this.save_bookmarks();
+  }
+
+  new_bookmark() {
+    if (this.add_label == "") {
+      // Cannot add for no label or no link.
+      this.uni_fail_prompt_gateway = $r('app.string.Fail_desc_no_name');
+      return;
+    }
+    if (this.add_link == "") {
+      // Cannot add for no label or no link.
+      this.uni_fail_prompt_gateway = $r('app.string.Fail_desc_no_link');
+      return;
+    }
+
+    let rest = this.bunch_of_bookmarks.add_bookmark(new bookmark(this.add_label, this.add_link), this.looking_at_path);
+
+    if (!rest) {
+      // If add failed
+      // Perhaps due to a name crash
+      this.uni_fail_prompt_gateway = $r('app.string.Fail_desc_bookmark_current_name_crash');
+      return;
+    }
+
+    this.adding_bookmark = false;
+    this.refresh_content_view();
+    this.save_bookmarks();
+  }
+
+  go_back() {
+    if (this.looking_at_path.includes("/")) {
+      // Pop current folder class
+      let upper_path = this.looking_at_path.split("/");
+      upper_path.pop()
+      // Modify looking_at_path to trigger auto update
+      this.looking_at_path = upper_path.join("/")
+    } else {
+      this.looking_at_path = "";
+      // If current folder is sitting in root folder
+      // or if current folder is exactly the root folder
+    }
+    // Since change of this.looking_at_path will automatically pull up refresh_root_content()
+    // There is no need to add this.refresh_root_content() here manually
+  }
+
+  export_bookmarks_html() {
+    this.exporting_bookmarks = false;
+
+    document_save_text("Bookmarks_" + Math.round(new Date().getTime() / 1000),
+      this.bunch_of_bookmarks.get_export_html().join("\n"))
+  }
+
+  import_bookmarks_html() {
+    this.importing_bookmarks = false;
+    document_pick_to_text().then(result => {
+      if (result == "") {
+        // Selected an empty file or cancelled selection
+        this.uni_fail_prompt_gateway = $r('app.string.Fail_desc_empty_picked_file');
+        return;
+      }
+      let import_result: number = this.bunch_of_bookmarks.import_html(result);
+      this.refresh_content_view();
+      console.log("[Meow][LinysBookmarks] Import result: " + import_result.toString())
+      this.save_bookmarks();
+
+      if (import_result != 0) {
+        // Show Fail Prompt
+        this.uni_fail_prompt_gateway = $r('app.string.Fail_desc_import_bookmarks');
+      }
+    })
+  }
+
+  save_bookmarks() {
+    console.log("[Meow][LinysBookmarks] Started to save HTML to disk!");
+    let html_bookmark: string[] = this.bunch_of_bookmarks.get_export_html();
+    // kv_store_put("html_bookmarks", html_bookmark.join("\n"));
+    sandbox_save('html_bookmarks.html', html_bookmark.join("\n"));
+  }
+
+  // Data
+
+  text_path() {
+    if (this.looking_at_path == "") {
+      if (this.showing_navigator) {
+        return $r('app.string.Bookmarks_root_path_expanded');
+      } else {
+        return $r('app.string.Bookmarks_root_path');
+      }
+    } else {
+      // Normal display
+      return "@ " + this.looking_at_path + "/";
+    }
+  }
+
+  is_right_align() {
+    return this.preferred_hand_left_or_right == 'right' && !this.preferred_hand_reverse_tabs_panel;
+  }
+
+  current_tabs_style() {
+    if (this.tablet_mode) {
+      return this.tabs_style;
+    } else {
+      return this.tabs_style_non_tablet_mode;
+    }
+  }
+}
+
+export default meowBookmarks;
+
+@Component
+struct bookmarkItem {
+  @Prop my_index: number = 0;
+  @StorageLink('bunch_of_bookmarks') bunch_of_bookmarks: bunch_of_bookmarks = new bunch_of_bookmarks("meow");
+  @StorageLink('bunch_of_tabs') bunch_of_tabs: bunch_of_tabs = new bunch_of_tabs(true);
+  @Link looking_at_path: string;
+  @Link viewing_contents: unified_item[];
+  @Prop parent_path: string;
+  @Prop height_of_panel: number;
+  // Public stuffs
+  @State item: unified_item = this.viewing_contents[this.my_index];
+  @State type: string = this.item.get_item().get_type();
+  @State label: string = this.item.get_item().get_label();
+  @State link: string = this.type == "bookmark" ? (this.item.get_item() as bookmark).get_link() : "";
+  @StorageLink('swapping_bookmarks') @Watch('on_swapping_bookmarks') swapping_bookmarks: boolean = false;
+  @StorageLink('bookmarks_scroll_by') bookmarks_scroll_by: number = 0;
+  // Settings / Accessibility
+  @StorageLink('preferred_hand_left_or_right') preferred_hand_left_or_right: string = 'right';
+  // Basic this properties
+  @State pressing: boolean = false;
+  @State show: Visibility = this.swapping_bookmarks ? Visibility.Visible : Visibility.Hidden;
+  @State offset_y: number = this.swapping_bookmarks ? 0 : 50;
+  @State editing: boolean = false;
+  @State press_timing_ok: boolean = false;
+  press_timing: number = 0;
+  bookmark_height_default: number = 42;
+  // Animations statuses
+  @State edit_label: string = this.label;
+  @State edit_link: string = this.link;
+  @StorageLink('swapping_offset_timeout') swapping_offset_timeout: number = 0;
+  // Edit inputs
+  @State @Watch('on_select_change') select_path_result: string | undefined = undefined;
+  @State moving_type: string = "";
+  // Dialogs
+  @StorageLink('universal_fail_prompt_desc_gateway') uni_fail_prompt_gateway: ResourceStr = "";
+  woofBookmarksPathSelector_control: CustomDialogController = new CustomDialogController({
+    builder: woofSelectBookmarksPath({
+      prompt_title: $r('app.string.Bookmarks_move_title'),
+      select: this.select_path_result,
+      looking_at_path: this.looking_at_path
+    }),
+    alignment: DialogAlignment.Center,
+    // showInSubWindow: true,
+    cornerRadius: 16,
+    width: "90%",
+  })
+  // Colors
+  @StorageProp('color_current_primary') color_current_primary: ResourceColor = $r('app.color.start_window_background');
+  @StorageProp('color_current_secondary') color_current_secondary: ResourceColor = $r('app.color.block_color');
+  @StorageProp('color_current_font') color_current_font: ResourceColor = $r('app.color.font_color_title');
+
+  build() {
+    Column() {
+      Row() {
+        Text(this.label)// Title
+          .fontColor(!this.pressing ? this.color_current_font : this.color_current_secondary)
+          .fontWeight(!this.pressing ? FontWeight.Regular : FontWeight.Bold)
+          .animation(animation_default())
+          .padding({ left: 2 })
+          .fontSize(fontSize_Normal())
+          .maxLines(1)
+          .textOverflow({ overflow: TextOverflow.Ellipsis })
+          .layoutWeight(1)
+
+        Scroll() {
+
+          Row({ space: 8 }) {
+            SymbolGlyph($r('sys.symbol.chevron_up'))
+              .fontSize(fontSize_Large())
+              .fontColor([this.color_current_font])
+              .opacity(this.my_index == 0 ? 0.5 : 1)
+              .animation(animation_default())
+              .onClick(() => {
+                this.swapping_offset_timeout = 600;
+                if (this.my_index != 0) {
+                  this.swap(this.my_index, this.my_index - 1);
+                  this.bookmarks_scroll_by = -this.bookmark_height_default - 5;
+                }
+              })
+            SymbolGlyph($r('sys.symbol.chevron_down'))
+              .fontSize(fontSize_Large())
+              .fontColor([this.color_current_font])
+              .opacity(this.my_index == this.viewing_contents.length - 1 ? 0.5 : 1)
+              .animation(animation_default())
+              .onClick(() => {
+                this.swapping_offset_timeout = 600;
+                if (this.my_index != this.viewing_contents.length - 1) {
+                  this.swap(this.my_index, this.my_index + 1);
+                  this.bookmarks_scroll_by = this.bookmark_height_default + 5;
+                }
+              })
+          }
+        } // Edit Icon
+        .scrollable(ScrollDirection.Horizontal)
+        .scrollBar(BarState.Off)
+        .width(this.swapping_bookmarks ? undefined : 0)
+        .animation(animation_default())
+
+        // .margin({ right: 4 })
+
+
+        if (this.type == "folder") {
+          SymbolGlyph($r(!this.pressing ? 'sys.symbol.folder' : 'sys.symbol.chevron_right'))
+            .fontSize(fontSize_Large())
+            .fontWeight(!this.pressing ? FontWeight.Regular : FontWeight.Bold)
+            .fontColor([!this.pressing ? this.color_current_font : this.color_current_secondary])
+            .animation(animation_default())
+            .margin({ left: 10 })
+        } // Folder Icon
+        Scroll() {
+          SymbolGlyph($r('sys.symbol.square_and_pencil'))
+            .fontSize(fontSize_Large())
+            .fontColor([this.color_current_secondary])
+        } // Edit Icon
+        .scrollable(ScrollDirection.Horizontal)
+        .scrollBar(BarState.Off)
+        .width(this.press_timing_ok ? 22 : 0)
+        .margin({ left: this.press_timing_ok ? 10 : 0 })
+        .animation(animation_default())
+
+      } // Bookmark button
+      .border({
+        radius: this.editing ? { topLeft: 10, topRight: 10 } : 10,
+        width: 2,
+        color: "transparent"
+      })
+      .backgroundColor(this.pressing ? this.color_current_font : this.color_current_secondary)
+      .animation(animation_default())
+      .padding(10)
+      .alignRules({
+        middle: { anchor: "__container__", align: HorizontalAlign.Center },
+        top: { anchor: "__container__", align: VerticalAlign.Top }
+      })
+      .onTouch((event) => {
+        if (event.type == TouchType.Up) {
+          // If touch ends
+          this.pressing = false;
+        } else {
+          // If touching
+          if (!this.swapping_bookmarks) {
+            this.pressing = true;
+          } else {
+            this.pressing = false;
+          }
+        }
+      })
+      .onClick(() => {
+        this.on_item_click();
+      })
+      .height(this.bookmark_height_default)
+      .onMouse((e) => {
+        if (e.button == MouseButton.Right && e.action == MouseAction.Press) {
+          // Right click
+          this.editing = !this.editing;
+        }
+      })
+
+      Scroll() {
+        Column({ space: 10 }) {
+          Row({ space: 10 }) {
+            linysSymbol({ symbol_glyph_target: "sys.symbol.rename" })
+            TextInput({ text: this.edit_label })
+              .fontWeight(FontWeight.Regular)
+              .fontColor(this.color_current_font)
+              .caretColor(this.color_current_font)
+              .selectedBackgroundColor(this.color_current_font)
+              .height(capsule_bar_height())
+              .layoutWeight(1)
+              .onChange((value) => {
+                this.edit_label = value;
+              })
+              .onSubmit(() => {
+                if (this.type == "folder") {
+                  this.save_changes_folder();
+                } else {
+                  this.save_changes_bookmark();
+                }
+                this.editing = false;
+              })
+
+          } // Edit label
+          .width("100%")
+
+          Row({ space: 10 }) {
+            linysSymbol({ symbol_glyph_target: "sys.symbol.paperclip" })
+            TextInput({ text: this.edit_link })
+              .onChange((value) => {
+                this.edit_link = value;
+              })
+              .fontWeight(FontWeight.Regular)
+              .fontColor(this.color_current_font)
+              .caretColor(this.color_current_font)
+              .selectedBackgroundColor(this.color_current_font)
+              .layoutWeight(1)
+              .onSubmit(() => {
+                this.save_changes_bookmark();
+                this.editing = false;
+              })
+              .height(capsule_bar_height())
+
+          } // Edit link
+          .width("100%")
+          .visibility(this.type == "bookmark" ? Visibility.Visible : Visibility.None)
+          .animation(animation_default())
+
+          Row({ space: 10 }) {
+            linysCapsuleButton({ text: "  󰂺  " })// Move
+              .onClick(() => {
+                if (this.type == "folder") {
+                  this.move_folder();
+                }
+                if (this.type == "bookmark") {
+                  this.move_bookmark();
+                }
+                this.editing = false;
+              })
+
+            linysTimeoutButton({
+              dialogText: "确定是否删除选中内容",
+              text: "  󰀁  ",
+              onExecution: () => {
+                this.delete_myself()
+              }
+            }) // Delete
+
+            linysCapsuleButton({ text: "  󰀻  " })// Save
+              .onClick(() => {
+                if (this.type == "folder") {
+                  this.save_changes_folder()
+                }
+                if (this.type == "bookmark") {
+                  this.save_changes_bookmark()
+                }
+                this.editing = false;
+              })
+
+          } // Buttons of operations
+          .justifyContent(this.preferred_hand_left_or_right == 'right' ? FlexAlign.End : FlexAlign.Start)
+          .width("100%")
+        }
+        .padding({
+          top: 6,
+          left: 14,
+          right: 14,
+          bottom: 14
+        })
+        .backgroundColor(this.color_current_secondary)
+        .border({
+          radius: { bottomLeft: 10, bottomRight: 10 }
+        })
+
+      } // Edit panel
+      .height(!this.editing ? 0 : (this.type == "bookmark" ? 142 : 0) + (this.type == "folder" ? 98 : 0))
+      .visibility(this.editing ? Visibility.Visible : Visibility.None)
+      .animation(animation_default())
+      .scrollBar(BarState.Off)
+      .nestedScroll({ scrollForward: NestedScrollMode.PARENT_FIRST, scrollBackward: NestedScrollMode.PARENT_FIRST })
+
+    }
+    .width("100%")
+    .visibility(this.show)
+    .offset({ y: this.offset_y })
+    .animation(animation_default())
+    .onAppear(() => {
+      this.pressing = false;
+      setTimeout(() => {
+        this.show = Visibility.Visible;
+        this.offset_y = 0;
+        // Animation of floating up
+      }, this.get_animation_timeout())
+      setInterval(() => {
+        // Count press time
+        if (this.pressing) {
+          this.press_timing += 1;
+        } else {
+          this.press_timing = 0;
+        }
+        this.press_timing_ok = this.press_timing > 20;
+      }, 10)
+    })
+  }
+
+  on_item_click() {
+    if (this.swapping_bookmarks) {
+      return;
+    }
+    if (this.press_timing_ok) {
+      this.editing = !this.editing;
+      return;
+    } // Toggle Edit Panel
+    if (this.type == "folder") {
+      // Open folder
+      this.looking_at_path = this.get_my_path();
+      // Will automatically cause meowBookmarks to update the UI list (@Watch)
+    } else {
+      // Load web
+      this.bunch_of_tabs.stop_onWorkingTab()
+      this.bunch_of_tabs.loadUrl_onWorkingTab((this.item.get_item() as bookmark).get_link())
+    }
+  }
+
+  get_animation_timeout() {
+    let unit_interval = 40;
+    let load_length = this.viewing_contents.length;
+    if (load_length < 5) {
+      unit_interval = 60;
+    } else if (load_length < 10) {
+      unit_interval = 40;
+    } else {
+      unit_interval = 30;
+    }
+    return Math.min(this.my_index, this.height_of_panel / this.bookmark_height_default) * unit_interval;
+  }
+
+  get_my_path() {
+    if (this.parent_path == "") {
+      return this.label;
+    } else {
+      return this.parent_path + "/" + this.label;
+    }
+  }
+
+  delete_myself() {
+    let my_position = "";
+    if (this.parent_path == "") {
+      my_position = this.label;
+    } else {
+      my_position = this.parent_path + "/" + this.label
+    }
+    if (this.type == "bookmark") {
+      this.bunch_of_bookmarks.del_bookmark(my_position)
+    } else {
+      this.bunch_of_bookmarks.del_folder(my_position)
+    }
+    // Delete myself from this.viewing_contents
+
+    let temp = this.looking_at_path;
+    this.looking_at_path = "/refresh";
+    this.looking_at_path = temp;
+    // Refresh root content
+    // Weird as hell but works, fine.
+
+    this.save_bookmarks();
+  }
+
+  save_changes_folder() {
+    this.item.get_item().set_label(this.edit_label);
+    this.label = this.edit_label;
+    this.bunch_of_bookmarks.reconstruct_cached_plain_bookmarks();
+    this.save_bookmarks();
+  }
+
+  save_changes_bookmark() {
+    this.item.get_item().set_label(this.edit_label);
+    (this.item.get_item() as bookmark).set_link(this.edit_link);
+    this.label = this.edit_label;
+    this.link = this.edit_link;
+    this.bunch_of_bookmarks.reconstruct_cached_plain_bookmarks();
+    this.save_bookmarks();
+  }
+
+  // Operations
+
+  move_folder() {
+    this.moving_type = "folder";
+    this.woofBookmarksPathSelector_control.open();
+  }
+
+  move_bookmark() {
+    this.moving_type = "bookmark";
+    this.woofBookmarksPathSelector_control.open();
+  }
+
+  save_bookmarks() {
+    console.log("[Meow][LinysBookmarks] Started to save HTML to disk!")
+    let html_bookmark: string[] = this.bunch_of_bookmarks.get_export_html();
+    // kv_store_put("html_bookmarks", html_bookmark.join("\n"));
+    sandbox_save('html_bookmarks.html', html_bookmark.join("\n"));
+  }
+
+  on_select_change() {
+    // Move folder or bookmark
+
+    if (this.select_path_result === undefined) {
+      return;
+    }
+
+    let operation_result = 0;
+    if (this.moving_type == "folder") {
+      operation_result = this.bunch_of_bookmarks.move_folder(this.get_my_path(), this.select_path_result);
+    } else {
+      // if (this.moving_type == "bookmark")
+      operation_result = this.bunch_of_bookmarks.move_bookmark(this.get_my_path(), this.select_path_result);
+    }
+
+    if (operation_result != 0) {
+      // Show Fail Prompt
+      if (this.moving_type == "folder") {
+        if (operation_result == 2) {
+          this.uni_fail_prompt_gateway = $r("app.string.Fail_desc_folder_destination_name_crash"); // name crash
+        }
+        if (operation_result == 3) {
+          this.uni_fail_prompt_gateway =
+            $r('app.string.Fail_desc_move_bookmarks_folders'); // sub folder in myself
+        }
+      } else { // if is a bookmark
+        if (operation_result == 2) {
+          this.uni_fail_prompt_gateway = $r("app.string.Fail_desc_bookmark_destination_name_crash"); // name crash
+        }
+      }
+    }
+
+    let temp = this.looking_at_path;
+    this.looking_at_path = "/refresh";
+    this.looking_at_path = temp;
+
+    // Refresh root content
+    // Weird as hell but works, fine.
+
+    this.save_bookmarks();
+    this.select_path_result = undefined;
+  }
+
+  swap(idx_a: number, idx_b: number) {
+    this.bunch_of_bookmarks.last_accessed = Date.now();
+    let c = this.viewing_contents[idx_a];
+    this.viewing_contents[idx_a] = this.viewing_contents[idx_b];
+    this.viewing_contents[idx_b] = c;
+    this.save_bookmarks();
+  }
+
+  on_swapping_bookmarks() {
+    if (this.swapping_bookmarks) {
+      this.editing = false;
+    }
+  }
+}

+ 414 - 0
home/src/main/ets/blocks/modules/meowDownloads.ets

@@ -0,0 +1,414 @@
+import linysTextTitle from '../../components/texts/linysTextTitle';
+import { bunch_of_downloads } from '../../hosts/bunch_of_downloads';
+import { animation_default, animation_popup_duration, click_effect_default, minimum_card_width } from '../../hosts/bunch_of_defaults';
+import { request } from '@kit.BasicServicesKit';
+import linysText from '../../components/texts/linysText';
+import { copy } from '../../utils/clipboard_tools';
+import { add_units_to_size, get_download_uri } from '../../utils/storage_tools';
+import linysShowButton from '../../components/buttons/linysShowButton';
+import linysTimeoutButton from '../../components/buttons/linysTimeoutButton';
+import linysCapsuleButton from '../../components/buttons/linysCapsuleButton';
+import { open_file_uri } from '../../utils/link_tools';
+import linysCapsuleButtonWithText from '../../components/buttons/linysCapsuleButtonWithText';
+
+@Component
+struct meowDownloads {
+  @StorageLink('bunch_of_downloads') @Watch('on_download_state_change') bunch_of_downloads: bunch_of_downloads = new bunch_of_downloads(true);
+  @StorageLink('universal_new_download_gateway') @Watch('on_new_download') uni_new_download_gateway: string = "";
+  @StorageLink('showing_downloads') showing_downloads: boolean = false;
+  // Downloads
+  @StorageLink('direct_download') direct_download: boolean = false;
+  @Link screen_width: number;
+  // Colors
+  @StorageProp('color_current_primary') color_current_primary: ResourceColor = $r('app.color.start_window_background');
+  @StorageProp('color_current_secondary') color_current_secondary: ResourceColor = $r('app.color.block_color');
+  @StorageProp('color_current_font') color_current_font: ResourceColor = $r('app.color.font_color_title');
+
+  build() {
+    Column({ space: 10 }) {
+      Column({ space: 5 }) {
+        linysTextTitle({
+          text: $r('app.string.Downloads_notice_title')
+        })
+        linysText({
+          text: $r('app.string.Downloads_notice_content'),
+          max_lines: 20
+        })
+      }
+      .visibility(this.direct_download ? Visibility.None : Visibility.Visible)
+      .padding(15)
+      .alignItems(HorizontalAlign.Start)
+      .justifyContent(FlexAlign.Start)
+      .width("100%")
+      .borderRadius(10)
+      .backgroundColor(this.color_current_primary)
+      .animation(animation_default())
+
+      Column({ space: 5 }) {
+        linysTextTitle({
+          text: $r('app.string.Index_downloads_direct'),
+          max_lines: 20
+        })
+          .width('100%')
+
+        linysText({ text: $r('app.string.Index_downloads_direct_desc'), max_lines: 20 })
+          .width('100%')
+
+        linysCapsuleButtonWithText({
+          desc_text: $r('app.string.Index_downloads_direct_desc_check'),
+          button_text: '  󰃋  ',
+          onExecution: async () => {
+            open_file_uri(await get_download_uri());
+          }
+        })
+          .width('100%')
+      }
+      .visibility(!this.direct_download ? Visibility.None : Visibility.Visible)
+      .padding(15)
+      .width("100%")
+      .borderRadius(10)
+      .backgroundColor(this.color_current_primary)
+      .animation(animation_default())
+
+      WaterFlow() {
+        if (this.bunch_of_downloads.list_of_on_going_tasks.length == 0) {
+          FlowItem() {
+            linysTextTitle({
+              text: "当前数量为空"
+            })
+              .alignRules({
+                center: { anchor: "__container__", align: VerticalAlign.Center },
+                middle: { anchor: "__container__", align: HorizontalAlign.Center },
+              })
+          }
+          .opacity(0.5)
+          .width("100%")
+          .height("100%")
+          .animation(animation_default())
+        }
+
+        ForEach(
+          this.bunch_of_downloads.list_of_on_going_tasks,
+          (_request_task: request.DownloadTask, key: number) => {
+            meowDownloadCard({
+              bunch_of_downloads: this.bunch_of_downloads,
+              my_index: key,
+            })
+          },
+        )
+      }
+      .columnsTemplate(this.bunch_of_downloads.list_of_on_going_tasks.length == 0 ? "1fr" : "1fr ".repeat(Math.ceil(this.screen_width / minimum_card_width())))
+      .rowsGap(10)
+      .columnsGap(10)
+      .width("100%")
+      .edgeEffect(EdgeEffect.Spring)
+      .scrollBar(BarState.Auto)
+      .layoutWeight(1)
+      .animation(animation_default())
+      .onAppear(() => {
+        // console.log("[Meow][meowDownloads] Downloads READY")
+        this.on_appear();
+      })
+    }
+    .height(this.showing_downloads ? "75%" : 0)
+    .margin({ left: 15, right: 15 })
+    .animation(animation_default())
+
+  }
+
+  on_appear() {
+    this.bunch_of_downloads.delete_all_downloaded_files();
+  }
+
+  on_download_state_change() {
+
+  }
+
+  // Gateway
+
+  on_new_download() {
+    if (this.uni_new_download_gateway != "") {
+      this.bunch_of_downloads.start_download_task(this.uni_new_download_gateway);
+      this.uni_new_download_gateway = "";
+    }
+  }
+}
+
+export default meowDownloads
+
+@Component
+struct meowDownloadCard {
+  @Prop my_index: number;
+  @Link @Watch('update_progress') bunch_of_downloads: bunch_of_downloads;
+  @StorageLink('showing_downloads') showing_downloads: boolean = false;
+  // Statuses
+  @State my_progress: number = 0;
+  @State my_file_name: string = "";
+  @State my_full_download_size: number = 0;
+  @State my_current_download_size: number = 0;
+  @State my_current_download_speed: number = 0;
+  @State my_paused: boolean = false;
+  @State my_link: string = "";
+  @State my_additional_info: string[] = [];
+  @State my_failed: boolean = false;
+  // Settings - Accessibility
+  @StorageLink('preferred_hand_left_or_right') preferred_hand_left_or_right: string = 'right';
+  // Display info
+  @State formatted_download_progress: string = "/";
+  @State formatted_download_percentage: string = "%";
+  // UI Effects
+  @State y_off_set: number = 50;
+  @State visible: Visibility = Visibility.Hidden;
+  @State exporting: boolean = false;
+  @State copied: number = 0;
+  @State showing_additional_info: boolean = false;
+  // Colors
+  @StorageProp('color_current_primary') color_current_primary: ResourceColor = $r('app.color.start_window_background');
+  @StorageProp('color_current_secondary') color_current_secondary: ResourceColor = $r('app.color.block_color');
+  @StorageProp('color_current_font') color_current_font: ResourceColor = $r('app.color.font_color_title');
+
+  build() {
+    FlowItem() {
+      Column({ space: 15 }) {
+
+        linysTextTitle({
+          text: '[' + this.formatted_download_percentage + '] ' + this.my_file_name,
+          max_lines: 4,
+        }) // File name
+
+        Row({ space: 10 }) {
+          linysText({
+            text: this.my_link,
+            max_lines: 4,
+          })// File link
+            .clickEffect(click_effect_default())
+            .onClick(() => {
+              copy(this.my_link);
+              this.copied = animation_popup_duration();
+            })
+            .opacity(0.85)
+            .layoutWeight(1)
+
+          linysTextTitle({
+            // text: $r('app.string.Downloads_copied'),
+            text: "当前内容已复制"
+          })// Copied!
+            .offset({ x: this.copied > 0 ? 0 : 50 })
+            .opacity(0.85)
+            .visibility(this.copied > 0 ? Visibility.Visible : Visibility.Hidden)
+            .animation(animation_default())
+        }
+        .visibility(!this.showing_additional_info ? Visibility.Visible : Visibility.None)
+        // .alignItems(HorizontalAlign.Start)
+        .animation(animation_default())
+
+        additionalInfoDisplay({
+          my_additional_info: this.my_additional_info
+        })
+          .visibility(this.showing_additional_info ? Visibility.Visible : Visibility.None)
+          .animation(animation_default())
+
+        Scroll() {
+          Row({ space: 8 }) {
+            linysText({
+              text: $r('app.string.Downloads_failed'),
+            })// Failed
+              .borderRadius(10)
+              .padding(10)
+              .backgroundColor($r('sys.color.comp_background_tertiary'))
+              .clickEffect(click_effect_default())
+              .visibility(this.my_failed ? Visibility.Visible : Visibility.None)
+              .animation(animation_default())
+
+            linysText({
+              text: add_units_to_size(this.my_current_download_speed) + '/s',
+            })// Speed
+              .borderRadius(10)
+              .padding(10)
+              .backgroundColor($r('sys.color.comp_background_tertiary'))
+              .clickEffect(click_effect_default())
+            // linysText({
+            //   text: this.formatted_download_percentage,
+            // }) // Percentage
+            //   .borderRadius(10)
+            //   .padding(10)
+            //   .backgroundColor($r('sys.color.comp_background_tertiary'))
+            //   .clickEffect(click_effect_default())
+            linysText({
+              text: this.formatted_download_progress,
+            })// Progress
+              .borderRadius(10)
+              .padding(10)
+              .backgroundColor($r('sys.color.comp_background_tertiary'))
+              .clickEffect(click_effect_default())
+          } // Status
+        }
+        .scrollBar(BarState.Off)
+        .edgeEffect(EdgeEffect.Spring)
+        .scrollable(ScrollDirection.Horizontal)
+
+        Row({ space: 10 }) {
+          linysShowButton({
+            symbol_glyph_target: 'sys.symbol.info_circle',
+            show: this.showing_additional_info,
+            text: $r('app.string.Index_downloads_additional_info')
+          })
+            .onClick(() => {
+              this.showing_additional_info = !this.showing_additional_info;
+            })
+
+          linysTimeoutButton({
+            dialogText: "确定是否删除该下载任务",
+            text: "  󰀁  ",
+            onExecution: () => {
+              this.delete_task();
+            }
+          })
+            .enabled(!this.exporting)
+
+          linysCapsuleButton({
+            text: this.my_paused ? "  󰂴  " : "  󰂱  ",
+          })// Pause continue
+            .onClick(() => {
+              if (!this.my_failed) {
+                this.pause_or_continue_task();
+              }
+            })
+            .enabled(!this.exporting)
+            .visibility(this.formatted_download_percentage == "100%" ? Visibility.None : Visibility.Visible)
+            .opacity(this.my_failed ? 0.85 : 1)
+            .animation(animation_default())
+
+          linysCapsuleButton({
+            text: "  󰀻  "
+          })// Save
+            .onClick(() => {
+              this.exporting = true;
+              this.export_file();
+            })
+            .enabled(!this.exporting)
+            .visibility(this.formatted_download_percentage == "100%" ? Visibility.Visible : Visibility.None)
+            .animation(animation_default())
+
+        } // Control Buttons
+        .justifyContent(this.preferred_hand_left_or_right == 'right' ? FlexAlign.End : FlexAlign.Start)
+        .animation(animation_default())
+        .width("100%")
+      }
+      .padding(15)
+      .alignItems(HorizontalAlign.Start)
+      .justifyContent(FlexAlign.Start)
+      .width("100%")
+      .borderRadius(10)
+      .backgroundColor(this.color_current_primary)
+      .visibility(this.visible)
+      .offset({ y: this.y_off_set })
+      .animation(animation_default())
+      .clip(true)
+    } // Start up
+    .width("100%")
+    .onAppear(async () => {
+      // Init download information
+      this.update_progress();
+      setTimeout(() => {
+        this.y_off_set = 0;
+        this.visible = Visibility.Visible;
+        // Animation
+      }, 10)
+
+      setInterval(() => {
+        // Reset copy
+        if (this.copied > 0) {
+          this.copied -= 10;
+        }
+      }, 10)
+    })
+  }
+
+  async update_progress() {
+    // console.log('[meowDownloads] update_progress');
+
+    this.my_file_name = this.bunch_of_downloads.list_of_file_names[this.my_index];
+    this.my_full_download_size = this.bunch_of_downloads.list_of_full_size[this.my_index];
+    this.my_current_download_size = this.bunch_of_downloads.list_of_downloaded_size[this.my_index];
+    this.my_current_download_speed = this.bunch_of_downloads.list_of_download_speed[this.my_index];
+    this.my_progress = Number.parseFloat((this.my_current_download_size / this.my_full_download_size * 100).toFixed(2));
+    this.my_paused = this.bunch_of_downloads.list_of_paused[this.my_index];
+    this.my_link = this.bunch_of_downloads.list_of_urls[this.my_index];
+    this.my_additional_info = this.bunch_of_downloads.list_of_additional_info[this.my_index];
+    this.my_failed = this.bunch_of_downloads.list_of_failed[this.my_index];
+
+    this.set_formatted_download_progress();
+    this.set_formatted_download_percentage();
+  }
+
+  pause_or_continue_task() {
+    if (this.my_paused) {
+      this.bunch_of_downloads.continue_task(this.my_index);
+    } else {
+      this.bunch_of_downloads.pause_task(this.my_index);
+    }
+  }
+
+  delete_task() {
+    // Delete
+    this.bunch_of_downloads.delete_task(this.my_index);
+    if (this.bunch_of_downloads.list_of_on_going_tasks.length == 0) {
+      this.showing_downloads = false;
+    }
+  }
+
+  export_file() {
+    this.bunch_of_downloads.save_downloaded_item_to_local(this.my_index).then(() => {
+      this.exporting = false;
+    })
+    // this.bunch_of_downloads.save_downloaded_item_to_download(this.my_index).then((uri_back) => {
+    //   this.exporting = false;
+    //   if (uri_back) {
+    //     open_file_uri(uri_back);
+    //   }
+    // })
+  }
+
+  private set_formatted_download_progress() {
+    let downloaded: string = add_units_to_size(this.my_current_download_size);
+    let total: string = add_units_to_size(this.my_full_download_size);
+
+    if (this.my_full_download_size == -1) {
+      // Undefined end
+      this.formatted_download_progress = downloaded;
+    } else {
+      // Defined end
+      this.formatted_download_progress = downloaded + " / " + total;
+    }
+  }
+
+  private set_formatted_download_percentage() {
+    if (this.my_full_download_size == -1) {
+      this.formatted_download_percentage = "当前数量为空";
+    } else {
+      this.formatted_download_percentage = this.my_progress.toString() + "%";
+    }
+  }
+}
+
+@Component
+struct additionalInfoDisplay {
+  @Prop my_additional_info: string[];
+
+  build() {
+    Scroll() {
+      Column({ space: 4 }) {
+        ForEach(this.my_additional_info, (info: string, _index: number) => {
+          linysText({ text: info })
+            .opacity(0.7)
+        }) // additional info
+      }
+      .margin({ bottom: 15 })
+      .alignItems(HorizontalAlign.Start)
+    }
+    .scrollBar(BarState.On)
+    .scrollable(ScrollDirection.Horizontal)
+    .edgeEffect(EdgeEffect.Spring)
+  }
+}

+ 395 - 0
home/src/main/ets/blocks/modules/meowScratchingBoard.ets

@@ -0,0 +1,395 @@
+import linysCapsuleButton from '../../components/buttons/linysCapsuleButton';
+import linysSymbol from '../../components/texts/linysSymbol';
+import linysText from '../../components/texts/linysText';
+import linysTimeoutButton from '../../components/buttons/linysTimeoutButton';
+import linysTextTitle from '../../components/texts/linysTextTitle';
+import { animation_default, animation_popup_duration, click_effect_default } from '../../hosts/bunch_of_defaults';
+import { copy } from '../../utils/clipboard_tools';
+import { preview_image } from '../../utils/preview_tools';
+import { add_units_to_size, document_save_from_uri, get_file_size_Sync, get_file_suffix, image_save_from_uri } from '../../utils/storage_tools';
+import { extract_links_from_text, is_downloadable } from '../../utils/url_tools';
+import { LengthMetrics, SymbolGlyphModifier } from '@kit.ArkUI';
+import woofWantDownload from '../../dialogs/prompts/woofWantDownload';
+
+@Component
+struct meowScratchingBoard {
+  // Links
+  @StorageLink('showing_scratching_board') showing_scratching_board: boolean = false;
+  @StorageLink('drop_result_strings') @Watch('on_data_change') data_list: string[] = [];
+  // UI
+  @Prop show_feed_prompt: boolean = true;
+  @State area_height: number = 200;
+  @StorageProp('screen_height') screen_height: number = 0;
+  @State delete_confirm: number = 0;
+  @State only_links: boolean = false;
+  // Processed data
+  @State extracted_data: string[] = [];
+  @State extracted_type: string[] = [];
+  // Else
+  scroll_controller: Scroller = new Scroller();
+  // Colors
+  @StorageProp('color_current_primary') color_current_primary: ResourceColor = $r('app.color.start_window_background');
+  @StorageProp('color_current_secondary') color_current_secondary: ResourceColor = $r('app.color.block_color');
+  @StorageProp('color_current_font') color_current_font: ResourceColor = $r('app.color.font_color_title');
+
+  build() {
+    Column({ space: 10 }) {
+      linysTextTitle({
+        text: this.show_feed_prompt ? $r('app.string.Index_drop_please') : $r('app.string.Index_drop_ok')
+      })
+        .width(this.show_feed_prompt ? undefined : "100%")
+        .animation(animation_default())
+
+      Scroll(this.scroll_controller) {
+        Column({ space: 10 }) {
+          ForEach(this.extracted_data, (item: string, index: number) => {
+            raw_data_display({
+              text: item,
+              type: this.extracted_type[index],
+              index: index,
+              showing_scratching_board: this.showing_scratching_board,
+            })// Filter only links
+              .visibility(this.only_links && this.extracted_type[index] != 'url' ? Visibility.None : Visibility.Visible)
+              .animation(animation_default())
+          })
+        }
+        .alignItems(HorizontalAlign.Start)
+        .onAreaChange((_old, n) => {
+          this.area_height = n.height as number;
+        })
+        .width("100%")
+        .animation(animation_default())
+      } // Text and links data display
+      .edgeEffect(EdgeEffect.Spring)
+      .align(Alignment.Top)
+      .layoutWeight(1)
+      .visibility(this.show_feed_prompt ? Visibility.None : Visibility.Visible)
+      .width("100%")
+      .animation(animation_default())
+
+      Row({ space: 10 }) {
+        linysTimeoutButton({
+          dialogText: "确定是否删除正在提取的内容",
+          text: "  󰀁  ",
+          onExecution: () => {
+            this.clear_data();
+          }
+        })
+
+        linysCapsuleButton({
+          text: this.only_links ? "  󰄏  " : "  󰃁  "
+        })
+          .animation(animation_default())
+          .onClick(() => {
+            this.only_links = !this.only_links;
+          })
+      }
+      .justifyContent(FlexAlign.End)
+      .width("100%")
+      .visibility(this.data_list.length > 0 ? Visibility.Visible : Visibility.None)
+      .animation(animation_default())
+
+    }
+    .justifyContent(FlexAlign.Center)
+    .padding({ left: 15, right: 15 })
+    .width("100%")
+    .height((this.show_feed_prompt ? 0.3 * this.screen_height :
+    Math.min(this.area_height + 43, 0.6 * this.screen_height)) + 40)
+    .animation(animation_default())
+    .onAppear(() => {
+      setInterval(() => {
+        if (this.delete_confirm > 0) {
+          this.delete_confirm -= 1;
+        }
+        // Reset delete confirm
+      }, 10)
+    })
+  }
+
+  /**
+   * Called when new data comes
+   *
+   * If data is cleared then show feed prompt next time
+   * */
+  on_data_change() {
+    if (this.data_list.length > 0) {
+      this.show_feed_prompt = false;
+    } else {
+      this.show_feed_prompt = true;
+    }
+
+    // Analyzes the data and extract web links
+    let extracted_result = extract_links_from_text(this.data_list);
+    this.extracted_data = extracted_result[0];
+    this.extracted_type = extracted_result[1];
+
+    // console.log(this.extracted_data.join("\n"))
+    this.scroll_controller.scrollEdge(Edge.Top);
+  }
+
+  clear_data() {
+    // Clear all scratching board data
+    this.data_list = [];
+    this.showing_scratching_board = false;
+  }
+}
+
+export default meowScratchingBoard;
+
+@Component
+struct raw_data_display {
+  @Link showing_scratching_board: boolean;
+  @State text: string = "meow";
+  @State type: string = "text";
+  @State downloadable: boolean = false;
+  @State download_filename: string = '';
+  @State image_size: string = "233x233";
+  @State index: number = 0;
+  @State offset_y: number = 50;
+  @State this_visibility: Visibility = Visibility.Hidden;
+  // Settings - Accessibility
+  @StorageLink('preferred_hand_left_or_right') preferred_hand_left_or_right: string = 'right';
+  // Environment
+  @StorageLink('universal_new_tab_gateway') new_tab_gateway: string = "";
+  @StorageProp('tablet_mode') tablet_mode: boolean = false;
+  @StorageProp('screen_width') screen_width: number = 0;
+  // Colors
+  @StorageProp('color_current_primary') color_current_primary: ResourceColor = $r('app.color.start_window_background');
+  @StorageProp('color_current_secondary') color_current_secondary: ResourceColor = $r('app.color.block_color');
+  @StorageProp('color_current_font') color_current_font: ResourceColor = $r('app.color.font_color_title');
+  // Status
+  @State copied: number = 0;
+  @State image_height: number = 0;
+  @State image_width: number = 0;
+  // dialogs
+  woofWantDownload_control: CustomDialogController = new CustomDialogController({
+    builder: woofWantDownload({
+      from: '',
+      file_name: '',
+      url: '',
+      try_additional_info: ''
+    }),
+    alignment: DialogAlignment.Center,
+    cornerRadius: 16
+  })
+
+  aboutToAppear(): void {
+    if (this.type == 'url') {
+      this.downloadable = is_downloadable(this.text)[0] as boolean;
+      this.download_filename = is_downloadable(this.text)[1] as string;
+    }
+  }
+
+  // Save menu
+  @Builder
+  SaveMenu() {
+    Menu() {
+      MenuItem({
+        content: $r('app.string.Save_to_files'),
+        symbolStartIcon: new SymbolGlyphModifier($r('sys.symbol.arrow_right_folder_circle')).fontSize('24vp'),
+      })
+        .onClick(() => {
+          document_save_from_uri(this.text);
+        })
+      MenuItem({
+        content: $r('app.string.Save_to_gallery'),
+        symbolStartIcon: new SymbolGlyphModifier($r('sys.symbol.picture_badge_arrow_down')).fontSize('24vp'),
+      }).onClick(() => {
+        image_save_from_uri(this.text);
+      })
+    }
+    .fontColor(this.color_current_font)
+    .backgroundColor(this.color_current_primary)
+  }
+
+  build() {
+    Row({ space: 15 }) {
+      if (this.type == 'image') {
+        RelativeContainer() {
+          Image(this.text)
+            .width(Math.min(300, this.screen_width - 100))
+            .borderRadius(10)
+            .onComplete((event) => {
+              let imageWidth = event?.width;
+              let imageHeight = event?.height;
+              this.image_size = imageWidth + 'x' + imageHeight
+              // console.info('imageWidth:'+imageWidth,'imageHeight:'+imageHeight);
+            })
+            .id('image')
+            .onAreaChange((_o, n) => {
+              this.image_height = n.height as number;
+              this.image_width = n.width as number;
+            })
+
+          Scroll() {
+            Flex({
+              direction: !this.tablet_mode ? FlexDirection.Row : FlexDirection.Column,
+              space: { main: LengthMetrics.vp(10), cross: LengthMetrics.vp(10) }
+            }) {
+              linysText({ text: this.image_size })// Size
+                .backgroundColor($r('sys.color.comp_background_tertiary'))
+                .padding(10)
+                .borderRadius(10)
+              linysText({ text: get_file_suffix(this.text) }) // Type
+                .backgroundColor($r('sys.color.comp_background_tertiary'))
+                .padding(10)
+                .borderRadius(10)
+              linysText({ text: add_units_to_size(get_file_size_Sync(this.text)) })
+                .backgroundColor($r('sys.color.comp_background_tertiary'))
+                .padding(10)
+                .borderRadius(10)
+            } // Info display
+          }
+          .borderRadius(10)
+          .edgeEffect(EdgeEffect.Spring)
+          .scrollBar(BarState.Off)
+          .constraintSize(this.tablet_mode ? { maxHeight: this.image_height } : { maxWidth: this.image_width })
+          .scrollable(this.tablet_mode ? ScrollDirection.Vertical : ScrollDirection.Horizontal)
+          .id('info')
+          .margin(this.tablet_mode ? { left: 10 } : { top: 10 })
+          .alignRules(this.tablet_mode ?
+            {
+              top: { anchor: 'image', align: VerticalAlign.Top },
+              left: { anchor: 'image', align: HorizontalAlign.End }
+            } :
+            {
+              top: { anchor: 'image', align: VerticalAlign.Bottom },
+              left: { anchor: 'image', align: HorizontalAlign.Start }
+            })
+        }
+        .width('auto')
+        .height('auto')
+        .animation(animation_default())
+
+        Column({ space: 10 }) {
+          linysSymbol({
+            symbol_glyph_target: 'sys.symbol.save'
+          }) // Save
+            .clickEffect(click_effect_default())
+            .bindMenu(this.SaveMenu)
+          linysSymbol({
+            symbol_glyph_target: 'sys.symbol.magnifyingglass'
+          }) // Search
+            .clickEffect(click_effect_default())
+            .onClick(() => {
+              // Search online
+            })
+          linysSymbol({
+            symbol_glyph_target: 'sys.symbol.arrow_up_left_and_arrow_down_right'
+          })
+            .clickEffect(click_effect_default())
+            .onClick(() => {
+              // Check details
+              preview_image(this.text);
+            }) // Preview
+        } // Buttons
+      } else {
+        linysSymbol({
+          symbol_glyph_target: this.type_symbol(),
+          font_weight: this.type == "text" ? FontWeight.Regular : FontWeight.Medium,
+        }) // Type Symbol
+
+        linysText({
+          text: this.text,
+          max_lines: 10,
+          font_weight: this.type == "text" ? FontWeight.Regular : FontWeight.Bold,
+        })// Text display
+          .layoutWeight(1)
+
+        Row({ space: 10 }) {
+          linysSymbol({
+            symbol_glyph_target: this.copied ? 'sys.symbol.checkmark_clipboard_fill' : 'sys.symbol.text_clipboard'
+          })// Text copy
+            .clickEffect(click_effect_default())
+            .onClick(() => {
+              copy(this.text);
+              this.copied = animation_popup_duration();
+            })
+
+          if (this.downloadable) {
+            linysSymbol({
+              symbol_glyph_target: 'sys.symbol.download'
+            })
+              .clickEffect(click_effect_default())
+              .onClick(() => {
+                // AppStorage.set('universal_new_download_gateway', this.text);
+                // Call popup
+                this.woofWantDownload_control = new CustomDialogController({
+                  builder: woofWantDownload({
+                    from: '󰊴!',
+                    file_name: this.download_filename,
+                    url: this.text,
+                    try_additional_info: '',
+                  }),
+                  alignment: DialogAlignment.Center,
+                  cornerRadius: 16
+                })
+                this.woofWantDownload_control.open();
+              })
+          } // Download button
+
+          if (this.type == 'url') {
+            linysSymbol({
+              symbol_glyph_target: 'sys.symbol.arrow_right'
+            })
+              .clickEffect(click_effect_default())
+              .onClick(() => {
+                this.default_click();
+              })
+          } // Url visit button
+        } // Buttons
+      } // Content display with Controls
+    }
+    .direction(this.preferred_hand_left_or_right == 'right' ? Direction.Ltr : Direction.Rtl)
+    .padding(15)
+    .borderRadius(20)
+    .backgroundColor(this.color_current_primary)
+    .justifyContent(FlexAlign.Start)
+    .width(this.type == 'image' ? undefined : "100%")
+    .visibility(this.this_visibility)
+    .offset({ y: this.offset_y })
+    .animation(animation_default())
+    .onAppear(() => {
+      setTimeout(() => {
+        this.offset_y = 0;
+        this.this_visibility = Visibility.Visible;
+      }, Math.min((this.index) * 60, 600))
+
+      setInterval(() => {
+        if (this.copied > 0) {
+          this.copied -= 10;
+        }
+        // Reset copy
+      }, 10)
+    })
+    .clickEffect(click_effect_default())
+    .onClick(() => {
+      this.default_click();
+    })
+  }
+
+  default_click() {
+    if (this.type == 'url') {
+      this.new_tab_gateway = this.text;
+      this.showing_scratching_board = false;
+    }
+    if (this.type == 'text') {
+      copy(this.text);
+      this.copied = animation_popup_duration();
+    }
+    if (this.type == 'image') {
+      preview_image(this.text);
+      // document_save_from_uri(this.text);
+    }
+  }
+
+  type_symbol() {
+    if (this.type == 'url') {
+      return 'sys.symbol.paperclip';
+    }
+    if (this.type == 'image') {
+      return 'sys.symbol.picture';
+    }
+    return 'sys.symbol.text_and_t'
+  }
+}

+ 178 - 0
home/src/main/ets/blocks/modules/meowSuggestions.ets

@@ -0,0 +1,178 @@
+import { animation_default } from '../../hosts/bunch_of_defaults';
+import linysText from '../../components/texts/linysText';
+import { get_relations } from '../../utils/label_link_relation_tools';
+import { bunch_of_bookmarks } from '../../hosts/bunch_of_bookmarks';
+import { bunch_of_history } from '../../hosts/bunch_of_history';
+
+@Component
+struct meowSuggestions {
+  // Environments or settings
+  @Prop top_margin: number = 0;
+  @Prop bottom_margin: number = 0;
+  @State content_height: number = 0;
+  @StorageProp('is_search_input_typing') is_search_input_typing: boolean = false;
+  @StorageProp('search_input') @Watch('refresh_suggestions') search_input: string = "";
+  @StorageLink('search_extracted_keyword') search_extracted_keyword: string = '';
+  @StorageProp('screen_height') screen_height: number = 0;
+  @StorageProp('tablet_mode') tablet_mode: boolean = false;
+  @StorageLink('leftAvoidWidth') leftAvoidWidth: number = 1;
+  @StorageLink('rightAvoidWidth') rightAvoidWidth: number = 1;
+  @StorageLink('showing_more_options') showing_more_options: boolean = false;
+  @StorageLink('showing_app_settings') showing_app_settings: boolean = false;
+  @StorageLink('showing_downloads') showing_downloads: boolean = false;
+  // Hosts
+  @StorageLink('bunch_of_bookmarks') bunch_of_bookmarks: bunch_of_bookmarks = new bunch_of_bookmarks("meow");
+  @StorageLink('bunch_of_history') bunch_of_history: bunch_of_history = new bunch_of_history(true);
+  // Settings
+  @StorageProp('max_bookmark_advice') max_bookmark_advice: number = 5;
+  @StorageProp('max_history_advice') max_history_advice: number = 5;
+  // @StorageProp('max_bookmark_advice') @Watch('refresh_suggestions') max_bookmark_advice: number = 5;
+  // @StorageProp('max_history_advice') @Watch('refresh_suggestions') max_history_advice: number = 5;
+  // Myself
+  @State bookmark_suggestions: string[][] = [];
+  @State history_suggestions: string[][] = [];
+  // Colors
+  @StorageProp('color_current_primary') color_current_primary: ResourceColor = $r('app.color.start_window_background');
+  @StorageProp('color_current_secondary') color_current_secondary: ResourceColor = $r('app.color.block_color');
+  @StorageProp('color_current_font') color_current_font: ResourceColor = $r('app.color.font_color_title');
+
+  build() {
+    Scroll() {
+      if (this.tablet_mode) {
+        Row({ space: 10 }) {
+          suggestionList({ suggestions: this.bookmark_suggestions })
+            .layoutWeight(1)
+            .visibility(this.bookmark_suggestions.length > 0 ? Visibility.Visible : Visibility.None)
+            .animation(animation_default())
+          suggestionList({ suggestions: this.history_suggestions })
+            .layoutWeight(1)
+            .visibility(this.history_suggestions.length > 0 ? Visibility.Visible : Visibility.None)
+            .animation(animation_default())
+        }
+        .alignItems(VerticalAlign.Top)
+        .onAreaChange((_o, n) => {
+          this.content_height = n.height as number;
+        })
+        .animation(animation_default())
+      } else {
+        Column({ space: 10 }) {
+          suggestionList({ suggestions: this.bookmark_suggestions })
+            .width("100%")
+            .visibility(this.bookmark_suggestions.length > 0 ? Visibility.Visible : Visibility.None)
+            .animation(animation_default())
+          suggestionList({ suggestions: this.history_suggestions })
+            .width("100%")
+            .visibility(this.history_suggestions.length > 0 ? Visibility.Visible : Visibility.None)
+            .animation(animation_default())
+        }
+        .alignItems(HorizontalAlign.Start)
+        .onAreaChange((_o, n) => {
+          this.content_height = n.height as number;
+        })
+        .animation(animation_default())
+      }
+    }
+    .scrollBar(BarState.Off)
+    .margin({
+      left: 15,
+      right: 15,
+      top: this.top_margin,
+      bottom: this.bottom_margin
+    })
+    .backgroundColor(Color.Red)
+    .height(Math.min(this.content_height, 0.4 * this.screen_height))
+    // .visibility(this.visibility_suggestions())
+    .animation(animation_default())
+    .edgeEffect(EdgeEffect.Spring)
+  }
+
+  visibility_suggestions() {
+    if (this.bookmark_suggestions.length + this.history_suggestions.length == 0) {
+      return Visibility.None;
+    }
+    if (this.showing_more_options) {
+      return Visibility.None;
+    }
+    if (this.search_input == "") {
+      return Visibility.None;
+    }
+    if (!this.is_search_input_typing) {
+      return Visibility.None;
+    }
+    return Visibility.Visible
+  }
+
+  refresh_suggestions() {
+    this.showing_more_options = false;
+    this.showing_app_settings = false;
+    this.showing_downloads = false;
+    if (this.search_input === undefined || this.search_input.length == 0) {
+      this.bookmark_suggestions = [];
+      this.history_suggestions = [];
+    } else {
+      let label_link_bookmark = this.bunch_of_bookmarks.get_all_plain_label_link();
+      this.bookmark_suggestions = get_relations(label_link_bookmark, this.search_input, this.max_bookmark_advice);
+      this.history_suggestions = bunch_of_history.search_with_index(this.search_input.toUpperCase(), this.max_history_advice);
+      // get_relations(label_link_history, this.search_input, this.max_history_advice);
+    }
+  }
+}
+
+export default meowSuggestions;
+
+@Component
+struct meowRelation {
+  @Prop relation: string[] = [];
+  @Prop index: number;
+  @State offset_y: number = 50;
+  @State this_visibility: Visibility = Visibility.Hidden;
+  @StorageLink('universal_load_url_gateway') load_url_gateway: string = "";
+
+  build() {
+    Column({ space: 2 }) {
+      linysText({
+        text: this.relation[0].length == 0 ? " " : this.relation[0]
+      })
+      linysText({
+        text: this.relation[1]
+      })
+        .opacity(0.7)
+    }
+    .alignItems(HorizontalAlign.Start)
+    .width("100%")
+    .visibility(this.this_visibility)
+    .offset({ y: this.offset_y })
+    .animation(animation_default())
+    .onClick(() => {
+      this.load_url_gateway = this.relation[1];
+    })
+    .onAppear(() => {
+      setTimeout(() => {
+        this.offset_y = 0;
+        this.this_visibility = Visibility.Visible;
+      }, (this.index) * 60)
+    })
+  }
+}
+
+@Component
+struct suggestionList {
+  @StorageProp('color_current_primary') color_current_primary: ResourceColor = $r('app.color.start_window_background');
+  @Prop suggestions: string[][] = [];
+
+  build() {
+    Column({ space: 8 }) {
+      ForEach(this.suggestions, (item: string[], index: number) => {
+        meowRelation({
+          relation: item,
+          index: index
+        })
+      })
+    }
+    .padding(15)
+    .backgroundColor(this.color_current_primary)
+    .borderRadius(10)
+    .visibility(this.suggestions.length > 0 ? Visibility.Visible : Visibility.None)
+    .animation(animation_default())
+  }
+}

+ 340 - 0
home/src/main/ets/blocks/modules/meowTabsHorizontal.ets

@@ -0,0 +1,340 @@
+import linysShowButton from '../../components/buttons/linysShowButton';
+import linysSymbol from '../../components/texts/linysSymbol';
+import { animation_default, click_effect_default, fontSize_Large, fontSize_Normal } from '../../hosts/bunch_of_defaults';
+import { bunch_of_tabs } from '../../hosts/bunch_of_tabs';
+import { LengthMetrics } from '@kit.ArkUI';
+import { allow_drop_types, drop_to_scratching_board } from '../../utils/drag_drop_tools';
+import linysText from '../../components/texts/linysText';
+import { deviceInfo } from '@kit.BasicServicesKit';
+import linysTimeoutButton from '../../components/buttons/linysTimeoutButton';
+
+@Component
+struct meowTabsHorizontal {
+  // HOST
+  @StorageLink('bunch_of_tabs') bunch_of_tabs: bunch_of_tabs = new bunch_of_tabs(true);
+  // Environments
+  @StorageLink('showing_tabs') showing_tabs: boolean = false;
+  @StorageLink('showing_more_options') showing_more_options: boolean = false;
+  @StorageLink('windowDecorWidth') windowDecorWidth: number = 0;
+  @StorageLink('current_main_tab_index') current_main_tab_index: number = 0;
+  @StorageLink('current_sub_tab_index') current_sub_tab_index: number = -1;
+  @StorageProp('tablet_mode') tablet_mode: boolean = false;
+  @StorageProp('pathDir') pathDir: string = "";
+  @StorageProp('title_bar_position') title_bar_position: string = "";
+  // Controls and environments
+  @StorageLink('tab_titles') tab_titles: string[] = []
+  @StorageLink('current_title') current_title: string = "= ̄ω ̄=";
+  @StorageLink('tab_urls') tab_urls: string[] = []
+  @StorageLink('current_url') current_url: string = "= ̄ω ̄=";
+  @StorageLink('tab_loading_progresses') tab_loading_progresses: number[] = [0]
+  @StorageLink('current_loading_progress') current_loading_progress: number = 0
+  @StorageLink('tab_is_loading') tab_is_loading: boolean[] = [true]
+  @StorageLink('current_is_loading') current_is_loading: boolean = true
+  // Settings - Accessibility
+  @StorageLink('preferred_hand_left_or_right') preferred_hand_left_or_right: string = 'right';
+  // Gateways
+  @StorageLink('universal_new_tab_gateway') new_tab_gateway: string = "";
+  @StorageLink('choosing_paralleow') choosing_paralleow: boolean = false;
+  @StorageLink('universal_close_all_tabs_gateway') close_all_tabs_gateway: number = -1;
+  // UI statuses
+  @State length_width_of_tabs: number = 0;
+  @State full_area_width: number = 10;
+  // Drop
+  @StorageLink('showing_scratching_board') showing_scratching_board: boolean = false;
+
+  build() {
+    Column() {
+      Row({ space: 10 }) {
+        linysTimeoutButton({
+          text: '  󰀁  ',
+          onExecution: () => {
+            this.close_all_tabs_gateway = Date.now();
+          }
+        })
+          .visibility(!this.tablet_mode || this.choosing_paralleow || this.current_sub_tab_index >= 0 ? Visibility.None : Visibility.Visible)
+          .animation(animation_default())
+          .padding({ left: 5 })
+
+        Row() {
+          linysText({
+            text: $r('app.string.Index_tabs_title_drag')
+          })
+        } // Drag area
+        .visibility(this.visibility_2in1_top())
+        .height(this.length_width_of_tabs > this.full_area_width ? 55 : 40)
+        .animation(animation_default())
+        .justifyContent(FlexAlign.Center)
+        .borderRadius(12)
+        .padding(10)
+        .width(50 + 10)
+        .backgroundColor($r('sys.color.comp_background_tertiary'))
+        .clickEffect(click_effect_default())
+
+        Scroll() {
+          Row({ space: this.tablet_mode ? 7 : 5 }) {
+            ForEach(this.tab_titles, (title: string, index: number) => {
+              HorizontalTabButton({
+                current_main_tab_index: this.current_main_tab_index,
+                current_sub_tab_index: this.current_sub_tab_index,
+                index: index,
+                title: title,
+                full_area_width: this.full_area_width,
+                tab_titles: this.tab_titles
+              })
+            })
+          }
+          .onAreaChange((_old, n) => {
+            this.length_width_of_tabs = n.width as number;
+          })
+          .margin({ bottom: this.length_width_of_tabs > this.full_area_width ? 15 : 0 })
+          .animation(animation_default())
+        } // Scroll of tab buttons
+        .scrollBarWidth(5)
+        .scrollBar(this.tab_width() == 80 ? BarState.On : BarState.Off)
+        .scrollable(ScrollDirection.Horizontal)
+        .edgeEffect(EdgeEffect.Spring)
+        .align(Alignment.Start)
+        .layoutWeight(1)
+        .height(this.length_width_of_tabs > this.full_area_width ? 55 : 40)
+        .animation(animation_default())
+        .onAreaChange((_old, n) => {
+          this.full_area_width = n.width as number;
+        })
+
+        linysShowButton({
+          symbol_glyph_target: 'sys.symbol.map',
+          show: this.choosing_paralleow || this.current_sub_tab_index >= 0,
+          text: this.choosing_paralleow ? $r('app.string.Paralleow_choose_a_tab') : $r('app.string.Paralleow')
+        })// Paralleow entrance
+          .onClick(() => {
+            this.choose_paralleow();
+          })
+          .visibility(this.tablet_mode ? (this.tab_titles.length > 1 ? Visibility.Visible : Visibility.Hidden) : Visibility.None)
+          .margin({ left: 4 })
+          .animation(animation_default())
+
+        linysSymbol({ symbol_glyph_target: 'sys.symbol.plus_square' })// New tab
+          .onClick(() => {
+            this.new_tab_gateway = "new_tab";
+          })
+          .margin({ right: 8 })
+          .visibility(this.tablet_mode ? Visibility.Visible : Visibility.None)
+          .animation(animation_default())
+
+        Row() {
+          Row() {
+            linysText({
+              text: $r('app.string.Index_tabs_title_drag')
+            })
+          }
+          .justifyContent(FlexAlign.Center)
+          .padding(10)
+          .height(this.length_width_of_tabs > this.full_area_width ? 55 : 40)
+          .borderRadius(12)
+          .backgroundColor($r('sys.color.comp_background_tertiary'))
+          .width(50 + 10)
+          .clickEffect(click_effect_default())
+        } // Drag area
+        .justifyContent(FlexAlign.Start)
+        .visibility(this.visibility_2in1_top())
+        .width(this.windowDecorWidth + 50)
+        .animation(animation_default())
+
+      } // Base container
+      .alignItems(VerticalAlign.Center)
+
+      Row({ space: 5 }) {
+        linysTimeoutButton({
+          text: '  󰀁  ',
+          onExecution: () => {
+            this.close_all_tabs_gateway = Date.now();
+          }
+        })
+          .visibility(this.choosing_paralleow || this.current_sub_tab_index >= 0 ? Visibility.None : Visibility.Visible)
+          .animation(animation_default())
+        // .padding({ left: 5 })
+
+        Blank()
+
+        linysShowButton({
+          symbol_glyph_target: 'sys.symbol.map',
+          show: this.choosing_paralleow || this.current_sub_tab_index >= 0,
+          text: this.choosing_paralleow ? $r('app.string.Paralleow_choose_a_tab') : $r('app.string.Paralleow')
+        })// Paralleow entrance
+          .onClick(() => {
+            this.choose_paralleow();
+          })
+          .visibility(this.tab_titles.length > 1 ? Visibility.Visible :
+          Visibility.Hidden)// .margin({ left: 4, bottom: this.length_width_of_tabs > this.full_area_width ? 15 : 0 })
+          .animation(animation_default())
+
+        linysSymbol({ symbol_glyph_target: 'sys.symbol.plus_square' })// New tab
+          .onClick(() => {
+            AppStorage.set('extra_background', false);
+            this.new_tab_gateway = "new_tab";
+          })// .margin({ bottom: this.length_width_of_tabs > this.full_area_width ? 15 : 0, right: 8 })
+          .animation(animation_default())
+      } // Extension for compact (non-tablet mode) layout
+      .alignItems(VerticalAlign.Center)
+      .direction(this.preferred_hand_left_or_right == 'right' ? Direction.Ltr : Direction.Rtl)
+      .visibility(this.tablet_mode ? Visibility.None : Visibility.Visible)
+      .padding({ left: 7, right: 7, top: this.length_width_of_tabs > this.full_area_width ? 0 : 10 })
+      .animation(animation_default())
+      .width('100%')
+
+    }
+    .padding({ left: 8, right: 8 })
+    .animation(animation_default())
+    .allowDrop(allow_drop_types())
+    .onDrop((e) => {
+      drop_to_scratching_board(e);
+    })
+    .onDragEnter(() => {
+      this.showing_scratching_board = true;
+      this.showing_more_options = false;
+    })
+    .onDragLeave(() => {
+      this.showing_scratching_board = false;
+    })
+  }
+
+  choose_paralleow() {
+    if (this.choosing_paralleow) {
+      // if is choosing, cancel choosing, back to no paralleow
+      this.choosing_paralleow = false;
+    } else {
+      if (this.current_sub_tab_index < 0) {
+        // if haven't chosen one, then start to choose
+        this.choosing_paralleow = true;
+      } else {
+        // Quit Paralleow
+        this.choosing_paralleow = false;
+        this.current_sub_tab_index = -1;
+      }
+    }
+  }
+
+  tab_width() {
+    let result = (this.full_area_width) / (this.tab_titles.length) - (this.tablet_mode ? 7 : 5);
+    if (result > 200) {
+      return 200;
+    }
+    if (result < 80) {
+      return 80;
+    }
+    return result;
+  }
+
+  visibility_2in1_top() {
+    return deviceInfo.deviceType == '2in1' && this.title_bar_position == 'top' && this.tablet_mode ? Visibility.Visible : Visibility.None;
+  }
+}
+
+export default meowTabsHorizontal;
+
+@Component
+struct HorizontalTabButton {
+  @Link current_main_tab_index: number;
+  @Link current_sub_tab_index: number;
+  @Prop index: number;
+  @Prop title: string;
+  @Link full_area_width: number;
+  @Link tab_titles: string[];
+  @StorageProp('tablet_mode') tablet_mode: boolean = false;
+  // Gateways
+  @StorageLink('universal_close_tab_gateway') close_tab_gateway: number = -1;
+  @StorageLink('universal_tab_button_click_gateway') uni_tab_button_clicked: number = -1;
+  @State offset_y: number = 100;
+  // Settings - Accessibility
+  @StorageLink('preferred_hand_left_or_right') preferred_hand_left_or_right: string = 'right';
+  // Colors
+  @StorageProp('color_current_primary') color_current_primary: ResourceColor = $r('app.color.start_window_background');
+  @StorageProp('color_current_font') color_current_font: ResourceColor = $r('app.color.font_color_title');
+
+  build() {
+    Row({ space: 8 }) {
+      if (this.preferred_hand_left_or_right == 'left') {
+        SymbolGlyph($r('sys.symbol.xmark'))// Close icon
+          .fontSize(fontSize_Normal())
+          .fontColor([this.color_current_font])
+          .fontWeight(FontWeight.Bold)
+          .onClick(() => {
+            this.close_tab_gateway = this.index;
+            // Close tab
+          })
+          .visibility(this.tab_width() <= 80 ? this.visibility_visible_on_chosen(this.index) : Visibility.Visible)
+          .animation(animation_default())
+      }
+
+      Text("󰇀")// Paralleow icon
+        .fontSize(fontSize_Large())
+        .fontColor(this.color_current_font)
+        .fontWeight((this.current_main_tab_index == this.index || this.current_sub_tab_index == this.index) ? FontWeight.Bold : undefined)
+        .visibility(this.current_sub_tab_index == this.index ? Visibility.Visible : Visibility.None)
+        .animation(animation_default())
+        .offset({ y: -2 })
+
+      Text(this.title == "" ? "Meow" : this.title)
+        .fontColor(this.color_current_font)
+        .fontSize(fontSize_Normal())
+        .fontWeight(FontWeight.Regular)
+        .textAlign(TextAlign.Start)
+        .maxLines(1)
+        .lineSpacing(LengthMetrics.vp(4))
+        .textOverflow({ overflow: TextOverflow.Ellipsis })
+        .layoutWeight(1)
+
+      if (this.preferred_hand_left_or_right == 'right') {
+        SymbolGlyph($r('sys.symbol.xmark'))// Close icon
+          .fontSize(fontSize_Normal())
+          .fontColor([this.color_current_font])
+          .fontWeight(FontWeight.Bold)
+          .onClick(() => {
+            this.close_tab_gateway = this.index;
+            // Close tab
+          })
+          .visibility(this.tab_width() <= 80 ? this.visibility_visible_on_chosen(this.index) : Visibility.Visible)
+          .animation(animation_default())
+      }
+    }
+    .offset({ y: this.offset_y })
+    .width(this.tab_width())
+    .onClick(() => {
+      this.uni_tab_button_clicked = this.index;
+    })
+    .padding(10)
+    .height("100%")
+    .border({
+      radius: 10,
+      width: 2,
+      color: (this.current_main_tab_index == this.index || this.current_sub_tab_index == this.index)
+        ? this.color_current_font : "transparent"
+    })
+    .backgroundColor(this.color_current_primary)
+    .animation(animation_default())
+    .onAppear(() => {
+      setTimeout(() => {
+        this.offset_y = 0;
+      }, 50)
+    })
+    .clickEffect(click_effect_default())
+  }
+
+  tab_width() {
+    let result = (this.full_area_width) / (this.tab_titles.length) - (this.tablet_mode ? 7 : 5);
+    if (result > 200) {
+      return 200;
+    }
+    if (result < 80) {
+      return 80;
+    }
+    return result;
+  }
+
+  visibility_visible_on_chosen(index: number) {
+    if (this.current_main_tab_index == index || this.current_sub_tab_index == index) {
+      return Visibility.Visible;
+    }
+    return Visibility.None;
+  }
+}

+ 775 - 0
home/src/main/ets/blocks/modules/meowTabsVertical.ets

@@ -0,0 +1,775 @@
+import linysSymbol from '../../components/texts/linysSymbol';
+import { bunch_of_tabs, tab_label } from '../../hosts/bunch_of_tabs';
+import { url_resource_to_meow } from '../../utils/url_tools';
+import { animation_default, click_effect_default, fontSize_Large, fontSize_Normal, url_default_blank } from '../../hosts/bunch_of_defaults';
+import { kv_store_get, kv_store_put } from '../../utils/kv_store_tools';
+import { bunch_of_settings } from '../../hosts/bunch_of_settings';
+import linysShowButton from '../../components/buttons/linysShowButton';
+import linysText from '../../components/texts/linysText';
+import { sandbox_read_arrayBuffer_sync, sandbox_unlink_sync } from '../../utils/storage_tools';
+import { bunch_of_key_shortcuts } from '../../hosts/bunch_of_key_shortcuts';
+import { allow_drop_types, drop_to_scratching_board } from '../../utils/drag_drop_tools';
+import linysTimeoutButton from '../../components/buttons/linysTimeoutButton';
+
+@Component
+struct meowTabs {
+  // Hosts and environments
+  @StorageLink('bunch_of_tabs') bunch_of_tabs: bunch_of_tabs = new bunch_of_tabs(true);
+  @StorageLink('bunch_of_settings') bunch_of_settings: bunch_of_settings = new bunch_of_settings(true);
+  @StorageLink('bunch_of_key_shortcuts') bunch_of_key_shortcuts: bunch_of_key_shortcuts = new bunch_of_key_shortcuts(true);
+  @StorageProp('tablet_mode') tablet_mode: boolean = false;
+  @StorageLink('showing_tabs') showing_tabs: boolean = false;
+  @StorageLink('showing_bookmarks') showing_bookmarks: boolean = false;
+  @StorageLink('showing_more_options') showing_more_options: boolean = false;
+  @StorageLink('search_input') search_input: string = "*(੭*ˊᵕˋ)੭*ଘ";
+  @StorageLink('search_input_source') search_input_update_source: string = "tab_switch";
+  @StorageProp('is_search_input_typing') is_search_input_typing: boolean = false;
+  @StorageLink('tabs_style_non_tablet_mode') tabs_style_non_tablet_mode: string = "";
+  // Controls
+  @StorageLink('tab_titles') tab_titles: string[] = []
+  @StorageLink('current_title') current_title: string = "= ̄ω ̄=";
+  @StorageLink('tab_urls') tab_urls: string[] = []
+  @StorageLink('current_url') current_url: string = "= ̄ω ̄=";
+  @StorageLink('tab_loading_progresses') tab_loading_progresses: number[] = [0]
+  @StorageLink('current_loading_progress') current_loading_progress: number = 0
+  @StorageLink('tab_is_loading') tab_is_loading: boolean[] = [true]
+  @StorageLink('current_is_loading') current_is_loading: boolean = true
+  @StorageLink('current_in_page_searching_keyword') current_in_page_searching_keyword: string = ''
+  @StorageLink('current_in_page_searching_stats_current') current_in_page_searching_stats_current: number = 0;
+  @StorageLink('current_in_page_searching_stats_total') current_in_page_searching_stats_total: number = 0;
+  // Web control statuses
+  @StorageLink('current_main_tab_index') @Watch('on_did_switch_tab') current_main_tab_index: number = 0;
+  @StorageLink('current_sub_tab_index') @Watch('on_did_switch_tab') current_sub_tab_index: number = -1;
+  @StorageLink('current_accessForward') current_accessForward: boolean = false;
+  @StorageLink('current_accessBackward') current_accessBackward: boolean = false;
+  // Other Info / Statuse
+  @State recover_tabs_finished: boolean = false;
+  //是否开启并行模式
+  @StorageLink('choosing_paralleow') choosing_paralleow: boolean = false;
+  @StorageLink('intelligent_tracking_prevention') intelligent_tracking_prevention: boolean = false;
+  // Animation info
+  tab_height_default: number = 42;
+  scroll_controller: Scroller = new Scroller();
+  @State extension_area: number[] = [];
+  @State offset_area: number[] = [];
+  @State count_down_for_switching_back_to_spring: number = 0;
+  @State count_down_for_scrolling_to_bottom: number = 0;
+  @State scroll_area_height: number = 0;
+  @State scroll_animation_enabled: boolean = true;
+  @State paralleow_description_height: number = 0;
+  @StorageLink('tab_animation') tab_animation: AnimateParam = animation_default();
+  // Settings - Accessibility
+  @StorageLink('preferred_hand_left_or_right') preferred_hand_left_or_right: string = 'right';
+  @StorageLink('preferred_hand_reverse_tabs_panel') preferred_hand_reverse_tabs_panel: boolean = false;
+  // Gateways
+  @StorageLink('universal_load_url_gateway') @Watch('uni_load_url') load_url_gateway: string = "";
+  @StorageLink('universal_new_tab_gateway') @Watch('uni_new_tab') new_tab_gateway: string = "";
+  @StorageLink('universal_close_tab_gateway') @Watch('uni_close_tab') close_tab_gateway: number = -1;
+  @StorageLink('universal_close_all_tabs_gateway') @Watch('uni_close_all_tabs') close_all_tabs_gateway: number = -1;
+  @StorageLink('universal_tab_button_click_gateway') @Watch('uni_tab_button_click') uni_tab_button_clicked: number = -1;
+  @StorageLink('universal_global_custom_ua_gateway') @Watch('on_global_custom_UA_change') global_custom_UA: string = "";
+  // Colors
+  @StorageLink('color_current_primary') color_current_primary: ResourceColor = $r('app.color.start_window_background');
+  @StorageProp('color_current_secondary') color_current_secondary: ResourceColor = $r('app.color.block_color');
+  @StorageProp('color_current_font') color_current_font: ResourceColor = $r('app.color.font_color_title');
+  // Drop
+  @StorageLink('showing_scratching_board') showing_scratching_board: boolean = false;
+  // Pan Gesture
+  pan_x: number = 0;
+
+  async aboutToAppear() {
+    // set Intelligent Tracking Prevention
+    this.intelligent_tracking_prevention =
+      await this.bunch_of_settings.get('intelligent_tracking_prevention') as boolean;
+
+    // Get Data
+    let home_url = await this.bunch_of_settings.get('home_url') as string;
+    let new_tab_url = await this.bunch_of_settings.get('new_tab_url') as string;
+    let start_up_option = await this.bunch_of_settings.get('start_up_option') as string;
+    let continue_tabs_count_string = await kv_store_get("continue_tabs_count");
+    let continue_tabs_main_on_string = await kv_store_get("continue_tabs_main_on");
+    let continue_tabs_sub_on_string = await kv_store_get("continue_tabs_sub_on");
+    let continue_tabs_count = 0;
+    let continue_tabs_main_on = 0;
+    let continue_tabs_sub_on = 0;
+
+    this.bunch_of_tabs.start_up = start_up_option;
+    this.bunch_of_tabs.new_tab_url = new_tab_url;
+    this.bunch_of_tabs.home_url = home_url;
+
+    if (continue_tabs_count_string == "undefined") {
+      kv_store_put("continue_tabs_count", "0");
+      continue_tabs_count = 0;
+    } else {
+      continue_tabs_count = Number.parseInt(continue_tabs_count_string);
+    }
+
+    if (continue_tabs_main_on_string == "undefined") {
+      kv_store_put("continue_tabs_main_on", "0");
+      continue_tabs_main_on = 0;
+    } else {
+      continue_tabs_main_on = Number.parseInt(continue_tabs_main_on_string);
+    }
+
+    if (continue_tabs_sub_on_string == "undefined") {
+      kv_store_put("continue_tabs_sub_on", "-1");
+      continue_tabs_sub_on = -1;
+    } else {
+      continue_tabs_sub_on = Number.parseInt(continue_tabs_sub_on_string);
+    }
+
+    // Log
+    console.log("[Meow][meowTabs] Got continue_tabs_count: " + continue_tabs_count_string);
+    console.log("[Meow][meowTabs] Got continue_tabs_main_on: " + continue_tabs_main_on_string);
+    console.log("[Meow][meowTabs] Got continue_tabs_sub_on: " + continue_tabs_sub_on_string);
+
+    // Start Up: generate / recover tabs
+    if (start_up_option == "new tab") {
+      this.new_tab(new_tab_url);
+      if (new_tab_url == "") {
+        // If is default blank page
+      } else {
+
+      }
+      this.switch_tab(0);
+
+    } else if (start_up_option == "home") {
+      if (home_url == "") {
+        this.new_tab(url_default_blank());
+      } else {
+
+        this.new_tab(home_url);
+      }
+      this.switch_tab(0);
+
+    } else {
+      // continue
+      // Prepare web states
+      let web_state_arrays_to_restore: Uint8Array[] = [];
+      for (let index = 0; index < continue_tabs_count; index++) {
+        // Read continue tabs data
+        let key = "continue/continue_tabs_web_state_array_" + index.toString();
+        let array_buffer = sandbox_read_arrayBuffer_sync(key);
+        if (array_buffer) {
+          web_state_arrays_to_restore.push(new Uint8Array(array_buffer));
+        } else {
+          // An unexpected error may occur.
+          web_state_arrays_to_restore.push(new Uint8Array())
+          this.current_sub_tab_index = -1;
+          this.current_main_tab_index = 0;
+        }
+      }
+
+      // Save to AppStorage for restoration use
+      AppStorage.setOrCreate('restore_web_state_arrays', web_state_arrays_to_restore);
+
+      // Create new tabs
+      for (let index = 0; index < web_state_arrays_to_restore.length; index++) {
+        this.new_tab("", true);
+      }
+
+      // Try to switch tab
+      this.tab_animation = { duration: 0 };
+      this.switch_tab(continue_tabs_main_on);
+      this.sync_all_list_info();
+
+      // Recover Paralleow
+      if (continue_tabs_sub_on < this.tab_titles.length) {
+        this.current_sub_tab_index = continue_tabs_sub_on;
+      }
+    }
+
+    this.recover_tabs_finished = true;
+  }
+
+  build() {
+    Column({ space: 10 }) {
+      Scroll(this.scroll_controller) {
+        Column({ space: 5 }) {
+          ForEach(this.bunch_of_tabs.Labels, (Label: tab_label) => {
+            TabButton({
+              Label: Label,
+              extension_area: this.extension_area,
+              offset_area: this.offset_area,
+            })
+          }, (Label: tab_label) => Label.timestamp.toString())
+        }
+        .width("100%")
+        .onAreaChange((_o, n) => {
+          let my_height = n.height as number;
+          if (my_height > this.scroll_area_height) {
+            // Scroll Enabled, animation disabled
+            this.scroll_animation_enabled = false;
+          } else {
+            // Scroll Disabled, animation enabled
+            this.scroll_animation_enabled = true;
+          }
+        })
+
+      } // Tab Buttons List
+      .direction(Direction.Rtl)
+      .align(Alignment.Bottom)
+      .edgeEffect(this.count_down_for_switching_back_to_spring == 0 ? EdgeEffect.Spring : EdgeEffect.None)
+      .width("100%")
+      .height(this.scroll_area_height - (this.choosing_paralleow || this.current_sub_tab_index >= 0 ? this.paralleow_description_height : 0))
+      .animation(animation_default())
+      .nestedScroll({scrollForward:NestedScrollMode.SELF_ONLY,scrollBackward:NestedScrollMode.SELF_ONLY})
+
+      Row({ space: 10 }) {
+        linysTimeoutButton({
+          text: '  󰀁  ',
+          onExecution: () => {
+            this.close_all_tabs_gateway = Date.now();
+          }
+        })
+          .visibility(this.choosing_paralleow || this.current_sub_tab_index >= 0 ? Visibility.None : Visibility.Visible)
+          .animation(animation_default())
+
+        Blank()
+
+        linysSymbol({ symbol_glyph_target: 'sys.symbol.plus_square' })
+          .onClick(() => {
+            this.new_tab("")
+          })
+          .keyboardShortcut(this.bunch_of_key_shortcuts.new_tab.main_key, this.bunch_of_key_shortcuts.new_tab.modifier)
+      } // New Tab and Paralleow Button
+      .direction(this.preferred_hand_left_or_right == 'right' ? Direction.Ltr : Direction.Rtl)
+      .width("100%")
+
+    } // Tabs Panel
+    .justifyContent(FlexAlign.End)
+    // .padding(10)
+    .width("100%")
+    .height("100%")
+    .onAppear(() => {
+      this.extension_area = [];
+      this.offset_area = [];
+      for (let i = 0; i < this.bunch_of_tabs.Tabs.length; i++) {
+        this.extension_area.push(0);
+        this.offset_area.push(0);
+      }
+      // Init extension_area
+      setInterval(() => {
+        this.on_timer();
+      }, 10)
+      // Set Task to calculate animation
+      // console.log("[Meow][meowTabs] Tabs Panel READY")
+    })
+    .onAreaChange((_o, n) => {
+      console.log("oldValue:" + (_o.height as number) + "  newValue:" + (n.height as number))
+      this.scroll_area_height = n.height as number - 60;
+    })
+    // 当图片或其他资源文件拖拽进入标签页时,进行相关的操作
+    .allowDrop(allow_drop_types())
+    .onDrop((e) => {
+      drop_to_scratching_board(e);
+    })
+    .onDragEnter(() => {
+      this.showing_scratching_board = true;
+      this.showing_more_options = false;
+    })
+    .onDragLeave(() => {
+      this.showing_scratching_board = false;
+    })
+    .gesture(
+      PanGesture({ direction: PanDirection.Left | PanDirection.Right })
+        .onActionStart(() => {
+          this.pan_x = 0;
+        })
+        .onActionUpdate((e) => {
+          this.pan_x += e.offsetX;
+        })
+        .onActionEnd(() => {
+          if (this.pan_x < 0) {
+            // ← Switch to Bookmarks
+            this.showing_bookmarks = true;
+            this.showing_tabs = false;
+          }
+          if (this.pan_x > 0 && this.is_right_align()) {
+            // → Right Align Close panel
+            this.showing_tabs = false;
+          }
+        })
+    )
+  }
+
+  // Tab controls
+
+  /**
+   * Creates a new tab.
+   * @param target_url A string, if url is "", then load the default new_tab_url set in this object.
+   * @param do_recovery A boolean, if set true, then the tab will restore the serialized web state in
+   * @description While if new_tab_url is also not set ("" or undefined), then will load the url_default_blank().
+   * */
+  new_tab(target_url: string, do_recovery?: boolean) {
+    AppStorage.set('extra_background', false);
+
+    this.tab_animation = { duration: 0 };
+
+    this.current_main_tab_index = this.bunch_of_tabs.newTab(target_url, do_recovery);
+
+    if (!do_recovery) {
+      // Store tabs count
+      let store_tabs_count = this.bunch_of_tabs.get_tabs_count().toString();
+      console.log("[Meow][meowTabs] Stored Tabs Count: " + store_tabs_count)
+      kv_store_put("continue_tabs_count", store_tabs_count);
+    }
+
+    // Animation part
+    this.extension_area.push(0);
+
+    if (this.scroll_animation_enabled) {
+      for (let index = 0; index < this.offset_area.length; index++) {
+        this.offset_area[index] += this.tab_height_default + 5;
+        // Move downward for up going animation
+      }
+      // Set offset for the new tab
+      this.offset_area.push(this.tab_height_default + 1000)
+    } else {
+      this.offset_area.push(this.tab_height_default)
+    }
+
+    // Scroll to the new tab later
+    this.count_down_for_scrolling_to_bottom = 30;
+  }
+
+  switch_tab(target: number) {
+    if (!this.tablet_mode) {
+      // Auto close panel
+      if (this.tabs_style_non_tablet_mode == "vertical") {
+        this.showing_tabs = false;
+      }
+    }
+
+    this.choosing_paralleow = false;
+
+    // Switch
+    this.current_main_tab_index = this.bunch_of_tabs.switchToTab(target);
+    console.log('[Meow][meowTabs] Switch to tab ' + this.current_main_tab_index.toString())
+    if (this.recover_tabs_finished) {
+      // Update history
+      this.update_backward_forward_access()
+    }
+    // Set CURRENT variables
+    this.update_current_info()
+    // Update Input Search Box
+    this.set_search_box_text(this.current_url)
+
+    this.determine_extra_background();
+  }
+
+  close_tab(target: number) {
+    if (!this.recover_tabs_finished) {
+      return;
+    }
+
+    // Some kind of bug, idk why but this is essential when closing tabs quickly
+    if (target >= this.offset_area.length) {
+      console.error("[Meow][LinysTabs][ERROR] Ran into the weird bug of target >= this.offset_area.length")
+      return;
+    }
+
+    // Set animation duration
+    // this is stupid
+    this.tab_animation = { duration: 0 };
+
+    // Turn off Paralleow
+    if (target == this.current_sub_tab_index || target == this.current_main_tab_index) {
+      console.log('[Meow][LinysTabs] Paralleow off')
+      this.current_sub_tab_index = -1;
+    }
+
+    // Close
+    this.current_main_tab_index = this.bunch_of_tabs.closeTab(target, url_default_blank())
+    // Update history
+    this.update_backward_forward_access()
+    // Get synced lists
+    this.sync_all_list_info()
+    // Set CURRENT variables
+    this.update_current_info()
+    // Update Input Search Box
+    this.set_search_box_text(this.current_url)
+
+    // Store tabs count
+    let store_tabs_count = this.tab_titles.length.toString();
+    console.log("[Meow][meowTabs] Store Tabs Count: " + store_tabs_count)
+    kv_store_put("continue_tabs_count", store_tabs_count);
+
+    // clear extra continue storage
+    let index = this.bunch_of_tabs.Tabs.length;
+    while (true) {
+      let key = "continue/continue_tabs_web_state_array_" + index.toString();
+      // console.log('awa')
+      if (!sandbox_unlink_sync(key)) {
+        break;
+      }
+      index += 1;
+    }
+    // re-save web states
+    this.bunch_of_tabs.re_save_web_state(target);
+
+    // Paralleow
+    if (this.current_main_tab_index == this.current_sub_tab_index) {
+      // if Paralleow crash then turn off
+      this.current_sub_tab_index = -1;
+    }
+
+    // if only 1 tab left then reset Paralleow stuff
+    if (this.tab_titles.length == 1) {
+      this.current_sub_tab_index = -1;
+      this.choosing_paralleow = false;
+    }
+    if (target < this.current_sub_tab_index) {
+      this.current_sub_tab_index -= 1;
+    }
+
+    // Animation part
+    if (target != 0) {
+      this.extension_area[target-1] += this.tab_height_default + this.extension_area[target];
+      // If isn't closing the top most tab
+    }
+    if (this.offset_area.length == 1) {
+      // If closing the last tab
+      this.offset_area[0] = this.tab_height_default + 1000;
+    } else {
+      // If not closing the last tab
+      this.extension_area.splice(target, 1)
+      this.offset_area.splice(target, 1)
+    }
+
+    // Set Scroll spring on edge effect off
+    // So that when closing the last tab, it is always stuck at the bottom
+    // Or we'll have to wait for the spring animation to finish, bringing the last tab button down.
+    this.count_down_for_switching_back_to_spring += 10;
+
+    this.determine_extra_background();
+  }
+
+  close_all_tabs() {
+    for (let index = this.bunch_of_tabs.Tabs.length - 1; index >= 0; index--) {
+      this.close_tab(0);
+    }
+  }
+
+  choose_paralleow() {
+    if (this.choosing_paralleow) {
+      // if is choosing, cancel choosing, back to no paralleow
+      this.choosing_paralleow = false;
+      return;
+    }
+
+    if (this.current_sub_tab_index < 0) {
+      // if haven't chosen one, then start to choose
+      this.choosing_paralleow = true;
+    } else {
+      // Quit Paralleow
+      this.choosing_paralleow = false;
+      this.current_sub_tab_index = -1;
+    }
+
+    this.determine_extra_background();
+  }
+
+  switch_paralleow() {
+    let new_sub = this.current_main_tab_index;
+    this.switch_tab(this.current_sub_tab_index);
+    this.current_sub_tab_index = new_sub;
+  }
+
+  // Utils
+
+  on_timer() {
+    for (let index = 0; index < this.extension_area.length; index++) {
+      this.extension_area[index] += (0 - this.extension_area[index]) / 6;
+      this.offset_area[index] += (0 - this.offset_area[index]) / 5;
+      // Shrink areas
+    }
+    if (this.count_down_for_switching_back_to_spring > 0) {
+      this.count_down_for_switching_back_to_spring -= 1;
+    }
+    // Count down
+    if (this.count_down_for_scrolling_to_bottom > 0) {
+      this.count_down_for_scrolling_to_bottom -= 1;
+      if (this.count_down_for_scrolling_to_bottom == 1) {
+        this.scroll_controller.scrollEdge(Edge.Bottom)
+        // Execute
+      }
+    }
+  }
+
+  update_current_info() {
+    this.current_title = this.tab_titles[this.current_main_tab_index];
+    this.current_url = this.tab_urls[this.current_main_tab_index];
+    this.current_url = url_resource_to_meow(this.current_url);
+    // translate "resource://" into "meow://"
+    this.current_loading_progress = this.tab_loading_progresses[this.current_main_tab_index];
+    this.current_is_loading = this.tab_is_loading[this.current_main_tab_index];
+    // Set loading progress
+    this.current_in_page_searching_keyword = this.bunch_of_tabs.workingMainTab().searching_keyword;
+    this.current_in_page_searching_stats_current = this.bunch_of_tabs.workingMainTab().searching_keyword_stats_current;
+    this.current_in_page_searching_stats_total = this.bunch_of_tabs.workingMainTab().searching_keyword_stats_total;
+  }
+
+  sync_all_list_info() {
+    this.tab_titles = this.bunch_of_tabs.get_all_titles()
+    this.tab_urls = this.bunch_of_tabs.get_all_urls()
+    this.tab_is_loading = this.bunch_of_tabs.get_all_is_loading();
+    this.tab_loading_progresses = this.bunch_of_tabs.get_all_loading_progress()
+  }
+
+  update_backward_forward_access() {
+    try {
+      if (this.bunch_of_tabs.workingMainTab()) {
+        this.current_accessBackward = this.bunch_of_tabs.workingMainTab().controller.accessBackward()
+        this.current_accessForward = this.bunch_of_tabs.workingMainTab().controller.accessForward()
+      } else {
+        setTimeout(() => {
+          this.update_backward_forward_access();
+        }, 100)
+      }
+    } catch (e) {
+      setTimeout(() => {
+        this.update_backward_forward_access();
+      }, 100)
+    }
+  }
+
+  set_search_box_text(text: string) {
+    if (!this.is_search_input_typing && text) {
+      this.search_input_update_source = 'tab_switch';
+      this.search_input = url_resource_to_meow(text);
+      // Update Input Search Box
+    }
+  }
+
+  determine_extra_background() {
+    let need_extra_background = true;
+    // Determine background
+    if (!AppStorage.get('meowWebView_init_OK') as boolean) {
+      need_extra_background = false;
+    }
+    if (this.current_sub_tab_index > -1) {
+      // Paralleowing
+      if (this.current_url == 'meow://home' && this.tab_urls[this.current_sub_tab_index] == 'meow://home') {
+        need_extra_background = false;
+      }
+    } else {
+      // Not Paralleowing
+      if (this.current_url == 'meow://home') {
+        need_extra_background = false;
+      }
+    }
+    AppStorage.set('extra_background', need_extra_background);
+  }
+
+  // Events
+
+  // 点击标签条目(分为并行模式与普通模式)
+  uni_tab_button_click() {
+    let clicked = this.uni_tab_button_clicked;
+    let sub = this.current_sub_tab_index;
+    let main = this.current_main_tab_index;
+
+    if (clicked >= 0) {
+      if (this.choosing_paralleow && main != clicked) {
+        // if choosing Paralleow
+        // and is not selected as main web
+        // (this tab is selectable to be a sub view)
+        // then set this tab as sub view
+        this.choosing_paralleow = false;
+        this.current_sub_tab_index = clicked;
+      } else if (sub >= 0 && (sub == clicked || main == clicked)) {
+        // if Paralleow enabled
+        // and this tab clicked is one of main or sub
+        // then switch the two tabs
+        this.switch_paralleow();
+      } else {
+        // simply switch tab
+        this.tab_animation = { duration: 0 };
+        this.switch_tab(clicked);
+      }
+    }
+    // Reset
+    this.uni_tab_button_clicked = -1;
+  }
+
+  on_did_switch_tab() {
+    if (this.recover_tabs_finished) {
+      // Store where am I
+      let where_main_am_i = this.current_main_tab_index.toString();
+      console.log("[Meow][meowTabs] Stored where main am I: " + where_main_am_i)
+      kv_store_put("continue_tabs_main_on", where_main_am_i);
+
+      let where_sub_am_i = this.current_sub_tab_index.toString();
+      console.log("[Meow][meowTabs] Stored where sub am I: " + where_sub_am_i)
+      kv_store_put("continue_tabs_sub_on", where_sub_am_i);
+    }
+  }
+
+  on_global_custom_UA_change() {
+    this.bunch_of_tabs.set_global_custom_UA(this.global_custom_UA);
+  }
+
+  uni_new_tab() {
+    // console.log('qwq_new_tab')
+    let tab = this.new_tab_gateway;
+    if (tab != "") {
+      if (tab == "new_tab") {
+        tab = ""
+      }
+
+      this.new_tab(tab);
+      setTimeout(() => {
+        // Avoid opening duplicate tabs, idk why
+        this.new_tab_gateway = "";
+      }, 20)
+    }
+  }
+
+  uni_close_tab() {
+    if (this.close_tab_gateway >= 0) {
+      this.close_tab(this.close_tab_gateway);
+      this.close_tab_gateway = -1;
+    }
+  }
+
+  uni_close_all_tabs() {
+    if (this.close_all_tabs_gateway != -1) {
+      this.close_all_tabs();
+    }
+  }
+
+  uni_load_url() {
+    if (this.load_url_gateway != "") {
+      setTimeout(() => {
+        this.bunch_of_tabs.loadUrl_onWorkingTab(this.load_url_gateway);
+        this.load_url_gateway = "";
+      }, 50)
+    }
+  }
+
+  // Data
+
+  is_right_align() {
+    return this.preferred_hand_left_or_right == 'right' && !this.preferred_hand_reverse_tabs_panel;
+  }
+}
+
+export default meowTabs;
+
+@Component
+struct TabButton {
+  // Sync
+  @Link offset_area: number[];
+  @Link extension_area: number[];
+  @Prop Label: tab_label;
+  tab_height_default: number = 42;
+  // Public
+  @StorageLink('bunch_of_key_shortcuts') bunch_of_key_shortcuts: bunch_of_key_shortcuts = new bunch_of_key_shortcuts(true);
+  @StorageLink('current_main_tab_index') current_main_tab_index: number = 0;
+  @StorageLink('current_sub_tab_index') current_sub_tab_index: number = -1;
+  @StorageLink('tab_titles') tab_titles: string[] = []
+  // Settings - Accessibility
+  @StorageLink('preferred_hand_left_or_right') preferred_hand_left_or_right: string = 'right';
+  // Gateways
+  @StorageLink('universal_tab_button_click_gateway') uni_tab_button_clicked: number = -1;
+  @StorageLink('universal_close_tab_gateway') close_tab_gateway: number = -1;
+  // Colors
+  @StorageProp('color_current_secondary') color_current_secondary: ResourceColor = $r('app.color.block_color');
+  @StorageProp('color_current_font') color_current_font: ResourceColor = $r('app.color.font_color_title');
+  // Animation
+  @State pressing: boolean = false;
+
+  build() {
+    RelativeContainer() {
+      Row() {
+        SymbolGlyph($r('sys.symbol.xmark'))
+          .padding({ right: 5 })
+          .fontSize(fontSize_Normal())
+          .fontColor([this.color_current_font])
+          .fontWeight(FontWeight.Bold)
+          .visibility(this.preferred_hand_left_or_right == 'left' ? Visibility.Visible : Visibility.None)
+          .animation(animation_default())
+
+        Text("󰇀")
+          .padding({ right: 5 })
+          .fontSize(fontSize_Large())
+          .fontColor(this.color_current_font)
+          .fontWeight((this.current_main_tab_index == this.Label.index_key ||
+            this.current_sub_tab_index == this.Label.index_key)
+            ? FontWeight.Bold : undefined)
+          .visibility(this.current_sub_tab_index == this.Label.index_key ? Visibility.Visible : Visibility.None)
+          .animation(animation_default())
+          .offset({ y: -2 })
+
+        Text(this.tab_titles[this.Label.index_key] == "" ? "Meow" : this.tab_titles[this.Label.index_key])
+          .padding({ left: 2 })
+          .fontSize(fontSize_Normal())
+          .fontColor(this.color_current_font)
+          .fontWeight((this.current_main_tab_index == this.Label.index_key ||
+            this.current_sub_tab_index == this.Label.index_key)
+            ? FontWeight.Bold : undefined)
+          .maxLines(1)
+          .textOverflow({ overflow: TextOverflow.Ellipsis })
+          .layoutWeight(1)
+
+        SymbolGlyph($r('sys.symbol.xmark'))
+          .fontSize(fontSize_Normal())
+          .fontColor([this.color_current_font])
+          .fontWeight(FontWeight.Bold)
+          .visibility(this.preferred_hand_left_or_right == 'right' ? Visibility.Visible : Visibility.None)
+          .animation(animation_default())
+
+      } // Title and xmark
+      .border({
+        radius: 10,
+        width: 2,
+        color: (this.current_main_tab_index == this.Label.index_key || this.current_sub_tab_index == this.Label.index_key) ?
+        this.color_current_font : "transparent"
+      })
+      .backgroundColor(this.color_current_secondary)
+      .animation(animation_default())
+      .width("100%")
+      .height(this.tab_height_default)
+      .padding(10)
+      .offset({ y: this.offset_area[this.Label.index_key] })
+      .alignRules({
+        middle: { anchor: "__container__", align: HorizontalAlign.Center },
+        top: { anchor: "__container__", align: VerticalAlign.Top }
+      })
+      .onClick(() => {
+        this.uni_tab_button_clicked = this.Label.index_key;
+      })
+
+      Column() {
+      } // The True Tab Closer
+      .width(35)
+      .height("100%")
+      .alignRules(this.preferred_hand_left_or_right == 'right' ?
+        {
+          right: { anchor: "__container__", align: HorizontalAlign.End },
+          center: { anchor: "__container__", align: VerticalAlign.Center }
+        } : {
+          left: { anchor: "__container__", align: HorizontalAlign.Start },
+          center: { anchor: "__container__", align: VerticalAlign.Center }
+        })
+      .onClick(() => {
+        this.close_tab_gateway = this.Label.index_key;
+      })
+      .keyboardShortcut(this.current_main_tab_index == this.Label.index_key ? this.bunch_of_key_shortcuts.close_tab.main_key : '',
+        this.bunch_of_key_shortcuts.close_tab.modifier)
+    }
+    .onTouch((event) => {
+      if (event.type == TouchType.Up) {
+        this.pressing = false;
+        // If touch ends
+      } else {
+        this.pressing = true;
+        // If touching
+      }
+    })
+    .width("100%")
+    .height(this.tab_height_default + this.extension_area[this.Label.index_key])
+    .clickEffect(click_effect_default())
+  }
+}

+ 1270 - 0
home/src/main/ets/blocks/modules/meowTitleBar.ets

@@ -0,0 +1,1270 @@
+import linysSymbol from '../../components/texts/linysSymbol';
+import linysShowButton from '../../components/buttons/linysShowButton';
+import meowAppSettings from './meowAppSettings';
+import { bunch_of_tabs } from '../../hosts/bunch_of_tabs';
+import { extract_search, match_domain, unify_search_input_into_url, url_meow_to_resource, url_resource_to_meow } from '../../utils/url_tools';
+import {
+  animation_default,
+  animation_popup_duration,
+  capsule_bar_height,
+  click_effect_default,
+  fontSize_Icon_Button,
+  fontSize_Large,
+  fontSize_Normal
+} from '../../hosts/bunch_of_defaults';
+import { bunch_of_user_agents } from '../../hosts/bunch_of_user_agents';
+import meowDownloads from './meowDownloads';
+import { bunch_of_bookmarks } from '../../hosts/bunch_of_bookmarks';
+import woofQR from '../../dialogs/contents/woofQR';
+import meowTabsHorizontal from './meowTabsHorizontal';
+import meowScratchingBoard from './meowScratchingBoard';
+import woofAdsBlocker from '../../dialogs/managers/woofAdsBlocker';
+import woofQuickSE from '../../dialogs/quicks/woofQuickSE';
+import woofCookies from '../../dialogs/managers/woofCookies';
+import linysText from '../../components/texts/linysText';
+import woofGeneralManage from '../../dialogs/managers/woofGeneralManage';
+import { bunch_of_key_shortcuts } from '../../hosts/bunch_of_key_shortcuts';
+import { share_link } from '../../utils/share_tools';
+import { bunch_of_downloads } from '../../hosts/bunch_of_downloads';
+import { allow_drop_types, drop_to_scratching_board } from '../../utils/drag_drop_tools';
+import meowDebug from '../panels/meowDebug';
+import linysProgressInfo from '../../components/linysProgressInfo';
+import { add_transparency } from '../../utils/color_tools';
+import { current_tabs_style_is_horizontal } from '../../utils/ui_tools';
+import { search_engine } from '../../hosts/bunch_of_search_engines';
+import { copy } from '../../utils/clipboard_tools';
+import { print_web } from '../../utils/print_tools';
+import woofHistory from '../../dialogs/managers/woofHistory';
+import { bunch_of_settings } from '../../hosts/bunch_of_settings';
+import { parseTimestamp } from '../../utils/environment_tools';
+import { Share } from 'module_share';
+
+@Component
+struct meowTitleBar {
+  // Hosts
+  @StorageLink('bunch_of_bookmarks') bunch_of_bookmarks: bunch_of_bookmarks = new bunch_of_bookmarks("meow");
+  @StorageLink('bunch_of_user_agents') bunch_of_user_agents: bunch_of_user_agents = new bunch_of_user_agents();
+  @StorageLink('bunch_of_tabs') bunch_of_tabs: bunch_of_tabs = new bunch_of_tabs(true);
+  @StorageLink('bunch_of_key_shortcuts') bunch_of_key_shortcuts: bunch_of_key_shortcuts = new bunch_of_key_shortcuts(true);
+  @StorageLink('bunch_of_downloads') bunch_of_downloads: bunch_of_downloads = new bunch_of_downloads();
+  @StorageLink('bunch_of_settings') bunch_of_settings: bunch_of_settings = new bunch_of_settings(true);
+  @StorageLink('collect_new_history') collect_new_history: boolean = true;
+  // Environments
+  @StorageLink('fullscreen_mode') fullscreen_mode: boolean = false;
+  @StorageProp('tablet_mode') tablet_mode: boolean = false;
+  @StorageProp('screen_width') screen_width: number = 0;
+  @StorageProp('screen_height') screen_height: number = 0;
+  @StorageLink('full_screen_height') full_screen_height: number = 0;
+  @StorageProp('universal_new_download_gateway') @Watch('on_download_start') uni_new_download_gateway: string = "";
+  @Link bar_height: number;
+  @StorageLink('leftAvoidWidth') leftAvoidWidth: number = 1;
+  @StorageLink('rightAvoidWidth') rightAvoidWidth: number = 1;
+  @StorageProp('reindexing_history_progress') reindexing_history_progress: string = "";
+  @StorageProp('history_index_loading_progress') history_index_loading_progress: string = "";
+  @StorageProp('history_index_saving_progress') history_index_saving_progress: string = "";
+  // Current environment
+  @Link title_bar_alignRules: AlignRuleOption;
+  @StorageLink('search_input') @Watch('on_search_input_change') search_input: string = "*(੭*ˊᵕˋ)੭*ଘ";
+  @StorageLink('search_input_source') search_input_update_source: string = "extracted_key_edit";
+  @State search_input_unified: string = "";
+  @StorageLink('is_search_input_typing') is_search_input_typing: boolean = false;
+  @State download_started_popup: boolean = false;
+  @StorageLink('drop_result_strings') drop_result_string: string[] = [];
+  @StorageLink('current_in_page_searching_keyword') current_in_page_searching_keyword: string = "";
+  @StorageLink('current_in_page_searching_stats_current') current_in_page_searching_stats_current: number = 0;
+  @StorageLink('current_in_page_searching_stats_total') current_in_page_searching_stats_total: number = 0;
+  // Search analyze
+  @StorageLink('search_extracted_keyword') search_extracted_keyword: string = '';
+  @State search_extracted_engine: string = '';
+  @State showing_search_extracted_field: boolean = false;
+  @State search_extracted_field_available: boolean = false;
+  @State is_search_input_address: boolean = false;
+  @State search_input_unify_scheme_http: boolean = true;
+  // Web statuses
+  @StorageLink('tab_titles') tab_titles: string[] = []
+  @StorageLink('current_title') current_title: string = "= ̄ω ̄=";
+  @StorageLink('tab_urls') tab_urls: string[] = []
+  @StorageLink('current_url') current_url: string = "= ̄ω ̄=";
+  @StorageLink('tab_loading_progresses') tab_loading_progresses: number[] = [0]
+  @StorageLink('current_loading_progress') current_loading_progress: number = 0
+  @StorageLink('tab_is_loading') tab_is_loading: boolean[] = [true]
+  @StorageLink('current_is_loading') current_is_loading: boolean = true
+  // Web control statuses
+  @StorageLink('current_main_tab_index') current_main_tab_index: number = 0;
+  @StorageLink('current_sub_tab_index') current_sub_tab_index: number = -1;
+  @StorageLink('current_accessForward') current_accessForward: boolean = false;
+  @StorageLink('current_accessBackward') current_accessBackward: boolean = false;
+  // UI control actions
+  @StorageLink('showing_tabs') showing_tabs: boolean = false;
+  @StorageLink('showing_bookmarks') showing_bookmarks: boolean = false;
+  @StorageLink('showing_more_options') showing_more_options: boolean = false;
+  @StorageLink('showing_app_settings') showing_app_settings: boolean = false;
+  @StorageLink('showing_downloads') showing_downloads: boolean = false;
+  @StorageLink('showing_scratching_board') showing_scratching_board: boolean = false;
+  @State address_analyzers_desc_close_cd: number = 0;
+  @State horizontal_tabs_height: number = 120;
+  // settings
+  @StorageLink('DEV_MODE') DEV_MODE: boolean = false;
+  @StorageLink('resource_monitor') resource_monitor: boolean = true;
+  @StorageLink('disable_js') disable_js: boolean = true;
+  @StorageLink('disable_js_these_sites') disable_js_these_sites: string[] = [];
+  @StorageLink('disable_js_all_sites') disable_js_all_sites: boolean = false;
+  @StorageLink('disable_image') disable_image: boolean = true;
+  @StorageLink('disable_image_these_sites') disable_image_these_sites: string[] = [];
+  @StorageLink('disable_image_all_sites') disable_image_all_sites: boolean = false;
+  @StorageProp('title_bar_position') title_bar_position: string = "";
+  @StorageLink('tabs_style') tabs_style: string = "";
+  @StorageLink('tabs_style_non_tablet_mode') tabs_style_non_tablet_mode: string = "";
+  @StorageLink('max_bookmark_advice') max_bookmark_advice: number = 5;
+  @StorageLink('max_history_advice') max_history_advice: number = 5;
+  // Settings - Accessibility
+  @StorageLink('preferred_hand_left_or_right') preferred_hand_left_or_right: string = 'right';
+  // Dialogs
+  woofQR_control: CustomDialogController = new CustomDialogController({
+    builder: woofQR({
+      title: this.current_title,
+      link: this.current_url
+    }),
+    alignment: DialogAlignment.Center,
+    cornerRadius: 16
+  });
+
+  woofHistory_control: CustomDialogController = new CustomDialogController({
+    builder: woofHistory({ showing_settings: this.showing_app_settings }),
+    alignment: DialogAlignment.Center,
+    cornerRadius: 22,
+    // showInSubWindow: true,
+    width: "90%",
+  });
+  quick_keyword: string = '';
+  quick_se: search_engine | undefined = undefined;
+  woofQuickSE_control: CustomDialogController = new CustomDialogController({
+    builder: woofQuickSE({
+      default_new_se: this.quick_se,
+      keyword: this.quick_keyword
+    }),
+    alignment: DialogAlignment.Center,
+    cornerRadius: 26
+  });
+  // 广告过滤
+  woofAdsBlocker_control: CustomDialogController = new CustomDialogController({
+    builder: woofAdsBlocker({
+      add_exception_domain_edit: match_domain(this.search_input)[1],
+      showing_add_panel: true
+    }),
+    // showInSubWindow: true,
+    width: "90%",
+    alignment: DialogAlignment.Center,
+    cornerRadius: 16
+  });
+
+  cookies_control: CustomDialogController = new CustomDialogController({
+    builder: woofCookies(),
+    alignment: DialogAlignment.Center,
+    cornerRadius: 16,
+    // showInSubWindow: true,
+    width: "90%",
+  });
+  js_manage_control: CustomDialogController = new CustomDialogController({
+    builder: woofGeneralManage({
+      general_switch: this.disable_js,
+      general_sites_list: this.disable_js_these_sites,
+      general_on_all_sites_switch: this.disable_js_all_sites,
+      add_site_edit: match_domain(this.search_input)[1],
+      showing_add_panel: true
+    }),
+    alignment: DialogAlignment.Center,
+    cornerRadius: 16,
+    // showInSubWindow: true,
+    width: "90%",
+  });
+  image_manage_control: CustomDialogController = new CustomDialogController({
+    builder: woofGeneralManage({
+      general_switch: this.disable_image,
+      general_sites_list: this.disable_image_these_sites,
+      general_on_all_sites_switch: this.disable_image_all_sites,
+      add_site_edit: match_domain(this.search_input)[1],
+      showing_add_panel: true,
+      general_descriptions: [
+        $r('app.string.Settings_image_desc_1'),
+        $r('app.string.Settings_image_desc_2'),
+        $r('app.string.Settings_image_desc_3')],
+      general_tips: $r('app.string.Settings_image_already_disabled'),
+      general_title: $r('app.string.Settings_image_manage'),
+      general_switch_desc: $r('app.string.Settings_image_disable_image'),
+      general_subtitle_execute_on_these_sites: $r('app.string.Settings_image_some_sites'),
+      general_subtitle_execute_on_all_sites: $r('app.string.Settings_image_all_sites'),
+      general_switch_settings_id: 'disable_image',
+      general_sites_list_settings_id: 'disable_image_these_sites',
+      general_switch_all_sites_settings_id: 'disable_image_all_sites',
+    }),
+    alignment: DialogAlignment.Center,
+    cornerRadius: 16,
+    // showInSubWindow: true,
+    width: "90%",
+  });
+  // Colors
+  @StorageProp('color_current_primary') color_current_primary: ResourceColor = $r('app.color.start_window_background');
+  @StorageProp('color_current_secondary') color_current_secondary: ResourceColor = $r('app.color.block_color');
+  @StorageProp('color_current_font') color_current_font: ResourceColor = $r('app.color.font_color_title');
+
+  build() {
+    Column({ space: 10 }) {
+      // if (this.title_bar_position == "top") {
+      //   Row().height(0)
+      // }
+
+
+      meowTabsHorizontal()// Horizontal tabs
+        .visibility(this.visibility_of_horizontal_tabs())
+        .animation(animation_default())
+        .onAreaChange((_oo, n) => {
+          this.horizontal_tabs_height = Math.max(0, n.height as number + 10);
+        })
+
+      EstimatedDestination({
+        is_http_no_s: this.search_input_unify_scheme_http,
+        is_search_input_typing: this.is_search_input_typing,
+        is_search_input_address: this.is_search_input_address,
+        search_input: this.search_input,
+        search_input_unified: this.search_input_unified
+      })
+        .visibility(this.is_search_input_typing && this.title_bar_position == 'bottom' ? Visibility.Visible : Visibility.None)
+        .padding({ left: 15, right: 15 })
+        .width("100%")
+        .animation(animation_default())
+        .constraintSize({ minHeight: 20 })
+
+      Row({ space: 15 }) {
+        Column({ space: 2.5 }) {
+          Text(this.current_title)
+            .fontColor(this.color_current_font)
+            .fontWeight(FontWeight.Bold)
+            .fontSize(fontSize_Large())
+            .maxLines(2)
+            .textOverflow({ overflow: TextOverflow.Ellipsis })
+            .clickEffect(click_effect_default())
+            .onClick(() => {
+              copy(this.current_title);
+            })
+          Text(this.current_url)
+            .fontColor(this.color_current_font)
+            .fontWeight(FontWeight.Bold)
+            .fontSize(fontSize_Normal())
+            .opacity(0.7)
+            .maxLines(2)
+            .textOverflow({ overflow: TextOverflow.Ellipsis })
+            .clickEffect(click_effect_default())
+            .onClick(() => {
+              copy(this.current_url);
+            })
+        }
+        .alignItems(HorizontalAlign.Start)
+        .layoutWeight(1)
+
+        Share({
+          qrCodeInfo:{
+            id: 'post_1',
+            type: 2,
+            articleFrom: '搜索时间',
+            title: this.current_title,
+            createTime: "" + parseTimestamp(Date.now()),
+            isVideo: false,
+            link: this.current_url,
+          },
+          shareRenderBuilder: () => {
+            this.shareCommentBuilder()
+          }
+        })
+      } // Title Bar for This Page Info display
+      .direction(this.preferred_hand_left_or_right == 'right' ? Direction.Ltr : Direction.Rtl)
+      .padding({ left: 15, right: 15, top: 6 })
+      .width("100%")
+      .visibility(this.current_in_page_searching_stats_total <= 0 && this.showing_more_options && !this.showing_scratching_board
+        ? Visibility.Visible : Visibility.None)
+      .animation(animation_default())
+
+      RelativeContainer() {
+        Row({ space: this.tablet_mode ? 10 : 6 }) {
+          linysSymbol({
+            symbol_glyph_target: 'sys.symbol.arrow_left'
+          })// Backward
+            .enabled(this.current_accessBackward)
+            .opacity(this.current_accessBackward ? 1 : 0.5)
+            .visibility(this.visible_when_no_panels_open_in_normal_mode())
+            .animation(animation_default())
+            .onClick(() => {
+              this.go_backward();
+            })
+
+          linysSymbol({
+            symbol_glyph_target: 'sys.symbol.arrow_right'
+          })// Forward
+            .enabled(this.current_accessForward)
+            .opacity(this.current_accessForward ? 1 : 0.5)
+            .visibility(this.visible_when_no_panels_open_in_normal_mode())
+            .animation(animation_default())
+            .onClick(() => {
+              this.go_forward();
+            })
+
+          SymbolGlyph(!this.current_is_loading ? $r('sys.symbol.arrow_clockwise') : $r('sys.symbol.xmark'))
+            .fontSize(fontSize_Icon_Button())
+            .fontColor([this.color_current_font])
+            .symbolEffect(new ReplaceSymbolEffect(EffectScope.WHOLE), this.current_is_loading)
+            .visibility(this.visible_when_no_panels_open_in_normal_mode())
+            .animation(animation_default())
+            .onClick(() => {
+              if (this.current_is_loading) {
+                this.stop_page()
+              } else {
+                this.refresh_page()
+              }
+            })
+            .keyboardShortcut(this.bunch_of_key_shortcuts.refresh.main_key, this.bunch_of_key_shortcuts.refresh.modifier)
+
+          linysSymbol({
+            symbol_glyph_target: 'sys.symbol.house'
+          })// Home
+            .visibility(this.visible_when_no_panels_open_in_normal_mode())
+            .animation(animation_default())
+            .onClick(() => {
+              this.go_home()
+            })
+
+        } // Title Bar of left controls
+        .height(36)
+        .alignRules({
+          left: { anchor: '__container__', align: HorizontalAlign.Start },
+          top: { anchor: '__container__', align: VerticalAlign.Top }
+        })
+        .animation(animation_default())
+        .id('left_controls')
+
+        Row({ space: 10 }) {
+          RelativeContainer() {
+            Scroll() {
+              TextInput({ text: this.search_input })
+                .allowDrop(null)
+                .visibility(this.showing_scratching_board ? Visibility.Hidden : Visibility.Visible)
+                .height(capsule_bar_height())
+                .fontWeight(FontWeight.Regular)
+                .fontColor(this.color_current_font)
+                .caretColor(this.color_current_font)
+                .selectedBackgroundColor(this.color_current_font)
+                .offset({ y: !this.showing_search_extracted_field ? 0 : -50 })
+                .opacity(!this.showing_search_extracted_field ? (this.showing_scratching_board ? 0.8 : 1) : 0)
+                .animation(animation_default())
+                .onDragEnter(() => {
+                  this.showing_scratching_board = false;
+                })
+                .onFocus(() => {
+                  this.is_search_input_typing = true;
+                })
+                .onBlur(() => {
+                  this.is_search_input_typing = false;
+                })
+                .enabled(this.showing_scratching_board ? false : true)
+                .onChange((content) => {
+                  this.update_search_input(content);
+                })
+                .onSubmit(() => {
+                  this.submit_searching()
+                })
+                .selectAll(true)
+                .id('address_bar')
+                .keyboardShortcut(!this.showing_search_extracted_field ? this.bunch_of_key_shortcuts.focus_address.main_key : '',
+                  this.bunch_of_key_shortcuts.focus_address.modifier)
+            }
+            .scrollable(ScrollDirection.Vertical)
+            .height(this.showing_search_extracted_field ? 0 : 36)
+            .scrollBar(BarState.Off)
+            .animation(animation_default())
+
+            Scroll() {
+              TextInput({ text: this.search_extracted_keyword })
+                .allowDrop(null)
+                .visibility(this.showing_scratching_board ? Visibility.Hidden : Visibility.Visible)
+                .height(capsule_bar_height())
+                .fontWeight(FontWeight.Regular)
+                .fontColor(this.color_current_font)
+                .caretColor(this.color_current_font)
+                .offset({ y: this.showing_search_extracted_field ? 0 : 50 })
+                .opacity(this.showing_search_extracted_field ? (this.showing_scratching_board ? 0.8 : 1) : 0)
+                .animation(animation_default())
+                .onDragEnter(() => {
+                  this.showing_scratching_board = false;
+                })
+                .onFocus(() => {
+                  this.is_search_input_typing = true;
+                })
+                .onBlur(() => {
+                  this.is_search_input_typing = false;
+                })
+                .enabled(this.showing_scratching_board ? false : true)
+                .onChange((content) => {
+                  this.search_extracted_keyword = content;
+                  if (content == '') {
+                    this.search_input_unified = this.search_extracted_engine.replaceAll('%s', "");
+                  } else {
+                    this.search_input_unified = this.search_extracted_engine.replaceAll('%s', encodeURIComponent(content));
+                  }
+                  this.search_input_update_source = 'extracted_key_edit';
+                  this.search_input = this.search_input_unified;
+                })
+                .onSubmit(() => {
+                  this.submit_searching();
+                  // After submit, as the web loads, the new url will be synced to the search_input (address bar)
+                  // Then it will trigger @Watch('on_search_input_change')
+                  // And the extracted keyword would be synced to its TextInput.
+                })
+                .selectAll(true)
+                .selectedBackgroundColor(this.color_current_font)
+                .id('key_bar')
+                .keyboardShortcut(this.showing_search_extracted_field ? this.bunch_of_key_shortcuts.focus_address.main_key : '',
+                  this.bunch_of_key_shortcuts.focus_address.modifier)
+            }
+            .scrollable(ScrollDirection.Vertical)
+            .scrollBar(BarState.Off)
+            .height(this.showing_search_extracted_field ? 36 : 0)
+            .animation(animation_default())
+
+          } // Two inputs
+          .layoutWeight(1)
+
+          linysSymbol({ symbol_glyph_target: 'sys.symbol.arrow_right' })// Search
+            .visibility(this.visible_when_typing())
+            .onClick(() => {
+              this.submit_searching()
+            })
+            .animation(animation_default())
+
+          linysShowButton({
+            symbol_glyph_target: this.showing_search_extracted_field ? 'sys.symbol.text_and_t' : 'sys.symbol.link',
+            text: $r(this.showing_search_extracted_field ?
+              'app.string.Address_analyzer_keyword' :
+              'app.string.Address_analyzer_address'),
+            show: this.address_analyzers_desc_close_cd > 0,
+          })// Key Link
+            .visibility(this.showing_scratching_board ? Visibility.None : this.search_extracted_field_available ? Visibility.Visible : Visibility.None)
+            .onClick(() => {
+              this.showing_search_extracted_field = !this.showing_search_extracted_field;
+              if (this.is_search_input_typing) {
+                // Switch focus
+                if (this.showing_search_extracted_field) {
+                  focusControl.requestFocus('key_bar');
+                } else {
+                  focusControl.requestFocus('address_bar');
+                }
+              }
+              this.address_analyzers_desc_close_cd = 1200;
+            })
+            .animation(animation_default())
+
+          linysSymbol({
+            symbol_glyph_target: 'sys.symbol.magnifyingglass',
+          })// Add Search Engine
+            .enabled(this.showing_scratching_board ? false : true)
+            .visibility((this.is_search_input_typing || this.search_extracted_field_available) && !this.showing_scratching_board
+              ? Visibility.Visible : Visibility.None)
+            .onClick(() => {
+              let extract: string[] = extract_search(this.search_input);
+              let key = extract[1];
+              let link = extract[0];
+              if (key == '' && link == '') {
+                this.quick_keyword = this.search_input;
+                this.quick_se = undefined;
+              } else {
+                this.quick_keyword = this.search_extracted_keyword;
+                // Set default add Search engine
+                let label = this.current_title;
+                label = label.replaceAll(key, '');
+                label = label.replace(/ ?[_-] ?/g, '');
+                this.quick_se = new search_engine(label, link);
+              }
+              this.woofQuickSE_control.open();
+            })
+            .animation(animation_default())
+        } // Searching bar
+        .direction(this.preferred_hand_left_or_right == 'right' ? Direction.Ltr : Direction.Rtl)
+        .height(36)
+        .padding(this.tablet_mode ? { left: 10, right: 10 } : {})
+        .alignRules(this.tablet_mode ?
+          {
+            left: { anchor: 'left_controls', align: HorizontalAlign.End },
+            right: { anchor: 'right_controls', align: HorizontalAlign.Start },
+            bottom: { anchor: '__container__', align: VerticalAlign.Bottom }
+          } : { bottom: { anchor: '__container__', align: VerticalAlign.Bottom } })
+        .animation(animation_default())
+
+        Row({ space: this.tablet_mode ? 10 : 6 }) {
+          linysShowButton({
+            show: this.showing_scratching_board,
+            text: $r('app.string.Index_drop_scratching_board'),
+            symbol_glyph_target: this.showing_scratching_board ? 'sys.symbol.chevron_down' : 'sys.symbol.doc_rectify'
+          })// Scratching board
+            .visibility(this.drop_result_string.length > 0 ? Visibility.Visible : Visibility.None)
+            .animation(animation_default())
+            .onClick(() => {
+              this.show_scratching_board();
+            })
+
+          linysShowButton({
+            show: this.showing_more_options,
+            text: $r('app.string.Index_more_title'),
+            symbol_glyph_target: this.showing_more_options ? 'sys.symbol.chevron_down' : 'sys.symbol.dot_grid_2x2'
+          })// More options
+            .onClick(() => {
+              this.show_more_options()
+            })
+
+          linysShowButton({
+            symbol_glyph_target: 'sys.symbol.rectangle_stack',
+            show: this.showing_tabs
+          })// Tabs
+            .onClick(() => {
+              this.show_tabs();
+            })
+            .gesture(
+              LongPressGesture({ repeat: false })
+                .onAction(() => {
+                  this.new_tab();
+                }))
+            .onMouse((e) => {
+              if (e.button == MouseButton.Right && e.action == MouseAction.Press) {
+                // Right click
+                this.new_tab();
+              }
+            })
+            .keyboardShortcut(this.bunch_of_key_shortcuts.show_tabs.main_key, this.bunch_of_key_shortcuts.show_tabs.modifier)
+
+          linysShowButton({
+            show: this.showing_bookmarks,
+            text: $r('app.string.Index_bookmarks_title'),
+            symbol_glyph_target: 'sys.symbol.bookmark'
+          })// Bookmarks
+            .gesture(
+              LongPressGesture({ repeat: false })
+                .onAction(() => {
+                  this.show_add_bookmarks();
+                }))
+            .onMouse((e) => {
+              if (e.button == MouseButton.Right && e.action == MouseAction.Press) {
+                // Right click
+                this.show_add_bookmarks();
+              }
+            })
+            .onClick(() => {
+              this.show_bookmarks();
+            })
+
+          Row() {
+            linysShowButton({
+              text: $r('app.string.Index_downloads_title'),
+              symbol_glyph_target: this.showing_downloads ? 'sys.symbol.chevron_down' : 'sys.symbol.download',
+              show: this.showing_downloads,
+              color_false: !this.showing_downloads && this.download_started_popup ?
+              this.color_current_primary : this.color_current_font
+            })
+            Scroll() {
+              Text($r('app.string.Index_download_task_start'))
+                .fontSize(fontSize_Large() - 2)
+                .fontColor(this.color_current_primary)
+                .margin({ right: this.showing_downloads ? 5 : 0 })
+            }
+            .width(this.download_started_popup ? undefined : 0)
+            .scrollable(ScrollDirection.Horizontal)
+            .scrollBar(BarState.Off)
+            .animation(animation_default())
+          } // Downloads
+          .padding(!this.showing_downloads && this.download_started_popup ? 5 : 0)
+          .backgroundColor(this.download_started_popup ? this.color_current_font : "transparent")
+          .borderRadius(10)
+          .clickEffect(click_effect_default())
+          // .visibility(this.bunch_of_downloads.list_of_urls.length > 0 ? Visibility.Visible : Visibility.None)
+          .animation(animation_default())
+          .onClick(() => {
+            if (this.download_started_popup) {
+              this.download_started_popup = false;
+            } else {
+              this.show_downloads();
+            }
+          })
+
+          linysShowButton({
+            show: this.showing_app_settings,
+            text: $r('app.string.Index_app_settings_title'),
+            symbol_glyph_target: this.showing_app_settings ? 'sys.symbol.chevron_down' : 'sys.symbol.gearshape'
+          })// Settings
+            .onClick(() => {
+              this.show_app_settings()
+            })
+
+        } // Title Bar of right controls
+        .height(36)
+        .alignRules({
+          right: { anchor: '__container__', align: HorizontalAlign.End },
+          top: { anchor: '__container__', align: VerticalAlign.Top }
+        })
+        .animation(animation_default())
+        .id('right_controls')
+
+      } // Button controls and search
+      .padding({ left: 15, right: 15 })
+      .width("100%")
+      .height(this.tablet_mode || this.showing_scratching_board ? 36 : 82)
+      .animation(animation_default())
+
+      EstimatedDestination({
+        is_http_no_s: this.search_input_unify_scheme_http,
+        is_search_input_typing: this.is_search_input_typing,
+        is_search_input_address: this.is_search_input_address,
+        search_input: this.search_input,
+        search_input_unified: this.search_input_unified
+      })
+        .visibility(this.is_search_input_typing && this.title_bar_position == 'top' ? Visibility.Visible : Visibility.None)
+        .padding({ left: 15, right: 15 })
+        .width("100%")
+        .animation(animation_default())
+        .constraintSize({ minHeight: 20 })
+
+      Scroll() {
+        Column({ space: 10 }) {
+          RelativeContainer() {
+            Row({ space: 10 }) {
+              linysSymbol({
+                symbol_glyph_target: 'sys.symbol.doc_text_badge_magnifyingglass'
+              }) // in-page Search
+
+              TextInput({
+                text: this.current_in_page_searching_keyword,
+                placeholder: $r('app.string.More_in_page_search')
+              })
+                .onChange((edit) => {
+                  this.current_in_page_searching_keyword = edit;
+                  this.bunch_of_tabs.workingMainTab().searching_keyword = edit;
+                })
+                .visibility(this.showing_scratching_board ? Visibility.Hidden : Visibility.Visible)// .margin({ left: 10 })
+                .layoutWeight(1)
+                .allowDrop(null)
+                .height(capsule_bar_height())
+                .fontWeight(FontWeight.Regular)
+                .fontColor(this.color_current_font)
+                .caretColor(this.color_current_font)
+                .selectedBackgroundColor(this.color_current_font)
+                .onDragEnter(() => {
+                  this.showing_scratching_board = false;
+                })
+                .enabled(this.showing_scratching_board ? false : true)
+                .opacity(this.showing_scratching_board ? 0.8 : 1)
+                .onSubmit(() => {
+                  try {
+                    this.bunch_of_tabs.workingMainTab().controller.searchAllAsync(this.current_in_page_searching_keyword);
+                  } catch (e) {
+                    console.error(e);
+                  }
+                })
+                .id('in_page_search_blank')
+
+              linysSymbol({ symbol_glyph_target: 'sys.symbol.magnifyingglass' })
+                .onClick(() => {
+                  try {
+                    this.bunch_of_tabs.workingMainTab().controller.searchAllAsync(this.current_in_page_searching_keyword);
+                  } catch (e) {
+                    console.error(e);
+                  }
+                })
+              // .margin({ left: 10 })
+
+              linysSymbol({ symbol_glyph_target: 'sys.symbol.xmark' })
+                .onClick(() => {
+                  this.current_in_page_searching_keyword = '';
+                  this.current_in_page_searching_stats_current = 0;
+                  this.current_in_page_searching_stats_total = 0;
+                  this.bunch_of_tabs.workingMainTab().searching_keyword_stats_current = 0;
+                  this.bunch_of_tabs.workingMainTab().searching_keyword_stats_total = 0;
+                  this.bunch_of_tabs.workingMainTab().searching_keyword = '';
+                  try {
+                    this.bunch_of_tabs.workingMainTab().controller.clearMatches();
+                  } catch (e) {
+                    console.error(e);
+                  }
+                })// .margin({ left: 10 })
+                .visibility(this.current_in_page_searching_keyword != "" ? Visibility.Visible : Visibility.None)
+                .animation(animation_default())
+            } // Search bar
+            .margin(this.tablet_mode ? (this.preferred_hand_left_or_right == 'right' ? { right: 10, left: 5 } : { left: 10, right: 5 }) : { left: 5, right: 5 })
+            .direction(this.preferred_hand_left_or_right == 'right' ? Direction.Ltr : Direction.Rtl)
+            .id('in_page_search_bar')
+            .alignRules(this.tablet_mode ? {
+              left: { anchor: "__container__", align: HorizontalAlign.Start },
+              right: { anchor: "in_page_search_operation_buttons", align: HorizontalAlign.Start }
+            } : {
+              left: { anchor: "__container__", align: HorizontalAlign.Start },
+              right: { anchor: "__container__", align: HorizontalAlign.End },
+              top: { anchor: "__container__", align: VerticalAlign.Top }
+            })
+            .animation(animation_default())
+
+            Row({ space: 10 }) {
+              linysText({
+                text: this.current_in_page_searching_stats_current.toString() + '/' +
+                this.current_in_page_searching_stats_total.toString()
+              })
+                .animation(animation_default())
+              linysSymbol({ symbol_glyph_target: 'sys.symbol.chevron_up' })
+                .keyboardShortcut(FunctionKey.DPAD_UP, [])
+                .onClick(() => {
+                  try {
+                    this.bunch_of_tabs.workingMainTab().controller.searchNext(false);
+                  } catch (e) {
+                    console.error(e);
+                  }
+                })
+              linysSymbol({ symbol_glyph_target: 'sys.symbol.chevron_down' })
+                .keyboardShortcut(FunctionKey.DPAD_DOWN, [])
+                .onClick(() => {
+                  try {
+                    this.bunch_of_tabs.workingMainTab().controller.searchNext(true);
+                  } catch (e) {
+                    console.error(e);
+                  }
+                })
+            } // Operation buttons
+            .justifyContent(this.preferred_hand_left_or_right == 'right' ? FlexAlign.End : FlexAlign.Start)
+            .margin(!this.tablet_mode ? { left: 5, right: 5 } : 0)
+            .id('in_page_search_operation_buttons')
+            .padding({ left: this.tablet_mode ? 10 : 0 })
+            .alignRules(this.tablet_mode ? {
+              right: { anchor: "__container__", align: HorizontalAlign.End },
+              center: { anchor: "__container__", align: VerticalAlign.Center }
+            } : {
+              left: { anchor: "__container__", align: HorizontalAlign.Start },
+              right: { anchor: "__container__", align: HorizontalAlign.End },
+              bottom: { anchor: "__container__", align: VerticalAlign.Bottom }
+            })
+            .animation(animation_default())
+          } // Search in page
+          .direction(this.preferred_hand_left_or_right == 'right' ? Direction.Ltr : Direction.Rtl)
+          .width("100%")
+          .height(20 + (this.tablet_mode ? capsule_bar_height() : 2 * capsule_bar_height()))
+          .backgroundColor(this.current_in_page_searching_stats_total <= 0
+            ? this.color_current_primary
+            : add_transparency(this.color_current_primary, 128))
+          .borderRadius(10)
+          .padding(10)
+          .animation(animation_default())
+
+          Scroll() {
+            Row({ space: 10 }) {
+              linysSymbol({
+                symbol_glyph_target: 'sys.symbol.printer'
+              })// Print
+                .onClick(() => {
+                  print_web(this.bunch_of_tabs.workingMainTab().controller);
+                })
+              linysSymbol({
+                symbol_glyph_target: 'sys.symbol.tracking_cookie_interception'
+              })// Cookies
+                .onClick(() => {
+                  this.cookies_control.open();
+                })
+              linysSymbol({
+                symbol_glyph_target: 'sys.symbol.picture_2'
+              })// Image
+                .onClick(() => {
+                  this.image_manage_control.open();
+                })
+              linysSymbol({
+                symbol_glyph_target: 'sys.symbol.ad_circle_slash'
+              })// Enable ads blocker
+                .onClick(() => {
+                  this.woofAdsBlocker_control.open();
+                })
+              linysSymbol({
+                symbol_glyph_target: 'sys.symbol.arrow_counterclockwise_clock'
+              })// History
+                .onClick(() => {
+                  this.woofHistory_control.open();
+                })
+              linysSymbol({
+                symbol_glyph_target: this.collect_new_history ? 'sys.symbol.eye_slash' : 'sys.symbol.eye'
+              })// UA
+                .onClick(() => {
+                  // todo
+                  this.collect_new_history = !this.collect_new_history;
+                  this.bunch_of_settings.set('collect_new_history', this.collect_new_history);
+                  this.getUIContext().getPromptAction().showToast({
+                    message: this.collect_new_history ? '无痕浏览已关闭' : '无痕浏览已开启',
+                  })
+                })
+              linysSymbol({
+                symbol_glyph_target: this.fullscreen_mode ?
+                  'sys.symbol.arrow_down_right_and_arrow_up_left' :
+                  'sys.symbol.arrow_up_left_and_arrow_down_right'
+              })// FullScreen
+                .onClick(() => {
+                  this.fullscreen_mode = !this.fullscreen_mode;
+                  this.showing_more_options = false;
+                })
+            } // Small buttons
+            .justifyContent(FlexAlign.End)
+            .margin({bottom: 5})
+          }
+          .scrollable(ScrollDirection.Horizontal)
+          .scrollBar(BarState.Off)
+          .edgeEffect(EdgeEffect.Spring)
+          .visibility(this.visible_when_in_page_search())
+          .animation(animation_default())
+
+        } // Title Bar of More Options
+        .alignItems(this.preferred_hand_left_or_right == 'right' ? HorizontalAlign.End : HorizontalAlign.Start)
+        .width("100%")
+        .animation(animation_default())
+      } // More Options
+      .margin({ left: 15, right: 15 })
+      .align(Alignment.Bottom)
+      .borderRadius(16)
+      .visibility(this.showing_more_options ? Visibility.Visible : Visibility.None)
+      // .layoutWeight(this.showing_more_options ? 1 : undefined)
+      .animation(animation_default())
+      .scrollable(ScrollDirection.Vertical)
+      .edgeEffect(EdgeEffect.Spring)
+      .scrollBar(BarState.Off)
+
+      meowAppSettings({ showing_app_settings: this.showing_app_settings })// Settings
+        .visibility(this.showing_app_settings ? Visibility.Visible : Visibility.None)
+        .animation(animation_default())
+
+      meowDownloads({ screen_width: this.screen_width })
+        .visibility(this.showing_downloads ? Visibility.Visible : Visibility.None)
+        .animation(animation_default())
+
+      meowScratchingBoard()
+        .visibility(this.showing_scratching_board ? Visibility.Visible : Visibility.None)
+        .animation(animation_default())
+
+
+      if (this.title_bar_position == "bottom") {
+        Row().height(5)
+      }
+    } // Title Bars
+    .clip(true)
+    .margin({ bottom: 5 })
+    .allowDrop(allow_drop_types())
+    .onDrop((e) => {
+      drop_to_scratching_board(e);
+    })
+    .onDragEnter(() => {
+      this.showing_scratching_board = true;
+      this.showing_more_options = false;
+    })
+    .onDragLeave(() => {
+      this.showing_scratching_board = false;
+    })
+    .width("100%")
+    .constraintSize({
+      maxHeight: this.max_height_of_title_bar()
+    })
+    .alignItems(HorizontalAlign.Start)
+    .backgroundColor(!this.showing_more_options || this.current_in_page_searching_stats_total <= 0
+      ? this.color_current_secondary
+      : add_transparency(this.color_current_secondary, 128))
+    .alignRules(this.title_bar_alignRules)
+    .padding({ left: this.leftAvoidWidth, right: this.rightAvoidWidth })
+    .onAreaChange((_o, n) => {
+      if (!this.showing_more_options && !this.is_search_input_typing && !this.showing_app_settings && !this.showing_downloads &&
+        !this.showing_scratching_board) {
+        this.bar_height = n.height as number;
+      }
+    })
+    .offset({ y: !this.fullscreen_mode ? 0 : (this.title_bar_position == 'bottom' ? 1.1 : -1.1) * this.bar_height })
+    .animation(animation_default())
+    .onAppear(() => {
+      // console.log("[Meow][meowTitleBar] Title Bar READY")
+      setInterval(() => {
+        if (this.address_analyzers_desc_close_cd > 0) {
+          this.address_analyzers_desc_close_cd -= 10;
+        }
+      }, 10)
+    })
+    .keyboardShortcut(this.bunch_of_key_shortcuts.in_page_search.main_key, this.bunch_of_key_shortcuts.in_page_search.modifier, () => {
+      this.showing_more_options = true;
+      focusControl.requestFocus("in_page_search_blank")
+    })
+  }
+
+  // Visibility
+
+  visible_when_search_input_is_not_blank() {
+    return this.search_input == "" ? Visibility.None : Visibility.Visible
+  }
+
+  visible_when_typing() {
+    return this.is_search_input_typing ? Visibility.Visible : Visibility.None
+  }
+
+  visible_when_typing_not() {
+    return !this.is_search_input_typing ? Visibility.Visible : Visibility.None
+  }
+
+  visible_when_title_bar_on_top() {
+    return this.title_bar_position == "top" ? Visibility.Visible : Visibility.None;
+  }
+
+  visible_when_title_bar_on_bottom() {
+    return this.title_bar_position == "bottom" ? Visibility.Visible : Visibility.None;
+  }
+
+  visible_when_no_panels_open_in_normal_mode() {
+    let result: Visibility = Visibility.Visible;
+    if (this.showing_more_options || this.showing_downloads || this.showing_app_settings || this.showing_tabs ||
+    this.showing_bookmarks || this.download_started_popup || this.showing_scratching_board) {
+      if (this.tablet_mode == false) {
+        result = Visibility.None;
+      }
+    }
+    return result;
+  }
+
+  visible_in_tablet_mode() {
+    return this.tablet_mode ? Visibility.Visible : Visibility.None;
+  }
+
+  visible_when_typing_in_tablet_mode() {
+    return this.tablet_mode && this.is_search_input_typing ? Visibility.Visible : Visibility.None;
+  }
+
+  visibility_of_horizontal_tabs() {
+    if (!this.showing_tabs) {
+      return Visibility.None;
+    }
+
+    if (this.tablet_mode) {
+      if (this.tabs_style == "horizontal") {
+        return Visibility.Visible;
+      }
+    } else {
+      // Non tablet mode
+      if (this.tabs_style_non_tablet_mode == "horizontal") {
+        return Visibility.Visible;
+      }
+    }
+
+    return Visibility.None
+  }
+
+  visible_when_in_page_search() {
+    return (this.current_in_page_searching_stats_total <= 0 ? Visibility.Visible : Visibility.None);
+  }
+
+  // Events
+
+  on_download_start() {
+    this.download_started_popup = true;
+    setTimeout(() => {
+      this.download_started_popup = false;
+    }, animation_popup_duration())
+  }
+
+  on_search_input_change() {
+    if (this.search_input_update_source == 'extracted_key_edit') {
+      return;
+    }
+    if (this.search_input) {
+      let extract: string[] = extract_search(this.search_input);
+      if (extract[1] == '' && extract[0] == '') {
+        this.showing_search_extracted_field = false;
+        this.search_extracted_field_available = false;
+      } else {
+        this.search_extracted_field_available = true;
+        this.search_extracted_engine = extract[0];
+        this.search_extracted_keyword = extract[1];
+      }
+    }
+  }
+
+  // UI controls
+
+  show_tabs() {
+    if (!this.tablet_mode && this.tabs_style_non_tablet_mode == "vertical") {
+      this.showing_more_options = false;
+      this.showing_app_settings = false;
+      this.showing_downloads = false;
+      this.showing_scratching_board = false;
+      // allow bookmarks and tabs coexist in tablet mode
+      this.showing_bookmarks = false;
+    }
+    this.showing_tabs = !this.showing_tabs;
+  }
+
+  show_bookmarks() {
+    if (!this.tablet_mode) {
+      this.showing_more_options = false;
+      this.showing_app_settings = false;
+      this.showing_downloads = false;
+      this.showing_scratching_board = false;
+    }
+    if (!this.tablet_mode) {
+      // allow bookmarks and tabs coexist in tablet mode
+      if (this.tabs_style_non_tablet_mode == "vertical") {
+        this.showing_tabs = false;
+      }
+    }
+    this.showing_bookmarks = !this.showing_bookmarks;
+  }
+
+  close_tabs_and_bookmarks_non_tablet() {
+    if (!this.tablet_mode) {
+      if (this.tabs_style_non_tablet_mode == "vertical") {
+        this.showing_tabs = false;
+      }
+      this.showing_bookmarks = false;
+    }
+  }
+
+  show_more_options() {
+    this.close_tabs_and_bookmarks_non_tablet();
+
+    // Close all other panels
+    this.showing_scratching_board = false;
+    this.showing_downloads = false;
+    this.showing_app_settings = false;
+    this.showing_more_options = !this.showing_more_options;
+  }
+
+  show_app_settings() {
+    this.close_tabs_and_bookmarks_non_tablet();
+
+    // Close all other panels
+    this.showing_scratching_board = false;
+    this.showing_downloads = false;
+    this.showing_more_options = false;
+    this.showing_app_settings = !this.showing_app_settings;
+  }
+
+  show_downloads() {
+    this.close_tabs_and_bookmarks_non_tablet();
+
+    // Close all other panels
+    this.showing_scratching_board = false;
+    this.showing_more_options = false;
+    this.showing_app_settings = false;
+    this.showing_downloads = !this.showing_downloads;
+  }
+
+  show_scratching_board() {
+    this.close_tabs_and_bookmarks_non_tablet();
+
+    // Close all other panels
+    this.showing_more_options = false;
+    this.showing_downloads = false;
+    this.showing_app_settings = false;
+    this.showing_scratching_board = !this.showing_scratching_board;
+  }
+
+  // Operations
+
+  show_add_bookmarks() {
+    if (this.showing_bookmarks) {
+      this.showing_bookmarks = false;
+      AppStorage.set('adding_bookmark', false);
+    } else {
+      // Add Bookmark
+      AppStorage.set('bookmark_add_label', this.bunch_of_tabs.workingMainTab().title);
+      AppStorage.set('bookmark_add_link', url_resource_to_meow(this.bunch_of_tabs.workingMainTab().url));
+      this.showing_bookmarks = true;
+      AppStorage.set('adding_bookmark', true);
+    }
+  }
+
+  new_tab() {
+    AppStorage.set('extra_background', false);
+    AppStorage.set('universal_new_tab_gateway', "new_tab");
+  }
+
+  // Web control events
+
+  submit_searching() {
+    if (this.search_input_unified != "") {
+      let unified_url: string = this.search_input_unified;
+      // unify input into a legal link
+      unified_url = url_meow_to_resource(unified_url);
+      // translate "meow://" into "resource://"
+      try {
+        this.bunch_of_tabs.loadUrl_onWorkingTab(unified_url);
+      } catch (e) {
+        console.error('[Meow][meowTitleBar][submit_searching][loadUrl_onWorkingTab] ' + e);
+        console.error('\t' + unified_url);
+      }
+    }
+    if (!this.is_search_input_address) {
+      this.showing_search_extracted_field = true;
+    }
+  }
+
+  go_backward() {
+
+    this.bunch_of_tabs.goBackward_onWorkingTab();
+  }
+
+  go_forward() {
+    this.bunch_of_tabs.goForward_onWorkingTab();
+  }
+
+  go_home() {
+
+    this.bunch_of_tabs.go_home_onWorkingTab();
+  }
+
+  refresh_page() {
+
+    this.bunch_of_tabs.refresh_onWorkingTab()
+    this.bunch_of_tabs.workingMainTab().update_is_loading(true)
+  }
+
+  stop_page() {
+    this.bunch_of_tabs.stop_onWorkingTab();
+    this.bunch_of_tabs.workingMainTab().update_is_loading(false);
+    this.sync_tabs_list_info();
+    this.update_tabs_current_info();
+  }
+
+  // Values
+
+  max_height_of_title_bar() {
+    if (!this.showing_more_options) {
+      return undefined;
+    }
+
+    if (this.current_in_page_searching_stats_total > 0) {
+      // in-page Searching
+      let base_height = this.tablet_mode ? 110 : 190;
+      let tabs_offset = 0
+      let position_offset = this.title_bar_position == 'top' ? 0 : 15;
+      return base_height + tabs_offset + position_offset;
+    } else {
+      // Normal height
+      return Math.min(this.full_screen_height, this.tablet_mode ? 370 : 460);
+    }
+  }
+
+  // Data synchronizing
+
+  update_search_input(content?: string) {
+    this.search_input_update_source = 'direct_edit';
+    if (content) {
+      this.search_input = content;
+    } else {
+      this.search_input = "";
+    }
+    let s: string = content || "当前数量为空";
+    let uni_result = unify_search_input_into_url(s, this.search_input_unify_scheme_http ? "http" : "https");
+    this.search_input_unified = uni_result[0] as string;
+    this.is_search_input_address = uni_result[1] as boolean;
+    this.search_input_unified = url_resource_to_meow(this.search_input_unified);
+  }
+
+  update_tabs_current_info() {
+    this.current_title = this.tab_titles[this.current_main_tab_index];
+    this.current_url = this.tab_urls[this.current_main_tab_index];
+    this.current_url = url_resource_to_meow(this.current_url);
+    // translate "resource://" into "meow://"
+    this.current_loading_progress = this.tab_loading_progresses[this.current_main_tab_index];
+    this.current_is_loading = this.tab_is_loading[this.current_main_tab_index];
+    // Set loading progress
+    this.current_in_page_searching_keyword = this.bunch_of_tabs.workingMainTab().searching_keyword;
+    this.current_in_page_searching_stats_current = this.bunch_of_tabs.workingMainTab().searching_keyword_stats_current;
+    this.current_in_page_searching_stats_total = this.bunch_of_tabs.workingMainTab().searching_keyword_stats_total;
+  }
+
+  sync_tabs_list_info() {
+    this.tab_titles = this.bunch_of_tabs.get_all_titles()
+    this.tab_urls = this.bunch_of_tabs.get_all_urls()
+    this.tab_is_loading = this.bunch_of_tabs.get_all_is_loading();
+    this.tab_loading_progresses = this.bunch_of_tabs.get_all_loading_progress()
+  }
+
+  @Builder
+  shareCommentBuilder() {
+    Column({ space: 5 }) {
+      Image($r('app.media.share_active'))
+        .width(21)
+        .height(21)
+    }
+  }
+}
+
+export default meowTitleBar;
+
+@Component
+struct EstimatedDestination {
+  // Colors
+  @StorageProp('color_current_primary') color_current_primary: ResourceColor = $r('app.color.start_window_background');
+  @StorageProp('color_current_secondary') color_current_secondary: ResourceColor = $r('app.color.block_color');
+  @StorageProp('color_current_font') color_current_font: ResourceColor = $r('app.color.font_color_title');
+  // Env
+  @StorageLink('search_input_source') search_input_update_source: string = "web";
+  // Data
+  @Link is_http_no_s: boolean;
+  @Link is_search_input_typing: boolean;
+  @Link is_search_input_address: boolean;
+  @Link search_input: string;
+  @Link search_input_unified: string;
+
+  build() {
+    Row({ space: 10 }) {
+      Row({ space: 5 }) {
+        linysSymbol({
+          symbol_glyph_target: this.is_http_no_s ? "sys.symbol.lock_open" : "sys.symbol.lock"
+        })
+        linysText({
+          text: this.is_http_no_s ? "http" : "https"
+        })
+      }
+      .padding(8)
+      .onClick(() => {
+        this.is_http_no_s = !this.is_http_no_s;
+        this.update_search_input_unified(this.search_input);
+      })
+      .borderRadius(8)
+      .backgroundColor(this.color_current_primary)
+      .visibility(this.visible_when_input_is_a_scheme_less_address())
+      .clickEffect(click_effect_default())
+      .animation(animation_default())
+
+      Text("→ " + (this.search_input_unified == "" ? "( o=^•ェ•)o ?" : this.search_input_unified))
+        .fontColor(this.color_current_font)
+        .fontWeight(FontWeight.Bold)
+        .fontSize(fontSize_Normal())
+        .maxLines(4)
+        .textOverflow({ overflow: TextOverflow.Ellipsis })
+        .layoutWeight(1)
+    } // Title Bar for estimated destination indication
+  }
+
+  visible_when_typing() {
+    return this.is_search_input_typing ? Visibility.Visible : Visibility.None
+  }
+
+  is_input_scheme_less_address() {
+    if (!this.search_input || this.search_input.includes("://")) {
+      return false;
+    }
+    return this.is_search_input_address ? true : false;
+  }
+
+  visible_when_input_is_a_scheme_less_address() {
+    return this.is_input_scheme_less_address() ? Visibility.Visible : Visibility.None;
+  }
+
+  // Data
+
+  update_search_input_unified(content?: string) {
+    let s: string = content || "当前数量为空";
+    let uni_result = unify_search_input_into_url(s, this.is_http_no_s ? "http" : "https");
+    this.search_input_unified = uni_result[0] as string;
+    this.is_search_input_address = uni_result[1] as boolean;
+  }
+
+}

+ 898 - 0
home/src/main/ets/blocks/modules/meowWebView.ets

@@ -0,0 +1,898 @@
+import { animation_default, capsule_bar_height, click_effect_default, fontSize_Large } from '../../hosts/bunch_of_defaults';
+import { bunch_of_history, history_record } from '../../hosts/bunch_of_history';
+import { bunch_of_tabs, tab_label } from '../../hosts/bunch_of_tabs';
+import { sandbox_save } from '../../utils/storage_tools';
+import { match_domain, url_meow_to_resource, url_resource_to_meow, viewable_domains } from '../../utils/url_tools';
+import { webview } from '@kit.ArkWeb';
+import { bunch_of_settings } from '../../hosts/bunch_of_settings';
+import woofWantDownload from '../../dialogs/prompts/woofWantDownload';
+import { bookmark, bunch_of_bookmarks, folder, unified_item } from '../../hosts/bunch_of_bookmarks';
+import woofWantJump from '../../dialogs/prompts/woofWantJump';
+import { bunch_of_downloads } from '../../hosts/bunch_of_downloads';
+import woofWantResources from '../../dialogs/prompts/woofWantProtectedResources';
+import { print } from '@kit.BasicServicesKit';
+import { print_web } from '../../utils/print_tools';
+
+@Component
+struct meowWebView {
+  @StorageProp('currentColorMode') current_color_mode: number = 0;
+  @StorageProp('pathDir') pathDir: string = "";
+  @StorageProp('tablet_mode') tablet_mode: boolean = false;
+  @StorageLink('fullscreen_mode') fullscreen_mode: boolean = false;
+  @StorageLink('fullscreen_handler') handler: FullScreenExitHandler | null = null;
+  @StorageLink('restore_web_state_arrays') restore_web_state_arrays: Uint8Array[] = [];
+  @StorageLink('bunch_of_tabs') bunch_of_tabs: bunch_of_tabs = new bunch_of_tabs(true);
+  @StorageLink('bunch_of_history') bunch_of_history: bunch_of_history = new bunch_of_history(true);
+  @StorageLink('bunch_of_bookmarks') @Watch('refresh_homepage_shortcuts') bunch_of_bookmarks: bunch_of_bookmarks = new bunch_of_bookmarks('Bookmarks~Meow');
+  @StorageLink('bunch_of_settings') bunch_of_settings: bunch_of_settings = new bunch_of_settings(true);
+  @StorageLink('bunch_of_downloads') bunch_of_downloads: bunch_of_downloads = new bunch_of_downloads();
+  @StorageLink('current_main_tab_index') @Watch('on_tab_info_change') current_main_tab_index: number = 0;
+  @StorageLink('current_sub_tab_index') @Watch('on_tab_info_change') current_sub_tab_index: number = -1;
+  @StorageLink('homepage_background') homepage_background: PixelMap | undefined = undefined;
+  @StorageProp('screen_height') screen_height: number = 100;
+  // Controls and environments
+  @StorageLink('tab_titles') tab_titles: string[] = []
+  @StorageLink('current_title') current_title: string = "= ̄ω ̄=";
+  @StorageLink('tab_urls') tab_urls: string[] = []
+  @StorageLink('current_url') current_url: string = "= ̄ω ̄=";
+  @StorageLink('tab_loading_progresses') tab_loading_progresses: number[] = [0]
+  @StorageLink('current_loading_progress') current_loading_progress: number = 0
+  @StorageLink('tab_is_loading') tab_is_loading: boolean[] = [true]
+  @StorageLink('current_is_loading') current_is_loading: boolean = true
+  @StorageLink('current_in_page_searching_stats_current') current_in_page_searching_stats_current: number = 0;
+  @StorageLink('current_in_page_searching_stats_total') current_in_page_searching_stats_total: number = 0;
+  @StorageLink('current_in_page_searching_keyword') current_in_page_searching_keyword: string = ''
+  // Web control statuses
+  @StorageLink('current_accessForward') current_accessForward: boolean = false;
+  @StorageLink('current_accessBackward') current_accessBackward: boolean = false;
+  @StorageLink('search_input') search_input: string = "*(੭*ˊᵕˋ)੭*ଘ";
+  @StorageLink('search_input_source') search_input_update_source: string = "web";
+  @StorageLink('is_search_input_typing') is_search_input_typing: boolean = false;
+  // Settings
+  @StorageProp('collect_new_history') collect_new_history: boolean = true;
+  @StorageProp('intelligent_tracking_prevention') intelligent_tracking_prevention: boolean = false;
+  @StorageLink('use_adblock') @Watch('on_use_adblock_state_change') use_adblock: boolean = true;
+  @StorageLink('homepage_shortcuts_bookmarks_dir') @Watch('refresh_homepage_shortcuts') homepage_shortcuts_dir: string = '/';
+  @StorageLink('homepage_shortcuts_init_height') homepage_shortcuts_init_height: number = 50;
+  @StorageLink('web_force_dark_mode') web_force_dark_mode: boolean = false;
+  @StorageLink('disable_js') disable_js: boolean = false;
+  @StorageLink('disable_js_these_sites') disable_js_these_sites: string[] = [];
+  @StorageLink('disable_js_all_sites') disable_js_all_sites: boolean = true;
+  @StorageLink('disable_image') disable_image: boolean = false;
+  @StorageLink('disable_image_these_sites') disable_image_these_sites: string[] = [];
+  @StorageLink('disable_image_all_sites') disable_image_all_sites: boolean = true;
+  // Settings - Accessibility
+  @StorageLink('preferred_hand_left_or_right') preferred_hand_left_or_right: string = 'right';
+  @StorageLink('preferred_hand_reverse_homepage_shortcuts') preferred_hand_reverse_homepage_shortcuts: boolean = false;
+  // Stuffs
+  @StorageLink('universal_new_tab_gateway') new_tab_gateway: string = "";
+  @StorageLink('universal_global_custom_ua_gateway') now_global_custom_UA: string = "";
+  @StorageLink('universal_new_download_filename_gateway') new_download_filename_gateway: string = "";
+  @StorageLink('universal_new_download_additional_info') new_download_additional_info: string = "";
+  @StorageLink('universal_new_download_gateway') new_download_gateway: string = "";
+  // Visual
+  @StorageProp('tab_animation') tab_animation: AnimateParam = animation_default();
+  @State last_timeout_id: number | undefined = undefined;
+  @State shortcut_scroll_offset: number = 0;
+  @State shortcut_scroll_total_height: number = 100;
+  @State homepage_shortcuts: unified_item[] = [];
+  @State potential_jump_link: string = '';
+  @StorageLink('extra_background') extra_background: boolean = false;
+  // Download
+  @StorageLink('dl_delegate') dl_delegate: webview.WebDownloadDelegate = new webview.WebDownloadDelegate();
+  @StorageLink('blob_delegate') blob_delegate: webview.WebDownloadDelegate = new webview.WebDownloadDelegate();
+  @StorageLink('dl_controller') dl_controller: webview.WebviewController = new webview.WebviewController();
+  @StorageLink('print_adapter') print_adapter: print.PrintDocumentAdapter | undefined = undefined;
+  // dialogs
+  woofWantDownload_control: CustomDialogController = new CustomDialogController({
+    builder: woofWantDownload({
+      from: this.current_url,
+      file_name: '',
+      url: '',
+      try_additional_info: ''
+    }),
+    alignment: DialogAlignment.Center,
+    cornerRadius: 16
+  })
+  woofLinkJumper_control: CustomDialogController = new CustomDialogController({
+    builder: woofWantJump({
+      link: this.potential_jump_link,
+    }),
+    alignment: DialogAlignment.Center,
+    cornerRadius: 16
+  })
+  onPermit?: () => void;
+  onDeny?: () => void;
+  ask_resources: string[] = [];
+  source: string = '';
+  woofWantProtectedResources_control: CustomDialogController = new CustomDialogController({
+    builder: woofWantResources({
+      protected_resource_types: this.ask_resources,
+      onPermit: this.onPermit,
+      onDeny: this.onDeny,
+      source: this.source
+    }),
+    alignment: DialogAlignment.Center,
+    cornerRadius: 16
+  })
+
+  // Colors
+  @StorageProp('color_current_secondary') color_current_secondary: ResourceColor = $r('app.color.block_color');
+  // Else
+  @StorageLink('meowWebView_init_OK') init_OK: boolean = false;
+  adsBlocker_exceptions: string[] = [];
+  shortcut_scroller: Scroller = new Scroller();
+  httpAuth: boolean = false;
+
+
+  async aboutToAppear(): Promise<void> {
+    let sites_got: string;
+
+    try {
+      // Get whether image is disabled
+      this.disable_image = await this.bunch_of_settings.get('disable_image') as boolean;
+      this.disable_image_all_sites = await this.bunch_of_settings.get('disable_image_all_sites') as boolean;
+
+      // Get image ban specifications
+      sites_got = await this.bunch_of_settings.get('disable_image_these_sites') as string;
+      if (sites_got == '') {
+        this.disable_image_these_sites = [];
+      } else {
+        this.disable_image_these_sites = sites_got.split('\n');
+      }
+    } catch (e) {
+      console.error(e);
+    }
+
+    try {
+      // Get whether js is disabled
+      this.disable_js = await this.bunch_of_settings.get('disable_js') as boolean;
+      this.disable_js_all_sites = await this.bunch_of_settings.get('disable_js_all_sites') as boolean;
+
+      // Get js ban specifications
+      sites_got = await this.bunch_of_settings.get('disable_js_these_sites') as string;
+      if (sites_got == '') {
+        this.disable_js_these_sites = [];
+      } else {
+        this.disable_js_these_sites = sites_got.split('\n');
+      }
+
+    } catch (e) {
+      console.error(e);
+    }
+
+    try {
+      // Get whether ads blocker is enabled
+      this.use_adblock = await this.bunch_of_settings.get('use_adblock') as boolean;
+
+      // Get adblock exceptions
+      let got_adsBlocker_exceptions = await this.bunch_of_settings.get('adblock_exceptions') as string;
+      this.adsBlocker_exceptions = [];
+      if (got_adsBlocker_exceptions == '') {
+        this.adsBlocker_exceptions = [];
+      } else {
+        this.adsBlocker_exceptions = got_adsBlocker_exceptions.split('\n');
+      }
+      // Sync to AppStorage
+      AppStorage.set('adblock_exceptions', this.adsBlocker_exceptions);
+
+      // Get homepage shortcuts height
+      this.homepage_shortcuts_init_height = await this.bunch_of_settings.get('homepage_shortcuts_init_height') as number;
+
+    } catch (e) {
+      console.error(e);
+    }
+
+    // try {
+    //   let atManager = abilityAccessCtrl.createAtManager();
+    //   atManager.requestPermissionsFromUser(this.getUIContext().getHostContext(), ['ohos.permission.CAMERA', 'ohos.permission.MICROPHONE'])
+    //     .then((data) => {
+    //       console.info('data:' + JSON.stringify(data));
+    //       console.info('data permissions:' + data.permissions);
+    //       console.info('data authResults:' + data.authResults);
+    //     }).catch((error: BusinessError) => {
+    //     console.error(`Failed to request permissions from user. Code is ${error.code}, message is ${error.message}`);
+    //   })
+    // } catch (e) {
+    //   console.error(e);
+    // }
+
+    // init OK
+    setTimeout(() => {
+      this.init_OK = true;
+      // console.log('qwq')
+    }, 10)
+  }
+
+  build() {
+    RelativeContainer() {
+
+      Row() // Background color
+        .backgroundColor(this.current_color_mode == 0 ? (this.web_force_dark_mode ? 'black' : 'white') : 'white')
+        .height('100%')
+        .width('100%')
+        .visibility(this.extra_background ? Visibility.Visible : Visibility.Hidden)
+      // .animation(animation_default())
+
+      // Specific web to handle downloads
+      Web({ src: $rawfile('home.html'), controller: this.dl_controller })
+        .onPageEnd(() => {
+          try {
+            // Bind delegate at init
+            this.dl_controller.setDownloadDelegate(this.dl_delegate);
+          } catch (e) {
+            console.error(e);
+          }
+        })
+        // .opacity(0.2)
+        .visibility(Visibility.Hidden)
+
+
+      // 为每一个标签创建一个web视图
+      ForEach(this.bunch_of_tabs.Labels, (Label: tab_label) => {
+        Column() {
+          Web({
+            src: this.src_of_tab_index(Label.index_key),
+            controller: this.bunch_of_tabs.Tabs[Label.index_key] !== undefined
+              ? this.bunch_of_tabs.Tabs[Label.index_key].controller
+              : undefined,
+            // renderMode: RenderMode.SYNC_RENDER
+          })// Main WEB
+            .width("100%")
+            .height("100%")
+            .backgroundColor('transparent')
+            .darkMode(this.web_force_dark_mode ? WebDarkMode.On : WebDarkMode.Off)
+            .databaseAccess(true)
+            .javaScriptAccess(this.my_js(Label.index_key))
+            .imageAccess(this.my_image(Label.index_key))
+            .onlineImageAccess(this.my_image(Label.index_key))
+            .fileAccess(true)
+            .domStorageAccess(true)
+            .multiWindowAccess(true)
+            .mixedMode(MixedMode.All)
+            .allowWindowOpenMethod(true)
+            .onSearchResultReceive((result) => {
+              this.current_in_page_searching_stats_current =
+                Math.min(result.numberOfMatches, result.activeMatchOrdinal + 1);
+              this.current_in_page_searching_stats_total = result.numberOfMatches;
+              this.bunch_of_tabs.workingMainTab().searching_keyword_stats_current =
+                Math.min(result.numberOfMatches, result.activeMatchOrdinal + 1);
+              this.bunch_of_tabs.workingMainTab().searching_keyword_stats_total = result.numberOfMatches;
+            })
+            .onWindowNew((event) => {
+              // TODO: Find out why this triggers twice when click once
+              let new_target_url = event.targetUrl;
+              console.log('[meowWebView] New tab in new window!')
+              this.new_tab_gateway = new_target_url;
+              event.handler.setWebController(null);
+            })
+            .onErrorReceive((e) => {
+              if (e) {
+                console.error("[ArkWeb][ERROR] " + e.error.getErrorCode() + ", " + e.error.getErrorInfo()
+                  + " @ " + this.tab_urls[Label.index_key])
+                // Log web errors
+              }
+            })
+            .onPageBegin(() => {
+              console.log("[Meow][ArkWeb] on page begin");
+
+              // bind blob download delegate
+              this.bunch_of_downloads.init_blob_delegate();
+              this.bunch_of_tabs.Tabs[Label.index_key].controller.setDownloadDelegate(this.blob_delegate);
+
+              if (Label.index_key >= this.bunch_of_tabs.get_tabs_count()) {
+                // If not synced, idk why but o(=•ェ•=)m
+                return;
+              }
+
+              // Update History
+              this.current_accessBackward = this.bunch_of_tabs.Tabs[Label.index_key].controller.accessBackward()
+              this.current_accessForward = this.bunch_of_tabs.Tabs[Label.index_key].controller.accessForward()
+              // Ask tab to update its info (Reset)
+              this.bunch_of_tabs.Tabs[Label.index_key].update_title()
+              this.bunch_of_tabs.Tabs[Label.index_key].update_url()
+              this.bunch_of_tabs.Tabs[Label.index_key].update_is_loading(true)
+              this.bunch_of_tabs.Tabs[Label.index_key].update_loading_progress(0)
+              // Get synced lists
+              this.sync_list_info()
+              // Update Input Search Box
+              this.update_current_info()
+              this.update_search_box_text(this.current_url)
+
+            })
+            .onPageEnd(() => {
+              // console.log("[Meow][ArkWeb] on page end")
+              this.bunch_of_tabs.Tabs[Label.index_key].update_title();
+              this.bunch_of_tabs.Tabs[Label.index_key].update_url();
+              this.bunch_of_tabs.Tabs[Label.index_key].update_is_loading(false);
+              this.tab_is_loading = this.bunch_of_tabs.get_all_is_loading();
+              this.update_current_info();
+
+              // Save browse history if is neither resource tab nor a recovery tab
+              if (this.collect_new_history
+                && !this.bunch_of_tabs.Tabs[Label.index_key].restore_on_creation
+                && !this.tab_urls[Label.index_key].includes("resource://")
+              ) {
+                let new_record = new history_record(this.tab_titles[Label.index_key], this.tab_urls[Label.index_key])
+                this.bunch_of_history.add_history(new_record, true, true);
+              } else {
+                this.bunch_of_tabs.Tabs[Label.index_key].restore_on_creation = false;
+                // log
+                if (!this.collect_new_history) {
+                  console.log("[meowWebView] Didn't save history for settings disabled.");
+                } else {
+                  console.log("[meowWebView] Didn't save history for a recovery process in progress");
+                }
+              }
+
+              this.determine_extra_background();
+            })
+            .onProgressChange((event) => {
+              if (!event) {
+                return;
+              }
+
+              let progress: number = event.newProgress;
+              // console.log("[Meow][ArkWeb] on progress change: " + progress.toString())
+              // Update current loading progress
+              if (progress == 0) {
+                return;
+              }
+
+              if (Label.index_key >= this.bunch_of_tabs.get_tabs_count()) {
+                // If not synced, idk why but o(=•ェ•=)m
+                return;
+              }
+
+              this.bunch_of_tabs.Tabs[Label.index_key].update_title()
+              this.bunch_of_tabs.Tabs[Label.index_key].update_url()
+              this.bunch_of_tabs.Tabs[Label.index_key].update_loading_progress(progress)
+              // Ask tab to update its info
+              this.sync_list_info()
+              // Get synced lists
+              this.update_current_info()
+              // Update Input Search Box
+              this.update_search_box_text(this.current_url)
+
+              if (this.bunch_of_tabs.Tabs[Label.index_key].restore_on_creation) {
+                return;
+              }
+
+              // Get web_state
+              let web_state = this.bunch_of_tabs.Tabs[Label.index_key].controller.serializeWebState();
+              // Sync to bunch_of_tabs
+              this.bunch_of_tabs.Tabs[Label.index_key].web_state_array = web_state;
+              // save web state to sandbox storage
+              if (web_state && !this.bunch_of_tabs.Tabs[Label.index_key].restore_on_creation) {
+                let identifier = "continue/continue_tabs_web_state_array_" + Label.index_key.toString();
+                sandbox_save(identifier, web_state.buffer);
+              }
+            })
+            .onDownloadStart((event) => {
+              this.download_event(event);
+            })
+            .onFullScreenEnter((event) => {
+              this.fullscreen_mode = true;
+              this.handler = event.handler;
+            })
+            .onFullScreenExit(() => {
+              this.fullscreen_mode = false;
+            })
+            .onControllerAttached(async () => {
+              // Set UA
+              if (this.now_global_custom_UA != "") {
+                this.bunch_of_tabs.Tabs[Label.index_key].controller.setCustomUserAgent(this.now_global_custom_UA);
+              }
+
+              // Set Ads Blocker
+              console.log('[Meow][meowWebView] enableAdsBlock: ' + (this.use_adblock ? 'true' : 'false'))
+              this.bunch_of_tabs.Tabs[Label.index_key].controller.enableAdsBlock(this.use_adblock);
+
+              // TODO: Find out why this doesn't work sometimes for a recovered webpage :O getting crazy
+              // Set Ads Blocker exception list
+              try {
+                console.log('[Meow][meowWebView] add Ads Blocker Exceptions success. Operated by meowWebView ~')
+                webview.AdsBlockManager.addAdsBlockDisallowedList(this.adsBlocker_exceptions);
+              } catch (e) {
+                console.error('[ERROR][Meow][meowWebView] add Ads Blocker Exceptions error: ' + e);
+              }
+
+              // Set intelligent tracking prevention
+              this.bunch_of_tabs.Tabs[Label.index_key].controller.enableIntelligentTrackingPrevention(this.intelligent_tracking_prevention);
+              console.log('[Meow][meowWebView] ArkWeb Intelligent Tracking Prevention ' +
+              this.intelligent_tracking_prevention.toString() + '!')
+
+              // Restore Web State
+              setTimeout(() => {
+                if (this.bunch_of_tabs.Tabs[Label.index_key].restore_on_creation) {
+                  if (Label.index_key < this.restore_web_state_arrays.length) {
+                    this.bunch_of_tabs.restore_web_state(this.restore_web_state_arrays[Label.index_key],
+                      Label.index_key);
+                  } else {
+                    console.error('[ERROR][Meow][meowWebView] Restore web state failed ' +
+                      'for an out-of-bound index in restore_web_state_arrays.')
+                  }
+                }
+              }, 0)
+
+              // Set allowed resources
+              try {
+                let allowed = [
+                  this.getUIContext().getHostContext()!.resourceDir,
+                // this.getUIContext().getHostContext()!.filesDir,
+                // '/data/storage/el2/base/haps/cache'
+                ]
+                this.bunch_of_tabs.Tabs[Label.index_key].controller.setPathAllowingUniversalAccess(allowed);
+                console.log('[meowWebView] Init set allowed resources: [' + allowed.join(", ") + ']');
+              } catch (e) {
+                console.error('[meowWebView] Init set allowed resources error: ' + e);
+              }
+            })
+            .forceDarkAccess(this.web_force_dark_mode)// Force dark mode
+            .onLoadIntercept((event) => {
+              if (event) {
+                let url: string = event.data.getRequestUrl();
+                // Do not intercept data:
+                if (url.substring(0, 5) == 'data:') {
+                  return false;
+                }
+                // Links or urls
+                let match_domain_result = match_domain(url);
+                if (viewable_domains().includes(match_domain_result[0])) {
+                  // Viewable
+                  return false;
+                } else {
+                  // Not viewable, perhaps is a link that can jump?
+                  this.potential_jump_link = url;
+                  this.woofLinkJumper_control.open();
+                  return true;
+                }
+              }
+              return false;
+            })
+            .onPermissionRequest((event) => {
+              if (!event) {
+                return;
+              }
+              this.onPermit = () => {
+                event.request.grant(event.request.getAccessibleResource());
+              }
+              this.onDeny = () => {
+                event.request.deny();
+              }
+              this.ask_resources = event.request.getAccessibleResource();
+              this.source = event.request.getOrigin();
+              console.log('[Meow][meowWebView] Requesting Protected Resources: ' + event.request.getAccessibleResource());
+
+              if (this.ask_resources.length == 1 && this.ask_resources[0] == 'TYPE_SENSOR') {
+                // TODO: work out a workable permission manager!
+                this.onPermit();
+                console.log('[Meow][meowWebView] Permitted TYPE_SENSOR automatically!');
+                return;
+              }
+
+              this.woofWantProtectedResources_control.open();
+            })
+            .onClientAuthenticationRequest((event) => {
+              console.log('[meowWebView] onClientAuthenticationRequest event.host: ' + event.host);
+              console.log('[meowWebView] onClientAuthenticationRequest event.port ' + event.port);
+              console.log('[meowWebView] onClientAuthenticationRequest event.keyTypes: ' + event.keyTypes.join('\n'));
+              console.log('[meowWebView] onClientAuthenticationRequest event.issuers: ' + event.issuers.join('\n'));
+            })
+            .onOverScroll(() => {
+              console.log('[meowWebView] Over scrolled!')
+            })
+            .nestedScroll({ scrollForward: NestedScrollMode.SELF_FIRST, scrollBackward: NestedScrollMode.SELF_FIRST })
+
+        } // Webs
+        .borderColor(this.color_current_secondary)
+        .borderWidth(this.border_width_tab(this.current_sub_tab_index == Label.index_key))
+        .width(this.current_sub_tab_index < 0 ? "100%" : this.width_tab())
+        .height(this.current_sub_tab_index < 0 ? "100%" : this.height_tab())
+        .alignRules(this.current_main_tab_index == Label.index_key ? this.align_rules_main_tab() : this.align_rules_sub_tab())
+        .visibility(this.my_visibility(Label.index_key))
+        .animation(this.tab_animation)
+      }, (Label: tab_label) => Label.timestamp.toString())
+    }
+    .width("100%")
+    .height("100%")
+
+  }
+
+  // Operations
+
+  save_pdf() {
+  }
+
+  print() {
+    print_web(this.bunch_of_tabs.workingMainTab().controller);
+  }
+
+  update_current_info() {
+    this.current_title = this.tab_titles[this.current_main_tab_index];
+    this.current_url = this.tab_urls[this.current_main_tab_index];
+    this.current_url = url_resource_to_meow(this.current_url);
+    // translate "resource://" into "meow://"
+    this.current_loading_progress = this.tab_loading_progresses[this.current_main_tab_index];
+    this.current_is_loading = this.tab_is_loading[this.current_main_tab_index];
+    // Set loading progress
+    this.current_in_page_searching_keyword = this.bunch_of_tabs.workingMainTab().searching_keyword;
+    this.current_in_page_searching_stats_current = this.bunch_of_tabs.workingMainTab().searching_keyword_stats_current;
+    this.current_in_page_searching_stats_total = this.bunch_of_tabs.workingMainTab().searching_keyword_stats_total;
+  }
+
+  sync_list_info() {
+    this.tab_titles = this.bunch_of_tabs.get_all_titles()
+    this.tab_urls = this.bunch_of_tabs.get_all_urls()
+    this.tab_is_loading = this.bunch_of_tabs.get_all_is_loading();
+    this.tab_loading_progresses = this.bunch_of_tabs.get_all_loading_progress()
+  }
+
+  update_search_box_text(text: string) {
+    if (!this.is_search_input_typing) {
+      this.search_input_update_source = 'web';
+      this.search_input = url_resource_to_meow(text);
+      // Update Input Search Box
+    }
+    // Update Input Search Box when new page loaded
+  }
+
+  on_tab_info_change() {
+    if (this.last_timeout_id) {
+      clearTimeout(this.last_timeout_id);
+    }
+    this.last_timeout_id = setTimeout(() => {
+      this.tab_animation = animation_default();
+    }, 50)
+  }
+
+  // 广告过滤功能
+  on_use_adblock_state_change() {
+    console.log('[Meow][meowWebView] enableAdsBlock: ' + (this.use_adblock ? 'true' : 'false'))
+    for (let index = 0; index < this.bunch_of_tabs.Tabs.length; index++) {
+      this.bunch_of_tabs.Tabs[index].controller.enableAdsBlock(this.use_adblock);
+    }
+  }
+
+  refresh_homepage_shortcuts() {
+    if (this.homepage_shortcuts_dir == '') {
+      return;
+    }
+    // let result = this.bunch_of_bookmarks.get_folder(this.homepage_shortcuts_dir)?.get_only_bookmarks_content();
+    let result = this.bunch_of_bookmarks.get_folder(this.homepage_shortcuts_dir)?.get_content();
+    if (result) {
+      console.log('[Meow][meowWebView] Directory selected / refreshed for shortcuts: [' + this.homepage_shortcuts_dir + ']');
+      this.homepage_shortcuts = result;
+      this.bunch_of_settings.set('homepage_shortcuts_bookmarks_dir', this.homepage_shortcuts_dir);
+    } else {
+      // directory doesn't exist
+      console.log('[Meow][meowWebView] Directory selected for shortcuts doesn\'t exist! [' + this.homepage_shortcuts_dir + ']');
+      this.homepage_shortcuts = [];
+      if (this.homepage_shortcuts_dir != '/') {
+        this.homepage_shortcuts_dir = '';
+      }
+    }
+  }
+
+  download_event(event: OnDownloadStartEvent) {
+    if (event.url.substring(0, 5) == 'blob:') {
+      // Would be already handled by blob delegate!
+      return;
+    }
+    if (event) {
+      this.download_non_blob(event.url, event.userAgent, event.contentDisposition, event.contentLength, event.mimetype)
+    }
+  }
+
+  download_non_blob(url: string, userAgent: string, contentDisposition: string, contentLength: number, mimetype: string) {
+    // Save file download additional info
+    let try_additional_info = [
+      'url: ' + url,
+      'userAgent: ' + userAgent,
+      'contentDisposition: ' + contentDisposition,
+      'contentLength: ' + contentLength.toString(),
+      'mimetype: ' + mimetype
+    ].join('\n');
+    console.log(try_additional_info);
+
+    // Cut out original file name
+    let url_split = url.split("/");
+    let name_original = url_split[url_split.length-1].split("?")[0];
+    name_original = decodeURIComponent(name_original);
+
+    // Cut out overwrite file name
+    let name_overwrite = this.cut_filename_overwrite(contentDisposition);
+
+    // Overwrite
+    if (name_overwrite != '') {
+      name_original = name_overwrite;
+    }
+
+    // Call popup
+    this.woofWantDownload_control = new CustomDialogController({
+      builder: woofWantDownload({
+        from: this.current_url,
+        file_name: name_original,
+        url: url,
+        try_additional_info: try_additional_info,
+      }),
+      alignment: DialogAlignment.Center,
+      cornerRadius: 16
+    })
+    this.woofWantDownload_control.open();
+  }
+
+  // Kind of constants
+
+  align_rules_main_tab() {
+    let align: AlignRuleOption;
+    if (this.tablet_mode) {
+      align = { left: { anchor: "__container__", align: HorizontalAlign.Start } }
+    } else {
+      align = { top: { anchor: "__container__", align: VerticalAlign.Top } }
+    }
+    return align;
+  }
+
+  align_rules_sub_tab() {
+    let align: AlignRuleOption;
+    if (this.tablet_mode) {
+      align = { right: { anchor: "__container__", align: HorizontalAlign.End } }
+    } else {
+      align = { bottom: { anchor: "__container__", align: VerticalAlign.Bottom } }
+    }
+    return align;
+  }
+
+  width_tab() {
+    if (this.tablet_mode) {
+      return "50%";
+    } else {
+      return "100%";
+    }
+  }
+
+  height_tab() {
+    if (this.tablet_mode) {
+      return "100%";
+    } else {
+      return "50%";
+    }
+  }
+
+  border_width_tab(is_sub: boolean) {
+    let border_width: EdgeWidths;
+    if (this.tablet_mode) {
+      border_width = { left: is_sub ? 2 : 0, top: 0 };
+    } else {
+      border_width = { top: is_sub ? 2 : 0, left: 0 };
+    }
+    return border_width;
+  }
+
+  my_visibility(index: number) {
+    if (!(this.current_main_tab_index == index || this.current_sub_tab_index == index)) {
+      // Not present
+      return Visibility.None;
+    }
+    return Visibility.Visible;
+  }
+
+  my_js(index: number) {
+    if (!this.disable_js) {
+      return true;
+    }
+    // Disable js toggle ON
+    if (this.disable_js_all_sites) {
+      // Disable on all sites
+      return false;
+    }
+    // Disable on some sites
+    if (this.tab_urls[index] && this.disable_js_these_sites.includes(match_domain(this.tab_urls[index])[1])) {
+      return false;
+    }
+    return true;
+  }
+
+  my_image(index: number) {
+    if (!this.disable_image) {
+      return true;
+    }
+    // Disable image toggle ON
+    if (this.disable_image_all_sites) {
+      // Disable on all sites
+      return false;
+    }
+    // Disable on some sites
+    if (this.tab_urls[index] && this.disable_image_these_sites.includes(match_domain(this.tab_urls[index])[1])) {
+      return false;
+    }
+    return true;
+  }
+
+  shortcut_alignItems() {
+    let status = this.preferred_hand_left_or_right == 'right';
+    if (this.preferred_hand_reverse_homepage_shortcuts) {
+      status = !status;
+    }
+    return status ? HorizontalAlign.End : HorizontalAlign.Start;
+  }
+
+  // Ladders
+  //书签页相关
+  private src_of_tab_index(index: number): string {
+    let target_packed_tab_info = this.bunch_of_tabs.Tabs[index];
+    if (target_packed_tab_info == undefined) {
+      return "";
+    } else {
+      if (target_packed_tab_info.restore_on_creation) {
+        return "";
+      } else {
+        console.log("srcIndex: " + index)
+        console.log('[meowWebView] load: ' + url_meow_to_resource(target_packed_tab_info.url));
+        return url_meow_to_resource(target_packed_tab_info.url);
+      }
+    }
+  }
+
+  private determine_extra_background() {
+    let need_extra_background = true;
+    // Determine background
+    if (!this.init_OK) {
+      need_extra_background = false;
+    }
+    if (this.current_sub_tab_index > -1) {
+      // Paralleowing
+      if (this.current_url == 'meow://home' && this.tab_urls[this.current_sub_tab_index] == 'meow://home') {
+        need_extra_background = false;
+      }
+    } else {
+      // Not Paralleowing
+      if (this.current_url == 'meow://home') {
+        need_extra_background = false;
+      }
+    }
+    this.extra_background = need_extra_background;
+  }
+
+  /**
+   * Determines if an overwrite file name could be cut out and used
+   * @param contentDisposition e.g. "filename='qwq.txt'; type: TEXT"
+   * @returns a file name
+   * */
+  private cut_filename_overwrite(contentDisposition: string) {
+    let name_overwrite = '';
+
+    let filename_start = contentDisposition.toLowerCase().indexOf('filename=');
+    if (filename_start == -1) {
+      return '';
+    }
+    filename_start += 9;
+
+    let filename_end = contentDisposition.indexOf(';', filename_start);
+    if (filename_end == -1) {
+      filename_end = contentDisposition.length;
+    }
+
+    // cut out file_name_overwrite
+    name_overwrite = contentDisposition.slice(filename_start, filename_end);
+
+    // eliminate quotes
+    let last_idx = name_overwrite.length - 1;
+    if (["'", '"'].includes(name_overwrite[last_idx])) {
+      name_overwrite = name_overwrite.slice(1, last_idx);
+    }
+
+    return name_overwrite;
+  }
+}
+
+export default meowWebView;
+
+@Component
+struct shortcutButton {
+  @Prop my_index: number;
+  @Prop bookmark: unified_item;
+  // Environment
+  @StorageLink('current_url') current_url: string = "= ̄ω ̄=";
+  @StorageLink('bunch_of_bookmarks') bunch_of_bookmarks: bunch_of_bookmarks = new bunch_of_bookmarks('Bookmarks~Meow');
+  // Settings - Accessibility
+  @StorageLink('preferred_hand_left_or_right') preferred_hand_left_or_right: string = 'right';
+  @StorageLink('preferred_hand_reverse_homepage_shortcuts') preferred_hand_reverse_homepage_shortcuts: boolean = false;
+  // Gateways
+  @StorageLink('universal_new_tab_gateway') new_tab_gateway: string = "";
+  // Colors
+  @StorageProp('color_current_primary') color_current_primary: ResourceColor = $r('app.color.start_window_background');
+  @StorageProp('color_current_secondary') color_current_secondary: ResourceColor = $r('app.color.block_color');
+  @StorageProp('color_current_font') color_current_font: ResourceColor = $r('app.color.font_color_title');
+  // SHow
+  @State show_contents: Visibility = Visibility.None;
+  @State show: Visibility = Visibility.Hidden;
+  @State offset_y: number = 50;
+
+  build() {
+    Column({ space: 10 }) {
+      Button((this.bookmark.get_item().get_type() == 'folder' ? (this.show_contents == Visibility.Visible ? '󰃆  ' : '󰃅  ') : '')
+        + this.bookmark.get_item().get_label())// Shortcut "󰄏"
+        .type(ButtonType.Capsule)
+        .backgroundColor(this.color_current_secondary)
+        .fontColor(this.color_current_font)
+        .border({
+          width: 2,
+          color: "transparent"
+        })
+        .fontSize(fontSize_Large())
+        .fontWeight(FontWeight.Regular)
+        .height(capsule_bar_height())
+        .visibility(this.show)
+        .offset({ y: this.offset_y })
+        .clickEffect(click_effect_default())
+        .animation(animation_default())
+        .onAppear(() => {
+          setTimeout(() => {
+            this.show = Visibility.Visible;
+            this.offset_y = 0;
+            // Animation of floating up
+          }, this.get_animation_timeout())
+        })
+        .onClick(() => {
+          let item = this.bookmark.get_item();
+          if (item.get_type() == 'bookmark') {
+            // Is bookmark, access website
+            setTimeout(() => {
+              this.new_tab_gateway = (item as bookmark).get_link();
+              this.current_url = (item as bookmark).get_link();
+            }, 100)
+          } else {
+            // Is folder, expand contents
+            if (this.show_contents == Visibility.None) {
+              this.show_contents = Visibility.Visible;
+            } else {
+              this.show_contents = Visibility.None;
+            }
+          }
+        })
+
+      if (this.bookmark.get_item().get_type() == 'folder') {
+        Column({ space: 10 }) {
+          ForEach((this.bookmark.get_item() as folder).get_content(), (shortcut: unified_item, key: number) => {
+            shortcutButton({
+              bookmark: shortcut,
+              my_index: key,
+            })
+          })
+        }
+        .alignItems(this.get_alignItems())
+        .visibility(this.show_contents)
+        .animation(animation_default())
+      }
+    }
+    .alignItems(this.get_alignItems())
+    .padding(this.show_contents == Visibility.Visible ? (this.is_right_align() ? { right: 30 } : { left: 30 }) : {})
+    .animation(animation_default())
+  }
+
+  is_right_align() {
+    return this.preferred_hand_left_or_right == 'right' && !this.preferred_hand_reverse_homepage_shortcuts
+  }
+
+  get_alignItems() {
+    if (this.is_right_align()) {
+      return HorizontalAlign.End;
+    } else {
+      return HorizontalAlign.Start
+    }
+  }
+
+  get_animation_timeout() {
+    let unit_interval = 40;
+    if (this.my_index < 5) {
+      unit_interval = 80;
+    } else if (this.my_index < 10) {
+      unit_interval = 70;
+    } else {
+      unit_interval = 60;
+    }
+    return this.my_index * unit_interval + 100;
+  }
+}

+ 76 - 0
home/src/main/ets/blocks/panels/meowAnimationManager.ets

@@ -0,0 +1,76 @@
+import linysLockSlider from '../../components/linysLockSlider';
+import linysText from '../../components/texts/linysText';
+import { bunch_of_settings } from '../../hosts/bunch_of_settings';
+import linysTimeoutButtonWithText from '../../components/buttons/linysTimeoutButtonWithText';
+
+@Component
+struct meowAnimationManager {
+  // Environment
+  @StorageLink('bunch_of_settings') bunch_of_settings: bunch_of_settings = new bunch_of_settings(true);
+  // Colors
+  @StorageProp('color_current_primary') color_current_primary: ResourceColor =
+    $r('app.color.start_window_background');
+  @StorageProp('color_current_secondary') color_current_secondary: ResourceColor =
+    $r('app.color.block_color');
+  @StorageProp('color_current_font') color_current_font: ResourceColor =
+    $r('app.color.font_color_title');
+  // editing
+  @State delete_confirm: number = 0;
+  @StorageLink('animation_response') animation_response: number = 0;
+  @StorageLink('animation_damping_coefficient') animation_damping_coefficient: number = 0;
+  @State lock_response: boolean = true;
+  @State lock_damping_coefficient: boolean = true;
+
+  build() {
+    Column({ space: 10 }) {
+
+      linysText({
+        text: $r('app.string.Settings_appearance_animation_response')
+      }) // Slider and display of animation_response
+
+      linysLockSlider({
+        slider_min: 1,
+        slider_max: 100,
+        display: (0.01 * this.animation_response).toFixed(2),
+        slider_value: this.animation_response,
+        onTouchUp: () => {
+          this.bunch_of_settings.set('animation_response', this.animation_response);
+        }
+      })
+
+      linysText({
+        text: $r('app.string.Settings_appearance_animation_damping_coefficient')
+      }) // Slider and display of animation damping coefficient
+
+      linysLockSlider({
+        slider_min: 0,
+        slider_max: 75,
+        slider_value: this.animation_damping_coefficient,
+        display: (0.01 * this.animation_damping_coefficient).toFixed(2),
+        onTouchUp: () => {
+          this.bunch_of_settings.set('animation_damping_coefficient',
+            this.animation_damping_coefficient);
+        }
+      })
+
+      linysTimeoutButtonWithText({
+        desc_text: $r('app.string.Settings_appearance_animation_reset'),
+        button_text: '  󰃈  ',
+        onExecution: async () => {
+          // Reset
+          this.bunch_of_settings.reset('animation_response');
+          this.bunch_of_settings.reset('animation_damping_coefficient');
+          // Sync
+          this.animation_response = await this.bunch_of_settings.get('animation_response') as number;
+          console.log(this.animation_response.toString())
+          this.animation_damping_coefficient =
+            await this.bunch_of_settings.get('animation_damping_coefficient') as number;
+        }
+      })
+    }
+    .alignItems(HorizontalAlign.Start)
+    .justifyContent(FlexAlign.Start)
+  }
+}
+
+export default meowAnimationManager;

+ 151 - 0
home/src/main/ets/blocks/panels/meowColorsManager.ets

@@ -0,0 +1,151 @@
+import linysText from '../../components/texts/linysText';
+import woofSelectColor from '../../dialogs/prompts/woofSelectColor';
+import { animation_default, click_effect_default } from '../../hosts/bunch_of_defaults';
+import { bunch_of_settings } from '../../hosts/bunch_of_settings';
+import linysTimeoutButtonWithText from '../../components/buttons/linysTimeoutButtonWithText';
+
+@Component
+struct meowColorsManager {
+  @StorageLink('bunch_of_settings') bunch_of_settings: bunch_of_settings = new bunch_of_settings(true);
+  // Colors
+  @StorageProp('color_current_primary') color_current_primary: ResourceColor =
+    $r('app.color.start_window_background');
+  @StorageProp('color_current_secondary') color_current_secondary: ResourceColor =
+    $r('app.color.block_color');
+  @StorageProp('color_current_font') color_current_font: ResourceColor =
+    $r('app.color.font_color_title');
+  @StorageLink('color_light_primary') color_light_primary: ResourceColor = $r('app.color.start_window_background');
+  @StorageLink('color_light_secondary') color_light_secondary: ResourceColor = $r('app.color.block_color');
+  @StorageLink('color_light_font') color_light_font: ResourceColor = $r('app.color.font_color_title');
+  @StorageLink('color_dark_primary') color_dark_primary: ResourceColor = $r('app.color.start_window_background');
+  @StorageLink('color_dark_secondary') color_dark_secondary: ResourceColor = $r('app.color.block_color');
+  @StorageLink('color_dark_font') color_dark_font: ResourceColor = $r('app.color.font_color_title');
+  // Color editing
+  @State delete_confirm: number = 0;
+
+  build() {
+    Column({ space: 10 }) {
+      linysText({ text: $r('app.string.Appearance_color_scheme_Light') })// Light
+        .width("100%")
+      Row({ space: 5 }) {
+        colorBlock({
+          color: this.color_light_primary,
+          color_theme_id: 'color_light_primary',
+          my_color_mode: 1,
+        })
+        colorBlock({
+          color: this.color_light_secondary,
+          color_theme_id: 'color_light_secondary',
+          my_color_mode: 1,
+        })
+        colorBlock({
+          color: this.color_light_font,
+          color_theme_id: 'color_light_font',
+          my_color_mode: 1,
+        })
+      }
+      .padding(6)
+      .borderRadius(13.5)
+      .backgroundColor($r('sys.color.comp_background_secondary'))
+      .width("100%")
+      .animation(animation_default())
+
+      linysText({ text: $r('app.string.Appearance_color_scheme_Dark') })// Dark
+        .width("100%")
+      Row({ space: 5 }) {
+        colorBlock({
+          color: this.color_dark_primary,
+          color_theme_id: 'color_dark_primary',
+          my_color_mode: 0,
+        })
+        colorBlock({
+          color: this.color_dark_secondary,
+          color_theme_id: 'color_dark_secondary',
+          my_color_mode: 0,
+        })
+        colorBlock({
+          color: this.color_dark_font,
+          color_theme_id: 'color_dark_font',
+          my_color_mode: 0,
+        })
+      }
+      .padding(6)
+      .borderRadius(13.5)
+      .backgroundColor($r('sys.color.comp_background_secondary'))
+      .width("100%")
+      .animation(animation_default())
+
+      linysTimeoutButtonWithText({
+        desc_text: $r('app.string.Color_reset'),
+        button_text: '  󰃈  ',
+        onExecution: async () => {
+          // Reset
+          this.bunch_of_settings.reset('color_light_font');
+          this.bunch_of_settings.reset('color_light_primary');
+          this.bunch_of_settings.reset('color_light_secondary');
+          this.bunch_of_settings.reset('color_dark_font');
+          this.bunch_of_settings.reset('color_dark_primary');
+          this.bunch_of_settings.reset('color_dark_secondary');
+
+          // Figure out what color should i use
+          this.color_light_primary = await this.bunch_of_settings.get('color_light_primary') as string;
+          this.color_light_secondary =
+            await this.bunch_of_settings.get('color_light_secondary') as string;
+          this.color_light_font = await this.bunch_of_settings.get('color_light_font') as string;
+          this.color_dark_primary = await this.bunch_of_settings.get('color_dark_primary') as string;
+          this.color_dark_secondary = await this.bunch_of_settings.get('color_dark_secondary') as string;
+          this.color_dark_font = await this.bunch_of_settings.get('color_dark_font') as string;
+        }
+      }) // Reset
+    } // Colors
+
+  }
+}
+
+export default meowColorsManager;
+
+@Component
+struct colorBlock {
+  @Link @Watch('on_color_set') color: ResourceColor;
+  @State color_theme_id: string = 'color_light_font';
+  @State my_color_mode: number = 0;
+  @StorageProp('currentColorMode') current_color_mode: number = 0;
+  @StorageProp('color_current_font') color_current_font: ResourceColor = $r('app.color.font_color_title');
+  @StorageLink('bunch_of_settings') bunch_of_settings: bunch_of_settings = new bunch_of_settings(true);
+  woofColorSelect_control: CustomDialogController = new CustomDialogController({
+    builder: woofSelectColor({ color: this.color, dark_mode_color: false, color_theme_id: this.color_theme_id }),
+    alignment: DialogAlignment.Center,
+    cornerRadius: 16,
+    // showInSubWindow: true,
+    width: "90%",
+  });
+
+  build() {
+    Column()
+      .layoutWeight(1)
+      .backgroundColor(this.color)
+      .height(80)
+      .border(this.current_color_mode == this.my_color_mode ? { width: 2, color: this.color_current_font } :
+        { width: 0 })
+      .borderRadius(10)
+      .clickEffect(click_effect_default())
+      .onClick(() => {
+        this.woofColorSelect_control = new CustomDialogController({
+          builder: woofSelectColor({
+            color: this.color,
+            dark_mode_color: this.color_theme_id.includes('dark'),
+            color_theme_id: this.color_theme_id
+          }),
+          alignment: DialogAlignment.Center,
+          cornerRadius: 16,
+          // showInSubWindow: true,
+          width: "90%",
+        });
+        this.woofColorSelect_control.open();
+      })
+  }
+
+  on_color_set() {
+    this.bunch_of_settings.set(this.color_theme_id, this.color as string);
+  }
+}

+ 165 - 0
home/src/main/ets/blocks/panels/meowDebug.ets

@@ -0,0 +1,165 @@
+import linysSymbol from '../../components/texts/linysSymbol';
+import linysText from '../../components/texts/linysText';
+import { click_effect_default } from '../../hosts/bunch_of_defaults';
+import { getPrivateDirty, getPss, getSharedDirty, getVRAM, getVss } from '../../utils/any_concurrent_tools';
+import { getCPU_sync, getNativeHeapSize_sync } from '../../utils/performance_tools';
+import { add_units_to_size } from '../../utils/storage_tools';
+
+@Component
+struct meowDebug {
+  @State data: string[] = ['pss', 'vss', 'shared dirty', 'private dirty', 'cpu', 'native heap', 'vram'];
+  @StorageLink('showing_more_options') showing_more_options: boolean = false;
+  // Settings - Accessibility
+  @StorageLink('preferred_hand_left_or_right') preferred_hand_left_or_right: string = 'right';
+
+  build() {
+    Scroll() {
+      Row({ space: 10 }) {
+        dataBoard({
+          label: $r('app.string.Debug_cpu'),
+          data: this.data[4],
+          symbol_glyph_target: 'sys.symbol.hardware',
+          loop_execution: () => {
+            if (!this.showing_more_options) {
+              return
+            }
+            this.data[4] = (getCPU_sync() * 100).toFixed(2) + '%';
+          }
+        })
+        dataBoard({
+          label: $r('app.string.Debug_native_vram'),
+          data: this.data[6],
+          symbol_glyph_target: 'sys.symbol.hardware',
+          loop_time: 5000,
+          loop_execution: () => {
+            if (!this.showing_more_options) {
+              return
+            }
+            getVRAM().then(result => {
+              this.data[6] = add_units_to_size(result * 1024);
+            })
+          }
+        })
+        dataBoard({
+          label: $r('app.string.Debug_physical_ram'),
+          data: this.data[0],
+          symbol_glyph_target: 'sys.symbol.archivebox',
+          loop_execution: () => {
+            if (!this.showing_more_options) {
+              // Saves performance and battery, maybe
+              return
+            }
+            getPss().then(result => {
+              this.data[0] = add_units_to_size(result * 1024n);
+            })
+          }
+        })
+        dataBoard({
+          label: $r('app.string.Debug_virtual_ram'),
+          data: this.data[1],
+          symbol_glyph_target: 'sys.symbol.archivebox_badge_clock',
+          loop_execution: () => {
+            if (!this.showing_more_options) {
+              return
+            }
+            getVss().then(result => {
+              // this.data[1] = add_units_to_size(result * 1024n);
+              // Due to somewhat reason, this value returned is too large if its unit is KB
+              // So Liny guess it's a mistake of documentation, hence it is not multiplied by 1024n
+              // Assuming it's in Byte as unit.
+              // TODO: consider further about unit of getVss().
+              this.data[1] = add_units_to_size(result);
+            })
+          }
+        })
+        dataBoard({
+          label: $r('app.string.Debug_native_heap'),
+          data: this.data[5],
+          symbol_glyph_target: 'sys.symbol.doc_zipper',
+          loop_execution: () => {
+            if (!this.showing_more_options) {
+              return
+            }
+            this.data[5] = add_units_to_size(getNativeHeapSize_sync());
+          }
+        })
+        dataBoard({
+          label: $r('app.string.Debug_shared_dirty'),
+          data: this.data[2],
+          symbol_glyph_target: 'sys.symbol.rectangle_text_rectangle_portrait',
+          loop_execution: () => {
+            if (!this.showing_more_options) {
+              return
+            }
+            getSharedDirty().then(result => {
+              this.data[2] = add_units_to_size(result * 1024n);
+            })
+          }
+        })
+        dataBoard({
+          label: $r('app.string.Debug_private_dirty'),
+          data: this.data[3],
+          symbol_glyph_target: 'sys.symbol.lock_text_rectangle_portrait',
+          loop_execution: () => {
+            if (!this.showing_more_options) {
+              return
+            }
+            getPrivateDirty().then(result => {
+              this.data[3] = add_units_to_size(result * 1024n);
+            })
+          }
+        })
+      }
+    }
+    .borderRadius(12)
+    .scrollable(ScrollDirection.Horizontal)
+    .edgeEffect(EdgeEffect.Spring)
+    .scrollBar(BarState.Off)
+    .align(this.preferred_hand_left_or_right == 'right' ? Alignment.BottomEnd : Alignment.BottomStart)
+    .width('100%')
+  }
+}
+
+export default meowDebug;
+
+@Component
+struct dataBoard {
+  @Prop data: string = 'label';
+  @Prop symbol_glyph_target: string | undefined = undefined;
+  @Prop label: ResourceStr = 'label';
+  loop_execution?: () => void;
+  @StorageProp('tablet_mode') tablet_mode: boolean = false;
+  @Prop loop_time: number = 1000;
+
+  build() {
+    Row({ space: 10 }) {
+      if (this.symbol_glyph_target) {
+        linysSymbol({
+          symbol_glyph_target: this.symbol_glyph_target,
+        })
+          .margin({ left: 3 })
+      }
+      Column() {
+        linysText({
+          text: this.label,
+        })
+        linysText({
+          text: this.data,
+        })
+      }
+      .alignItems(HorizontalAlign.Start)
+    }
+    .clickEffect(click_effect_default())
+    .padding(10)
+    .backgroundColor($r('sys.color.comp_background_tertiary'))
+    .borderRadius(12)
+    .justifyContent(FlexAlign.Start)
+    .onAppear(() => {
+      setInterval(() => {
+        if (this.loop_execution) {
+          this.loop_execution();
+        }
+      }, this.loop_time)
+    })
+  }
+}

+ 72 - 0
home/src/main/ets/blocks/panels/meowHomepageBackgroundManager.ets

@@ -0,0 +1,72 @@
+import linysCapsuleButton from '../../components/buttons/linysCapsuleButton';
+import linysText from '../../components/texts/linysText';
+import linysTimeoutButton from '../../components/buttons/linysTimeoutButton';
+import { arrayBuffer_2_pixelMap, image_pick_to_ArrayBuffer, sandbox_save, sandbox_unlink } from '../../utils/storage_tools';
+import linysCapsuleButtonWithText from '../../components/buttons/linysCapsuleButtonWithText';
+import linysTimeoutButtonWithText from '../../components/buttons/linysTimeoutButtonWithText';
+
+@Component
+struct meowHomepageBackgroundManager {
+  // States
+  @State homepage_background_delete_confirm: number = 0;
+  // Gateways
+  @StorageLink('universal_fail_prompt_desc_gateway') uni_fail_prompt_gateway: ResourceStr = "";
+  // Environments
+  @StorageLink('homepage_background') homepage_background: PixelMap | undefined = undefined;
+  // Colors
+  @StorageProp('color_current_primary') color_current_primary: ResourceColor = $r('app.color.start_window_background');
+  @StorageProp('color_current_secondary') color_current_secondary: ResourceColor = $r('app.color.block_color');
+  @StorageProp('color_current_font') color_current_font: ResourceColor = $r('app.color.font_color_title');
+
+  build() {
+    Column({ space: 10 }) {
+      linysCapsuleButtonWithText({
+        desc_text: $r('app.string.Homepage_background_select'),
+        button_text: "  󰃟  ",
+        onExecution: () => {
+          this.select_set_background_image();
+        }
+      }) // Homepage background select
+      linysTimeoutButtonWithText({
+        desc_text: $r('app.string.Homepage_background_delete'),
+        button_text: "  󰀁  ",
+        onExecution: () => {
+          this.delete_homepage_background();
+        }
+      }) // Homepage background delete
+    }
+    .onAppear(() => {
+      setInterval(() => {
+        if (this.homepage_background_delete_confirm > 0) {
+          this.homepage_background_delete_confirm -= 1;
+        }
+        // Reset homepage_background_delete_confirm
+      }, 10)
+    })
+  }
+
+  select_set_background_image() {
+    image_pick_to_ArrayBuffer().then(result => {
+      if (result !== undefined) {
+        let buf = result as ArrayBuffer;
+        // Set image for homepage
+        this.homepage_background = arrayBuffer_2_pixelMap(buf);
+        // Save this to sandbox
+        sandbox_save('homepage_background_arrayBuffer', buf);
+      } else {
+        console.log('[meowAppSettings] Select homepage background failed. Received undefined?')
+        this.uni_fail_prompt_gateway = $r('app.string.Fail_select_bad_image');
+      }
+    });
+
+  }
+
+  delete_homepage_background() {
+    // Delete homepage background image
+    this.homepage_background = undefined;
+    sandbox_unlink('homepage_background_arrayBuffer');
+    console.log("[meowAppSettings] Deleted homepage background!")
+  }
+}
+
+export default meowHomepageBackgroundManager;

+ 371 - 0
home/src/main/ets/blocks/panels/meowSEManager.ets

@@ -0,0 +1,371 @@
+import linysCapsuleButton from '../../components/buttons/linysCapsuleButton';
+import linysSymbol from '../../components/texts/linysSymbol';
+import linysTimeoutButton from '../../components/buttons/linysTimeoutButton';
+import { animation_default, capsule_bar_height, default_search_engine, fontSize_Large, fontSize_Normal } from '../../hosts/bunch_of_defaults';
+import { bunch_of_search_engines, search_engine } from '../../hosts/bunch_of_search_engines';
+import { bunch_of_settings } from '../../hosts/bunch_of_settings';
+
+@Component
+struct meowSEManager {
+  @Prop default_new_se: search_engine = new search_engine(new Date().toLocaleString(), "https://bing.com/search?q=%s");
+  // Environment
+  @StorageLink('bunch_of_search_engines') bunch_of_search_engines: bunch_of_search_engines = new bunch_of_search_engines();
+  @StorageLink('bunch_of_settings') bunch_of_settings: bunch_of_settings = new bunch_of_settings(true);
+  @StorageLink('search_engine') search_engine: string = "";
+  @StorageLink('search_engine_selected') @Watch('on_select') selected_index: number = -2; // -1 for system default
+  @State default_pressing: boolean = false;
+  // Colors
+  @StorageProp('color_current_primary') color_current_primary: ResourceColor = $r('app.color.start_window_background');
+  @StorageProp('color_current_secondary') color_current_secondary: ResourceColor = $r('app.color.block_color');
+  @StorageProp('color_current_font') color_current_font: ResourceColor = $r('app.color.font_color_title');
+  // Settings / Accessibility
+  @StorageLink('preferred_hand_left_or_right') preferred_hand_left_or_right: string = 'right';
+  // execution
+  on_normal_execution?: (idx: number, se: search_engine) => void;
+  on_default_execution?: () => void;
+
+  build() {
+    Column({ space: 2.5 }) {
+      Row() {
+        Row() {
+          Text($r('app.string.Settings_general_custom_ua_default'))// Title
+            .fontColor(!this.default_pressing ? this.color_current_font : this.color_current_secondary)
+            .fontWeight(!this.default_pressing ? FontWeight.Regular : FontWeight.Bold)
+            .padding({ left: 2 })
+            .fontSize(fontSize_Normal())
+            .maxLines(1)
+            .textOverflow({ overflow: TextOverflow.Ellipsis })
+            .layoutWeight(1)
+            .margin(10)
+            .animation(animation_default())
+        }
+        .width("100%")
+        .height("100%")
+        .borderRadius(7)
+        .backgroundColor(this.default_pressing ? this.color_current_font :
+        this.color_current_primary)
+        .animation(animation_default())
+
+      } // Default
+      .width("100%")
+      .border({
+        radius: 10,
+        width: 2,
+        color: -1 == this.selected_index ? this.color_current_font : "transparent",
+        // color: "red"
+      })
+      .alignRules({
+        middle: { anchor: "__container__", align: HorizontalAlign.Center },
+        top: { anchor: "__container__", align: VerticalAlign.Top }
+      })
+      .onTouch((event) => {
+        if (event.type == TouchType.Up) {
+          this.default_pressing = false;
+          // If touch ends
+        } else {
+          this.default_pressing = true;
+          // If touching
+        }
+      })
+      .onClick(() => {
+        if (this.on_default_execution) {
+          this.on_default_execution();
+        } else {
+          this.selected_index = -1;
+          // Set UA
+          this.bunch_of_settings.set('custom_search_engines_selected_index', -1);
+        }
+      })
+      .height(46)
+      .animation(animation_default())
+
+      ForEach(this.bunch_of_search_engines.list_of_search_engines, (_se: search_engine, key: number) => {
+        meowSEButton({
+          selected_index: this.selected_index,
+          my_index: key,
+          onExecution: this.on_normal_execution
+        })
+      })
+
+      Row() {
+        linysSymbol({ symbol_glyph_target: 'sys.symbol.plus_square' })
+          .onClick(() => {
+            this.add_new_custom_search_engine(this.default_new_se);
+          })
+      } // Add Button
+      .justifyContent(this.preferred_hand_left_or_right == 'right' ? FlexAlign.End : FlexAlign.Start)
+      .width("100%")
+      .padding(5)
+    }
+    .padding(5)
+    .borderRadius(13.5)
+    .backgroundColor($r('sys.color.comp_background_tertiary'))
+    .width("100%")
+    .animation(animation_default())
+  }
+
+  async aboutToAppear() {
+    let custom_search_engine = await this.bunch_of_settings.get('custom_search_engines') as string;
+    this.bunch_of_search_engines.import_string(custom_search_engine);
+    this.selected_index = await this.bunch_of_settings.get('custom_search_engines_selected_index') as number;
+
+    console.log("[Meow][meowSEManager] Search engine Manager READY");
+    console.log("[Meow][meowSEManager] Search engine index: " + this.selected_index.toString());
+  }
+
+  // on_SE_change() {
+  //   console.log('[Meow][meowSEManager] Search Engine list change! Syncing data to Settings...')
+  // }
+
+  on_select() {
+    this.select_search_engine(this.selected_index);
+  }
+
+  select_search_engine(index: number) {
+    if (index <= -1) {
+      // If set back to default
+      this.search_engine = default_search_engine();
+    } else {
+      this.search_engine = this.bunch_of_search_engines.list_of_search_engines[index].url;
+    }
+  }
+
+  add_new_custom_search_engine(se: search_engine) {
+    this.bunch_of_search_engines.add_search_engine(se);
+    this.bunch_of_settings.set('custom_search_engines', this.bunch_of_search_engines.export_string());
+  }
+}
+
+export default meowSEManager;
+
+@Component
+struct meowSEButton {
+  @Link selected_index: number;
+  @StorageLink('bunch_of_search_engines') bunch_of_search_engines: bunch_of_search_engines = new bunch_of_search_engines();
+  @StorageLink('bunch_of_settings') bunch_of_settings: bunch_of_settings = new bunch_of_settings(true);
+  @Prop my_index: number;
+  @State my_se: search_engine = this.bunch_of_search_engines.list_of_search_engines[this.my_index];
+  @State my_label: string = this.my_se.label;
+  @State my_content: string = this.my_se.url;
+  // Settings - Accessibility
+  @StorageLink('preferred_hand_left_or_right') preferred_hand_left_or_right: string = 'right';
+  // UI effects
+  @State height_of_text_area: number = 42;
+  @State is_editing: boolean = false;
+  @State is_pressing: boolean = false;
+  @State is_press_timing_ok: boolean = false;
+  press_timing: number = 0;
+  button_height_default: number = 42;
+  // Edit inputs
+  @State edit_label: string = this.my_label;
+  @State edit_content: string = this.my_content;
+  // Colors
+  @StorageProp('color_current_primary') color_current_primary: ResourceColor = $r('app.color.start_window_background');
+  @StorageProp('color_current_secondary') color_current_secondary: ResourceColor = $r('app.color.block_color');
+  @StorageProp('color_current_font') color_current_font: ResourceColor = $r('app.color.font_color_title');
+  // execution
+  onExecution?: (idx: number, se: search_engine) => void;
+
+  build() {
+    Column() {
+      Column() {
+        Row() {
+          Text(this.my_label)// Title
+            .fontColor(!this.is_pressing ? this.color_current_font : this.color_current_secondary)
+            .fontWeight(!this.is_pressing ? FontWeight.Regular : FontWeight.Bold)
+            .animation(animation_default())
+            .padding({ left: 2 })
+            .fontSize(fontSize_Normal())
+            .maxLines(1)
+            .textOverflow({ overflow: TextOverflow.Ellipsis })
+            .layoutWeight(1)
+
+          Scroll() {
+            SymbolGlyph($r('sys.symbol.square_and_pencil'))
+              .fontSize(fontSize_Large())
+              .fontColor([this.color_current_secondary])
+          } // Edit Icon
+          .scrollBar(BarState.Off)
+          .scrollable(ScrollDirection.Horizontal)
+          .width(this.is_press_timing_ok ? 22 : 0)
+          .margin({ left: this.is_press_timing_ok ? 10 : 0 })
+          .animation(animation_default())
+
+        } // Bookmark button
+        .borderRadius(this.is_editing ? { topLeft: 7, topRight: 7 } : 7)
+        .backgroundColor(this.is_pressing ? this.color_current_font : this.color_current_primary)
+        .animation(animation_default())
+        .padding(10)
+        .alignRules({
+          middle: { anchor: "__container__", align: HorizontalAlign.Center },
+          top: { anchor: "__container__", align: VerticalAlign.Top }
+        })
+        .onTouch((event) => {
+          if (event.type == TouchType.Up) {
+            this.is_pressing = false;
+            // If touch ends
+          } else {
+            this.is_pressing = true;
+            // If touching
+          }
+        })
+        .onClick(() => {
+          if (this.is_press_timing_ok) {
+            this.is_editing = !this.is_editing;
+            return;
+          } // Toggle Edit Panel
+          if (this.onExecution) {
+            this.onExecution(this.my_index, this.my_se);
+          } else {
+            AppStorage.set('search_engine_selected', -99);
+            AppStorage.set('search_engine_selected', this.my_index);
+            this.bunch_of_settings.set('custom_search_engines_selected_index', this.my_index);
+          }
+        })
+        .height(this.button_height_default)
+        .onMouse((e) => {
+          if (e.button == MouseButton.Right && e.action == MouseAction.Press) {
+            // Right click
+            this.is_editing = !this.is_editing;
+          }
+        })
+
+        Scroll() {
+          Column({ space: 10 }) {
+            Row({ space: 10 }) {
+              linysSymbol({ symbol_glyph_target: "sys.symbol.rename" })
+              TextInput({ text: this.edit_label })
+                .onChange((value) => {
+                  this.edit_label = value;
+                })
+                .fontWeight(FontWeight.Regular)
+                .fontColor(this.color_current_font)
+                .caretColor(this.color_current_font)
+                .selectedBackgroundColor(this.color_current_font)
+                .layoutWeight(1)
+                .onSubmit(() => {
+                  this.save_changes();
+                  this.is_editing = false;
+                })
+                .height(capsule_bar_height())
+
+            } // Edit label
+            .width("100%")
+
+            Row({ space: 10 }) {
+              linysSymbol({ symbol_glyph_target: "sys.symbol.paperclip" })
+              TextArea({ text: this.edit_content })
+                .onChange((value) => {
+                  this.edit_content = value;
+                })
+                .fontWeight(FontWeight.Regular)
+                .fontColor(this.color_current_font)
+                .caretColor(this.color_current_font)
+                .selectedBackgroundColor(this.color_current_font)
+                .layoutWeight(1)
+                .onSubmit(() => {
+                  this.save_changes();
+                  this.is_editing = false;
+                })
+                .onAreaChange((_o, n) => {
+                  this.height_of_text_area = n.height as number;
+                })
+              // .height(capsule_bar_height())
+
+            } // Edit content
+            .width("100%")
+            .animation(animation_default())
+
+            Row({ space: 10 }) {
+              linysTimeoutButton({
+                dialogText: "确定是否删除选中内容",
+                text: "  󰀁  ",
+                onExecution: () => {
+                  this.delete_myself();
+                }
+              })
+              linysCapsuleButton({ text: "  󰀻  " })
+                .onClick(() => {
+                  this.save_changes();
+                  this.is_editing = false;
+                })
+
+            } // Buttons of operations
+            .justifyContent(this.preferred_hand_left_or_right == 'right' ? FlexAlign.End : FlexAlign.Start)
+            .width("100%")
+          }
+          .padding({
+            top: 6,
+            left: 14,
+            right: 14,
+            bottom: 14
+          })
+          .backgroundColor(this.color_current_primary)
+          .border({
+            radius: { bottomLeft: 10, bottomRight: 10 }
+          })
+
+        } // Edit panel
+        .height(!this.is_editing ? 0 : 108 + this.height_of_text_area)
+        .visibility(this.is_editing ? Visibility.Visible : Visibility.None)
+        .animation(animation_default())
+        .scrollBar(BarState.Off)
+        .nestedScroll({ scrollForward: NestedScrollMode.PARENT_FIRST, scrollBackward: NestedScrollMode.PARENT_FIRST })
+      }
+      .border({
+        radius: 10,
+        width: 2,
+        color: this.my_index == this.selected_index ? this.color_current_font : "transparent"
+      })
+    }
+    .width("100%")
+    .animation(animation_default())
+    .onAppear(() => {
+      setInterval(() => {
+        if (this.is_pressing) {
+          this.press_timing += 1;
+        } else {
+          this.press_timing = 0;
+        }
+        this.is_press_timing_ok = this.press_timing > 16;
+        // Count press time
+      }, 10)
+    })
+  }
+
+  save_changes() {
+    this.edit_content = this.edit_content.replaceAll("\n", "");
+
+    this.my_label = this.edit_label;
+    this.my_content = this.edit_content;
+    this.bunch_of_search_engines.list_of_search_engines[this.my_index].label = this.edit_label;
+    this.bunch_of_search_engines.list_of_search_engines[this.my_index].url = this.edit_content;
+    this.save_search_engine_to_settings();
+
+    // This would refresh UI in other places
+    this.bunch_of_search_engines.update_last_accessed();
+  }
+
+  delete_myself() {
+
+    // Confirm delete
+    this.is_editing = false;
+    this.bunch_of_search_engines.del_search_engine(this.my_index);
+
+    if (this.selected_index == this.my_index) {
+      this.selected_index = this.my_index - 1;
+    } else {
+      // Refresh
+      let prev_selected = this.selected_index;
+      this.selected_index = -2;
+      this.selected_index = Math.min(this.bunch_of_search_engines.list_of_search_engines.length - 1, prev_selected);
+    }
+    this.save_search_engine_to_settings();
+
+  }
+
+  save_search_engine_to_settings() {
+    console.log("[Meow][meowUAManager] Started to save custom user-agents to KVStore!")
+    this.bunch_of_settings.set('custom_search_engines', this.bunch_of_search_engines.export_string());
+  }
+}

+ 368 - 0
home/src/main/ets/blocks/panels/meowUAManager.ets

@@ -0,0 +1,368 @@
+import linysCapsuleButton from '../../components/buttons/linysCapsuleButton';
+import linysSymbol from '../../components/texts/linysSymbol';
+import linysTimeoutButton from '../../components/buttons/linysTimeoutButton';
+import { animation_default, capsule_bar_height, fontSize_Large, fontSize_Normal } from '../../hosts/bunch_of_defaults';
+import { bunch_of_settings } from '../../hosts/bunch_of_settings';
+import { bunch_of_user_agents, user_agent } from '../../hosts/bunch_of_user_agents';
+
+@Component
+struct meowUAManager {
+  @StorageLink('bunch_of_user_agents') bunch_of_user_agents: bunch_of_user_agents = new bunch_of_user_agents();
+  @StorageLink('bunch_of_settings') bunch_of_settings: bunch_of_settings = new bunch_of_settings(true);
+  @StorageLink('universal_global_custom_ua_gateway') now_global_custom_UA: string = "";
+  @StorageLink('user_agent_selected') @Watch('on_select') selected_index: number = -1; // -1 for system default
+  @State default_pressing: boolean = false;
+  // Colors
+  @StorageProp('color_current_primary') color_current_primary: ResourceColor = $r('app.color.start_window_background');
+  @StorageProp('color_current_secondary') color_current_secondary: ResourceColor = $r('app.color.block_color');
+  @StorageProp('color_current_font') color_current_font: ResourceColor = $r('app.color.font_color_title');
+  // Settings / Accessibility
+  @StorageLink('preferred_hand_left_or_right') preferred_hand_left_or_right: string = 'right';
+
+  build() {
+    Column({ space: 2.5 }) {
+      // linysText({
+      //   text: this.selected_index.toString() + ': ' + this.now_global_custom_UA
+      // })
+
+      Row() {
+        Row() {
+          Text($r('app.string.Settings_general_custom_ua_default'))// Title
+            .fontColor(!this.default_pressing ? this.color_current_font : this.color_current_secondary)
+            .fontWeight(!this.default_pressing ? FontWeight.Regular : FontWeight.Bold)
+            .padding({ left: 2 })
+            .fontSize(fontSize_Normal())
+            .maxLines(1)
+            .textOverflow({ overflow: TextOverflow.Ellipsis })
+            .layoutWeight(1)
+            .margin(10)
+            .animation(animation_default())
+
+        }
+        .width("100%")
+        .height("100%")
+        .borderRadius(7)
+        .backgroundColor(this.default_pressing ? this.color_current_font :
+        this.color_current_primary)
+        .animation(animation_default())
+
+      } // Default
+      .width("100%")
+      .border({
+        radius: 10,
+        width: 2,
+        color: -1 == this.selected_index ? this.color_current_font : "transparent",
+        // color: "red"
+      })
+      .alignRules({
+        middle: { anchor: "__container__", align: HorizontalAlign.Center },
+        top: { anchor: "__container__", align: VerticalAlign.Top }
+      })
+      .onTouch((event) => {
+        if (event.type == TouchType.Up) {
+          this.default_pressing = false;
+          // If touch ends
+        } else {
+          this.default_pressing = true;
+          // If touching
+        }
+      })
+      .onClick(() => {
+        this.set_global_UA(-1);
+        // Set UA
+      })
+      .height(46)
+      .animation(animation_default())
+
+      ForEach(this.bunch_of_user_agents.list_of_user_agents, (_user_agent: user_agent, key: number) => {
+        meowUAButton({
+          selected_index: this.selected_index,
+          my_index: key,
+        })
+      })
+
+      Row() {
+        linysSymbol({ symbol_glyph_target: 'sys.symbol.plus_square' })
+          .onClick(() => {
+            this.add_new_custom_ua();
+          })
+      } // Add Button
+      .justifyContent(this.preferred_hand_left_or_right == 'right' ? FlexAlign.End : FlexAlign.Start)
+      .width("100%")
+      .padding(5)
+    }
+    .padding(5)
+    .borderRadius(13.5)
+    .backgroundColor($r('sys.color.comp_background_tertiary'))
+    .width("100%")
+    .animation(animation_default())
+  }
+
+  async aboutToAppear() {
+    let custom_user_agents = await this.bunch_of_settings.get('custom_user_agents') as string;
+    this.bunch_of_user_agents.import_string(custom_user_agents);
+    this.selected_index = await this.bunch_of_settings.get('custom_user_agents_selected_index') as number;
+
+    // Get UA
+
+    console.log("[Meow][meowUAManager] UA Manager READY");
+  }
+
+  on_select() {
+    // console.log('meow ' + this.selected_index.toString())
+    if (this.selected_index <= -1) {
+      // If set back to default
+      this.now_global_custom_UA = "";
+    } else {
+      this.now_global_custom_UA = this.bunch_of_user_agents.list_of_user_agents[this.selected_index].user_agent_content;
+    }
+  }
+
+  set_global_UA(idx: number) {
+    this.selected_index = -99;
+    this.selected_index = idx;
+    this.bunch_of_settings.set('custom_user_agents_selected_index', idx);
+  }
+
+  add_new_custom_ua() {
+    this.bunch_of_user_agents.add_user_agent(new user_agent(new Date().toLocaleString(),
+      "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:132.0) Gecko/20100101 Firefox/132.0"));
+    this.bunch_of_settings.set('custom_user_agents', this.bunch_of_user_agents.export_string());
+  }
+}
+
+export default meowUAManager;
+
+@Component
+struct meowUAButton {
+  @Link selected_index: number;
+  @StorageLink('bunch_of_user_agents') bunch_of_user_agents: bunch_of_user_agents = new bunch_of_user_agents();
+  @StorageLink('bunch_of_settings') bunch_of_settings: bunch_of_settings = new bunch_of_settings(true);
+  @Prop my_index: number;
+  @State my_ua: user_agent = this.bunch_of_user_agents.list_of_user_agents[this.my_index];
+  @State my_label: string = this.my_ua.label;
+  @State my_content: string = this.my_ua.user_agent_content;
+  // Settings / Accessibility
+  @StorageLink('preferred_hand_left_or_right') preferred_hand_left_or_right: string = 'right';
+  // UI effects
+  @State height_of_text_area: number = 42;
+  @State is_pressing: boolean = false;
+  @State is_editing: boolean = false;
+  @State is_press_timing_ok: boolean = false;
+  press_timing: number = 0;
+  button_height_default: number = 42;
+  // Edit inputs
+  @State edit_label: string = this.my_label;
+  @State edit_content: string = this.my_content;
+  // Colors
+  @StorageProp('color_current_primary') color_current_primary: ResourceColor = $r('app.color.start_window_background');
+  @StorageProp('color_current_secondary') color_current_secondary: ResourceColor = $r('app.color.block_color');
+  @StorageProp('color_current_font') color_current_font: ResourceColor = $r('app.color.font_color_title');
+
+  build() {
+    Column() {
+      Column() {
+        Row() {
+          Text(this.my_label)// Title
+            .fontColor(!this.is_pressing ? this.color_current_font : this.color_current_secondary)
+            .fontWeight(!this.is_pressing ? FontWeight.Regular : FontWeight.Bold)
+            .animation(animation_default())
+            .padding({ left: 2 })
+            .fontSize(fontSize_Normal())
+            .maxLines(1)
+            .textOverflow({ overflow: TextOverflow.Ellipsis })
+            .layoutWeight(1)
+
+          Scroll() {
+            SymbolGlyph($r('sys.symbol.square_and_pencil'))
+              .fontSize(fontSize_Large())
+              .fontColor([this.color_current_secondary])
+          } // Edit Icon
+          .scrollable(ScrollDirection.Horizontal)
+          .scrollBar(BarState.Off)
+          .width(this.is_press_timing_ok ? 22 : 0)
+          .margin({ left: this.is_press_timing_ok ? 10 : 0 })
+          .animation(animation_default())
+
+        } // UA button
+        .borderRadius(this.is_editing ? { topLeft: 7, topRight: 7 } : 7)
+        .backgroundColor(this.is_pressing ? this.color_current_font : this.color_current_primary)
+        .animation(animation_default())
+        .padding(10)
+        .alignRules({
+          middle: { anchor: "__container__", align: HorizontalAlign.Center },
+          top: { anchor: "__container__", align: VerticalAlign.Top }
+        })
+        .onTouch((event) => {
+          if (event.type == TouchType.Up) {
+            this.is_pressing = false;
+            // If touch ends
+          } else {
+            this.is_pressing = true;
+            // If touching
+          }
+        })
+        .onClick(() => {
+          if (this.is_press_timing_ok) {
+            this.is_editing = !this.is_editing;
+            return;
+          } // Toggle Edit Panel
+          this.set_global_UA(this.my_index);
+          // Set UA
+        })
+        .height(this.button_height_default)
+        .onMouse((e) => {
+          if (e.button == MouseButton.Right && e.action == MouseAction.Press) {
+            // Right click
+            this.is_editing = !this.is_editing;
+          }
+        })
+
+        Scroll() {
+          Column({ space: 10 }) {
+            Row({ space: 10 }) {
+              linysSymbol({ symbol_glyph_target: "sys.symbol.rename" })
+              TextInput({ text: this.edit_label })
+                .onChange((value) => {
+                  this.edit_label = value;
+                })
+                .fontWeight(FontWeight.Regular)
+                .fontColor(this.color_current_font)
+                .caretColor(this.color_current_font)
+                .selectedBackgroundColor(this.color_current_font)
+                .layoutWeight(1)
+                .onSubmit(() => {
+                  this.save_changes();
+                  this.is_editing = false;
+                })
+                .height(capsule_bar_height())
+
+            } // Edit label
+            .width("100%")
+
+            Row({ space: 10 }) {
+              linysSymbol({ symbol_glyph_target: "sys.symbol.paperclip" })
+              TextArea({ text: this.edit_content })
+                .onChange((value) => {
+                  this.edit_content = value;
+                })
+                .fontWeight(FontWeight.Regular)
+                .fontColor(this.color_current_font)
+                .caretColor(this.color_current_font)
+                .selectedBackgroundColor(this.color_current_font)
+                .layoutWeight(1)
+                .onSubmit(() => {
+                  this.save_changes();
+                  this.is_editing = false;
+                })
+                .onAreaChange((_o, n) => {
+                  this.height_of_text_area = n.height as number;
+                })
+              // .height(capsule_bar_height())
+
+            } // Edit content
+            .width("100%")
+            .animation(animation_default())
+
+            Row({ space: 10 }) {
+              linysTimeoutButton({
+                text: "  󰀁  ",
+                onExecution: () => {
+                  this.delete_myself();
+                }
+              })
+              linysCapsuleButton({ text: "  󰀻  " })
+                .onClick(() => {
+                  this.save_changes();
+                  this.is_editing = false;
+                })
+
+            } // Buttons of operations
+            .justifyContent(this.preferred_hand_left_or_right == 'right' ? FlexAlign.End : FlexAlign.Start)
+            .animation(animation_default())
+            .width("100%")
+          }
+          .padding({
+            top: 6,
+            left: 14,
+            right: 14,
+            bottom: 14
+          })
+          .backgroundColor(this.color_current_primary)
+          .border({
+            radius: { bottomLeft: 10, bottomRight: 10 }
+          })
+
+        } // Edit panel
+        .height(!this.is_editing ? 0 : 108 + this.height_of_text_area)
+        .visibility(this.is_editing ? Visibility.Visible : Visibility.None)
+        .animation(animation_default())
+        .scrollBar(BarState.Off)
+        .nestedScroll({ scrollForward: NestedScrollMode.PARENT_FIRST, scrollBackward: NestedScrollMode.PARENT_FIRST })
+      }
+      .border({
+        radius: 10,
+        width: 2,
+        color: this.my_index == this.selected_index ? this.color_current_font : "transparent"
+      })
+    }
+    .width("100%")
+    .animation(animation_default())
+    .onAppear(() => {
+      setInterval(() => {
+        if (this.is_pressing) {
+          this.press_timing += 1;
+        } else {
+          this.press_timing = 0;
+        }
+        this.is_press_timing_ok = this.press_timing > 16;
+        // Count press time
+      }, 10)
+    })
+
+  }
+
+  set_global_UA(idx: number) {
+    this.selected_index = -99;
+    this.selected_index = idx;
+    this.bunch_of_settings.set('custom_user_agents_selected_index', idx);
+  }
+
+  save_changes() {
+    this.edit_content = this.edit_content.replaceAll("\n", "");
+
+    this.my_label = this.edit_label;
+    this.my_content = this.edit_content;
+    this.bunch_of_user_agents.list_of_user_agents[this.my_index].label = this.edit_label;
+    this.bunch_of_user_agents.list_of_user_agents[this.my_index].user_agent_content = this.edit_content;
+    this.save_user_agents_to_settings();
+
+    if (this.selected_index == this.my_index) {
+      this.set_global_UA(this.my_index);
+    }
+
+    // This would refresh UI in other places
+    this.bunch_of_user_agents.update_last_accessed();
+  }
+
+  delete_myself() {
+    this.is_editing = false;
+    this.bunch_of_user_agents.del_user_agent(this.my_index);
+
+    if (this.selected_index == this.my_index) {
+      this.selected_index = this.my_index - 1;
+    } else {
+      let previous_selected = this.selected_index;
+      this.selected_index = -2;
+      this.selected_index = Math.min(this.bunch_of_user_agents.list_of_user_agents.length - 1, previous_selected);
+      // Refresh
+    }
+    this.save_user_agents_to_settings();
+  }
+
+  save_user_agents_to_settings() {
+    console.log("[Meow][meowUAManager] Started to save custom user-agents to Settings!")
+    this.bunch_of_settings.set('custom_user_agents', this.bunch_of_user_agents.export_string());
+  }
+}
+

+ 124 - 0
home/src/main/ets/components/HeaderSearch.ets

@@ -0,0 +1,124 @@
+import { LengthUnit } from "@kit.ArkUI"
+import linysSymbol from "./texts/linysSymbol"
+
+@Component
+export default struct HeaderSearch {
+  @State isShow:boolean = false
+  // 是否可以输入内容
+  isEnable:boolean = true
+  // 当前处于的页面
+  @Require @Prop placeHolder:string
+  //----搜索栏属性
+  @Watch('submit')@State searchValue:string = ''
+  // 筛选类型
+  @Watch('submit')@State currentType: string = ''
+  // 筛选类别
+  @Watch('submit')@State currentCategory: string = ''
+  // 筛选世代
+  @Watch('submit')@State currentGeneration: string = ''
+  // 筛选顺序
+  @Watch('submit')@State sort: 'seq' | 'rev' = 'seq'
+  // 筛选索引
+  @Watch('submit')@State currentIndex: string = ''
+  // 文本框搜索函数
+  search?:(key: string) => void
+  @Prop bottomPadding: number
+  searchHeight:number = 40
+  searchMargin:Padding|Length = {left: 5,right: 5}
+  searchPadding:Padding|Length = {left:14,right:14}
+
+  placeholderColor:ResourceColor = '#CC333333'
+  placeholderFont:Font = {size:14}
+  needLeftBuilder:boolean = true
+  //----默认搜索图片属性 在传入leftBuilder或needLeftBuilder=false后无效
+  src:ResourceStr = $r('app.media.search')
+  imgWidth:Length = 18
+
+  // 添加状态变量来跟踪类型筛选器的重新渲染
+  @State filterKey: number = 0
+
+  //----提交函数,回调以回传输入内容
+  submit() {
+    if (this.search) {
+      this.search(this.searchValue)
+    }
+  }
+
+  @BuilderParam leftBuilder:()=>void = this.defaultLeftBuilder
+
+  aboutToAppear(): void {
+    console.info('HeaderSearch aboutToAppear:' + this.bottomPadding)
+  }
+
+  build() {
+    Row(){
+      Row(){
+        if (this.needLeftBuilder){
+          this.leftBuilder()
+        }
+
+        TextInput({text:$$this.searchValue,placeholder: '搜索' + this.placeHolder})
+          .backgroundColor(Color.Transparent)
+          .height('100%')
+          .layoutWeight(1)
+          .fontSize(14)
+          .borderRadius(0)
+          .placeholderFont(this.placeholderFont)
+          .placeholderColor(this.placeholderColor)
+          .padding(0)
+          .enabled(this.isEnable)
+          .onSubmit(()=>{
+            this.submit()
+          })
+
+      }
+      .margin(this.searchMargin)
+      .padding(this.searchPadding)
+      .borderRadius(this.searchHeight/2)
+      .height("100%")
+      .width("90%")
+      .backgroundColor('#FFE7E7E7')
+
+      linysSymbol({
+        symbol_glyph_target: 'sys.symbol.magnifyingglass'
+      })
+        .aspectRatio(1)
+        .layoutWeight(1)
+        .onClick(() => {
+          this.isShow = !this.isShow
+          // 每次打开筛选面板时更新key,强制重新渲染
+          if (this.isShow) {
+            this.filterKey += 1
+          }
+        })
+
+      // Flex()
+      //   .width(0)
+      //   .height(0)
+      //   .borderRadius({ topRight: 30, bottomRight: 30 })
+      //   .bindSheet(this.isShow, this.FilterView(), {
+      //     title: {
+      //       title: '筛选',
+      //     },
+      //     height: SheetSize.FIT_CONTENT,
+      //     showClose: true,
+      //     dragBar: false,
+      //     blurStyle: BlurStyle.COMPONENT_ULTRA_THIN,
+      //     onDisappear: () => {
+      //       this.isShow = false
+      //     },
+      //   })
+    }
+    .width("100%")
+    .height(this.searchHeight)
+  }
+
+  @Builder
+  defaultLeftBuilder(){
+    Image(this.src)
+      .fillColor(Color.Black)
+      .width(this.imgWidth)
+      .aspectRatio(1)
+      .margin({right:4})
+  }
+}

+ 35 - 0
home/src/main/ets/components/buttons/linysCapsuleButton.ets

@@ -0,0 +1,35 @@
+import {
+  animation_default,
+  capsule_bar_height,
+  click_effect_default,
+  fontSize_Large
+} from '../../hosts/bunch_of_defaults';
+
+@Component
+struct linysCapsuleButton {
+  @Prop text: ResourceStr = "";
+  // Colors
+  @StorageProp('color_current_primary') color_current_primary: ResourceColor = $r('app.color.start_window_background');
+  @StorageProp('color_current_font') color_current_font: ResourceColor = $r('app.color.font_color_title');
+  @Prop color_button: ResourceColor = "";
+  @Prop color_text: ResourceColor = "";
+
+  build() {
+    Button(this.text)
+      .opacity(0.9)
+      .type(ButtonType.Capsule)
+      .backgroundColor(this.color_button == "" ? this.color_current_font : this.color_button)
+      .fontColor(this.color_text == "" ? this.color_current_primary : this.color_text)
+      .fontSize(fontSize_Large())
+      .fontWeight(FontWeight.Medium)
+      .height(capsule_bar_height())
+      .border({
+        width: 2,
+        color: "transparent"
+      })
+      .clickEffect(click_effect_default())
+      .animation(animation_default())
+  }
+}
+
+export default linysCapsuleButton;

+ 39 - 0
home/src/main/ets/components/buttons/linysCapsuleButtonWithText.ets

@@ -0,0 +1,39 @@
+import { animation_default } from '../../hosts/bunch_of_defaults';
+import linysText from '../texts/linysText';
+import linysCapsuleButton from './linysCapsuleButton';
+
+@Component
+struct linysCapsuleButtonWithText {
+  // Info
+  @Prop desc_text: ResourceStr = 'desc';
+  @Prop button_text: ResourceStr = ' OwO ';
+  // Settings / Accessibility
+  @StorageLink('preferred_hand_left_or_right') preferred_hand: string = 'right';
+  onExecution?: () => void;
+
+  build() {
+    Row({ space: 10 }) {
+      Row() {
+        linysText({ text: this.desc_text, max_lines: 10 })
+          .animation(animation_default())
+      } // Text
+      // .opacity(0.9)
+      .justifyContent(this.preferred_hand == 'right' ? FlexAlign.End : FlexAlign.Start)
+      .animation(animation_default())
+      .layoutWeight(1)
+
+      linysCapsuleButton({ text: this.button_text })// Button
+        .onClick(() => {
+          if (this.onExecution) {
+            this.onExecution();
+          }
+        })
+    }
+    .justifyContent(this.preferred_hand == 'right' ? FlexAlign.End : FlexAlign.Start)
+    .direction(this.preferred_hand == 'right' ? Direction.Ltr : Direction.Rtl)
+    .animation(animation_default())
+    .width('100%')
+  }
+}
+
+export default linysCapsuleButtonWithText;

+ 47 - 0
home/src/main/ets/components/buttons/linysShowButton.ets

@@ -0,0 +1,47 @@
+import { animation_default, click_effect_default, fontSize_Large } from '../../hosts/bunch_of_defaults';
+import linysSymbol from '../texts/linysSymbol';
+
+@Component
+struct linysShowButton {
+  // Colors
+  @StorageProp('color_current_primary') color_current_primary: ResourceColor = $r('app.color.start_window_background');
+  @StorageProp('color_current_font') color_current_font: ResourceColor = $r('app.color.font_color_title');
+  @Prop show: boolean = false;
+  @Prop symbol_glyph_target: string = "sys.symbol.square_grid_2x2";
+  @Prop color_false: ResourceColor = "";
+  @Prop color_true: ResourceColor = "";
+  @Prop text: ResourceStr = $r('app.string.Index_tabs_title');
+
+  build() {
+    Row() {
+      linysSymbol({
+        symbol_glyph_target: this.symbol_glyph_target,
+        color: this.show ?
+          (this.color_true == "" ? this.color_current_primary : this.color_current_font) :
+          (this.color_false == "" ? this.color_current_font : this.color_false)
+      })
+
+      Scroll() {
+        Text(this.text)
+          .fontSize(fontSize_Large() - 2)
+          .fontColor(this.color_current_primary)
+          .margin({ left: 4, right: 3 })
+      }
+      .width(this.show ? undefined : 0)
+      .scrollable(ScrollDirection.Horizontal)
+      .edgeEffect(EdgeEffect.Spring)
+      .scrollBar(BarState.Off)
+      .animation(animation_default())
+    }
+    .padding(this.show ? 5 : 0)
+    .backgroundColor(this.show ? (this.color_false == "" ? this.color_current_font : this.color_false) : "transparent")
+    .borderRadius(10)
+    .clickEffect(click_effect_default())
+    .animation(animation_default())
+    .accessibilityText(this.text as string)
+    .accessibilityDescription($r('app.string.Accessibility_desc_linysShowButton'))
+    .accessibilityGroup(true)
+  }
+}
+
+export default linysShowButton;

+ 69 - 0
home/src/main/ets/components/buttons/linysTimeoutButton.ets

@@ -0,0 +1,69 @@
+import { Dialog } from '../../dialogs/Dialog';
+import { animation_default, capsule_bar_height, click_effect_default, fontSize_Large } from '../../hosts/bunch_of_defaults';
+
+@Component
+struct linysTimeoutButton {
+  @Prop text: ResourceStr = '  󰃈  ';
+  @Prop dialogText: ResourceStr = '确定是否删除所有标签内容';
+  @State visible: boolean = false
+  // editing
+  @State delete_confirm: number = 0;
+  // Colors
+  @StorageProp('color_current_primary') color_current_primary: ResourceColor =
+    $r('app.color.start_window_background');
+  @StorageProp('color_current_secondary') color_current_secondary: ResourceColor =
+    $r('app.color.block_color');
+  @StorageProp('color_current_font') color_current_font: ResourceColor =
+    $r('app.color.font_color_title');
+  // Operations
+  onExecution?: () => void;
+
+  build() {
+    Column(){
+      Button(this.text)// Reset
+        .opacity(0.9)
+        .type(ButtonType.Capsule)
+        .backgroundColor(this.delete_confirm > 0 ? this.color_current_primary : this.color_current_font)
+        .fontColor(this.delete_confirm > 0 ? this.color_current_font : this.color_current_primary)
+        .border({
+          width: 2,
+          color: this.delete_confirm > 0 ? this.color_current_font : "transparent"
+        })
+        .fontSize(fontSize_Large())
+        .fontWeight(FontWeight.Medium)
+        .height(capsule_bar_height())
+        .onClick(() => {
+          this.visible = true
+          // this.delete_confirm += 300;
+          // if (this.delete_confirm >= 301) {
+          //   if (this.onExecution) {
+          //     this.onExecution();
+          //   }
+          //   this.delete_confirm = 0;
+          // }
+        })
+        .clickEffect(click_effect_default())
+        // .animation(animation_default())
+        .onAppear(() => {
+          setInterval(() => {
+            if (this.delete_confirm > 0) {
+              this.delete_confirm -= 1;
+            }
+            // Reset delete confirm
+          }, 10)
+        })
+      Dialog({
+        visible: $visible,
+        text: this.dialogText,
+        onConfirm: () => {
+          if (this.onExecution) {
+            this.onExecution();
+          }
+          this.visible = false
+        }
+      })
+    }
+  }
+}
+
+export default linysTimeoutButton

+ 40 - 0
home/src/main/ets/components/buttons/linysTimeoutButtonWithText.ets

@@ -0,0 +1,40 @@
+import { animation_default } from '../../hosts/bunch_of_defaults';
+import linysText from '../texts/linysText';
+import linysTimeoutButton from './linysTimeoutButton';
+
+@Component
+struct linysTimeoutButtonWithText {
+  // Info
+  @State desc_text: ResourceStr = 'desc';
+  @State button_text: ResourceStr = ' OwO ';
+  // Settings / Accessibility
+  @StorageLink('preferred_hand_left_or_right') preferred_hand: string = 'right';
+  onExecution?: () => void;
+
+  build() {
+    Row({ space: 10 }) {
+      Row() {
+        linysText({ text: this.desc_text, max_lines: 10 })
+      } // Text
+      .justifyContent(this.preferred_hand == 'right' ? FlexAlign.End : FlexAlign.Start)
+      .animation(animation_default())
+      .layoutWeight(1)
+
+      linysTimeoutButton({
+        dialogText: "确定是否删除选中内容",
+        text: this.button_text,
+        onExecution: () => {
+          if (this.onExecution) {
+            this.onExecution();
+          }
+        }
+      }) // Button
+    }
+    .justifyContent(this.preferred_hand == 'right' ? FlexAlign.End : FlexAlign.Start)
+    .direction(this.preferred_hand == 'right' ? Direction.Ltr : Direction.Rtl)
+    .animation(animation_default())
+    .width('100%')
+  }
+}
+
+export default linysTimeoutButtonWithText;

+ 53 - 0
home/src/main/ets/components/linysLockSlider.ets

@@ -0,0 +1,53 @@
+import { animation_default } from '../hosts/bunch_of_defaults';
+import linysLockToggle from './toggles/linysLockToggle';
+import linysText from './texts/linysText';
+
+@Component
+struct linysLockSlider {
+  @State is_locked: boolean = true;
+  @Prop slider_min: number = 0;
+  @Prop slider_max: number = 100;
+  @Prop display: string | undefined = undefined;
+  @Link slider_value: number;
+  // Colors
+  @StorageProp('color_current_primary') color_current_primary: ResourceColor = $r('app.color.start_window_background');
+  @StorageProp('color_current_secondary') color_current_secondary: ResourceColor = $r('app.color.block_color');
+  @StorageProp('color_current_font') color_current_font: ResourceColor = $r('app.color.font_color_title');
+  // Operations
+  onTouchUp?: () => void;
+
+  build() {
+    Row({ space: 5 }) {
+      linysLockToggle({ locked: this.is_locked })
+
+      Slider({
+        min: this.slider_min,
+        max: this.slider_max,
+        value: this.slider_value,
+        style: SliderStyle.InSet,
+      })
+        .enabled(!this.is_locked)
+        .opacity(!this.is_locked ? 1 : 0.5)
+        .animation(animation_default())
+        .layoutWeight(1)
+        .blockColor(this.color_current_primary)
+        .selectedColor(this.color_current_font)
+        .onChange((value) => {
+          this.slider_value = value;
+        })
+        .onTouch(e => {
+          if (e.type == TouchType.Up) {
+            // this.bunch_of_settings.set('animation_damping_coefficient',
+            //   this.slider_value);
+            if (this.onTouchUp) {
+              this.onTouchUp();
+            }
+          }
+        })
+      linysText({ text: this.display ? this.display : this.slider_value.toString() })
+        .margin({ right: 5 })
+    }
+  }
+}
+
+export default linysLockSlider;

+ 99 - 0
home/src/main/ets/components/linysPathTree.ets

@@ -0,0 +1,99 @@
+import { animation_default, click_effect_default } from '../hosts/bunch_of_defaults';
+import linysText from './texts/linysText';
+
+@Component
+struct linysPathTree {
+  @Link @Watch('on_current_viewing_path_change') current_viewing_path: string;
+  @StorageProp('screen_height') screen_height: number = 100;
+  @Prop max_height_screen_percentage: number = 0.4;
+  @State path_stack: string[] = [];
+  @State label_stack: string[] = [];
+  scroller: Scroller = new Scroller();
+  // Settings / Accessibility
+  @StorageLink('preferred_hand_left_or_right') preferred_hand_left_or_right: string = 'right';
+  // Colors
+  @StorageProp('color_current_secondary') color_current_secondary: ResourceColor = $r('app.color.block_color');
+
+  build() {
+    Scroll(this.scroller) {
+      Column({ space: 5 }) {
+        Row() {
+          linysText({ text: " 󰀧 ", font_weight: FontWeight.Bold });
+        }
+        .borderRadius(10)
+        .padding(10)
+        .backgroundColor(this.color_current_secondary)
+        .animation(animation_default())
+        .clickEffect(click_effect_default())
+        .onClick(() => {
+          this.current_viewing_path = "";
+        })
+
+        ForEach(this.label_stack, (item: string, index: number) => {
+          linysPathButton({
+            current_viewing_path: this.current_viewing_path,
+            label: item,
+            my_directory: this.path_stack[index],
+          })
+        })
+      }
+      .alignItems(this.preferred_hand_left_or_right == 'right' ? HorizontalAlign.End : HorizontalAlign.Start)
+      .width("100%")
+      .animation(animation_default())
+    }
+    .scrollBar(BarState.Off)
+    .height(42 + Math.min(this.screen_height * this.max_height_screen_percentage, 45 * this.label_stack.length))
+    .animation(animation_default())
+    .edgeEffect(EdgeEffect.Spring)
+  }
+
+  on_current_viewing_path_change() {
+    this.decompose_path(this.current_viewing_path);
+  }
+
+  decompose_path(path: string) {
+    let split_labels = path.split("/");
+    let result: string[] = [];
+    let meow_path = "";
+    for (let index = 0; index < split_labels.length; index++) {
+      let label = split_labels[index];
+      if (meow_path == '') {
+        meow_path = label;
+        result.push(label);
+      } else {
+        meow_path = meow_path + "/" + label;
+        result.push(meow_path);
+      }
+    }
+    this.path_stack = result;
+    this.label_stack = split_labels;
+    if (this.label_stack.join() == "") {
+      this.label_stack = [];
+    }
+  }
+}
+
+export default linysPathTree;
+
+@Component
+struct linysPathButton {
+  @Prop label: string = "label";
+  @Prop my_directory: string = "label";
+  @Link current_viewing_path: string;
+  // Colors
+  @StorageProp('color_current_secondary') color_current_secondary: ResourceColor = $r('app.color.block_color');
+
+  build() {
+    Row() {
+      linysText({ text: this.label });
+    }
+    .borderRadius(10)
+    .padding(10)
+    .backgroundColor(this.color_current_secondary)
+    .animation(animation_default())
+    .clickEffect(click_effect_default())
+    .onClick(() => {
+      this.current_viewing_path = this.my_directory;
+    })
+  }
+}

+ 54 - 0
home/src/main/ets/components/linysProgress.ets

@@ -0,0 +1,54 @@
+@Component
+struct linysProgress {
+  // Colors
+  @StorageProp('color_current_primary') color_current_primary: ResourceColor = $r('app.color.start_window_background');
+  @StorageProp('color_current_font') color_current_font: ResourceColor = $r('app.color.font_color_title');
+  @State bar_height: Length = 8;
+  @State bar_width: Length = "100%";
+  @Watch('on_update') @Prop is_loading: boolean = false;
+  @Watch('on_update') @Prop percentage: number = 0;
+  @State display_rate: number = 0.0;
+  @State color_progress: ResourceColor = "";
+  @State color_undone: ResourceColor = "";
+  @State show: boolean = false;
+
+  build() {
+    Row() {
+      Row()
+        .height("100%")
+        .layoutWeight(this.display_rate)
+        .backgroundColor(this.color_progress == "" ? this.color_current_font : this.color_progress)
+        .animation({ duration: this.show ? 500 : 0, curve: Curve.EaseInOut })
+
+      Row()
+        .height("100%")
+        .layoutWeight(1 - (this.display_rate))
+        .backgroundColor(this.color_undone == "" ? this.color_current_primary : this.color_undone)
+        .animation({ duration: this.show ? 500 : 0, curve: Curve.EaseInOut })
+    }
+    .height(this.show ? this.bar_height : 0)
+    .width(this.bar_width)
+    .animation({ duration: 200, curve: Curve.ExtremeDeceleration })
+    .accessibilityText(this.percentage?.toString() + "%")
+    .accessibilityDescription($r('app.string.Accessibility_desc_linysProgress'))
+    .accessibilityGroup(true)
+  }
+
+  on_update() {
+    this.display_rate = this.percentage / 100
+    if (this.is_loading) {
+      this.show = true;
+    } else {
+      setTimeout(() => {
+        // Auto hide after is not loading
+        this.show = false;
+      }, 500)
+      setTimeout(() => {
+        // Reset progress bar
+        this.display_rate = 0;
+      }, 800)
+    }
+  }
+}
+
+export default linysProgress

+ 30 - 0
home/src/main/ets/components/linysProgressInfo.ets

@@ -0,0 +1,30 @@
+import { animation_default, click_effect_default } from '../hosts/bunch_of_defaults';
+import linysText from './texts/linysText';
+
+@Component
+struct linysProgressInfo {
+  @Prop progress: string = "qwq";
+  @Prop notification: ResourceStr = "The task desc.";
+
+  build() {
+    Row({ space: 10 }) {
+      linysText({ text: this.notification, max_lines: 3 })
+        .layoutWeight(1)
+      linysText({ text: this.progress })
+    } // Reindexing indicator
+    .padding({
+      left: 15,
+      right: 15,
+      top: 8,
+      bottom: 8
+    })
+    .alignItems(VerticalAlign.Center)
+    .borderRadius(12)
+    .backgroundColor($r('sys.color.comp_background_tertiary'))
+    .animation(animation_default())
+    .clickEffect(click_effect_default())
+    .constraintSize({ maxWidth: 500 })
+  }
+}
+
+export default linysProgressInfo;

+ 35 - 0
home/src/main/ets/components/texts/linysLink.ets

@@ -0,0 +1,35 @@
+import { fontSize_Normal } from '../../hosts/bunch_of_defaults';
+import { LengthMetrics } from '@kit.ArkUI';
+
+@Component
+struct linysLink {
+  // Colors
+  @StorageProp('color_current_font') color_current_font: ResourceColor = $r('app.color.font_color_title');
+  @Prop text: ResourceStr = $r('app.string.HomeAbility_label');
+  @Prop color: ResourceColor = "";
+  @Prop max_lines: number = 1;
+  @Prop font_weight: FontWeight = FontWeight.Regular;
+  @State link: string = "huawei.com";
+  @StorageLink('universal_new_tab_gateway') universal_new_tab_gateway: string = "";
+  // In fact this param is designed for closing some opened panel
+  @Link will_be_reversed_boolean: boolean;
+
+  build() {
+    Text(this.text)
+      .fontColor(this.color == "" ? this.color_current_font : this.color)
+      .fontSize(fontSize_Normal())
+      .fontWeight(this.font_weight)
+      .textAlign(TextAlign.Start)
+      .maxLines(this.max_lines)
+      .lineSpacing(LengthMetrics.vp(4))
+      .textOverflow({ overflow: TextOverflow.Ellipsis })
+      .onClick(() => {
+        this.universal_new_tab_gateway = this.link;
+        this.will_be_reversed_boolean = !this.will_be_reversed_boolean;
+      })
+      .accessibilityText(this.text + ',' + this.link)
+      .accessibilityDescription($r('app.string.Accessibility_desc_linysLink'))
+  }
+}
+
+export default linysLink

+ 21 - 0
home/src/main/ets/components/texts/linysSymbol.ets

@@ -0,0 +1,21 @@
+import { animation_default, click_effect_default, fontSize_Icon_Button } from '../../hosts/bunch_of_defaults';
+
+@Component
+struct linysSymbol {
+  @Prop symbol_glyph_target: string = 'sys.symbol.arrow_left';
+  // Colors
+  @StorageProp('color_current_font') color_current_font: ResourceColor = $r('app.color.font_color_title');
+  @Prop color: ResourceColor = "";
+  @Prop font_weight: FontWeight = FontWeight.Regular;
+
+  build() {
+    SymbolGlyph($r(this.symbol_glyph_target))
+      .fontWeight(this.font_weight)
+      .fontSize(fontSize_Icon_Button())
+      .fontColor([this.color == "" ? this.color_current_font : this.color])
+      .animation(animation_default())
+      .clickEffect(click_effect_default())
+  }
+}
+
+export default linysSymbol

+ 54 - 0
home/src/main/ets/components/texts/linysText.ets

@@ -0,0 +1,54 @@
+import { animation_default, click_effect_default, fontSize_Normal } from '../../hosts/bunch_of_defaults';
+import { LengthMetrics } from '@kit.ArkUI';
+
+@Component
+struct linysText {
+  // Colors
+  @StorageProp('color_current_font') color_current_font: ResourceColor = $r('app.color.font_color_title');
+  @Prop text: ResourceStr = $r('app.string.HomeAbility_label');
+  @Prop color: ResourceColor = "";
+  @Prop max_lines: number = 1;
+  @Prop font_weight: FontWeight = FontWeight.Regular;
+  @Prop is_description: boolean = false;
+  @Prop is_expanded: boolean = false;
+
+  build() {
+    if (this.is_description) {
+      Scroll() {
+        Text(this.text)
+          .fontColor(this.color == "" ? this.color_current_font : this.color)
+          .fontSize(fontSize_Normal())
+          .fontWeight(this.font_weight)
+          .maxLines(!this.is_expanded ? 1 : undefined)
+          .lineSpacing(LengthMetrics.vp(4))
+          .textOverflow({ overflow: TextOverflow.Ellipsis })
+          .animation(animation_default())
+      }
+      .nestedScroll({scrollForward: NestedScrollMode.PARENT_FIRST, scrollBackward: NestedScrollMode.PARENT_FIRST })
+      .align(Alignment.TopStart)
+      .scrollable(ScrollDirection.Vertical)
+      .scrollBar(BarState.Off)
+      // .edgeEffect(EdgeEffect.Spring)
+      .constraintSize(this.is_expanded ? {} : { maxHeight: 40 })
+      .animation(animation_default())
+      .borderRadius(12)
+      .padding(10)
+      .backgroundColor($r('sys.color.comp_background_tertiary'))
+      .width('100%')
+      .onClick(() => {
+        this.is_expanded = !this.is_expanded;
+      })
+      .clickEffect(click_effect_default())
+    } else {
+      Text(this.text)
+        .fontColor(this.color == "" ? this.color_current_font : this.color)
+        .fontSize(fontSize_Normal())
+        .fontWeight(this.font_weight)
+        .maxLines(this.max_lines)
+        .lineSpacing(LengthMetrics.vp(4))
+        .textOverflow({ overflow: TextOverflow.Ellipsis })
+    }
+  }
+}
+
+export default linysText;

+ 33 - 0
home/src/main/ets/components/texts/linysTextSubtitleDivision.ets

@@ -0,0 +1,33 @@
+import { fontSize_Large } from '../../hosts/bunch_of_defaults';
+
+@Component
+struct linysTextSubtitleDivision {
+  // Colors
+  @StorageProp('color_current_font') color_current_font: ResourceColor = $r('app.color.font_color_title');
+  @Prop icon: ResourceStr = '';
+  @Prop text: ResourceStr = $r('app.string.HomeAbility_label');
+  @Prop color: ResourceColor = "";
+  @Prop max_lines: number = 1;
+
+  build() {
+    Row({ space: 8 }) {
+
+      if (this.icon) {
+        Text(this.icon)
+          .fontColor(this.color == "" ? this.color_current_font : this.color)
+          .fontSize(fontSize_Large() + 4)
+          .fontWeight(FontWeight.Medium)
+      }
+      Text(this.text)
+        .fontColor(this.color == "" ? this.color_current_font : this.color)
+        .fontSize(fontSize_Large())
+        .fontWeight(FontWeight.Bold)
+      // Row()
+      //   .layoutWeight(1)
+      //   .border({ width: { bottom: 2 }, color: this.color == "" ? this.color_current_font : this.color })
+    }
+    .opacity(0.6)
+  }
+}
+
+export default linysTextSubtitleDivision;

+ 21 - 0
home/src/main/ets/components/texts/linysTextTitle.ets

@@ -0,0 +1,21 @@
+import { fontSize_Large } from '../../hosts/bunch_of_defaults';
+
+@Component
+struct linysTextTitle {
+  // Colors
+  @StorageProp('color_current_font') color_current_font: ResourceColor = $r('app.color.font_color_title');
+  @Prop text: ResourceStr = $r('app.string.HomeAbility_label');
+  @Prop color: ResourceColor = "";
+  @Prop max_lines: number = 1;
+
+  build() {
+    Text(this.text)
+      .fontColor(this.color == "" ? this.color_current_font : this.color)
+      .fontSize(fontSize_Large())
+      .fontWeight(FontWeight.Bold)
+      .maxLines(this.max_lines)
+      .textOverflow({ overflow: TextOverflow.Ellipsis })
+  }
+}
+
+export default linysTextTitle;

+ 23 - 0
home/src/main/ets/components/toggles/linysLockToggle.ets

@@ -0,0 +1,23 @@
+import { fontSize_Large } from '../../hosts/bunch_of_defaults';
+
+@Component
+struct linysLockToggle {
+  @Link locked: boolean;
+  @StorageProp('color_current_font') color_current_font: ResourceColor = $r('app.color.font_color_title');
+  @State color: ResourceColor | undefined = undefined;
+  // animation
+  @State triggerValueReplace: number = 0;
+
+  build() {
+    SymbolGlyph(this.locked ? $r('sys.symbol.lock') : $r('sys.symbol.lock_open'))
+      .onClick(() => {
+        this.locked = !this.locked;
+        this.triggerValueReplace += 1;
+      })// .clickEffect(click_effect_default())
+      .fontSize(fontSize_Large())
+      .symbolEffect(new ReplaceSymbolEffect(EffectScope.WHOLE), this.triggerValueReplace)
+      .fontColor([this.color ? this.color : this.color_current_font])
+  }
+}
+
+export default linysLockToggle

+ 41 - 0
home/src/main/ets/components/toggles/linysSwitchWithText.ets

@@ -0,0 +1,41 @@
+import { animation_default } from '../../hosts/bunch_of_defaults';
+import linysText from '../texts/linysText';
+
+@Component
+struct linysSwitchWithText {
+  // Links
+  @Prop text: ResourceStr = "";
+  @Link toggle_state: boolean;
+  onExecution?: () => void;
+  // Settings / Accessibility
+  @StorageLink('preferred_hand_left_or_right') preferred_hand: string = 'right';
+  // Colors
+  @StorageProp('color_current_secondary') color_current_secondary: ResourceColor = $r('app.color.block_color');
+  @StorageProp('color_current_font') color_current_font: ResourceColor = $r('app.color.font_color_title');
+
+  build() {
+    Row({ space: 10 }) {
+      Row() {
+        linysText({ text: this.text, max_lines: 5 })
+      }
+      // .opacity(0.9)
+      .layoutWeight(1)
+      .justifyContent(FlexAlign.Start)
+
+      Toggle({ isOn: this.toggle_state, type: ToggleType.Switch })
+        .switchPointColor(this.color_current_secondary)
+        .selectedColor(this.color_current_font)
+        .onChange(isOn => {
+          this.toggle_state = isOn;
+          if (this.onExecution) {
+            this.onExecution();
+          }
+        })
+    } // Toggle
+    .direction(this.preferred_hand == 'right' ? Direction.Ltr : Direction.Rtl)
+    .animation(animation_default())
+    .width("100%")
+  }
+}
+
+export default linysSwitchWithText;

+ 47 - 0
home/src/main/ets/dialogs/CustomDialogView.ets

@@ -0,0 +1,47 @@
+import promptAction from '@ohos.promptAction';
+
+@CustomDialog
+export struct CustomDialogView {
+  @Link visible: boolean;
+  controller: CustomDialogController;
+  // 弹窗交互事件参数,点击确认和取消按钮时的回调函数
+  onCancel?: () => void;
+  onConfirm?: () => void;
+  @Prop text: ResourceStr
+
+  build() {
+    Column() {
+      Text(this.text)
+        .fontSize($r('app.integer.custom_dialog_content_font_size'))
+        .fontColor(Color.Black)
+        .padding({ top: $r('app.integer.ohos_id_card_padding_start') })
+      Row() {
+        Button($r('app.string.custom_dialog_cancel'))
+          .backgroundColor($r('app.color.ohos_id_color_background'))
+          .fontColor($r('app.color.ohos_id_color_emphasize'))
+          .fontSize($r('app.integer.custom_dialog_content_font_size'))
+          .width($r('app.integer.custom_dialog_button_width'))
+          .onClick(() => {
+            this.visible = false;
+            this.onCancel?.();
+          })
+        Button($r('app.string.custom_dialog_confirm'))
+          .backgroundColor($r('app.color.ohos_id_color_background'))
+          .fontColor($r('app.color.ohos_id_color_emphasize'))
+          .fontSize($r('app.integer.custom_dialog_content_font_size'))
+          .width($r('app.integer.custom_dialog_button_width'))
+          .onClick(() => {
+            if (this.onConfirm) {
+              this.onConfirm()
+            }
+          })
+      }
+      .justifyContent(FlexAlign.Center)
+    }
+    .borderRadius($r('app.integer.ohos_id_corner_radius_default_m'))
+    .justifyContent(FlexAlign.SpaceAround)
+    .backgroundColor($r('app.color.ohos_id_color_background'))
+    .height($r('app.integer.custom_dialog_column_height'))
+    .width($r('app.integer.custom_dialog_column_width'))
+  }
+}

+ 38 - 0
home/src/main/ets/dialogs/Dialog.ets

@@ -0,0 +1,38 @@
+import { CustomDialogView } from './CustomDialogView';
+
+@Component
+export struct Dialog {
+  // 监听外部传入的visible变量,visible值发生变化时触发onChange回调函数
+  @Watch("onChange") @Link visible: boolean;
+  @Prop text: ResourceStr = $r('app.string.custom_dialog_delete')
+  onCancel?: () => void;
+  onConfirm?: () => void;
+  // 通过CustomDialogController的builder参数绑定弹窗组件CustomDialogView
+  private controller = new CustomDialogController({
+    builder: CustomDialogView({
+      text: this.text,
+      visible: $visible,
+      onCancel: this.onCancel,
+      onConfirm: this.onConfirm,
+    }),
+    autoCancel: false,
+    customStyle: true,
+    alignment: DialogAlignment.Center,
+    maskColor: $r('app.color.custom_dialog_mask_color'),
+  })
+
+  /**
+   * 当visible的值变化时触发回调
+   */
+  onChange(): void{
+    if (this.visible) {
+      this.controller.open();
+    } else {
+      this.controller.close();
+    }
+  }
+
+  // 二次封装的Dialog组件主要通过控制器控制弹窗,不需要任何界面
+  build() {
+  }
+}

+ 55 - 0
home/src/main/ets/dialogs/contents/woofQR.ets

@@ -0,0 +1,55 @@
+import linysText from '../../components/texts/linysText';
+import linysTextTitle from '../../components/texts/linysTextTitle';
+import woofControlFrame from '../woofControlFrame';
+
+@CustomDialog
+struct woofQR {
+  controller: CustomDialogController;
+  @Prop link: string = '';
+  @Prop title: string = '';
+  // Colors
+  @StorageProp('color_current_primary') color_current_primary: ResourceColor = $r('app.color.start_window_background');
+  @StorageProp('color_current_secondary') color_current_secondary: ResourceColor = $r('app.color.block_color');
+  @StorageProp('color_current_font') color_current_font: ResourceColor = $r('app.color.font_color_title');
+  // Status
+  @State content_height: number = 233;
+
+  build() {
+    woofControlFrame({
+      title: $r('app.string.QR_code'),
+      controller: this.controller
+    }) {
+      Scroll() {
+        Column({ space: 5 }) {
+          QRCode(this.link)
+            .color(this.color_current_font)
+            .backgroundColor("transparent")
+            .padding(20)
+
+          linysTextTitle({
+            text: this.title,
+            max_lines: 3
+          })
+            .width("100%")
+          linysText({
+            text: this.link,
+            max_lines: 5
+          })
+            .width("100%")
+            .opacity(0.7)
+        }
+        .alignItems(HorizontalAlign.Center)
+        .justifyContent(FlexAlign.Center)
+        .width("100%")
+        .onAreaChange((_o, n) => {
+          this.content_height = n.height as number;
+        })
+      }
+      .layoutWeight(1)
+      .edgeEffect(EdgeEffect.Spring)
+      .constraintSize({ maxHeight: this.content_height })
+    }
+  }
+}
+
+export default woofQR;

+ 124 - 0
home/src/main/ets/dialogs/contents/woofRecentFaultLogs.ets

@@ -0,0 +1,124 @@
+import { FaultLogger } from '@kit.PerformanceAnalysisKit';
+import { BusinessError } from '@kit.BasicServicesKit';
+import woofControlFrame from '../woofControlFrame';
+import { copy } from '../../utils/clipboard_tools';
+import linysCapsuleButton from '../../components/buttons/linysCapsuleButton';
+import linysTextTitle from '../../components/texts/linysTextTitle';
+import { animation_default } from '../../hosts/bunch_of_defaults';
+
+@CustomDialog
+struct woofRecentFaultLogs {
+  controller: CustomDialogController;
+  @State faultLogs: string[] = [];
+
+  build() {
+    woofControlFrame({
+      title: $r('app.string.Fault_logs'),
+      controller: this.controller
+    }) {
+      Scroll() {
+        Column({ space: 10 }) {
+          if (this.faultLogs.length == 0) {
+            linysTextTitle({
+              text: $r('app.string.Fault_logs_empty')
+            })
+            linysTextTitle({
+              text: ' ¯\\_(ツ)_/¯ '
+            })
+              .opacity(0.7)
+          } else {
+            ForEach(this.faultLogs, (fault: string, index: number) => {
+              faultItem({
+                fault: fault,
+                idx: index
+              })
+            })
+          }
+        }
+        .alignItems(HorizontalAlign.Center)
+      }
+      .borderRadius(13.5)
+      .layoutWeight(1)
+      .scrollable(ScrollDirection.Vertical)
+      .edgeEffect(EdgeEffect.Spring)
+      .onAppear(() => {
+        this.get_fault_logs();
+      })
+    }
+  }
+
+  get_fault_logs() {
+    this.faultLogs = [];
+    try {
+      FaultLogger.query(FaultLogger.FaultType.JS_CRASH).then(value => {
+        if (value) {
+          console.info("FaultLog length is " + value.length);
+          let len: number = value.length;
+          for (let i = 0; i < len; i++) {
+            let this_fault: string[] = [];
+            // this_fault.push("log: " + i)
+            this_fault.push("Log pid: " + value[i].pid)
+            this_fault.push("Log uid: " + value[i].uid)
+            this_fault.push("Log type: " + value[i].type)
+            this_fault.push("Log timestamp: " + value[i].timestamp +
+              " (" + new Date(value[i].timestamp).toLocaleString() + ")")
+            this_fault.push("Log reason: " + value[i].reason)
+            this_fault.push("Log module: " + value[i].module)
+            // this_fault.push("Log summary: " + value[i].summary)
+            this_fault.push("Log text: " + value[i].fullLog)
+            this.faultLogs.push(this_fault.join("\n"));
+          }
+        }
+      });
+    } catch (err) {
+      console.error(`code: ${(err as BusinessError).code}, message: ${(err as BusinessError).message}`);
+    }
+  }
+}
+
+export default woofRecentFaultLogs;
+
+@Component
+struct faultItem {
+  @Prop fault: string = 'meow meow meow wait a second meow!';
+  @Prop idx: number;
+  // Colors
+  @StorageProp('color_current_font') color_current_font: ResourceColor = $r('app.color.font_color_title');
+  // Edit
+  @State copied: boolean = false;
+  // Settings / Accessibility
+  @StorageLink('preferred_hand_left_or_right') preferred_hand_left_or_right: string = 'right';
+
+  build() {
+    Column({ space: 10 }) {
+      Row({ space: 10 }) {
+        Row() {
+          linysTextTitle({ text: (this.idx + 1).toString() + "." })
+        }
+        .layoutWeight(this.preferred_hand_left_or_right == 'right' ? 1 : undefined)
+        .animation(animation_default())
+
+        linysCapsuleButton({
+          text: this.copied ? " 󰆊 " : " 󰆝 "
+        })
+          .onClick(() => {
+            this.copy_this();
+          })
+      }
+      .alignItems(VerticalAlign.Bottom)
+      .width('100%')
+
+      TextArea({ text: this.fault })
+        .fontWeight(FontWeight.Regular)
+        .fontColor(this.color_current_font)
+        .caretColor(this.color_current_font)
+        .selectedBackgroundColor(this.color_current_font)
+      // .height(200)
+    }
+  }
+
+  copy_this() {
+    this.copied = true;
+    copy(this.fault);
+  }
+}

+ 173 - 0
home/src/main/ets/dialogs/contents/woofUpdateHistory.ets

@@ -0,0 +1,173 @@
+import linysText from '../../components/texts/linysText';
+import linysTextTitle from '../../components/texts/linysTextTitle';
+import { animation_default, click_effect_default } from '../../hosts/bunch_of_defaults';
+import woofControlFrame from '../woofControlFrame';
+
+@CustomDialog
+struct woofUpdateHistory {
+  controller: CustomDialogController;
+  // Colors
+  @StorageProp('color_current_primary') color_current_primary: ResourceColor = $r('app.color.start_window_background');
+  @StorageProp('color_current_secondary') color_current_secondary: ResourceColor = $r('app.color.block_color');
+  @StorageProp('color_current_font') color_current_font: ResourceColor = $r('app.color.font_color_title');
+  @State texts: string[] = [
+    'v1.7.8-beta 2025 07',
+    '一个补充性的更新!将浏览器界面右键支持、权限申请、更好的页面内搜索、网页打印、Blob 下载支持、代理下载支持、跨设备接续等功能带入冲浪喵~',
+    'A supplementary update! Bringing right click support for ui, Permission Request, better in-page searching, web printing, ' +
+      'Blob download support, proxy enabled download support and cross-device continuation support into BrowserCat~',
+
+    'v1.7.7-beta 2025 06',
+    '一个实验性的更新!将历史记录索引、更多辅助功能带入冲浪喵~\n' +
+      '实现了猫抓板图片处理、一键关闭所有标签页等功能!\n' +
+      '在不科学的实验中,这个历史记录索引成功承载长达 20 年、共 1500000 条历史记录而不崩溃,因此 Liny 还有 20 年时间优化它!:P',
+    'An experimental update! Bringing history indexing and more accessibility features into BrowserCat~\n' +
+      'Implemented image processing for Scratching Board and one button to close all tabs!\n' +
+      'In an unscientific experiment, this history indexing system succeeded to host up to 1,500,000 histories for a span of ' +
+      'over 20 years without crashing. So, Liny has got another 20 years to optimize it! :P',
+
+    'v1.7.6-beta 2025 05',
+    '一个普通的更新,将故障日志查看、性能指示器和大量体验与性能优化带入冲浪喵~\n' +
+      '实现了更好的显示区域避让,尝试修复了计算缓存卡顿、历史面板打开卡顿、下载文件名 uri 不解码、自动抓取搜索词加号不解码等体验问题。\n' +
+      '并且升级到了 API 16!',
+    'A normal update, bringing fault logs viewer, performance monitor and quite a lot of experience & performance optimizations into BrowserCat!\n' +
+      'Implemented a better safe area display policy, and ' +
+      'tried to fix experience issues like lags when calculating cache size, lags when opening History panel, ' +
+      'download file name not decoding from uri codes, ' +
+      'and plus signs in auto extracted search keywords not being replaced by spaces, etc.\n' +
+      'And upgraded to API 16!',
+
+    'v1.7.5-beta 2025 04',
+    '一个神奇的更新,将自定义主页背景、快捷链接带入冲浪喵~\n' +
+      '并且尝试修复神秘的崩溃等大量隐藏/关键问题,请务必安装此更新!',
+    'A magical (?) update, bringing custom homepage background and shortcuts into BrowserCat!\n' +
+      'AND, tried to fix plenty of hidden / critical problems like weird crashing on startup, PLEASE INSTALL THIS UPDATE!',
+
+    'v1.7.4-beta 2025 01',
+    '一个主要更新,将键盘快捷键、页面内搜索、JS 管理和无图模式带入冲浪喵~\n' +
+      '并且修复了下载无法保存、下载文件名混乱等关键问题,请务必安装此更新!',
+    'A major update, bringing Keyboard shortcuts, In-page searching, JavaScript management and No picture mode into BrowserCat!\n' +
+      'AND, fixed download saving failure, download wrong file name problems, PLEASE INSTALL THIS UPDATE!',
+
+    'v1.7.3-beta 2024 12',
+    '一个常规更新,将沉浸式全屏、曲奇管理和更准确的缓存大小统计带入冲浪喵~\n' +
+      '并且并且,现在这个 App 可以被设置为默认浏览器了!',
+    'A regular update, bringing Immersive Fullscreen, Cookie management, and more accurate cache size calculation into BrowserCat!\n' +
+      'What\'s more, The app can now be set as default browser of system!',
+
+    'v1.7.2-beta 2024 12',
+    '一个常规更新,将智能防追踪功能带入冲浪喵~\n' +
+      '并且并且,现在这个 App 可以被设置为默认浏览器了!',
+    'A regular update, bringing Intelligent Tracking Prevention feature into BrowserCat!\n' +
+      'What\'s more, The app can now be set as default browser of system!',
+
+    'v1.7.1-beta 2024 12',
+    '一个关键的补充更新,提供了历史自动迁移能力,这样老版本冲浪喵上的历史记录会被继承了!',
+    'A KEY PATCH. INTRODUCING HISTORY MIGRATION FROM OLD BROWSERCATS.\n' +
+      'So that history records on old BrowserCats would be inherited.',
+
+    'v1.7.0-beta 2024 12',
+    '一个关键的发行版更新,将网页深色模式、更新记录、致谢和一些体验优化带入冲浪喵~\n' +
+      '这个版本正在尝试优化一些组件的性能问题,但是这需要一些深层的修改。\n' +
+      '所以,以前的历史浏览记录和上一次打开的标签页并没有得到继承……对不起!!!\n>︿<',
+    'A KEY release of a KEY update, bringing better History system and a few experience optimizations into BrowserCat!\n' +
+      'This version is trying to solve some performance issue of parts of this project, but this requires some deep level modification.\n' +
+      'Therefore, the previous History records and last status of opened tabs are not inherited. So sorry for that...',
+
+    'v1.6.4-beta 2024 12',
+    '一个体面的发行版更新,将网页深色模式、更新记录、致谢和一些体验优化带入冲浪喵~',
+    'A decent release of update, bringing Web Dark Mode, Update History, Credits and a few experience optimizations into BrowserCat!',
+
+    'v1.6.3-beta 2024 12',
+    '一个主要更新计划的早期预览,将储存管理和一些体验优化(和一些 Bug)带入冲浪喵~',
+    'An early preview of a major update plan, bringing Storage Management and a few experience optimizations (and some Bugs) into BrowserCat!',
+
+    'v1.6.2-beta 2024 12',
+    '一个主要更新计划的早期预览,将自定义主题颜色(和一些 Bug)带入冲浪喵~',
+    'An early preview of a major update plan, bringing Customizable Theme Colors (and some Bugs) into BrowserCat!',
+
+    'v1.6.0-beta 2024 11',
+    '一个主要更新计划的早期预览,将(操作失败时的)弹窗提醒和更新日志带入冲浪喵~',
+    'An early preview of a major update plan, bringing notice prompts (when an operation fails) and update notes into BrowserCat!',
+
+    'v1.5.3-beta 2024 11',
+    '一个主要更新,将更好的设置面板布局、更好的历史面板 UI 和广告过滤带入冲浪喵~\n' +
+      '将 HarmonyOS 的 compileSdkVersion 提升到了 13,修复了若干个问题、完成了一些代码清理!',
+    'A major update, bringing better Settings Panel arrangement, better UI for History Panel and Ads Blocker into BrowserCat!\n' +
+      'Changed compileSdkVersion for the HarmonyOS product to 13. Fixed several bugs and did bunch of code clean up!',
+
+    'v1.5.0-beta 2024 10',
+    '一个主要更新,将横排标签页面板和拖放面板(猫抓板!)带入冲浪喵~\n' +
+      '增加了移动书签/文件夹的入口,并且修复了若干个问题、完成了一些代码清理!',
+    'A major update, bringing Horizontal Tabs Panel and links / text dropping panel (Scratching Board!) into BrowserCat!\n' +
+      'Added entry to move a bookmark or folder to another position, fixed several bugs and did bunch of code clean up!',
+
+    'v1.4.0-beta 2024 10',
+    '冲浪喵的第一个发行版本!\n' +
+      '完成了垂直标签页、书签管理、历史记录、网页内下载、搜索关键词联想、自定义UA、自定义搜索引擎,和很多动效!',
+    'The first release of BrowserCat!\n' +
+      'Finished vertical tabs, bookmarks management, history records, in web downloads, keyword typing suggestions, custom UA, custom search engine, and some many animations!'
+  ];
+
+  build() {
+    woofControlFrame({
+      title: $r('app.string.Update_record'),
+      controller: this.controller
+    }) {
+      Scroll() {
+        Column({ space: 10 }) {
+          ForEach(this.texts, (text: string, index: number) => {
+            if (index % 3 == 0) {
+              release_card({
+                title: text,
+                text_eng: this.texts[index + 1],
+                text_chi: this.texts[index + 2],
+              })
+            }
+          })
+        }
+        .alignItems(HorizontalAlign.Center)
+        .justifyContent(FlexAlign.Center)
+        .width("100%")
+      }
+      .edgeEffect(EdgeEffect.Spring)
+      .layoutWeight(1)
+    }
+  }
+}
+
+export default woofUpdateHistory;
+
+@Component
+struct release_card {
+  @State title: string = '';
+  @State text_eng: string = '';
+  @State text_chi: string = '';
+
+  build() {
+    Column({ space: 8 }) {
+      linysTextTitle({
+        text: this.title,
+      })
+        .width("100%")
+      linysText({
+        text: this.text_eng,
+        max_lines: 30
+      })
+        .width("100%")
+        .opacity(0.7)
+      linysText({
+        text: this.text_chi,
+        max_lines: 20
+      })
+        .width("100%")
+        .opacity(0.7)
+    }
+    .clickEffect(click_effect_default())
+    .alignItems(HorizontalAlign.Start)
+    .padding(10)
+    .borderRadius(10)
+    .backgroundColor($r('sys.color.comp_background_tertiary'))
+    .width("100%")
+    .animation(animation_default())
+  }
+}

+ 390 - 0
home/src/main/ets/dialogs/managers/woofAdsBlocker.ets

@@ -0,0 +1,390 @@
+import linysSymbol from '../../components/texts/linysSymbol';
+import linysText from '../../components/texts/linysText';
+import linysTextTitle from '../../components/texts/linysTextTitle';
+import { animation_default, capsule_bar_height, click_effect_default } from '../../hosts/bunch_of_defaults';
+import { bunch_of_settings } from '../../hosts/bunch_of_settings';
+import { webview } from '@kit.ArkWeb';
+import linysShowButton from '../../components/buttons/linysShowButton';
+import linysCapsuleButton from '../../components/buttons/linysCapsuleButton';
+import woofControlFrame from '../woofControlFrame';
+import linysTimeoutButton from '../../components/buttons/linysTimeoutButton';
+import linysSwitchWithText from '../../components/toggles/linysSwitchWithText';
+
+@CustomDialog
+struct woofAdsBlocker {
+  controller: CustomDialogController;
+  @StorageLink('bunch_of_settings') bunch_of_settings: bunch_of_settings = new bunch_of_settings(true);
+  @StorageLink('tablet_mode') tablet_mode: boolean = false;
+  @StorageLink('use_adblock') use_adblock: boolean = true;
+  @StorageLink('adblock_exceptions') adblock_exceptions: string[] = [];
+  @State add_exception_domain_edit: string = "";
+  @State showing_add_panel: boolean = false;
+  // Settings / Accessibility
+  @StorageLink('preferred_hand_left_or_right') preferred_hand_left_or_right: string = 'right';
+  // Manage
+  @State @Watch('on_select_change') selected: boolean[] = this.all_false(this.adblock_exceptions.length);
+  @State selecting: boolean = false;
+  @State selected_number: number = 0;
+  @State delete_confirm: number = 0;
+  // alignRules
+  alignRules_head_non_tablet: AlignRuleOption = {
+    top: { anchor: "__container__", align: VerticalAlign.Top },
+    left: { anchor: "__container__", align: HorizontalAlign.Start }
+  };
+  alignRules_head_tablet: AlignRuleOption = {
+    top: { anchor: "__container__", align: VerticalAlign.Top },
+    bottom: { anchor: "controls_ads_blocker", align: VerticalAlign.Top },
+    left: { anchor: "__container__", align: HorizontalAlign.Start },
+  };
+  alignRules_foot: AlignRuleOption = {
+    bottom: { anchor: "__container__", align: VerticalAlign.Bottom },
+    left: { anchor: "__container__", align: HorizontalAlign.Start }
+  };
+  alignRules_body_tablet: AlignRuleOption = {
+    top: { anchor: "__container__", align: VerticalAlign.Top },
+    right: { anchor: "__container__", align: HorizontalAlign.End }
+  };
+  alignRules_body_non_tablet: AlignRuleOption = {
+    top: { anchor: "title_ads_blocker", align: VerticalAlign.Bottom },
+    bottom: { anchor: "controls_ads_blocker", align: VerticalAlign.Top },
+    right: { anchor: "__container__", align: HorizontalAlign.End }
+  };
+  // Colors
+  @StorageProp('color_current_primary') color_current_primary: ResourceColor = $r('app.color.start_window_background');
+  @StorageProp('color_current_secondary') color_current_secondary: ResourceColor = $r('app.color.block_color');
+  @StorageProp('color_current_font') color_current_font: ResourceColor = $r('app.color.font_color_title');
+  // Gateways
+  @StorageLink('universal_fail_prompt_desc_gateway') uni_fail_prompt_gateway: ResourceStr = "";
+
+  build() {
+    woofControlFrame({
+      title: $r('app.string.Settings_experience_manage_adblock'),
+      controller: this.controller
+    }) {
+      RelativeContainer() {
+        Scroll() {
+          Column({ space: 10 }) {
+            linysText({
+              text: $r('app.string.Ads_block_desc_1'),
+              max_lines: 6
+            })
+              .width("100%")
+            linysText({
+              text: $r('app.string.Ads_block_desc_2'),
+              max_lines: 6
+            })
+              .width("100%")
+            linysText({
+              text: $r('app.string.Ads_block_desc_3'),
+              max_lines: 6
+            })
+              .width("100%")
+            linysText({
+              text: $r('app.string.Have_a_Nice_Surf'),
+              max_lines: 2
+            })
+              .width("100%")
+              .opacity(0.9)
+          } // Title
+          .width("100%")
+        }
+        .align(Alignment.TopStart)
+        .edgeEffect(EdgeEffect.Spring)
+        .padding({ bottom: 10, right: 10 })
+        .width(this.tablet_mode ? "40%" : "100%")
+        .alignRules(this.tablet_mode ? this.alignRules_head_tablet : this.alignRules_head_non_tablet)
+        .constraintSize(this.tablet_mode ? {} : { maxHeight: '35%' })
+        .animation(animation_default())
+        .id("title_ads_blocker")
+
+        Column({ space: 10 }) {
+          linysText({
+            text: $r('app.string.Ads_block_dont_block_sites'),
+            max_lines: 3
+          })
+            .padding({ top: this.tablet_mode ? 0 : 10 })
+            .width("100%")
+            .opacity(!this.use_adblock ? 0.5 : 1)
+            .animation(animation_default())
+
+          Scroll() {
+            Column() {
+              ForEach(this.adblock_exceptions, (item: string, index: number) => {
+                Domain({
+                  domain: item,
+                  index: index,
+                  selected: this.selected,
+                  selected_number: this.selected_number,
+                  selecting: this.selecting
+                })
+                  .width("100%")
+              })
+              if (this.adblock_exceptions.length == 0) {
+                linysTextTitle({
+                  text: "当前数量为空"
+                })
+                  .margin(30)
+                  .opacity(0.7)
+                  .animation(animation_default())
+              }
+            }
+            .width("100%")
+          } // domains display list
+          .scrollable(ScrollDirection.Vertical)
+          .edgeEffect(EdgeEffect.Spring)
+          .align(Alignment.Top)
+          .borderRadius(10)
+          .backgroundColor(this.color_current_secondary)
+          .width("100%")
+          .layoutWeight(1)
+          .opacity(!this.use_adblock ? 0.5 : 1)
+          .animation(animation_default())
+
+          linysSwitchWithText({
+            text: $r('app.string.Settings_experience_use_adblock'),
+            toggle_state: this.use_adblock,
+            onExecution: () => {
+              this.bunch_of_settings.set('use_adblock', this.use_adblock);
+            }
+          }) // Reverse settings menu
+
+        } // Whitelist
+        .width(this.tablet_mode ? "60%" : "100%")
+        .height(this.tablet_mode ? "100%" : undefined)
+        .alignRules(this.tablet_mode ? this.alignRules_body_tablet : this.alignRules_body_non_tablet)
+        .animation(animation_default())
+
+        Column() {
+          linysShowButton({
+            show: this.showing_add_panel,
+            symbol_glyph_target: "sys.symbol.plus",
+            text: $r('app.string.Ads_block_add_exception_domain')
+          })// Add Button
+            .onClick(() => {
+              this.showing_add_panel = !this.showing_add_panel;
+            })
+
+          Scroll() {
+            Column({ space: 8 }) {
+              TextInput({ text: this.add_exception_domain_edit })// Input domain
+                .onChange((value) => {
+                  this.add_exception_domain_edit = value;
+                })
+                .fontWeight(FontWeight.Regular)
+                .fontColor(this.color_current_font)
+                .caretColor(this.color_current_font)
+                .selectedBackgroundColor(this.color_current_font)
+                .width("100%")
+                .onSubmit(() => {
+                  this.add_exception(this.add_exception_domain_edit);
+                  this.add_exception_domain_edit = "";
+                  this.showing_add_panel = false;
+                })
+                .height(capsule_bar_height())
+                .animation(animation_default())
+
+              linysCapsuleButton({ text: "  󰀓  " })
+                .animation(animation_default())
+                .onClick(() => {
+                  this.add_exception(this.add_exception_domain_edit);
+                  this.add_exception_domain_edit = "";
+                  this.showing_add_panel = false;
+                })
+            }
+            .padding({ top: 10 })
+            .alignItems(this.preferred_hand_left_or_right == 'right' ? HorizontalAlign.End : HorizontalAlign.Start)
+            .animation(animation_default())
+            .width("100%")
+          } // Add
+          .scrollBar(BarState.Off)
+          .width("100%")
+          .height(this.showing_add_panel ? 90 : 0)
+          .animation(animation_default())
+
+          Scroll() {
+            Column({ space: 10 }) {
+              Row() {
+                linysTextTitle({ text: $r("app.string.Settings_edit_selecting") })
+                linysTextTitle({ text: " " + this.selected_number.toString() + " " })
+                linysTextTitle({ text: $r("app.string.Settings_edit_selecting_items") })
+              }
+
+              Row() {
+                linysSymbol({ symbol_glyph_target: 'sys.symbol.list_checkmask' })
+                  .onClick(() => {
+                    this.select_all();
+                  })
+                Blank()
+                linysTimeoutButton({
+                  dialogText: "确定是否删除选中内容",
+                  text: "  󰀁  ",
+                  onExecution: () => {
+                    this.delete_selected();
+                  }
+                })
+              }
+              .width("100%")
+            }
+            .padding({ top: 10 })
+            .alignItems(HorizontalAlign.Start)
+            .width("100%")
+          } // Select controls
+          .scrollBar(BarState.Off)
+          .width("100%")
+          .height(this.selecting ? 80 : 0)
+          .animation(animation_default())
+        } // Controls
+        .padding({ top: 10, right: this.tablet_mode ? 10 : 0 })
+        .alignItems(this.preferred_hand_left_or_right == 'right' ? HorizontalAlign.End : HorizontalAlign.Start)
+        .alignRules(this.alignRules_foot)
+        .opacity(!this.use_adblock ? 0.5 : 1)
+        .animation(animation_default())
+        .width(this.tablet_mode ? "40%" : "100%")
+        .id("controls_ads_blocker")
+        .onAppear(() => {
+          setInterval(() => {
+            if (this.delete_confirm > 0) {
+              this.delete_confirm -= 1;
+            }
+            // Reset delete confirm
+          }, 10)
+        })
+      }.layoutWeight(1)
+    }
+  }
+
+  all_false(length: number) {
+    let result: boolean[] = [];
+    for (let index = 0; index < length; index++) {
+      result.push(false);
+    }
+    return result;
+  }
+
+  on_select_change() {
+    if (this.selected.includes(true)) {
+      this.selecting = true;
+    } else {
+      this.selecting = false;
+    }
+    this.delete_confirm = 0;
+  }
+
+  select_all() {
+    let new_selected: boolean[] = [];
+    if (this.selected.includes(false)) {
+      for (let index = 0; index < this.selected.length; index++) {
+        new_selected.push(true);
+      }
+      this.selected_number = this.selected.length;
+    } else {
+      for (let index = 0; index < this.selected.length; index++) {
+        new_selected.push(false);
+      }
+      this.selected_number = 0;
+    }
+    this.selected = new_selected;
+  }
+
+  add_exception(domain: string) {
+    if (domain == '') {
+      this.uni_fail_prompt_gateway = $r('app.string.Fail_desc_no_content');
+      return;
+    }
+    webview.AdsBlockManager.addAdsBlockDisallowedList([domain]);
+    if (this.adblock_exceptions.includes(domain)) {
+      // don't add same domains
+      this.uni_fail_prompt_gateway = $r('app.string.Fail_desc_duplicate_domain');
+      return;
+    }
+    this.adblock_exceptions.push(domain);
+    this.bunch_of_settings.set('adblock_exceptions', this.adblock_exceptions.join("\n"))
+  }
+
+  delete_selected() {
+    let new_exceptions: string[] = [];
+    let delete_exceptions: string[] = [];
+    for (let index = 0; index < this.selected.length; index++) {
+      if (!this.selected[index]) {
+        new_exceptions.push(this.adblock_exceptions[index]);
+      } else {
+        delete_exceptions.push(this.adblock_exceptions[index]);
+      }
+    }
+    try {
+      webview.AdsBlockManager.removeAdsBlockDisallowedList(delete_exceptions);
+    } catch (e) {
+      console.error(e);
+    }
+    this.adblock_exceptions = new_exceptions;
+    this.delete_confirm = 0;
+    this.selecting = false;
+    this.selected_number = 0;
+    this.selected = this.all_false(this.adblock_exceptions.length);
+    this.bunch_of_settings.set('adblock_exceptions', this.adblock_exceptions.join("\n"))
+  }
+}
+
+export default woofAdsBlocker;
+
+@Component
+struct Domain {
+  @State domain: string = "huawei.com";
+  @Prop index: number;
+  @Link selected: boolean[];
+  @Link selected_number: number;
+  @Prop selecting: boolean;
+  // Color
+  @StorageProp('color_current_secondary') color_current_secondary: ResourceColor = $r('app.color.block_color');
+  @StorageProp('color_current_font') color_current_font: ResourceColor = $r('app.color.font_color_title');
+
+  build() {
+    Column() {
+      linysText({
+        color: this.selected[this.index] ? this.color_current_secondary : this.color_current_font,
+        text: this.domain,
+        font_weight: this.selected[this.index] ? FontWeight.Bold : FontWeight.Regular
+      })
+    }
+    .alignItems(HorizontalAlign.Start)
+    .width("100%")
+    .backgroundColor(this.selected[this.index] ? this.color_current_font : 'transparent')
+    .animation(animation_default())
+    .padding(5)
+    .clickEffect(click_effect_default())
+    .padding({
+      left: 12,
+      right: 12,
+      top: 6,
+      bottom: 5
+    })
+    .onClick(() => {
+      if (this.selecting) {
+        // Select
+        this.select_unselect();
+      }
+    })
+    .width("100%")
+    .gesture(
+      LongPressGesture({ repeat: false })
+        .onAction(() => {
+          this.select_unselect();
+        })
+    )
+    .onMouse((e) => {
+      if (e.button == MouseButton.Right && e.action == MouseAction.Press) {
+        // Right click
+        this.select_unselect();
+      }
+    })
+  }
+
+  select_unselect() {
+    if (this.selected[this.index]) {
+      this.selected_number -= 1;
+    } else {
+      this.selected_number += 1;
+    }
+    this.selected[this.index] = !this.selected[this.index];
+  }
+}

+ 200 - 0
home/src/main/ets/dialogs/managers/woofCookies.ets

@@ -0,0 +1,200 @@
+import linysText from '../../components/texts/linysText';
+import linysTextTitle from '../../components/texts/linysTextTitle';
+import { animation_default, click_effect_default } from '../../hosts/bunch_of_defaults';
+import { webview } from '@kit.ArkWeb';
+import { match_domain } from '../../utils/url_tools';
+import woofControlFrame from '../woofControlFrame';
+import linysTimeoutButtonWithText from '../../components/buttons/linysTimeoutButtonWithText';
+
+@CustomDialog
+struct woofCookies {
+  controller: CustomDialogController;
+  // Environment
+  @StorageProp('tablet_mode') tablet_mode: boolean = false;
+  @StorageProp('current_url') current_url: string = "= ̄ω ̄=";
+  @State domain_url: string = this.get_domain_url(this.current_url);
+  // Interfaces
+  @State cookies_site: string[] = this.get_cookies_site();
+  // alignRules
+  alignRules_head_non_tablet: AlignRuleOption = {
+    top: { anchor: "__container__", align: VerticalAlign.Top },
+    left: { anchor: "__container__", align: HorizontalAlign.Start },
+  };
+  alignRules_head_tablet: AlignRuleOption = {
+    top: { anchor: "__container__", align: VerticalAlign.Top },
+    bottom: { anchor: "controls_cookies", align: VerticalAlign.Top },
+    left: { anchor: "__container__", align: HorizontalAlign.Start },
+  };
+  alignRules_foot: AlignRuleOption = {
+    bottom: { anchor: "__container__", align: VerticalAlign.Bottom },
+    left: { anchor: "__container__", align: HorizontalAlign.Start }
+  };
+  alignRules_body_tablet: AlignRuleOption = {
+    top: { anchor: "__container__", align: VerticalAlign.Top },
+    right: { anchor: "__container__", align: HorizontalAlign.End }
+  };
+  alignRules_body_non_tablet: AlignRuleOption = {
+    top: { anchor: "title_cookies_manager", align: VerticalAlign.Bottom },
+    bottom: { anchor: "controls_cookies", align: VerticalAlign.Top },
+    right: { anchor: "__container__", align: HorizontalAlign.End }
+  };
+  // Colors
+  @StorageProp('color_current_primary') color_current_primary: ResourceColor = $r('app.color.start_window_background');
+  @StorageProp('color_current_secondary') color_current_secondary: ResourceColor = $r('app.color.block_color');
+  @StorageProp('color_current_font') color_current_font: ResourceColor = $r('app.color.font_color_title');
+
+  build() {
+    woofControlFrame({
+      title: $r('app.string.Settings_manage_cookies'),
+      controller: this.controller
+    }) {
+      RelativeContainer() {
+        Scroll() {
+          Column({ space: 10 }) {
+            linysText({
+              text: $r('app.string.Settings_manage_cookies_desc_1'),
+              max_lines: 10
+            })
+              .width("100%")
+            linysText({
+              text: $r('app.string.Settings_manage_cookies_desc_2'),
+              max_lines: 10
+            })
+              .width("100%")
+            linysText({
+              text: $r('app.string.Settings_manage_cookies_desc_3'),
+              max_lines: 10
+            })
+              .width("100%")
+          }
+          .width("100%")
+        } // Title
+        .align(Alignment.TopStart)
+        .edgeEffect(EdgeEffect.Spring)
+        .padding({ bottom: 10, right: 10 })
+        .width(this.tablet_mode ? "40%" : "100%")
+        .alignRules(this.tablet_mode ? this.alignRules_head_tablet : this.alignRules_head_non_tablet)
+        .animation(animation_default())
+        .constraintSize(this.tablet_mode ? {} : { maxHeight: '35%' })
+        .id("title_cookies_manager")
+
+        Column() {
+          linysText({ text: $r('app.string.Settings_manage_cookies_this_site'), max_lines: 3 })// Cookies on
+            .width("100%")
+            .padding({ top: this.tablet_mode ? 0 : 10, bottom: 5 })
+
+          linysText({ text: this.domain_url })// domain
+            .width("100%")
+            .padding({ bottom: 10 })
+
+          Scroll() {
+            Column() {
+              ForEach(this.cookies_site, (item: string, index: number) => {
+                Cookie({
+                  text: item,
+                  index: index,
+                })
+                  .width("100%")
+              })
+              if (this.cookies_site.length == 0) {
+                linysTextTitle({
+                  text: "当前数量为空"
+                })
+                  .margin(30)
+                  .opacity(0.7)
+                  .animation(animation_default())
+              }
+            }
+            .width("100%")
+          } // cookies display list
+          .scrollable(ScrollDirection.Vertical)
+          .edgeEffect(EdgeEffect.Spring)
+          .align(Alignment.Top)
+          .borderRadius(10)
+          .backgroundColor(this.color_current_secondary)
+          .width("100%")
+          .layoutWeight(1)
+        } // Cookies
+        .width(this.tablet_mode ? "60%" : "100%")
+        .height(this.tablet_mode ? "100%" : undefined)
+        .alignRules(this.tablet_mode ? this.alignRules_body_tablet : this.alignRules_body_non_tablet)
+        .animation(animation_default())
+
+        Column({ space: 10 }) {
+          linysText({ text: $r('app.string.Settings_manage_cookies_clear_all'), max_lines: 5 })
+            .width('100%')
+
+          linysTimeoutButtonWithText({
+            desc_text: '>︿<',
+            button_text: '  󰀁  ',
+            onExecution: () => {
+              this.clear_all_cookies();
+            }
+          }) // Delete
+
+        } // Controls
+        .margin({ top: 15 })
+        .padding(this.tablet_mode ? { right: 10 } : undefined)
+        .alignItems(HorizontalAlign.End)
+        .alignRules(this.alignRules_foot)
+        .animation(animation_default())
+        .width(this.tablet_mode ? "40%" : "100%")
+        .id("controls_cookies")
+      }.layoutWeight(1)
+    }
+  }
+
+  clear_all_cookies() {
+    webview.WebCookieManager.clearAllCookiesSync();
+    webview.WebCookieManager.saveCookieAsync();
+    this.refresh_site_cookies_list();
+  }
+
+  refresh_site_cookies_list() {
+    this.cookies_site = this.get_cookies_site();
+  }
+
+  get_cookies_site() {
+    let list = webview.WebCookieManager.fetchCookieSync(this.domain_url).split('; ');
+    if (list[list.length-1] == '') {
+      list = list.slice(0, list.length - 1);
+    }
+    return list;
+  }
+
+  get_domain_url(url: string) {
+    let match_domain_result = match_domain(url);
+    return match_domain_result[0] + '://' + match_domain_result[1];
+  }
+}
+
+export default woofCookies;
+
+@Component
+struct Cookie {
+  @State text: string = "qwq";
+  @Prop index: number;
+  // Color
+  @StorageProp('color_current_secondary') color_current_secondary: ResourceColor = $r('app.color.block_color');
+  @StorageProp('color_current_font') color_current_font: ResourceColor = $r('app.color.font_color_title');
+
+  build() {
+    Column() {
+      linysText({
+        text: this.text,
+        max_lines: 25
+      })
+    }
+    .padding({
+      left: 10,
+      right: 10,
+      top: 5,
+      bottom: 5
+    })
+    .alignItems(HorizontalAlign.Start)
+    .backgroundColor(this.color_current_secondary)
+    .animation(animation_default())
+    .clickEffect(click_effect_default())
+    .width("100%")
+  }
+}

+ 430 - 0
home/src/main/ets/dialogs/managers/woofGeneralManage.ets

@@ -0,0 +1,430 @@
+import linysSymbol from '../../components/texts/linysSymbol';
+import linysText from '../../components/texts/linysText';
+import linysTextTitle from '../../components/texts/linysTextTitle';
+import { animation_default, capsule_bar_height, click_effect_default } from '../../hosts/bunch_of_defaults';
+import { bunch_of_settings } from '../../hosts/bunch_of_settings';
+import { webview } from '@kit.ArkWeb';
+import linysShowButton from '../../components/buttons/linysShowButton';
+import linysCapsuleButton from '../../components/buttons/linysCapsuleButton';
+import { match_domain } from '../../utils/url_tools';
+import woofControlFrame from '../woofControlFrame';
+import linysTimeoutButton from '../../components/buttons/linysTimeoutButton';
+import linysSwitchWithText from '../../components/toggles/linysSwitchWithText';
+
+@CustomDialog
+struct woofGeneralManage {
+  controller: CustomDialogController;
+  // Environment and Hosts
+  @StorageLink('bunch_of_settings') bunch_of_settings: bunch_of_settings = new bunch_of_settings(true);
+  @StorageLink('tablet_mode') tablet_mode: boolean = false;
+  @StorageLink('current_url') current_url: string = "= ̄ω ̄=";
+  @State add_site_edit: string = "";
+  @State showing_add_panel: boolean = false;
+  // Generals
+  @Link general_switch: boolean;
+  @Link general_sites_list: string[];
+  @Link general_on_all_sites_switch: boolean;
+  @State general_descriptions: ResourceStr[] = [
+    $r('app.string.Settings_js_desc_1'),
+    $r('app.string.Settings_js_desc_2'),
+    $r('app.string.Settings_js_desc_3')
+  ];
+  @State general_tips: ResourceStr = $r('app.string.Settings_js_already_disabled');
+  @State general_title: ResourceStr = $r('app.string.Settings_js_manage');
+  @State general_switch_desc: ResourceStr = $r('app.string.Settings_js_disable_js');
+  @State general_subtitle_execute_on_these_sites: ResourceStr = $r('app.string.Settings_js_some_sites');
+  @State general_subtitle_execute_on_all_sites: ResourceStr = $r('app.string.Settings_js_all_sites');
+  @State general_switch_settings_id: string = 'disable_js';
+  @State general_sites_list_settings_id: string = 'disable_js_these_sites';
+  @State general_switch_all_sites_settings_id: string = 'disable_js_all_sites';
+  // Settings / Accessibility
+  @StorageLink('preferred_hand_left_or_right') preferred_hand_left_or_right: string = 'right';
+  // Manage
+  @State @Watch('on_select_change') selected: boolean[] = this.all_false(this.general_sites_list.length);
+  @State selecting: boolean = false;
+  @State selected_number: number = 0;
+  @State delete_confirm: number = 0;
+  // alignRules
+  alignRules_head_non_tablet: AlignRuleOption = {
+    top: { anchor: "__container__", align: VerticalAlign.Top },
+    left: { anchor: "__container__", align: HorizontalAlign.Start }
+  };
+  alignRules_head_tablet: AlignRuleOption = {
+    top: { anchor: "__container__", align: VerticalAlign.Top },
+    bottom: { anchor: "controls", align: VerticalAlign.Top },
+    left: { anchor: "__container__", align: HorizontalAlign.Start },
+  };
+  alignRules_foot: AlignRuleOption = {
+    bottom: { anchor: "__container__", align: VerticalAlign.Bottom },
+    left: { anchor: "__container__", align: HorizontalAlign.Start }
+  };
+  alignRules_body_tablet: AlignRuleOption = {
+    top: { anchor: "__container__", align: VerticalAlign.Top },
+    right: { anchor: "__container__", align: HorizontalAlign.End }
+  };
+  alignRules_body_non_tablet: AlignRuleOption = {
+    top: { anchor: "title", align: VerticalAlign.Bottom },
+    bottom: { anchor: "controls", align: VerticalAlign.Top },
+    right: { anchor: "__container__", align: HorizontalAlign.End }
+  };
+  // Colors
+  @StorageProp('color_current_primary') color_current_primary: ResourceColor = $r('app.color.start_window_background');
+  @StorageProp('color_current_secondary') color_current_secondary: ResourceColor = $r('app.color.block_color');
+  @StorageProp('color_current_font') color_current_font: ResourceColor = $r('app.color.font_color_title');
+  // Gateways
+  @StorageLink('universal_fail_prompt_desc_gateway') uni_fail_prompt_gateway: ResourceStr = "";
+
+  build() {
+    woofControlFrame({
+      title: this.general_title,
+      controller: this.controller
+    }) {
+      RelativeContainer() {
+        Scroll() {
+          Column({ space: 10 }) {
+            ForEach(this.general_descriptions, (desc: ResourceStr, _index: number) => {
+              linysText({
+                text: desc,
+                max_lines: 10
+              })
+                .width("100%")
+            })
+
+            linysText({
+              text: this.general_tips,
+              max_lines: 3
+            })
+              .width("100%")
+              .visibility(this.match_this_domain(this.current_url) ? Visibility.None : Visibility.Visible)
+              .animation(animation_default())
+
+            linysText({
+              text: $r('app.string.Have_a_Nice_Surf'),
+              max_lines: 2
+            })
+              .width("100%")
+              .opacity(0.9)
+
+          } // Title
+          .width("100%")
+        }
+        .align(Alignment.TopStart)
+        .edgeEffect(EdgeEffect.Spring)
+        .padding({ bottom: 10, right: 10 })
+        .width(this.tablet_mode ? "40%" : "100%")
+        .constraintSize(this.tablet_mode ? {} : { maxHeight: '35%' })
+        .alignRules(this.tablet_mode ? this.alignRules_head_tablet : this.alignRules_head_non_tablet)
+        .animation(animation_default())
+        .id("title")
+
+        Column({ space: 10 }) {
+          linysText({
+            text: this.general_subtitle_execute_on_these_sites,
+            max_lines: 3
+          })
+            .padding({ top: this.tablet_mode ? 0 : 10 })
+            .width("100%")
+            .opacity(this.general_on_all_sites_switch || !this.general_switch ? 0.5 : 1)
+            .animation(animation_default())
+
+          Scroll() {
+            Column() {
+              ForEach(this.general_sites_list, (item: string, index: number) => {
+                Element({
+                  domain: item,
+                  index: index,
+                  selected: this.selected,
+                  selected_number: this.selected_number,
+                  selecting: this.selecting
+                })
+                  .width("100%")
+              })
+              if (this.general_sites_list.length == 0) {
+                linysTextTitle({
+                  text: "当前数量为空"
+                })
+                  .margin(30)
+                  .opacity(0.7)
+                  .animation(animation_default())
+              }
+            }
+            .width("100%")
+          } // domains display list
+          .scrollable(ScrollDirection.Vertical)
+          .edgeEffect(EdgeEffect.Spring)
+          .align(Alignment.Top)
+          .borderRadius(10)
+          .backgroundColor(this.color_current_secondary)
+          .width("100%")
+          .layoutWeight(1)
+          .opacity(this.general_on_all_sites_switch || !this.general_switch ? 0.5 : 1)
+          .animation(animation_default())
+
+          linysSwitchWithText({
+            text: this.general_subtitle_execute_on_all_sites,
+            toggle_state: this.general_on_all_sites_switch,
+            onExecution: () => {
+              this.bunch_of_settings.set(this.general_switch_all_sites_settings_id, this.general_on_all_sites_switch);
+            }
+          })// Toggle on all sites
+            .opacity(!this.general_switch ? 0.5 : 1)
+            .animation(animation_default())
+
+          linysSwitchWithText({
+            text: this.general_switch_desc,
+            toggle_state: this.general_switch,
+            onExecution: () => {
+              this.bunch_of_settings.set(this.general_switch_settings_id, this.general_switch);
+            }
+          }) // Feature enable
+
+        } // Whitelist
+        .width(this.tablet_mode ? "60%" : "100%")
+        .height(this.tablet_mode ? "100%" : undefined)
+        .alignRules(this.tablet_mode ? this.alignRules_body_tablet : this.alignRules_body_non_tablet)
+        .animation(animation_default())
+
+        Column() {
+          linysShowButton({
+            show: this.showing_add_panel,
+            symbol_glyph_target: "sys.symbol.plus",
+            text: $r("app.string.Settings_woof_add_a_domain")
+          })// Add Button
+            .onClick(() => {
+              this.showing_add_panel = !this.showing_add_panel;
+            })
+
+          Scroll() {
+            Column({ space: 8 }) {
+              TextInput({ text: this.add_site_edit })// Input domain
+                .onChange((value) => {
+                  this.add_site_edit = value;
+                })
+                .fontWeight(FontWeight.Regular)
+                .fontColor(this.color_current_font)
+                .caretColor(this.color_current_font)
+                .selectedBackgroundColor(this.color_current_font)
+                .width("100%")
+                .onSubmit(() => {
+                  this.add_item(this.add_site_edit);
+                  this.add_site_edit = "";
+                  this.showing_add_panel = false;
+                })
+                .height(capsule_bar_height())
+                .animation(animation_default())
+
+              linysCapsuleButton({ text: "  󰀓  " })
+                .animation(animation_default())
+                .onClick(() => {
+                  this.add_item(this.add_site_edit);
+                  this.add_site_edit = "";
+                  this.showing_add_panel = false;
+                })
+            }
+            .padding({ top: 10 })
+            .alignItems(this.preferred_hand_left_or_right == 'right' ? HorizontalAlign.End : HorizontalAlign.Start)
+            .animation(animation_default())
+            .width("100%")
+          } // Add
+          .scrollBar(BarState.Off)
+          .width("100%")
+          .height(this.showing_add_panel ? 90 : 0)
+          .animation(animation_default())
+
+          Scroll() {
+            Column({ space: 10 }) {
+              Row() {
+                linysTextTitle({ text: $r("app.string.Settings_edit_selecting") })
+                linysTextTitle({ text: " " + this.selected_number.toString() + " " })
+                linysTextTitle({ text: $r("app.string.Settings_edit_selecting_items") })
+              }
+
+              Row() {
+                linysSymbol({ symbol_glyph_target: 'sys.symbol.list_checkmask' })
+                  .onClick(() => {
+                    this.select_all();
+                  })
+                Blank()
+                linysTimeoutButton({
+                  dialogText: "确定是否删除选中内容",
+                  text: "  󰀁  ",
+                  onExecution: () => {
+                    this.delete_selected();
+                  }
+                }) // Delete
+              }
+              .width("100%")
+            }
+            .padding({ top: 10 })
+            .alignItems(HorizontalAlign.Start)
+            .width("100%")
+          } // Select controls
+          .scrollBar(BarState.Off)
+          .width("100%")
+          .height(this.selecting ? 80 : 0)
+          .animation(animation_default())
+        } // Controls
+        .padding({ top: 10, right: this.tablet_mode ? 10 : 0 })
+        .alignItems(this.preferred_hand_left_or_right == 'right' ? HorizontalAlign.End : HorizontalAlign.Start)
+        .alignRules(this.alignRules_foot)
+        .opacity(this.general_on_all_sites_switch || !this.general_switch ? 0.5 : 1)
+        .animation(animation_default())
+        .width(this.tablet_mode ? "40%" : "100%")
+        .id("controls")
+        .onAppear(() => {
+          setInterval(() => {
+            if (this.delete_confirm > 0) {
+              this.delete_confirm -= 1;
+            }
+            // Reset delete confirm
+          }, 10)
+        })
+      }.layoutWeight(1)
+    }
+  }
+
+  all_false(length: number) {
+    let result: boolean[] = [];
+    for (let index = 0; index < length; index++) {
+      result.push(false);
+    }
+    return result;
+  }
+
+  on_select_change() {
+    if (this.selected.includes(true)) {
+      this.selecting = true;
+    } else {
+      this.selecting = false;
+    }
+    this.delete_confirm = 0;
+  }
+
+  select_all() {
+    let new_selected: boolean[] = [];
+    if (this.selected.includes(false)) {
+      for (let index = 0; index < this.selected.length; index++) {
+        new_selected.push(true);
+      }
+      this.selected_number = this.selected.length;
+    } else {
+      for (let index = 0; index < this.selected.length; index++) {
+        new_selected.push(false);
+      }
+      this.selected_number = 0;
+    }
+    this.selected = new_selected;
+  }
+
+  add_item(domain: string) {
+    if (domain == '') {
+      this.uni_fail_prompt_gateway = $r('app.string.Fail_desc_no_content');
+      return;
+    }
+    webview.AdsBlockManager.addAdsBlockDisallowedList([domain]);
+    if (this.general_sites_list.includes(domain)) {
+      // don't add same domains
+      this.uni_fail_prompt_gateway = $r('app.string.Fail_desc_duplicate_domain');
+      return;
+    }
+    this.general_sites_list.push(domain);
+    this.bunch_of_settings.set(this.general_sites_list_settings_id, this.general_sites_list.join("\n"))
+  }
+
+  delete_selected() {
+    let new_exceptions: string[] = [];
+    let delete_exceptions: string[] = [];
+    for (let index = 0; index < this.selected.length; index++) {
+      if (!this.selected[index]) {
+        new_exceptions.push(this.general_sites_list[index]);
+      } else {
+        delete_exceptions.push(this.general_sites_list[index]);
+      }
+    }
+    this.general_sites_list = new_exceptions;
+    this.delete_confirm = 0;
+    this.selecting = false;
+    this.selected_number = 0;
+    this.selected = this.all_false(this.general_sites_list.length);
+    this.bunch_of_settings.set(this.general_sites_list_settings_id, this.general_sites_list.join("\n"))
+  }
+
+  match_this_domain(address: string) {
+    if (!this.general_switch) {
+      return true;
+    }
+    // Disable toggle ON
+    if (this.general_on_all_sites_switch) {
+      // Disable on all sites
+      return false;
+    }
+    // Disable on some sites
+    if (address && this.general_sites_list.includes(match_domain(address)[1])) {
+      return false;
+    }
+    return true;
+  }
+}
+
+export default woofGeneralManage;
+
+@Component
+struct Element {
+  @State domain: string = "huawei.com";
+  @Prop index: number;
+  @Link selected: boolean[];
+  @Link selected_number: number;
+  @Prop selecting: boolean;
+  // Color
+  @StorageProp('color_current_secondary') color_current_secondary: ResourceColor = $r('app.color.block_color');
+  @StorageProp('color_current_font') color_current_font: ResourceColor = $r('app.color.font_color_title');
+
+  build() {
+    Column() {
+      linysText({
+        color: this.selected[this.index] ? this.color_current_secondary : this.color_current_font,
+        text: this.domain,
+        font_weight: this.selected[this.index] ? FontWeight.Bold : FontWeight.Regular
+      })
+    }
+    .alignItems(HorizontalAlign.Start)
+    .width("100%")
+    .backgroundColor(this.selected[this.index] ? this.color_current_font : 'transparent')
+    .animation(animation_default())
+    .padding({
+      left: 10,
+      right: 10,
+      top: 5,
+      bottom: 5
+    })
+    .clickEffect(click_effect_default())
+    .onClick(() => {
+      if (this.selecting) {
+        // Select
+        this.select_unselect();
+      }
+    })
+    .width("100%")
+    .gesture(
+      LongPressGesture({ repeat: false })
+        .onAction(() => {
+          this.select_unselect();
+        })
+    )
+    .onMouse((e) => {
+      if (e.button == MouseButton.Right && e.action == MouseAction.Press) {
+        // Right click
+        this.select_unselect();
+      }
+    })
+  }
+
+  select_unselect() {
+    if (this.selected[this.index]) {
+      this.selected_number -= 1;
+    } else {
+      this.selected_number += 1;
+    }
+    this.selected[this.index] = !this.selected[this.index];
+  }
+}

+ 458 - 0
home/src/main/ets/dialogs/managers/woofHistory.ets

@@ -0,0 +1,458 @@
+import linysSymbol from '../../components/texts/linysSymbol';
+import linysText from '../../components/texts/linysText';
+import linysTimeoutButton from '../../components/buttons/linysTimeoutButton';
+import linysTextTitle from '../../components/texts/linysTextTitle';
+import { animation_default, click_effect_default } from '../../hosts/bunch_of_defaults';
+import { bunch_of_history, history_record } from '../../hosts/bunch_of_history';
+import woofControlFrame from '../woofControlFrame';
+import HistoryDataSource from '../../objects/HistoryDataSource';
+import HeaderSearch from '../../components/HeaderSearch';
+
+@CustomDialog
+struct woofHistory {
+  controller: CustomDialogController;
+  @StorageLink('bunch_of_history') bunch_of_history: bunch_of_history = new bunch_of_history(true);
+  @StorageLink('tablet_mode') tablet_mode: boolean = false;
+  @State scroll_area_height: number = 0;
+  @Link showing_settings: boolean;
+  // Interactions
+  @State viewing_year: number = this.bunch_of_history.get_current_year();
+  @State viewing_month: number = this.bunch_of_history.get_current_month();
+  // Dates
+  @State available_months: number[][] = bunch_of_history.get_history_months();
+  @State history_list: history_record[] = this.bunch_of_history.get_history_this_month();
+  @State filter_list: history_record[] = this.bunch_of_history.get_history_this_month();
+  // Manage
+  @State @Watch('on_select_change') selected: boolean[] = this.all_false(this.history_list.length);
+  @State selecting: boolean = false;
+  @State selected_number: number = 0;
+  // alignRules
+  alignRules_head_non_tablet: AlignRuleOption = {
+    top: { anchor: "__container__", align: VerticalAlign.Top },
+    left: { anchor: "__container__", align: HorizontalAlign.Start },
+  };
+  alignRules_head_tablet: AlignRuleOption = {
+    top: { anchor: "__container__", align: VerticalAlign.Top },
+    bottom: { anchor: "controls_history", align: VerticalAlign.Top },
+    left: { anchor: "__container__", align: HorizontalAlign.Start },
+  };
+  alignRules_foot: AlignRuleOption = {
+    bottom: { anchor: "__container__", align: VerticalAlign.Bottom },
+    left: { anchor: "__container__", align: HorizontalAlign.Start }
+  };
+  alignRules_body_tablet: AlignRuleOption = {
+    top: { anchor: "__container__", align: VerticalAlign.Top },
+    right: { anchor: "__container__", align: HorizontalAlign.End }
+  };
+  alignRules_body_non_tablet: AlignRuleOption = {
+    top: { anchor: "title_history", align: VerticalAlign.Bottom },
+    bottom: { anchor: "controls_history", align: VerticalAlign.Top },
+    right: { anchor: "__container__", align: HorizontalAlign.End }
+  };
+  // Colors
+  @StorageProp('color_current_primary') color_current_primary: ResourceColor = $r('app.color.start_window_background');
+  @StorageProp('color_current_secondary') color_current_secondary: ResourceColor = $r('app.color.block_color');
+  @StorageProp('color_current_font') color_current_font: ResourceColor = $r('app.color.font_color_title');
+  scroller: Scroller = new Scroller();
+  // Data
+  private data: HistoryDataSource = new HistoryDataSource(this.bunch_of_history.get_history_this_month());
+
+  build() {
+    woofControlFrame({
+      title: $r('app.string.Settings_experience_history_view'),
+      controller: this.controller
+    }) {
+      RelativeContainer() {
+        Scroll() {
+          Column({ space: 15 }) {
+            linysText({
+              text: $r('app.string.History_desc_1'),
+              max_lines: 6
+            })
+              .width("100%")
+              .opacity(0.9)
+            linysText({
+              text: $r('app.string.History_desc_2'),
+              max_lines: 6
+            })
+              .width("100%")
+              .opacity(0.9)
+            linysText({
+              text: $r('app.string.Have_a_Nice_Surf'),
+              max_lines: 2
+            })
+              .width("100%")
+              .opacity(0.9)
+          } // Title
+          .width("100%")
+        } // Title and descriptions
+        .scrollBar(BarState.Off)
+        .align(Alignment.TopStart)
+        .edgeEffect(EdgeEffect.Spring)
+        .padding({ bottom: 10, right: this.tablet_mode ? 10 : 0 })
+        .width(this.tablet_mode ? "35%" : "100%")
+        .alignRules(this.tablet_mode ? this.alignRules_head_tablet : this.alignRules_head_non_tablet)
+        .animation(animation_default())
+        .id("title_history")
+
+        Column({ space: 10 }) {
+
+          Row() {
+            linysText({
+              text: $r('app.string.Settings_experience_history_date_format'),
+              font_weight: FontWeight.Bold
+            })
+              .opacity(0.7)
+            linysText({ text: " (" + this.filter_list.length.toString() + ")" })
+              .opacity(0.7)
+
+            HeaderSearch({
+              placeHolder: '历史记录',
+              search: (key) => {
+                this.loadData(key)
+              }
+            })
+          } // Time elapse description
+          .width("100%")
+
+          Scroll() {
+            List({ scroller: this.scroller }) {
+              LazyForEach(this.data, (_record_item: history_record, key: number) => {
+                ListItem() {
+                  // linysText({ text: key.toString() })
+                  if (this.filter_list[this.filter_list.length - 1 - key]) {
+                    meowRelation({
+                      index: this.filter_list.length - 1 - key,
+                      show: this.showing_settings,
+                      selected: this.selected,
+                      selected_number: this.selected_number,
+                      selecting: this.selecting,
+                      label_link: this.filter_list[this.filter_list.length - 1 - key],
+                      controller: this.controller,
+                    })
+                      .width("100%")
+                  }
+                }
+              }, (item: history_record) => item.accessed_time.toString() + this.filter_list.length.toString()) // Items
+
+              if (this.filter_list.length == 0) {
+                linysTextTitle({
+                  text: "当前数量为空"
+                })
+                  .margin(30)
+                  .opacity(0.7)
+                  .animation(animation_default())
+              }
+            }
+            .width("100%")
+            .height("100%")
+          } // Main history list
+          .padding({ bottom: 5 })
+          .scrollable(ScrollDirection.Vertical)
+          .edgeEffect(EdgeEffect.Spring)
+          .onAreaChange((_o, n) => {
+            this.scroll_area_height = n.height as number;
+          })
+          .align(Alignment.Top)
+          .layoutWeight(1)
+          .width("100%")
+          .borderRadius(13.5)
+          .backgroundColor(this.color_current_secondary)
+
+          Scroll() {
+            Row({ space: 5 }) {
+              ForEach(this.available_months, (item: number[], _key: number) => {
+                month_button({
+                  year: item[0],
+                  month: item[1],
+                  viewing_year: this.viewing_year,
+                  viewing_month: this.viewing_month,
+                })
+                  .onClick(() => {
+                    // MEOW
+                    this.bunch_of_history.open_month_from_disk_sync(item[0], item[1]);
+                    this.refresh_current_history_list();
+                    this.viewing_year = item[0];
+                    this.viewing_month = item[1];
+                    this.scroller.scrollToIndex(0, false);
+                    this.data.setData(this.bunch_of_history.get_history_this_month());
+                  })
+              }) // Items
+            }
+          } // Available months
+          .scrollable(ScrollDirection.Horizontal)
+          .edgeEffect(EdgeEffect.Spring)
+          .onAreaChange((_o, n) => {
+            this.scroll_area_height = n.height as number;
+          })
+          .padding(5)
+          .align(Alignment.Start)
+          .width("100%")
+          .borderRadius(13.5)
+          .backgroundColor(this.color_current_secondary)
+
+        } // List
+        .width(this.tablet_mode ? "65%" : "100%")
+        .alignRules(this.tablet_mode ? this.alignRules_body_tablet : this.alignRules_body_non_tablet)
+        .animation(animation_default())
+
+        Column() {
+          Scroll() {
+            Column({ space: 10 }) {
+              Row() {
+                linysTextTitle({ text: $r("app.string.Settings_edit_selecting") })
+                linysTextTitle({ text: " " + this.selected_number.toString() + " " })
+                linysTextTitle({ text: $r("app.string.Settings_edit_selecting_items") })
+              } // Selecting ... item(s)
+              Row() {
+                linysSymbol({ symbol_glyph_target: 'sys.symbol.list_checkmask' })
+                  .onClick(() => {
+                    this.select_all();
+                  })
+                Blank()
+                linysTimeoutButton({
+                  text: "  󰀁  ",
+                  onExecution: () => {
+                    this.delete_selected();
+                  }
+                }) // Delete
+              } // Buttons
+              .width("100%")
+            }
+            .padding({ top: 10, right: this.tablet_mode ? 10 : 0, bottom: 5 })
+            .alignItems(HorizontalAlign.Start)
+            .width("100%")
+            .animation(animation_default())
+          } // Select controls
+          .scrollBar(BarState.Off)
+          .width("100%")
+          .height(this.selecting ? 80 : 0)
+          .animation(animation_default())
+        } // Controls
+        .alignItems(HorizontalAlign.End)
+        .id("controls_history")
+        .alignRules(this.alignRules_foot)
+        .animation(animation_default())
+        .width(this.tablet_mode ? "35%" : "100%")
+      }
+      .layoutWeight(1)
+    }
+  }
+
+  loadData(text: string){
+    this.filter_list = this.history_list.filter(history => {
+      return history.label.includes(text) || history.link.includes(text)
+    })
+    this.data.setData(this.filter_list);
+  }
+
+  // Utils
+
+  /**
+   * Constructs and returns a boolean list of specified length consists of false.
+   * @param length A number, the length of the list.
+   * @returns A boolean[] array, all elements are false.
+   * */
+  all_false(length: number) {
+    let result: boolean[] = [];
+    for (let index = 0; index < length; index++) {
+      result.push(false);
+    }
+    return result;
+  }
+
+  // Select Events and operations
+
+  /**
+   * Triggered when selecting or unselecting a history.
+   *
+   * Sets this.selecting to true, if is selecting something.
+   *
+   * Otherwise set it to false.
+   *
+   * @abstract Also resets this.delete_confirm.
+   * */
+  on_select_change() {
+    if (this.selected.includes(true)) {
+      this.selecting = true;
+    } else {
+      this.selecting = false;
+    }
+  }
+
+  /**
+   * Refreshes this.history_list according to the time range selected.
+   * */
+  refresh_current_history_list() {
+    this.history_list = this.bunch_of_history.get_history_this_month();
+    this.selected_number = 0;
+    this.selected = this.all_false(this.history_list.length);
+  }
+
+  /**
+   * One click to select or unselect all history records.
+   *
+   * @abstract If there is still items unselected, then selects all.
+   * If all items are already selected, then unselect all.
+   * */
+  select_all() {
+    let new_selected: boolean[] = [];
+    if (this.selected.includes(false)) {
+      for (let index = 0; index < this.selected.length; index++) {
+        new_selected.push(true);
+      }
+      this.selected_number = this.selected.length;
+    } else {
+      for (let index = 0; index < this.selected.length; index++) {
+        new_selected.push(false);
+      }
+      this.selected_number = 0;
+    }
+    this.selected = new_selected;
+  }
+
+  /**
+   * Deletes all items selected, and refreshes the current history list according to time range specified.
+   *
+   * @abstract Also reconstructs the plain history cache and saves the operated history to sandbox storage.
+   * */
+  delete_selected() {
+    let indices: number[] = [];
+    for (let index = 0; index < this.selected.length; index++) {
+      if (this.selected[index]) {
+        indices.push(index);
+        // Notify LazyForEach
+        // this.data.notifyDataDelete(indices[index]);
+
+        // TODO: use better strategy to notify LazyForEach about a deletion.
+        this.data.setData(this.bunch_of_history.get_history_this_month());
+      }
+    }
+    this.bunch_of_history.remove_histories_at_indices(this.viewing_year, this.viewing_month, indices, true);
+    this.refresh_current_history_list();
+  }
+}
+
+export default woofHistory;
+
+@Component
+struct meowRelation {
+  // Base
+  controller: CustomDialogController | undefined;
+  @Prop index: number;
+  @Prop label_link: history_record;
+  @Link show: boolean;
+  @Link selected: boolean[];
+  @Link selected_number: number;
+  @Prop selecting: boolean;
+  // UI Effects
+  @Prop scroll_area_height: number;
+  @Prop human_time: string = new Date(this.label_link.accessed_time).toLocaleString();
+  // Gateways
+  @StorageLink('universal_new_tab_gateway') universal_new_tab_gateway: string = "";
+  // Colors
+  @StorageProp('color_current_primary') color_current_primary: ResourceColor = $r('app.color.start_window_background');
+  @StorageProp('color_current_secondary') color_current_secondary: ResourceColor = $r('app.color.block_color');
+  @StorageProp('color_current_font') color_current_font: ResourceColor = $r('app.color.font_color_title');
+
+  build() {
+    Column({ space: 2 }) {
+      linysText({
+        color: this.selected[this.index] ? this.color_current_secondary : this.color_current_font,
+        text: this.label_link.label.length == 0 ? " " : this.label_link.label,
+        font_weight: this.selected[this.index] ? FontWeight.Bold : FontWeight.Regular
+      })
+      linysText({
+        color: this.selected[this.index] ? this.color_current_secondary : this.color_current_font,
+        text: this.label_link.link,
+        max_lines: 2
+      })
+        .opacity(0.88) // Happy New Year!
+      linysText({
+        color: this.selected[this.index] ? this.color_current_secondary : this.color_current_font,
+        text: this.human_time
+      })
+        .opacity(0.7)
+    }
+    .padding({
+      left: 12,
+      right: 12,
+      top: 9,
+      bottom: 3
+    })
+    .alignItems(HorizontalAlign.Start)
+    .width("100%")
+    .backgroundColor(this.selected[this.index] ? this.color_current_font : this.color_current_secondary)
+    .animation(animation_default())
+    .onClick(() => {
+      if (this.selecting) {
+        // Select
+        this.select_unselect();
+        return;
+      }
+      this.universal_new_tab_gateway = this.label_link.link;
+      if (this.controller) {
+        this.show = false;
+        this.controller.close();
+      }
+    })
+    .clickEffect(click_effect_default())
+    .gesture(
+      LongPressGesture({ repeat: false })
+        .onAction(() => {
+          this.select_unselect();
+        })
+    )
+    .onMouse((e) => {
+      if (e.button == MouseButton.Right && e.action == MouseAction.Press) {
+        // Right click
+        this.select_unselect();
+      }
+    })
+  }
+
+  /**
+   * Selects or unselects myself, usually called when onClick.
+   * */
+  select_unselect() {
+    if (this.selected[this.index]) {
+      this.selected_number -= 1;
+    } else {
+      this.selected_number += 1;
+    }
+    this.selected[this.index] = !this.selected[this.index];
+  }
+}
+
+@Component
+struct month_button {
+  @Prop year: number;
+  @Prop month: number;
+  @Link viewing_year: number;
+  @Link viewing_month: number;
+  @StorageProp('color_current_primary') color_current_primary: ResourceColor = $r('app.color.start_window_background');
+  @StorageProp('color_current_font') color_current_font: ResourceColor = $r('app.color.font_color_title');
+
+  build() {
+    Column({ space: 0 }) {
+      linysText({
+        text: this.year.toString(),
+        font_weight: this.selected() ? FontWeight.Bold : FontWeight.Regular,
+      })
+      linysText({
+        text: this.month.toString(),
+        font_weight: this.selected() ? FontWeight.Bold : FontWeight.Regular,
+      })
+    }
+    .border({ color: this.selected() ? this.color_current_font : 'transparent', width: 2, radius: 10 })
+    .alignItems(HorizontalAlign.Start)
+    .clickEffect(click_effect_default())
+    .padding(8)
+    .backgroundColor(this.color_current_primary)
+    .animation(animation_default())
+  }
+
+  selected() {
+    return this.month == this.viewing_month && this.year == this.viewing_year;
+  }
+}

+ 149 - 0
home/src/main/ets/dialogs/prompts/woofPromptFail.ets

@@ -0,0 +1,149 @@
+import linysText from '../../components/texts/linysText';
+import linysTextTitle from '../../components/texts/linysTextTitle';
+import { animation_default, fontSize_Normal } from '../../hosts/bunch_of_defaults';
+import { LengthMetrics } from '@kit.ArkUI';
+import linysCapsuleButton from '../../components/buttons/linysCapsuleButton';
+
+@CustomDialog
+struct woofPromptFail {
+  controller: CustomDialogController;
+  @Prop desc: ResourceStr = 'Desc';
+  @State @Watch('on_meow_reversed') meow: boolean = false;
+  // Settings / Accessibility
+  @StorageLink('preferred_hand_left_or_right') preferred_hand_left_or_right: string = 'right';
+  // Colors
+  @StorageProp('color_current_primary') color_current_primary: ResourceColor = $r('app.color.start_window_background');
+  @StorageProp('color_current_secondary') color_current_secondary: ResourceColor = $r('app.color.block_color');
+  @StorageProp('color_current_font') color_current_font: ResourceColor = $r('app.color.font_color_title');
+
+  build() {
+    Column({ space: 10 }) {
+      linysTextTitle({
+        text: $r('app.string.Fail'),
+        max_lines: 1
+      })
+        .padding({ top: 5, bottom: 3 })
+        .width("100%")
+
+      linysText({
+        text: this.desc,
+        max_lines: 10
+      })
+        .width("100%")
+
+      linysText({
+        text: $r('app.string.Fail_tips_general_call_liny'),
+        max_lines: 10
+      })
+        .opacity(0.8)
+        .width("100%")
+
+      linysText({
+        text: 'ヽ( ̄ω ̄( ̄ω ̄〃)ゝ',
+        max_lines: 1
+      })
+        .width("100%")
+
+      goodByeLink({
+        meow: this.meow,
+        text: $r('app.string.About_bilibili'),
+        link: "https://space.bilibili.com/678438347",
+      })
+        .opacity(0.8)
+
+      goodByeLink({
+        meow: this.meow,
+        text: $r('app.string.About_GitHub'),
+        link: "https://github.com/awaLiny2333/LinysBrowser_NEXT",
+      })
+        .opacity(0.8)
+
+      goodByeLink({
+        meow: this.meow,
+        text: $r('app.string.About_Gitee'),
+        link: "https://gitee.com/awa_Liny/LinysBrowser_NEXT",
+      })
+        .opacity(0.8)
+
+      linysCapsuleButton({
+        text: $r('app.string.OK')
+      })
+        .onClick(() => {
+          if (this.controller) {
+            this.controller.close();
+          }
+        })
+    }
+    .padding(15)
+    .backgroundColor(this.color_current_primary)
+    .alignItems(this.preferred_hand_left_or_right == 'right' ? HorizontalAlign.End : HorizontalAlign.Start)
+    .animation(animation_default())
+    .justifyContent(FlexAlign.Center)
+    .width("100%")
+  }
+
+  on_meow_reversed() {
+    // Visit one of the support pages
+    if (this.controller) {
+      this.controller.close();
+    }
+  }
+}
+
+export default woofPromptFail;
+
+@Component
+struct goodByeLink {
+  // Environment
+  @StorageProp('tablet_mode') tablet_mode: boolean = false;
+  @StorageLink('showing_tabs') showing_tabs: boolean = false;
+  @StorageLink('showing_bookmarks') showing_bookmarks: boolean = false;
+  @StorageLink('showing_more_options') showing_more_options: boolean = false;
+  @StorageLink('showing_app_settings') showing_app_settings: boolean = false;
+  @StorageLink('showing_downloads') showing_downloads: boolean = false;
+  @StorageLink('showing_scratching_board') showing_scratching_board: boolean = false;
+  @StorageLink('tabs_style_non_tablet_mode') tabs_style_non_tablet_mode: string = "";
+  // Colors
+  @StorageProp('color_current_primary') color_current_primary: ResourceColor = $r('app.color.start_window_background');
+  @StorageProp('color_current_secondary') color_current_secondary: ResourceColor = $r('app.color.block_color');
+  @StorageProp('color_current_font') color_current_font: ResourceColor = $r('app.color.font_color_title');
+  // Internal stuff
+  @Prop text: ResourceStr = $r('app.string.HomeAbility_label');
+  @Prop color: ResourceColor;
+  @Prop max_lines: number = 1;
+  @Prop font_weight: FontWeight = FontWeight.Regular;
+  @State link: string = "huawei.com";
+  @StorageLink('universal_new_tab_gateway') universal_new_tab_gateway: string = "";
+  // This is for status noticing
+  @Link meow: boolean;
+
+  build() {
+    Text(this.text)
+      .fontColor(this.color || this.color_current_font)
+      .fontSize(fontSize_Normal())
+      .fontWeight(this.font_weight)
+      .textAlign(TextAlign.Start)
+      .maxLines(this.max_lines)
+      .lineSpacing(LengthMetrics.vp(4))
+      .textOverflow({ overflow: TextOverflow.Ellipsis })
+      .onClick(() => {
+        // Reverse meow to trigger something
+        this.meow = !this.meow;
+        // New tab!
+        this.universal_new_tab_gateway = this.link;
+        // Close all panels
+        if (!this.tablet_mode) {
+          // These are expected to remain open in tablet mode
+          if (this.tabs_style_non_tablet_mode == "vertical") {
+            this.showing_tabs = false;
+          }
+          this.showing_bookmarks = false;
+        }
+        this.showing_more_options = false;
+        this.showing_app_settings = false;
+        this.showing_downloads = false;
+        this.showing_scratching_board = false;
+      })
+      .width("100%")
+  }
+}

+ 54 - 0
home/src/main/ets/dialogs/prompts/woofPromptOK.ets

@@ -0,0 +1,54 @@
+import linysCapsuleButton from '../../components/buttons/linysCapsuleButton';
+import linysText from '../../components/texts/linysText';
+import { animation_default } from '../../hosts/bunch_of_defaults';
+import woofControlFrame from '../woofControlFrame';
+
+@CustomDialog
+struct woofPromptOK {
+  controller: CustomDialogController;
+  @Prop title: ResourceStr = $r('app.string.Settings_toolbox_import_settings')
+  @Prop desc: ResourceStr = $r('app.string.Settings_toolbox_import_settings_ok')
+  // Colors
+  @StorageProp('color_current_primary') color_current_primary: ResourceColor = $r('app.color.start_window_background');
+  @StorageProp('color_current_secondary') color_current_secondary: ResourceColor = $r('app.color.block_color');
+  @StorageProp('color_current_font') color_current_font: ResourceColor = $r('app.color.font_color_title');
+  // Status
+  @State content_height: number = 233;
+  // Settings / Accessibility
+  @StorageLink('preferred_hand_left_or_right') preferred_hand_left_or_right: string = 'right';
+
+  build() {
+    woofControlFrame({
+      title: this.title,
+      controller: this.controller
+    }) {
+      Scroll() {
+        Column({ space: 15 }) {
+          linysText({
+            text: this.desc,
+            max_lines: 5
+          })
+            .width("100%")
+          linysCapsuleButton({ text: $r('app.string.OK') })
+            .onClick(()=>{
+              if (this.controller) {
+                this.controller.close();
+              }
+            })
+        }
+        .alignItems(this.preferred_hand_left_or_right == 'right' ? HorizontalAlign.End : HorizontalAlign.Start)
+        .animation(animation_default())
+        .justifyContent(FlexAlign.Center)
+        .width("100%")
+        .onAreaChange((_o, n) => {
+          this.content_height = n.height as number;
+        })
+      }
+      .layoutWeight(1)
+      .edgeEffect(EdgeEffect.Spring)
+      .constraintSize({ maxHeight: this.content_height })
+    }
+  }
+}
+
+export default woofPromptOK;

+ 303 - 0
home/src/main/ets/dialogs/prompts/woofSelectBookmarksPath.ets

@@ -0,0 +1,303 @@
+import linysCapsuleButton from '../../components/buttons/linysCapsuleButton';
+import linysPathTree from '../../components/linysPathTree';
+import linysSymbol from '../../components/texts/linysSymbol';
+import linysTextTitle from '../../components/texts/linysTextTitle';
+import { bookmark, bunch_of_bookmarks, folder, unified_item } from '../../hosts/bunch_of_bookmarks';
+import { animation_default, click_effect_default, fontSize_Large, fontSize_Normal } from '../../hosts/bunch_of_defaults';
+
+@CustomDialog
+struct woofSelectBookmarksPath {
+  @Link select: string | undefined;
+  @Prop prompt_title: ResourceStr = ':O';
+  @State scroll_area_height: number = 0;
+  @StorageLink('bunch_of_bookmarks') bunch_of_bookmarks: bunch_of_bookmarks = new bunch_of_bookmarks("meow");
+  @State viewing_contents: unified_item[] = this.filter_only_folders(this.bunch_of_bookmarks.root.get_content());
+  @Prop @Watch('refresh_dir_content') looking_at_path: string;
+  @State showing_navigator: boolean = false;
+  // Settings / Accessibility
+  @StorageLink('preferred_hand_left_or_right') preferred_hand_left_or_right: string = 'right';
+  // Colors
+  @StorageProp('color_current_primary') color_current_primary: ResourceColor = $r('app.color.start_window_background');
+  @StorageProp('color_current_secondary') color_current_secondary: ResourceColor = $r('app.color.block_color');
+  @StorageProp('color_current_font') color_current_font: ResourceColor = $r('app.color.font_color_title');
+  controller: CustomDialogController;
+
+  aboutToAppear(): void {
+    this.refresh_dir_content();
+  }
+
+  build() {
+    Column({ space: 5 }) {
+      linysTextTitle({ text: this.prompt_title })
+        .width("100%")
+
+      Scroll() {
+        Column({ space: 5 }) {
+          ForEach(
+            this.viewing_contents,
+            (_unified_item: unified_item, key: number) => {
+              bookmarkItem({
+                my_index: key,
+                viewing_contents: this.viewing_contents, // For editing
+                looking_at_path: this.looking_at_path, // the path of now showing list (directory) on the panel
+                height_of_panel: this.scroll_area_height, // for animation params
+                my_parent_path: this.looking_at_path, // for get_my_path()
+              });
+            },
+          )
+
+          linysTextTitle({ text: "当前数量为空" })
+            .visibility(this.viewing_contents.length == 0 ? Visibility.Visible : Visibility.None)
+            .opacity(0.7)
+            .animation(animation_default())
+
+        }
+        .width("100%")
+      } // List of this directory
+      .direction(Direction.Rtl)
+      .align(Alignment.Bottom)
+      .edgeEffect(EdgeEffect.Spring)
+      .width("100%")
+      .layoutWeight(1)
+      .margin({ bottom: 10 })
+      .onAreaChange((_o, n) => {
+        this.scroll_area_height = n.height as number;
+        // Update height of scroll area
+        // So that bookmark buttons will know if they are out of visible area
+        // and reduce their appearance animation time.
+      })
+
+      Row({ space: 10 }) {
+        linysSymbol({ symbol_glyph_target: 'sys.symbol.arrow_left' })
+          .onClick(() => {
+            this.go_back();
+          })
+          .visibility(this.looking_at_path == "" ? Visibility.None : Visibility.Visible)
+          .animation(animation_default())
+
+        Blank()
+          .visibility(this.preferred_hand_left_or_right == 'right' ? Visibility.Visible : Visibility.None)
+          .animation(animation_default())
+
+        linysCapsuleButton({ text: "  󰀓  " })
+          .onClick(() => {
+            if (this.controller) {
+              // To force refresh
+              // I know this is stupid but it works
+              this.select = Date.now().toString();
+              // Forgive me
+              // TODO: find a better way to refresh shortcuts
+              this.select = this.looking_at_path;
+              console.log('[Meow][woofBookmarkPathSelector] Selected Path: \'' + this.select + '\'');
+              this.controller.close();
+            }
+          }) // Submit
+
+        linysCapsuleButton({ text: "  󰁖  " })
+          .onClick(() => {
+            if (this.controller) {
+              this.controller.close();
+            }
+          }) // Cancel
+      } // Buttons of submit and cancel
+      .width("100%")
+
+      Text(this.looking_at_path == "" ? this.showing_navigator ? $r('app.string.Bookmarks_root_path_expanded') : $r('app.string.Bookmarks_root_path') :
+        "@ " + this.looking_at_path + "/")
+        .fontColor(this.color_current_font)
+        .fontWeight(FontWeight.Bold)
+        .fontSize(fontSize_Normal())
+        .textAlign(TextAlign.End)
+        .margin({ top: 5 })
+        .opacity(0.7)
+        .animation(animation_default())
+        .clickEffect(click_effect_default())
+        .onClick(() => {
+          this.showing_navigator = !this.showing_navigator;
+        })
+
+      linysPathTree({
+        current_viewing_path: this.looking_at_path,
+        max_height_screen_percentage: 0.2,
+      })
+        .height(this.showing_navigator ? undefined : 0)
+        .animation(animation_default())
+    }
+    .alignItems(this.preferred_hand_left_or_right == 'right' ? HorizontalAlign.End : HorizontalAlign.Start)
+    .backgroundColor(this.color_current_primary)
+    .animation(animation_default())
+    .padding(15)
+    .height("80%")
+  }
+
+  refresh_dir_content() {
+    if (this.looking_at_path == "/") {
+      this.looking_at_path = '';
+    }
+    let got_folder: folder | undefined;
+    if (this.looking_at_path == "") {
+      got_folder = this.bunch_of_bookmarks.root;
+    } else {
+      got_folder = this.bunch_of_bookmarks.get_folder(this.looking_at_path);
+      console.log('[Meow][woofBookmarksMove] Got Folder ' + got_folder?.get_label());
+    }
+    if (got_folder !== undefined) {
+      this.viewing_contents = this.filter_only_folders(got_folder.get_content());
+      console.log('[Meow][woofBookmarksMove] Got Folder contents, length: ' + this.viewing_contents.length.toString());
+    }
+  }
+
+  filter_only_folders(raw: unified_item[]) {
+    let raw_content = raw;
+    let folder_only_content: unified_item[] = [];
+    for (let index = 0; index < raw_content.length; index++) {
+      if (raw_content[index].get_item().get_type() == "folder") {
+        folder_only_content.push(raw_content[index]);
+      }
+    }
+    return folder_only_content;
+  }
+
+  go_back() {
+    if (this.looking_at_path.includes("/")) {
+      let upper_path = this.looking_at_path.split("/");
+      upper_path.pop()
+      // Pop current folder class
+      this.looking_at_path = upper_path.join("/")
+    } else {
+      this.looking_at_path = "";
+      // If current folder is sitting in root folder
+      // or if current folder is exactly the root folder
+    }
+    // Since change of this.looking_at_path will automatically pull up refresh_root_content()
+    // There is no need to add this.refresh_root_content() here manually
+  }
+}
+
+export default woofSelectBookmarksPath;
+
+@Component
+struct bookmarkItem {
+  // Public stuffs
+  @StorageLink('bunch_of_bookmarks') bunch_of_bookmarks: bunch_of_bookmarks = new bunch_of_bookmarks("meow");
+  @Link looking_at_path: string;
+  @Link viewing_contents: unified_item[];
+  @Prop height_of_panel: number;
+  // Basic this properties
+  @Prop my_index: number = 0;
+  @Prop my_parent_path: string;
+  @State my_item: unified_item = this.viewing_contents[this.my_index];
+  @State my_type: string = this.my_item.get_item().get_type();
+  @State my_label: string = this.my_item.get_item().get_label();
+  @State my_link: string = this.my_type == "bookmark" ? (this.my_item.get_item() as bookmark).get_link() : "";
+  bookmark_height_default: number = 42;
+  // Animations statuses
+  @State pressing: boolean = false;
+  @State press_timing_ok: boolean = false;
+  @State show: Visibility = Visibility.Hidden;
+  @State offset_y: number = 50;
+  @State editing: boolean = false;
+  // Edit
+  @State edit_label: string = this.my_label;
+  @State edit_link: string = this.my_link;
+  @State delete_confirm: number = 0;
+  // Colors
+  @StorageProp('color_current_primary') color_current_primary: ResourceColor = $r('app.color.start_window_background');
+  @StorageProp('color_current_secondary') color_current_secondary: ResourceColor = $r('app.color.block_color');
+  @StorageProp('color_current_font') color_current_font: ResourceColor = $r('app.color.font_color_title');
+
+  // Edit inputs
+
+  build() {
+    Column() {
+      Row() {
+        Text(this.my_label)// Title
+          .fontColor(!this.pressing ? this.color_current_font : this.color_current_secondary)
+          .fontWeight(!this.pressing ? FontWeight.Regular : FontWeight.Bold)
+          .animation(animation_default())
+          .padding({ left: 2 })
+          .fontSize(fontSize_Normal())
+          .maxLines(1)
+          .textOverflow({ overflow: TextOverflow.Ellipsis })
+          .layoutWeight(1)
+        if (this.my_type == "folder") {
+          SymbolGlyph($r('sys.symbol.folder'))
+            .fontSize(fontSize_Large())
+            .fontWeight(!this.pressing ? FontWeight.Regular : FontWeight.Bold)
+            .fontColor([!this.pressing ? this.color_current_font : this.color_current_secondary])
+            .animation(animation_default())
+        } // Folder Icon
+        Scroll() {
+          SymbolGlyph($r('sys.symbol.square_and_pencil'))
+            .fontSize(fontSize_Large())
+            .fontColor([this.color_current_secondary])
+        } // Edit Icon
+        .scrollable(ScrollDirection.Horizontal)
+        .width(this.press_timing_ok ? 22 : 0)
+        .margin({ left: this.press_timing_ok ? 10 : 0 })
+        .animation(animation_default())
+
+      } // Bookmark button
+      .border({
+        radius: this.editing ? { topLeft: 10, topRight: 10 } : 10,
+        width: 2,
+        color: "transparent"
+      })
+      .backgroundColor(this.pressing ? this.color_current_font : this.color_current_secondary)
+      .animation(animation_default())
+      .padding(10)
+      .alignRules({
+        middle: { anchor: "__container__", align: HorizontalAlign.Center },
+        top: { anchor: "__container__", align: VerticalAlign.Top }
+      })
+      .onTouch((event) => {
+        if (event.type == TouchType.Up) {
+          this.pressing = false;
+          // If touch ends
+        } else {
+          this.pressing = true;
+          // If touching
+        }
+      })
+      .onClick(() => {
+        if (this.my_type == "folder") {
+          this.looking_at_path = this.get_my_path();
+          // Will automatically cause LinysBookmarks to update the UI list (@Watch)
+        }
+      })
+      .height(this.bookmark_height_default)
+    }
+    .width("100%")
+    .visibility(this.show)
+    .offset({ y: this.offset_y })
+    .animation(animation_default())
+    .onAppear(() => {
+      setTimeout(() => {
+        this.show = Visibility.Visible;
+        this.offset_y = 0;
+        // Animation of floating up
+      }, this.get_animation_timeout())
+    })
+  }
+
+  get_animation_timeout() {
+    let unit_interval = 40;
+    let load_length = this.viewing_contents.length;
+    if (load_length < 5) {
+      unit_interval = 60;
+    } else if (load_length < 10) {
+      unit_interval = 40;
+    } else {
+      unit_interval = 30;
+    }
+    return Math.min(this.my_index, this.height_of_panel / this.bookmark_height_default) * unit_interval;
+  }
+
+  get_my_path() {
+    if (this.my_parent_path == "") {
+      return this.my_label;
+    } else {
+      return this.my_parent_path + "/" + this.my_label;
+    }
+  }
+}

+ 281 - 0
home/src/main/ets/dialogs/prompts/woofSelectColor.ets

@@ -0,0 +1,281 @@
+import linysCapsuleButton from '../../components/buttons/linysCapsuleButton';
+import linysText from '../../components/texts/linysText';
+import { animation_default, capsule_bar_height } from '../../hosts/bunch_of_defaults';
+import { hex2hsv, hsv2hex, is_legal_hex } from '../../utils/color_tools';
+import woofControlFrame from '../woofControlFrame';
+
+@CustomDialog
+struct woofSelectColor {
+  controller: CustomDialogController;
+  @Link color: ResourceColor;
+  @Prop color_theme_id: string = 'color_light_font';
+  @State dark_mode_color: boolean = false;
+  // Settings / Accessibility
+  @StorageLink('preferred_hand_left_or_right') preferred_hand_left_or_right: string = 'right';
+  // Colors
+  @StorageProp('color_current_primary') color_current_primary: ResourceColor = $r('app.color.start_window_background');
+  @StorageProp('color_current_font') color_current_font: ResourceColor = $r('app.color.font_color_title');
+  @StorageProp('color_light_primary') color_light_primary: ResourceColor = $r('app.color.start_window_background');
+  @StorageProp('color_light_secondary') color_light_secondary: ResourceColor = $r('app.color.block_color');
+  @StorageProp('color_light_font') color_light_font: ResourceColor = $r('app.color.font_color_title');
+  @StorageProp('color_dark_primary') color_dark_primary: ResourceColor = $r('app.color.start_window_background');
+  @StorageProp('color_dark_secondary') color_dark_secondary: ResourceColor = $r('app.color.block_color');
+  @StorageProp('color_dark_font') color_dark_font: ResourceColor = $r('app.color.font_color_title');
+  // Edit
+  @State @Watch('on_color_change') color_hex_display: ResourceColor = this.color;
+  @State color_hsv: number[] = hex2hsv(this.color);
+  @StorageProp('tablet_mode') tablet_mode: boolean = false;
+  @State dialog_width: number = 0;
+  @State dialog_height: number = 0;
+  @State max_square_size: number = 0;
+  @State color_input: string = this.color as string;
+  @State focus_on_code_input: boolean = false;
+  // alignRules
+  alignRules_head_non_tablet: AlignRuleOption = {
+    top: { anchor: "__container__", align: VerticalAlign.Top },
+    left: { anchor: "__container__", align: HorizontalAlign.Start },
+  };
+  alignRules_head_tablet: AlignRuleOption = {
+    top: { anchor: "__container__", align: VerticalAlign.Top },
+    bottom: { anchor: "controls_color", align: VerticalAlign.Top },
+    left: { anchor: "__container__", align: HorizontalAlign.Start },
+  };
+  alignRules_foot: AlignRuleOption = {
+    bottom: { anchor: "__container__", align: VerticalAlign.Bottom },
+    left: { anchor: "__container__", align: HorizontalAlign.Start }
+  };
+  alignRules_body_tablet: AlignRuleOption = {
+    top: { anchor: "__container__", align: VerticalAlign.Top },
+    right: { anchor: "__container__", align: HorizontalAlign.End }
+  };
+  alignRules_body_non_tablet: AlignRuleOption = {
+    top: { anchor: "title_color", align: VerticalAlign.Bottom },
+    right: { anchor: "__container__", align: HorizontalAlign.End }
+  };
+
+  build() {
+    woofControlFrame({
+      title: $r('app.string.Appearance_color_panel'),
+      controller: this.controller
+    }) {
+      RelativeContainer() {
+        Column({ space: 10 }) {
+          linysText({
+            text: $r('app.string.Color_desc_'.concat(this.color_theme_id))
+          })
+            .width("100%")
+        } // Title
+        .width("100%")
+        .align(Alignment.TopStart)
+        .padding({ top: 5, bottom: 10, right: this.tablet_mode ? 12 : 0 })
+        .width(this.tablet_mode ? "40%" : "100%")
+        .alignRules(this.tablet_mode ? this.alignRules_head_tablet : this.alignRules_head_non_tablet)
+        .animation(animation_default())
+        .id("title_color")
+
+        Column() {
+          Scroll() {
+            Column({ space: 10 }) {
+              Column({ space: 10 }) {
+                linysText({
+                  text: $r('app.string.Example_text'),
+                  color: this.dark_mode_color ? this.color_dark_font : this.color_light_font,
+                  max_lines: 10,
+                })
+                linysText({
+                  text: $r('app.string.Example_text'),
+                  color: this.dark_mode_color ? this.color_dark_font : this.color_light_font,
+                  max_lines: 10,
+                })
+                linysText({
+                  text: $r('app.string.Example_text'),
+                  color: this.dark_mode_color ? this.color_dark_font : this.color_light_font,
+                  max_lines: 10,
+                })
+                linysCapsuleButton({
+                  text: "  󰀓  ",
+                  color_button: this.dark_mode_color ? this.color_dark_font : this.color_light_font,
+                  color_text: this.dark_mode_color ? this.color_dark_primary : this.color_light_primary
+                })
+                  .alignSelf(ItemAlign.End)
+              } // Example
+              .width("80%")
+              .alignItems(HorizontalAlign.Start)
+              .padding(15)
+              .backgroundColor(this.dark_mode_color ? this.color_dark_primary : this.color_light_primary)
+              .borderRadius(10)
+              .animation(animation_default())
+
+              Column() {
+              } // Color
+              .width("55%")
+              .height(100)
+              .alignItems(HorizontalAlign.Start)
+              .justifyContent(FlexAlign.End)
+              .padding(15)
+              .backgroundColor(this.color_hex_display)
+              .borderRadius(10)
+              .animation(animation_default())
+            }
+            .alignItems(HorizontalAlign.Start)
+          }
+          .edgeEffect(EdgeEffect.Spring)
+          .align(Alignment.TopStart)
+          .padding(15)
+          .width("100%")
+          .height("100%")
+          .backgroundColor(this.dark_mode_color ? this.color_dark_secondary : this.color_light_secondary)
+          .borderRadius(16)
+          .animation(animation_default())
+
+        } // Color board
+        .animation(animation_default())
+        .alignItems(HorizontalAlign.End)
+        .width(this.tablet_mode ? "40%" : "100%")
+        .height(this.tablet_mode ? "100%" : "40%")
+        .alignRules(this.tablet_mode ? this.alignRules_body_tablet : this.alignRules_body_non_tablet)
+
+        Scroll() {
+          Column({ space: 10 }) {
+            linysText({
+              text: $r('app.string.Color_hue')
+            })
+              .width("100%")
+            Slider({
+              min: 0,
+              max: 360,
+              value: this.color_hsv[0],
+              style: SliderStyle.InSet,
+            })
+              .blockColor(this.color_current_primary)
+              .selectedColor(this.color_current_font)
+              .onChange((value) => {
+                this.color_hsv[0] = value;
+                this.color_input = hsv2hex(this.color_hsv);
+                this.color_hex_display = this.color_input;
+              })
+
+            linysText({
+              text: $r('app.string.Color_saturation')
+            })
+              .width("100%")
+            Slider({
+              min: 0,
+              max: 100,
+              value: this.color_hsv[1],
+              style: SliderStyle.InSet,
+            })
+              .blockColor(this.color_current_primary)
+              .selectedColor(this.color_current_font)
+              .onChange((value) => {
+                this.color_hsv[1] = value;
+                this.color_input = hsv2hex(this.color_hsv);
+                this.color_hex_display = this.color_input;
+              })
+
+            linysText({
+              text: $r('app.string.Color_value')
+            })
+              .width("100%")
+            Slider({
+              min: 0,
+              max: 100,
+              value: this.color_hsv[2],
+              style: SliderStyle.InSet,
+            })
+              .blockColor(this.color_current_primary)
+              .selectedColor(this.color_current_font)
+              .onChange((value) => {
+                this.color_hsv[2] = value;
+                this.color_input = hsv2hex(this.color_hsv);
+                this.color_hex_display = this.color_input;
+              })
+
+            linysText({
+              text: $r('app.string.Color_hex')
+            })
+              .width("100%")
+            TextInput({ text: this.color_input })
+              .onClick(() => {
+                this.focus_on_code_input = true;
+              })
+              .onBlur(() => {
+                this.focus_on_code_input = false;
+              })
+              .onChange((value) => {
+                if (value[0] != "#") {
+                  value = "#" + value;
+                }
+                this.color_input = value;
+                if (is_legal_hex(value) && this.focus_on_code_input) {
+                  this.color_hex_display = value.toUpperCase();
+                  this.color_hsv = hex2hsv(value);
+                }
+              })
+              .fontWeight(FontWeight.Regular)
+              .fontColor(this.color_current_font)
+              .caretColor(this.color_current_font)
+              .selectedBackgroundColor(this.color_current_font)
+              .selectAll(true)
+              .onSubmit(() => {
+              })
+              .height(capsule_bar_height())
+            Blank()
+            Row({ space: 10 }) {
+              linysCapsuleButton({ text: "  󰀓  " })
+                .onClick(() => {
+                  if (this.controller) {
+                    this.color = this.color_hex_display;
+                    this.controller.close();
+                  }
+                }) // Submit
+              linysCapsuleButton({ text: "  󰁖  " })
+                .onClick(() => {
+                  if (this.controller) {
+                    this.controller.close();
+                  }
+                }) // Cancel
+            } // Buttons of submit and cancel
+            .justifyContent(this.preferred_hand_left_or_right == 'right' ? FlexAlign.Start : FlexAlign.End)
+            .animation(animation_default())
+            .width("100%")
+          }
+          .width("100%")
+        } // Operations
+        .edgeEffect(EdgeEffect.Spring)
+        .scrollBar(BarState.Off)
+        .padding({ right: this.tablet_mode ? 12 : 0 })
+        .id("controls_color")
+        .alignRules(this.alignRules_foot)
+        .animation(animation_default())
+        .height(this.tablet_mode ? undefined : "50%")
+        .width(this.tablet_mode ? "60%" : "100%")
+      }
+      .onAreaChange((_old, n) => {
+        this.dialog_width = n.width as number;
+        this.dialog_height = n.height as number;
+        this.max_square_size =
+          (this.tablet_mode ? Math.min(this.dialog_height, this.dialog_width * 0.5) : this.dialog_width) - 30;
+      })
+      .layoutWeight(1)
+    }
+  }
+
+  on_color_change() {
+    if (this.color_theme_id == 'color_light_font') {
+      this.color_light_font = this.color_hex_display;
+    } else if (this.color_theme_id == 'color_light_primary') {
+      this.color_light_primary = this.color_hex_display;
+    } else if (this.color_theme_id == 'color_light_secondary') {
+      this.color_light_secondary = this.color_hex_display;
+    } else if (this.color_theme_id == 'color_dark_font') {
+      this.color_dark_font = this.color_hex_display;
+    } else if (this.color_theme_id == 'color_dark_primary') {
+      this.color_dark_primary = this.color_hex_display;
+    } else if (this.color_theme_id == 'color_dark_secondary') {
+      this.color_dark_secondary = this.color_hex_display;
+    }
+  }
+}
+
+export default woofSelectColor;

+ 143 - 0
home/src/main/ets/dialogs/prompts/woofWantDownload.ets

@@ -0,0 +1,143 @@
+import linysCapsuleButton from '../../components/buttons/linysCapsuleButton';
+import linysShowButton from '../../components/buttons/linysShowButton';
+import linysText from '../../components/texts/linysText';
+import linysTextTitle from '../../components/texts/linysTextTitle';
+import { animation_default, capsule_bar_height } from '../../hosts/bunch_of_defaults';
+import { is_legal_filename } from '../../utils/storage_tools';
+
+@CustomDialog
+struct woofWantDownload {
+  controller: CustomDialogController;
+  //当前下载的链接
+  @State from: string = '';
+  //文件名
+  @State file_name: string = '';
+  @State url: string = '';
+  @State try_additional_info: string = '';
+  // UI
+  @State showing_additional_info: boolean = false;
+  @State showing_illegal_chars: boolean = false;
+  // Gateways
+  @StorageLink('universal_new_download_filename_gateway') new_download_filename_gateway: string = "";
+  @StorageLink('universal_new_download_additional_info') new_download_additional_info: string = "";
+  @StorageLink('universal_new_download_gateway') new_download_gateway: string = "";
+  // Colors
+  @StorageProp('color_current_primary') color_current_primary: ResourceColor = $r('app.color.start_window_background');
+  @StorageProp('color_current_secondary') color_current_secondary: ResourceColor = $r('app.color.block_color');
+  @StorageProp('color_current_font') color_current_font: ResourceColor = $r('app.color.font_color_title');
+  // Settings / Accessibility
+  @StorageLink('preferred_hand_left_or_right') preferred_hand_left_or_right: string = 'right';
+
+  build() {
+    Column({ space: 15 }) {
+      linysTextTitle({ text: $r('app.string.Index_downloads_prompt') })// prompt 要开始下载吗
+        .width("100%")
+
+      linysText({ text: this.from, max_lines: 10 })// from website
+        .width("100%")
+        .opacity(0.9)
+        .visibility(!this.showing_additional_info ? Visibility.Visible : Visibility.None)
+        .animation(animation_default())
+
+      // 详情信息查看
+      additionalInfoDisplay({
+        my_additional_info: this.try_additional_info.split('\n')
+      })
+        .visibility(this.showing_additional_info ? Visibility.Visible : Visibility.None)
+        .animation(animation_default())
+
+      // 文件名不合法时,给出修改提示
+      linysText({ text: $r('app.string.Index_downloads_illegal_chars_in_filename'), max_lines: 3 }) // File name error prompt
+        .visibility(!is_legal_filename(this.file_name) ? Visibility.Visible : Visibility.None)
+        .animation(animation_default())
+
+      TextInput({ text: this.file_name })// Edit file name
+        .onSubmit(()=>{
+          // 判断当前文件名是否合法,合法就直接保存更改的文件名
+          if (is_legal_filename(this.file_name)) {
+            this.submit();
+          }
+        })
+        .onChange((value) => {
+          this.file_name = value;
+        })
+        .fontWeight(FontWeight.Regular)
+        .fontColor(this.color_current_font)
+        .caretColor(this.color_current_font)
+        .selectedBackgroundColor(this.color_current_font)
+        .height(capsule_bar_height())
+
+      Row({ space: 10 }) {
+        // 详情信息是否展示
+        linysShowButton({
+          symbol_glyph_target: 'sys.symbol.info_circle',
+          show: this.showing_additional_info,
+          text: $r('app.string.Index_downloads_additional_info')
+        })
+          .onClick(() => {
+            this.showing_additional_info = !this.showing_additional_info;
+          })
+        linysCapsuleButton({ text: "  󰁖  " })// Cancel
+          .onClick(() => {
+            if (this.controller) {
+              this.controller.close();
+            }
+          })
+        linysCapsuleButton({ text: "  󰀓  " })// Submit
+          .onClick(() => {
+            if (is_legal_filename(this.file_name)) {
+              this.submit();
+            }
+          })
+          .opacity(is_legal_filename(this.file_name) ? 1 : 0.7)
+      } // Edit
+      .justifyContent(this.preferred_hand_left_or_right == 'right' ? FlexAlign.End : FlexAlign.Start)
+      .animation(animation_default())
+      .width('100%')
+    }
+    .padding(20)
+    .alignItems(HorizontalAlign.Start)
+    .justifyContent(FlexAlign.Center)
+    .width("100%")
+    .backgroundColor(this.color_current_primary)
+  }
+
+  submit() {
+    // Assigned file name overwrite
+    this.new_download_filename_gateway = this.file_name;
+
+    // Assign additional info
+    this.new_download_additional_info = this.try_additional_info;
+
+    // LINK START!
+    this.new_download_gateway = this.url;
+
+    // Goodbye
+    if (this.controller) {
+      this.controller.close();
+    }
+  }
+}
+
+export default woofWantDownload;
+
+@Component
+struct additionalInfoDisplay {
+  @Prop my_additional_info: string[];
+
+  build() {
+    Scroll() {
+      Column({ space: 4 }) {
+        ForEach(this.my_additional_info, (info: string, _index: number) => {
+          linysText({ text: info })
+            .opacity(0.7)
+        }) // additional info
+      }
+      .margin({ bottom: 15 })
+      .alignItems(HorizontalAlign.Start)
+    }
+    .scrollBar(BarState.On)
+    .scrollable(ScrollDirection.Horizontal)
+    .edgeEffect(EdgeEffect.Spring)
+  }
+}

+ 66 - 0
home/src/main/ets/dialogs/prompts/woofWantJump.ets

@@ -0,0 +1,66 @@
+import linysCapsuleButton from '../../components/buttons/linysCapsuleButton';
+import linysText from '../../components/texts/linysText';
+import linysTextTitle from '../../components/texts/linysTextTitle';
+import { animation_default, click_effect_default } from '../../hosts/bunch_of_defaults';
+import { jump_external_link } from '../../utils/url_tools';
+
+@CustomDialog
+struct woofWantJump {
+  controller: CustomDialogController;
+  @Prop link: string = '';
+  // Colors
+  @StorageProp('color_current_primary') color_current_primary: ResourceColor = $r('app.color.start_window_background');
+  @StorageProp('color_current_secondary') color_current_secondary: ResourceColor = $r('app.color.block_color');
+  @StorageProp('color_current_font') color_current_font: ResourceColor = $r('app.color.font_color_title');
+  // Settings / Accessibility
+  @StorageLink('preferred_hand_left_or_right') preferred_hand_left_or_right: string = 'right';
+  // UI display
+  @StorageLink('screen_height') screen_height: number = 0;
+
+  build() {
+    Column({ space: 15 }) {
+      linysTextTitle({ text: $r('app.string.LinkJumper_webpage_asks_for_jump') })// This page is asking for a jump
+        .width("100%")
+
+      TextArea({ text: this.link })// linysText({ text: this.link, max_lines: 5 })// link
+        .fontWeight(FontWeight.Regular)
+        .fontColor(this.color_current_font)
+        .caretColor(this.color_current_font)
+        .selectedBackgroundColor(this.color_current_font)
+        .backgroundColor(this.color_current_secondary)
+        .width("100%")
+        .padding(10)
+        .borderRadius(13.5)
+        .clickEffect(click_effect_default())
+        .constraintSize({ maxHeight: Math.max(0, this.screen_height - 160) })
+
+      linysText({ text: $r('app.string.LinkJumper_allow_jump') })// allow?
+        .width("100%")
+      Row({ space: 10 }) {
+        linysCapsuleButton({ text: "  󰁖  " })// Cancel
+          .onClick(() => {
+            if (this.controller) {
+              this.controller.close();
+            }
+          })
+        linysCapsuleButton({ text: "  󰀓  " })// Submit
+          .onClick(() => {
+            if (this.controller) {
+              this.controller.close();
+            }
+            jump_external_link(this.link);
+          })
+      }
+      .justifyContent(this.preferred_hand_left_or_right == 'right' ? FlexAlign.End : FlexAlign.Start)
+      .animation(animation_default())
+      .width('100%')
+    }
+    .padding(20)
+    .alignItems(HorizontalAlign.Start)
+    .justifyContent(FlexAlign.Center)
+    .width("100%")
+    .backgroundColor(this.color_current_primary)
+  }
+}
+
+export default woofWantJump;

+ 77 - 0
home/src/main/ets/dialogs/prompts/woofWantProtectedResources.ets

@@ -0,0 +1,77 @@
+import linysCapsuleButton from '../../components/buttons/linysCapsuleButton';
+import linysText from '../../components/texts/linysText';
+import linysTextTitle from '../../components/texts/linysTextTitle';
+import { animation_default, click_effect_default } from '../../hosts/bunch_of_defaults';
+import { desc_for_ProtectedResourceTypes, permissions_for_ProtectedResourceTypes, request_user_permission } from '../../utils/permission_tools';
+
+@CustomDialog
+struct woofWantResources {
+  controller: CustomDialogController;
+  @Prop protected_resource_types: string[] = [];
+  @Prop source: string = 'source';
+  onPermit?: () => void;
+  onDeny?: () => void;
+  // Colors
+  @StorageProp('color_current_primary') color_current_primary: ResourceColor = $r('app.color.start_window_background');
+  @StorageProp('color_current_secondary') color_current_secondary: ResourceColor = $r('app.color.block_color');
+  @StorageProp('color_current_font') color_current_font: ResourceColor = $r('app.color.font_color_title');
+  // Settings / Accessibility
+  @StorageLink('preferred_hand_left_or_right') preferred_hand_left_or_right: string = 'right';
+  // UI display
+  @StorageLink('screen_height') screen_height: number = 0;
+
+  build() {
+    Column({ space: 15 }) {
+      linysTextTitle({ text: $r('app.string.Permission_Manager_webpage_asks_for_permission'), max_lines: 3 })// This page is asking for a jump
+        .width("100%")
+      linysText({ text: this.source })
+
+      Column({ space: 10 }) {
+        ForEach(desc_for_ProtectedResourceTypes(this.protected_resource_types), ((desc: ResourceStr) => {
+          linysText({
+            text: desc,
+            max_lines: 5
+          })
+            .padding(10)
+            .borderRadius(13.5)
+            .clickEffect(click_effect_default())
+            .backgroundColor(this.color_current_secondary)
+        }))
+      }
+
+      linysText({ text: $r('app.string.Permission_Manager_grant_permission') })// allow?
+        .width("100%")
+      Row({ space: 10 }) {
+        linysCapsuleButton({ text: "  󰁖  " })// Cancel
+          .onClick(() => {
+            if (this.onDeny) {
+              this.onDeny();
+            }
+            if (this.controller) {
+              this.controller.close();
+            }
+          })
+        linysCapsuleButton({ text: "  󰀓  " })// Submit
+          .onClick(() => {
+            request_user_permission(permissions_for_ProtectedResourceTypes(this.protected_resource_types));
+            if (this.onPermit) {
+              this.onPermit();
+            }
+            if (this.controller) {
+              this.controller.close();
+            }
+          })
+      }
+      .justifyContent(this.preferred_hand_left_or_right == 'right' ? FlexAlign.End : FlexAlign.Start)
+      .animation(animation_default())
+      .width('100%')
+    }
+    .padding(20)
+    .alignItems(HorizontalAlign.Start)
+    .justifyContent(FlexAlign.Center)
+    .width("100%")
+    .backgroundColor(this.color_current_primary)
+  }
+}
+
+export default woofWantResources;

+ 84 - 0
home/src/main/ets/dialogs/quicks/woofQuickSE.ets

@@ -0,0 +1,84 @@
+import { animation_default, click_effect_default, default_search_engine } from '../../hosts/bunch_of_defaults';
+import { bunch_of_search_engines, search_engine } from '../../hosts/bunch_of_search_engines';
+import { bunch_of_settings } from '../../hosts/bunch_of_settings';
+import woofControlFrame from '../woofControlFrame';
+import meowSEManager from '../../blocks/panels/meowSEManager';
+import linysText from '../../components/texts/linysText';
+
+@CustomDialog
+struct woofQuickSE {
+  controller: CustomDialogController;
+  @Prop keyword: string = ''
+  @Prop default_new_se: search_engine = new search_engine(new Date().toLocaleString(), default_search_engine());
+  // Colors
+  @StorageProp('color_current_primary') color_current_primary: ResourceColor = $r('app.color.start_window_background');
+  @StorageProp('color_current_secondary') color_current_secondary: ResourceColor = $r('app.color.block_color');
+  @StorageProp('color_current_font') color_current_font: ResourceColor = $r('app.color.font_color_title');
+  // Host
+  @StorageLink('bunch_of_search_engines') bunch_of_search_engines: bunch_of_search_engines = new bunch_of_search_engines();
+  @StorageLink('bunch_of_settings') bunch_of_settings: bunch_of_settings = new bunch_of_settings(true);
+  // Settings - Accessibility
+  @StorageLink('preferred_hand_left_or_right') preferred_hand_left_or_right: string = 'right';
+  // Animation UI Statuses
+  @State adding_SE: boolean = false;
+  @State height_of_add_panel: number = 0;
+  button_height_default: number = 42;
+  // Gateways
+  @StorageLink('universal_load_url_gateway') load_url_gateway: string = "";
+
+  build() {
+    woofControlFrame({
+      title: $r('app.string.AddSE_search_again_on'),
+      controller: this.controller
+    }) {
+      Scroll() {
+        Column({ space: 10 }) {
+          linysText({
+            text: ' 󰀩  ' + this.keyword + ' '
+          })
+            .padding(10)
+            .borderRadius(13.5)
+            .clickEffect(click_effect_default())
+            .backgroundColor(this.color_current_secondary)
+
+          meowSEManager({
+            default_new_se: this.default_new_se,
+            on_default_execution: () => {
+              this.search_with_se(this.keyword);
+            },
+            on_normal_execution: (idx, _se) => {
+              this.search_with_se(this.keyword, idx);
+            }
+          })
+        }
+        .alignItems(HorizontalAlign.Start)
+      }
+      .borderRadius(13.5)
+      .layoutWeight(1)
+      .scrollBar(BarState.Off)
+      .edgeEffect(EdgeEffect.Spring)
+      .constraintSize({
+        maxHeight: 45 + 48.5 * this.bunch_of_search_engines.list_of_search_engines.length + 100 + (this.adding_SE ? 20 + this.height_of_add_panel : 0)
+      })
+      .animation(animation_default())
+      .onAppear(() => {
+        console.log('[woofQuickSE] About to search: "' + this.keyword + '" on another engine.')
+      })
+    }
+  }
+
+  search_with_se(key: string, clicked_index?: number) {
+    // Choose to search on another engine
+    if (clicked_index !== undefined) {
+      this.load_url_gateway = this.bunch_of_search_engines.list_of_search_engines[clicked_index].url.replaceAll('%s', key);
+    } else {
+      this.load_url_gateway = default_search_engine().replaceAll('%s', key);
+    }
+    console.log('[woofQuickSE] Search: "' + this.keyword + '" => ' + this.load_url_gateway);
+    if (this.controller) {
+      this.controller.close();
+    }
+  }
+}
+
+export default woofQuickSE;

+ 48 - 0
home/src/main/ets/dialogs/woofControlFrame.ets

@@ -0,0 +1,48 @@
+import linysSymbol from '../components/texts/linysSymbol';
+import linysTextTitle from '../components/texts/linysTextTitle';
+import { animation_default } from '../hosts/bunch_of_defaults';
+
+@Component
+struct woofControlFrame {
+  // Pass in content components
+  @BuilderParam content_section: () => void;
+  // Information
+  @Prop title: ResourceStr = 'Meow';
+  // Control
+  controller?: CustomDialogController;
+  // Color
+  @StorageProp('color_current_primary') color_current_primary: ResourceColor = $r('app.color.start_window_background');
+  // animation
+  @State off_y_contents: number = 120;
+
+  build() {
+    Column({ space: 15 }) {
+      Row() {
+        Blank()
+          .width(30)
+        linysTextTitle({ text: this.title, max_lines: 3 })
+          // .layoutWeight(1)
+        linysSymbol({ symbol_glyph_target: 'sys.symbol.xmark' })
+          .onClick(() => {
+            if (this.controller) {
+              this.controller.close();
+            }
+          })
+      }
+      .width('100%')
+      .justifyContent(FlexAlign.SpaceBetween)
+
+      // Contents
+      this.content_section();
+    }
+    .offset({ y: this.off_y_contents })
+    .animation(animation_default())
+    .padding(15)
+    .backgroundColor(this.color_current_primary)
+    .onAppear(() => {
+      this.off_y_contents = 0;
+    })
+  }
+}
+
+export default woofControlFrame;

+ 281 - 0
home/src/main/ets/homeability/HomeAbility.ets

@@ -0,0 +1,281 @@
+import { AbilityConstant, Configuration, UIAbility, Want, wantConstant } from '@kit.AbilityKit';
+import { hilog } from '@kit.PerformanceAnalysisKit';
+import { display, window } from '@kit.ArkUI';
+import { BusinessError, deviceInfo } from '@kit.BasicServicesKit';
+import { kv_store_get, kv_store_put } from '../utils/kv_store_tools';
+
+export default class HomeAbility extends UIAbility {
+  storage: LocalStorage = new LocalStorage();
+
+  async onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): Promise<void> {
+    hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onCreate');
+    console.log('want bundle name: ' + want.bundleName);
+    console.log('launchParam launchReason: ' + launchParam.launchReason);
+
+    // save environment
+    AppStorage.setOrCreate('context', this.context);
+    AppStorage.setOrCreate('pathDir', this.context.filesDir);
+    AppStorage.setOrCreate('currentColorMode', this.context.config.colorMode);
+
+    // Perhaps this could get the KVStore manager?
+    await kv_store_get('meow');
+
+    if (launchParam.launchReason === AbilityConstant.LaunchReason.CONTINUATION) {
+      this.process_continue(want);
+      this.context.restoreWindowStage(this.storage);
+    } else {
+      // Process want
+      this.process_want(want);
+    }
+  }
+
+  onNewWant(want: Want, launchParam: AbilityConstant.LaunchParam): void {
+    if (launchParam.launchReason === AbilityConstant.LaunchReason.CONTINUATION) {
+      this.process_continue(want);
+    } else {
+      // Process want
+      this.process_want(want);
+    }
+  }
+
+  onContinue(wantParam: Record<string, Object>) {
+    // Check if continuation_auto_exit
+    let auto_exit = AppStorage.get('continuation_auto_exit') as boolean;
+    let close_tab = AppStorage.get('continuation_auto_close_tab') as boolean;
+    // Set continue information
+    console.info(`onContinue version = ${wantParam.version}, targetDevice: ${wantParam.targetDevice}`);
+    let current_url = AppStorage.get('current_url') as string;
+    wantParam['data'] = current_url;
+    if (!auto_exit) {
+      wantParam[wantConstant.Params.SUPPORT_CONTINUE_SOURCE_EXIT_KEY] = false;
+      if (close_tab) {
+        let current_main_tab_index = AppStorage.get('current_main_tab_index') as number;
+        AppStorage.set('universal_close_tab_gateway', current_main_tab_index);
+      }
+    }
+    wantParam[wantConstant.Params.SUPPORT_CONTINUE_PAGE_STACK_KEY] = false;
+    return AbilityConstant.OnContinueResult.AGREE;
+  }
+
+  onDestroy(): void {
+    hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onDestroy');
+  }
+
+  onWindowStageCreate(windowStage: window.WindowStage): void {
+    // Main window is created, set main page for this ability
+    hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageCreate');
+
+    windowStage.getMainWindow((err: BusinessError, win) => {
+      const errCode: number = err.code;
+      if (errCode) {
+        console.error(`Failed to obtain the main window. Cause code: ${err.code}, message: ${err.message}`);
+        return;
+      }
+    })
+
+    // Load window content
+    this.window_load_content(windowStage);
+  }
+
+  onWindowStageRestore(windowStage: window.WindowStage) {
+    let is_new_launch = !(AppStorage.get('settings_init_retrieved') as boolean);
+    console.log('[HomeAbility][onWindowStageRestore] is_new_launch? - ' + (is_new_launch ? 'yes!' : 'no...'))
+    // Only loads if is new launch
+    if (is_new_launch) {
+      this.window_load_content(windowStage);
+    }
+  }
+
+  onConfigurationUpdate(newConfig: Configuration): void {
+    AppStorage.setOrCreate('currentColorMode', newConfig.colorMode);
+  }
+
+  onWindowStageDestroy(): void {
+    // Main window is destroyed, release UI related resources
+    hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageDestroy');
+  }
+
+  onForeground(): void {
+    // Ability has brought to foreground
+    hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onForeground');
+  }
+
+  onBackground(): void {
+    // Ability has back to background
+    hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onBackground');
+  }
+
+  process_want(want: Want) {
+    AppStorage.setOrCreate('want_uri', '');
+    // want
+    let want_uri = want.uri;
+    let want_type = want.type;
+    let want_action = want.action;
+    let want_flags = want.flags;
+    if (want_uri == null || want_uri == undefined || want_uri.length < 1) {
+      // Invalid want
+      console.info('[Meow][HomeAbility] No or empty Want uri accepted.');
+      AppStorage.setOrCreate('want_uri', '');
+      AppStorage.setOrCreate('want_type', '');
+      AppStorage.setOrCreate('want_action', '');
+    } else {
+      // Good want
+      console.log('[Meow][HomeAbility] Want accepted!');
+      console.log('[Meow][HomeAbility] Want uri: ' + want_uri);
+
+      // Process the rest params
+      if (want_type) {
+        AppStorage.setOrCreate('want_type', want_type);
+        console.log('[Meow][HomeAbility] Want type: ' + want_type);
+      } else {
+        AppStorage.setOrCreate('want_type', '');
+        console.log('[Meow][HomeAbility] Want type is empty?');
+      }
+      if (want_action) {
+        AppStorage.setOrCreate('want_action', want_action);
+        console.log('[Meow][HomeAbility] Want action: ' + want_action);
+      } else {
+        AppStorage.setOrCreate('want_action', '');
+        console.log('[Meow][HomeAbility] Want action is empty?');
+      }
+      if (want_action) {
+        AppStorage.setOrCreate('want_flags', want_flags);
+        console.log('[Meow][HomeAbility] Want flags: ' + want_flags);
+      } else {
+        AppStorage.setOrCreate('want_flags', '');
+        console.log('[Meow][HomeAbility] Want flags are empty?');
+      }
+
+      // refresh want_uri in the last place
+      AppStorage.setOrCreate('want_uri', want_uri);
+    }
+  }
+
+  /**
+   * Cross device continuation
+   * @param want the want
+   * */
+  process_continue(want: Want) {
+    let is_new_launch = !(AppStorage.get('settings_init_retrieved') as boolean);
+    console.log('[HomeAbility][onWindowStageRestore] is_new_launch? - ' + (is_new_launch ? 'yes' : 'no'))
+    // Get the data
+    let continueInput = '';
+    if (want.parameters !== undefined) {
+      try {
+        continueInput = want.parameters.data.toString();
+        console.info('[Meow][HomeAbility][onNewWant] continue input: ' + continueInput);
+        // Recovery of window
+        setTimeout(() => {
+          AppStorage.set('universal_new_tab_gateway', continueInput);
+        }, is_new_launch ? 1000 : 100)
+      } catch (e) {
+        console.error(e);
+      }
+    }
+  }
+
+  /**
+   * Implements a same px2vp as this.getUIContext().px2vp in UI interfaces
+   * @param px the real pixel
+   * @returns the vp equivalent
+   * */
+  px2vp(px: number) {
+    return px / display.getDefaultDisplaySync().densityPixels;
+  }
+
+  window_load_content(windowStage: window.WindowStage) {
+    windowStage.loadContent('pages/Index', (err) => {
+      if (err.code) {
+        hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? '');
+        return;
+      }
+      hilog.info(0x0000, 'testTag', 'Succeeded in loading the content.');
+
+      let windowClass = windowStage.getMainWindowSync(); // Get app main window
+
+      console.log('Succeeded in obtaining the main window. Data: ' + JSON.stringify(windowClass));
+      console.log('[HomeAbility] deviceType: ' + deviceInfo.deviceType);
+
+      if (deviceInfo.deviceType == '2in1') {
+        console.log('\ttreated as a 2in1.');
+        // windowClass.setWindowCornerRadius(26);
+        // windowClass.setWindowTitleButtonVisible(true, true, true);
+        windowClass.setWindowDecorVisible(false);
+        let windowDecorHeight = windowClass.getWindowDecorHeight();
+
+        windowClass.on('windowTitleButtonRectChange', (titleButtonRect) => {
+          console.info('[HomeAbility] Succeeded in enabling the listener for window title buttons area changes. Data: ' + JSON.stringify(titleButtonRect));
+
+          AppStorage.setOrCreate('windowDecorHeight', titleButtonRect.height);
+          console.log('\twindowDecorHeight: ' + titleButtonRect.height);
+
+          AppStorage.setOrCreate('windowDecorWidth', titleButtonRect.width);
+          console.log('\twindowDecorWidth: ' + titleButtonRect.width);
+        })
+
+        AppStorage.setOrCreate('topAvoidHeight', windowDecorHeight);
+        console.log('[HomeAbility] topAvoidHeight: ' + windowDecorHeight);
+        // windowClass.setResizeByDragEnabled(true);
+        // windowClass.setWindowTitle('qwq');
+      } else {
+        console.log('\tnot "2in1", treated as else.');
+
+        // 1. Set fullscreen window
+        windowClass.setImmersiveModeEnabledState(true);
+
+        console.log('[HomeAbility] Get avoid areas:')
+        // 2. Get avoid area
+        let type = window.AvoidAreaType.TYPE_NAVIGATION_INDICATOR; // 以导航条避让为例
+        let avoidArea = windowClass.getWindowAvoidArea(type);
+        let avoidBottom = this.px2vp(avoidArea.bottomRect.height); // 获取到导航条区域的高度
+        AppStorage.setOrCreate('bottomAvoidHeight', avoidBottom);
+        console.log('\tNew avoidBottomHeight: ' + avoidBottom);
+
+        type = window.AvoidAreaType.TYPE_SYSTEM;
+        avoidArea = windowClass.getWindowAvoidArea(type);
+        let avoidTop = this.px2vp(avoidArea.topRect.height);
+        AppStorage.setOrCreate('topAvoidHeight', avoidTop + (avoidTop == 0 ? 0 : 2));
+        console.log('\ttopAvoidHeight: ' + avoidTop + (avoidTop == 0 ? 0 : 2));
+
+        type = window.AvoidAreaType.TYPE_CUTOUT;
+        avoidArea = windowClass.getWindowAvoidArea(type);
+        let avoidLeft = this.px2vp(avoidArea.leftRect.width);
+        AppStorage.setOrCreate('leftAvoidWidth', avoidLeft + (avoidLeft == 0 ? 0 : 2));
+        console.log('\tNew leftAvoidWidth: ' + avoidLeft + (avoidLeft == 0 ? 0 : 2));
+
+        type = window.AvoidAreaType.TYPE_CUTOUT;
+        avoidArea = windowClass.getWindowAvoidArea(type);
+        let avoidRight = this.px2vp(avoidArea.rightRect.width);
+        AppStorage.setOrCreate('rightAvoidWidth', avoidRight + (avoidRight == 0 ? 0 : 2));
+        console.log('\tNew avoidRightWidth: ' + avoidRight + (avoidRight == 0 ? 0 : 2));
+
+        // Change listener
+        windowClass.on('avoidAreaChange', (data) => {
+          console.log('[HomeAbility] Changes on avoid areas:')
+
+          if (data.type == window.AvoidAreaType.TYPE_SYSTEM) {
+            avoidTop = this.px2vp(data.area.topRect.height);
+            AppStorage.setOrCreate('topAvoidHeight', avoidTop + (avoidTop == 0 ? 0 : 2));
+            console.log('\tNew topAvoidHeight: ' + avoidTop + (avoidTop == 0 ? 0 : 2));
+          }
+          if (data.type == window.AvoidAreaType.TYPE_NAVIGATION_INDICATOR) {
+            avoidBottom = this.px2vp(data.area.bottomRect.height);
+            AppStorage.setOrCreate('bottomAvoidHeight', avoidBottom);
+            console.log('\tNew avoidBottomHeight: ' + avoidBottom);
+          }
+          if (data.type == window.AvoidAreaType.TYPE_CUTOUT) {
+            avoidLeft = this.px2vp(data.area.leftRect.width);
+            AppStorage.setOrCreate('leftAvoidWidth', avoidLeft + (avoidLeft == 0 ? 0 : 2));
+            console.log('\tNew leftAvoidWidth: ' + avoidLeft + (avoidLeft == 0 ? 0 : 2));
+          }
+          if (data.type == window.AvoidAreaType.TYPE_CUTOUT) {
+            avoidRight = this.px2vp(data.area.rightRect.width);
+            AppStorage.setOrCreate('rightAvoidWidth', avoidRight + (avoidRight == 0 ? 0 : 2));
+            console.log('\tNew avoidRightWidth: ' + avoidRight + (avoidRight == 0 ? 0 : 2));
+          }
+        })
+      }
+      AppStorage.set('isAppRunning', true);
+    });
+  }
+}

+ 12 - 0
home/src/main/ets/homebackupability/HomeBackupAbility.ets

@@ -0,0 +1,12 @@
+import { hilog } from '@kit.PerformanceAnalysisKit';
+import { BackupExtensionAbility, BundleVersion } from '@kit.CoreFileKit';
+
+export default class HomeBackupAbility extends BackupExtensionAbility {
+  async onBackup() {
+    hilog.info(0x0000, 'testTag', 'onBackup ok');
+  }
+
+  async onRestore(bundleVersion: BundleVersion) {
+    hilog.info(0x0000, 'testTag', 'onRestore ok %{public}s', JSON.stringify(bundleVersion));
+  }
+}

+ 844 - 0
home/src/main/ets/hosts/bunch_of_bookmarks.ets

@@ -0,0 +1,844 @@
+import { decode_html_code_to_string, encode_string_to_html_code } from '../utils/html_tools';
+import { ensure_scheme } from '../utils/url_tools';
+
+export class bunch_of_bookmarks {
+  root: folder;
+  last_accessed: number = 0;
+  private cached_plain_bookmarks: string[][] | undefined = undefined;
+
+  /**
+   * A class holding a root folder, in which there stores bookmarks and folders.
+   * @param root_label The label of root folder.
+   */
+  constructor(root_label: string) {
+    this.root = new folder(root_label);
+  }
+
+  /**
+   * Reconstruct plain bookmarks and store in a cache array as a member variable of this bunch_of_bookmarks.
+   * @summary So that requests can be satisfied with this cached data in stead of another reconstruction which
+   * consumes another a lot of performance and time.
+   * */
+  reconstruct_cached_plain_bookmarks() {
+    this.cached_plain_bookmarks = this.root.get_plain_bookmarks();
+    console.log("[bunch_of_bookmarks][plain_bookmarks] Reconstructed plain bookmarks! length: " +
+    this.cached_plain_bookmarks.length.toString());
+  }
+
+  /**
+   * Adds a bookmark to this bunch_of_bookmarks.
+   * @param item A bookmark object.
+   * @param path A string, technically the path of the folder which the bookmark item will be put into.
+   * @param no_reconstruct_plain_bookmarks A boolean, will not reconstruct the plain bookmarks array if set true,
+   * otherwise (unfilled or set false) will reconstruct the plain bookmarks array.
+   * @returns true if success.
+   * @returns false if failed (for a name crash or a nonexistent destination).
+   * */
+  add_bookmark(item: bookmark, path: string, no_reconstruct_plain_bookmarks?: boolean) {
+    let check_path = "";
+    if (path == "") {
+      check_path = item.get_label()
+    } else {
+      check_path = path + "/" + item.get_label()
+    }
+    if (this.get_bookmark(check_path)) {
+      // already exist
+      return false;
+    } else {
+      let add_result = this.add_bookmark_process(item, path);
+
+      if (no_reconstruct_plain_bookmarks) {
+        return add_result;
+      }
+      // Update plain_bookmarks
+      this.reconstruct_cached_plain_bookmarks();
+      this.last_accessed = Date.now();
+      return add_result;
+    }
+  }
+
+  /**
+   * Adds a folder to this bunch_of_bookmarks.
+   * @param item A folder object.
+   * @param path A string, technically the path of the folder which the bookmark item will be put into.
+   * @param no_reconstruct_plain_bookmarks A boolean, will not reconstruct the plain bookmarks array if set true,
+   * otherwise (unfilled or set false) will reconstruct the plain bookmarks array.
+   * @returns true if success.
+   * @returns false if failed (for a name crash or a nonexistent destination).
+   * */
+  add_folder(item: folder, path: string, no_reconstruct_plain_bookmarks?: boolean) {
+    let check_path = "";
+    if (path == "") {
+      check_path = item.get_label()
+    } else {
+      check_path = path + "/" + item.get_label()
+    }
+
+    let add_result: boolean;
+    if (this.get_folder(check_path)) {
+      // already exist
+      add_result = false;
+    } else {
+      add_result = this.add_folder_process(item, path);
+    }
+
+    if (no_reconstruct_plain_bookmarks) {
+      return add_result;
+    }
+    // Update plain_bookmarks
+    this.reconstruct_cached_plain_bookmarks();
+    this.last_accessed = Date.now();
+    return add_result;
+  }
+
+  get_bookmark(path: string, base_folder?: folder): bookmark | undefined {
+    // Basic idea:
+    // Will recursively open folders and sub folders, until the path is chopped down to a single level.
+    // Then just pick the bookmark requested.
+
+    if (base_folder === undefined) {
+      base_folder = this.root;
+      // use root as default;
+    }
+
+    let base_content = base_folder.get_content();
+
+    if (!path.includes("/")) {
+      // if at destination folder
+      for (let index = 0; index < base_content.length; index++) {
+        // Temporarily get the item
+        let item = base_content[index].get_item();
+        if (item.get_type() == "bookmark" && item.get_label() == path) {
+          return item as bookmark;
+        }
+      }
+      // not found
+      return undefined;
+    } else {
+      let rest_path = path.split("/").slice(1).join("/");
+      let next_folder_label = path.split("/")[0];
+      for (let index = 0; index < base_content.length; index++) {
+        // Temporarily get the item
+        let item = base_content[index].get_item();
+        if (item.get_type() == "folder" && item.get_label() == next_folder_label) {
+          return this.get_bookmark(rest_path, item as folder);
+        }
+      }
+      // not found
+      return undefined;
+    }
+  }
+
+  get_folder(path: string, base_folder?: folder): folder | undefined {
+    // Basic idea:
+    // Will recursively open folders and sub folders, until the path is chopped down to a single level.
+    // Then just pick the folder requested.
+
+    if (path == '/') {
+      return undefined;
+    }
+
+    if (path == '') {
+      return this.root;
+    }
+
+    if (base_folder === undefined) {
+      base_folder = this.root;
+      // use root as default;
+    }
+
+    let base_content = base_folder.get_content();
+
+    if (!path.includes("/")) {
+      // if at destination folder
+      for (let index = 0; index < base_content.length; index++) {
+        // Temporarily get the item
+        let item = base_content[index].get_item();
+        if (item.get_type() == "folder" && item.get_label() == path) {
+          return item as folder;
+        }
+      }
+      // not found
+      return undefined;
+    } else {
+      let rest_path = path.split("/").slice(1).join("/");
+      for (let index = 0; index < base_content.length; index++) {
+        // Temporarily get the item
+        let item = base_content[index].get_item();
+        if (item.get_type() == "folder" && item.get_label() == path.split("/")[0]) {
+          return this.get_folder(rest_path, item as folder);
+        }
+      }
+      return undefined;
+    }
+  }
+
+  del_bookmark(path: string) {
+    if (!this.get_bookmark(path)) {
+      // bookmark doesn't exist
+      return false;
+    }
+    let delete_result = this.delete_bookmark_process(path);
+
+    // Update plain_bookmarks
+    this.reconstruct_cached_plain_bookmarks();
+    this.last_accessed = Date.now();
+    return delete_result;
+  }
+
+  del_folder(path: string) {
+    if (!this.get_folder(path)) {
+      // folder doesn't exist
+      return false;
+    }
+    let delete_result = this.delete_folder_process(path);
+
+    // Update plain_bookmarks
+    this.reconstruct_cached_plain_bookmarks();
+    this.last_accessed = Date.now();
+    return delete_result;
+  }
+
+  /**
+   * Moves a bookmark to another directory
+   * @param bookmark_path directory of bookmark to be moved
+   * @param to_path directory of destination
+   * @returns 0 if success
+   * @returns 1 if no bookmark selected
+   * @returns 2 if name crash
+   * */
+  move_bookmark(bookmark_path: string, to_path: string) {
+    if (bookmark_path == "") {
+      console.log("[ERROR][Meow][bunch_of_bookmarks] Empty bookmark_path!")
+      return 1;
+    }
+    // Get objects
+    let item_bookmark = this.get_bookmark(bookmark_path);
+    let target_folder: folder | undefined;
+    if (to_path == "") {
+      target_folder = this.root;
+    } else {
+      target_folder = this.get_folder(to_path);
+    }
+
+    // If Objects got
+    if (item_bookmark && target_folder) {
+      // Check name crash
+      let check_path = "";
+      if (to_path == "") {
+        check_path = item_bookmark.get_label()
+      } else {
+        check_path = to_path + "/" + item_bookmark.get_label()
+      }
+      if (this.get_bookmark(check_path)) {
+        // name crash
+        console.log("[ERROR][Meow][bunch_of_bookmarks] Duplicate bookmark name @ \"" + check_path + "\"!")
+        return 2;
+      }
+      // All OK
+      target_folder.add_content(new unified_item(item_bookmark, undefined));
+      this.del_bookmark(bookmark_path);
+    }
+    this.last_accessed = Date.now();
+    return 0;
+  }
+
+  /**
+   * Moves a folder to another directory
+   * @param folder_path directory of folder to be moved
+   * @param to_path directory of destination
+   * @returns 0 if success
+   * @returns 1 if no folder selected
+   * @returns 2 if name crash
+   * @returns 3 if moving a folder to its sub folder
+   * */
+  move_folder(folder_path: string, to_path: string) {
+    if (folder_path == "") {
+      console.log("[ERROR][Meow][bunch_of_bookmarks] Empty folder_path!")
+      return 1;
+    }
+    if (to_path.indexOf(folder_path) == 0) {
+      console.log("[ERROR][bunch_of_bookmarks] Cannot move myself to my sub folder!")
+      return 3;
+    }
+
+    // Get objects
+    let item_folder = this.get_folder(folder_path);
+    let target_folder: folder | undefined;
+    if (to_path == "") {
+      target_folder = this.root;
+    } else {
+      target_folder = this.get_folder(to_path);
+    }
+
+    // If Objects got
+    if (item_folder && target_folder) {
+      // Check name crash
+      let check_path = "";
+      if (to_path == "") {
+        check_path = item_folder.get_label()
+      } else {
+        check_path = to_path + "/" + item_folder.get_label()
+      }
+      if (this.get_folder(check_path)) {
+        // name crash
+        console.log("[ERROR][Meow][bunch_of_bookmarks] Duplicate folder name @ \"" + check_path + "\"!")
+        return 2;
+      }
+      // All OK
+      target_folder.add_content(new unified_item(undefined, item_folder));
+      this.del_folder(folder_path);
+    }
+    this.last_accessed = Date.now();
+    return 0;
+  }
+
+  /**
+   * Get the encoded HTML text of the whole bunch_of_bookmark.
+   * @returns A string[] array, each element refers to a line after .join("\n").
+   * */
+  get_export_html() {
+    let result: string[] = [];
+    let head: string = "<!DOCTYPE NETSCAPE-Bookmark-file-1>\n" +
+      "<!-- This is an automatically generated file.\n" +
+      "     It will be read and overwritten.\n" +
+      "     DO NOT EDIT! -->\n" +
+      "<META HTTP-EQUIV=\"Content-Type\" CONTENT=\"text/html; charset=UTF-8\">\n" +
+      "<TITLE>Bookmarks Meow</TITLE>\n" +
+      "<H1>Bookmarks Meow</H1>\n"
+    result.push(head);
+    result.push("<!-- Meow, Meow, Meow -->\n");
+    result.push("<DL><p>");
+    result = result.concat(this.root.get_contents_in_html(0));
+    result.push("</DL><p>");
+    return result;
+  }
+
+  /**
+   * Imports HTML format bookmarks to bunch_of_bookmarks, allowing to either overwrite totally or add to root.
+   *
+   * If overwrite is set true then removes whatever was previously in this bunch_of_bookmarks.
+   * @param html The string of .html format bookmarks.
+   * @param overwrite Set false in default.
+   * @returns -1 for no <H3> error.
+   * @returns 0 for SUCCESS.
+   * @returns 1 for no label.
+   * @returns 2 for no link.
+   * @returns 3 for add_folder method fail (Perhaps because label collision).
+   * */
+  import_html(html: string, overwrite?: boolean) {
+    let overwrite_data = false;
+    if (overwrite !== undefined) {
+      overwrite_data = overwrite;
+    }
+    // Set overwrite options
+
+    let one_line_data: string = html.replace(/\s*\n+\s*/g, "") // Eliminate all returns
+    one_line_data = one_line_data.replace(/<p>/g, "") // Eliminate all <p>
+    one_line_data = one_line_data.replace(/<DT>/g, "") // Eliminate all <DT>
+    one_line_data = one_line_data.replace(/<DL>/g, "") // Eliminate all <DL>
+    one_line_data = one_line_data.replace(/<\/A>/g, "") // Eliminate all </A>
+    one_line_data = one_line_data.replace(/<\/H3>/g, "") // Eliminate all </H3>
+    one_line_data = one_line_data.replace(/</g, "\n<") // Put returns
+    let first_H3 = one_line_data.indexOf("<H3")
+    if (first_H3 == -1) {
+      return -1; // -1 for no <H3> error
+    }
+
+    one_line_data = one_line_data.substring(first_H3)
+    let lines_split = one_line_data.split("\n")
+    // Split Into lines
+
+    let new_bunch_of_bookmarks_label = new Date().toLocaleString()
+    let new_bunch_of_bookmarks: bunch_of_bookmarks = new bunch_of_bookmarks(new_bunch_of_bookmarks_label);
+    let now_path_stack: string[] = [];
+    // Set new bookmarks bundle
+
+    for (let index = 0; index < lines_split.length; index++) {
+      let line = lines_split[index]; // For each line
+
+      if (line.length == 0) {
+        // Skip empty lines
+        continue;
+      }
+      let now_path_string = now_path_stack.join("/");
+      // Connect now path
+
+      let position_of_label = line.indexOf(">");
+      if (position_of_label == -1) {
+        return 1; // 1 for no label error
+      }
+      // if (position_of_label + 1 == line.length) {
+      //   return 1;
+      // }
+      let label = line.substring(position_of_label + 1);
+      label = decode_html_code_to_string(label);
+      // Extract Label
+
+      let add_date = Number.parseInt(this.import_html_get_key("ADD_DATE=\"", line));
+      let last_modified = Number.parseInt(this.import_html_get_key("LAST_MODIFIED=\"", line));
+      if (last_modified == Number.NaN) {
+        // No last_modified value, set new default
+        last_modified = Math.round(new Date().getTime() / 1000)
+      }
+      // Extract dates
+
+      if (line.includes("<H3 ")) {
+        // is a folder
+        new_bunch_of_bookmarks.add_folder(new folder(label, add_date, last_modified), now_path_string, true);
+        now_path_stack.push(label)
+
+      } else if (line.includes("<A HREF=\"")) {
+        // is a bookmark
+        let link = this.import_html_get_key("<A HREF=\"", line);
+        if (link == "") {
+          return 2; // 2 for empty link error
+        }
+        new_bunch_of_bookmarks.add_bookmark(new bookmark(label, link, add_date), now_path_string, true);
+
+      } else if (line.includes("</DL>")) {
+        // is a closing mark
+        now_path_stack.pop();
+        // go back to upper class folder
+      }
+
+    }
+
+    let final_add_result = false;
+    if (overwrite_data) {
+      let root_contents: folder = (new_bunch_of_bookmarks.root.get_content()[0].get_item() as folder);
+      this.root = root_contents;
+    } else {
+      final_add_result = this.add_folder(new_bunch_of_bookmarks.root, "", true) as boolean;
+    }
+
+    // Update plain_bookmarks
+    this.reconstruct_cached_plain_bookmarks();
+
+    if (final_add_result) {
+      this.last_accessed = Date.now();
+      return 0; // 0 for a successful import
+    } else {
+      return 3; // 3 for add_folder errors
+    }
+  }
+
+  /**
+   * Get the plain label link array from the cached array.
+   * @returns A string[][] array, cached_plain_bookmarks, which consists of plain label and link pairs.
+   * @example [['bing', 'www.bing.com'], ['loading', 'www.google.com']]
+   * */
+  get_all_plain_label_link(): string[][] {
+    if (!this.cached_plain_bookmarks) {
+      this.reconstruct_cached_plain_bookmarks();
+    }
+    return this.cached_plain_bookmarks as string[][];
+  }
+
+  private import_html_get_key(key: string, from: string) {
+    let start: number = from.indexOf(key) + key.length;
+    let end: number = from.length;
+    if (start == -1) {
+      return "";
+    }
+    for (let index = start; index < from.length; index++) {
+      if (from[index] == "\"") {
+        end = index;
+        break;
+      }
+    }
+    return from.substring(start, end);
+  }
+
+  private delete_bookmark_process(path: string, base_folder?: folder): boolean {
+    if (base_folder === undefined) {
+      base_folder = this.root;
+      // use root as default;
+    }
+
+    if (!path.includes("/")) {
+      // Directly delete in this folder
+      let dir: unified_item[] = base_folder.get_content();
+      let target: number = -1;
+
+      for (let index = 0; index < dir.length; index++) {
+        if (dir[index].get_item().get_type() == "bookmark") {
+          if (dir[index].get_item().get_label() == path) {
+            target = index;
+          }
+        }
+      }
+      if (target == -1) {
+        return false;
+      } else {
+        base_folder.del_content(target);
+        return true;
+      }
+
+    } else {
+      // Continue to open
+      let rest_path = path.split("/").slice(1).join("/");
+      let base_content = base_folder.get_content()
+
+      for (let index = 0; index < base_content.length; index++) {
+        let item = base_content[index].get_item()
+        if (item.get_type() == "folder" && item.get_label() == path.split("/")[0]) {
+          item = item as folder;
+          return this.delete_bookmark_process(rest_path, item);
+        }
+      }
+      return false;
+      // if next path part is not found in this directory;
+    }
+  }
+
+  private delete_folder_process(path: string, base_folder?: folder): boolean {
+    if (base_folder === undefined) {
+      base_folder = this.root;
+      // use root as default;
+    }
+
+    if (!path.includes("/")) {
+      // Directly delete in this folder
+      let dir: unified_item[] = base_folder.get_content();
+      let target: number = -1;
+
+      for (let index = 0; index < dir.length; index++) {
+        if (dir[index].get_item().get_type() == "folder") {
+          if (dir[index].get_item().get_label() == path) {
+            target = index;
+          }
+        }
+      }
+      if (target == -1) {
+        return false;
+      } else {
+        base_folder.del_content(target);
+        return true;
+      }
+
+    } else {
+      // Continue to open
+      let rest_path = path.split("/").slice(1).join("/");
+      let base_content = base_folder.get_content()
+
+      for (let index = 0; index < base_content.length; index++) {
+        let item = base_content[index].get_item()
+        if (item.get_type() == "folder" && item.get_label() == path.split("/")[0]) {
+          item = item as folder;
+          return this.delete_folder_process(rest_path, item);
+        }
+      }
+      return false;
+      // if next path part is not found in this directory;
+    }
+  }
+
+  private add_bookmark_process(item: bookmark, path: string, base_folder?: folder): boolean {
+    if (base_folder === undefined) {
+      base_folder = this.root;
+      // use root as default;
+    }
+    let this_folder = base_folder.get_content();
+    let next_folder = path.split("/")[0];
+    let rest_path = "";
+    if (path.includes("/")) {
+      rest_path = path.split("/").slice(1).join("/");
+    }
+
+    if (path == "") {
+      // if at root of this base_folder
+      base_folder.add_content(new unified_item(item));
+      // console.log("[Meow][BunchOfBookmarks] Added bookmark [" + item.get_label() + "]!")
+      return true;
+      // Directly add
+    } else {
+      // continue to open folders
+      for (let index = 0; index < this_folder.length; index++) {
+        let checking = this_folder[index].get_item();
+        if (checking.get_type() == "folder") {
+          if (checking.get_label() == next_folder) {
+            return this.add_bookmark_process(item, rest_path, checking as folder);
+            // next target found
+          }
+        }
+      }
+      // If target folder not found
+      return false;
+    }
+  }
+
+  private add_folder_process(item: folder, path: string, base_folder?: folder): boolean {
+    if (base_folder === undefined) {
+      base_folder = this.root;
+      // use root as default;
+    }
+
+    let this_folder = base_folder.get_content();
+    let next_folder = path.split("/")[0];
+    let rest_path = "";
+    if (path.includes("/")) {
+      rest_path = path.split("/").slice(1).join("/");
+    }
+
+    if (path == "") {
+      // if at root of this base_folder
+      base_folder.add_content(new unified_item(undefined, item));
+      // console.log("[Meow][BunchOfBookmarks] Added folder [" + item.get_label() + "]!")
+      return true;
+      // Directly add
+    } else {
+      // continue to open folders
+      for (let index = 0; index < this_folder.length; index++) {
+        let checking = this_folder[index].get_item();
+        if (checking.get_type() == "folder") {
+          if (checking.get_label() == next_folder) {
+            return this.add_folder_process(item, rest_path, checking as folder);
+            // next target found
+          }
+        }
+      }
+      return false;
+    }
+  }
+}
+
+export class bookmark {
+  private label: string;
+  private link: string;
+  private add_date: number;
+
+  /**
+   * bookmark item, consists of a label, a link and an add timestamp.
+   * @param label A string, the name of this bookmark.
+   * @param link A string, the link of this bookmark.
+   * @param add_date A number, the add date of this folder (timestamp in seconds).
+   * */
+  constructor(label: string, link: string, add_date?: number) {
+    let date = new Date();
+    this.label = label.replaceAll("/", "/");
+    this.link = link;
+    this.add_date = add_date !== undefined ? add_date : Math.round(date.getTime() / 1000);
+  }
+
+  set_label(set: string) {
+    // TODO: Find a better way to access a bookmark directory or store labels
+    // Avoid slash collision in directory access
+    this.label = set.replaceAll("/", "/");
+  }
+
+  set_link(set: string) {
+    this.link = set;
+  }
+
+  get_label() {
+    return this.label;
+  }
+
+  get_link() {
+    return this.link;
+  }
+
+  get_add_date() {
+    return this.add_date;
+  }
+
+  /**
+   * gets the type of this object
+   * @returns "bookmark"
+   * */
+  get_type() {
+    return "bookmark";
+  }
+}
+
+export class folder {
+  private contents: unified_item[] = [];
+  private add_date: number;
+  private last_modified: number;
+  private label: string;
+
+  /**
+   * folder item, consists of a label, an add timestamp, and a last modified timestamp.
+   * @param label A string, the name of this folder.
+   * @param add_date A number, the add date of this folder (timestamp in seconds).
+   * @param last_modified A number, the last modified date of this folder (timestamp in seconds).
+   * */
+  constructor(label: string, add_date?: number, last_modified?: number) {
+    let date = new Date();
+    this.label = label.replaceAll("/", "/");
+    this.add_date = add_date !== undefined ? add_date : Math.round(date.getTime() / 1000);
+    this.last_modified = last_modified !== undefined ? last_modified : Math.round(date.getTime() / 1000);
+  }
+
+  set_label(set: string) {
+    // TODO: Find a better way to access a bookmark directory or store labels
+    // Avoid slash collision in directory access
+    this.label = set.replaceAll("/", "/");
+    // Record last modified time
+    this.last_modified = Math.round((new Date()).getTime() / 1000);
+  }
+
+  get_add_date() {
+    return this.add_date;
+  }
+
+  get_last_modified() {
+    return this.last_modified;
+  }
+
+  get_label() {
+    return this.label;
+  }
+
+  /**
+   * gets the type of this object
+   * @returns "folder"
+   * */
+  get_type() {
+    return "folder";
+  }
+
+  add_content(item: unified_item) {
+    this.contents.push(item);
+  }
+
+  /**
+   * Deletes a bookmark or folder in this directory, according to the index given.
+   * @param index The index of the delete target in this directory.
+   * @returns true if success.
+   * @returns false if failed, due to an index out of bounds, perhaps.
+   * */
+  del_content(index: number) {
+    if (index >= this.contents.length) {
+      // Index out of bounds
+      return false;
+    }
+    this.contents.splice(index, 1);
+    return true;
+  }
+
+  /**
+   * Returns The contents of this folder.
+   * @returns A unified_item[] array.
+   * */
+  get_content() {
+    return this.contents as unified_item[];
+  }
+
+  /**
+   * Returns The bookmarks, excluding folders, of this folder.
+   * @returns A bookmark[] array.
+   * */
+  get_only_bookmarks_content() {
+    let result_bookmarks: bookmark[] = [];
+    for (let index = 0; index < this.contents.length; index++) {
+      let this_item = this.contents[index].get_item();
+      if (this_item.get_type() == 'bookmark') {
+        result_bookmarks.push(this_item as bookmark);
+      }
+    }
+    return result_bookmarks;
+  }
+
+  /**
+   * Get plain information of all bookmarks in this folder and sub folders.
+   * @returns A string[][] array consists of labels and links.
+   * @example [['bing','www.bing.com'],['google','www.google.com']]
+   * */
+  get_plain_bookmarks() {
+    let type: string = "";
+    let item: bookmark | folder;
+    let result: string[][] = [];
+    for (let index = 0; index < this.contents.length; index++) {
+      item = this.contents[index].get_item();
+      type = item.get_type();
+      // Get item data
+      if (type == "bookmark") {
+        item = item as bookmark;
+        result.push([item.get_label(), item.get_link()]); // Push bookmark
+      } else if (type == "folder") {
+        item = item as folder;
+        result = result.concat(item.get_plain_bookmarks());
+      }
+    }
+    return result;
+  }
+
+  /**
+   * Encode this folder into HTML style text.
+   * @param number_of_spaces The number of spaces in the front the line (forming an indent).
+   * @returns The encoded HTML style text of this folder, containing its sub folders.
+   * */
+  get_contents_in_html(number_of_spaces: number) {
+    let spaces: number = number_of_spaces;
+    let type: string = "";
+    let item: bookmark | folder;
+    let result: string[] = [];
+    result.push(" ".repeat(spaces) +
+      "<DT><H3 ADD_DATE=\"" + this.add_date.toString() +
+      "\" LAST_MODIFIED=\"" + this.last_modified.toString() +
+      "\">" + encode_string_to_html_code(this.label) +
+      "</H3>\n" + " ".repeat(spaces) + "<DL><p>"); // Head of folder
+    for (let index = 0; index < this.contents.length; index++) {
+      item = this.contents[index].get_item();
+      type = item.get_type();
+      // Get item data
+      if (type == "bookmark") {
+        item = item as bookmark;
+        result.push(
+          " ".repeat(spaces + 4) +
+            "<DT><A HREF=\"" + ensure_scheme(item.get_link()) +
+            "\" ADD_DATE=\"" + item.get_add_date().toString() +
+            "\">" + encode_string_to_html_code(item.get_label()) + "</A>"
+        ); // Push bookmark
+      } else if (type == "folder") {
+        item = item as folder;
+        result = result.concat(item.get_contents_in_html(spaces + 4));
+      }
+    }
+    result.push(" ".repeat(spaces) + "</DL><p>"); // END of folder
+    return result;
+  }
+}
+
+export class unified_item {
+  private bookmark: bookmark | undefined;
+  private folder: folder | undefined;
+
+  /**
+   * The packaged item of a folder or a bookmark.
+   *
+   * This design is intended to enable bookmarks and folders to be put into the same array.
+   * @param A bookmark or folder item. If is packaging a folder, then should fill an *undefined* for the first field.
+   * @summary Perhaps this is stupid. :P
+   * */
+  constructor(bm?: bookmark, fd?: folder) {
+    if (bm === undefined && fd === undefined) {
+      // If is creating an empty one, then set default
+      this.bookmark = new bookmark("Liny's Browser", "https://github.com/awaLiny2333/LinysBrowser_NEXT");
+      console.error("[Meow][BunchOfBookmarks][ERROR] Creating an empty unified_item, set default.")
+    } else {
+      // at least one of them is not undefined
+      if (bm !== undefined) {
+        // If is packaging a bookmark
+        this.bookmark = bm;
+      } else if (fd !== undefined) {
+        // If is packaging a folder
+        this.folder = fd;
+      }
+    }
+  }
+
+  /**
+   * Get the packaged item of this unified_item.
+   * @returns A bookmark or folder object.
+   * */
+  get_item() {
+    if (this.bookmark !== undefined) {
+      return this.bookmark as bookmark;
+    } else {
+      return this.folder as folder;
+    }
+  }
+}

+ 130 - 0
home/src/main/ets/hosts/bunch_of_defaults.ets

@@ -0,0 +1,130 @@
+import Curves from '@ohos.curves';
+
+/**
+ * Default fontSize_Extra for texts.
+ *
+ * Usually used on titles.
+ * @returns 26
+ * */
+export function fontSize_Extra() {
+  return 24;
+}
+
+/**
+ * Default fontSize_Large for texts.
+ *
+ * Usually used on sub titles.
+ * @returns 20
+ * */
+export function fontSize_Large() {
+  return 20;
+}
+
+/**
+ * Default fontSize_Normal for texts.
+ *
+ * Usually used on body parts.
+ * @returns 16
+ * */
+export function fontSize_Normal() {
+  return 16;
+}
+
+/**
+ * Default fontSize_Icon_Button for HarmonyOS Symbols.
+ *
+ * Usually used on buttons, like linysSymbol.
+ * @returns 25
+ * */
+export function fontSize_Icon_Button() {
+  return 25;
+}
+
+/**
+ * Default capsule bar height for capsule-shape components, like a capsule button.
+ * @returns 35
+ * */
+export function capsule_bar_height() {
+  return 35;
+}
+
+/**
+ * Default minimum width for cards in panels, like those in settings and downloads panel.
+ * @returns 480
+ * */
+export function minimum_card_width() {
+  return 480;
+}
+
+/**
+ * Default animation params.
+ * @returns An AnimateParam, reading the arguments from app settings.
+ * */
+export function animation_default() {
+  let animation_damping_coefficient = AppStorage.get('animation_damping_coefficient') as number;
+  let animation_response = AppStorage.get('animation_response') as number;
+  let spring = Curves.springMotion(animation_response / 100, (100 - animation_damping_coefficient) / 100);
+  let ap: AnimateParam = { curve: spring };
+  return ap;
+}
+
+/**
+ * Standard popup notice duration for intervals of 1 millisecond, 2 seconds.
+ * @returns 2000
+ * */
+export function animation_popup_duration() {
+  return 2000;
+}
+
+/**
+ * Default Click effect.
+ * @returns ClickEffect = { level: ClickEffectLevel.LIGHT }
+ * */
+export function click_effect_default() {
+  let ce: ClickEffect = { level: ClickEffectLevel.LIGHT };
+  return ce;
+}
+
+/**
+ * Default url of blank page.
+ * @returns 'meow://home'
+ * */
+export function url_default_blank() {
+  return 'meow://home';
+}
+
+/**
+ * Preset search engines in a specified string format.
+ * @returns Baidu and Google.
+ * @example 'Bing\nhttps://www.cn.bing.com/search?q=%s'
+ * */
+export function preset_search_engines() {
+  let result = [
+    "Baidu\nhttps://www.baidu.com/s?wd=%s",
+    "Google\nhttps://www.google.com/search?q=%s",
+  ];
+  return result.join("\n");
+}
+
+/**
+ * The single default search engine.
+ * @returns 'https://bing.com/search?q=%s'
+ * */
+export function default_search_engine() {
+  return 'https://bing.com/search?q=%s';
+}
+
+/**
+ * Default user agents in a specified string format.
+ * @returns Firefox, Chrome and Edge.
+ * @example 'Firefox Windows 132\nMozilla/5.0 (Windows NT 10.0; Win64; x64; rv:132.0) Gecko/20100101 Firefox/132.0'
+ * */
+export function default_user_agents() {
+  let result = [
+    "Firefox Windows 132\nMozilla/5.0 (Windows NT 10.0; Win64; x64; rv:132.0) Gecko/20100101 Firefox/132.0",
+    "Edge Windows 130\nMozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36 Edg/130.0.0.0",
+    "Chrome Windows 130\nMozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36",
+    "Chrome Android 130\nMozilla/5.0 (Linux; Android 12; ALN-AL80 Build/5.0.0.107) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Mobile Safari/537.36",
+  ];
+  return result.join("\n");
+}

+ 443 - 0
home/src/main/ets/hosts/bunch_of_downloads.ets

@@ -0,0 +1,443 @@
+import { BusinessError } from '@kit.BasicServicesKit';
+import { fileUri, fileIo as fs, picker } from '@kit.CoreFileKit';
+import { webview } from '@kit.ArkWeb';
+import { add_units_to_size, download_save_from_path } from '../utils/storage_tools';
+import woofWantDownload from '../dialogs/prompts/woofWantDownload';
+import { open_file_uri } from '../utils/link_tools';
+import { meowContext } from '../utils/environment_tools';
+
+let context = meowContext();
+
+export class bunch_of_downloads {
+  list_of_on_going_tasks: webview.WebDownloadItem[] = [];
+  list_of_downloaded_size: number[] = [];
+  list_of_download_speed: number[] = [];
+  list_of_target_folders: string[] = [];
+  list_of_full_size: number[] = [];
+  list_of_file_names: string[] = [];
+  list_of_paused: boolean[] = [];
+  list_of_urls: string[] = [];
+  list_of_completed: boolean[] = [];
+  list_of_additional_info: string[][] = [];
+  list_of_failed: boolean[] = [];
+  last_action: number = 0;
+  // dialogs
+  woofWantDownload_control: CustomDialogController = new CustomDialogController({
+    builder: woofWantDownload({
+      from: '',
+      file_name: '',
+      url: '',
+      try_additional_info: ''
+    }),
+    alignment: DialogAlignment.Center,
+    cornerRadius: 16
+  })
+
+  /**
+   * A class to handle download tasks.
+   * Will ENSURE there is a downloads directory in the sandbox directory on creation.
+   * @param no_init Will not check directory existence nor do anything else.
+   * Usually set true if this object is only created to sit the place of StorageLink initialization.
+   */
+  constructor(no_init?: boolean) {
+    if (no_init == true) {
+      return;
+    }
+    let filesDir = context.filesDir;
+    try {
+      fs.mkdirSync(filesDir + '/downloads');
+    } catch (e) {
+      // console.log('[Meow][bunch_of_downloads] Init: E: /downloads already exists.')
+    }
+
+    // console.log('[Meow][bunch_of_downloads] Init success!')
+  }
+
+  /**
+   * Initializes the download delegate for normal http downloads.
+   * Delegate: AppStorage.get('dl_delegate')
+   * */
+  init_dl_delegate() {
+    // get additional info
+    let additional_info: string[] = [];
+    let try_additional_info = AppStorage.get('universal_new_download_additional_info') as string;
+    if (try_additional_info) {
+      additional_info = try_additional_info.split('\n');
+    }
+
+    // Init dl_delegate
+    let dl_delegate = AppStorage.get('dl_delegate') as webview.WebDownloadDelegate;
+    dl_delegate.onBeforeDownload((webDownloadItem: webview.WebDownloadItem) => {
+
+      // Determine file name
+      let file_name = AppStorage.get('universal_new_download_filename_gateway') as string;
+      AppStorage.set('universal_new_download_filename_gateway', '');
+      // 当文件名未包含后缀,则使用默认文件名
+      if (file_name.length < 3) {
+        file_name = webDownloadItem.getSuggestedFileName();
+      }
+      // Create task folder
+      let file_dir = context.filesDir + '/downloads/' + file_name + '_' + Date.now().toString() + '/';
+      try {
+        fs.mkdirSync(file_dir);
+      } catch (e) {
+        console.log('[Meow][bunch_of_downloads] Task folder already created')
+      }
+      // connect path
+      let download_target_path = file_dir + file_name;
+
+      // Register on download start
+      this.list_of_on_going_tasks.push(webDownloadItem);
+      this.list_of_downloaded_size.push(0);
+      this.list_of_download_speed.push(0);
+      this.list_of_full_size.push(-1);
+      this.list_of_paused.push(false);
+      this.list_of_completed.push(false);
+      this.list_of_additional_info.push(additional_info);
+      this.list_of_target_folders.push(file_dir);
+      this.list_of_file_names.push(file_name);
+      this.list_of_urls.push(webDownloadItem.getUrl());
+      this.list_of_failed.push(false);
+      // this.list_of_urls.push('url');
+
+      // LINK START!
+      try {
+        webDownloadItem.start(download_target_path);
+      } catch (e) {
+        console.error(e);
+        return;
+      }
+      console.log('[bunch_of_downloads] dl_delegate.onBeforeDownload! download_target_path: [' + download_target_path + ']')
+
+      // Update Class
+      this.last_action = Date.now();
+    })
+
+    dl_delegate.onDownloadUpdated((webDownloadItem: webview.WebDownloadItem) => {
+      this.onDownloadUpdated(webDownloadItem);
+    })
+
+    dl_delegate.onDownloadFailed((webDownloadItem: webview.WebDownloadItem) => {
+      console.error("[bunch_of_downloads] download failed guid: " + webDownloadItem.getGuid());
+      console.error("[bunch_of_downloads] download failed error: " + webDownloadItem.getLastErrorCode());
+      this.list_of_failed[this.index_of_task(webDownloadItem)] = true;
+      this.list_of_paused[this.index_of_task(webDownloadItem)] = true;
+      this.last_action = Date.now();
+    })
+
+    dl_delegate.onDownloadFinish((webDownloadItem: webview.WebDownloadItem) => {
+      this.onDownloadFinish(webDownloadItem);
+    })
+  }
+
+  /**
+   * Initializes the download delegate for blob downloads.
+   * Delegate: AppStorage.get('blob_delegate')
+   * */
+  init_blob_delegate() {
+    // Init blob_delegate
+    let blob_delegate = AppStorage.get('blob_delegate') as webview.WebDownloadDelegate;
+
+    blob_delegate.onBeforeDownload((webDownloadItem: webview.WebDownloadItem) => {
+
+      if (webDownloadItem.getUrl().substring(0, 5) != 'blob:') {
+        return;
+      }
+
+      AppStorage.set('universal_new_download_gateway', 'meow');
+      AppStorage.set('universal_new_download_gateway', '');
+
+      let additional_info: string[] = [
+        'url: ' + webDownloadItem.getUrl(),
+        'type: Blob download, so less info here owo.',
+        'meow: meow meow meow', // To express my joy after fixing this download logic @ Jun 22 2025!
+        'totalBytes: ' + webDownloadItem.getTotalBytes().toString(),
+        'mimetype: ' + webDownloadItem.getMimeType(),
+      ];
+
+      // Determine file name
+      let file_name = webDownloadItem.getSuggestedFileName();
+
+      // Create task folder
+      let file_dir = context.filesDir + '/downloads/' + file_name + '_' + Date.now().toString() + '/';
+      try {
+        fs.mkdirSync(file_dir);
+      } catch (e) {
+        console.log('[Meow][bunch_of_downloads] Task folder already created')
+      }
+      // connect path
+      let download_target_path = file_dir + file_name;
+
+      // Register on download start
+      this.list_of_on_going_tasks.push(webDownloadItem);
+      this.list_of_downloaded_size.push(0);
+      this.list_of_download_speed.push(0);
+      this.list_of_full_size.push(-1);
+      this.list_of_paused.push(false);
+      this.list_of_completed.push(false);
+      this.list_of_additional_info.push(additional_info);
+      this.list_of_target_folders.push(file_dir);
+      this.list_of_file_names.push(file_name);
+      this.list_of_urls.push(webDownloadItem.getUrl());
+      this.list_of_failed.push(false);
+      // this.list_of_urls.push('url');
+
+      // LINK START!
+      try {
+        webDownloadItem.start(download_target_path);
+      } catch (e) {
+        console.error(e);
+        return;
+      }
+      console.log('[bunch_of_downloads] dl_delegate.onBeforeDownload! download_target_path: [' + download_target_path + ']')
+
+
+      // Update Class
+      this.last_action = Date.now();
+    })
+
+    blob_delegate.onDownloadUpdated((webDownloadItem: webview.WebDownloadItem) => {
+      this.onDownloadUpdated(webDownloadItem);
+    })
+
+    blob_delegate.onDownloadFailed((webDownloadItem: webview.WebDownloadItem) => {
+      console.error("[bunch_of_downloads] download failed guid: " + webDownloadItem.getGuid());
+      console.error("[bunch_of_downloads] download failed error: " + webDownloadItem.getLastErrorCode());
+      this.list_of_failed[this.index_of_task(webDownloadItem)] = true;
+      this.list_of_paused[this.index_of_task(webDownloadItem)] = true;
+      this.last_action = Date.now();
+    })
+
+    blob_delegate.onDownloadFinish((webDownloadItem: webview.WebDownloadItem) => {
+      this.onDownloadFinish(webDownloadItem);
+    })
+  }
+
+  onDownloadUpdated(webDownloadItem: webview.WebDownloadItem) {
+    // console.log("download update guid: " + webDownloadItem.getGuid());
+
+    // Get my index
+    let my_index = this.index_of_task(webDownloadItem);
+
+    console.info('[bunch_of_downloads] Download progress of index <' + my_index.toString() + '>: '
+      + add_units_to_size(webDownloadItem.getReceivedBytes())
+      + ", total: " + add_units_to_size(webDownloadItem.getTotalBytes()));
+
+    this.list_of_downloaded_size[my_index] = webDownloadItem.getReceivedBytes();
+    this.list_of_full_size[my_index] = webDownloadItem.getTotalBytes();
+    this.list_of_download_speed[my_index] = webDownloadItem.getCurrentSpeed();
+
+    if (webDownloadItem.getState() == webview.WebDownloadState.PAUSED) {
+      this.list_of_paused[my_index] = true;
+    } else if (webDownloadItem.getState() == webview.WebDownloadState.IN_PROGRESS) {
+      this.list_of_paused[my_index] = false;
+    }
+
+    // Refresh
+    this.last_action = Date.now();
+  }
+
+  onDownloadFinish(webDownloadItem: webview.WebDownloadItem) {
+    // console.info('[bunch_of_downloads] Download complete, path: ' + download_target_path);
+
+    // Get my index
+    let my_index = this.index_of_task(webDownloadItem);
+
+    // Update list
+    this.list_of_full_size[my_index] = this.list_of_downloaded_size[my_index];
+    this.list_of_paused[my_index] = false;
+    this.list_of_completed[my_index] = true;
+
+    // webDownloadItem.stop(); // No longer need this! owo
+
+    let direct_download = AppStorage.get('direct_download') as boolean;
+    let direct_download_auto_open = AppStorage.get('direct_download_auto_open') as boolean;
+    if (direct_download) {
+      this.save_downloaded_item_to_download(my_index).then((uri) => {
+        if (uri && direct_download_auto_open) {
+          open_file_uri(uri);
+        }
+      });
+    }
+
+    // Refresh
+    this.last_action = Date.now();
+  }
+
+  /**
+   * Start a download task in this bunch_of_downloads.
+   * @param url The https:// proxy url of the download target
+   * */
+  start_download_task(url: string) {
+    if (url.substring(0, 5) == 'blob:') {
+      return;
+    }
+
+    // Init
+    this.init_dl_delegate();
+
+    // Invoke download
+    console.log('[meow][bunch_of_downloads] Try to invoke download on dl_controller!');
+    let dl_controller = AppStorage.get('dl_controller') as webview.WebviewController;
+
+    try {
+      dl_controller.startDownload(url);
+    } catch (e) {
+      console.error(e);
+    }
+  }
+
+  /**
+   * Pause a download task in this bunch_of_downloads.
+   * @param index The index of target task in the tasks list to be paused.
+   * */
+  pause_task(index: number) {
+    this.list_of_on_going_tasks[index].pause();
+    // this.list_of_paused[index] = true;
+
+    // Refresh
+    // this.last_action = Date.now();
+  }
+
+  /**
+   * Resume a download task in this bunch_of_downloads.
+   * @param index The index of target task in the tasks list to be resumed.
+   * */
+  continue_task(index: number) {
+    this.list_of_on_going_tasks[index].resume();
+    // this.list_of_paused[index] = false;
+
+    // Refresh
+    // this.last_action = Date.now();
+  }
+
+  /**
+   * Delete a download task in this bunch_of_downloads.
+   * @param index The index of target task in the tasks list to be deleted.
+   * */
+  delete_task(index: number) {
+
+    this.list_of_on_going_tasks[index].cancel();
+
+    try {
+      fs.rmdir(this.list_of_target_folders[index]);
+    } catch (e) {
+      console.error("[ERROR][bunch_of_downloads][delete_task] Delete task file failed. " + e)
+    }
+
+    this.list_of_on_going_tasks.splice(index, 1);
+    this.list_of_downloaded_size.splice(index, 1);
+    this.list_of_download_speed.splice(index, 1);
+    this.list_of_target_folders.splice(index, 1);
+    this.list_of_full_size.splice(index, 1);
+    this.list_of_file_names.splice(index, 1);
+    this.list_of_paused.splice(index, 1);
+    this.list_of_urls.splice(index, 1);
+    this.list_of_completed.splice(index, 1);
+    this.list_of_additional_info.splice(index, 1);
+    this.list_of_failed.splice(index, 1);
+
+    this.last_action = Date.now();
+  }
+
+  /**
+   * Move a downloaded item, specified by the index, from sandbox directory to a device directory.
+   * @param index The index of target file in the tasks list to be moved.
+   * */
+  async save_downloaded_item_to_local(index: number) {
+    if (!this.list_of_completed[index]) {
+      return;
+    }
+
+    // Finished task, do job
+    let file_path = this.list_of_target_folders[index];
+    let file_name = this.list_of_file_names[index];
+    let file_size = this.list_of_full_size[index];
+    let target_uri: string[] = [];
+
+    // Save
+    try {
+      let documentSaveOptions = new picker.DocumentSaveOptions();
+      documentSaveOptions.newFileNames = [file_name];
+      let documentPicker = new picker.DocumentViewPicker(context);
+
+      await documentPicker.save(documentSaveOptions).then((documentSaveResult: Array<string>) => {
+        target_uri = documentSaveResult
+        console.log("[bunch_of_downloads][Export] from: " + file_path + file_name)
+        console.log("[bunch_of_downloads][Export] to: " + target_uri[0])
+
+        // let target_path = (new fileUri.FileUri(target_uri[0])).path;
+        // This operation is likely to cause target_path
+        // to be identified as unauthorized!
+
+        let writeLen = file_size;
+        fs.copy(fileUri.getUriFromPath(file_path + file_name), target_uri[0])
+        // .then(() => {
+        //   fs.rmdir(this.list_of_target_folders[index]);
+        // });
+
+        console.info("[bunch_of_downloads] write data to file succeed and size is:" + writeLen + " @ " + target_uri[0]);
+        // this.delete_task(index);
+
+      })
+        .catch((err: BusinessError) => {
+          console.error('[bunch_of_downloads][ERROR] DocumentViewPicker.save failed with err: ' +
+          JSON.stringify(err));
+        });
+
+    } catch (error) {
+      let err: BusinessError = error as BusinessError;
+      console.error('[bunch_of_downloads][ERROR] DocumentViewPicker failed with err: ' + JSON.stringify(err));
+    }
+  }
+
+  /**
+   * Copies a downloaded item, specified by the index, from sandbox directory to device's DOWNLOAD folder.
+   * @param index The index of target file in the task list to be moved.
+   * */
+  async save_downloaded_item_to_download(index: number) {
+    if (!this.list_of_completed[index]) {
+      return;
+    }
+
+    let file_path = this.list_of_target_folders[index];
+    let file_name = this.list_of_file_names[index];
+    let from = file_path + '/' + file_name;
+    console.log('[bunch_of_downloads] Trying to save_downloaded_item_to_download: [' + from + ']');
+    let uri_back = await download_save_from_path(from);
+    return uri_back;
+  }
+
+  /**
+   * Delete all downloaded files in sandbox storage,
+   * usually executed when the app launches.
+   * */
+  delete_all_downloaded_files() {
+    try {
+      let filesDir = context.filesDir;
+      fs.rmdirSync(filesDir + "/downloads");
+      fs.mkdirSync(filesDir + "/downloads")
+
+      // let list = fs.listFileSync(filesDir + "/downloads");
+      //
+      // for (let index = 0; index < list.length; index++) {
+      //   fs.rmdir(filesDir + "/downloads/" + list[index]);
+      // }
+      // Delete all downloaded files
+    } catch (e) {
+      console.error(e)
+    }
+  }
+
+  private index_of_task(webDownloadItem: webview.WebDownloadItem) {
+    for (let index = 0; index < this.list_of_on_going_tasks.length; index++) {
+      const t = this.list_of_on_going_tasks[index];
+      // console.log('[bunch_of_downloads] index_of_task: testing guid t.Guid(): ' + t.getGuid()
+      //   + ', webDownloadItem.getGuid(): ' + webDownloadItem.getGuid() + '.');
+      if (t.getGuid() == webDownloadItem.getGuid()) {
+        return index;
+      }
+    }
+    return -1;
+  }
+}

+ 551 - 0
home/src/main/ets/hosts/bunch_of_history.ets

@@ -0,0 +1,551 @@
+import { sandbox_read_text_sync, sandbox_save, sandbox_unlink_sync } from '../utils/storage_tools';
+import { fileIo as fs } from '@kit.CoreFileKit';
+import { bunch_of_history_index } from './bunch_of_history_index';
+import {
+  ensure_this_month_history_file,
+  ensure_this_month_index_file,
+  history_index_full_rebuild_worker,
+  history_index_load_from_disk_worker,
+  history_index_save_to_disk_worker,
+  history_path_of_month
+} from './bunch_of_history_index_x_functions';
+import { common } from '@kit.AbilityKit';
+import { bunch_of_history_index_lite } from './bunch_of_history_index_lite';
+
+let context = AppStorage.get("context") as common.UIAbilityContext;
+
+export class history_record {
+  accessed_time: number = Date.now();
+  label: string;
+  link: string;
+
+  /**
+   * history_record item, consists of a label, a link and an accessed timestamp.
+   * @param label A string, the name of this bookmark.
+   * @param link A string, the link of this bookmark.
+   * @param accessed_time A number, the access time of this history (timestamp in milliseconds)
+   * */
+  constructor(label: string, link: string, accessed_time?: number) {
+    if (accessed_time !== undefined) {
+      this.accessed_time = accessed_time;
+    }
+    this.label = label;
+    this.link = link;
+  }
+
+  /**
+   * Increases this history's accessed_time by one (1).
+   * */
+  shift_time() {
+    this.accessed_time++;
+  }
+
+  toString() {
+    return "[" + this.accessed_time.toString() + "] " + this.label + " (" + this.link + ") ";
+  }
+}
+
+export class bunch_of_history {
+  static history_index: bunch_of_history_index = new bunch_of_history_index();
+  private history_this_month: history_record[] = [];
+  private current_year: number = new Date().getUTCFullYear();
+  private current_month: number = new Date().getUTCMonth() + 1;
+  private current_opened_file_path: string = '';
+
+  /**
+   * A class holding a history_record[] array, in which there stores history_record objects.
+   * @param no_init Will not open any data from disk nor do anything else.
+   * Usually set true if this object is only created to sit the place of StorageLink initialization.
+   */
+  constructor(no_init?: boolean) {
+    if (no_init == true) {
+      return;
+    }
+
+    let first_launch = false;
+
+    // Creates folders
+    try {
+      fs.mkdirSync(context.filesDir + '/history');
+    } catch (e) {
+      // console.log('[Meow][bunch_of_history] Check disk of month: E: /history folder already exists.')
+    }
+    try {
+      fs.mkdirSync(context.filesDir + '/history-index');
+    } catch (e) {
+      // console.log('[Meow][bunch_of_history] Check disk of month: E: /history folder already exists.')
+    }
+
+    // History
+    if (fs.listFileSync(context.filesDir + '/history-index', { recursion: false }).length == 0) {
+      // First launch of app
+      first_launch = true;
+    } else {
+      // Compatibility path for older BrowserCats
+      this.patch_old_history_files();
+      this.patch_history_files_before_index_era();
+    }
+
+    // Indexing
+    if (fs.listFileSync(context.filesDir + '/history-index', { recursion: false }).length == 0) {
+      if (first_launch) {
+        // No history. First launch of app.
+        console.log(bunch_of_history_index.log_head() + ' First launch of app. Skipping loading index.')
+      } else {
+        console.log(bunch_of_history_index.log_head() + ' No index found. Perhaps this is an upgrade from older BrowserCats?')
+        console.log(bunch_of_history_index.log_head() + ' Rebuilding... ')
+        history_index_full_rebuild_worker();
+      }
+    } else {
+      // Load index
+      // In a concurrent way?!
+      history_index_load_from_disk_worker("normal");
+    }
+
+    // Ensure
+    ensure_this_month_index_file();
+    ensure_this_month_history_file();
+
+    // Load today
+    this.open_month_from_disk_sync(this.current_year, this.current_month);
+
+    // Log
+    // console.log('[Meow][bunch_of_history] bunch_of_history initialization finished!');
+  }
+
+  // Statics
+
+  /**
+   * Connects a year and a month to string.
+   * @param year A number, the year.
+   * @param month A number, the month.
+   * @returns A string, in the format (year - month).
+   * */
+  static year_month_to_string(year: number, month: number) {
+    return "(" + year.toString() + " - " + month.toString() + ")"
+  }
+
+  /**
+   * Lists all the months in which ther are history records made.
+   * @returns A number[][] array, e.g. [[2024, 2], [2023, 4], ...].
+   * */
+  static get_history_months(context_filesDir?: string) {
+    let filesDir: string = context_filesDir || context.filesDir;
+    // Ensure this month
+    ensure_this_month_history_file(context_filesDir);
+    // Traverse directory
+    let result: number[][] = [];
+    let history_files = fs.listFileSync(filesDir + '/history', { recursion: false });
+    for (let index = 0; index < history_files.length; index++) {
+      let f_name = history_files[index].split('.')[0];
+      // 'history_2024_02.txt'
+      let f_split = f_name.split('_');
+      let year = parseInt(f_split[1]);
+      let month = parseInt(f_split[2]);
+      result.push([year, month]);
+    }
+    result = result.sort((a, b) => (b[0] * 12 + b[1]) - (a[0] * 12 + a[1]));
+    console.log('[Meow][bunch_of_history] Got_available_months. Result: ' + result.join(' ') + '.')
+    return result;
+  }
+
+  /**
+   * Search all histories with help of index
+   * @param key the search key
+   * @param max_result_number max result number. if not filled then unlimited.
+   * @returns label-link string[][]
+   * */
+  static search_with_index(key: string, max_result_number?: number): string[][] {
+    // Timer
+    let s = Date.now();
+    // console.log('[Meow][bunch_of_history_index] Start Searching with Index! Start: ' + s.toString())
+
+    let result = bunch_of_history_index.search_history_label_link(key, max_result_number);
+    let result_count = result.length;
+    if (result.length > 0) {
+      console.log('[Meow][bunch_of_history][bunch_of_history_index] ' + result_count.toString() + ' results of Key "' + key + '" ' +
+        'searched in ' + (Date.now() - s).toString() + "ms among " + bunch_of_history_index.index_map.size.toString() + " keys!");
+    }
+    return result;
+  }
+
+  // PATCHES
+
+  /**
+   * Executed when an old format history.txt is found.
+   * This migrates old histories to new format and deletes the old file.
+   * */
+  patch_old_history_files() {
+    let old_history = sandbox_read_text_sync('history.txt');
+    if (old_history != 'undefined') {
+      console.log('[Meow][bunch_of_history] Patch old history: found old history:\n' + old_history.toString())
+      this.import_string_full(old_history);
+      sandbox_unlink_sync('history.txt');
+    }
+  }
+
+  /**
+   * This migration happens between 1.7.6(1000024) and 1.7.7(1000025)
+   * to switch all original timestamps into UTC time.
+   * */
+  patch_history_files_before_index_era() {
+    let version_file = sandbox_read_text_sync('last_app_versionCode.txt');
+    if (version_file != 'undefined') {
+      return;
+    }
+    console.log('[Meow][bunch_of_history] Patch old history of migration happens between 1.7.6(1000024) and 1.7.7(1000025).')
+
+    let result: string = "";
+    let months = bunch_of_history.get_history_months();
+    for (let index = 0; index < months.length; index++) {
+      const year = months[index][0];
+      const month = months[index][1];
+      const path = history_path_of_month(year, month);
+      if (result == "") {
+        result = sandbox_read_text_sync(path);
+      } else {
+        result = result + "\n" + sandbox_read_text_sync(path);
+      }
+      // console.log(result.split("\n").length.toString());
+    }
+
+    // clear history
+    try {
+      let filesDir = context.filesDir;
+      fs.rmdirSync(filesDir + "/history");
+      fs.mkdirSync(filesDir + "/history")
+    } catch (e) {
+      console.error(e)
+    }
+
+    this.import_string_full(result);
+  }
+
+  // Properties
+
+  get_current_year() {
+    return this.current_year;
+  }
+
+  get_current_month() {
+    return this.current_month;
+  }
+
+  get_history_this_month() {
+    return this.history_this_month;
+  }
+
+  // Operations
+
+  /**
+   * Adds a history, automatically puts it in the order of time.
+   * @param history A history_record object.
+   * @param no_reconstruct_plain_history A boolean, will not reconstruct plain history cache if set true.
+   * */
+  add_history(history: history_record, instantly_save_to_disk: boolean, index: boolean, layer?: number) {
+    // console.log('[Meow][bunch_of_history]' + '    '.repeat(layer || 0) + '(' + (layer || 0).toString() + ') Add history: ' + history.toString());
+    let history_date = new Date(history.accessed_time);
+    let year = history_date.getUTCFullYear();
+    let month = history_date.getUTCMonth() + 1;
+
+    // Opens the month required
+    this.open_month_from_disk_sync(year, month);
+
+    // Determine insert position
+    let insert_position = this.index_of_first_record_at_or_after_time(history.accessed_time);
+    if (insert_position < 0) {
+      insert_position = this.history_this_month.length;
+    } else {
+      // Avoid same accessed_time
+      while (this.history_this_month[insert_position].accessed_time == history.accessed_time) {
+        insert_position++;
+        history.shift_time();
+        if (insert_position >= this.history_this_month.length) {
+          insert_position = this.history_this_month.length;
+          break;
+        }
+      }
+    }
+
+    // Perhaps insert position determination process caused this history item to shift time, entering another month.
+    // check if this reaches another month
+    let new_history_date = new Date(history.accessed_time);
+    let new_year = new_history_date.getUTCFullYear();
+    let new_month = new_history_date.getUTCMonth() + 1;
+    if (new_year * 100 + new_month != year * 100 + month) {
+      // Reaches another month
+      this.add_history(history, instantly_save_to_disk, index, (layer || 0) + 1);
+    } else {
+      // Still in this month
+      this.history_this_month.splice(insert_position, 0, history);
+      // Saving
+      if (instantly_save_to_disk) {
+        this.save_month_to_disk();
+      }
+      // Indexing
+      if (index) {
+        let history_index_loading = AppStorage.get('history_index_loading') as boolean;
+        if (history_index_loading) {
+          // Loading task is ongoing
+          console.log("[Meow][bunch_of_history][bunch_of_history_index] History_index_loading task is ongoing! Interrupted this one.")
+        } else {
+          // Add to index
+          try {
+            // TODO: IMPROVE THIS LOGIC to avoid missing add_index_string jobs.
+            // ↑ This got relaxed due to monthly division of index system.
+            // This try catch is just to avoid crashes.
+            bunch_of_history_index.add_index_string_full(history.label + " " + encodeURI(history.link), history.accessed_time);
+            // TODO: This only works when history is in same month.
+            bunch_of_history_index_lite.add_index_string(history.label + " " + encodeURI(history.link), history.accessed_time);
+          } catch (e) {
+            console.error(e);
+          }
+          try {
+            // TODO: figure out why history_index_loading is false for the initial loading
+            console.log(bunch_of_history_index_lite.log_head() + " Loading: " + (AppStorage.get('history_index_loading') as boolean ? "loading." : "OK."));
+            history_index_save_to_disk_worker(bunch_of_history_index_lite.index_map, bunch_of_history_index_lite.this_file_name(), false);
+          } catch (e) {
+            // Import not finished
+            console.error(e);
+          }
+        }
+      }
+    }
+  }
+
+  /**
+   * Removes some selected histories, according to a given array of indices.
+   *
+   * Should be preferred when deleting a lot of history records.
+   * @param indices A number[] array, indicating the indices of the items to be deleted.
+   * @param no_reconstruct_plain_history A boolean, will not reconstruct plain history cache if set true.
+   * */
+  remove_histories_at_indices(year: number, month: number, indices: number[], instantly_save_to_disk: boolean) {
+
+    // Opens the month required
+    this.open_month_from_disk_sync(year, month);
+
+    indices.sort((a, b) => {
+      return a - b;
+    });
+
+    let selected = new Array<boolean>(this.history_this_month.length);
+    let result_history: history_record[] = [];
+
+    for (let i = 0; i < indices.length; i++) {
+      selected[indices[i]] = true;
+    }
+
+    for (let i = 0; i < this.history_this_month.length; i++) {
+      if (selected[i] == true) {
+        // Marked to be deleted
+      } else {
+        result_history.push(this.history_this_month[i]);
+      }
+    }
+
+    this.history_this_month = result_history;
+
+    // Save
+    if (instantly_save_to_disk) {
+      this.save_month_to_disk();
+    }
+  }
+
+  /**
+   * Get the index of the first history item whose access_time is not smaller than Target.
+   * @param timestamp The limit of earliest timestamp.
+   * @returns A positive number, the index.
+   * @returns -1 if list is empty.
+   * @returns -2 if Target is bigger than all histories.
+   * */
+  index_of_first_record_at_or_after_time(timestamp: number) {
+    if (this.history_this_month.length == 0) {
+      return -1;
+    }
+    if (timestamp > this.history_this_month[this.history_this_month.length - 1].accessed_time) {
+      return -2;
+    }
+    if (timestamp <= this.history_this_month[0].accessed_time) {
+      return 0;
+    }
+
+    // Binary search
+    let start = 0;
+    let end = this.history_this_month.length - 1;
+    let mid = Math.floor((start + end) / 2);
+    while (start <= end) {
+      mid = Math.floor((start + end) / 2);
+      let mid_time = this.history_this_month[mid].accessed_time;
+      if (mid_time > timestamp) {
+        end = mid - 1;
+      }
+      if (mid_time < timestamp) {
+        start = mid + 1;
+      }
+      if (mid_time == timestamp) {
+        while (mid > 0 && this.history_this_month[mid].accessed_time == timestamp) {
+          // in case of same access_time
+          // choose the front-most
+          mid -= 1;
+        }
+        break;
+      }
+    }
+    if (mid + 1 < this.history_this_month.length && this.history_this_month[mid].accessed_time < timestamp) {
+      // Make sure the history at mid has an access_time equal to or larger than Target timestamp
+      mid = mid + 1;
+    }
+    return mid;
+  }
+
+  // Saving
+
+  /**
+   * Opens a month of history using lite import, that is, assuming all of them are in time range one month.
+   * @param path A string, the path of the file.
+   * */
+  open_month_from_disk_sync(year: number, month: number) {
+    if (history_path_of_month(year, month) == this.current_opened_file_path) {
+      // Already opened
+      // console.log('already opened')
+      return;
+    }
+    ensure_this_month_history_file();
+    let path = history_path_of_month(year, month);
+    let the_file = sandbox_read_text_sync(path);
+    this.current_opened_file_path = path;
+    this.current_year = year;
+    this.current_month = month;
+    if (the_file == "undefined" || the_file.length <= 1) {
+      console.info("[Meow][bunch_of_history] Import history SKIPPED: Too short. (" + path + ").");
+      return;
+    }
+    this.import_string_lite(the_file);
+  }
+
+  /**
+   * Saves the current opened file to disk
+   * */
+  save_month_to_disk() {
+    sandbox_save(this.current_opened_file_path, this.export_string());
+  }
+
+  /**
+   * Export history records in a specific plain text format.
+   * @returns "\n" connected string of history records in the format of:
+   * @example 'bing\nbing.com\n127771721'
+   * */
+  private export_string() {
+    let export_list: string[] = []
+    for (let index = 0; index < this.history_this_month.length; index++) {
+      let history_item: history_record = this.history_this_month[index];
+      export_list.push(history_item.label);
+      export_list.push(history_item.link);
+      export_list.push(history_item.accessed_time.toString());
+    }
+    return export_list.join("\n");
+  }
+
+  /**
+   * Import history records in a specific plain text format.
+   *
+   * Usually used when to import a file containing histories spanning over months.
+   *
+   * In default overwrites whatever was in this history_list.
+   * @param imp The string in the correct format:
+   * @example 'bing\nbing.com\n127771721'
+   * */
+  private import_string_full(imp: string) {
+    let import_list: string[] = imp.split("\n");
+    if (import_list.length % 3 > 0) {
+      // Incorrect format, log an error.
+      // Though this is not very likely to happen so often
+      // since all the txt files are generated by a fixed algorithm.
+      console.error("[ERROR][Meow][bunch_of_history] Import history Error: not 3*n length. Raw string: \n" + imp)
+      return;
+    }
+
+    console.log("[Meow][bunch_of_history] Starting to import history");
+    let start_time = Date.now();
+
+    // Clear
+    this.history_this_month = [];
+
+    let last_month = [0, 0];
+    for (let index = 0; index < import_list.length; index += 3) {
+      // if (index % (3 * 10) == 0) {
+      //   console.log("[Meow][bunch_of_history] Import progress: " + (index * 100 / import_list.length).toFixed(2) + "%")
+      // }
+      if (import_list[index] != "") {
+        let access_time = Number.parseInt(import_list[index+2]);
+        let access_Date = new Date(access_time);
+        let new_month = [new Date(access_time).getUTCFullYear(), new Date(access_time).getUTCMonth() + 1];
+
+        if ((last_month[0] != new_month[0]) || (last_month[1] != new_month[1])) {
+          // if changed month, then save this file
+          if ((last_month[0] != 0) && (last_month[1] != 0)) {
+            console.log('[Meow][bunch_of_history] Import history saved month ' + last_month.toString() + 'to disk.');
+            this.save_month_to_disk();
+            this.history_this_month = []; // I believe this should be added
+          }
+        }
+
+        let new_history_record = new history_record(import_list[index], import_list[index+1], access_time);
+        this.add_history(new_history_record, false, false);
+
+        last_month = [access_Date.getUTCFullYear(), access_Date.getUTCMonth() + 1];
+      }
+    }
+    this.save_month_to_disk();
+    console.log('[Meow][bunch_of_history] Import history success! Time used: ' +
+    (Date.now() - start_time).toString() + "ms.");
+  }
+
+  /**
+   * Import history records in a specific plain text format.
+   *
+   * Assuming that all records are in one month.
+   *
+   * In default overwrites whatever was in this history_list.
+   *
+   * Usually used when importing a month file, because it is ensured to be legal.
+   * @param imp The string in the correct format:
+   * @example 'bing\nbing.com\n127771721'
+   * */
+  private import_string_lite(imp: string) {
+    let import_list: string[] = imp.split("\n");
+    if (import_list.length <= 1) {
+      // This is theoretically prevented in previous codes
+      // As far as this function is used in such way (19 May, 2025)
+      console.log("[Meow][bunch_of_history][lite] Import history string SKIPPED: too short length. Raw string: '" + imp + "'.");
+      return;
+    }
+    if (import_list.length % 3 > 0) {
+      // Incorrect format, log an error.
+      // Though this is not very likely to happen so often
+      // since all the txt files are generated by a fixed algorithm.
+      console.error("[ERROR][Meow][bunch_of_history][lite] Import history Error: not 3*n length. Raw string: '" + imp + "'.")
+      return;
+    }
+
+    console.log("[Meow][bunch_of_history][lite] Starting to import history");
+    let start_time = Date.now();
+
+    // Clear
+    this.history_this_month = [];
+
+    for (let index = 0; index < import_list.length; index += 3) {
+      // if (index % 1000 == 0) {
+      //   console.log("[Meow][bunch_of_history][lite] Import progress: " + (index * 100 / import_list.length).toFixed(2) +
+      //     "%")
+      // }
+      let access_time = Number.parseInt(import_list[index+2]);
+      let new_history_record = new history_record(import_list[index], import_list[index+1], access_time);
+      this.add_history(new_history_record, false, false);
+    }
+
+    console.log('[Meow][bunch_of_history][lite] Import history success! Time used: ' +
+    (Date.now() - start_time).toString() + "ms.");
+  }
+}

+ 261 - 0
home/src/main/ets/hosts/bunch_of_history_index.ets

@@ -0,0 +1,261 @@
+import { sandbox_read_text_sync } from '../utils/storage_tools';
+import { fileIo as fs } from '@kit.CoreFileKit';
+import { collections } from '@kit.ArkTS';
+import { divide_string, get_histories_from_disk, index_file_name_of_month, insert_into_array } from './bunch_of_history_index_x_functions';
+import { bunch_of_history_index_lite } from './bunch_of_history_index_lite';
+import { common } from '@kit.AbilityKit';
+
+export class bunch_of_history_index {
+  static index_map: collections.Map<string, collections.Array<number>> = new collections.Map<string, collections.Array<number>>();
+
+  constructor() {
+    // Create history index folder
+    try {
+      let context = AppStorage.get("context") as common.UIAbilityContext;
+      let filesDir = context.filesDir;
+      fs.mkdirSync(filesDir + '/history-index');
+    } catch (e) {
+      // console.log(log_head() + 'Check disk of month: E: /history-index folder already exists.')
+    }
+  }
+
+  /**
+   * Clears index by setting it a new empty Map.
+   * */
+  static clear() {
+    bunch_of_history_index.index_map = new collections.Map<string, collections.Array<number>>();
+  }
+
+  /**
+   * Searches in the index_map and return eligible history of label-links
+   * @param key the keywords, for example "huawei developers harmony"
+   * @returns string[][] of label-links
+   * */
+  static search_history_label_link(key: string, max?: number) {
+    let history_index_saving = AppStorage.get('history_index_saving') as boolean | undefined;
+    let history_index_loading = AppStorage.get('history_index_loading') as boolean | undefined;
+    let reindexing = AppStorage.get('reindexing_history') as boolean | undefined;
+
+    if (history_index_saving == true) {
+      console.log(bunch_of_history_index.log_head() + " A history_index_saving task is ongoing! Interrupted this search_history_label_link.");
+      return [];
+    }
+    if (history_index_loading == true) {
+      console.log(bunch_of_history_index.log_head() + " A history_index_loading task is ongoing! Interrupted this search_history_label_link.");
+      return [];
+    }
+    if (reindexing == true) {
+      console.log(bunch_of_history_index.log_head() + " A reindexing task is ongoing! Interrupted this search_history_label_link.");
+      return [];
+    }
+
+    // TODO: find a more elegant way instead of reverse().
+    let stamps = bunch_of_history_index.search_timestamps(key, max);
+    stamps.reverse();
+    let result = get_histories_from_disk(stamps, max);
+    result.reverse();
+    return result;
+  }
+
+  /**
+   * Searches in the index_map and return eligible timestamps
+   * @param key the keywords, for example "huawei developers harmony"
+   * @returns number[] of timestamps
+   * */
+  static search_timestamps(key: string, max?: number) {
+    let div_keys = divide_string(key);
+    // console.log("[qwq]" + div_keys.toString());
+    let ranges: collections.Array<collections.Array<number>> = new collections.Array<collections.Array<number>>();
+
+    for (const key of div_keys) {
+      // Pushes it
+      ranges.push(bunch_of_history_index.index_map.get(key) || new collections.Array<number>());
+      // console.log("[awa]" + (bunch_of_history_index.index_map.get(key) || []).toString());
+    }
+    // console.log(log_head() + "Found ranges of keys: " + ranges.join(" "));
+    return bunch_of_history_index.findCommonElements(ranges, max);
+  }
+
+  /**
+   * Index from built index file.
+   * @param path the path of index file, for example, 'history-index/index_2002_12.txt'.
+   * @param context_filesDir getContext().filesDir.
+   * */
+  static index_from_index_file(path: string, context_filesDir: string) {
+    // Open the file
+    let imp = sandbox_read_text_sync(path, context_filesDir);
+    let import_list: string[] = imp.split("\n");
+
+    // traverse each key - values
+    for (let index = 0; index < import_list.length - 1; index += 2) {
+      const key = import_list[index];
+      const values = import_list[index+1].split("_");
+
+      // TODO: Improve logics here to boost efficiency!
+      for (let j = 0; j < values.length; j++) {
+        bunch_of_history_index.add_index_key_string(key, parseInt(values[j]), false);
+      }
+    }
+  }
+
+  /**
+   * Pushes a timestamp to the end of values of str (keys).
+   * @param key the SINGLE, SPECIFIC key.
+   * @param timestamp the timestamp.
+   * @param check_month set true to check if this action fits in the currently opened month in bunch_of_history_index_lite.
+   * */
+  static add_index_key_string(key: string, timestamp: number, check_month: boolean) {
+    if (check_month) {
+      const current_year: number = new Date(timestamp).getUTCFullYear();
+      const current_month: number = new Date(timestamp).getUTCMonth() + 1;
+      if (index_file_name_of_month(current_year, current_month) != bunch_of_history_index_lite.this_file_name()) {
+        // This could happen when system time is manually changed.
+        // DONT DO THIS IN YOUR DAILY USE PLZ.
+        return;
+      }
+    }
+
+    if (bunch_of_history_index.index_map.has(key)) {
+      // console.log(bunch_of_history_index.index_map.get(item)?.toString());
+      // bunch_of_history_index.index_map.get(key)!.push(timestamp);
+
+      let got_array = bunch_of_history_index.index_map.get(key)!;
+      let old_last = got_array[got_array.length-1];
+      if (timestamp < old_last) {
+        console.log(bunch_of_history_index.log_head() + ' Adding new index (' + timestamp + ') earlier than the old ones (' + old_last + ')??? How???');
+      } else {
+        got_array.push(timestamp);
+      }
+    } else {
+      bunch_of_history_index.index_map.set(key, new collections.Array<number>(timestamp));
+    }
+  }
+
+  /**
+   * Pushes a timestamp to the correct position of values of str (keys).
+   * @param str the keys.
+   * @param timestamp the timestamp.
+   * */
+  static add_index_string_full(str: string, timestamp: number) {
+    // Divide into words
+    let all_words = divide_string(str.toUpperCase());
+
+    // Traverse and add
+    for (const item of all_words) {
+      let original = bunch_of_history_index.index_map.get(item) || new collections.Array<number>();
+      // concat it in ascending order
+      bunch_of_history_index.index_map.set(item, insert_into_array(original, timestamp));
+      // bunch_of_history_index.index_map.get(item)?.push(timestamp);
+    }
+  }
+
+  /**
+   * Returns the intersection of two array of unique, ascending numbers, in O(m+n)
+   *
+   * 双指针法求两个有序数组的交集(O(m+n))
+   * @param a the first number[]
+   * @param b the other number[]
+   * @returns their intersection
+   * @author Generated by DeepSeek on 14th May, 2025, modified by awa_Liny
+   * */
+  static intersect(a: number[], b: number[]): number[] {
+    let res: number[] = [];
+    let i = 0, j = 0;
+
+    while (i < a.length && j < b.length) {
+      if (a[i] === b[j]) {
+        // 确保不重复(根据题意数组本身无重复)
+        res.push(a[i]);
+        i++;
+        j++;
+      } else if (a[i] < b[j]) {
+        i++;
+      } else {
+        j++;
+      }
+    }
+
+    return res;
+  }
+
+  /**
+   * Returns the common elements appearing in arrays of unique numbers in ascending orders.
+   * @param ranges the arrays
+   * @returns another array
+   * @author Generated by DeepSeek on 16th May, 2025, modified by awa_Liny
+   * */
+  static findCommonElements(ranges: collections.Array<collections.Array<number>>, max?: number): number[] {
+    if (ranges.length === 0) {
+      return [];
+    }
+
+    // Find the shortest array as the base to minimize iterations
+    let baseArray = ranges[0];
+    for (const arr of ranges) {
+      if (arr.length < baseArray.length) {
+        baseArray = arr;
+      }
+    }
+
+    const result: number[] = [];
+    for (let k = baseArray.length - 1; k >= 0; k--) {
+      const val = baseArray[k];
+      let isCommon = true;
+
+      for (const arr of ranges) {
+        if (arr === baseArray) {
+          continue;
+        } // Skip the base array itself
+
+        if (!bunch_of_history_index.binarySearch(arr, val)) {
+          isCommon = false;
+          break;
+        }
+      }
+      if (isCommon) {
+        result.push(val);
+        if (max !== undefined && result.length === max) {
+          return result;
+        }
+      }
+    }
+
+    return result;
+  }
+
+  /**
+   * Helper function to perform binary search
+   * @author Generated by DeepSeek on 16th May, 2025, modified by awa_Liny
+   * */
+  static binarySearch(arr: collections.Array<number>, target: number): boolean {
+    let left = 0;
+    let right = arr.length - 1;
+    while (left <= right) {
+      const mid = Math.floor((left + right) / 2);
+      if (arr[mid] === target) {
+        return true;
+      } else if (arr[mid] < target) {
+        left = mid + 1;
+      } else {
+        right = mid - 1;
+      }
+    }
+    return false;
+  }
+
+  /**
+   * The head of logging information.
+   * @returns "[Meow][bunch_of_history_index]"
+   * */
+  static log_head() {
+    return '[Meow][bunch_of_history_index]';
+  }
+
+  /**
+   * The head of logging information for workers.
+   * @returns "[Meow][bunch_of_history_index]"
+   * */
+  static log_head_worker() {
+    return '[Meow][bunch_of_history_index][Worker]';
+  }
+}

+ 190 - 0
home/src/main/ets/hosts/bunch_of_history_index_lite.ets

@@ -0,0 +1,190 @@
+import { sandbox_read_text_sync } from '../utils/storage_tools';
+import { collections } from '@kit.ArkTS';
+import { divide_string, history_path_of_month, index_file_name_of_month, index_path_of_month, insert_into_array } from './bunch_of_history_index_x_functions';
+
+export class bunch_of_history_index_lite {
+  static index_map: collections.Map<string, collections.Array<number>> = new collections.Map<string, collections.Array<number>>();
+  static this_month: number;
+  static this_year: number;
+
+  /**
+   * This bunch_of_history_index could only clear and push data in a sorted manner.
+   * */
+  constructor() {
+  }
+
+  /**
+   * Clears index by setting it a new empty Map.
+   * */
+  static clear() {
+    bunch_of_history_index_lite.index_map = new collections.Map<string, collections.Array<number>>();
+  }
+
+  /**
+   * Returns the opened file name of this lite index.
+   * @returns the file name
+   * */
+  static this_file_name(): string {
+    return index_file_name_of_month(bunch_of_history_index_lite.this_year, bunch_of_history_index_lite.this_month);
+  }
+
+  /**
+   * Index from history file.
+   * @param path the path of history file, for example, 'history/history_2002_12.txt'.
+   * @param context_filesDir getContext().filesDir.
+   * */
+  static index_from_history_month_file(year: number, month: number, context_filesDir: string) {
+    // Set year and month
+    bunch_of_history_index_lite.this_year = year;
+    bunch_of_history_index_lite.this_month = month;
+
+    // Open the file
+    let path = history_path_of_month(year, month);
+    let imp = sandbox_read_text_sync(path, context_filesDir);
+
+    let import_list: string[] = imp.split("\n");
+    if (import_list.length <= 1) {
+      console.info(bunch_of_history_index_lite.log_head() + " Import history SKIPPED: Too short. (" + path + ").");
+      return;
+    }
+    if (import_list.length % 3 > 0) {
+      console.error("[ERROR]" + bunch_of_history_index_lite.log_head() + " Import history Error: not 3*n length. Raw string: \n" + imp);
+      return;
+    }
+
+    // Traverse
+    for (let index = 0; index < import_list.length; index += 3) {
+      let access_time = parseInt(import_list[index+2]);
+      bunch_of_history_index_lite.add_index_string(import_list[index] + " " + import_list[index+1], access_time);
+    }
+  }
+
+  /**
+   * Index from built index file.
+   * @param path the path of index file, for example, 'history-index/index_2002_12.txt'.
+   * @param context_filesDir getContext().filesDir.
+   * */
+  static index_from_index_month_file(year: number, month: number, context_filesDir: string) {
+    // Set year and month
+    bunch_of_history_index_lite.this_year = year;
+    bunch_of_history_index_lite.this_month = month;
+
+    // Open the file
+    let path = index_path_of_month(year, month);
+    let imp = sandbox_read_text_sync(path, context_filesDir);
+    let import_list: string[] = imp.split("\n");
+
+    if (import_list.length <= 1) {
+      console.info(bunch_of_history_index_lite.log_head() + " Import index SKIPPED: Too short. (" + path + ").");
+      return;
+    }
+
+    // traverse each key - values
+    for (let index = 0; index < import_list.length - 1; index += 2) {
+      const key = import_list[index];
+      const values = import_list[index+1].split("_");
+
+      // TODO: Improve logics here to boost efficiency!
+      for (let j = 0; j < values.length; j++) {
+        bunch_of_history_index_lite.add_index_key_string(key, parseInt(values[j]));
+      }
+    }
+  }
+
+  /**
+   * Pushes a timestamp to the end of values of str (keys).
+   * @param str the keys.
+   * @param timestamp the timestamp.
+   * */
+  static add_index_string(str: string, timestamp: number) {
+    const target_year: number = new Date(timestamp).getUTCFullYear();
+    const target_month: number = new Date(timestamp).getUTCMonth() + 1;
+    if (index_file_name_of_month(target_year, target_month) != bunch_of_history_index_lite.this_file_name()) {
+      // This could happen when system time is manually changed.
+      // DONT DO THIS IN YOUR DAILY USE PLZ.
+      const this_year_month: string = bunch_of_history_index_lite.this_year.toString() + ' - ' + bunch_of_history_index_lite.this_month.toString();
+      const target_year_month: string = target_year.toString() + ' - ' + target_month.toString();
+      console.log(bunch_of_history_index_lite.log_head() +
+        ' Adding history to another month (' + target_year_month + ') but current month loaded is ' + this_year_month + '! Interrupted add_index_string.');
+      return;
+    }
+
+    // Divide into words
+    let all_words = divide_string(str.toUpperCase());
+
+    // Traverse and add
+    for (const item of all_words) {
+      if (bunch_of_history_index_lite.index_map.has(item)) {
+        let got_array = bunch_of_history_index_lite.index_map.get(item)!;
+        let old_last = got_array[got_array.length-1];
+        if (timestamp < old_last) {
+          console.log(bunch_of_history_index_lite.log_head() + ' Adding new index (' + timestamp + ') earlier than the old ones (' + old_last + ')??? How???');
+        } else {
+          got_array.push(timestamp);
+        }
+      } else {
+        bunch_of_history_index_lite.index_map.set(item, new collections.Array<number>(timestamp));
+      }
+    }
+  }
+
+  /**
+   * Pushes a timestamp to the correct position of values of str (keys).
+   * @param str the keys.
+   * @param timestamp the timestamp.
+   * */
+  static add_index_string_full(str: string, timestamp: number) {
+    const target_year: number = new Date(timestamp).getUTCFullYear();
+    const target_month: number = new Date(timestamp).getUTCMonth() + 1;
+    if (index_file_name_of_month(target_year, target_month) != bunch_of_history_index_lite.this_file_name()) {
+      // This could happen when system time is manually changed.
+      // DONT DO THIS IN YOUR DAILY USE PLZ.
+      const this_year_month: string = bunch_of_history_index_lite.this_year.toString() + ' - ' + bunch_of_history_index_lite.this_month.toString();
+      const target_year_month: string = target_year.toString() + ' - ' + target_month.toString();
+      console.log(bunch_of_history_index_lite.log_head() +
+        ' Adding history to another month (' + target_year_month + ') but current month loaded is ' + this_year_month + '! Interrupted add_index_string_full.');
+      return;
+    }
+
+    // Divide into words
+    let all_words = divide_string(str.toUpperCase());
+
+    // Traverse and add
+    for (const item of all_words) {
+      let original = bunch_of_history_index_lite.index_map.get(item) || new collections.Array<number>();
+      // concat it in ascending order
+      bunch_of_history_index_lite.index_map.set(item, insert_into_array(original, timestamp));
+      // bunch_of_history_index.index_map.get(item)?.push(timestamp);
+    }
+  }
+
+  /**
+   * Pushes a timestamp to the end of the values of key (only ONE key).
+   * @param key the key, for example, "BING".
+   * @param timestamp the timestamp of history record to be linked to.
+   * */
+  static add_index_key_string(key: string, timestamp: number) {
+    if (bunch_of_history_index_lite.index_map.has(key)) {
+      // console.log(bunch_of_history_index.index_map.get(item)?.toString());
+      bunch_of_history_index_lite.index_map.get(key)!.push(timestamp);
+    } else {
+      bunch_of_history_index_lite.index_map.set(key, new collections.Array<number>(timestamp));
+    }
+  }
+
+  /**
+   * The head of logging information.
+   * @returns "[Meow][bunch_of_history_index_indexer_only]"
+   * */
+  static log_head() {
+    return '[Meow][bunch_of_history_index_indexer_only]';
+  }
+
+  /**
+   * The head of logging information for workers.
+   * @returns "[Meow][bunch_of_history_index]"
+   * */
+  static log_head_worker() {
+    return '[Meow][bunch_of_history_index_add_only][Worker]';
+  }
+}

+ 569 - 0
home/src/main/ets/hosts/bunch_of_history_index_x_functions.ets

@@ -0,0 +1,569 @@
+import { collections, MessageEvents, worker } from '@kit.ArkTS';
+import { sandbox_read_text_sync, sandbox_save } from '../utils/storage_tools';
+import { bunch_of_history_index } from './bunch_of_history_index';
+import { bunch_of_history_index_lite } from './bunch_of_history_index_lite';
+import { fileIo } from '@kit.CoreFileKit';
+import { common } from '@kit.AbilityKit';
+
+let context = AppStorage.get("context") as common.UIAbilityContext;
+
+/**
+ * Calls a worker thread to load built index from disk.
+ * @param load_type either "normal" or "reindex".
+ * */
+export function history_index_load_from_disk_worker(load_type: string) {
+  let history_index_saving = AppStorage.get('history_index_saving') as boolean | undefined;
+  let history_index_loading = AppStorage.get('history_index_loading') as boolean | undefined;
+  let reindexing = AppStorage.get('reindexing_history') as boolean | undefined;
+
+  if (history_index_saving == true) {
+    console.log(bunch_of_history_index.log_head() +
+      " A history_index_saving task is ongoing! Interrupted this history_index_load_from_disk_worker.")
+    return;
+  }
+  if (history_index_loading == true) {
+    console.log(bunch_of_history_index.log_head() +
+      " Another history_index_loading task is ongoing! Interrupted this history_index_load_from_disk_worker.")
+    return;
+  }
+  if (reindexing == true && load_type != 'reindex') {
+    console.log(bunch_of_history_index.log_head() +
+      " A reindexing task is ongoing! Interrupted this history_index_load_from_disk_worker.")
+    return;
+  }
+
+  AppStorage.set('history_index_loading', true);
+
+  ensure_this_month_index_file();
+
+  let workerInstance = new worker.ThreadWorker("home/ets/workers/History_index_loader.ets");
+  workerInstance.postMessage(context.filesDir);
+
+  console.log(bunch_of_history_index.log_head() + " Full Index loader worker CREATED!");
+
+  workerInstance.onmessage = (e: MessageEvents): void => {
+    if (typeof e.data == "string") {
+      // Coming back with progress
+      let data: string = e.data;
+      AppStorage.set('history_index_loading_progress', data);
+    } else {
+      // Coming back with the whole set
+      let result: collections.Array<collections.Map<string, collections.Array<number>>> = e.data;
+      bunch_of_history_index.index_map = result[0];
+      bunch_of_history_index_lite.index_map = result[1];
+
+      // Set info for month_host (bunch_of_history_index_lite)
+      const current_year: number = new Date().getUTCFullYear();
+      const current_month: number = new Date().getUTCMonth() + 1;
+      bunch_of_history_index_lite.this_year = current_year;
+      bunch_of_history_index_lite.this_month = current_month;
+
+      // STOP
+      workerInstance.terminate();
+      console.log(bunch_of_history_index.log_head() + " Got load result. Whole Map size: " + bunch_of_history_index.index_map.size.toString() + ".");
+      console.log(bunch_of_history_index_lite.log_head() + " Got load result. Month Map size: " + bunch_of_history_index_lite.index_map.size.toString() + ".");
+    }
+  }
+  workerInstance.onexit = (() => {
+    console.log(bunch_of_history_index.log_head() + " Index loader worker TERMINATED!");
+
+    // Set status
+    if (load_type == 'reindex') {
+      // Set status if this is part of reindexing
+      console.log(bunch_of_history_index.log_head() + ' Index loader finished!');
+      AppStorage.set('reindexing_history', false);
+    }
+    AppStorage.set('history_index_loading', false);
+  })
+}
+
+/**
+ * Calls a worker thread to save built index to disk.
+ * @param map the map to be saved.
+ * @param file_name the name of stored file, like 'index_2024_04_00_00_00_00_000'
+ * */
+export function history_index_save_to_disk_worker(map: collections.Map<string, collections.Array<number>>, file_name: string, clear: boolean) {
+  let history_index_saving = AppStorage.get('history_index_saving') as boolean | undefined;
+  let history_index_loading = AppStorage.get('history_index_loading') as boolean | undefined;
+  let reindexing = AppStorage.get('reindexing_history') as boolean | undefined;
+
+  if (history_index_saving == true) {
+    console.log(bunch_of_history_index.log_head() + " Another history_index_saving task is ongoing! Interrupted this history_index_save_to_disk_worker.")
+    return;
+  }
+  if (history_index_loading == true) {
+    console.log(bunch_of_history_index.log_head() + " A history_index_loading task is ongoing! Interrupted this history_index_save_to_disk_worker.")
+    return;
+  }
+  if (reindexing == true) {
+    console.log(bunch_of_history_index.log_head() + " A reindexing task is ongoing! Interrupted this history_index_save_to_disk_worker.")
+    return;
+  }
+
+  AppStorage.set('history_index_saving', true);
+
+  // Get this month params
+  let workerInstance = new worker.ThreadWorker("home/ets/workers/History_index_saver.ets");
+  workerInstance.postMessage('filesDir:' + context.filesDir);
+  // workerInstance.postMessage('file_name:' + 'index.txt');
+  workerInstance.postMessage('file_name:' + file_name);
+  // workerInstance.postMessageWithSharedSendable(bunch_of_history_index.index_map);
+  workerInstance.postMessage('clear:' + clear ? 'true' : 'false');
+  // workerInstance.postMessageWithSharedSendable(bunch_of_history_index.index_map);
+  workerInstance.postMessageWithSharedSendable(map);
+  console.log(bunch_of_history_index.log_head() + " Index saver worker CREATED!");
+
+  workerInstance.onmessage = (e: MessageEvents): void => {
+    let data: string = e.data;
+    // console.info("main thread data is  " + data);
+    if (data == "done") {
+      workerInstance.terminate();
+      // Set status
+      AppStorage.set('history_index_saving', false);
+    } else {
+      // Coming back with progress
+      AppStorage.set('history_index_saving_progress', data);
+    }
+  }
+  workerInstance.onexit = (() => {
+    console.log(bunch_of_history_index.log_head() + " Index saver worker TERMINATED!");
+    // AppStorage.set('history_index_saving_progress', "...");
+  })
+}
+
+
+/**
+ * Calls a worker thread to rebuild index for all history and saves (overwrites) the index file onto disk.
+ *
+ * This could avoid blocking the main thread.
+ *
+ * Adapted from https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-worker
+ * */
+export function history_index_full_rebuild_worker() {
+  let history_index_saving = AppStorage.get('history_index_saving') as boolean | undefined;
+  let history_index_loading = AppStorage.get('history_index_loading') as boolean | undefined;
+  let reindexing = AppStorage.get('reindexing_history') as boolean | undefined;
+
+  if (history_index_saving == true) {
+    console.log(bunch_of_history_index.log_head() +
+      " A history_index_saving task is ongoing! Interrupted this history_index_full_rebuild_worker.")
+    return;
+  }
+  if (history_index_loading == true) {
+    console.log(bunch_of_history_index.log_head() +
+      " A history_index_loading task is ongoing! Interrupted this history_index_full_rebuild_worker.")
+    return;
+  }
+  if (reindexing == true) {
+    console.log(bunch_of_history_index.log_head() +
+      " Another reindexing task is ongoing! Interrupted this history_index_full_rebuild_worker.")
+    return;
+  }
+
+  AppStorage.set('reindexing_history', true);
+
+  let workerInstance = new worker.ThreadWorker("home/ets/workers/History_indexer.ets");
+  workerInstance.postMessage(context.filesDir);
+  console.log(bunch_of_history_index.log_head() + " Index worker CREATED!");
+
+  workerInstance.onmessage = (e: MessageEvents): void => {
+    let message: string = e.data;
+    if (message == 'done') {
+      // FINISH
+      workerInstance.terminate();
+
+      // Read from disk
+      console.log("[Meow][bunch_of_history_index] Calling loader worker.");
+      history_index_load_from_disk_worker('reindex');
+    } else {
+      // Coming back with progress
+      let data: string = e.data;
+      AppStorage.set('reindexing_history_progress', data);
+    }
+  }
+  workerInstance.onexit = (() => {
+    console.log(bunch_of_history_index.log_head() + " Index worker TERMINATED!");
+
+    ensure_this_month_index_file();
+  })
+}
+
+/**
+ * Divides an input into single chars (CJK) and leave words (Other) as words.
+ * @param input the string to be divided
+ * @returns a set of parts
+ * @copyright Generated by DeepSeek on 13th May, 2025
+ * */
+export function divide_string(input: string): Set<string> {
+  // 使用正则表达式匹配所有 CJK 字符(包括中文、日文汉字、韩文汉字等)
+  const cjkRegex = /[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Hangul}]/gu;
+
+  // 先按分隔符切分
+  const parts = input.split(/[^\p{L}\p{N}]+/u);
+  const result = new Set<string>();
+
+  for (const part of parts) {
+    if (part.length === 0) {
+      continue;
+    }
+
+    let currentSegment = '';
+
+    for (const char of part) {
+      // 检查是否是 CJK 字符
+      if (cjkRegex.test(char)) {
+        // 如果是 CJK 字符,先把之前的非 CJK 段加入结果
+        if (currentSegment.length > 0) {
+          result.add(currentSegment);
+          currentSegment = '';
+        }
+        // 加入 CJK 单字
+        result.add(char);
+        // 重置正则表达式的 lastIndex 因为我们在循环中使用 test()
+        cjkRegex.lastIndex = 0;
+      } else {
+        // 非 CJK 字符,累积到当前段
+        currentSegment += char;
+      }
+    }
+
+    // 加入最后一个累积的非 CJK 段
+    // Join the last cumulated non-CJK part
+    if (currentSegment.length > 0) {
+      result.add(currentSegment);
+    }
+  }
+
+  return result;
+}
+
+/**
+ * Inserts a number into given array in ascending order
+ * @param arr the array
+ * @param new_num the new number
+ * @returns a new array of result
+ * @author Generated by DeepSeek on 14th May, 2025, modified by awa_Liny
+ * */
+export function insert_into_array(arr: collections.Array<number>, new_num: number): collections.Array<number> {
+  let left = 0;
+  let right = arr.length;
+
+  // 二分查找插入位置
+  while (left < right) {
+    const mid = Math.floor((left + right) / 2);
+    if (arr[mid] < new_num) {
+      left = mid + 1;
+    } else {
+      right = mid;
+    }
+  }
+
+  // 创建新数组(原数组保持不变)
+  return new collections.Array<number>(...arr.slice(0, left), new_num, ...arr.slice(left));
+}
+
+/**
+ * Access the disk and returns the label-link string[][] histories for given timestamps
+ * @param timestamps the timestamps of history
+ * @param max max number of result
+ * @returns the label-link string[][] histories, for example [["SEARCH", "bing.com"], ["CCW", "ccw.site"], ...]
+ * */
+export function get_histories_from_disk(timestamps: number[], max?: number): string[][] {
+  let result: string[][] = [];
+  if (timestamps.length == 0) {
+    return [];
+  }
+  // console.log(log_head() + 'Starting to get history.')
+  let year = new Date(timestamps[0]).getUTCFullYear();
+  let month = new Date(timestamps[0]).getUTCMonth() + 1;
+  let last_year = year;
+  let last_month = month;
+  let imp = sandbox_read_text_sync(history_path_of_month(year, month)).split("\n");
+  // console.log(imp.join("\n"));
+  let temp_pointer = -1;
+  for (let index = 0; index < timestamps.length; index++) {
+    const stamp = timestamps[index];
+    // console.log("considering stamp: " + stamp.toString())
+
+    year = new Date(stamp).getUTCFullYear();
+    month = new Date(stamp).getUTCMonth() + 1;
+
+    // Check if need to change file
+    if (last_year != year || last_month != month) {
+      imp = sandbox_read_text_sync(history_path_of_month(year, month)).split("\n");
+
+      if (imp.length % 3 > 0 && imp.length > 1) {
+        console.error("[ERROR][Meow][bunch_of_history_index] Open history Error: not 3*n length. Raw string: " + imp)
+        return [];
+      }
+      last_year = year;
+      last_month = month;
+      temp_pointer = -1;
+    }
+
+    while (true) {
+      temp_pointer += 3;
+      if (parseInt(imp[temp_pointer]) > stamp) {
+        // This history doesn't exist.
+        // Maybe is deleted?
+        // console.log(parseInt(imp[temp_pointer]).toString());
+        // console.log(stamp.toString());
+        console.error("[Meow][bunch_of_history_index] History missing!");
+        temp_pointer -= 3;
+        break;
+      } else if (temp_pointer >= imp.length) {
+        console.log(timestamps.toString());
+        console.log(imp.join("\n"));
+        console.error("[Meow][bunch_of_history_index] Rushed over end?");
+        break;
+      } else if (parseInt(imp[temp_pointer]) == stamp) {
+        // Found
+        result.push([imp[temp_pointer-2], imp[temp_pointer-1]]);
+        if (max) {
+          if (result.length >= max) {
+            // Meets requirement of max results;
+            // console.log("[Meow][bunch_of_history_index][get_history] Met max results! Quitting in advance.")
+            return result;
+          }
+        }
+        break;
+      }
+    }
+  }
+
+  // console.log(log_head() + "get_history result: " + result.toString());
+  return result;
+}
+
+/**
+ * Constructs the file path of history records in which history in [year, month] should be.
+ * @param year A number, the year, like 2024.
+ * @param month A number, the month, like 12.
+ * @returns A string, 'history/history_year_month.txt'
+ * */
+export function history_path_of_month(year: number, month: number) {
+  return 'history/' + history_file_name_of_month(year, month);
+}
+
+/**
+ * Constructs the file name of history records in which history in [year, month] should be.
+ * @param year A number, the year, like 2024.
+ * @param month A number, the month, like 12.
+ * @returns A string, 'history_YYYY_MM.txt'
+ * */
+export function history_file_name_of_month(year: number, month: number) {
+  let month_unified = month.toString();
+  if (month_unified.length == 1) {
+    month_unified = '0' + month_unified;
+  }
+  return 'history_' + year.toString() + "_" + month_unified + '.txt';
+}
+
+/**
+ * Constructs the file path of history index data in which history in [year, month] should be.
+ * @param year A number, the year, like 2024.
+ * @param month A number, the month, like 12.
+ * @returns A string, 'history-index/index_year_month_00_00_00_00_000.txt'
+ * */
+export function index_path_of_month(year: number, month: number) {
+  return 'history-index/' + index_file_name_of_month(year, month);
+}
+
+/**
+ * Constructs the file name of history index data in which history in [year, month] should be.
+ * @param year A number, the year, like 2024.
+ * @param month A number, the month, like 12.
+ * @returns A string, 'index_year_month_00_00_00_00_000.txt'
+ * */
+export function index_file_name_of_month(year: number, month: number) {
+  let month_unified = month.toString();
+  if (month_unified.length == 1) {
+    month_unified = '0' + month_unified;
+  }
+  return 'index_' + year.toString() + "_" + month_unified + '_00_00_00_00_000.txt';
+}
+
+/**
+ * Ensures that this month has an index file.
+ *
+ * If there doesn't exist such, then create one.
+ * */
+export function ensure_this_month_index_file() {
+  // Create this month index
+  const current_year: number = new Date().getUTCFullYear();
+  const current_month: number = new Date().getUTCMonth() + 1;
+  const this_month_file_path = index_path_of_month(current_year, current_month);
+  if (!fileIo.accessSync(context.filesDir + '/' + this_month_file_path)) {
+    // If this month have no index
+    console.log(bunch_of_history_index.log_head() + ' No this month index found. Creating one.')
+    bunch_of_history_index_lite.this_year = current_year;
+    bunch_of_history_index_lite.this_month = current_month;
+    sandbox_save(this_month_file_path, '');
+  }
+}
+
+/**
+ * Ensures that this month has an history file.
+ *
+ * If there doesn't exist such, then create one.
+ * */
+export function ensure_this_month_history_file(context_filesDir?: string) {
+  let filesDir: string = context_filesDir || context.filesDir;
+  // Create this month history
+  const current_year: number = new Date().getUTCFullYear();
+  const current_month: number = new Date().getUTCMonth() + 1;
+  const this_month_file_path = history_path_of_month(current_year, current_month);
+  if (!fileIo.accessSync(filesDir + '/' + this_month_file_path)) {
+    // If this month have no history
+    console.log('[Meow][bunch_of_history] No this month history found. Creating one.')
+    sandbox_save(this_month_file_path, '', context_filesDir);
+  }
+}
+
+/**
+ * Generates fake history from a given year to another given year, inclusive.
+ * @param from_year the starting year
+ * @param to_year the ending year
+ * @param quantity quantity of histories
+ * @author Generated by DeepSeek on 17th May, 2025, modified by awa_Liny
+ * */
+export function fill_fake_history(from_year: number, to_year: number, quantity: number): void {
+  // 生成月份时间戳范围(明确标注返回类型)
+  const getMonthRanges = (): Array<[number, number]> => {
+    const ranges: Array<[number, number]> = [];
+
+    // 明确循环参数类型
+    for (let year: number = from_year; year <= to_year; year++) {
+      const maxMonth: number = (year === to_year) ? 11 : 11;
+
+      for (let month: number = 0; month <= maxMonth; month++) {
+        const start: number = Date.UTC(year, month, 1);
+        const end: number = Date.UTC(year, month + 1, 1) - 1;
+        ranges.push([start, end]);
+      }
+    }
+    return ranges;
+  };
+
+  // 数量分配函数(明确参数和返回类型)
+  const distributeQuantity = (total: number, months: number): number[] => {
+    const base: number = Math.floor(total / months);
+    const remainder: number = total % months;
+    const result: number[] = [];
+
+    for (let i: number = 0; i < months; i++) {
+      result.push(base + (i < remainder ? 1 : 0));
+    }
+    return result;
+  };
+
+  // 主逻辑(明确所有变量类型)
+  const monthRanges: Array<[number, number]> = getMonthRanges();
+  const quantities: number[] = distributeQuantity(quantity, (1 + to_year - from_year) * 12);
+
+  for (let index: number = 0; index < monthRanges.length; index++) {
+    const currentRange: [number, number] = monthRanges[index];
+    const start: number = currentRange[0];
+    const end: number = currentRange[1];
+
+    // 生成数据
+    const monthData: string[] = fake_gen(start, end, quantities[index]);
+
+    // 构造时间标签
+    const date: Date = new Date(start);
+    const year: number = date.getUTCFullYear();
+    const monthNumber: number = date.getUTCMonth() + 1;
+    const monthString: string = monthNumber.toString().padStart(2, "0");
+    const monthLabel: string = `history/history_${year}_${monthString}.txt`;
+
+    // 控制台输出
+    console.log(monthLabel + ": " + monthData);
+    sandbox_save(monthLabel, monthData.join("\n"));
+  }
+}
+
+/**
+ *
+ * @param from min timestamp
+ * @param to max timestamp
+ * @param quantity total quantity of histories
+ * @returns a string[] of history records in ascending order of timestamp,
+ * like ["Quick Zone","www.healthcloud.com","1580515200000",...]
+ * @author Generated by DeepSeek on 17th May, 2025, modified by awa_Liny
+ * */
+function fake_gen(from: number, to: number, quantity: number): string[] {
+  // 生成随机网站名称
+  const generateRandomName = (): string => {
+    const prefixes = ['Cool', 'Best', 'Fast', 'Smart', 'Quick', 'Easy', 'Super'];
+    const suffixes = ['Site', 'Web', 'Page', 'Portal', 'Hub', 'Zone'];
+    const name_parts = [
+      'Hub', 'Central', 'Portal', 'Network',
+      "快递", "外卖", "地铁", "公交", "超市", "菜场", "早餐", "奶茶", "咖啡", "电梯",
+      "充电", "流量", "WiFi", "密码", "排队", "扫码", "会员", "折扣", "发票", "收据",
+      "车位", "堵车", "导航", "违章", "快递柜", "快递员", "外卖员", "保洁", "物业", "维修",
+      "水电费", "燃气费", "物业费", "停车费", "话费", "宽带", "路由器", "遥控器", "充电器", "数据线",
+      "洗衣机", "空调", "冰箱", "微波炉", "电磁炉", "电饭煲", "垃圾桶", "塑料袋", "保鲜膜", "洗洁精",
+      "洗手液", "抽纸巾", "湿纸巾", "晾衣架", "拖把", "扫帚", "垃圾袋", "快递单", "外卖盒", "一次性",
+      "便利店", "自助餐", "团购券", "优惠券", "会员卡", "积分卡", "停车场", "电梯间", "楼道灯", "门禁卡",
+      "快递站", "外卖柜", "共享单车", "充电宝", "健康码", "行程码", "体温计", "口罩", "消毒液", "酒精棉",
+      "工作群", "微信群", "表情包", "朋友圈", "点赞", "评论", "转发", "收藏", "截图", "录屏",
+      "天气预报", "闹钟", "备忘录", "日历", "记事本", "文件夹", "打印机", "复印件", "扫描件", "电子版",
+      "桌子", "椅子", "窗户", "书包", "水杯", "手机", "钥匙", "电视", "冰箱", "空调",
+      "跑步", "阅读", "烹饪", "购物", "旅行", "学习", "工作", "休息", "聊天", "散步",
+      "晴天", "雨天", "雪花", "云朵", "月亮", "星星", "阳光", "微风", "雷电", "彩虹",
+      "医生", "教师", "司机", "厨师", "警察", "作家", "工程师", "画家", "护士", "记者",
+      "苹果", "香蕉", "米饭", "面条", "鸡蛋", "牛奶", "面包", "咖啡", "西瓜", "番茄",
+      "公园", "学校", "医院", "超市", "银行", "车站", "图书馆", "电影院", "健身房", "餐厅",
+      "红色", "蓝色", "绿色", "黄色", "黑色", "白色", "紫色", "橙色", "灰色", "粉色",
+      "猫咪", "狗狗", "鸟儿", "兔子", "金鱼", "蝴蝶", "蜜蜂", "蚂蚁", "熊猫", "老虎",
+      "音乐", "电影", "游戏", "绘画", "舞蹈", "摄影", "写作", "编程", "园艺", "手工",
+      "时间", "空间", "未来", "过去", "希望", "梦想", "勇气", "友谊", "爱情", "自由"
+    ]
+    const prefix = prefixes[Math.floor(Math.random() * prefixes.length)];
+    const suffix = suffixes[Math.floor(Math.random() * suffixes.length)];
+    const name1 = name_parts[Math.floor(Math.random() * name_parts.length)];
+    const name2 = name_parts[Math.floor(Math.random() * name_parts.length)];
+    return `${prefix} ${suffix} ${name1} ${name2}`;
+  };
+
+  const domainWords = [
+    'shop', 'blog', 'news', 'tech', 'food', 'travel', 'book',
+    'game', 'music', 'cloud', 'data', 'market', 'art', 'edu',
+    'health', 'hub', 'lab', 'live', 'media', 'space', 'store',
+    'tech', 'web', 'net', 'data', 'info', 'digital', 'cloud',
+    'ai', 'code', 'app', 'dev', 'blog', 'news', 'world', 'online'
+  ];
+
+  // 生成有意义的域名组合
+  const generateMeaningfulDomain = (): string => {
+    // 随机选取两个单词组合
+    const getRandomWord = () => domainWords[Math.floor(Math.random() * domainWords.length)];
+    const word1 = getRandomWord();
+    const word2 = getRandomWord();
+    const word3 = getRandomWord();
+
+    // 确保组合长度合理 (3-15字符)
+    const combined = word1 + "." + word2 + "." + word3;
+    return combined;
+  };
+
+  // 生成并排序时间戳
+  const timestamps: number[] = [];
+  for (let i = 0; i < quantity; i++) {
+    const timestamp = Math.floor(Math.random() * (to - from + 1)) + from;
+    timestamps.push(timestamp);
+  }
+  timestamps.sort((a, b) => a - b);
+
+  // 构建结果数组
+  const result: string[] = [];
+  for (const timestamp of timestamps) {
+    result.push(
+      generateRandomName(),
+      generateMeaningfulDomain(),
+      timestamp.toString()
+    );
+  }
+
+  return result;
+}

+ 192 - 0
home/src/main/ets/hosts/bunch_of_key_shortcuts.ets

@@ -0,0 +1,192 @@
+import { bunch_of_settings } from './bunch_of_settings';
+
+let function_keys = [FunctionKey.ESC, FunctionKey.F1, FunctionKey.F2, FunctionKey.F3, FunctionKey.F4, FunctionKey.F5,
+  FunctionKey.F6, FunctionKey.F7, FunctionKey.F8, FunctionKey.F9, FunctionKey.F10, FunctionKey.F11, FunctionKey.F12,
+  FunctionKey.TAB, FunctionKey.DPAD_UP, FunctionKey.DPAD_DOWN, FunctionKey.DPAD_LEFT, FunctionKey.DPAD_RIGHT,
+];
+let function_strings =
+  ['esc', 'f1', 'f2', 'f3', 'f4', 'f5', 'f6', 'f7', 'f8', 'f9', 'f10', 'f11', 'f12', 'tab', 'dpad_up', 'dpad_down', 'dpad_left', 'dpad_right'];
+let modifier_keys = [ModifierKey.CTRL, ModifierKey.SHIFT, ModifierKey.ALT];
+let modifier_strings = ['ctrl', 'shift', 'alt'];
+
+export class bunch_of_key_shortcuts {
+  // Singles
+  new_tab: key_combination = new key_combination('t', [ModifierKey.CTRL]);
+  close_tab: key_combination = new key_combination('w', [ModifierKey.CTRL]);
+  show_tabs: key_combination = new key_combination('a', [ModifierKey.SHIFT, ModifierKey.CTRL]);
+  focus_address: key_combination = new key_combination('d', [ModifierKey.ALT]);
+  in_page_search: key_combination = new key_combination('f', [ModifierKey.CTRL]);
+  fullscreen: key_combination = new key_combination(FunctionKey.F11);
+  history: key_combination = new key_combination('h', [ModifierKey.CTRL]);
+  refresh: key_combination = new key_combination(FunctionKey.F5);
+  print: key_combination = new key_combination('p', [ModifierKey.CTRL]);
+  // Lists
+  key_shortcut_labels =
+    ['new_tab', 'close_tab', 'show_tabs', 'focus_address', 'in_page_search', 'fullscreen', 'history', 'refresh', 'print'];
+  key_shortcut_combinations =
+    [this.new_tab, this.close_tab, this.show_tabs, this.focus_address, this.in_page_search, this.fullscreen, this.history, this.refresh, this.print];
+  last_edit: number = 0;
+
+  /**
+   * A class holding key shortcuts.
+   * @param no_init Will not load configuration from disk nor do anything else.
+   * Usually set true if this object is only created to sit the place of StorageLink initialization.
+   */
+  constructor(no_init?: boolean) {
+    if (no_init == true) {
+      return;
+    }
+    this.load_from_disk();
+  }
+
+  /**
+   * Gets the string[] of shortcut of a specific action.
+   * @param label A string, the label of the action. e.g. 'show_tabs'.
+   * @returns A string[], an array containing keys in the shortcut.
+   * e.g. ['a', 'ctrl', 'shift']
+   * */
+  get_shortcut(label: string): string[] {
+    let index = this.key_shortcut_labels.indexOf(label);
+    if (index > -1) {
+      return this.key_shortcut_combinations[index].to_string_array();
+    } else {
+      return [];
+    }
+  }
+
+  /**
+   * Sets the shortcut of a specific action.
+   * @param label A string, the label of the action. e.g. 'show_tabs'.
+   * @param key_com A string[], an array containing keys in the shortcut.
+   * e.g. ['a', 'ctrl', 'shift']
+   * */
+  set_shortcut(label: string, key_com: string[]) {
+    let index = this.key_shortcut_labels.indexOf(label);
+    if (index > -1) {
+      this.key_shortcut_combinations[index].load_string_array(key_com);
+      this.save_to_disk();
+    }
+  }
+
+  /**
+   * Loads key combination settings from disk
+   * */
+  async load_from_disk() {
+    try {
+      let combinations = await (AppStorage.get('bunch_of_settings') as bunch_of_settings).get('key_shortcuts') as string;
+      // console.log('[bunch_of_key_shortcuts][load_from_disk] Combinations: ' + combinations);
+      let keys_lines: string[] = [];
+      if (combinations != '') {
+        keys_lines = combinations.split('\n');
+        for (let index = 0; index < keys_lines.length; index++) {
+          if (this.key_shortcut_combinations[index]) {
+            this.key_shortcut_combinations[index].load_string(keys_lines[index]);
+          }
+        }
+      }
+    } catch (e) {
+      console.error('[bunch_of_key_shortcuts][load_from_disk] ' + e);
+    }
+    this.last_edit = Date.now();
+  }
+
+  /**
+   * Saves current key combination settings to disk
+   * */
+  save_to_disk() {
+    let list: string[] = [];
+    for (let index = 0; index < this.key_shortcut_combinations.length; index++) {
+      list.push(this.key_shortcut_combinations[index].toString());
+    }
+    (AppStorage.get('bunch_of_settings') as bunch_of_settings).set('key_shortcuts', list.join('\n'));
+    this.last_edit = Date.now();
+  }
+}
+
+class key_combination {
+  main_key: string | FunctionKey = '';
+  modifier: Array<ModifierKey> = [];
+
+  /**
+   * An object holding a key combination,
+   * which consists of a main key and some modifier keys,
+   * if the main key isn't a function key.
+   * @param main A string or FunctionKey
+   * @param mod A ModifierKey[]
+   * */
+  constructor(main?: string | FunctionKey, mod?: Array<ModifierKey>) {
+    if (main) {
+      this.main_key = main;
+    }
+    if (mod) {
+      this.modifier = mod;
+    }
+  }
+
+  /**
+   * @returns A string[], the key combination.
+   * e.g. ['a', 'ctrl', 'shift']
+   * */
+  to_string_array() {
+    let result: string[] = [];
+    if (typeof this.main_key == 'string') {
+      result.push(this.main_key);
+    } else {
+      result.push(function_strings[function_keys.indexOf(this.main_key)]);
+    }
+    for (let index = 0; index < this.modifier.length; index++) {
+      result.push(modifier_strings[modifier_keys.indexOf(this.modifier[index])]);
+    }
+    return result;
+  }
+
+  /**
+   * @returns A string, the key combination.
+   * e.g. 'a ctrl shift'
+   * */
+  toString() {
+    return this.to_string_array().join(' ');
+  }
+
+  /**
+   * Loads a string array as a key combination, OVERWRITING whatever this combination was.
+   * @param combination A string[], containing the keys.
+   * e.g. ['a', 'ctrl', 'shift']
+   * */
+  load_string_array(combination: string[]) {
+    if (combination.length == 0) {
+      return;
+    }
+    // clear modifiers
+    this.modifier.splice(0, this.modifier.length);
+    // determine modifiers
+    for (let index = 0; index < combination.length; index++) {
+      let key_str = combination[index];
+      if (modifier_strings.includes(key_str)) {
+        // Is a modifier
+        let modifier_index = modifier_strings.indexOf(key_str);
+        this.modifier.push(modifier_keys[modifier_index]);
+      } else if (function_strings.includes(key_str)) {
+        // Is a function key
+        let index_of_first_key = function_strings.indexOf(key_str);
+        this.main_key = function_keys[index_of_first_key];
+      } else {
+        // Is a normal letter
+        this.main_key = key_str;
+      }
+    }
+  }
+
+  /**
+   * Loads a string as a key combination, OVERWRITING whatever this combination was.
+   * @param combination A string, containing the keys.
+   * e.g. 'a ctrl shift'
+   * */
+  load_string(combination: string) {
+    if (combination == '') {
+      return;
+    }
+    let keys_split = combination.split(' ');
+    this.load_string_array(keys_split);
+  }
+}

+ 86 - 0
home/src/main/ets/hosts/bunch_of_search_engines.ets

@@ -0,0 +1,86 @@
+export class bunch_of_search_engines {
+  list_of_search_engines: search_engine[] = [];
+  last_accessed: number = 0;
+
+  /**
+   * A class holding a search_engine[] array, in which there stores search_engine objects.
+   */
+  constructor() {
+  }
+
+  /**
+   * Update last accessed time. This would trigger some refresh on UI layer.
+   * */
+  update_last_accessed() {
+    this.last_accessed = Date.now();
+  }
+
+  /**
+   * Add a new search engine to the current search engine list.
+   * @param se A search_engine object, the search engine to be added.
+   * */
+  add_search_engine(se: search_engine) {
+    this.list_of_search_engines.push(se);
+    this.update_last_accessed();
+  }
+
+  /**
+   * Remove a search engine from the current search engine list.
+   * @param index A number, the index of the search engine to be removed in the list.
+   * */
+  del_search_engine(index: number) {
+    this.list_of_search_engines.splice(index, 1);
+    this.update_last_accessed();
+  }
+
+  /**
+   * Export search engines in a specific plain text format.
+   * @returns '\n' connected string of search engines in the format of:
+   * @example 'Bing\nhttps://www.cn.bing.com/search?q=%s\nGoogle\nhttps://www.google.com/search?q=%s'
+   * */
+  export_string() {
+    let export_list: string[] = []
+    for (let index = 0; index < this.list_of_search_engines.length; index++) {
+      let ua: search_engine = this.list_of_search_engines[index];
+      export_list.push(ua.label);
+      export_list.push(ua.url);
+    }
+    return export_list.join("\n");
+  }
+
+  /**
+   * Import search engines in a specific plain text format.
+   * In default overwrites whatever was in this list_of_search_engines.
+   * @param imp The string in the correct format:
+   * @example 'Bing\nhttps://www.cn.bing.com/search?q=%s\nGoogle\nhttps://www.google.com/search?q=%s'
+   * */
+  import_string(imp: string) {
+    // Clear
+    this.list_of_search_engines = [];
+    if (imp == "") {
+      return;
+    }
+    // Import
+    let import_list: string[] = imp.split("\n");
+    for (let index = 0; index < import_list.length; index += 2) {
+      if (import_list[index] != "") {
+        this.add_search_engine(new search_engine(import_list[index], import_list[index+1]));
+      }
+    }
+  }
+}
+
+export class search_engine {
+  label: string;
+  url: string;
+
+  /**
+   * A search_engine object, which consists of a label and the search engine url.
+   * @param label The name of the search engine.
+   * @param url The url of the search engine, '%s' in which will be replaced with real search keywords.
+   */
+  constructor(label: string, content: string) {
+    this.label = label;
+    this.url = content;
+  }
+}

+ 390 - 0
home/src/main/ets/hosts/bunch_of_settings.ets

@@ -0,0 +1,390 @@
+import { kv_store_get, kv_store_put } from '../utils/kv_store_tools';
+import { default_user_agents, preset_search_engines } from './bunch_of_defaults';
+
+export class bunch_of_settings {
+  settings_list: settings_item[] = [];
+  // Defaults
+  settings_defaults_key: string[] = [];
+  settings_defaults_type: string[] = [];
+  settings_defaults_value: string[] = [];
+
+  /**
+   * A class holding a settings_list[] array, in which there stores settings_list objects.
+   * Provides get and set functions to access app settings.
+   * @param no_init Will not register default settings nor do anything else.
+   * Usually set true if this object is only created to sit the place of StorageLink initialization.
+   */
+  constructor(no_init?: boolean) {
+    if (no_init == true) {
+      return;
+    }
+    // Register the default Settings
+
+    // Customization
+    this.register_setting('title_bar_position', 'string', 'bottom');
+    this.register_setting('collect_new_history', 'boolean', 'true');
+    this.register_setting('intelligent_tracking_prevention', 'boolean', 'false');
+    this.register_setting('tabs_style', 'string', 'vertical');
+    this.register_setting('tabs_style_non_tablet_mode', 'string', 'vertical');
+    this.register_setting('sys_back_access_backward', 'boolean', 'true');
+    this.register_setting('max_bookmark_suggest', 'number', '5');
+    this.register_setting('max_history_suggest', 'number', '5');
+
+    // Statuses
+    this.register_setting('status_tabs_open', 'boolean', 'false');
+
+    // Continuation
+    this.register_setting('continuation_auto_exit', 'boolean', 'false');
+    this.register_setting('continuation_auto_close_tab', 'boolean', 'true');
+
+    // Keyboard shortcuts
+    this.register_setting('key_shortcuts', 'string', '');
+
+    // General defaults
+    this.register_setting('home_url', 'string', '');
+    this.register_setting('new_tab_url', 'string', '');
+    this.register_setting('start_up_option', 'string', 'new tab');
+
+    // Animation defaults
+    this.register_setting('animation_response', 'number', '36');
+    this.register_setting('animation_damping_coefficient', 'number', '20');
+
+    // UA and Search Engines and Homepage Shortcuts
+    this.register_setting('custom_user_agents', 'string', default_user_agents());
+    this.register_setting('custom_user_agents_selected_index', 'number', '-1');
+    this.register_setting('custom_search_engines', 'string', preset_search_engines());
+    this.register_setting('custom_search_engines_selected_index', 'number', '-1');
+    this.register_setting('homepage_shortcuts_bookmarks_dir', 'string', '');
+    this.register_setting('homepage_shortcuts_init_height', 'number', '50');
+
+    // Single hand accessibility
+    this.register_setting('preferred_hand_left_or_right', 'string', 'right');
+    this.register_setting('preferred_hand_reverse_settings_menu', 'boolean', 'true');
+    this.register_setting('preferred_hand_reverse_tabs_panel', 'boolean', 'true');
+    this.register_setting('preferred_hand_reverse_homepage_shortcuts', 'boolean', 'false');
+
+    // Ad blocker
+    this.register_setting('use_adblock', 'boolean', 'true');
+    this.register_setting('adblock_exceptions', 'string', '');
+
+    // JS blocker
+    this.register_setting('disable_js', 'boolean', 'true');
+    this.register_setting('disable_js_all_sites', 'boolean', 'false');
+    this.register_setting('disable_js_these_sites', 'string', '');
+
+    // Image blocker
+    this.register_setting('disable_image', 'boolean', 'true');
+    this.register_setting('disable_image_all_sites', 'boolean', 'false');
+    this.register_setting('disable_image_these_sites', 'string', '');
+
+    // Dark mode
+    this.register_setting('web_force_dark_mode', 'boolean', 'false');
+
+    // 主题颜色初值
+    // Colors
+    this.register_setting('color_light_primary', 'string', '#E1E9E3');
+    this.register_setting('color_light_secondary', 'string', '#CDD7CD');
+    this.register_setting('color_light_font', 'string', '#243B24');
+    this.register_setting('color_dark_primary', 'string', '#0F1A0F');
+    this.register_setting('color_dark_secondary', 'string', '#2A392A');
+    this.register_setting('color_dark_font', 'string', '#E1E9E3');
+
+    // Cached size of webview cache
+    this.register_setting('webview_cache_size', 'number', '-1');
+
+    // Debug
+    this.register_setting('DEV_MODE', 'boolean', 'false');
+    this.register_setting('resource_monitor', 'boolean', 'true');
+
+    // History Indexer
+    this.register_setting('history_index_size', 'number', '-1');
+
+    // Download
+    this.register_setting('direct_download', 'boolean', 'false');
+    this.register_setting('direct_download_auto_open', 'boolean', 'true');
+
+    // console.log('[Meow][bunch_of_settings] Init success!')
+  }
+
+  /**
+   * Gets the corresponding value of key.
+   *
+   * Retrieve from the settings_list cache if this key is already read from KvStore.
+   *
+   * Otherwise read from KvStore and cache it in the settings_list.
+   * @param key A string, the key of the setting.
+   * @returns A string | boolean | number, the corresponding value got for requested key.
+   * @returns 'unregistered' if the requested key is not registered.
+   * */
+  async get(key: string) {
+    for (let index = 0; index < this.settings_list.length; index++) {
+      if (this.settings_list[index].key == key) {
+        return this.settings_list[index].value;
+      }
+    }
+
+    // if not found in read cached results.
+    let key_default_index = this.settings_defaults_key.indexOf(key);
+    if (key_default_index == -1) {
+      // unregistered setting
+      console.error('[Error][Meow][bunch_of_settings][get] Unregistered setting key: ' + key);
+      return 'unregistered';
+    }
+    let key_default_value = this.settings_defaults_value[key_default_index];
+    let key_default_type = this.settings_defaults_type[key_default_index];
+
+    let result: string | boolean | number;
+    if (key_default_type == 'string') {
+      result = await this.get_settings_string(key, key_default_value);
+    } else if (key_default_type == 'number') {
+      result = await this.get_settings_number(key, key_default_value);
+    } else { // Boolean
+      result = await this.get_settings_boolean(key, key_default_value);
+    }
+
+    let item: settings_item = new settings_item(key, result);
+    this.settings_list.push(item);
+    return result;
+  }
+
+  /**
+   * Sets the corresponding value of key, and save it in the KvStore.
+   * @param key A string, the key of the setting.
+   * @param value A string | boolean | number, the corresponding value of key to be set.
+   * @returns true if success.
+   * @returns false if fails, perhaps due to an unregistered setting key.
+   * @returns undefined if fails, due to not initialized setting.
+   * */
+  set(key: string, value: string | boolean | number) {
+    if (!(AppStorage.get('settings_init_retrieved') as boolean)) {
+      // If settings not init
+      return;
+    }
+
+    let key_default_index = this.settings_defaults_key.indexOf(key);
+    if (key_default_index == -1) {
+      // unregistered setting
+      console.error('[Error][Meow][bunch_of_settings][get] Unregistered setting key: ' + key);
+      return false;
+    }
+    let key_default_type = this.settings_defaults_type[key_default_index];
+
+    // Sequential search
+    for (let index = 0; index < this.settings_list.length; index++) {
+      if (this.settings_list[index].key == key) {
+        this.settings_list[index].value = value;
+        break;
+      }
+    }
+
+    // Save to KvStore
+    if (key_default_type == 'string') {
+      kv_store_put(key, value as string);
+    } else if (key_default_type == 'number') {
+      kv_store_put(key, value.toString());
+    } else if (key_default_type == 'boolean') {
+      kv_store_put(key, this.boolean_to_string(value as boolean));
+    }
+    return true;
+  }
+
+  /**
+   * Resets a setting to default value.
+   * @param key A string, the key of the setting.
+   * */
+  reset(key: string) {
+    let index = this.settings_defaults_key.indexOf(key)
+    let def_value = this.settings_defaults_value[index]
+    let type = this.settings_defaults_type[index];
+    if (index >= 0) {
+      if (type == 'string') {
+        this.set(key, def_value);
+      } else if (type == 'boolean') {
+        this.set(key, this.string_to_boolean(def_value));
+      } else {
+        // is number
+        this.set(key, Number.parseFloat(def_value));
+      }
+    }
+  }
+
+  /**
+   * Outputs a string as result
+   * @returns a string.
+   * */
+  toString() {
+    let result_string: string[] = [];
+    for (let index = 0; index < this.settings_list.length; index++) {
+      const the_setting = this.settings_list[index];
+      result_string.push('    "' + the_setting.key + '": "' + (the_setting.value.toString()).replaceAll('\n', '\\n') + '"');
+    }
+    result_string.sort();
+    return '{\n' + result_string.join(',\n') + '\n}';
+  }
+
+  /**
+   * Import settings from json
+   * @param text the json
+   * */
+  import_json(text: string) {
+    let jsonObject: Record<string, string> | null = null;
+    if (text.length < 2) {
+      return false;
+    }
+
+    try {
+      jsonObject = JSON.parse(text);
+    } catch (e) {
+      console.log('[Meow][bunch_of_settings] Import settings failed! Json parse failed!');
+      return false;
+    }
+
+    Object.entries(jsonObject as Record<string, string>).forEach((item) => {
+      const key = item[0];
+      const val = item[1];
+      this.import_set(key, val);
+    });
+    console.log('[Meow][bunch_of_settings] Import settings:\n' + text);
+    return true;
+  }
+
+  /**
+   * Sets the corresponding value of key, and save it in the KvStore.
+   * @param key A string, the key of the setting.
+   * @param value A string, the corresponding value of key to be set.
+   * @returns true if success.
+   * @returns false if fails, perhaps due to an unregistered setting key.
+   * @returns undefined if fails, due to not initialized setting.
+   * */
+  import_set(key: string, value: string) {
+    if (!(AppStorage.get('settings_init_retrieved') as boolean)) {
+      // If settings not init
+      return;
+    }
+
+    let key_default_index = this.settings_defaults_key.indexOf(key);
+    if (key_default_index == -1) {
+      // unregistered setting
+      console.error('[Error][Meow][bunch_of_settings][get] Unregistered setting key: ' + key);
+      return false;
+    }
+    let key_default_type = this.settings_defaults_type[key_default_index];
+
+    let typed_value: string | boolean | number = -1;
+    if (key_default_type == 'number') {
+      typed_value = Number.parseFloat(value);
+    } else if (key_default_type == 'string') {
+      typed_value = value;
+    } else {
+      // boolean
+      typed_value = this.string_to_boolean(value);
+    }
+
+    // Sequential search
+    for (let index = 0; index < this.settings_list.length; index++) {
+      if (this.settings_list[index].key == key) {
+        this.settings_list[index].value = typed_value;
+        break;
+      }
+    }
+
+    kv_store_put(key, value);
+
+    return true;
+  }
+
+  /**
+   * Registers a setting
+   * @param key A string, the key of the setting.
+   * @param type A string, indicating the data type of the setting. Allow 'string' | 'number' | 'boolean'.
+   * @param value A string, sets the default (initial) value of the setting.
+   * @example this.register_setting('sys_back_access_backward', 'boolean', 'true');
+   * */
+  private register_setting(key: string, type: string, value: string) {
+    this.settings_defaults_key.push(key);
+    this.settings_defaults_type.push(type);
+    this.settings_defaults_value.push(value);
+  }
+
+  /**
+   * Gets the string value of a setting.
+   * @param key A string, the key of the setting.
+   * @param default_404_fall_back A string, who will be returned if this key is not found in KvStore.
+   * @returns A string, the value of this key.
+   * @returns A string, default_404_fall_back, if the key is not found in KvStore.
+   * */
+  private async get_settings_string(key: string, default_404_fall_back: string) {
+    let value = await kv_store_get(key);
+    if (value == ('undefined')) {
+      value = default_404_fall_back;
+    }
+    console.log('[bunch_of_settings][Uni] Got settings for ' + key + ':\n\t' + value.replaceAll('\n', '\n\t'));
+    return value;
+  }
+
+  /**
+   * Gets the string value of a setting.
+   * @param key A string, the key of the setting.
+   * @param default_404_fall_back A string, who will be returned if this key is not found in KvStore.
+   * @returns A number, the value of this key.
+   * @returns A number, Number.parseFloat(default_404_fall_back), if the key is not found in KvStore.
+   * */
+  private async get_settings_number(key: string, default_404_fall_back: string) {
+    let value = await kv_store_get(key);
+    if (value == ('undefined')) {
+      value = default_404_fall_back;
+    }
+    console.log('[bunch_of_settings][Uni] Got settings for ' + key + ': ' + value)
+    return Number.parseFloat(value);
+  }
+
+  /**
+   * Gets the string value of a setting.
+   * @param key A string, the key of the setting.
+   * @param default_404_fall_back A string, who will be returned if this key is not found in KvStore.
+   * @returns A number, the value of this key.
+   * @returns A boolean, string_to_boolean(default_404_fall_back), if the key is not found in KvStore.
+   * */
+  private async get_settings_boolean(key: string, default_404_fall_back: string): Promise<boolean> {
+    let value = await kv_store_get(key);
+    if (value == ('undefined')) {
+      value = default_404_fall_back;
+    }
+    console.log('[bunch_of_settings][Uni] Got settings for ' + key + ': ' + value)
+    return this.string_to_boolean(value);
+  }
+
+  /**
+   * Converts a boolean to string.
+   * @param bool A boolean.
+   * @returns A string form of the boolean: 'true' | 'false'.
+   * */
+  private boolean_to_string(bool: boolean) {
+    return bool ? 'true' : 'false';
+  }
+
+  /**
+   * Converts a boolean to string.
+   * @param str A string form of the boolean: 'true' | 'false'.
+   * @returns A boolean, true if the str is exactly 'true'.
+   * @returns A boolean, false if otherwise.
+   * */
+  private string_to_boolean(str: string) {
+    return (str == 'true') ? true : false;
+  }
+}
+
+class settings_item {
+  key: string;
+  value: string | boolean | number;
+
+  /**
+   * A settings_item item, consists of a key and its value.
+   * @param key A string, the key of this setting.
+   * @param value A string | boolean | number, the value of this setting.
+   * */
+  constructor(key: string, value: string | boolean | number) {
+    this.key = key;
+    this.value = value;
+  }
+}

+ 448 - 0
home/src/main/ets/hosts/bunch_of_tabs.ets

@@ -0,0 +1,448 @@
+import { webview } from '@kit.ArkWeb';
+import { match_domain, url_meow_to_resource, viewable_domains } from '../utils/url_tools';
+import { url_default_blank } from './bunch_of_defaults';
+import { fileIo as fs } from '@kit.CoreFileKit';
+import { sandbox_save } from '../utils/storage_tools';
+import { common } from '@kit.AbilityKit';
+
+let context = AppStorage.get("context") as common.UIAbilityContext;
+
+@Observed
+export class tab_label {
+  timestamp: number;
+  index_key: number;
+
+  /**
+   * A class holding a timestamp and an index_key.
+   * @param timestamp A number, usually the creation of the tab.
+   * @param index_key A number, indicating the index of this tab in the tabs list.
+   */
+  constructor(timestamp: number, index: number) {
+    this.index_key = index;
+    this.timestamp = timestamp;
+  }
+}
+
+export class tab_info_packed {
+  controller: WebviewController;
+  title: string;
+  url: string;
+  is_loading: boolean;
+  loading_progress: number;
+  // Keyword of in-page search
+  searching_keyword: string = '';
+  // Index of current viewing result during in-page search
+  searching_keyword_stats_current: number = 0;
+  // Total number of results of in-page search
+  searching_keyword_stats_total: number = 0;
+  restore_on_creation: boolean = false;
+  web_state_array: Uint8Array = new Uint8Array();
+
+  /**
+   * A class holding a controller and statuses of a tab.
+   * @param title A string, the initial title when the tab is created.
+   * @param url A string, the initial url when the tab is created.
+   * @param restore_on_creation A boolean, optional, will restore web state on creation if the index in valid.
+   */
+  constructor(title: string, url: string, restore_on_creation?: boolean) {
+    this.controller = new webview.WebviewController();
+    this.title = title;
+    this.url = url;
+    this.is_loading = true;
+    this.loading_progress = 0;
+
+    if (restore_on_creation) {
+      this.restore_on_creation = true;
+    }
+  }
+
+  /**
+   * Asks the tab to update its title, usually called when the tab loads something new.
+   * */
+  update_title() {
+    this.title = this.controller.getTitle();
+  }
+
+  /**
+   * Asks the tab to update its url, usually called when the tab loads something new.
+   * */
+  update_url() {
+    if (!viewable_domains().includes(match_domain(this.controller.getUrl())[0])) {
+      return;
+    }
+    this.url = this.controller.getUrl();
+  }
+
+  /**
+   * Asks the tab to update its loading state, usually called when the tab starts loading or finishes loading.
+   * */
+  update_is_loading(is_loading: boolean) {
+    this.is_loading = is_loading;
+  }
+
+  /**
+   * Asks the tab to update its loading progress, usually called in the tab's loading progress.
+   * */
+  update_loading_progress(progress: number) {
+    this.loading_progress = progress;
+  }
+}
+
+export class bunch_of_tabs {
+  Tabs: Array<tab_info_packed> = [];
+  Labels: Array<tab_label> = [];
+  current_working_tab_index: number = 0;
+  current_color_mode: number = AppStorage.get('currentColorMode') as number;
+  global_custom_UA: string = "";
+  new_tab_url: string | undefined = undefined;
+  home_url: string | undefined = undefined;
+  start_up: string | undefined = undefined;
+  private used_date_ids: number[] = [];
+
+  /**
+   * A class holding tabs in a list.
+   * Provides methods to operate tabs.
+   * @param no_init Will not check for continue folder nor do anything else.
+   * Usually set true if this object is only created to sit the place of StorageLink initialization.
+   */
+  constructor(no_init?: boolean) {
+    if (no_init == true) {
+      return;
+    }
+
+    let filesDir = context.filesDir;
+    try {
+      fs.mkdirSync(filesDir + '/continue');
+    } catch (e) {
+      // console.log('[Meow][bunch_of_tabs] constructor: E: /continue folder already exists.')
+    }
+
+    // console.log('[Meow][bunch_of_tabs] Init success!')
+  }
+
+  // Operations and Actions
+
+  /**
+   * Loads a url on the current main tab.
+   * @description Will load url_default_blank() if the home_url of this bunch_of_tabs is not set.
+   * */
+  loadUrl_onWorkingTab(url: string) {
+    url = url_meow_to_resource(url);
+    this.Tabs[this.current_working_tab_index].controller.loadUrl(url);
+  }
+
+  /**
+   * Loads home url on the current main tab.
+   * @description Will load url_default_blank() if the home_url of this bunch_of_tabs is not set.
+   * */
+  go_home_onWorkingTab() {
+    // let going_home_url = "";
+    // if (this.home_url == undefined || this.home_url == "") {
+    //   going_home_url = url_default_blank();
+    // } else {
+    //   going_home_url = this.home_url;
+    // }
+    this.loadUrl_onWorkingTab(url_default_blank());
+  }
+
+  /**
+   * Refreshes current main tab.
+   * */
+  refresh_onWorkingTab() {
+    this.Tabs[this.current_working_tab_index].controller.refresh();
+  }
+
+  /**
+   * Stops the load on current main tab.
+   * */
+  stop_onWorkingTab() {
+    this.Tabs[this.current_working_tab_index].controller.stop();
+  }
+
+  /**
+   * Try to go forward on current main tab.
+   * @returns true if success.
+   * @returns false if failed, perhaps there is no way forward.
+   * */
+  goForward_onWorkingTab() {
+    if (this.Tabs[this.current_working_tab_index].controller.accessForward()) {
+      this.Tabs[this.current_working_tab_index].controller.forward();
+    }
+  }
+
+  /**
+   * Try to go backward on current main tab.
+   * @returns true if success.
+   * @returns false if failed, perhaps there is no way backward.
+   * */
+  goBackward_onWorkingTab() {
+    if (this.Tabs[this.current_working_tab_index].controller.accessBackward()) {
+      this.Tabs[this.current_working_tab_index].controller.backward();
+      return true;
+    }
+    return false;
+  }
+
+  // UA Stuff
+
+  /**
+   * Sets the custom user agent of all tabs. Resets the custom user agent to ArkWeb default if param is "" (empty).
+   * @param ua A string, the user agent params.
+   * */
+  set_global_custom_UA(ua: string) {
+    this.global_custom_UA = ua;
+
+    if (this.global_custom_UA == "") {
+      this.reset_global_custom_UA();
+      console.log("[bunch_of_tabs][UA] Reset global_custom_UA to ArkWeb default! ua: " + this.get_global_default_UA())
+      return;
+      // Reset default if no input
+    }
+
+    for (let index = 0; index < this.Tabs.length; index++) {
+      this.Tabs[index].controller.setCustomUserAgent(this.global_custom_UA)
+    }
+    console.log("[bunch_of_tabs][UA] Set global_custom_UA! ua: " + ua)
+  }
+
+  /**
+   * Resets the custom user agent to ArkWeb default.
+   * */
+  reset_global_custom_UA() {
+    let default_ua = this.get_global_default_UA();
+    for (let index = 0; index < this.Tabs.length; index++) {
+      this.Tabs[index].controller.setCustomUserAgent(default_ua)
+    }
+  }
+
+  /**
+   * Gets the default user agent of system ArkWeb.
+   * @returns A string, the default user agent
+   * */
+  get_global_default_UA() {
+    return this.Tabs[0].controller.getUserAgent();
+  }
+
+  // General Tab Controls
+
+  /**
+   * Switches to another tab.
+   * @param target A number, the index of target tab.
+   * @returns A number, the index of target tab.
+   * */
+  switchToTab(target: number) {
+    // Switch
+    if (target >= this.Tabs.length) {
+      target = this.Tabs.length - 1;
+    }
+    this.current_working_tab_index = -1; // Weird way to refresh
+    this.current_working_tab_index = target;
+    return target;
+  }
+
+  /**
+   * Creates a new tab.
+   * @param target_url A string, if url is "", then load the default new_tab_url set in this object.
+   * @param recover_on_creation A boolean, if set true, then the tab will restore the serialized web state in
+   * @returns A number, the index of the new tab.
+   * @description While if new_tab_url is also not set ("" or undefined), then will load the url_default_blank().
+   * */
+  newTab(target_url: string, recover_on_creation?: boolean) {
+    // console.log('[bunch_of_tabs] newTab: [' + target_url + '].');
+    if (target_url == "") {
+      // If newing an empty tab
+      if (this.new_tab_url == "" || this.new_tab_url == undefined) {
+        // If new tab not specified
+        target_url = url_default_blank();
+      } else {
+        // If new tab is specified
+        target_url = this.new_tab_url;
+      }
+    }
+
+    let new_TabInfo: tab_info_packed = new tab_info_packed("Meow", target_url, recover_on_creation);
+    let Date_ID = Date.now();
+    while (this.used_date_ids.includes(Date_ID)) {
+      // Date_ID crash prevention
+      Date_ID += 1;
+    }
+    let new_TabLabel: tab_label = new tab_label(Date_ID, this.Tabs.length);
+
+    this.used_date_ids.push(Date_ID);
+    this.Tabs.push(new_TabInfo);
+    this.Labels.push(new_TabLabel);
+    this.current_working_tab_index = this.Tabs.length - 1;
+
+    return this.current_working_tab_index as number;
+  }
+
+  /**
+   * Closes a tab.
+   * @param target A number, the index of the tab requested to close.
+   * @param home_url A string, will load this url if closed the last tab and opened a new one right after it.
+   * @returns A number, the index of current working main tab which eventually switched to.
+   * */
+  closeTab(target: number, home_url: string) {
+    if (this.Tabs.length == 1) {
+      // Reset the only tab
+      this.Tabs[0].searching_keyword = '';
+      this.Tabs[0].searching_keyword_stats_current = 0;
+      this.Tabs[0].searching_keyword_stats_total = 0;
+      this.Tabs[0].controller.loadUrl(url_meow_to_resource(home_url));
+      this.Tabs[0].controller.clearHistory();
+      return 0;
+    }
+    // Now at least 2 items are in the Tabs list
+
+    let landing_destination = this.current_working_tab_index;
+    // Decide where to go
+
+    if (this.current_working_tab_index == target) {
+      // If deleting the tab currently viewing
+      if (target == 0) {
+        landing_destination = 1;
+        // If deleting the top tab
+      } else if (target == this.Tabs.length - 1) {
+        landing_destination = this.Tabs.length - 2;
+        // If deleting the last tab
+      } else {
+        landing_destination = target - 1;
+        // If in the middle
+      }
+    }
+
+    this.switchToTab(-1)
+    // Forces LinysTabs to refresh For Each loop to render
+    this.switchToTab(landing_destination)
+
+    this.Tabs.splice(target, 1)
+    this.Labels.splice(target, 1)
+
+    // Refresh index_keys
+    for (let i = target; i < this.Labels.length; ++i) {
+      this.Labels[i].index_key -= 1;
+    }
+
+    if (this.current_working_tab_index > target) {
+      this.current_working_tab_index -= 1;
+    }
+
+    return this.current_working_tab_index;
+  }
+
+  /**
+   * Gets the current main tab.
+   * @returns A tab_info_packed object, of the current main tab.
+   * */
+  workingMainTab() {
+    return this.Tabs[this.current_working_tab_index];
+  }
+
+  // Data Syncing
+
+  /**
+   * Returns the number of currently opened tabs
+   * @returns A number.
+   * */
+  get_tabs_count() {
+    return this.Tabs.length;
+  }
+
+  /**
+   * Gets a list of all tabs' titles of current viewing pages.
+   * @returns A string[] array, the titles.
+   * */
+  get_all_titles() {
+    let titles: string[] = [];
+
+    for (let index = 0; index < this.Tabs.length; index++) {
+      titles.push(this.Tabs[index].title);
+    }
+    return titles;
+  }
+
+  /**
+   * Gets a list of all tabs' current viewing urls.
+   * @returns A string[] array, the urls.
+   * */
+  get_all_urls() {
+    let urls: string[] = [];
+
+    for (let index = 0; index < this.Tabs.length; index++) {
+      urls.push(this.Tabs[index].url);
+    }
+    return urls;
+  }
+
+  /**
+   * Gets a list of all tabs' loading statuses.
+   * @returns A boolean[] array, the statuses are either true or false.
+   * */
+  get_all_is_loading() {
+    let is_loading: boolean[] = [];
+
+    for (let index = 0; index < this.Tabs.length; index++) {
+      is_loading.push(this.Tabs[index].is_loading);
+    }
+    return is_loading;
+  }
+
+  /**
+   * Gets a list of all tabs' loading progresses.
+   * @returns A number[] array, the progresses are in the range [0, 100].
+   * */
+  get_all_loading_progress() {
+    let progress: number[] = [];
+
+    for (let index = 0; index < this.Tabs.length; index++) {
+      progress.push(this.Tabs[index].loading_progress);
+    }
+    return progress;
+  }
+
+  /**
+   * Gets a list of all tabs' timestamps.
+   * @returns A number[] array, the timestamps.
+   * */
+  get_all_time_stamps() {
+    let stamps: number[] = [];
+
+    for (let index = 0; index < this.Tabs.length; index++) {
+      stamps.push(this.Labels[index].timestamp);
+    }
+    return stamps;
+  }
+
+  // Web states
+
+  /**
+   * Asks a tab to restore web state.
+   * @param web_state A Uint8Array, the serialized web state to be recovered of the tab.
+   * @param index_key A number, the index of the tab which is asked to do a recovery.
+   * */
+  restore_web_state(web_state: Uint8Array, index_key: number) {
+    if (web_state !== undefined) {
+      this.Tabs[index_key].url = '<Recovered> uwu';
+      this.Tabs[index_key].title = 'uwu';
+      console.log("[Meow][meowWebView] Restore web " + index_key.toString() + ", state Length: " + web_state.length.toString())
+      this.Tabs[index_key].controller.clearHistory();
+      this.Tabs[index_key].controller.restoreWebState(web_state);
+    } else {
+      console.error("[ERROR][Meow][meowWebView] Restore web state failed for an undefined web_state!")
+    }
+  }
+
+  /**
+   * Re-save all web states, usually used to ensure the correct order of tab indices.
+   * @param start A number, all tabs with index greater or equal to start would re-save their web states to disk.
+   * */
+  re_save_web_state(start: number) {
+    for (let index = start; index < this.Tabs.length; index++) {
+      let web_state = this.Tabs[index].web_state_array;
+      let identifier = "continue/continue_tabs_web_state_array_" + index.toString();
+      if (web_state != null) {
+        sandbox_save(identifier, web_state.buffer);
+      }
+    }
+  }
+}

+ 88 - 0
home/src/main/ets/hosts/bunch_of_user_agents.ets

@@ -0,0 +1,88 @@
+export class bunch_of_user_agents {
+  list_of_user_agents: user_agent[] = [];
+  last_accessed: number = 0;
+
+  /**
+   * A class holding a user_agent[] array, in which there stores user_agent objects.
+   */
+  constructor() {
+    // console.log('[Meow][bunch_of_user_agents] Init success!')
+  }
+
+  /**
+   * Update last accessed time. This would trigger some refresh on UI layer.
+   * */
+  update_last_accessed() {
+    this.last_accessed = Date.now();
+  }
+
+  /**
+   * Add a new user agent to the current user agent list.
+   * @param se A user_agent object, the user agent to be added.
+   * */
+  add_user_agent(ua: user_agent) {
+    this.list_of_user_agents.push(ua);
+    this.update_last_accessed();
+  }
+
+  /**
+   * Remove a user agent from the current user agent list.
+   * @param index A number, the index of the user agent to be removed in the list.
+   * */
+  del_user_agent(index: number) {
+    this.list_of_user_agents.splice(index, 1);
+    this.update_last_accessed();
+  }
+
+  /**
+   * Export user agents in a specific plain text format.
+   * @returns '\n' connected string of user agents in the format of:
+   * @example 'Firefox Windows 132\nMozilla/5.0 (Windows NT 10.0; Win64; x64; rv:132.0) Gecko/20100101 Firefox/132.0'
+   * */
+  export_string() {
+    let export_list: string[] = []
+    for (let index = 0; index < this.list_of_user_agents.length; index++) {
+      let ua: user_agent = this.list_of_user_agents[index];
+      export_list.push(ua.label);
+      export_list.push(ua.user_agent_content);
+    }
+    return export_list.join("\n");
+  }
+
+  /**
+   * Import user agents in a specific plain text format.
+   *
+   * In default overwrites whatever was in this list_of_user_agents.
+   * @param imp The string in the correct format:
+   * @example 'Firefox Windows 132\nMozilla/5.0 (Windows NT 10.0; Win64; x64; rv:132.0) Gecko/20100101 Firefox/132.0'
+   * */
+  import_string(imp: string) {
+    // Clear
+    this.list_of_user_agents = [];
+    if (imp == "") {
+      return;
+    }
+    // Import
+    let import_list: string[] = imp.split("\n");
+    for (let index = 0; index < import_list.length; index += 2) {
+      if (import_list[index] != "") {
+        this.add_user_agent(new user_agent(import_list[index], import_list[index+1]));
+      }
+    }
+  }
+}
+
+export class user_agent {
+  label: string;
+  user_agent_content: string;
+
+  /**
+   * A user_agent object, which consists of a label and the user agent text content.
+   * @param label The name of the user agent.
+   * @param user_agent_content The content of the user agent.
+   */
+  constructor(label: string, content: string) {
+    this.label = label;
+    this.user_agent_content = content;
+  }
+}

+ 108 - 0
home/src/main/ets/objects/HistoryDataSource.ets

@@ -0,0 +1,108 @@
+/**
+ * Adapted from https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-rendering-control-lazyforeach
+ * */
+import { history_record } from '../hosts/bunch_of_history';
+
+// BasicDataSource实现了IDataSource接口,用于管理listener监听,以及通知LazyForEach数据更新
+class BasicHistoryDataSource implements IDataSource {
+  private listeners: DataChangeListener[] = [];
+  private originDataArray: history_record[] = [];
+
+  public totalCount(): number {
+    return 0;
+  }
+
+  public getData(index: number): history_record {
+    return this.originDataArray[index];
+  }
+
+  // 该方法为框架侧调用,为LazyForEach组件向其数据源处添加listener监听
+  registerDataChangeListener(listener: DataChangeListener): void {
+    if (this.listeners.indexOf(listener) < 0) {
+      // console.info('add listener');
+      this.listeners.push(listener);
+    }
+  }
+
+  // 该方法为框架侧调用,为对应的LazyForEach组件在数据源处去除listener监听
+  unregisterDataChangeListener(listener: DataChangeListener): void {
+    const pos = this.listeners.indexOf(listener);
+    if (pos >= 0) {
+      // console.info('remove listener');
+      this.listeners.splice(pos, 1);
+    }
+  }
+
+  // 通知LazyForEach组件需要重载所有子组件
+  notifyDataReload(): void {
+    this.listeners.forEach(listener => {
+      listener.onDataReloaded();
+    });
+  }
+
+  // 通知LazyForEach组件需要在index对应索引处添加子组件
+  notifyDataAdd(index: number): void {
+    this.listeners.forEach(listener => {
+      listener.onDataAdd(index);
+      // 写法2:listener.onDatasetChange([{type: DataOperationType.ADD, index: index}]);
+    });
+  }
+
+  // 通知LazyForEach组件在index对应索引处数据有变化,需要重建该子组件
+  notifyDataChange(index: number): void {
+    this.listeners.forEach(listener => {
+      listener.onDataChange(index);
+      // 写法2:listener.onDatasetChange([{type: DataOperationType.CHANGE, index: index}]);
+    });
+  }
+
+  // 通知LazyForEach组件需要在index对应索引处删除该子组件
+  notifyDataDelete(index: number): void {
+    this.listeners.forEach(listener => {
+      listener.onDataDelete(index);
+      // 写法2:listener.onDatasetChange([{type: DataOperationType.DELETE, index: index}]);
+    });
+  }
+
+  // 通知LazyForEach组件将from索引和to索引处的子组件进行交换
+  notifyDataMove(from: number, to: number): void {
+    this.listeners.forEach(listener => {
+      listener.onDataMove(from, to);
+      // 写法2:listener.onDatasetChange(
+      //         [{type: DataOperationType.EXCHANGE, index: {start: from, end: to}}]);
+    });
+  }
+
+  notifyDatasetChange(operations: DataOperation[]): void {
+    this.listeners.forEach(listener => {
+      listener.onDatasetChange(operations);
+    });
+  }
+}
+
+export default class HistoryDataSource extends BasicHistoryDataSource {
+  private dataArray: history_record[] = [];
+
+  constructor(fromData: history_record[]) {
+    super();
+    this.dataArray = fromData;
+  }
+
+  public setData(data: history_record[]): void {
+    this.dataArray = data;
+    this.notifyDataReload();
+  }
+
+  public totalCount(): number {
+    return this.dataArray.length;
+  }
+
+  public getData(index: number): history_record {
+    return this.dataArray[index];
+  }
+
+  public pushData(data: history_record): void {
+    this.dataArray.push(data);
+    this.notifyDataAdd(this.dataArray.length - 1);
+  }
+}

+ 606 - 0
home/src/main/ets/pages/Index.ets

@@ -0,0 +1,606 @@
+import linysProgress from '../components/linysProgress';
+import meowTabs from '../blocks/modules/meowTabsVertical';
+import meowWebView from '../blocks/modules/meowWebView';
+import meowBookmarks from '../blocks/modules/meowBookmarks';
+import { animation_default } from '../hosts/bunch_of_defaults';
+import meowTitleBar from '../blocks/modules/meowTitleBar';
+import { bunch_of_settings } from '../hosts/bunch_of_settings';
+import { bunch_of_tabs } from '../hosts/bunch_of_tabs';
+import woofPromptFail from '../dialogs/prompts/woofPromptFail';
+import { AbilityConstant, bundleManager, ConfigurationConstant } from '@kit.AbilityKit';
+import { bunch_of_user_agents } from '../hosts/bunch_of_user_agents';
+import { bunch_of_history } from '../hosts/bunch_of_history';
+import { bunch_of_bookmarks } from '../hosts/bunch_of_bookmarks';
+import { bunch_of_downloads } from '../hosts/bunch_of_downloads';
+import { bunch_of_search_engines, search_engine } from '../hosts/bunch_of_search_engines';
+import { window } from '@kit.ArkUI';
+import { bunch_of_key_shortcuts } from '../hosts/bunch_of_key_shortcuts';
+import { sandbox_save, sandbox_unlink, uri_read_text_sync } from '../utils/storage_tools';
+import { deviceInfo } from '@kit.BasicServicesKit';
+import { fileIo } from '@kit.CoreFileKit';
+import woofHistory from '../dialogs/managers/woofHistory';
+import { meowContext } from '../utils/environment_tools';
+import { print_web } from '../utils/print_tools';
+
+import woofQuickSE from '../dialogs/quicks/woofQuickSE';
+import woofAdsBlocker from '../dialogs/managers/woofAdsBlocker';
+import { match_domain } from '../utils/url_tools';
+import woofCookies from '../dialogs/managers/woofCookies';
+import woofUpdateHistory from '../dialogs/contents/woofUpdateHistory';
+import woofPromptOK from '../dialogs/prompts/woofPromptOK';
+
+@Entry
+@Component
+struct Index {
+  // Want
+  @StorageLink('want_uri') @Watch('check_want') want_uri: string = 'want';
+  @StorageLink('want_action') want_action: string = '';
+  @StorageLink('want_type') want_type: string = '';
+  // Init Hosts
+  @StorageLink('bunch_of_settings') bunch_of_settings: bunch_of_settings = new bunch_of_settings();
+  @StorageLink('bunch_of_tabs') bunch_of_tabs: bunch_of_tabs = new bunch_of_tabs();
+  @StorageLink('bunch_of_user_agents') bunch_of_user_agents: bunch_of_user_agents = new bunch_of_user_agents();
+  @StorageLink('bunch_of_history') bunch_of_history: bunch_of_history = new bunch_of_history();
+  @StorageLink('bunch_of_bookmarks') bunch_of_bookmarks: bunch_of_bookmarks = new bunch_of_bookmarks("Bookmarks~Meow");
+  @StorageLink('bunch_of_downloads') bunch_of_downloads: bunch_of_downloads = new bunch_of_downloads();
+  @StorageLink('bunch_of_search_engines') bunch_of_search_engines: bunch_of_search_engines = new bunch_of_search_engines();
+  @StorageLink('bunch_of_key_shortcuts') @Watch('on_bunch_of_key_shortcuts_change') bunch_of_key_shortcuts: bunch_of_key_shortcuts = new bunch_of_key_shortcuts();
+  // UI Environment
+  @StorageLink('bottomAvoidHeight') bottomAvoidHeight: number = 1;
+  @StorageLink('topAvoidHeight') topAvoidHeight: number = 1;
+  @StorageLink('leftAvoidWidth') leftAvoidWidth: number = 1;
+  @StorageLink('rightAvoidWidth') rightAvoidWidth: number = 1;
+  @StorageLink('tablet_mode') tablet_mode: boolean = false;
+  @StorageLink('screen_width') screen_width: number = 0;
+  @StorageLink('screen_height') screen_height: number = 0;
+  @StorageLink('full_screen_height') full_screen_height: number = 0;
+  @StorageLink('fullscreen_mode') @Watch('on_fullscreen_switch') fullscreen_mode: boolean = false;
+  @StorageLink('animation_response') animation_response: number = 0.36;
+  @StorageLink('animation_damping_coefficient') animation_damping: number = 0.8;
+  @StorageProp('currentColorMode') @Watch('on_color_mode_change') current_color_mode: number = 0;
+  // History Indexing
+  @StorageProp('reindexing_history') reindexing_history: boolean | undefined = undefined;
+  @StorageProp('reindexing_history_progress') reindexing_history_progress: string = "";
+  @StorageProp('history_index_loading') history_index_loading: boolean | undefined = undefined;
+  @StorageProp('history_index_loading_progress') history_index_loading_progress: string = "";
+  @StorageProp('history_index_saving') history_index_saving: boolean | undefined = undefined;
+  @StorageProp('history_index_saving_progress') history_index_saving_progress: string = "";
+  // UI Statuses
+  @StorageLink('fullscreen_handler') handler: FullScreenExitHandler | null = null;
+  @StorageLink('showing_downloads') showing_downloads: boolean = false;
+  @StorageLink('showing_more_options') showing_more_options: boolean = false;
+  @StorageLink('showing_app_settings') showing_app_settings: boolean = false;
+  @StorageLink('showing_tabs') @Watch('on_showing_tabs_change') showing_tabs: boolean = false;
+  @StorageLink('showing_bookmarks') showing_bookmarks: boolean = false;
+  @StorageLink('showing_scratching_board') showing_scratching_board: boolean = false;
+  // UI params
+  @State title_bar_height: number = 0;
+  @State title_bar_alignRules: AlignRuleOption = {
+    middle: { anchor: "__container__", align: HorizontalAlign.Center },
+    bottom: { anchor: "__container__", align: VerticalAlign.Bottom }
+  };
+  // Interactions
+  @StorageLink('is_search_input_typing') is_search_input_typing: boolean = false;
+  @StorageLink('search_input') search_input: string = "加载中,请稍后";
+  // Web Statuses
+  @StorageLink('tab_titles') tab_titles: string[] = [];
+  @StorageLink('current_title') current_title: string = "= ̄ω ̄=";
+  @StorageLink('tab_urls') tab_urls: string[] = [];
+  @StorageLink('current_url') current_url: string = "= ̄ω ̄=";
+  @StorageLink('tab_loading_progresses') tab_loading_progresses: number[] = [0];
+  @StorageLink('current_loading_progress') current_loading_progress: number = 0;
+  @StorageLink('tab_is_loading') tab_is_loading: boolean[] = [true];
+  @StorageLink('current_is_loading') current_is_loading: boolean = true;
+  // Web control
+  @StorageLink('current_main_tab_index') current_main_tab_index: number = 0;
+  @StorageLink('current_sub_tab_index') current_sub_tab_index: number = -1;
+  @StorageLink('current_accessForward') current_accessForward: boolean = false;
+  @StorageLink('current_accessBackward') current_accessBackward: boolean = false;
+  // Settings
+  @StorageLink('title_bar_position') @Watch('on_title_bar_position_change') title_bar_position: string = "";
+  @StorageLink('sys_back_to_access_backward') sys_back_to_access_backward: boolean = false;
+  @StorageLink('web_force_dark_mode') web_force_dark_mode: boolean = false;
+  @StorageLink('tabs_style') tabs_style: string = "";
+  @StorageLink('tabs_style_non_tablet_mode') tabs_style_non_tablet_mode: string = "";
+  @StorageLink('collect_new_history') collect_new_history: boolean = true;
+  @StorageLink('intelligent_tracking_prevention') intelligent_tracking_prevention: boolean = false;
+  @StorageLink('use_adblock') use_adblock: boolean = true;
+  @StorageLink('adblock_exceptions') adblock_exceptions: string[] = [];
+  @StorageLink('max_bookmark_advice') max_bookmark_advice: number = 5;
+  @StorageLink('max_history_advice') max_history_advice: number = 5;
+  // Settings - Accessibility
+  @StorageLink('preferred_hand_left_or_right') preferred_hand_left_or_right: string = 'right';
+  @StorageLink('preferred_hand_reverse_tabs_panel') preferred_hand_reverse_tabs_panel: boolean = false;
+  // Gateways
+  @StorageLink('universal_close_tab_gateway') uni_close_tab_gateway: number = -1;
+  @StorageLink('universal_new_tab_gateway') uni_new_tab_gateway: string = "";
+  // Dialogs
+  @StorageLink('universal_fail_prompt_desc_gateway') @Watch('on_fail_prompt_gateway') universal_fail_prompt_desc_gateway: ResourceStr = "";
+  @State fail_prompt_desc: ResourceStr = '';
+  moveFailPrompt_control: CustomDialogController = new CustomDialogController({
+    builder: woofPromptFail({
+      desc: this.fail_prompt_desc,
+    }),
+    alignment: DialogAlignment.Center,
+    cornerRadius: 16,
+    // showInSubWindow: true,
+    width: "90%",
+  });
+  woofHistory_control: CustomDialogController = new CustomDialogController({
+    builder: woofHistory({ showing_settings: this.showing_app_settings }),
+    alignment: DialogAlignment.Center,
+    cornerRadius: 22,
+    // showInSubWindow: true,
+    width: "90%",
+  });
+  // Colors
+  @StorageLink('color_light_primary') @Watch('on_color_change') color_light_primary: ResourceColor = $r('app.color.start_window_background');
+  @StorageLink('color_light_secondary') @Watch('on_color_change') color_light_secondary: ResourceColor = $r('app.color.block_color');
+  @StorageLink('color_light_font') @Watch('on_color_change') color_light_font: ResourceColor = $r('app.color.font_color_title');
+  @StorageLink('color_dark_primary') @Watch('on_color_change') color_dark_primary: ResourceColor = $r('app.color.start_window_background');
+  @StorageLink('color_dark_secondary') @Watch('on_color_change') color_dark_secondary: ResourceColor = $r('app.color.block_color');
+  @StorageLink('color_dark_font') @Watch('on_color_change') color_dark_font: ResourceColor = $r('app.color.font_color_title');
+  @StorageLink('color_current_primary') color_current_primary: ResourceColor = $r('app.color.start_window_background');
+  @StorageLink('color_current_secondary') color_current_secondary: ResourceColor = $r('app.color.block_color');
+  @StorageLink('color_current_font') color_current_font: ResourceColor = $r('app.color.font_color_title');
+
+  async aboutToAppear(): Promise<void> {
+    this.animation_response = await this.bunch_of_settings.get('animation_response') as number;
+    this.animation_damping = await this.bunch_of_settings.get('animation_damping_coefficient') as number;
+    let showing_tabs = await this.bunch_of_settings.get('status_tabs_open') as boolean;
+    this.showing_tabs = showing_tabs;
+
+    // Figure out what color should i use
+    this.color_light_primary = await this.bunch_of_settings.get('color_light_primary') as string;
+    this.color_light_secondary = await this.bunch_of_settings.get('color_light_secondary') as string;
+    this.color_light_font = await this.bunch_of_settings.get('color_light_font') as string;
+    this.color_dark_primary = await this.bunch_of_settings.get('color_dark_primary') as string;
+    this.color_dark_secondary = await this.bunch_of_settings.get('color_dark_secondary') as string;
+    this.color_dark_font = await this.bunch_of_settings.get('color_dark_font') as string;
+
+    // Set cache dir of web-dragged image into Scratching Board
+    try {
+      fileIo.rmdirSync(this.getUIContext().getHostContext()!.filesDir + '/web-drag-image-cache');
+    } catch (e) {
+      console.error('[Index] rmdirSync /web-drag-image-cache failed! ' + e);
+    }
+    try {
+      fileIo.mkdirSync(this.getUIContext().getHostContext()!.filesDir + '/web-drag-image-cache');
+    } catch (e) {
+      console.error('[Index] mkdirSync /web-drag-image-cache failed! ' + e);
+    }
+
+    // Respond to want
+    this.check_want();
+
+    // delete Profile package
+    try {
+      sandbox_unlink('profile.zip');
+    } catch (e) {
+      console.error('[Index] unlink profile.zip! ' + e);
+    }
+
+    // Get and set app version info
+    bundleManager.getBundleInfoForSelf(bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION).then((bundleInfo) => {
+      console.log('[Meow][bunch_of_history] Saving app version: ' + bundleInfo.versionName + "(" + bundleInfo.versionCode.toString() + ")");
+      sandbox_save("last_app_versionCode.txt", bundleInfo.versionCode.toString());
+      sandbox_save("last_app_versionName.txt", bundleInfo.versionName);
+    })
+  }
+
+  onPageShow(): void {
+    // Enable continue
+    meowContext().setMissionContinueState(AbilityConstant.ContinueState.ACTIVE);
+  }
+
+  onBackPress(): boolean {
+    return this.back();
+  }
+
+
+  build() {
+    Column() {
+
+      // I know this is stupid
+      // Forgive me
+      // O.o
+      Row()// Keyboard shortcuts
+        .keyboardShortcut(this.bunch_of_key_shortcuts.fullscreen.main_key, this.bunch_of_key_shortcuts.fullscreen.modifier, () => {
+          this.fullscreen_mode = !this.fullscreen_mode;
+          this.showing_more_options = false;
+        })
+        .keyboardShortcut(this.bunch_of_key_shortcuts.history.main_key, this.bunch_of_key_shortcuts.history.modifier, () => {
+          this.woofHistory_control.open();
+        })
+        .keyboardShortcut(this.bunch_of_key_shortcuts.print.main_key, this.bunch_of_key_shortcuts.print.modifier, () => {
+          print_web(this.bunch_of_tabs.workingMainTab().controller);
+        })
+      // .keyboardShortcut(FunctionKey.ESC, [], () => {
+      //   this.back();
+      // })
+
+      Row()// Top Bar Avoid
+        .width("100%")
+        .height(this.height_of_top_avoid())
+        .backgroundColor(this.color_current_secondary)
+        .animation(animation_default())
+
+      // Scroll() {
+      //   Column() {
+
+      RelativeContainer() {
+        Row() {
+          Scroll() {
+            Row() {
+              // 点击标签页展示的内容
+              Scroll() {
+                meowTabs()
+                  .width(this.width_of_Tabs())
+                  .padding(10)
+              } // Tabs Panel
+              .height("100%")
+              .width(this.showing_tabs ? this.width_of_Tabs() : 0)
+              .animation(animation_default())
+              .scrollable(ScrollDirection.Horizontal)
+              .scrollBar(BarState.Off)
+
+              // 点击书签展示的内容
+              Scroll() {
+                meowBookmarks()
+                  .width(this.width_of_Bookmarks())
+                  .padding(10)
+              } // Bookmarks Panel
+              .height("100%")
+              .width(this.showing_bookmarks ? this.width_of_Bookmarks() : 0)
+              .animation(animation_default())
+              .scrollable(ScrollDirection.Horizontal)
+              .scrollBar(BarState.Off)
+            }
+            .width("100%")
+            .height("100%")
+          } // Bookmarks and Tabs
+          .width(this.width_of_Bookmarks_and_Tabs())
+          .margin(this.margin_of_Bookmarks_and_Tabs())
+          .animation(animation_default())
+          .scrollable(ScrollDirection.Horizontal)
+          .scrollBar(BarState.Off)
+          .height("100%")
+
+          Row() {
+            meowWebView()
+          } // WebViews
+          .width("100%")
+          .backgroundColor(this.color_current_primary)
+          .layoutWeight(this.tablet_mode ? 1 : 0)
+        } // Main Web
+        .direction(this.direction_of_web_and_side_panels())
+        .alignRules(this.title_bar_alignRules)
+        .layoutWeight(1)
+        .width("100%")
+        .backgroundColor(this.color_current_primary)
+        .onAreaChange((o, n) => {
+          this.on_main_area_change(o, n);
+        })
+        .margin(this.title_bar_position == "bottom"
+          ? { bottom: this.fullscreen_mode ? 0 : this.title_bar_height }
+          : { top: this.fullscreen_mode ? 0 : this.title_bar_height })
+        .animation(animation_default())
+
+        linysProgress({ percentage: this.current_loading_progress, is_loading: this.current_is_loading })
+          .alignRules(this.title_bar_alignRules)
+          .margin(this.title_bar_position == "bottom"
+            ? { bottom: this.fullscreen_mode ? 0 : this.title_bar_height }
+            : { top: this.fullscreen_mode ? 0 : this.title_bar_height })
+
+        meowTitleBar({
+          bar_height: this.title_bar_height,
+          title_bar_alignRules: this.title_bar_alignRules,
+        }) // Title Bar
+      } // Web, progress, and Title bar // .height(this.screen_height - this.height_of_top_avoid())
+      .layoutWeight(1)
+      .animation(animation_default())
+
+      Row()// Bottom Bar Avoid
+        .width("100%")
+        .height(this.height_of_bottom_avoid())
+        .animation(animation_default())
+        .backgroundColor(this.title_bar_position == "bottom" ? "transparent" : this.color_current_secondary)
+      //   }
+      // }
+      // .width('100%')
+      // .layoutWeight(1)
+
+    }
+    .height("100%")
+    .width("100%")
+    .alignItems(HorizontalAlign.Start)
+    .animation(animation_default())
+    .backgroundColor(this.color_current_secondary)
+    .onAreaChange((_o, n) => {
+      this.full_screen_height = n.height as number;
+    })
+    .onAppear(() => {
+      // console.log("[Meow][Index] Home Index READY")
+    })
+  }
+
+  // @Watch or environment Reactions
+
+  on_title_bar_position_change() {
+    if (this.title_bar_position == "bottom") {
+      this.title_bar_alignRules = {
+        middle: { anchor: "__container__", align: HorizontalAlign.Center },
+        bottom: { anchor: "__container__", align: VerticalAlign.Bottom }
+      }
+    } else {
+      this.title_bar_alignRules = {
+        middle: { anchor: "__container__", align: HorizontalAlign.Center },
+        top: { anchor: "__container__", align: VerticalAlign.Top }
+      }
+    }
+  }
+
+  on_main_area_change(_old: Area, n: Area) {
+    this.screen_width = n.width as number;
+    this.screen_height = n.height as number;
+    this.tablet_mode = this.screen_width > 600;
+    if (!this.tablet_mode) {
+      if (this.showing_tabs && this.showing_bookmarks) {
+        // If showing both tabs and bookmarks,
+        // then close tabs when switched to non-tablet mode
+        if (this.tabs_style_non_tablet_mode == "vertical") {
+          this.showing_tabs = false;
+        }
+      }
+    }
+  }
+
+  on_showing_tabs_change() {
+    this.bunch_of_settings.set('status_tabs_open', this.showing_tabs);
+  }
+
+  on_fail_prompt_gateway() {
+    if (this.universal_fail_prompt_desc_gateway != "") {
+      this.fail_prompt_desc = this.universal_fail_prompt_desc_gateway;
+      this.moveFailPrompt_control.open();
+      this.universal_fail_prompt_desc_gateway = "";
+    }
+  }
+
+  on_color_mode_change() {
+    if (this.current_color_mode == ConfigurationConstant.ColorMode.COLOR_MODE_DARK) {
+      // Is dark mode
+      this.color_current_primary = this.color_dark_primary;
+      this.color_current_secondary = this.color_dark_secondary;
+      this.color_current_font = this.color_dark_font;
+    } else {
+      this.color_current_primary = this.color_light_primary;
+      this.color_current_secondary = this.color_light_secondary;
+      this.color_current_font = this.color_light_font;
+    }
+  }
+
+  on_color_change() {
+    console.log('[Meow][Index] Color theme changed!');
+    // Refresh current color
+    if (this.current_color_mode == ConfigurationConstant.ColorMode.COLOR_MODE_DARK) {
+      // Is dark mode
+      this.color_current_primary = this.color_dark_primary;
+      this.color_current_secondary = this.color_dark_secondary;
+      this.color_current_font = this.color_dark_font;
+    } else {
+      this.color_current_primary = this.color_light_primary;
+      this.color_current_secondary = this.color_light_secondary;
+      this.color_current_font = this.color_light_font;
+    }
+  }
+
+  on_fullscreen_switch() {
+    window.getLastWindow(this.getUIContext().getHostContext()).then((win) => {
+      if (this.fullscreen_mode) {
+        win.setWindowSystemBarEnable([]);
+      } else {
+        win.setWindowSystemBarEnable(["status", "navigation"]);
+      }
+    })
+
+  }
+
+  on_bunch_of_key_shortcuts_change() {
+    console.log('[Index] on_bunch_of_key_shortcuts_change!');
+  }
+
+  // kinda params
+
+  width_of_Bookmarks_and_Tabs() {
+    let result = 0;
+    if (this.tablet_mode) {
+      if (this.showing_tabs && this.tabs_style != 'horizontal') {
+        result += 250;
+      }
+      if (this.showing_bookmarks) {
+        result += 350;
+      }
+    } else {
+      // non_tablet_mode
+      if (this.showing_bookmarks) {
+        result += 0.9 * this.screen_width;
+      }
+      if (this.showing_tabs && this.tabs_style_non_tablet_mode == 'vertical') {
+        result += 0.9 * this.screen_width;
+      }
+    }
+    return result;
+  }
+
+  margin_of_Bookmarks_and_Tabs(): Padding | number {
+    if (this.width_of_Bookmarks_and_Tabs() == 0) {
+      return 0;
+    }
+    let status = this.preferred_hand_left_or_right == 'right';
+    if (this.preferred_hand_reverse_tabs_panel) {
+      status = !status;
+    }
+    return status ? { right: this.rightAvoidWidth } : { left: this.leftAvoidWidth };
+  }
+
+  width_of_Bookmarks() {
+    if (this.tablet_mode) {
+      return 350;
+    }
+    return 0.9 * this.screen_width;
+  }
+
+  width_of_Tabs() {
+    if (this.tablet_mode) {
+      // Tablet mode
+      if (this.tabs_style == 'horizontal') {
+        return 0;
+      } else {
+        return 250;
+      }
+    } else {
+      // non_tablet_mode
+      if (this.tabs_style_non_tablet_mode == 'horizontal') {
+        return 0;
+      } else {
+        return 0.9 * this.screen_width;
+      }
+    }
+  }
+
+  height_of_top_avoid() {
+    if (this.fullscreen_mode) {
+      return 0;
+    }
+    if (deviceInfo.deviceType == '2in1' && this.title_bar_position == 'top' && this.tabs_style == 'horizontal' && this.showing_tabs && this.tablet_mode) {
+      return 10;
+    }
+    return this.topAvoidHeight;
+  }
+
+  height_of_bottom_avoid() {
+    if (this.fullscreen_mode) {
+      return 0;
+    }
+    if (deviceInfo.deviceType == '2in1') {
+      return 0;
+    }
+    let target = this.bottomAvoidHeight;
+    if (this.title_bar_position == "bottom") {
+      target -= 20;
+    }
+    return Math.max(0, target);
+  }
+
+  direction_of_web_and_side_panels() {
+    let status = this.preferred_hand_left_or_right == 'right';
+    if (this.preferred_hand_reverse_tabs_panel) {
+      status = !status;
+    }
+    return status ? Direction.Rtl : Direction.Ltr;
+  }
+
+  // Operations
+
+  check_want() {
+    if (this.want_uri.length == 0) {
+      // console.log('[Index] Didn\'t load want for an empty want.');
+      return;
+    }
+    console.log('[Meow][Index] Load want with action: ' + this.want_action + ', type: ' + this.want_type);
+
+    if (this.want_uri.substring(0, 7) == 'file://') {
+      this.want_file();
+    } else {
+      this.want_web();
+    }
+
+    // Respond to want
+    this.want_uri = "";
+  }
+
+  want_file() {
+    // TODO: Find a more elegant way to open things...
+    console.log('[Meow][Index][Want] Want uri is file://. Copying file to sandbox root!');
+    // Copy want file
+    try {
+      let file_content = uri_read_text_sync(this.want_uri);
+      this.uni_new_tab_gateway = 'data:text/html, ' + encodeURIComponent(file_content);
+      // let want_file_uri = copy_from_uri_to_sandbox(this.want_uri, '', 'want-cache.html');
+      // this.uni_new_tab_gateway = want_file_uri;
+    } catch (e) {
+      console.error(e);
+    }
+  }
+
+  want_web() {
+    if (this.want_action == "ohos.want.action.viewData") {
+      console.log('[Meow][Index] Load want uri: ' + this.want_uri);
+      this.uni_new_tab_gateway = this.want_uri;
+    }
+  }
+
+  /**
+   * Universal Go back function
+   * @returns true if successfully executed <Back> for some effects.
+   * */
+  back() {
+    let stop_system_back = false;
+
+    if (this.fullscreen_mode) {
+      if (this.handler != null) {
+        this.handler.exitFullScreen();
+      }
+      this.fullscreen_mode = false;
+      return true;
+    }
+
+    let showing_ui_panels = this.showing_downloads || this.showing_more_options || this.showing_app_settings || this.showing_scratching_board;
+
+    // I know this is stupid but ¯\_(ツ)_/¯ it just works
+    // Prioritize closing UI panels
+
+    if (this.tablet_mode) {
+      if (showing_ui_panels) {
+        this.showing_downloads = false;
+        this.showing_more_options = false;
+        this.showing_app_settings = false;
+        this.showing_scratching_board = false;
+        return true;
+      }
+    } else {
+      // Non-tablet mode
+      if (showing_ui_panels || this.showing_bookmarks || this.showing_tabs) {
+        this.showing_downloads = false;
+        this.showing_more_options = false;
+        this.showing_app_settings = false;
+        this.showing_scratching_board = false;
+        // Specials
+        if (this.tabs_style_non_tablet_mode == "vertical") {
+          this.showing_tabs = false;
+        }
+        this.showing_bookmarks = false;
+        return true;
+      }
+    }
+
+    // Other returns
+    // Only triggers if no closable UI panels are opened
+    if (this.sys_back_to_access_backward) {
+      if (this.go_back_tab()) {
+        stop_system_back = true;
+      } else if (this.bunch_of_tabs.get_tabs_count() > 1) {
+        // Can close something
+        this.uni_close_tab_gateway = this.current_main_tab_index;
+        stop_system_back = true;
+      }
+    }
+
+    return stop_system_back;
+  }
+
+  go_back_tab(): boolean {
+    return this.bunch_of_tabs.goBackward_onWorkingTab();
+  }
+}

+ 48 - 0
home/src/main/ets/utils/any_concurrent_tools.ets

@@ -0,0 +1,48 @@
+import { get_folder_size_Sync } from './storage_tools';
+import { taskpool } from '@kit.ArkTS';
+import { getGraphicsMemory_sync, getPrivateDirty_sync, getPss_sync, getSharedDirty_sync, getVss_sync } from './performance_tools';
+import { common } from '@kit.AbilityKit';
+
+let context = AppStorage.get("context") as common.UIAbilityContext;
+
+@Concurrent
+function get_sandbox_folder_size_concurrent(path: string): number {
+  return get_folder_size_Sync(path, true);
+}
+
+/**
+ * Get size of any folder, including its contents, in sandbox in a concurrent way.
+ * @param folder_path the folder path.
+ * @param sandbox_root to set sandbox root as root or not.
+ * @returns a number of folder size.
+ * */
+export async function get_sandbox_folder_size(folder_path: string, sandbox_root: boolean): Promise<number> {
+  let path: string;
+  if (sandbox_root) {
+    const filesDir = context.filesDir;
+    path = filesDir + '/' + folder_path;
+  } else {
+    path = folder_path;
+  }
+  return await taskpool.execute(get_sandbox_folder_size_concurrent, path) as number;
+}
+
+export async function getPss(): Promise<bigint> {
+  return await taskpool.execute(getPss_sync) as bigint;
+}
+
+export async function getVss(): Promise<bigint> {
+  return await taskpool.execute(getVss_sync) as bigint;
+}
+
+export async function getSharedDirty(): Promise<bigint> {
+  return await taskpool.execute(getSharedDirty_sync) as bigint;
+}
+
+export async function getPrivateDirty(): Promise<bigint> {
+  return await taskpool.execute(getPrivateDirty_sync) as bigint;
+}
+
+export async function getVRAM(): Promise<number> {
+  return await taskpool.execute(getGraphicsMemory_sync) as number;
+}

+ 18 - 0
home/src/main/ets/utils/clipboard_tools.ets

@@ -0,0 +1,18 @@
+import { BusinessError, pasteboard } from '@kit.BasicServicesKit';
+
+export function copy(content: string) {
+  let systemPasteboard: pasteboard.SystemPasteboard = pasteboard.getSystemPasteboard();
+  let pasteData: pasteboard.PasteData = pasteboard.createData(pasteboard.MIMETYPE_TEXT_PLAIN, content);
+
+  systemPasteboard.setData(pasteData).then(() => {
+    console.info('Succeeded in setting PasteData. Copied \"' + content + "\"");
+  }).catch((err: BusinessError) => {
+    console.error('Failed to set PasteData. Cause: ' + err.message);
+  });
+}
+
+// export async function read_first_of_board() {
+//   let systemPasteboard: pasteboard.SystemPasteboard = pasteboard.getSystemPasteboard();
+//   let text: string = (await systemPasteboard.getData()).getPrimaryText();
+//   return text;
+// }

+ 208 - 0
home/src/main/ets/utils/color_tools.ets

@@ -0,0 +1,208 @@
+import { common, ConfigurationConstant } from "@kit.AbilityKit";
+import { BusinessError } from "@kit.BasicServicesKit";
+
+/**
+ * Converts a hsv color into rgb style.
+ * @param h A number, the hue.
+ * @param s A number, the saturation.
+ * @param v A number, the value (of brightness).
+ * @returns A number[] array, [r, g, b].
+ * */
+export function hsv2rgb(h: number, s: number, v: number): number[] {
+  h = Math.max(0, Math.min(360, h));
+  s = Math.max(0, Math.min(100, s)) / 100;
+  v = Math.max(0, Math.min(100, v)) / 100;
+
+  const i = Math.floor(h / 60);
+  const f = (h / 60) - i;
+  const p = v * (1 - s);
+  const q = v * (1 - f * s);
+  const t = v * (1 - (1 - f) * s);
+
+  let r: number = 0, g: number = 0, b: number = 0;
+
+  switch (i % 6) {
+    case 0:
+      r = v;
+      g = t;
+      b = p;
+      break;
+    case 1:
+      r = q;
+      g = v;
+      b = p;
+      break;
+    case 2:
+      r = p;
+      g = v;
+      b = t;
+      break;
+    case 3:
+      r = p;
+      g = q;
+      b = v;
+      break;
+    case 4:
+      r = t;
+      g = p;
+      b = v;
+      break;
+    case 5:
+      r = v;
+      g = p;
+      b = q;
+      break;
+  }
+
+  r = Math.round(r * 255);
+  g = Math.round(g * 255);
+  b = Math.round(b * 255);
+
+  return [r, g, b];
+}
+
+/**
+ * Converts an rgb style color into hex format.
+ * @param r A number, standing for the red value of the original rgb color.
+ * @param g A number, standing for the green value of the original rgb color.
+ * @param b A number, standing for the blue value of the original rgb color.
+ * @returns A string, the hex color. Like #rrggbb.
+ * */
+export function rgb2hex(r: number, g: number, b: number) {
+  let hex = ("#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1)).toUpperCase();
+  return hex;
+}
+
+/**
+ * Converts a hsv number style color into hex format.
+ * @param hsv A number[] array, [hue, saturation, value] standing for the hsv color in number style.
+ * @returns A string, the hex color. Like #rrggbb.
+ * */
+export function hsv2hex(hsv: number[]) {
+  let rgb: number[] = hsv2rgb(hsv[0], hsv[1], hsv[2]);
+  let hex: string = rgb2hex(rgb[0], rgb[1], rgb[2]);
+  return hex;
+}
+
+/**
+ * Converts a hex string color to rgb format.
+ * @param hex A string, in the format of #______.
+ * @returns [r, g, b] if success.
+ * */
+export function hex2rgb(hex: ResourceColor) {
+  hex = (hex as string).toLowerCase();
+  let rgb: number[] = [];
+  for (let i = 1; i < 7; i += 2) {
+    rgb.push(parseInt('0x' + hex.slice(i, i + 2)));
+  }
+  return rgb;
+}
+
+/**
+ * Converts an rgb format color to hsv values.
+ * @param r A number, standing for the red value of the original rgb color.
+ * @param g A number, standing for the green value of the original rgb color.
+ * @param b A number, standing for the blue value of the original rgb color.
+ * @returns A number[] array, [h, s, v].
+ * */
+export function rgb2hsv(r: number, g: number, b: number) {
+  r = Math.max(0, Math.min(255, r));
+  g = Math.max(0, Math.min(255, g));
+  b = Math.max(0, Math.min(255, b));
+
+  const rPrime = r / 255;
+  const gPrime = g / 255;
+  const bPrime = b / 255;
+
+  const max = Math.max(rPrime, gPrime, bPrime);
+  const min = Math.min(rPrime, gPrime, bPrime);
+
+  const v = max;
+
+  let s: number;
+  if (max !== 0) {
+    s = (max - min) / max;
+  } else {
+    s = 0;
+  }
+
+  let h: number;
+  const delta = max - min;
+  if (delta !== 0) {
+    if (rPrime === max) {
+      h = (gPrime - bPrime) / delta;
+    } else if (gPrime === max) {
+      h = 2 + (bPrime - rPrime) / delta;
+    } else {
+      // bPrime === max
+      h = 4 + (rPrime - gPrime) / delta;
+    }
+
+    h = Math.round(h * 60);
+
+    if (h < 0) {
+      h += 360;
+    }
+  } else {
+    h = 0;
+  }
+
+  return [h, Math.round(s * 100), Math.round(v * 100)];
+}
+
+/**
+ * Converts a hex string color to hsv values.
+ * @param hex A string, the hex color.
+ * @returns A number[] array, [h, s, v].
+ * */
+export function hex2hsv(hex: ResourceColor) {
+  let rgb: number[] = hex2rgb(hex as string);
+  let hsv = rgb2hsv(rgb[0], rgb[1], rgb[2]);
+  return hsv;
+}
+
+/**
+ * Examines a string and check if it is a legal #______ hex color.
+ * @param hex A string.
+ * */
+export function is_legal_hex(hex: ResourceColor) {
+  let digits: string[] = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'];
+  hex = (hex as string).toUpperCase();
+  if (hex[0] != "#") {
+    return false;
+  }
+  if (hex.length != 7) {
+    return false;
+  }
+  for (let index = 1; index < hex.length; index++) {
+    if (!digits.includes(hex[index])) {
+      return false;
+    }
+  }
+  return true;
+}
+
+/**
+ * Adds transparency (0-255) to a ResourceColor
+ * */
+export function add_transparency(color: ResourceColor, transparency: number) {
+  let transparency_hex = (transparency & 0xff).toString(16).padStart(2, '0');
+  return '#' + transparency_hex + (color as string).substring(1);
+}
+
+const setColorMode = (context: common.UIAbilityContext, colorMode: ConfigurationConstant.ColorMode) => {
+  try {
+    context.getApplicationContext().setColorMode(colorMode);
+  } catch (err) {
+    let error = err as BusinessError;
+    console.error('ColorModeChangeFunctions: ' + `setColorMode failed. error code=${error.code}, message=${error.message}`);
+  }
+}
+
+export const setDarkColorMode = (context: common.UIAbilityContext) => {
+  setColorMode(context, ConfigurationConstant.ColorMode.COLOR_MODE_DARK);
+}
+
+export const setLightColorMode = (context: common.UIAbilityContext) => {
+  setColorMode(context, ConfigurationConstant.ColorMode.COLOR_MODE_LIGHT);
+}

+ 57 - 0
home/src/main/ets/utils/drag_drop_tools.ets

@@ -0,0 +1,57 @@
+import { unifiedDataChannel, uniformTypeDescriptor } from '@kit.ArkData';
+import { copy_from_uri_to_sandbox } from './storage_tools';
+
+export function allow_drop_types() {
+  return [uniformTypeDescriptor.UniformDataType.TEXT,
+    uniformTypeDescriptor.UniformDataType.HYPERLINK,
+    uniformTypeDescriptor.UniformDataType.PLAIN_TEXT,
+    uniformTypeDescriptor.UniformDataType.IMAGE]
+}
+
+/**
+ * Pass a DragEvent and some of the contents of correct type would be added to meowScratchingBoard
+ * @param e DragEvent
+ * */
+export function drop_to_scratching_board(e: DragEvent) {
+  let drop_data_records = e.getData().getRecords();
+  let result_list_string: string[] = [];
+  for (let i = 0; i < drop_data_records.length; i++) {
+    let record = drop_data_records[i];
+    if (record.getType() == uniformTypeDescriptor.UniformDataType.HYPERLINK) {
+      // Push Hyperlink
+      let desc = (record as unifiedDataChannel.Hyperlink).description;
+      let url = (record as unifiedDataChannel.Hyperlink).url;
+      if (desc && !result_list_string.includes(desc)) {
+        // Push link description
+        result_list_string.push(desc);
+      }
+      if (!result_list_string.includes(url)) {
+        // Push link
+        result_list_string.push(url);
+      }
+      console.log("[drop_to_scratching_board] Hyperlink url: [" + url + "]");
+
+    } else if (record.getType() == uniformTypeDescriptor.UniformDataType.PLAIN_TEXT) {
+      // Push text
+      let textContent = (record as unifiedDataChannel.PlainText).textContent;
+      if (!result_list_string.includes(textContent)) {
+        result_list_string.push(textContent);
+      }
+      console.log("[drop_to_scratching_board] Plain Text: [" + textContent + "]");
+
+    } else if (record.getType() == uniformTypeDescriptor.UniformDataType.IMAGE) {
+      // Push Image
+      let image_uri = (record as unifiedDataChannel.Image).imageUri;
+      console.log("[drop_to_scratching_board] image uri: [" + image_uri + "]");
+      try {
+        result_list_string.push(copy_from_uri_to_sandbox(image_uri, 'web-drag-image-cache'));
+      } catch (e) {
+        console.error('[drag_drop_tools] Plan A of Image drop failed: ' + e);
+      }
+    }
+  }
+  // Append to start of original scratching board content
+  let original_drop_result_strings = AppStorage.get('drop_result_strings') as string[];
+  AppStorage.set('drop_result_strings', result_list_string.concat(original_drop_result_strings));
+  e.setResult(DragResult.DRAG_SUCCESSFUL);
+}

+ 26 - 0
home/src/main/ets/utils/environment_tools.ets

@@ -0,0 +1,26 @@
+import { common } from '@kit.AbilityKit';
+
+/**
+ * Retrieves the context from AppStorage.
+ * @returns common.UIAbilityContext
+ * */
+export function meowContext(): common.UIAbilityContext {
+  return AppStorage.get('context') as common.UIAbilityContext;
+}
+
+export function parseTimestamp(timestamp: number, format = 'yyyy-MM-dd HH:mm') {  const date = new Date(timestamp);
+
+  const year = date.getFullYear().toString();
+  const month = (date.getMonth() + 1).toString().padStart(2, '0');
+  const day = date.getDate().toString().padStart(2, '0');
+  const hours = (date.getHours() + 1).toString().padStart(2, '0');
+  const minutes = date.getMinutes().toString().padStart(2, '0');
+
+  // 根据格式返回对应字符串
+  return format
+    .replace('yyyy', year)
+    .replace('MM', month)
+    .replace('dd', day)
+    .replace('HH', hours)
+    .replace('mm', minutes)
+}

+ 30 - 0
home/src/main/ets/utils/html_tools.ets

@@ -0,0 +1,30 @@
+let chars: string[] =
+  ["\"", "\'", "&", "<", ">", String.fromCharCode(8194), String.fromCharCode(8195), String.fromCharCode(160),
+    "©", "®", "™", "×", "÷"]
+let codes: string[] =
+  ["&quot;", "&#39;", "&amp;", "&lt;", "&gt;", "&ensp;", "&emsp;", "&nbsp;",
+    "&copy;", "&reg;", "&trade;", "&times;", "&divide;"]
+
+export function encode_string_to_html_code(input: string) {
+  let result = "";
+  for (let index = 0; index < input.length; index++) {
+    // Check each character
+    let char = input.substring(index, index + 1);
+    if (chars.includes(char)) {
+      // Encode special chars
+      result += codes[chars.indexOf(char)];
+    } else {
+      // No action for else chars
+      result += char;
+    }
+  }
+  return result;
+}
+
+export function decode_html_code_to_string(input: string) {
+  let result = input;
+  for (let index = 0; index < codes.length; index++) {
+    result = result.replaceAll(codes[index], chars[index]);
+  }
+  return result;
+}

+ 187 - 0
home/src/main/ets/utils/kv_store_tools.ets

@@ -0,0 +1,187 @@
+import { distributedKVStore } from '@kit.ArkData';
+import { BusinessError } from '@kit.BasicServicesKit';
+import { meowContext } from './environment_tools';
+
+let kvs: distributedKVStore.DeviceKVStore | undefined = undefined;
+
+export async function kv_store_get(key_name: string) {
+  if (kvs == undefined) {
+    await get_kv_store_into_app_storage();
+    // console.log("[kv_store_tools][GET] kvStore not found from AppStorage! Created & Stored a new one.");
+  } else {
+    // console.log('[kv_store_tools][GET] Using an existing KVStore.');
+  }
+
+  if (kvs) {
+    // If kvStore already got
+    let get_result = "";
+
+    await kvs.get(key_name).then((value) => {
+      // console.log("[kv_store_tools] Succeeded in getting data, key: " + key_name + ".")
+      get_result = value as string;
+    }).catch(() => {
+      get_result = "undefined"
+    })
+    return get_result;
+  } else {
+    let get_result = "error"
+    console.error("[kv_store_tools][GET][ERROR] Undefined KVStore! ")
+    return get_result;
+  }
+}
+
+export async function kv_store_put(key_name: string, put_content: string) {
+  if (kvs == undefined) {
+    await get_kv_store_into_app_storage();
+    // console.log("[kv_store_tools][GET] kvStore not found from AppStorage! Created & Stored a new one.");
+  } else {
+    // console.log('[kv_store_tools][GET] Using an existing KVStore.');
+  }
+
+  if (kvs) {
+    kvs.put(key_name, put_content, (err) => {
+      if (err !== undefined) {
+        console.error(`[kv_store_tools][ERROR] Failed to put data. Code: ${err.code},message: ${err.message}`);
+      } else {
+        console.log("[kv_store_tools] Succeeded in putting data, key: " + key_name + ", content:\n\t" + put_content);
+      }
+    });
+  }
+
+}
+
+/**
+ *
+ * @param key_name The name of key stored in kvStore.
+ * @returns The Uint8Array.
+ * @returns An Empty Uint8Array if the key is not found.
+ * @returns An Uint8Array containing -1 if kvStore is undefined.
+ * */
+export async function kv_store_get_uint_8_array(key_name: string) {
+  if (kvs == undefined) {
+    console.log("[kv_store_tools][GET] Launch of app, now getting kvStore")
+    await get_kv_store_into_app_storage();
+  }
+
+  if (kvs) {
+    // If kvStore already got
+    let get_result: Uint8Array = new Uint8Array();
+    await kvs.get(key_name).then((value) => {
+      console.log("[kv_store_tools] Succeeded in getting data, key: " + key_name + ".")
+      get_result = value as Uint8Array;
+    }).catch(() => {
+      get_result = new Uint8Array();
+    })
+    return get_result;
+
+  } else {
+    let get_result = new Uint8Array(-1);
+    console.error("[kv_store_tools][GET][ERROR] Undefined KVStore! ")
+    return get_result;
+
+  }
+}
+
+export async function kv_store_put_uint_8_array(key_name: string, put_content: Uint8Array) {
+  if (kvs == undefined) {
+    console.log("[kv_store_tools][GET] Launch of app, now getting kvStore")
+    await get_kv_store_into_app_storage();
+  }
+  if (kvs) {
+    kvs.put(key_name, put_content, (err) => {
+      if (err !== undefined) {
+        console.error(`[kv_store_tools][ERROR] Failed to put data. Code: ${err.code},message: ${err.message}`);
+      } else {
+        // console.log("[kv_store_tools] Succeeded in putting data, key: " + key_name + ".")
+      }
+    });
+  }
+
+}
+
+export async function kv_store_delete(key_name: string) {
+  if (kvs == undefined) {
+    console.log("[kv_store_tools][GET] Launch of app, now getting kvStore")
+    await get_kv_store_into_app_storage();
+  }
+  if (kvs) {
+    kvs.delete(key_name, (err) => {
+      if (err !== undefined) {
+        console.error(`[kv_store_tools][ERROR] Failed to delete data. Code: ${err.code},message: ${err.message}`);
+      } else {
+        console.info("[kv_store_tools] Succeeded in deleting data, key: " + key_name + ".");
+      }
+    });
+  }
+}
+
+async function get_kv_store_into_app_storage() {
+  console.log('[kv_store_tools] Getting a KVStore into AppStorage...')
+  let t0 = Date.now();
+
+  const kvManagerConfig: distributedKVStore.KVManagerConfig = {
+    context: meowContext(),
+    bundleName: 'com.next.liny.linysbrowserNEXT',
+  }
+
+  try {
+    let kvManager = distributedKVStore.createKVManager(kvManagerConfig);
+    console.info("[kv_store_tools] Succeeded in creating KVManager!");
+    // Get kvManager
+
+    const options: distributedKVStore.Options = {
+      createIfMissing: true,
+      encrypt: false,
+      backup: false,
+      autoSync: false,
+      kvStoreType: distributedKVStore.KVStoreType.DEVICE_COLLABORATION,
+      securityLevel: distributedKVStore.SecurityLevel.S2,
+    };
+
+    await kvManager.getKVStore<distributedKVStore.DeviceKVStore>('storeId', options)
+      .then((store: distributedKVStore.DeviceKVStore) => {
+        kvs = store;
+        let t = Date.now() - t0;
+        console.info("[kv_store_tools] Succeeded in getting KVStore! (" + t + ' ms)');
+        // Got kvStore
+      }).catch((err: BusinessError) => {
+        console.error(`[kv_store_tools][ERROR] Failed to get KVStore. Code is ${err.code},message is ${err.message}.`);
+      });
+    // Get kvStore
+
+  } catch (e) {
+    let error = e as BusinessError;
+    console.error(`[kv_store_tools][ERROR] Failed to create KVManager. Code is ${error.code},message is ${error.message}.`);
+  }
+}
+
+export async function export_json_kv_store() {
+  let result_string: string[] = [];
+  result_string.push('    "continue_tabs_count": "' + await kv_store_get('continue_tabs_count') + '"');
+  result_string.push('    "continue_tabs_main_on": "' + await kv_store_get('continue_tabs_main_on') + '"');
+  result_string.push('    "continue_tabs_sub_on": "' + await kv_store_get('continue_tabs_sub_on') + '"');
+  result_string.sort();
+  return '{\n' + result_string.join(',\n') + '\n}';
+}
+
+export async function import_json_kv_store(text: string) {
+  let jsonObject: Record<string, string> | null = null;
+  if (text.length < 2) {
+    return false;
+  }
+
+  try {
+    jsonObject = JSON.parse(text);
+  } catch (e) {
+    console.log('[Meow][kv_store_tools] Import kv_store failed! Json parse failed!');
+    return false;
+  }
+
+  Object.entries(jsonObject as Record<string, string>).forEach((item) => {
+    const key = item[0];
+    const val = item[1];
+    kv_store_put(key, val);
+  })
+  console.log('[Meow][kv_store_tools] Import kv_store:\n' + text);
+  return true;
+}

+ 65 - 0
home/src/main/ets/utils/label_link_relation_tools.ets

@@ -0,0 +1,65 @@
+export function get_relations(label_link_array: string[][], key: string, max_result_number?: number) {
+  if (key.length == 0) {
+    return [];
+  }
+  if (max_result_number == undefined) {
+    // Default fall back
+    max_result_number = 5;
+  }
+
+  // Get first filtered related items
+  let related_items: string[][] = [];
+  let relation_levels: number[] = [];
+  for (let index = 0; index < label_link_array.length; index++) {
+    let this_relation_level = get_relation_level(label_link_array[index], key);
+    if (this_relation_level < 0) {
+    } else {
+      relation_levels.push(this_relation_level);
+      related_items.push(label_link_array[index]);
+    }
+  }
+
+  // Limited not quick selection sort
+  let search_numbers = Math.min(related_items.length, max_result_number);
+  let result: string[][] = [];
+  let chosen_links: string[] = [];
+  for (let index = 0; index < search_numbers; index++) {
+    // Search for search_number results
+    let minimum = 0;
+    let found = false;
+    while (minimum < relation_levels.length - 1 && chosen_links.includes(related_items[minimum][1])) {
+      // Go to the first item that has not been chosen
+      minimum += 1;
+    }
+    for (let inner_index = 0; inner_index < relation_levels.length; inner_index++) {
+      if (relation_levels[inner_index] <= relation_levels[minimum]) {
+        if (!chosen_links.includes(related_items[inner_index][1])) {
+          found = true;
+          minimum = inner_index;
+        }
+      }
+    }
+    if (found) {
+      chosen_links.push(related_items[minimum][1]);
+      result.push(related_items[minimum]);
+      related_items.splice(minimum, 1);
+      relation_levels.splice(minimum, 1);
+    }
+  }
+  return result;
+}
+
+function get_relation_level(label_link: string[], key: string) {
+  let index_of_label = label_link[0].toUpperCase().indexOf(key.toUpperCase());
+  let index_of_link = label_link[1].toUpperCase().indexOf(key.toUpperCase());
+  if (index_of_label == -1 && index_of_link == -1) {
+    return -1;
+  }
+  if (index_of_label == -1) {
+    return index_of_link;
+  }
+  if (index_of_link == -1) {
+    return index_of_label;
+  }
+  return Math.min(index_of_label, index_of_link);
+}

+ 21 - 0
home/src/main/ets/utils/link_tools.ets

@@ -0,0 +1,21 @@
+import { OpenLinkOptions } from '@kit.AbilityKit';
+import { meowContext } from './environment_tools';
+
+/**
+ * Opens uri with filemanager.
+ * @param uri the uri.
+ * */
+export function open_file_uri(uri: string) {
+  // Open folder
+  let link: string = 'filemanager://openDirectory';
+  let openLinkOptions: OpenLinkOptions = {
+    parameters: {
+      'fileUri': uri
+    }
+  };
+  try {
+    meowContext().openLink(link, openLinkOptions)
+  } catch (e) {
+    console.error(e);
+  }
+}

+ 36 - 0
home/src/main/ets/utils/performance_tools.ets

@@ -0,0 +1,36 @@
+import { hidebug } from '@kit.PerformanceAnalysisKit';
+
+@Concurrent
+export function getPss_sync(): bigint {
+  return hidebug.getPss();
+}
+
+@Concurrent
+export function getVss_sync(): bigint {
+  return hidebug.getVss();
+}
+
+@Concurrent
+export function getCPU_sync(): number {
+  return hidebug.getCpuUsage();
+}
+
+@Concurrent
+export function getSharedDirty_sync(): bigint {
+  return hidebug.getSharedDirty();
+}
+
+@Concurrent
+export function getPrivateDirty_sync(): bigint {
+  return hidebug.getPrivateDirty();
+}
+
+@Concurrent
+export function getNativeHeapSize_sync(): bigint {
+  return hidebug.getNativeHeapSize();
+}
+
+@Concurrent
+export function getGraphicsMemory_sync(): number {
+  return hidebug.getGraphicsMemorySync();
+}

+ 55 - 0
home/src/main/ets/utils/permission_tools.ets

@@ -0,0 +1,55 @@
+import { abilityAccessCtrl, common, Permissions } from '@kit.AbilityKit';
+// import { webview } from '@kit.ArkWeb';
+
+let context = AppStorage.get("context") as common.UIAbilityContext;
+
+// webview.WebviewController.setWebDebuggingAccess(true);
+let atManager = abilityAccessCtrl.createAtManager();
+
+/**
+ * Requests permission from user.
+ * @param p Permissions[]
+ * */
+export function request_user_permission(p: Permissions[]) {
+  if (p.length == 0) {
+    return;
+  }
+  atManager.requestPermissionsFromUser(context, p);
+}
+
+/**
+ * Analyzes a list of protected resource types and return their according permissions.
+ * @param prt the list of protected resource types.
+ * @returns a list of Permissions.
+ * */
+export function permissions_for_ProtectedResourceTypes(prt: string[]) {
+  let permissions: Permissions[] = [];
+  for (let index = 0; index < prt.length; index++) {
+    const ProtectedResourceType = prt[index];
+    if (ProtectedResourceType == 'TYPE_AUDIO_CAPTURE') {
+      permissions.push('ohos.permission.MICROPHONE');
+    }
+    if (ProtectedResourceType == 'TYPE_VIDEO_CAPTURE') {
+      permissions.push('ohos.permission.CAMERA');
+    }
+  }
+  return permissions;
+}
+
+/**
+ * Analyzes a list of protected resource types and return their according descriptions in human language.
+ * @param prt the list of protected resource types.
+ * @returns list of ResourceStr.
+ * */
+export function desc_for_ProtectedResourceTypes(prt: string[]) {
+  let result: ResourceStr[] = [];
+  for (let index = 0; index < prt.length; index++) {
+    const r = prt[index];
+    if ($r('app.string.protected_resource_'.concat(r))) {
+      result.push($r('app.string.protected_resource_'.concat(r)));
+    } else {
+      result.push('[Error] Unknown Protected Resource Type? ' + r);
+    }
+  }
+  return result;
+}

+ 36 - 0
home/src/main/ets/utils/preview_tools.ets

@@ -0,0 +1,36 @@
+import { BusinessError } from '@kit.BasicServicesKit';
+import { filePreview } from '@kit.PreviewKit';
+import { common } from '@kit.AbilityKit';
+import { meowContext } from './environment_tools';
+
+const image_suffixes = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp', 'svg'];
+const image_mimes = ['jpeg', 'jpeg', 'png', 'gif', 'webp', 'bmp', 'svg+xml'];
+
+export function preview_image(uri: string) {
+  // Determine file name
+  const split_name_result = uri.split('/');
+  const file_name = split_name_result[split_name_result.length-1];
+  // Determine file suffix
+  const file_suffix_result = file_name.split('.');
+  const file_suffix = file_suffix_result[file_suffix_result.length-1];
+  // Determine mime
+  let mime: string = 'image/' + image_mimes[image_suffixes.indexOf(file_suffix.toLowerCase())];
+  // Show
+  let displayInfo: filePreview.DisplayInfo = {
+    x: 100,
+    y: 100,
+    width: 800,
+    height: 800
+  };
+  let fileInfo: filePreview.PreviewInfo = {
+    title: '(ฅ^・ﻌ・^)ฅ - ' + file_name,
+    uri: uri,
+    mimeType: mime
+  };
+  filePreview.openPreview(meowContext(), fileInfo, displayInfo).then(() => {
+    console.info('[preview_image] Succeeded in opening preview');
+  }).catch((err: BusinessError) => {
+    console.error(`[preview_image] Failed to open preview, err.code = ${err.code}, err.message = ${err.message}`);
+  });
+}
+

+ 20 - 0
home/src/main/ets/utils/print_tools.ets

@@ -0,0 +1,20 @@
+import { print } from '@kit.BasicServicesKit';
+import { meowContext } from './environment_tools';
+
+/**
+ * Print web!
+ * @param controller The webview controller.
+ * */
+export function print_web(controller: WebviewController) {
+  let print_adapter: print.PrintDocumentAdapter | undefined = AppStorage.get('print_adapter') as print.PrintDocumentAdapter | undefined;
+  if (print_adapter == undefined) {
+    let jobName = 'webPrint' + Date.now().toString();
+    print_adapter = controller.createWebPrintDocumentAdapter(jobName);
+    print.print(jobName, print_adapter, null, meowContext()).then(() => {
+      print_adapter = undefined;
+      AppStorage.set('print_adapter', undefined);
+    });
+  } else {
+    console.log('[Meow][meowWebView] Cannot print! Another print job going on?');
+  }
+}

+ 11 - 0
home/src/main/ets/utils/resource_tools.ets

@@ -0,0 +1,11 @@
+import { meowContext } from "./environment_tools";
+
+/**
+ * Analyses a ResourceStr and converts it to a string object.
+ * @param resourceStr A Resource, indicating the identifier of the ResourceStr.
+ * @returns A string, the string content retrieved.
+ * @example resource_to_string($r('app.string.Whats_new_content_1'))
+ * */
+export function resource_to_string(resourceStr: Resource) {
+  return meowContext().resourceManager.getStringSync(resourceStr);
+}

+ 19 - 0
home/src/main/ets/utils/share_tools.ets

@@ -0,0 +1,19 @@
+import { common } from '@kit.AbilityKit';
+import { systemShare } from '@kit.ShareKit';
+import { uniformTypeDescriptor as utd } from '@kit.ArkData';
+import { meowContext } from './environment_tools';
+
+export function share_link(link: string, title: string) {
+  let data: systemShare.SharedData = new systemShare.SharedData({
+    utd: utd.UniformDataType.HYPERLINK,
+    description: link,
+    title: title,
+    content: link
+  });
+  let controller: systemShare.ShareController = new systemShare.ShareController(data);
+  controller.show(meowContext(), {
+    previewMode: systemShare.SharePreviewMode.DEFAULT,
+    selectionMode: systemShare.SelectionMode.BATCH
+  });
+}
+

+ 770 - 0
home/src/main/ets/utils/storage_tools.ets

@@ -0,0 +1,770 @@
+import { BusinessError, zlib } from '@kit.BasicServicesKit';
+import { common } from '@kit.AbilityKit';
+import { fileUri, fileIo as fs, picker } from '@kit.CoreFileKit';
+import { buffer } from '@kit.ArkTS';
+import { image } from '@kit.ImageKit';
+import { bunch_of_settings } from '../hosts/bunch_of_settings';
+import { export_json_kv_store, import_json_kv_store } from './kv_store_tools';
+import { photoAccessHelper } from '@kit.MediaLibraryKit';
+import { meowContext } from './environment_tools';
+
+let context = AppStorage.get("context") as common.UIAbilityContext;
+
+// Pick & Save
+
+/**
+ * Save (Copy) any file from a path to download folder.
+ * @param path source path of object to be stored.
+ * */
+export async function download_save_from_path(path: string) {
+  let uri: string = '';
+  // 请在组件内获取context,确保this.getUIContext().getHostContext()返回结果为UIAbilityContext
+  const documentViewPicker = new picker.DocumentViewPicker(context);
+  const documentSaveOptions = new picker.DocumentSaveOptions();
+  documentSaveOptions.pickerMode = picker.DocumentPickerMode.DOWNLOAD;
+  await documentViewPicker.save(documentSaveOptions).then((documentSaveResult: Array<string>) => {
+    uri = documentSaveResult[0];
+    // console.info('documentViewPicker.save succeed and uri is:' + uri);
+
+    let path_split = path.split('/');
+    let file_name = path_split[path_split.length-1];
+
+    const save_uri = uri + '/' + file_name;
+
+    fs.copy((fileUri.getUriFromPath(path)), save_uri);
+
+  }).catch((err: BusinessError) => {
+    console.error(`[storage_tools][download_save_from_path] Invoke documentViewPicker.save failed, code is ${err.code}, message is ${err.message}`);
+  })
+  return uri;
+}
+
+/**
+ * Save a text file to device storage, pulling up a DocumentViewPicker.
+ * @param file_name A string, setting the file's name.
+ * @param content A string, fills the file's content.
+ * */
+export function document_save_text(file_name: string, content: string) {
+  let target_uri: string[] = [];
+
+  try {
+    let documentSaveOptions = new picker.DocumentSaveOptions();
+    documentSaveOptions.newFileNames = [file_name];
+    // documentSaveOptions.fileSuffixChoices = [".html", ".txt", ".json"];
+    let documentPicker = new picker.DocumentViewPicker(context);
+
+    documentPicker.save(documentSaveOptions).then((documentSaveResult: Array<string>) => {
+      target_uri = documentSaveResult
+
+      let file = fs.openSync(target_uri[0], fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);
+      let writeLen = fs.writeSync(file.fd, content);
+      console.info("[document_saver] write data to file succeed and size is:" + writeLen + " @ " + target_uri[0]);
+      fs.closeSync(file);
+
+      console.info('[document_saver] DocumentViewPicker.save successfully, documentSaveResult uri: ' +
+      target_uri[0]);
+    }).catch((err: BusinessError) => {
+      console.error('[document_saver][ERROR] DocumentViewPicker.save failed with err: ' + JSON.stringify(err));
+    });
+
+  } catch (error) {
+    let err: BusinessError = error as BusinessError;
+    console.error('[document_saver][ERROR] DocumentViewPicker failed with err: ' + JSON.stringify(err));
+  }
+}
+
+/**
+ * Save (Copy) any file from a uri (file://...) to a selected position, pulling up a DocumentViewPicker.
+ * @param from the uri.
+ * */
+export async function document_save_from_uri(from: string, file_name_overwrite?: string) {
+  // Save
+  try {
+    // Get file name
+    const split_result = from.split('/');
+    let file_name = decodeURI(split_result[split_result.length-1]);
+    if (file_name_overwrite) {
+      file_name = file_name_overwrite;
+    }
+
+    let documentSaveOptions = new picker.DocumentSaveOptions();
+    documentSaveOptions.newFileNames = [file_name];
+    let documentPicker = new picker.DocumentViewPicker(context);
+
+    await documentPicker.save(documentSaveOptions).then((documentSaveResult: Array<string>) => {
+      let target_uri = documentSaveResult
+      console.log("[document_save_from_uri] file_name: " + file_name + ' - ' + split_result[split_result.length-1])
+      console.log("[document_save_from_uri] from: " + from)
+      console.log("[document_save_from_uri] to: " + target_uri[0])
+
+      // let target_path = (new fileUri.FileUri(target_uri[0])).path;
+      // This operation is likely to cause target_path
+      // to be identified as unauthorized!
+
+      fs.copy(from, target_uri[0])
+      // .then(() => {
+      //   fs.rmdir(this.list_of_target_folders[index]);
+      // });
+
+      console.info("[document_save_from_uri] write data to file succeed @ " + target_uri[0]);
+      // this.delete_task(index);
+
+    })
+      .catch((err: BusinessError) => {
+        console.error('[document_save_from_uri][ERROR] DocumentViewPicker.save failed with err: ' +
+        JSON.stringify(err));
+      });
+
+  } catch (error) {
+    let err: BusinessError = error as BusinessError;
+    console.error('[document_save_from_uri][ERROR] DocumentViewPicker failed with err: ' + JSON.stringify(err));
+  }
+}
+
+/**
+ * Save (Copy) any file from a path to a selected position, pulling up a DocumentViewPicker.
+ * @param from the path.
+ * */
+export async function document_save_from_path(from: string, file_name_overwrite?: string) {
+  await document_save_from_uri(fileUri.getUriFromPath(from), file_name_overwrite);
+}
+
+/**
+ * Save (Copy) an image file from a uri to Gallery.
+ * @param from The uri of file.
+ * */
+export async function image_save_from_uri(from: string) {
+  try {
+    let srcFileUris: Array<string> = [from];
+    let photoCreationConfigs: Array<photoAccessHelper.PhotoCreationConfig> = [{
+      fileNameExtension: get_file_suffix(from),
+      photoType: photoAccessHelper.PhotoType.IMAGE,
+    }];
+    let context = meowContext();
+    let phAccessHelper = photoAccessHelper.getPhotoAccessHelper(context);
+    let desFileUris: Array<string> = await phAccessHelper.showAssetsCreationDialog(srcFileUris, photoCreationConfigs);
+    console.info('[storage_tools][image_save_from_uri] showAssetsCreationDialog success, data is ' + desFileUris);
+    try {
+      let srcFile = fs.openSync(srcFileUris[0], fs.OpenMode.READ_ONLY)
+      let desFile = fs.openSync(desFileUris[0], fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE)
+      fs.copyFileSync(srcFile.fd, desFile.fd)
+      fs.closeSync(srcFile);
+      fs.closeSync(desFile);
+    } catch (e) {
+      console.error('[storage_tools][image_save_from_uri] ' + e);
+    }
+  } catch (err) {
+    console.error('[storage_tools][image_save_from_uri] showAssetsCreationDialog failed, errCode is ' + err.code + ', errMsg is ' + err.message);
+  }
+}
+
+// Pick & Read
+
+/**
+ * Pick a text file from device storage, pulling up a DocumentViewPicker.
+ * @returns A string, of the chosen file's content.
+ * */
+export async function document_pick_to_text(suffixFilters?: string[]) {
+  let target_uri: string[] = [];
+
+  try {
+    let documentPicker = new picker.DocumentViewPicker(context);
+    let contents: string = "";
+    await documentPicker.select({ maxSelectNumber: 1, fileSuffixFilters: suffixFilters || [".html", ".txt"] })
+      .then((documentSelectResult) => {
+        target_uri = documentSelectResult;
+        let file = fs.openSync(target_uri[0], fs.OpenMode.READ_WRITE);
+        console.info('[document_reader] DocumentViewPicker.select successfully, documentSelectResult uri: ' +
+        target_uri[0])
+        let target_path = (new fileUri.FileUri(target_uri[0])).path;
+        let stat = fs.statSync(target_path);
+        let size = stat.size;
+        let buf = new ArrayBuffer(size);
+        let readLen = fs.readSync(file.fd, buf);
+        fs.closeSync(file);
+        let result = buf.slice(0, readLen);
+        console.info("[document_reader] read file data succeed, Length: " + readLen.toString());
+        contents = buffer.from(result).toString();
+      })
+      .catch((err: BusinessError) => {
+        console.error('[document_reader] DocumentViewPicker.select failed with err: ' + JSON.stringify(err));
+      });
+    return contents;
+  } catch (error) {
+    let err: BusinessError = error as BusinessError;
+    console.error('[ERROR][document_reader] DocumentViewPicker failed with err: ' + JSON.stringify(err));
+    return "";
+  }
+}
+
+/**
+ * Pick an image file from device storage, pulling up a DocumentViewPicker.
+ * @returns A string, of the chosen file's content.
+ * */
+export async function image_pick_to_ArrayBuffer() {
+  let selected_uri: string[] = [];
+
+  try {
+    let documentPicker = new picker.DocumentViewPicker(context);
+    let buf: ArrayBuffer | undefined = undefined;
+
+    await documentPicker.select({
+      maxSelectNumber: 1,
+      fileSuffixFilters: [".png", ".jpg", ".jpeg"]
+    })
+      .then((documentSelectResult) => {
+        selected_uri = documentSelectResult;
+        console.info('[image_document_reader] DocumentViewPicker.select successfully, documentSelectResult uri: ' +
+        selected_uri[0])
+        let file = fs.openSync(selected_uri[0], fs.OpenMode.READ_ONLY);
+        // let selected_path = (new fileUri.FileUri(selected_uri[0])).path;
+        let target_path = (new fileUri.FileUri(selected_uri[0])).path;
+        let stat = fs.statSync(target_path);
+        let size = stat.size;
+        buf = new ArrayBuffer(size);
+        fs.readSync(file.fd, buf)
+        fs.closeSync(file);
+        // fs.copyFileSync(selected_path, sand_path);
+
+        console.info("[image_document_reader] Read image from " + selected_uri[0] + " Success!");
+      })
+      .catch((err: BusinessError) => {
+        console.error('[image_document_reader] DocumentViewPicker.select failed with err: ' +
+        JSON.stringify(err));
+        return undefined;
+      });
+    return buf;
+  } catch (error) {
+    let err: BusinessError = error as BusinessError;
+    console.error('[ERROR][image_document_reader] DocumentViewPicker failed with err: ' + JSON.stringify(err));
+    return undefined;
+  }
+}
+
+// Sandbox Operations
+
+/**
+ * Save a text file or ArrayBuffer to sandbox storage, overwrites whatever that was at that place.
+ * @param file_name A string, setting the file's name.
+ * @param content A string or an ArrayBuffer, fills the file's content.
+ * */
+export function sandbox_save(file_name: string, content: string | ArrayBuffer, context_filesDir?: string) {
+  let filesDir: string = context_filesDir || context.filesDir;
+  let full_file_directory = filesDir + '/' + file_name;
+  if (fs.accessSync(full_file_directory)) {
+    fs.unlinkSync(full_file_directory);
+  }
+  let file = fs.openSync(full_file_directory, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);
+  let writeLen = fs.writeSync(file.fd, content);
+  console.info("[sandbox_save] write to file \"" + file_name + "\" succeed and size: " + writeLen + " @ " + file_name);
+  fs.closeSync(file);
+}
+
+/**
+ * Unlink (delete) a file.
+ * @param file_name A string, the path of the file.
+ * */
+export function sandbox_unlink(file_name: string, context_filesDir?: string) {
+  let filesDir: string = context_filesDir || context.filesDir;
+  let full_file_directory = filesDir + '/' + file_name;
+  try {
+    fs.unlink(full_file_directory);
+  } catch (e) {
+    console.error('[storage_tools][sandbox_unlink] [' + file_name + ']' + e);
+    return false;
+  }
+  return true;
+}
+
+/**
+ * Unlink (delete) a file in a sync way.
+ * @param file_name A string, the path of the file.
+ * */
+export function sandbox_unlink_sync(file_name: string, context_filesDir?: string) {
+  let filesDir: string = context_filesDir || context.filesDir;
+  let full_file_directory = filesDir + '/' + file_name;
+  try {
+    fs.unlinkSync(full_file_directory);
+  } catch (e) {
+    console.error('[storage_tools][sandbox_unlink_sync] [' + file_name + ']' + e);
+    return false;
+  }
+  return true;
+}
+
+/**
+ * Unlink (delete) a directory in a sync way.
+ * @param dir_name A string, the path of the file.
+ * */
+export function sandbox_rmdir_sync(dir_name: string, context_filesDir?: string) {
+  let filesDir: string = context_filesDir || context.filesDir;
+  let full_dir_directory = filesDir + '/' + dir_name;
+  try {
+    fs.rmdirSync(full_dir_directory);
+  } catch (e) {
+    console.error('[storage_tools][sandbox_rmdir_sync] [' + dir_name + ']' + e);
+    return false;
+  }
+  return true;
+}
+
+/**
+ * Read a text file from sandbox storage.
+ *
+ * If you have a context filesDir already, you can pass it to me.
+ * @returns A string, of the requested file's content.
+ * */
+export function sandbox_read_text_sync(file_name: string, context_filesDir?: string) {
+  let filesDir: string = context_filesDir || context.filesDir;
+  file_name = filesDir + "/" + file_name
+  if (!(fs.accessSync(file_name))) {
+    // file doesn't exist
+    return "undefined";
+  }
+  let file = fs.openSync(file_name, fs.OpenMode.READ_WRITE);
+  let stat = fs.statSync(file_name);
+  let size = stat.size;
+  let buf = new ArrayBuffer(size);
+  let readLen = fs.readSync(file.fd, buf);
+  fs.closeSync(file);
+  let result = buf.slice(0, readLen);
+  console.info("[sandbox_open] read file \"" + file_name + "\" data succeed, Length: " + readLen.toString());
+  return buffer.from(result).toString();
+}
+
+/**
+ * Read a text file from file uri.
+ *
+ * If you have a context filesDir already, you can pass it to me.
+ * @returns A string, of the requested file's content.
+ * */
+export function uri_read_text_sync(file_uri: string) {
+  console.log('[storage_tools][uri_read_text_sync] ' + file_uri);
+  let path = uri_2_path(file_uri);
+  if (!(fs.accessSync(path))) {
+    // file doesn't exist
+    console.log('[storage_tools][uri_read_text_sync][' + path + '] Doesn\'t exist!');
+    return "undefined";
+  }
+  let file = fs.openSync(path, fs.OpenMode.READ_WRITE);
+  let stat = fs.statSync(path);
+  let size = stat.size;
+  let buf = new ArrayBuffer(size);
+  let readLen = fs.readSync(file.fd, buf);
+  fs.closeSync(file);
+  let result = buf.slice(0, readLen);
+  console.info("[sandbox_open] read file \"" + file_uri + "\" data succeed, Length: " + readLen.toString());
+  return buffer.from(result).toString();
+}
+
+/**
+ * Read an ArrayBuffer file from sandbox storage.
+ * @returns An ArrayBuffer, of the requested file's content.
+ * */
+export function sandbox_read_arrayBuffer_sync(file_path_name: string, context_filesDir?: string) {
+  let filesDir: string = context_filesDir || context.filesDir;
+  file_path_name = filesDir + "/" + file_path_name
+  if (!(fs.accessSync(file_path_name))) {
+    // file doesn't exist
+    return undefined;
+  }
+  let file = fs.openSync(file_path_name, fs.OpenMode.READ_WRITE);
+  let stat = fs.statSync(file_path_name);
+  let size = stat.size;
+  let buf = new ArrayBuffer(size);
+  let readLen = fs.readSync(file.fd, buf);
+  console.info("[sandbox_open] read file data succeed, Length: " + readLen.toString());
+  fs.closeSync(file);
+  let result = buf.slice(0, readLen);
+  return result;
+}
+
+/**
+ * Copy a text file to sandbox storage, overwrites whatever that was at that place.
+ *
+ * THIS IS A SYNC FUNCTION
+ * @param from from a uri (file://).
+ * @param to a sandbox directory, the new parent folder of this file (in sandbox).
+ * @returns a sandbox uri, root is context.filesDir.
+ * */
+export function copy_from_uri_to_sandbox(from: string, to: string, max_name_length?: number): string {
+  let filesDir: string = context.filesDir;
+  let max_name_l: number = max_name_length || 64;
+  // Get from path
+  const from_path = new fileUri.FileUri(from).path;
+  // Get file name
+  const split_result = from.split('/');
+  let file_name = decodeURI(split_result[split_result.length-1]);
+  if (file_name.length > max_name_l) {
+    file_name = file_name.substring(file_name.length - max_name_l, file_name.length + 1);
+  }
+  // Try to prevent collision
+  file_name = Date.now().toString() + '-' + file_name;
+  // Combine target path
+  let target_path = filesDir + '/' + to + '/' + file_name;
+  console.log('[uri_copy_sandbox] Copy from [' + from_path + '] to [' + target_path + '] return [' + to + '/' + file_name + ']');
+  fs.copyFileSync(from_path, target_path);
+  return path_2_uri(target_path);
+}
+
+/**
+ * Copies a file from rawfile to sandbox
+ * @param from
+ * @param to
+ * */
+export function copy_from_rawfile_to_sandbox(from: string, to: string) {
+  let boxPath = context.filesDir
+  let to_split = to.split("/");
+  let folderPath = boxPath + "/" + to_split.slice(0, to_split.length - 2).join("/");
+  let fullPath = boxPath + "/" + to
+
+  // 获取rawfile资源文件,转换数据
+  let uint8Array: Uint8Array = context.resourceManager.getRawFileContentSync(from);
+  let bf = buffer.from(uint8Array).buffer;
+
+  // 创建沙箱目录
+  try {
+    fs.mkdirSync(folderPath)
+  } catch (e) {
+  }
+
+  // 打开文件
+  const fsOpen =
+    fs.openSync(fullPath, fs.OpenMode.READ_WRITE | fs.OpenMode.READ_ONLY | fs.OpenMode.CREATE | fs.OpenMode.TRUNC)
+
+  // 写入文件
+  let destFile = fs.writeSync(fsOpen.fd, bf);
+  // 关闭文件
+  fs.close(destFile)
+}
+
+// Info
+
+/**
+ * Returns the download folder uri.
+ * @returns DOWNLOADS uri
+ * */
+export async function get_download_uri() {
+  let uri: string = '';
+  const documentViewPicker = new picker.DocumentViewPicker(context);
+  const documentSaveOptions = new picker.DocumentSaveOptions();
+  documentSaveOptions.pickerMode = picker.DocumentPickerMode.DOWNLOAD;
+  await documentViewPicker.save(documentSaveOptions).then((documentSaveResult: Array<string>) => {
+    uri = documentSaveResult[0];
+  }).catch((err: BusinessError) => {
+    console.error(`[storage_tools][download_save_from_path] Invoke documentViewPicker.save failed, code is ${err.code}, message is ${err.message}`);
+  })
+  return uri;
+}
+
+/**
+ * Calculate the size of a folder and its containing items recursively in a sync way.
+ * @param path A string, the path of the folder.
+ * @returns A number, the size of that folder.
+ * */
+export function get_folder_size_Sync(path: string, log: boolean) {
+  let sum = fs.statSync(path).size;
+  try {
+    let dir_list = fs.listFileSync(path);
+    for (let index = 0; index < dir_list.length; index++) {
+      sum += get_folder_size_Sync(path + '/' + dir_list[index], false)
+    }
+  } catch (e) {
+
+  }
+  if (log) {
+    console.log('[storage_tools] Calculated folder size: ' + sum.toString())
+  }
+  return sum;
+}
+
+/**
+ * Gets the size of a file
+ * @param path the path or uri
+ * @returns the size in number
+ * */
+export function get_file_size_Sync(path: string) {
+  let file = fs.openSync(path);
+  let filestat = fs.statSync(file.fd);
+  fs.close(file);
+  return filestat.size;
+}
+
+/**
+ * Returns the suffix of a file name.
+ * @param file for example, 'qwq.txt.awa'
+ * @returns the suffix. e.g. 'awa'
+ * */
+export function get_file_suffix(file: string) {
+  let s = file.split('.');
+  return s[s.length-1].toUpperCase();
+}
+
+/**
+ * 将字节大小转换为带单位的可读格式(仅使用 KB、MB、GB)
+ * @param size_in_bytes 字节大小,可以是 number 或 bigint 类型
+ * @returns 带单位的可读格式字符串,如 "1.25 MB"
+ */
+export function add_units_to_size(size_in_bytes: number | bigint) {
+  // 检查参数是否有效
+  if (size_in_bytes == undefined || size_in_bytes == null) {
+    return 'No Bytes!';
+  }
+
+  let bytes: bigint;
+
+  // 安全地转换为 BigInt
+  try {
+    if (typeof size_in_bytes === 'bigint') {
+      bytes = size_in_bytes;
+    } else if (typeof size_in_bytes === 'number') {
+      // 检查是否为有效数字
+      if (isNaN(size_in_bytes) || !isFinite(size_in_bytes)) {
+        return 'Invalid size!';
+      }
+      // 转换为整数
+      bytes = BigInt(Math.floor(size_in_bytes));
+    } else {
+      return 'Invalid size!';
+    }
+  } catch (e) {
+    return 'Invalid size!';
+  }
+
+  // 如果是负数,返回错误信息
+  if (bytes < 0) {
+    return 'Invalid size!';
+  }
+
+  // 如果是0字节,直接返回
+  if (bytes === 0n) {
+    return '0 Bytes';
+  }
+
+  const units = ['Bytes', 'KB', 'MB', 'GB'];
+  let unitIndex = 0;
+  let numberResult = Number(bytes);
+
+  // 转换为合适的单位(仅使用 KB、MB、GB)
+  while (numberResult >= 1024 && unitIndex < units.length - 1) {
+    numberResult /= 1024;
+    unitIndex++;
+  }
+
+  // 对于 Bytes 单位,不显示小数点
+  if (unitIndex === 0) {
+    return Math.round(numberResult) + ' ' + units[unitIndex];
+  }
+
+  // 对于其他单位,保留2位小数
+  return numberResult.toFixed(2) + ' ' + units[unitIndex];
+}
+
+/**
+ * Determines whether filename is legal.
+ * @param filename A string, the filename.
+ * @returns true or false.
+ * */
+export function is_legal_filename(filename: string) {
+  if (filename.length > 255) {
+    return false;
+  }
+  if (filename.includes('\\')) {
+    return false;
+  }
+  if (filename.includes('/')) {
+    return false;
+  }
+  if (filename.includes(':')) {
+    return false;
+  }
+  if (filename.includes('*')) {
+    return false;
+  }
+  if (filename.includes('?')) {
+    return false;
+  }
+  if (filename.includes('\"')) {
+    return false;
+  }
+  if (filename.includes('<')) {
+    return false;
+  }
+  if (filename.includes('>')) {
+    return false;
+  }
+  if (filename.includes('|')) {
+    return false;
+  }
+  return true;
+}
+
+// Conversions
+
+/**
+ * Converts a uri to path.
+ * @param uri The uri.
+ * @returns Its path.
+ * */
+export function uri_2_path(uri: string) {
+  return new fileUri.FileUri(uri).path;
+}
+
+/**
+ * Converts a path to uri.
+ * @param path The path.
+ * @returns Its uri.
+ * */
+export function path_2_uri(path: string) {
+  return fileUri.getUriFromPath(path);
+}
+
+/**
+ * Converts an ArrayBuffer to image.PixelMap object.
+ * @param buf the ArrayBuffer
+ * @returns an image.PixelMap
+ * */
+export function arrayBuffer_2_pixelMap(buf: ArrayBuffer) {
+  // ArrayBuffer => PixelMap
+  const imageSource = image.createImageSource(buf);
+  console.log('imageSource: ' + JSON.stringify(imageSource));
+  let pixelMap = imageSource.createPixelMapSync({});
+  imageSource.release();
+  return pixelMap;
+}
+
+// Global Data
+
+/**
+ * Exports everything, ALL USER DATA.
+ * */
+export async function export_everything() {
+  sandbox_save('settings.json.browsercatsettings.txt', (AppStorage.get('bunch_of_settings') as bunch_of_settings).toString());
+  sandbox_save('kv_store.json.browsercatstore.txt', await export_json_kv_store());
+  sandbox_unlink_sync('profile.zip');
+  sandbox_rmdir_sync('temp');
+
+  let inFile = context.filesDir;
+  let outFile = context.filesDir + '/profile.zip';
+
+  let options: zlib.Options = {
+    level: zlib.CompressLevel.COMPRESS_LEVEL_DEFAULT_COMPRESSION,
+    memLevel: zlib.MemLevel.MEM_LEVEL_DEFAULT,
+    strategy: zlib.CompressStrategy.COMPRESS_STRATEGY_DEFAULT_STRATEGY
+  };
+
+  try {
+    zlib.compressFile(inFile, outFile, options).then((data: void) => {
+      console.info('compressFile success. data: ' + JSON.stringify(data));
+      // SAVE
+      document_save_from_path(outFile, 'profile.zip.browsercatprofile').then(() => {
+        sandbox_unlink_sync('settings.json.browsercatsettings.txt');
+        sandbox_unlink_sync('kv_store.json.browsercatstore.txt');
+        // sandbox_unlink_sync('profile.zip');
+        AppStorage.set('compressing_output_profile', false);
+      })
+    }).catch((errData: BusinessError) => {
+      console.error(`errData is errCode:${errData.code}  message:${errData.message}`);
+    })
+
+  } catch (errData) {
+    let code = (errData as BusinessError).code;
+    let message = (errData as BusinessError).message;
+    console.error(`errData is errCode:${code}  message:${message}`);
+  }
+}
+
+/**
+ * Imports everything, overwriting all current user data.
+ * */
+export async function import_everything() {
+  try {
+    fs.mkdirSync(context.filesDir + '/temp')
+  } catch (e) {
+    console.error('[storage_tools][import_everything] Create temp dir error: ' + e);
+  }
+
+  try {
+    let selected_uri: string[] = [];
+    let documentPicker = new picker.DocumentViewPicker(context);
+
+    await documentPicker.select({
+      maxSelectNumber: 1,
+      fileSuffixFilters: ["browsercatprofile"]
+    })
+      .then((documentSelectResult) => {
+        selected_uri = documentSelectResult;
+        console.info('[import_everything] DocumentViewPicker.select successfully, documentSelectResult uri: ' + selected_uri[0]);
+      })
+      .catch((err: BusinessError) => {
+        console.error('[import_everything] DocumentViewPicker.select failed with err: ' +
+        JSON.stringify(err));
+        return false;
+      });
+
+    // Decompress
+    let inFile = new fileUri.FileUri(selected_uri[0]).path;
+    let outFileDir = context.filesDir + '/temp';
+    let options: zlib.Options = {
+      level: zlib.CompressLevel.COMPRESS_LEVEL_DEFAULT_COMPRESSION,
+      // parallel: zlib.ParallelStrategy.PARALLEL_STRATEGY_PARALLEL_DECOMPRESSION
+    };
+
+    try {
+      await zlib.decompressFile(inFile, outFileDir, options).then(() => {
+
+        // Traverse all files and folders
+        // Clear sandbox
+        fs.listFileSync(context.filesDir).forEach((item) => {
+          const fullPath = context.filesDir + '/' + item;
+          const stat = fs.statSync(fullPath);
+          if (stat.isDirectory()) {
+            if (item !== 'temp') {
+              sandbox_rmdir_sync(item);
+            }
+          } else {
+            sandbox_unlink_sync(item);
+          }
+        });
+
+        // Traverse temp and copy
+        fs.listFileSync(context.filesDir + '/temp').forEach((item) => {
+          const fullPath = context.filesDir + '/temp/' + item;
+          const target = context.filesDir + '/' + item;
+          // console.log(fullPath);
+          const stat = fs.statSync(fullPath);
+          if (stat.isDirectory()) {
+            fs.copyDirSync(fullPath, context.filesDir);
+            console.log('[storage_tools][import_everything] copyDirSync from ' + fullPath + ', to ' + context.filesDir + '/' + item);
+          } else {
+            fs.copyFileSync(fullPath, target);
+            console.log('[storage_tools][import_everything] copyFileSync from ' + fullPath + ', to ' + context.filesDir + '/' + item);
+          }
+        });
+
+        sandbox_rmdir_sync('temp');
+
+        // Import settings
+        (AppStorage.get('bunch_of_settings') as bunch_of_settings).import_json(sandbox_read_text_sync('settings.json.browsercatsettings.txt'));
+        import_json_kv_store(sandbox_read_text_sync('kv_store.json.browsercatstore.txt'));
+        // Delete
+        sandbox_unlink_sync('settings.json.browsercatsettings.txt');
+        sandbox_unlink_sync('kv_store.json.browsercatstore.txt');
+
+      }).catch((errData: BusinessError) => {
+        console.error(`errData is errCode:${errData.code}  message:${errData.message}`);
+        return false;
+      })
+    } catch (e) {
+      console.error('[import_everything] ' + e);
+      return false;
+    }
+
+    console.info("[import_everything] Read zip from " + selected_uri[0] + " Success!");
+    return true;
+  } catch (error) {
+    let err: BusinessError = error as BusinessError;
+    console.error('[ERROR][import_everything] DocumentViewPicker failed with err: ' + JSON.stringify(err));
+    return false;
+  }
+}

+ 14 - 0
home/src/main/ets/utils/ui_tools.ets

@@ -0,0 +1,14 @@
+/**
+ * Gets current tabs style is whether horizontal or not.
+ * @returns a boolean. True for is horizontal.
+ * */
+export function current_tabs_style_is_horizontal() {
+  let tablet_mode = AppStorage.get('tablet_mode') as boolean;
+  let tabs_style = AppStorage.get('tabs_style') as string;
+  let tabs_style_non_tablet_mode = AppStorage.get('tabs_style_non_tablet_mode') as string;
+  if (tablet_mode) {
+    return tabs_style == 'horizontal';
+  } else {
+    return tabs_style_non_tablet_mode == 'horizontal';
+  }
+}

+ 406 - 0
home/src/main/ets/utils/url_tools.ets

@@ -0,0 +1,406 @@
+import { default_search_engine } from '../hosts/bunch_of_defaults';
+import { Want } from '@kit.AbilityKit';
+import { meowContext } from './environment_tools';
+
+/**
+ * Process the search_input into a visitable address.
+ * @param search_input A string, the user input to search or visit.
+ * @returns A [string, boolean] array, string for the unified, visitable address,
+ * while the boolean for whether the input itself is an address.
+ * */
+export function unify_search_input_into_url(search_input: string, auto_scheme_overwrite?: string) {
+  let url = search_input
+
+  if (url == "") {
+    // if is empty
+    return ["", false]
+  }
+
+  if (is_legal_ipv4(url)) {
+    return [ensure_scheme(url, auto_scheme_overwrite ? auto_scheme_overwrite : "http") as string, true]
+  }
+
+  if (is_legal_url(url)) {
+    return [ensure_scheme(url, auto_scheme_overwrite ? auto_scheme_overwrite : "https") as string, true]
+  }
+
+  let search_engine = AppStorage.get('search_engine') as string;
+  if (search_engine === undefined) {
+    search_engine = default_search_engine();
+  }
+  let url_processed = search_engine.replaceAll("%s", url);
+  return [url_processed as string, false];
+}
+
+function is_pure_number(input: string) {
+  let processed_input = Number.parseInt(input).toString()
+  if (processed_input == "NaN") {
+    // If the first letter of input is not a number
+    return false
+  }
+  return processed_input.length == input.length
+}
+
+function is_containing_number(input: string) {
+  let input_replaced = input.replace(/\d+/g, "")
+  if (input_replaced.length != input.length) {
+    // if contains number
+    return true;
+  } else {
+    return false;
+  }
+}
+
+export function ensure_scheme(input: string, scheme?: string) {
+  let url = input
+  if (url.substring(0, 5) == 'data:') {
+    // If is data
+    return url;
+  }
+  if (url.includes("://")) {
+    let url_split = url.split("/")
+    if ((url_split[0].substring(url_split[0].length - 1) == ":") && (url_split[1] == "")) {
+      // if input has a valid scheme
+      return url;
+    }
+  }
+  // if input needs a scheme
+  if (scheme) {
+    return scheme + "://" + url
+  }
+  return "http://" + url
+}
+
+function is_legal_ipv4(input: string) {
+  if (input == "") {
+    // if is empty
+    return false;
+  }
+
+  // input = "https://114.114.114.114:32767/somewhat"
+
+  input = match_domain(input)[1]
+  // input = "114.114.114.114:32767/somewhat"
+  input = input.split("/")[0]
+  // eliminate slashes
+  // input = "114.114.114.114:32767"
+  let split_ports = input.split(":");
+  input = split_ports[0]
+  // eliminate ports
+  // input = "114.114.114.114"
+
+  let input_split = input.split(".")
+  if (input_split.length != 4) {
+    return false;
+    // if the length of parts isn't 4
+  }
+
+  for (let index = 0; index < 4; index++) {
+    let part = input_split[index]
+    if (!is_pure_number(part)) {
+      // if any part isn't a pure number
+      return false;
+    }
+    let pure_number = Number.parseInt(part)
+    if (!(0 <= pure_number && pure_number <= 255)) {
+      // if any part not in legal range [0, 255]
+      return false;
+    }
+  }
+
+  // if there is a port
+  if (split_ports.length == 2) {
+    let port = split_ports[1];
+    // examine ports, like 32767
+    if (!is_pure_number(port)) {
+      return false;
+    }
+    let port_int = Number.parseInt(port);
+    if (!(0 <= port_int && port_int <= 65535)) {
+      // if any part not in legal range [0, 255]
+      // or port not in legal range [0, 65535]
+      return false;
+    }
+  }
+
+  return true;
+}
+
+function is_legal_url(input: string) {
+  if (input == "") {
+    // if is empty
+    return false;
+  }
+
+  // input = "https://www.google.com/about"
+
+  let scheme_result = match_domain(input);
+  // scheme_result = ["https","www.google.com/about"]
+
+  let scheme: string = scheme_result[0];
+
+  // Other schemes, directly jump
+  if (scheme.length >= 1 && !viewable_domains().includes(scheme)) {
+    return true;
+  }
+
+  let url_first_part = scheme_result[1].split("/")[0].split(".")
+  // eliminate slashes and split by dots
+  // url_first_part = ["www", "google", "com"]
+
+  if (url_first_part.length < 2 && scheme == "") {
+    // if doesn't contain dots and has no scheme
+    return false;
+  }
+
+  let top_domain = url_first_part[url_first_part.length - 1]
+  // top_domain = "com"
+
+  if (top_domain.length == 0) {
+    // if url ends with a dot
+    return false;
+  }
+
+  if (is_containing_number(top_domain)) {
+    // if top domain contains numbers
+    return false;
+  }
+
+  return true
+}
+
+let urls: string[] = [
+  "resource://rawfile/home.html"
+]
+
+let built_ins: string[] = [
+  "meow://home"
+]
+
+/**
+ * Translates a url with resource:// to meow://
+ * @param url The url.
+ * @returns The translated url.
+ * */
+export function url_resource_to_meow(url: string) {
+  let result: string = url;
+
+  if (url == undefined) {
+    return result;
+  }
+  if (url == null) {
+    return result;
+  }
+  if (url == "") {
+    return result;
+  }
+  // No process for empty inputs
+
+  if (url.length > 11) {
+    if (url.substring(0, 11) == "resource://") {
+      let index = urls.indexOf(url)
+      if (index != -1) {
+        result = built_ins[index]
+      } else {
+        // If input is a non-existing resource:// url
+      }
+    }
+  }
+  return result;
+}
+
+/**
+ * Translates a url with meow:// to resource://
+ * @param url The url.
+ * @returns The translated url.
+ * */
+export function url_meow_to_resource(url: string) {
+  let result: string = url;
+
+  if (url == undefined || url == null) {
+    return result;
+  }
+  // No process for empty inputs
+
+  if (url.length > 7) {
+    if (url.substring(0, 7) == "meow://") {
+      // if is meow:// scheme
+      let index = built_ins.indexOf(url);
+      if (index != -1) {
+        // if this link exists
+        result = urls[index];
+      } else {
+        // if this link of meow:// scheme doesn't exist
+      }
+    }
+  }
+  return result;
+}
+
+/**
+ * Analyze an url and divides it into scheme and domain.
+ *
+ * If the input comes with no scheme then '' is returned in scheme field.
+ * @param input the url
+ * @returns ['scheme', 'domain']
+ * */
+export function match_domain(input: string) {
+  let url = input
+  let scheme: string = ""
+  if (url.substring(0, 5) == 'data:') {
+    return ['data', url.substring(6)];
+  }
+  if (url.includes("://")) {
+    // if input contains protocol scheme
+    let url_split = url.split("/");
+    if ((url_split[0].substring(url_split[0].length - 1) == ":") && (url_split[1] == "")) {
+      // if protocol scheme is at the beginning
+      // url = url.split("/").slice(2).join("/");
+      url = url.split("/")[2];
+      scheme = url_split[0].split(":")[0];
+    }
+  }
+  return [scheme, url];
+}
+
+/**
+ * Domains that can be loaded and displayed in web components
+ * @returns
+ * */
+export function viewable_domains() {
+  return ['http', 'https', 'meow', 'resource', 'file'];
+}
+
+/**
+ * Extracts search engine and search keyword from an url
+ * @param url
+ * @returns ['search.engine/q=%s', 'keyword']
+ * */
+export function extract_search(url: string) {
+  // url = decodeURI(url);
+  let result: string[] = ['', ''];
+
+  let keys: string[] =
+    ['q', 'query', 's', 'key', 'wd', 'w', 'word', 'searchKeyWord', 'search_text', 'keyword', 'val', 'value', 'text'];
+  let search_keys: string[] = [];
+  for (let k = 0; k < keys.length; k++) {
+    search_keys.push('?' + keys[k] + '=');
+  }
+  for (let k = 0; k < keys.length; k++) {
+    search_keys.push('&' + keys[k] + '=');
+  }
+
+  // Determine keyword
+  let keyword: string = '';
+
+  for (let s = 0; s < search_keys.length; s++) {
+    let search_key: string = search_keys[s];
+    let search_key_length: number = search_key.length;
+    let search_key_index = url.indexOf(search_key);
+    if (search_key_index > -1) {
+
+      // Search key match
+      let next_and_index = url.indexOf('&', search_key_index + search_key_length);
+      if (next_and_index > -1) {
+        // There is an '&' for end
+        keyword = url.substring(search_key_index + search_key_length, next_and_index);
+      } else {
+        // There is no '&' for end
+        keyword = url.substring(search_key_index + search_key_length);
+      }
+      // if (result[1] != '') {
+      //   result[1] = result[1].replaceAll('+', " ")
+      //   result[1] = decodeURIComponent(result[1]);
+      // }
+      result[0] = url.replaceAll(search_key + keyword, search_key + '%s');
+      result[1] = keyword;
+      result[1] = result[1].replaceAll('+', " ")
+      result[1] = decodeURIComponent(result[1]);
+      return result;
+    }
+  }
+
+  // ['baidu.com/s?q=%s','awa']
+  return result;
+}
+
+/**
+ * Analyze and divides the text data items and out put a unified list of texts and links
+ * @param data_list strings
+ * @returns [extracted_data: string[], extracted_type: string[]]
+ * */
+export function extract_links_from_text(data_list: string[]) {
+  let extracted_data: string[] = [];
+  let extracted_type: string[] = [];
+  for (let index = 0; index < data_list.length; index++) {
+    let text = data_list[index];
+    // let match = text.match(/(file|https?|meow):\/\/[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|]/g);
+    let match = text.match(/([a-zA-Z][a-zA-Z0-9+.-]*):\/\/[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|]/g);
+    console.log('[extract_links_from_text] [' + match?.join("\n") + "]");
+    if (match) {
+      for (let inner = 0; inner < match.length; inner++) {
+        let this_url = match[inner]
+        let position = text.indexOf(this_url);
+        if (text.substring(0, position).replace(/\s*/, "").length > 0) {
+          extracted_data.push(text.substring(0, position).replaceAll(/(^\s*)|(\s*$)/g, ""));
+          extracted_type.push("text");
+        }
+        extracted_data.push(this_url);
+        if (this_url.substring(0, 4) == 'file') {
+          // TODO: this is only valid when IMAGE is the only allowed file type.
+          extracted_type.push("image");
+        } else {
+          extracted_type.push("url");
+        }
+        text = text.substring(position + this_url.length);
+      }
+    }
+    if (text.replace(/\s*/, "").length > 0) {
+      extracted_data.push(text.replaceAll(/(^\s*)|(\s*$)/g, ""));
+      extracted_type.push("text");
+    }
+  }
+
+  return [extracted_data, extracted_type]
+}
+
+
+/**
+ * Determines if a link is downloadable.
+ * @param link The url.
+ * @returns [boolean, string(filename)] If it is downloadable.
+ * */
+export function is_downloadable(link: string) {
+  try {
+    let last_part = ((link.split('&')[0]).split('/').pop() || '').split('?')[0];
+    if (last_part?.includes('.')) {
+      // Has dot
+      let filename = last_part;
+      if (filename.length > 233) {
+        filename = filename.substring(filename.length - 233, filename.length);
+      }
+      return [true, filename];
+    }
+  } catch (e) {
+    console.error('[url_tools][is_downloadable] ' + e);
+  }
+  return [false, ''];
+}
+
+/**
+ * Jumps to an external link using want.
+ * @param link the link
+ * */
+export function jump_external_link(link: string) {
+  // jump to url received
+  const want: Want = {
+    uri: link
+  };
+  meowContext().startAbility(want).then(() => {
+    // Pull up success
+    console.log('[Meow][jump_external_link] Pull up application for link ' + link + ' Success!');
+  }).catch(() => {
+    // Pull up failure
+    console.error('[ERROR][Meow][url_tools] Pull up application for link ' + link + ' Failed!');
+  });
+}

Некоторые файлы не были показаны из-за большого количества измененных файлов