mirror of
https://github.com/jonasrafa/kunkun-ext-google-search.git
synced 2025-04-03 17:56:45 +00:00
init
This commit is contained in:
commit
1d81adbf47
177
.gitignore
vendored
Normal file
177
.gitignore
vendored
Normal file
@ -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/
|
||||
|
57
CHANGELOG.md
Normal file
57
CHANGELOG.md
Normal file
@ -0,0 +1,57 @@
|
||||
# template-ext-worker
|
||||
|
||||
## 0.0.10
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @kksh/api@0.1.5
|
||||
|
||||
## 0.0.9
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @kksh/api@0.1.4
|
||||
|
||||
## 0.0.8
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @kksh/api@0.1.2
|
||||
|
||||
## 0.0.7
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @kksh/api@0.1.1
|
||||
|
||||
## 0.0.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @kksh/api@0.1.0
|
||||
|
||||
## 0.0.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @kksh/api@0.0.53
|
||||
|
||||
## 0.0.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @kksh/api@0.0.48
|
||||
|
||||
## 0.0.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @kksh/api@0.0.47
|
85
README.md
Normal file
85
README.md
Normal file
@ -0,0 +1,85 @@
|
||||
# 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/template"
|
||||
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
|
||||
```
|
||||
|
||||
See [Documentation](https://docs.kunkun.sh/guides/extensions/publish/design/) for more details on how to publish your extension. You will need to publish your extension package to npm or jsr first with GitHub actioin, then register it on Kunkun's website.
|
30
build.ts
Normal file
30
build.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import { watch } from "fs"
|
||||
import { join } from "path"
|
||||
import { refreshTemplateWorkerCommand } 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 refreshTemplateWorkerCommand()
|
||||
}
|
||||
} 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()
|
||||
})
|
||||
}
|
50
package.json
Normal file
50
package.json
Normal file
@ -0,0 +1,50 @@
|
||||
{
|
||||
"$schema": "https://schema.kunkun.sh",
|
||||
"name": "google-search",
|
||||
"version": "0.0.10",
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"kunkun": {
|
||||
"name": "Google Search",
|
||||
"shortDescription": "Google search with autosuggestions",
|
||||
"longDescription": "Google search with autosuggestions",
|
||||
"identifier": "google-search",
|
||||
"permissions": [
|
||||
"fetch:all"
|
||||
],
|
||||
"demoImages": [],
|
||||
"icon": {
|
||||
"type": "iconify",
|
||||
"value": "material-symbols:extension"
|
||||
},
|
||||
"customUiCmds": [],
|
||||
"templateUiCmds": [
|
||||
{
|
||||
"name": "Google Search",
|
||||
"description": "Google search with autosuggestions",
|
||||
"main": "dist/index.js",
|
||||
"cmds": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "bun build.ts dev",
|
||||
"build": "bun build.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@kksh/api": "0.1.5",
|
||||
"i18next": "^23.15.1",
|
||||
"iconv-lite": "^0.6.3",
|
||||
"nanoid": "^4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bun": "latest"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": "^5.0.0"
|
||||
},
|
||||
"files": [
|
||||
"./dist",
|
||||
".gitignore"
|
||||
]
|
||||
}
|
2347
pnpm-lock.yaml
generated
Normal file
2347
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
5
src/i18n/en.ts
Normal file
5
src/i18n/en.ts
Normal file
@ -0,0 +1,5 @@
|
||||
const en = {
|
||||
welcome: "Welcome to Kunkun"
|
||||
}
|
||||
export default en
|
||||
export type Translation = typeof en
|
20
src/i18n/index.ts
Normal file
20
src/i18n/index.ts
Normal file
@ -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)
|
5
src/i18n/zh.ts
Normal file
5
src/i18n/zh.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import type { Translation } from "./en"
|
||||
|
||||
export default {
|
||||
welcome: "欢迎来到Kunkun"
|
||||
} satisfies Translation
|
97
src/index.ts
Normal file
97
src/index.ts
Normal file
@ -0,0 +1,97 @@
|
||||
import { expose, List, TemplateUiCommand, ui } from "@kksh/api/ui/template";
|
||||
import { getAutoSearchResults, getStaticResult } from "./utils/handleResults";
|
||||
import { SearchResult } from "./utils/types";
|
||||
|
||||
class DemoExtension extends TemplateUiCommand {
|
||||
private isLoading: boolean = false;
|
||||
private results: SearchResult[] = [];
|
||||
private searchText: string = "ola";
|
||||
private staticResults: SearchResult[] = [];
|
||||
private autoResults: SearchResult[] = [];
|
||||
private cancelRef: AbortController | null = null;
|
||||
|
||||
async load() {
|
||||
// load method is run when the extension is loaded, you can use it as an initializer
|
||||
console.log("Extension loaded");
|
||||
this.updateUI();
|
||||
}
|
||||
|
||||
async onSearchTermChange(searchText: string) {
|
||||
console.log("Search term changed:", searchText);
|
||||
this.searchText = searchText;
|
||||
this.staticResults = getStaticResult(searchText);
|
||||
await this.fetchAutoResults(searchText);
|
||||
this.combineResults();
|
||||
this.updateUI();
|
||||
}
|
||||
|
||||
async fetchAutoResults(searchText: string) {
|
||||
this.cancelRef?.abort();
|
||||
this.cancelRef = new AbortController();
|
||||
|
||||
try {
|
||||
this.isLoading = true;
|
||||
this.updateUI();
|
||||
console.log("Fetching auto results for:", searchText);
|
||||
|
||||
if (searchText) {
|
||||
const autoSearchResult = await getAutoSearchResults(
|
||||
searchText,
|
||||
this.cancelRef.signal
|
||||
);
|
||||
this.autoResults = autoSearchResult;
|
||||
console.log("Auto results fetched:", this.autoResults);
|
||||
} else {
|
||||
this.autoResults = [];
|
||||
console.log("No search text, auto results cleared");
|
||||
}
|
||||
|
||||
this.isLoading = false;
|
||||
this.updateUI();
|
||||
} catch (error) {
|
||||
if (error instanceof AbortError) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.error("Search error", error);
|
||||
ui.showToast({
|
||||
style: "error",
|
||||
title: "Could not perform search",
|
||||
message: String(error),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
combineResults() {
|
||||
console.log("Combining results");
|
||||
this.results = [...this.staticResults, ...this.autoResults].filter(
|
||||
(value, index, self) => index === self.findIndex((t) => t.id === value.id)
|
||||
);
|
||||
console.log("Combined results:", this.results);
|
||||
}
|
||||
|
||||
updateUI() {
|
||||
console.log("Updating UI with results:", this.results);
|
||||
return ui
|
||||
.setSearchBarPlaceholder("Enter a search term, and press enter to search")
|
||||
.then(() => {
|
||||
return ui.render(
|
||||
new List.List({
|
||||
sections: [
|
||||
new List.Section({
|
||||
items: this.results.map(
|
||||
(result) =>
|
||||
new List.Item({
|
||||
title: result.query,
|
||||
value: result.id,
|
||||
})
|
||||
),
|
||||
}),
|
||||
],
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
expose(new DemoExtension());
|
92
src/utils/handleResults.ts
Normal file
92
src/utils/handleResults.ts
Normal file
@ -0,0 +1,92 @@
|
||||
import { nanoid } from "nanoid";
|
||||
import { Preferences, SearchResult } from "./types";
|
||||
import iconv from "iconv-lite";
|
||||
import { fetch } from "@kksh/api/ui/template";
|
||||
|
||||
// export async function getSearchHistory(): Promise<SearchResult[]> {
|
||||
// const { rememberSearchHistory } = getPreferenceValues<Preferences>();
|
||||
|
||||
// if (!rememberSearchHistory) {
|
||||
// return [];
|
||||
// }
|
||||
|
||||
// const historyString = (await LocalStorage.getItem("history")) as string;
|
||||
|
||||
// if (historyString === undefined) {
|
||||
// return [];
|
||||
// }
|
||||
|
||||
// const items: SearchResult[] = JSON.parse(historyString);
|
||||
// return items;
|
||||
// }
|
||||
|
||||
export function getStaticResult(searchText: string): SearchResult[] {
|
||||
if (!searchText) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const result: SearchResult[] = [
|
||||
{
|
||||
id: nanoid(),
|
||||
query: searchText,
|
||||
description: `Search Google for '${searchText}'`,
|
||||
url: `https://www.google.com/search?q=${encodeURIComponent(searchText)}`,
|
||||
},
|
||||
];
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function getAutoSearchResults(
|
||||
searchText: string,
|
||||
signal: any
|
||||
): Promise<SearchResult[]> {
|
||||
const response = await fetch(
|
||||
`https://suggestqueries.google.com/complete/search?hl=en-us&output=chrome&q=${encodeURIComponent(
|
||||
searchText
|
||||
)}`,
|
||||
{
|
||||
method: "get",
|
||||
signal: signal,
|
||||
headers: {
|
||||
"Content-Type": "text/plain; charset=UTF-8",
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
return Promise.reject(response.statusText);
|
||||
}
|
||||
|
||||
const buffer = await response.arrayBuffer();
|
||||
const text = iconv.decode(Buffer.from(buffer), "iso-8859-1");
|
||||
const json = JSON.parse(text);
|
||||
|
||||
const results: SearchResult[] = [];
|
||||
|
||||
json[1].map((item: string, i: number) => {
|
||||
const type = json[4]["google:suggesttype"][i];
|
||||
const description = json[2][i];
|
||||
|
||||
if (type === "NAVIGATION") {
|
||||
results.push({
|
||||
id: nanoid(),
|
||||
query: description.length > 0 ? description : item,
|
||||
description: `Open URL for '${item}'`,
|
||||
url: item,
|
||||
isNavigation: true,
|
||||
});
|
||||
} else if (type === "QUERY") {
|
||||
results.push({
|
||||
id: nanoid(),
|
||||
query: item,
|
||||
description: `Search Google for '${item}'`,
|
||||
url: `https://www.google.com/search?q=${encodeURIComponent(item)}`,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
console.log(results);
|
||||
|
||||
return results;
|
||||
}
|
14
src/utils/types.ts
Normal file
14
src/utils/types.ts
Normal file
@ -0,0 +1,14 @@
|
||||
export const HISTORY_KEY = "history";
|
||||
|
||||
export interface SearchResult {
|
||||
id: string;
|
||||
description?: string;
|
||||
query: string;
|
||||
url: string;
|
||||
isNavigation?: boolean;
|
||||
isHistory?: boolean;
|
||||
}
|
||||
|
||||
export interface Preferences {
|
||||
rememberSearchHistory: boolean;
|
||||
}
|
27
tsconfig.json
Normal file
27
tsconfig.json
Normal file
@ -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
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user