From 8de45322f0efa4cf1762d9fa99b572131989a1e2 Mon Sep 17 00:00:00 2001 From: Huakun Shen Date: Sat, 18 Jan 2025 07:07:36 -0500 Subject: [PATCH] init --- .github/workflows/jsr-publish.yml | 23 + .gitignore | 177 ++++++ .vscode/settings.json | 3 + README.md | 125 ++++ build.ts | 30 + deno-src/deno.json | 9 + deno-src/deno.lock | 964 ++++++++++++++++++++++++++++++ deno-src/dev.ts | 7 + deno-src/index.ts | 13 + deno-src/lib.ts | 21 + jsr.json | 13 + mod.ts | 0 package.json | 116 ++++ src/bookmark.ts | 263 ++++++++ src/constants.ts | 2 + src/i18n/en.ts | 5 + src/i18n/index.ts | 20 + src/i18n/zh.ts | 5 + src/index.ts | 140 +++++ src/types.ts | 42 ++ tsconfig.json | 27 + 21 files changed, 2005 insertions(+) create mode 100644 .github/workflows/jsr-publish.yml create mode 100644 .gitignore create mode 100644 .vscode/settings.json create mode 100644 README.md create mode 100644 build.ts create mode 100644 deno-src/deno.json create mode 100644 deno-src/deno.lock create mode 100644 deno-src/dev.ts create mode 100644 deno-src/index.ts create mode 100644 deno-src/lib.ts create mode 100644 jsr.json create mode 100644 mod.ts create mode 100644 package.json create mode 100644 src/bookmark.ts create mode 100644 src/constants.ts create mode 100644 src/i18n/en.ts create mode 100644 src/i18n/index.ts create mode 100644 src/i18n/zh.ts create mode 100644 src/index.ts create mode 100644 src/types.ts create mode 100644 tsconfig.json diff --git a/.github/workflows/jsr-publish.yml b/.github/workflows/jsr-publish.yml new file mode 100644 index 0000000..48bbe9d --- /dev/null +++ b/.github/workflows/jsr-publish.yml @@ -0,0 +1,23 @@ +name: JSR Publish +on: + push: + branches: + - main + +jobs: + publish: + runs-on: ubuntu-latest + + permissions: + contents: read + id-token: write + + steps: + - uses: actions/checkout@v4 + - uses: oven-sh/setup-bun@v2 + - name: Install dependencies + run: bun install + - name: Build + run: bun run build + - name: Publish package + run: npx jsr publish diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8c67a06 --- /dev/null +++ b/.gitignore @@ -0,0 +1,177 @@ +# Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore + +# Logs + +logs +_.log +npm-debug.log_ +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Caches + +.cache + +# Diagnostic reports (https://nodejs.org/api/report.html) + +report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json + +# Runtime data + +pids +_.pid +_.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover + +lib-cov + +# Coverage directory used by tools like istanbul + +coverage +*.lcov + +# nyc test coverage + +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) + +.grunt + +# Bower dependency directory (https://bower.io/) + +bower_components + +# node-waf configuration + +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) + +build/Release + +# Dependency directories + +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) + +web_modules/ + +# TypeScript cache + +*.tsbuildinfo + +# Optional npm cache directory + +.npm + +# Optional eslint cache + +.eslintcache + +# Optional stylelint cache + +.stylelintcache + +# Microbundle cache + +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history + +.node_repl_history + +# Output of 'npm pack' + +*.tgz + +# Yarn Integrity file + +.yarn-integrity + +# dotenv environment variable files + +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) + +.parcel-cache + +# Next.js build output + +.next +out + +# Nuxt.js build / generate output + +.nuxt +dist + +# Gatsby files + +# Comment in the public line in if your project uses Gatsby and not Next.js + +# https://nextjs.org/blog/next-9-1#public-directory-support + +# public + +# vuepress build output + +.vuepress/dist + +# vuepress v2.x temp and cache directory + +.temp + +# Docusaurus cache and generated files + +.docusaurus + +# Serverless directories + +.serverless/ + +# FuseBox cache + +.fusebox/ + +# DynamoDB Local files + +.dynamodb/ + +# TernJS port file + +.tern-port + +# Stores VSCode versions used for testing VSCode extensions + +.vscode-test + +# yarn v2 + +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* + +# IntelliJ based IDEs +.idea + +# Finder (MacOS) folder config +.DS_Store +extensions_support/ + diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..81a8778 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "deno.enable": false +} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..e1c7cc7 --- /dev/null +++ b/README.md @@ -0,0 +1,125 @@ +# Kunkun Template UI Extension + +This is a template for a template UI extension. (UI follows pre-defined template) + +[./src/index.ts](./src/index.ts) is the default entrypoint for the extension. You can import any other files in this file, but the build process will bundle them into a single file. + +## Pros and Cons + +This type of extension is suitable for simple use cases, such as a list or form. All components are pre-defined, so there is not much room for customization. If you want more flexibility on the UI, consider using [Custom UI Extension](https://docs.kunkun.sh/extensions/custom-ui-ext/), which requires some frontend knowledge but gives you full control over the UI. + +Read documentation at https://docs.kunkun.sh/extensions/worker-template/ + +Make sure you understand what this type of extension is capable of. + +### Pros + +- Simple to develop, no need for any frontend knowledge. +- Small bundle size (~40KB) + - [Custom UI Extension](https://docs.kunkun.sh/extensions/custom-ui-ext/) are usually larger than 300KB. + +### Cons + +- Limited UI customization. Not suitable for complex use cases. + +Consider [Custom UI Extension](https://docs.kunkun.sh/extensions/custom-ui-ext/) if you need more complex UI. + +## Development + +```bash +pnpm install +``` + +Start extension in development mode. Every save will trigger a hot reload in Kunkun. + +```bash +pnpm dev +``` + +- During development, right click in Kunkun to open the developer tools. + - Error messages will be shown in the console. + - If you got any permission error while calling Kunknu's APIs, make sure you've declared the permission in `package.json`. Then go back to home page and enter the extension again to re-apply the permission. +- To develop and preview the extension in Kunkun, you need to run the `Add Dev Extension` command in Kunkun, and register this extension's path. + +Build the extension. Your extension source code can contain many files, but the build process will bundle them into a single file. + +```bash +pnpm build +# Due to Bun's bug, if you are on windows, and install dependencies with pnpm, you may get error during build. +# Try install dependencies with bun or npm instead. +``` + +## i18n + +[./src/i18n](./src/i18n/) contains optional internationalization support starter code. + +If you want to support i18n, you can use the `t` function to translate the strings in the extension. + +User's language setting is available via `app.language()`. + +```ts +import { app } from "@kksh/api/ui/worker" +import { setupI18n, t } from "./src/i18n" + +setupI18n("zh") +console.log(t("welcome")) + +setupI18n(await app.language()) +console.log(t("welcome")) +``` + +## Add More Commands + +If you want to add more template worker extension commands, simply modify the `entrypoints` array in [./build.ts](./build.ts). + +Then in `package.json`, register the new command. + +## Verify Build and Publish + +```bash +pnpm build # make sure the build npm script works +npx kksh@latest verify # Verify some basic settings +npx kksh@latest verify --publish # Verify some basic settings before publishing +``` + +It is recommended to build the extension with the same environment our CI uses. + +The docker image used by our CI is `huakunshen/kunkun-ext-builder:latest`. + +You can use the following command to build the extension with the same environment our CI uses. +This requires you to have docker installed, and the shell you are using has access to it via `docker` command. + +```bash +npx kksh@latest build # Build the extension with +``` + +`pnpm` is used to install dependencies and build the extension. + +The docker image environment also has `node`, `pnpm`, `npm`, `bun`, `deno` installed. +If your build failed, try debug with `huakunshen/kunkun-ext-builder:latest` image in interative mode and bind your extension volume to `/workspace`. + +After build successfully, you should find a tarball file ends with `.tgz` in the root of your extension. +The tarball is packaged with `npm pack` command. You can uncompress it to see if it contains all the necessary files. + +This tarball is the final product that will be published and installed in Kunkun. You can further verify your extension by installing this tarball directly in Kunkun. + +After verifying the tarball, it's ready to be published. + +Fork [KunkunExtensions](https://github.com/kunkunsh/KunkunExtensions) repo, add your extension to the `extensions` directory, and create a PR. + +Once CI passed and PR merged, you can use your extension in Kunkun. + +## Potential Error + +Our CI uses `pnpm` to install dependencies. If you are on Windows, you may get error during build. + +See issue https://github.com/kunkunsh/kunkun/issues/78 + +`bun` had problem building the extension when `pnpm` is used to install dependencies. + +### Options + +1. Install an older version of `bun` (1.1.27 should work) +2. Install dependencies with `bun` or `npm` instead of `pnpm` + +Our CI always builds the extension with on Linux and shouldn't have this problem. diff --git a/build.ts b/build.ts new file mode 100644 index 0000000..a99deb5 --- /dev/null +++ b/build.ts @@ -0,0 +1,30 @@ +import { watch } from "fs" +import { join } from "path" +import { refreshTemplateWorkerExtension } from "@kksh/api/dev" +import { $ } from "bun" + +const entrypoints = ["./src/index.ts"] + +async function build() { + try { + for (const entrypoint of entrypoints) { + await $`bun build --minify --target=browser --outdir=./dist ${entrypoint}` + } + if (Bun.argv.includes("dev")) { + await refreshTemplateWorkerExtension() + } + } catch (error) { + console.error(error) + } +} + +const srcDir = join(import.meta.dir, "src") + +await build() + +if (Bun.argv.includes("dev")) { + console.log(`Watching ${srcDir} for changes...`) + watch(srcDir, { recursive: true }, async (event, filename) => { + await build() + }) +} diff --git a/deno-src/deno.json b/deno-src/deno.json new file mode 100644 index 0000000..ead61d0 --- /dev/null +++ b/deno-src/deno.json @@ -0,0 +1,9 @@ +{ + "tasks": { + "dev": "deno run --watch main.ts" + }, + "imports": { + "@kunkun/api": "jsr:@kunkun/api@^0.0.52", + "@std/assert": "jsr:@std/assert@1" + } +} diff --git a/deno-src/deno.lock b/deno-src/deno.lock new file mode 100644 index 0000000..76abd28 --- /dev/null +++ b/deno-src/deno.lock @@ -0,0 +1,964 @@ +{ + "version": "4", + "specifiers": { + "jsr:@db/sqlite@0.11": "0.11.1", + "jsr:@db/sqlite@0.12.0": "0.12.0", + "jsr:@denosaurs/plug@1": "1.0.6", + "jsr:@kunkun/api@^0.0.40": "0.0.40", + "jsr:@std/assert@0.217": "0.217.0", + "jsr:@std/assert@0.221": "0.221.0", + "jsr:@std/assert@1": "1.0.8", + "jsr:@std/encoding@0.221": "0.221.0", + "jsr:@std/fmt@0.221": "0.221.0", + "jsr:@std/fs@0.221": "0.221.0", + "jsr:@std/internal@^1.0.5": "1.0.5", + "jsr:@std/path@0.217": "0.217.0", + "jsr:@std/path@0.221": "0.221.0", + "jsr:@valibot/valibot@0.42.1": "0.42.1", + "npm:@kksh/api@^0.0.40": "0.0.40", + "npm:@tauri-apps/api@^2.1.1": "2.1.1", + "npm:@tauri-apps/plugin-fs@^2.0.2": "2.0.2", + "npm:@tauri-apps/plugin-os@2": "2.0.0", + "npm:kkrpc@^0.0.12": "0.0.12_typescript@5.6.3", + "npm:lodash@^4.17.21": "4.17.21", + "npm:minimatch@^10.0.1": "10.0.1", + "npm:semver@^7.6.3": "7.6.3", + "npm:svelte-sonner@~0.3.28": "0.3.28_svelte@5.2.7__acorn@8.14.0", + "npm:tauri-api-adapter@~0.3.12": "0.3.13_typescript@5.6.3", + "npm:tauri-plugin-shellx-api@^2.0.14": "2.0.14", + "npm:valibot@0.40": "0.40.0_typescript@5.6.3" + }, + "jsr": { + "@db/sqlite@0.11.1": { + "integrity": "546434e7ed762db07e6ade0f963540dd5e06723b802937bf260ff855b21ef9c5", + "dependencies": [ + "jsr:@denosaurs/plug", + "jsr:@std/path@0.217" + ] + }, + "@db/sqlite@0.12.0": { + "integrity": "dd1ef7f621ad50fc1e073a1c3609c4470bd51edc0994139c5bf9851de7a6d85f", + "dependencies": [ + "jsr:@denosaurs/plug", + "jsr:@std/path@0.217" + ] + }, + "@denosaurs/plug@1.0.6": { + "integrity": "6cf5b9daba7799837b9ffbe89f3450510f588fafef8115ddab1ff0be9cb7c1a7", + "dependencies": [ + "jsr:@std/encoding", + "jsr:@std/fmt", + "jsr:@std/fs", + "jsr:@std/path@0.221" + ] + }, + "@kunkun/api@0.0.40": { + "integrity": "eab67c01e1cc87f3e5e7f7613a302cba7fccb18a1745f1a5508cf48df1e3649e", + "dependencies": [ + "npm:@kksh/api", + "npm:@tauri-apps/api", + "npm:@tauri-apps/plugin-fs", + "npm:@tauri-apps/plugin-os", + "npm:kkrpc", + "npm:lodash", + "npm:minimatch", + "npm:semver", + "npm:svelte-sonner", + "npm:tauri-api-adapter", + "npm:tauri-plugin-shellx-api", + "npm:valibot" + ] + }, + "@std/assert@0.217.0": { + "integrity": "c98e279362ca6982d5285c3b89517b757c1e3477ee9f14eb2fdf80a45aaa9642" + }, + "@std/assert@0.221.0": { + "integrity": "a5f1aa6e7909dbea271754fd4ab3f4e687aeff4873b4cef9a320af813adb489a" + }, + "@std/assert@1.0.8": { + "integrity": "ebe0bd7eb488ee39686f77003992f389a06c3da1bbd8022184804852b2fa641b", + "dependencies": [ + "jsr:@std/internal" + ] + }, + "@std/encoding@0.221.0": { + "integrity": "d1dd76ef0dc5d14088411e6dc1dede53bf8308c95d1537df1214c97137208e45" + }, + "@std/fmt@0.221.0": { + "integrity": "379fed69bdd9731110f26b9085aeb740606b20428ce6af31ef6bd45ef8efa62a" + }, + "@std/fs@0.221.0": { + "integrity": "028044450299de8ed5a716ade4e6d524399f035513b85913794f4e81f07da286", + "dependencies": [ + "jsr:@std/assert@0.221", + "jsr:@std/path@0.221" + ] + }, + "@std/internal@1.0.5": { + "integrity": "54a546004f769c1ac9e025abd15a76b6671ddc9687e2313b67376125650dc7ba" + }, + "@std/path@0.217.0": { + "integrity": "1217cc25534bca9a2f672d7fe7c6f356e4027df400c0e85c0ef3e4343bc67d11", + "dependencies": [ + "jsr:@std/assert@0.217" + ] + }, + "@std/path@0.221.0": { + "integrity": "0a36f6b17314ef653a3a1649740cc8db51b25a133ecfe838f20b79a56ebe0095", + "dependencies": [ + "jsr:@std/assert@0.221" + ] + }, + "@valibot/valibot@0.42.1": { + "integrity": "ba0f6f7964aaeec0e4b1f793d575061f325ae6254cbb9d7ff01fb65068a0a23b" + } + }, + "npm": { + "@ampproject/remapping@2.3.0": { + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dependencies": [ + "@jridgewell/gen-mapping", + "@jridgewell/trace-mapping" + ] + }, + "@isaacs/cliui@8.0.2": { + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dependencies": [ + "string-width@5.1.2", + "string-width-cjs@npm:string-width@4.2.3", + "strip-ansi@7.1.0", + "strip-ansi-cjs@npm:strip-ansi@6.0.1", + "wrap-ansi@8.1.0", + "wrap-ansi-cjs@npm:wrap-ansi@7.0.0" + ] + }, + "@jridgewell/gen-mapping@0.3.5": { + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "dependencies": [ + "@jridgewell/set-array", + "@jridgewell/sourcemap-codec", + "@jridgewell/trace-mapping" + ] + }, + "@jridgewell/resolve-uri@3.1.2": { + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==" + }, + "@jridgewell/set-array@1.2.1": { + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==" + }, + "@jridgewell/sourcemap-codec@1.5.0": { + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==" + }, + "@jridgewell/trace-mapping@0.3.25": { + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dependencies": [ + "@jridgewell/resolve-uri", + "@jridgewell/sourcemap-codec" + ] + }, + "@kksh/api@0.0.40": { + "integrity": "sha512-g7n/vwGWs+5OMdxHUPOFKaX7vPqzTfXkRPTR33G+fWv6mdaypaS6fOAMnGmcoEgLPzL4RPO1WaL3+ypSCV620A==", + "dependencies": [ + "@tauri-apps/api@2.1.1", + "@tauri-apps/cli", + "@tauri-apps/plugin-deep-link", + "@tauri-apps/plugin-dialog", + "@tauri-apps/plugin-fs", + "@tauri-apps/plugin-global-shortcut", + "@tauri-apps/plugin-http", + "@tauri-apps/plugin-log", + "@tauri-apps/plugin-notification", + "@tauri-apps/plugin-os", + "@tauri-apps/plugin-process", + "@tauri-apps/plugin-shell", + "@tauri-apps/plugin-store", + "@tauri-apps/plugin-updater", + "@tauri-apps/plugin-upload", + "kkrpc@0.0.10_typescript@5.6.3", + "lodash", + "minimatch@10.0.1", + "semver", + "svelte-sonner", + "tauri-api-adapter", + "tauri-plugin-network-api@2.0.4", + "tauri-plugin-shellx-api", + "tauri-plugin-system-info-api@2.0.8", + "valibot" + ] + }, + "@nodelib/fs.scandir@2.1.5": { + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dependencies": [ + "@nodelib/fs.stat", + "run-parallel" + ] + }, + "@nodelib/fs.stat@2.0.5": { + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==" + }, + "@nodelib/fs.walk@1.2.8": { + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dependencies": [ + "@nodelib/fs.scandir", + "fastq" + ] + }, + "@tauri-apps/api@2.0.1": { + "integrity": "sha512-eoQWT+Tq1qSwQpHV+nw1eNYe5B/nm1PoRjQCRiEOS12I1b+X4PUcREfXVX8dPcBT6GrzWGDtaecY0+1p0Rfqlw==" + }, + "@tauri-apps/api@2.1.1": { + "integrity": "sha512-fzUfFFKo4lknXGJq8qrCidkUcKcH2UHhfaaCNt4GzgzGaW2iS26uFOg4tS3H4P8D6ZEeUxtiD5z0nwFF0UN30A==" + }, + "@tauri-apps/cli-darwin-arm64@2.1.0": { + "integrity": "sha512-ESc6J6CE8hl1yKH2vJ+ALF+thq4Be+DM1mvmTyUCQObvezNCNhzfS6abIUd3ou4x5RGH51ouiANeT3wekU6dCw==" + }, + "@tauri-apps/cli-darwin-x64@2.1.0": { + "integrity": "sha512-TasHS442DFs8cSH2eUQzuDBXUST4ECjCd0yyP+zZzvAruiB0Bg+c8A+I/EnqCvBQ2G2yvWLYG8q/LI7c87A5UA==" + }, + "@tauri-apps/cli-linux-arm-gnueabihf@2.1.0": { + "integrity": "sha512-aP7ZBGNL4ny07Cbb6kKpUOSrmhcIK2KhjviTzYlh+pPhAptxnC78xQGD3zKQkTi2WliJLPmBYbOHWWQa57lQ9w==" + }, + "@tauri-apps/cli-linux-arm64-gnu@2.1.0": { + "integrity": "sha512-ZTdgD5gLeMCzndMT2f358EkoYkZ5T+Qy6zPzU+l5vv5M7dHVN9ZmblNAYYXmoOuw7y+BY4X/rZvHV9pcGrcanQ==" + }, + "@tauri-apps/cli-linux-arm64-musl@2.1.0": { + "integrity": "sha512-NzwqjUCilhnhJzusz3d/0i0F1GFrwCQbkwR6yAHUxItESbsGYkZRJk0yMEWkg3PzFnyK4cWTlQJMEU52TjhEzA==" + }, + "@tauri-apps/cli-linux-x64-gnu@2.1.0": { + "integrity": "sha512-TyiIpMEtZxNOQmuFyfJwaaYbg3movSthpBJLIdPlKxSAB2BW0VWLY3/ZfIxm/G2YGHyREkjJvimzYE0i37PnMA==" + }, + "@tauri-apps/cli-linux-x64-musl@2.1.0": { + "integrity": "sha512-/dQd0TlaxBdJACrR72DhynWftzHDaX32eBtS5WBrNJ+nnNb+znM3gON6nJ9tSE9jgDa6n1v2BkI/oIDtypfUXw==" + }, + "@tauri-apps/cli-win32-arm64-msvc@2.1.0": { + "integrity": "sha512-NdQJO7SmdYqOcE+JPU7bwg7+odfZMWO6g8xF9SXYCMdUzvM2Gv/AQfikNXz5yS7ralRhNFuW32i5dcHlxh4pDg==" + }, + "@tauri-apps/cli-win32-ia32-msvc@2.1.0": { + "integrity": "sha512-f5h8gKT/cB8s1ticFRUpNmHqkmaLutT62oFDB7N//2YTXnxst7EpMIn1w+QimxTvTk2gcx6EcW6bEk/y2hZGzg==" + }, + "@tauri-apps/cli-win32-x64-msvc@2.1.0": { + "integrity": "sha512-P/+LrdSSb5Xbho1LRP4haBjFHdyPdjWvGgeopL96OVtrFpYnfC+RctB45z2V2XxqFk3HweDDxk266btjttfjGw==" + }, + "@tauri-apps/cli@2.1.0": { + "integrity": "sha512-K2VhcKqBhAeS5pNOVdnR/xQRU6jwpgmkSL2ejHXcl0m+kaTggT0WRDQnFtPq6NljA7aE03cvwsbCAoFG7vtkJw==", + "dependencies": [ + "@tauri-apps/cli-darwin-arm64", + "@tauri-apps/cli-darwin-x64", + "@tauri-apps/cli-linux-arm-gnueabihf", + "@tauri-apps/cli-linux-arm64-gnu", + "@tauri-apps/cli-linux-arm64-musl", + "@tauri-apps/cli-linux-x64-gnu", + "@tauri-apps/cli-linux-x64-musl", + "@tauri-apps/cli-win32-arm64-msvc", + "@tauri-apps/cli-win32-ia32-msvc", + "@tauri-apps/cli-win32-x64-msvc" + ] + }, + "@tauri-apps/plugin-deep-link@2.0.0": { + "integrity": "sha512-cDa2k1OrRU5DoKc0IXl1Y8RlFOU107u2phdZfT7FkApsC6TL/VAPs3YOUTT8p9/PZ50EjOKP104HFMqVqnQ0bw==", + "dependencies": [ + "@tauri-apps/api@2.1.1" + ] + }, + "@tauri-apps/plugin-dialog@2.0.1": { + "integrity": "sha512-fnUrNr6EfvTqdls/ufusU7h6UbNFzLKvHk/zTuOiBq01R3dTODqwctZlzakdbfSp/7pNwTKvgKTAgl/NAP/Z0Q==", + "dependencies": [ + "@tauri-apps/api@2.1.1" + ] + }, + "@tauri-apps/plugin-fs@2.0.2": { + "integrity": "sha512-4YZaX2j7ta81M5/DL8aN10kTnpUkEpkPo1FTYPT8Dd0ImHe3azM8i8MrtjrDGoyBYLPO3zFv7df/mSCYF8oA0Q==", + "dependencies": [ + "@tauri-apps/api@2.1.1" + ] + }, + "@tauri-apps/plugin-global-shortcut@2.0.0": { + "integrity": "sha512-pnB4CUwFVjg4twtBSxoLJ4uLFTYxsvOdC1zIbG581pYzhYatOl6mjB+ijD5SSXgiS/jNoqMcfkOF9PWAisurew==", + "dependencies": [ + "@tauri-apps/api@2.1.1" + ] + }, + "@tauri-apps/plugin-http@2.0.1": { + "integrity": "sha512-j6IA3pVBybSCwPpsihpX4z8bs6PluuGtr06ahL/xy4D8HunNBTmRmadJrFOQi0gOAbaig4MkQ15nzNLBLy8R1A==", + "dependencies": [ + "@tauri-apps/api@2.1.1" + ] + }, + "@tauri-apps/plugin-log@2.0.0": { + "integrity": "sha512-C+NII9vzswqnOQE8k7oRtnaw0z5TZsMmnirRhXkCKDEhQQH9841Us/PC1WHtGiAaJ8za1A1JB2xXndEq/47X/w==", + "dependencies": [ + "@tauri-apps/api@2.1.1" + ] + }, + "@tauri-apps/plugin-notification@2.0.0": { + "integrity": "sha512-6qEDYJS7mgXZWLXA0EFL+DVCJh8sJlzSoyw6B50pxhLPVFjc5Vr5DVzl5W3mUHaYhod5wsC984eQnlCCGqxYDA==", + "dependencies": [ + "@tauri-apps/api@2.1.1" + ] + }, + "@tauri-apps/plugin-os@2.0.0": { + "integrity": "sha512-M7hG/nNyQYTJxVG/UhTKhp9mpXriwWzrs9mqDreB8mIgqA3ek5nHLdwRZJWhkKjZrnDT4v9CpA9BhYeplTlAiA==", + "dependencies": [ + "@tauri-apps/api@2.1.1" + ] + }, + "@tauri-apps/plugin-process@2.0.0": { + "integrity": "sha512-OYzi0GnkrF4NAnsHZU7U3tjSoP0PbeAlO7T1Z+vJoBUH9sFQ1NSLqWYWQyf8hcb3gVWe7P1JggjiskO+LST1ug==", + "dependencies": [ + "@tauri-apps/api@2.1.1" + ] + }, + "@tauri-apps/plugin-shell@2.0.1": { + "integrity": "sha512-akU1b77sw3qHiynrK0s930y8zKmcdrSD60htjH+mFZqv5WaakZA/XxHR3/sF1nNv9Mgmt/Shls37HwnOr00aSw==", + "dependencies": [ + "@tauri-apps/api@2.1.1" + ] + }, + "@tauri-apps/plugin-store@2.1.0": { + "integrity": "sha512-GADqrc17opUKYIAKnGHIUgEeTZ2wJGu1ZITKQ1WMuOFdv8fvXRFBAqsqPjE3opgWohbczX6e1NpwmZK1AnuWVw==", + "dependencies": [ + "@tauri-apps/api@2.1.1" + ] + }, + "@tauri-apps/plugin-updater@2.0.0": { + "integrity": "sha512-N0cl71g7RPr7zK2Fe5aoIwzw14NcdLcz7XMGFWZVjprsqgDRWoxbnUkknyCQMZthjhGkppCd/wN2MIsUz+eAhQ==", + "dependencies": [ + "@tauri-apps/api@2.1.1" + ] + }, + "@tauri-apps/plugin-upload@2.1.0": { + "integrity": "sha512-nSIyxp2sAHsj+1RMs3obTP2lC1rzeWcMxHtzLYjnXLnJMXvWlFG0T7jEZ9Sg/OErvQxPVIvDW+12evuFKa5t8Q==", + "dependencies": [ + "@tauri-apps/api@2.1.1" + ] + }, + "@types/estree@1.0.6": { + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==" + }, + "acorn-typescript@1.4.13_acorn@8.14.0": { + "integrity": "sha512-xsc9Xv0xlVfwp2o7sQ+GCQ1PgbkdcpWdTzrwXxO3xDMTAywVS3oXVOcOHuRjAPkS4P9b+yc/qNF15460v+jp4Q==", + "dependencies": [ + "acorn" + ] + }, + "acorn@8.14.0": { + "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==" + }, + "ansi-regex@5.0.1": { + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" + }, + "ansi-regex@6.1.0": { + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==" + }, + "ansi-styles@4.3.0": { + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": [ + "color-convert" + ] + }, + "ansi-styles@6.2.1": { + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==" + }, + "anymatch@3.1.3": { + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dependencies": [ + "normalize-path", + "picomatch" + ] + }, + "aria-query@5.3.2": { + "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==" + }, + "array-union@2.1.0": { + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==" + }, + "axobject-query@4.1.0": { + "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==" + }, + "balanced-match@1.0.2": { + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "binary-extensions@2.3.0": { + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==" + }, + "brace-expansion@1.1.11": { + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dependencies": [ + "balanced-match", + "concat-map" + ] + }, + "brace-expansion@2.0.1": { + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dependencies": [ + "balanced-match" + ] + }, + "braces@3.0.3": { + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dependencies": [ + "fill-range" + ] + }, + "chokidar@3.6.0": { + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dependencies": [ + "anymatch", + "braces", + "fsevents", + "glob-parent", + "is-binary-path", + "is-glob", + "normalize-path", + "readdirp" + ] + }, + "color-convert@2.0.1": { + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": [ + "color-name" + ] + }, + "color-name@1.1.4": { + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "commander@9.5.0": { + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==" + }, + "concat-map@0.0.1": { + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + }, + "cross-spawn@7.0.6": { + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dependencies": [ + "path-key", + "shebang-command", + "which" + ] + }, + "dir-glob@3.0.1": { + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dependencies": [ + "path-type" + ] + }, + "eastasianwidth@0.2.0": { + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" + }, + "emoji-regex@8.0.0": { + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "emoji-regex@9.2.2": { + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" + }, + "esm-env@1.1.4": { + "integrity": "sha512-oO82nKPHKkzIj/hbtuDYy/JHqBHFlMIW36SDiPCVsj87ntDLcWN+sJ1erdVryd4NxODacFTsdrIE3b7IamqbOg==" + }, + "esrap@1.2.2": { + "integrity": "sha512-F2pSJklxx1BlQIQgooczXCPHmcWpn6EsP5oo73LQfonG9fIlIENQ8vMmfGXeojP9MrkzUNAfyU5vdFlR9shHAw==", + "dependencies": [ + "@jridgewell/sourcemap-codec", + "@types/estree" + ] + }, + "fast-glob@3.3.2": { + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "dependencies": [ + "@nodelib/fs.stat", + "@nodelib/fs.walk", + "glob-parent", + "merge2", + "micromatch" + ] + }, + "fastq@1.17.1": { + "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", + "dependencies": [ + "reusify" + ] + }, + "fill-range@7.1.1": { + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dependencies": [ + "to-regex-range" + ] + }, + "foreground-child@3.3.0": { + "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", + "dependencies": [ + "cross-spawn", + "signal-exit" + ] + }, + "fs.realpath@1.0.0": { + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + }, + "fsevents@2.3.3": { + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==" + }, + "function-bind@1.1.2": { + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==" + }, + "glob-parent@5.1.2": { + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dependencies": [ + "is-glob" + ] + }, + "glob@11.0.0": { + "integrity": "sha512-9UiX/Bl6J2yaBbxKoEBRm4Cipxgok8kQYcOPEhScPwebu2I0HoQOuYdIO6S3hLuWoZgpDpwQZMzTFxgpkyT76g==", + "dependencies": [ + "foreground-child", + "jackspeak", + "minimatch@10.0.1", + "minipass", + "package-json-from-dist", + "path-scurry" + ] + }, + "glob@7.2.3": { + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dependencies": [ + "fs.realpath", + "inflight", + "inherits", + "minimatch@3.1.2", + "once", + "path-is-absolute" + ] + }, + "globby@11.1.0": { + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dependencies": [ + "array-union", + "dir-glob", + "fast-glob", + "ignore", + "merge2", + "slash" + ] + }, + "hasown@2.0.2": { + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dependencies": [ + "function-bind" + ] + }, + "ignore@5.3.2": { + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==" + }, + "inflight@1.0.6": { + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dependencies": [ + "once", + "wrappy" + ] + }, + "inherits@2.0.4": { + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "interpret@1.4.0": { + "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==" + }, + "is-binary-path@2.1.0": { + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dependencies": [ + "binary-extensions" + ] + }, + "is-core-module@2.15.1": { + "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==", + "dependencies": [ + "hasown" + ] + }, + "is-extglob@2.1.1": { + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==" + }, + "is-fullwidth-code-point@3.0.0": { + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" + }, + "is-glob@4.0.3": { + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dependencies": [ + "is-extglob" + ] + }, + "is-number@7.0.0": { + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" + }, + "is-reference@3.0.3": { + "integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==", + "dependencies": [ + "@types/estree" + ] + }, + "isexe@2.0.0": { + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" + }, + "jackspeak@4.0.2": { + "integrity": "sha512-bZsjR/iRjl1Nk1UkjGpAzLNfQtzuijhn2g+pbZb98HQ1Gk8vM9hfbxeMBP+M2/UUdwj0RqGG3mlvk2MsAqwvEw==", + "dependencies": [ + "@isaacs/cliui" + ] + }, + "kkrpc@0.0.10_typescript@5.6.3": { + "integrity": "sha512-lkQKVnN9f6JrS4ybKbGkV4mtuGhWYLTnaWx60ysytEap+sP5jcTbAuJlSrY6JqlwaohiS0X3ZbvJ2rAXYRdTng==", + "dependencies": [ + "typescript", + "ws" + ] + }, + "kkrpc@0.0.12_typescript@5.6.3": { + "integrity": "sha512-PBk4AhGfkesIdAwmIoj7dHHIp7qN97XT4yr5Rl7h2WL79gxWQVgZRJYLt7Gb17GoLDh991rnL85mhCoPG5VC/Q==", + "dependencies": [ + "typescript", + "ws" + ] + }, + "locate-character@3.0.0": { + "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==" + }, + "lodash@4.17.21": { + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "lru-cache@11.0.2": { + "integrity": "sha512-123qHRfJBmo2jXDbo/a5YOQrJoHF/GNQTLzQ5+IdK5pWpceK17yRc6ozlWd25FxvGKQbIUs91fDFkXmDHTKcyA==" + }, + "magic-string@0.30.13": { + "integrity": "sha512-8rYBO+MsWkgjDSOvLomYnzhdwEG51olQ4zL5KXnNJWV5MNmrb4rTZdrtkhxjnD/QyZUqR/Z/XDsUs/4ej2nx0g==", + "dependencies": [ + "@jridgewell/sourcemap-codec" + ] + }, + "merge2@1.4.1": { + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==" + }, + "micromatch@4.0.8": { + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dependencies": [ + "braces", + "picomatch" + ] + }, + "minimatch@10.0.1": { + "integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==", + "dependencies": [ + "brace-expansion@2.0.1" + ] + }, + "minimatch@3.1.2": { + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dependencies": [ + "brace-expansion@1.1.11" + ] + }, + "minimist@1.2.8": { + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==" + }, + "minipass@7.1.2": { + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==" + }, + "mylas@2.1.13": { + "integrity": "sha512-+MrqnJRtxdF+xngFfUUkIMQrUUL0KsxbADUkn23Z/4ibGg192Q+z+CQyiYwvWTsYjJygmMR8+w3ZDa98Zh6ESg==" + }, + "normalize-path@3.0.0": { + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" + }, + "once@1.4.0": { + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dependencies": [ + "wrappy" + ] + }, + "package-json-from-dist@1.0.1": { + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==" + }, + "path-is-absolute@1.0.1": { + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==" + }, + "path-key@3.1.1": { + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==" + }, + "path-parse@1.0.7": { + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + }, + "path-scurry@2.0.0": { + "integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==", + "dependencies": [ + "lru-cache", + "minipass" + ] + }, + "path-type@4.0.0": { + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==" + }, + "picomatch@2.3.1": { + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==" + }, + "plimit-lit@1.6.1": { + "integrity": "sha512-B7+VDyb8Tl6oMJT9oSO2CW8XC/T4UcJGrwOVoNGwOQsQYhlpfajmrMj5xeejqaASq3V/EqThyOeATEOMuSEXiA==", + "dependencies": [ + "queue-lit" + ] + }, + "queue-lit@1.5.2": { + "integrity": "sha512-tLc36IOPeMAubu8BkW8YDBV+WyIgKlYU7zUNs0J5Vk9skSZ4JfGlPOqplP0aHdfv7HL0B2Pg6nwiq60Qc6M2Hw==" + }, + "queue-microtask@1.2.3": { + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==" + }, + "readdirp@3.6.0": { + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dependencies": [ + "picomatch" + ] + }, + "rechoir@0.6.2": { + "integrity": "sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==", + "dependencies": [ + "resolve" + ] + }, + "resolve@1.22.8": { + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "dependencies": [ + "is-core-module", + "path-parse", + "supports-preserve-symlinks-flag" + ] + }, + "reusify@1.0.4": { + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==" + }, + "rimraf@6.0.1": { + "integrity": "sha512-9dkvaxAsk/xNXSJzMgFqqMCuFgt2+KsOFek3TMLfo8NCPfWpBmqwyNn5Y+NX56QUYfCtsyhF3ayiboEoUmJk/A==", + "dependencies": [ + "glob@11.0.0", + "package-json-from-dist" + ] + }, + "run-parallel@1.2.0": { + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dependencies": [ + "queue-microtask" + ] + }, + "semver@7.6.3": { + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==" + }, + "shebang-command@2.0.0": { + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dependencies": [ + "shebang-regex" + ] + }, + "shebang-regex@3.0.0": { + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==" + }, + "shelljs@0.8.5": { + "integrity": "sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==", + "dependencies": [ + "glob@7.2.3", + "interpret", + "rechoir" + ] + }, + "shx@0.3.4": { + "integrity": "sha512-N6A9MLVqjxZYcVn8hLmtneQWIJtp8IKzMP4eMnx+nqkvXoqinUPCbUFLp2UcWTEIUONhlk0ewxr/jaVGlc+J+g==", + "dependencies": [ + "minimist", + "shelljs" + ] + }, + "signal-exit@4.1.0": { + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==" + }, + "slash@3.0.0": { + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==" + }, + "string-width@4.2.3": { + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": [ + "emoji-regex@8.0.0", + "is-fullwidth-code-point", + "strip-ansi@6.0.1" + ] + }, + "string-width@5.1.2": { + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dependencies": [ + "eastasianwidth", + "emoji-regex@9.2.2", + "strip-ansi@7.1.0" + ] + }, + "strip-ansi@6.0.1": { + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": [ + "ansi-regex@5.0.1" + ] + }, + "strip-ansi@7.1.0": { + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dependencies": [ + "ansi-regex@6.1.0" + ] + }, + "supports-preserve-symlinks-flag@1.0.0": { + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==" + }, + "svelte-sonner@0.3.28_svelte@5.2.7__acorn@8.14.0": { + "integrity": "sha512-K3AmlySeFifF/cKgsYNv5uXqMVNln0NBAacOYgmkQStLa/UoU0LhfAACU6Gr+YYC8bOCHdVmFNoKuDbMEsppJg==", + "dependencies": [ + "svelte" + ] + }, + "svelte@5.2.7_acorn@8.14.0": { + "integrity": "sha512-cEhPGuLHiH2+Z8B1FwQgiZJgA39uUmJR4516TKrM5zrp0/cuwJkfhUfcTxhAkznanAF5fXUKzvYR4o+Ksx3ZCQ==", + "dependencies": [ + "@ampproject/remapping", + "@jridgewell/sourcemap-codec", + "@types/estree", + "acorn", + "acorn-typescript", + "aria-query", + "axobject-query", + "esm-env", + "esrap", + "is-reference", + "locate-character", + "magic-string", + "zimmerframe" + ] + }, + "tauri-api-adapter@0.3.13_typescript@5.6.3": { + "integrity": "sha512-ex4z3Zish6by1ew1ialbLc5g3dhly+6ihepJeJwVRQoMJdUTyfCX2dVPYW74i+px1hFXKFu3MYTheS7uBOeCbg==", + "dependencies": [ + "@tauri-apps/api@2.1.1", + "@tauri-apps/plugin-dialog", + "@tauri-apps/plugin-fs", + "@tauri-apps/plugin-http", + "@tauri-apps/plugin-log", + "@tauri-apps/plugin-notification", + "@tauri-apps/plugin-os", + "@tauri-apps/plugin-shell", + "@tauri-apps/plugin-upload", + "kkrpc@0.0.12_typescript@5.6.3", + "rimraf", + "shx", + "tauri-plugin-clipboard-api", + "tauri-plugin-network-api@2.0.4_typescript@5.6.3", + "tauri-plugin-shellx-api", + "tauri-plugin-system-info-api@2.0.8_typescript@5.6.3", + "tsc-alias", + "typescript", + "valibot" + ] + }, + "tauri-plugin-clipboard-api@2.1.11_typescript@5.6.3": { + "integrity": "sha512-VNkGaVPPfRoHg7/rJBcWqsvLvn4/kNEOOlzqwyI9Qdf6g54B3mc31GLZdnq/HWtX0vZskw3J8b/EF9YkASDCBQ==", + "dependencies": [ + "@tauri-apps/api@2.0.1", + "valibot" + ] + }, + "tauri-plugin-network-api@2.0.4": { + "integrity": "sha512-CJWF2g+uQifcIlE/AXUnezVjjbyY0FDBxoz4P6BmjNRR/qubpNMfdUnKLqdjX98o5MIXGW+UnyZTfbJo998dFw==", + "dependencies": [ + "@tauri-apps/api@2.1.1", + "valibot" + ] + }, + "tauri-plugin-network-api@2.0.4_typescript@5.6.3": { + "integrity": "sha512-CJWF2g+uQifcIlE/AXUnezVjjbyY0FDBxoz4P6BmjNRR/qubpNMfdUnKLqdjX98o5MIXGW+UnyZTfbJo998dFw==", + "dependencies": [ + "@tauri-apps/api@2.1.1", + "valibot" + ] + }, + "tauri-plugin-shellx-api@2.0.14": { + "integrity": "sha512-MdSYD2KDw63b7yEIa9Q2GXnbidL5Tk+s92BJX0XvYfHrv2l1fYE2vdRWGnyhvCWmUavyCeiOle5uMxM6QLOb2Q==", + "dependencies": [ + "@tauri-apps/api@2.1.1" + ] + }, + "tauri-plugin-system-info-api@2.0.8": { + "integrity": "sha512-EFdLXNGp6Zu9SNsZCkU+55A8027OnrVw/TQrd0oJHgfZzs4qvm1iMmSvyid4MLftt33iZDhjCzxYijaaOxeKSg==", + "dependencies": [ + "@tauri-apps/api@2.1.1", + "valibot" + ] + }, + "tauri-plugin-system-info-api@2.0.8_typescript@5.6.3": { + "integrity": "sha512-EFdLXNGp6Zu9SNsZCkU+55A8027OnrVw/TQrd0oJHgfZzs4qvm1iMmSvyid4MLftt33iZDhjCzxYijaaOxeKSg==", + "dependencies": [ + "@tauri-apps/api@2.1.1", + "valibot" + ] + }, + "to-regex-range@5.0.1": { + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dependencies": [ + "is-number" + ] + }, + "tsc-alias@1.8.10": { + "integrity": "sha512-Ibv4KAWfFkFdKJxnWfVtdOmB0Zi1RJVxcbPGiCDsFpCQSsmpWyuzHG3rQyI5YkobWwxFPEyQfu1hdo4qLG2zPw==", + "dependencies": [ + "chokidar", + "commander", + "globby", + "mylas", + "normalize-path", + "plimit-lit" + ] + }, + "typescript@5.6.3": { + "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==" + }, + "valibot@0.40.0_typescript@5.6.3": { + "integrity": "sha512-XHKnaVtwHqxPwnGOsLrwka9CEaL7yNeLNp707OKv/bmT29GnPVdl6PxBOZ6BW7hF66/6QT6iVbOlnW7qVPmoKw==", + "dependencies": [ + "typescript" + ] + }, + "which@2.0.2": { + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dependencies": [ + "isexe" + ] + }, + "wrap-ansi@7.0.0": { + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dependencies": [ + "ansi-styles@4.3.0", + "string-width@4.2.3", + "strip-ansi@6.0.1" + ] + }, + "wrap-ansi@8.1.0": { + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dependencies": [ + "ansi-styles@6.2.1", + "string-width@5.1.2", + "strip-ansi@7.1.0" + ] + }, + "wrappy@1.0.2": { + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "ws@8.18.0": { + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==" + }, + "zimmerframe@1.1.2": { + "integrity": "sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w==" + } + }, + "workspace": { + "dependencies": [ + "jsr:@kunkun/api@^0.0.40", + "jsr:@std/assert@1" + ] + } +} diff --git a/deno-src/dev.ts b/deno-src/dev.ts new file mode 100644 index 0000000..8589c19 --- /dev/null +++ b/deno-src/dev.ts @@ -0,0 +1,7 @@ +import { readFirefoxBookmarks } from "./lib.ts" + +const dbPath = + "/Users/hacker/Library/Application Support/Firefox/Profiles/czahqvgw.default-release/places.sqlite" + +const data = readFirefoxBookmarks(dbPath) +console.log(data) diff --git a/deno-src/index.ts b/deno-src/index.ts new file mode 100644 index 0000000..3346f9b --- /dev/null +++ b/deno-src/index.ts @@ -0,0 +1,13 @@ +import { expose } from "@kunkun/api/runtime/deno" +import { readFirefoxBookmarks } from "./lib.ts" + +expose({ + readFirefoxBookmarks +}) +// console.log( +// readFirefoxBookmarks( +// "/Users/hacker/Library/Application Support/Firefox/Profiles/czahqvgw.default-release/places.sqlite" +// ) +// ) + +// deno run --allow-env=DENO_SQLITE_PATH,DENO_SQLITE_LOCAL,DENO_DIR,HOME --allow-read=/Users/hacker/Library/Caches/deno/plug --allow-ffi index.ts diff --git a/deno-src/lib.ts b/deno-src/lib.ts new file mode 100644 index 0000000..ee517db --- /dev/null +++ b/deno-src/lib.ts @@ -0,0 +1,21 @@ +import { Database } from "jsr:@db/sqlite@0.12.0" + +export function readFirefoxBookmarks(dbPath: string) { + const db = new Database(dbPath, { readonly: true }) + const stmt = db.prepare(` + SELECT mb.title as name, mp.url as url, mp.title as title, mp.description as description, mp.preview_image_url as previewImageUrl + FROM moz_bookmarks mb + JOIN moz_places mp ON mb.fk = mp.id + `) + + const data = stmt.all<{ + name: string + url: string + title: string | null + description: string | null + previewImageUrl: string | null + }>() + + db.close() + return data +} diff --git a/jsr.json b/jsr.json new file mode 100644 index 0000000..3369cdc --- /dev/null +++ b/jsr.json @@ -0,0 +1,13 @@ +{ + "name": "@kunkun/kunkun-ext-ip-info", + "version": "0.0.3", + "license": "MIT", + "exports": "./mod.ts", + "publish": { + "include": ["dist", "deno-src", "README.md", "package.json", "mod.ts"] + }, + "imports": { + "@kunkun/api": "jsr:@kunkun/api@^0.0.52", + "@std/assert": "jsr:@std/assert@1" + } +} diff --git a/mod.ts b/mod.ts new file mode 100644 index 0000000..e69de29 diff --git a/package.json b/package.json new file mode 100644 index 0000000..b6a401b --- /dev/null +++ b/package.json @@ -0,0 +1,116 @@ +{ + "$schema": "https://schema.kunkun.sh", + "license": "MIT", + "name": "kunkun-ext-browser-bookmark", + "version": "0.0.3", + "repository": "https://github.com/kunkunsh/kunkun-ext-browser-bookmark", + "type": "module", + "kunkun": { + "name": "Browser Bookmark", + "shortDescription": "List Browser Bookmarks", + "longDescription": "Show a list of Browser Bookmarks", + "identifier": "browser-bookmark", + "permissions": [ + "shell:stdin-write", + "shell:kill", + { + "permission": "fs:read", + "allow": [ + { + "path": "$HOME/Library/Application Support/Google/Chrome/Default/Bookmarks" + }, + { + "path": "$HOME/Library/Application Support/Microsoft Edge/Default/Bookmarks" + } + ] + }, + { + "permission": "fs:exists", + "allow": [ + { + "path": "$HOME/Library/Application Support/Firefox/Profiles" + }, + { + "path": "$HOME/Library/Application Support/Firefox/Profiles/**" + } + ] + }, + { + "permission": "fs:read-dir", + "allow": [ + { + "path": "$HOME/Library/Application Support/Firefox/Profiles" + }, + { + "path": "$HOME/Library/Application Support/Firefox/Profiles/**" + } + ] + }, + { + "permission": "open:url", + "allow": [ + { + "url": "http://**" + }, + { + "url": "https://**" + } + ] + }, + { + "permission": "shell:deno:spawn", + "allow": [ + { + "path": "$EXTENSION/deno-src/index.ts", + "env": [ + "DENO_SQLITE_PATH", + "DENO_SQLITE_LOCAL", + "DENO_DIR", + "HOME", + "DENO_DENO" + ], + "read": "*", + "ffi": "*" + } + ] + } + ], + "demoImages": [], + "icon": { + "type": "iconify", + "value": "material-symbols:bookmark" + }, + "customUiCmds": [], + "templateUiCmds": [ + { + "name": "Browser Bookmarks", + "main": "dist/index.js", + "cmds": [] + } + ] + }, + "scripts": { + "dev": "bun build.ts dev", + "build": "bun build.ts" + }, + "dependencies": { + "@kksh/api": "^0.0.48", + "i18next": "^23.15.1", + "valibot": "^0.40.0" + }, + "devDependencies": { + "@types/bun": "latest", + "json-to-valibot": "^0.1.3" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "files": [ + "dist", + "deno-src", + "README.md", + "package.json", + "mod.ts" + ], + "packageManager": "pnpm@9.14.2" +} diff --git a/src/bookmark.ts b/src/bookmark.ts new file mode 100644 index 0000000..edfc930 --- /dev/null +++ b/src/bookmark.ts @@ -0,0 +1,263 @@ +import { fs, os, path, shell, toast } from "@kksh/api/ui/worker" +import * as v from "valibot" +import { ChromeBookmarksFile } from "./types" +import type { Browser, ChromeBookmark, Platform } from "./types" + +// given a url, compute it's favicon +function getFavicon(url: string): string | undefined { + if (!url.startsWith("http")) { + return undefined + } + try { + const urlObj = new URL(url) + const faviconUrl = `${urlObj.protocol}//${urlObj.hostname}/favicon.ico` + return faviconUrl + } catch (error) { + console.error("Failed to parse URL:", error) + return undefined + } +} + +export const FirefoxBookmark = v.object({ + name: v.string(), + url: v.string(), + title: v.nullable(v.string()), + description: v.nullable(v.string()), + previewImageUrl: v.nullable(v.string()) +}) + +export type FirefoxBookmark = v.InferOutput +export const FirefoxBookmarkList = v.array(FirefoxBookmark) +export type FirefoxBookmarkList = v.InferOutput + +export type Bookmark = { + name: string + subtitle?: string + url: string + visitCount?: number + favicon?: string +} + +export abstract class IBrowserBookmarks { + protected bookmarkPath: string + + constructor(bookmarkPath: string) { + this.bookmarkPath = bookmarkPath + } + abstract load(): Promise + + getBookmarkPath(): string { + return this.bookmarkPath + } +} + +async function flattenChromeBookmarkTree(bookmarkTree: ChromeBookmark): Promise { + const bookmarks: Bookmark[] = [] + + if (bookmarkTree.type === "folder") { + for (const child of bookmarkTree.children || []) { + bookmarks.push(...(await flattenChromeBookmarkTree(child))) + } + } else if (bookmarkTree.type === "url" && bookmarkTree.url) { + bookmarks.push({ + name: bookmarkTree.name, + subtitle: bookmarkTree.name.trim().length === 0 ? bookmarkTree.url : undefined, + url: bookmarkTree.url, + visitCount: bookmarkTree.visit_count ?? 0, + favicon: await getFavicon(bookmarkTree.url) + }) + } + return bookmarks +} + +export class ChromeBookmarks extends IBrowserBookmarks { + async load(): Promise { + return fs + .readTextFile(this.bookmarkPath) + .then(async (content) => { + try { + const jsonParsed = JSON.parse(content) + const parseResult = v.safeParse(ChromeBookmarksFile, jsonParsed) + if (!parseResult.success) { + throw new Error(`Failed to parse bookmark file: ${v.flatten(parseResult.issues)}`) + } + return [ + ...(await flattenChromeBookmarkTree(parseResult.output.roots.bookmark_bar)), + ...(await flattenChromeBookmarkTree(parseResult.output.roots.other)), + ...(await flattenChromeBookmarkTree(parseResult.output.roots.synced)) + ] + .flat() + .filter((b) => b.url || b.name) + .sort((a, b) => (b.visitCount ?? 0) - (a.visitCount ?? 0)) + } catch (error) { + toast.error(`Failed to parse bookmark file`, { description: this.bookmarkPath }) + throw new Error(`Failed to parse bookmark file: ${error}`) + } + }) + .catch((err) => { + toast.error(`Failed to read bookmarks`, { description: err.message }) + return [] + }) + } +} + +export class FirefoxBookmarks extends IBrowserBookmarks { + async load(): Promise { + const allowRead = await path.join(await path.homeDir(), "Library/Caches/deno/plug") + const { rpcChannel, process, command } = await shell.createDenoRpcChannel< + {}, + { + readFirefoxBookmarks: (dbPath: string) => FirefoxBookmarkList + } + >( + "$EXTENSION/deno-src/index.ts", + [], + { + allowRead: [allowRead], + allowEnv: ["DENO_SQLITE_PATH", "DENO_SQLITE_LOCAL", "DENO_DIR", "HOME"], + allowAllFfi: true + // allowNet: ["http://**", "https://**"] + }, + {} + ) + command.stderr.on("data", (data) => { + console.error(data.toString()) + }) + const api = rpcChannel.getAPI() + let bookmarks: Bookmark[] = [] + try { + const rawFFBookmarks = await api.readFirefoxBookmarks(this.bookmarkPath) + const parseResult = v.safeParse(FirefoxBookmarkList, rawFFBookmarks) + if (!parseResult.success) { + toast.error(`Failed to parse firefox bookmarks`) + return [] + } + bookmarks = parseResult.output.map((b) => ({ + name: b.name, + subtitle: b.title ?? undefined, + url: b.url, + favicon: getFavicon(b.url) + })) + } catch (error) { + } finally { + process.kill() + } + return bookmarks + } +} + +async function getChromeBookmarksPath(): Promise { + const platform: Platform = await os.platform() + let bookmarkPath = null + if (platform === "macos") { + bookmarkPath = await path.join( + await path.homeDir(), + "Library/Application Support/Google/Chrome/Default/Bookmarks" + ) + } else if (platform === "linux") { + bookmarkPath = await path.join(await path.homeDir(), ".config/google-chrome/Default/Bookmarks") + } else if (platform === "windows") { + bookmarkPath = await path.join( + await path.homeDir(), + "AppData/Local/Google/Chrome/User Data/Default/Bookmarks" + ) + } + if (bookmarkPath && (await fs.exists(bookmarkPath))) { + return bookmarkPath + } else { + return null + } +} + +async function getEdgeBookmarksPath(): Promise { + const platform: Platform = await os.platform() + let bookmarkPath = null + if (platform === "macos") { + bookmarkPath = await path.join( + await path.homeDir(), + "Library/Application Support/Microsoft Edge/Default/Bookmarks" + ) + } else if (platform === "linux") { + bookmarkPath = await path.join( + await path.homeDir(), + ".config/microsoft-edge-dev/Default/Bookmarks" + ) + } else if (platform === "windows") { + bookmarkPath = await path.join( + await path.homeDir(), + "AppData/Local/Microsoft/Edge/User Data/Default/Bookmarks" + ) + } + if (bookmarkPath && (await fs.exists(bookmarkPath))) { + return bookmarkPath + } else { + return null + } +} + +async function getFirefoxBookmarksPath(): Promise { + const platform: Platform = await os.platform() + let bookmarkPath = null + if (platform === "macos") { + const firefoxProfilesDir = await path.join( + await path.homeDir(), + "Library/Application Support/Firefox/Profiles" + ) + if (!(await fs.exists(firefoxProfilesDir))) { + return null + } + const firefoxProfiles = await fs.readDir(firefoxProfilesDir) + console.log("firefoxProfiles", firefoxProfiles) + const defaultReleaseDir = firefoxProfiles.find((dir) => dir.name.includes("default-release")) + if (!defaultReleaseDir) return null + bookmarkPath = await path.join(firefoxProfilesDir, defaultReleaseDir.name, "places.sqlite") + if (!(await fs.exists(bookmarkPath))) { + console.warn("firefox bookmark DB not exists", bookmarkPath) + return null + } + return bookmarkPath + } else if (platform === "linux") { + bookmarkPath = await path.join( + await path.homeDir(), + "snap/firefox/common/.mozilla/firefox/vuolckoc.default/places.sqlite" + ) + } else if (platform === "windows") { + bookmarkPath = await path.join( + await path.homeDir(), + "AppData/Roaming/Mozilla/Firefox/Profiles/" + ) + } + if (bookmarkPath && (await fs.exists(bookmarkPath))) { + return bookmarkPath + } else { + return null + } +} + +export async function createBookmarkLoader(browser: Browser): Promise { + switch (browser) { + case "chrome": + const chromeBookmarkPath = await getChromeBookmarksPath() + if (chromeBookmarkPath) { + return new ChromeBookmarks(chromeBookmarkPath) + } + break + case "edge": + const edgeBookmarkPath = await getEdgeBookmarksPath() + if (edgeBookmarkPath) { + return new ChromeBookmarks(edgeBookmarkPath) + } + break + case "firefox": + const firefoxSqlitePath = await getFirefoxBookmarksPath() + if (firefoxSqlitePath) { + return new FirefoxBookmarks(firefoxSqlitePath) + } + break + + default: + break + } + + return null +} diff --git a/src/constants.ts b/src/constants.ts new file mode 100644 index 0000000..12518a7 --- /dev/null +++ b/src/constants.ts @@ -0,0 +1,2 @@ +import type { Browser, Platform } from "./types"; + diff --git a/src/i18n/en.ts b/src/i18n/en.ts new file mode 100644 index 0000000..331e9f1 --- /dev/null +++ b/src/i18n/en.ts @@ -0,0 +1,5 @@ +const en = { + welcome: "Welcome to Kunkun" +} +export default en +export type Translation = typeof en diff --git a/src/i18n/index.ts b/src/i18n/index.ts new file mode 100644 index 0000000..2f0e1c8 --- /dev/null +++ b/src/i18n/index.ts @@ -0,0 +1,20 @@ +import i18next from "i18next" +import en, { type Translation } from "./en" +import zh from "./zh" + +export function setupI18n(language: "en" | "zh" = "en") { + i18next.init({ + resources: { + en: { + translation: en + }, + zh: { + translation: zh + } + }, + lng: language, // default language + fallbackLng: "en" + }) +} + +export const t = (key: keyof Translation, options?: any) => i18next.t(key, options) diff --git a/src/i18n/zh.ts b/src/i18n/zh.ts new file mode 100644 index 0000000..dc3ab95 --- /dev/null +++ b/src/i18n/zh.ts @@ -0,0 +1,5 @@ +import type { Translation } from "./en" + +export default { + welcome: "欢迎来到Kunkun" +} satisfies Translation diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..ad550f7 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,140 @@ +import { + Action, + app, + expose, + Form, + fs, + Icon, + IconEnum, + List, + NodeNameEnum, + open, + os, + path, + shell, + toast, + ui, + WorkerExtension +} from "@kksh/api/ui/worker" +import { ChromeBookmarks, createBookmarkLoader, type Bookmark } from "./bookmark" +import { setupI18n, t } from "./i18n" +import type { Platform } from "./types" + +function bookmarkToItem(bookmark: Bookmark, options: { browserIcon: Icon }): List.Item { + return new List.Item({ + title: bookmark.name, + subTitle: bookmark.subtitle, + value: bookmark.url, + icon: bookmark.favicon + ? new Icon({ + type: IconEnum.RemoteUrl, + value: bookmark.favicon + }) + : new Icon({ + type: IconEnum.Iconify, + value: options.browserIcon.value + }), + accessories: [ + new List.ItemAccessory({ + icon: new Icon({ + type: IconEnum.Iconify, + value: options.browserIcon.value + }) + }) + ] + }) +} + +class BrowserBookmark extends WorkerExtension { + async onFormSubmit(value: Record): Promise { + console.log("Form submitted", value) + toast.success(`Form submitted: ${JSON.stringify(value)}`) + } + async load() { + ui.showLoadingBar(true) + ui.setSearchBarPlaceholder("Search for bookmarks") + ui.render(new List.List({})) + // const platform: Platform = await os.platform() + const [chromeBookmarks, edgeBookmarks, firefoxBookmarks] = await Promise.all([ + createBookmarkLoader("chrome").then((loader) => loader?.load() ?? []), + createBookmarkLoader("edge").then((loader) => loader?.load() ?? []), + createBookmarkLoader("firefox").then((loader) => loader?.load() ?? []) + ]) + + const sections: List.Section[] = [] + + if (firefoxBookmarks.length > 0) { + sections.push( + new List.Section({ + title: "Firefox", + subtitle: "Firefox", + items: firefoxBookmarks.map((bookmark) => + bookmarkToItem(bookmark, { + browserIcon: new Icon({ type: IconEnum.Iconify, value: "logos:firefox" }) + }) + ) + }) + ) + } + + if (chromeBookmarks.length > 0) { + sections.push( + new List.Section({ + title: "Chrome", + subtitle: "Chrome", + items: chromeBookmarks.map((bookmark) => + bookmarkToItem(bookmark, { + browserIcon: new Icon({ type: IconEnum.Iconify, value: "logos:chrome" }) + }) + ) + }) + ) + } + if (edgeBookmarks.length > 0) { + sections.push( + new List.Section({ + title: "Edge", + subtitle: "Edge", + items: edgeBookmarks.map((bookmark) => + bookmarkToItem(bookmark, { + browserIcon: new Icon({ type: IconEnum.Iconify, value: "logos:microsoft-edge" }) + }) + ) + }) + ) + } + + return ui + .setSearchBarPlaceholder("Enter a search term, and press enter to search") + .then(async () => { + return ui.render(new List.List({ sections })) + }) + .finally(() => { + ui.showLoadingBar(false) + }) + } + + async onActionSelected(actionValue: string): Promise { + switch (actionValue) { + case "open": + break + + default: + break + } + } + + onSearchTermChange(term: string): Promise { + return Promise.resolve() + } + + onListItemSelected(value: string): Promise { + console.log("Item selected:", value) + if (value.startsWith("http")) { + open.url(value) + } + return Promise.resolve() + } +} + +expose(new BrowserBookmark()) diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 0000000..5f05237 --- /dev/null +++ b/src/types.ts @@ -0,0 +1,42 @@ +import { os } from "@kksh/api/ui/worker" +import * as v from "valibot" + +export type Platform = Awaited> +export type Browser = "chrome" | "firefox" | "edge" + +export type ChromeBookmark = { + date_added: string + date_last_used: string + guid: string + id: string + name: string + type: "url" | "folder" + url?: string + children?: ChromeBookmark[] + visit_count?: number +} + +export const ChromeBookmark: v.GenericSchema = v.object({ + date_added: v.string(), + date_last_used: v.string(), + guid: v.string(), + id: v.string(), + name: v.string(), + type: v.union([v.literal("url"), v.literal("folder")]), + url: v.optional(v.string()), + visit_count: v.optional(v.number()), + children: v.optional(v.array(v.lazy(() => ChromeBookmark))) +}) + +export const ChromeBookmarksFile = v.object({ + checksum: v.string(), + version: v.number(), + sync_metadata: v.string(), + roots: v.object({ + bookmark_bar: ChromeBookmark, + other: ChromeBookmark, + synced: ChromeBookmark + }) +}) + +export type ChromeBookmarksFile = v.InferOutput diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..49ca356 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + // Enable latest features + "lib": [ + "ESNext", + "DOM" + ], + "target": "ESNext", + "module": "ESNext", + "moduleDetection": "force", + "jsx": "react-jsx", + "allowJs": true, + // Bundler mode + "moduleResolution": "bundler", + "allowImportingTsExtensions": false, + "verbatimModuleSyntax": true, + "noEmit": true, + // Best practices + "strict": true, + "skipLibCheck": true, + "noFallthroughCasesInSwitch": true, + // Some stricter flags (disabled by default) + "noUnusedLocals": false, + "noUnusedParameters": false, + "noPropertyAccessFromIndexSignature": false + } +} \ No newline at end of file