1.7.1原版

This commit is contained in:
2026-07-18 15:20:48 +08:00
commit 8b7190ae69
53 changed files with 7962 additions and 0 deletions
+66
View File
@@ -0,0 +1,66 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Search Widget Preview</title>
<script type="module" src="/src/index.ts"></script>
<style>
body {
margin: 0 auto;
max-width: 1024px;
padding: 2rem;
}
.dark search-modal {
color-scheme: dark;
--halo-search-widget-muted-color: #cbd5e1;
--halo-search-widget-content-color: #f1f5f9;
--halo-search-widget-hit-bg-color: #090a11;
--halo-search-widget-modal-bg-color: #15172a;
--halo-search-widget-modal-layer-color: #000000cc;
--halo-search-widget-base-bg-color: #090a11;
--halo-search-widget-divider-color: #1e293b;
--halo-search-widget-kbd-border-color: #334155;
--halo-search-widget-kbd-shadow: 0px 2px 0px 0px #ffffff1a;
--halo-search-widget-color-modal-layer: rgba(0, 0, 0, 0.8);
--halo-search-widget-color-modal-content-bg: rgb(15 23 42);
--halo-search-widget-color-form-input: rgb(255, 255, 255);
--halo-search-widget-color-form-input-placeholder: rgb(148 163 184);
--halo-search-widget-color-form-input-bg: rgb(15 23 42);
--halo-search-widget-color-form-divider: rgb(30 41 59);
--halo-search-widget-color-result-item-bg: rgb(30 41 59);
--halo-search-widget-color-result-item-hover-bg: rgb(51 65 85);
--halo-search-widget-color-result-item-title: rgb(255 255 255);
--halo-search-widget-color-result-item-content: rgb(148 163 184);
--halo-search-widget-color-command-kbd-item: rgb(148 163 184);
--halo-search-widget-color-command-kbd-border: rgb(30 41 59);
--halo-search-widget-color-result-empty: rgb(148 163 184);
}
body > * {
margin: 1rem 0;
}
</style>
</head>
<body>
<button>启动</button>
<button id="color">暗色/亮色</button>
<search-modal></search-modal>
</body>
<script>
const button = document.querySelector('button');
const searchModal = document.querySelector('search-modal');
button.addEventListener('click', () => {
searchModal.open = !searchModal.open;
});
const color = document.getElementById('color');
color.addEventListener('click', () => {
document.body.classList.toggle('dark');
});
</script>
</html>
+15
View File
@@ -0,0 +1,15 @@
{
"$schema": "https://raw.githubusercontent.com/lit/lit/main/packages/localize-tools/config.schema.json",
"sourceLocale": "en",
"targetLocales": ["es", "zh-CN", "zh-TW"],
"tsConfig": "./tsconfig.json",
"output": {
"mode": "runtime",
"outputDir": "./src/generated/locales",
"localeCodesModule": "./src/generated/locale-codes.ts"
},
"interchange": {
"format": "xliff",
"xliffDir": "./xliff/"
}
}
+42
View File
@@ -0,0 +1,42 @@
{
"name": "@halo-dev/search-widget",
"version": "1.7.1",
"type": "module",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js"
},
"./var.css": "./var.css"
},
"main": "./dist/index.js",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"files": [
"dist",
"var.css"
],
"scripts": {
"dev": "vite --config vite.config.dev.ts",
"build": "vite build --config vite.config.lib.ts",
"lint": "lit-analyzer && eslint 'src/**/*.ts' --fix",
"prettier": "prettier \"**/*.{cjs,html,js,json,md,ts}\" --ignore-path ./.gitignore --write",
"prepublishOnly": "pnpm run build"
},
"dependencies": {
"@lit/localize": "^0.12.2",
"es-toolkit": "^1.39.7",
"lit": "^3.2.0",
"overlayscrollbars": "^2.10.0"
},
"devDependencies": {
"@iconify/json": "^2.2.234",
"@julr/unocss-preset-forms": "^2.0.0",
"@lit/localize-tools": "^0.8.0",
"@unocss/reset": "^66.3.3",
"lit-analyzer": "^1.2.1",
"unocss": "^66.3.3",
"vite-plugin-dts": "4.0.0-beta.2",
"vite": "npm:rolldown-vite@7.0.8"
}
}
@@ -0,0 +1,2 @@
export const HISTORY_KEY = 'halo:search-widgets:history:hits';
export const MAX_HISTORY_ITEMS = 50;
@@ -0,0 +1,27 @@
// Do not modify this file by hand!
// Re-generate this file by running lit-localize.
/**
* The locale code that templates in this source code are written in.
*/
export const sourceLocale = `en`;
/**
* The other locale codes that this application is localized into. Sorted
* lexicographically.
*/
export const targetLocales = [
`es`,
`zh-CN`,
`zh-TW`,
] as const;
/**
* All valid project locale codes. Sorted lexicographically.
*/
export const allLocales = [
`en`,
`es`,
`zh-CN`,
`zh-TW`,
] as const;
@@ -0,0 +1,20 @@
// Do not modify this file by hand!
// Re-generate this file by running lit-localize
/* eslint-disable no-irregular-whitespace */
/* eslint-disable @typescript-eslint/no-explicit-any */
export const templates = {
's5e8250fb85d64c23': `Cerrar`,
'sa84142fc34654130': `Sin resultados de búsqueda`,
'sad5f82f8364e5a1c': `Introducir palabras clave para buscar`,
'sb4f1dffbb6be6302': `Borrar`,
'sb85774dc5d18ff0f': `Confirmar`,
'sbce70cbdb856635e': `Recientes`,
'sfc63e31ac7c956ed': `Seleccionar`,
};
@@ -0,0 +1,20 @@
// Do not modify this file by hand!
// Re-generate this file by running lit-localize
/* eslint-disable no-irregular-whitespace */
/* eslint-disable @typescript-eslint/no-explicit-any */
export const templates = {
's5e8250fb85d64c23': `关闭`,
'sa84142fc34654130': `没有搜索结果`,
'sad5f82f8364e5a1c': `输入关键词以搜索`,
'sb4f1dffbb6be6302': `清空`,
'sb85774dc5d18ff0f': `确认`,
'sbce70cbdb856635e': `最近搜索`,
'sfc63e31ac7c956ed': `选择`,
};
@@ -0,0 +1,20 @@
// Do not modify this file by hand!
// Re-generate this file by running lit-localize
/* eslint-disable no-irregular-whitespace */
/* eslint-disable @typescript-eslint/no-explicit-any */
export const templates = {
's5e8250fb85d64c23': `關閉`,
'sa84142fc34654130': `沒有搜尋結果`,
'sad5f82f8364e5a1c': `輸入關鍵字以搜尋`,
'sb4f1dffbb6be6302': `清空`,
'sb85774dc5d18ff0f': `確認`,
'sbce70cbdb856635e': `最近搜尋`,
'sfc63e31ac7c956ed': `選擇`,
};
+5
View File
@@ -0,0 +1,5 @@
import { SearchForm } from './search-form';
import { SearchModal } from './search-modal';
import './locale';
export { SearchForm, SearchModal };
@@ -0,0 +1,38 @@
import { configureLocalization, type LocaleModule } from '@lit/localize';
import {
allLocales,
sourceLocale,
targetLocales,
} from '../generated/locale-codes';
import * as templates_es from '../generated/locales/es';
import * as templates_zh_CN from '../generated/locales/zh-CN';
import * as templates_zh_TW from '../generated/locales/zh-TW';
const localizedTemplates = new Map([
['es', templates_es],
['zh-CN', templates_zh_CN],
['zh-TW', templates_zh_TW],
]);
const { setLocale } = configureLocalization({
sourceLocale,
targetLocales,
loadLocale: async (locale) => localizedTemplates.get(locale) as LocaleModule,
});
setLocale(getLocale());
function getLanguageFromCookie() {
const match = document.cookie.match(/(^| )language=([^;]+)/);
const matchedLanguage = allLocales.find((locale) => locale === match?.[2]);
return matchedLanguage;
}
function getLanguageFromBrowser() {
const language = allLocales.find((locale) => locale === navigator.language);
return language;
}
export function getLocale() {
return getLanguageFromCookie() || getLanguageFromBrowser() || sourceLocale;
}
+350
View File
@@ -0,0 +1,350 @@
import type {
HaloDocument,
SearchOption,
SearchResult,
} from '@halo-dev/api-client';
import { msg } from '@lit/localize';
import resetStyles from '@unocss/reset/tailwind.css?inline';
import { type DebouncedFunction, debounce } from 'es-toolkit';
import { uniqBy } from 'es-toolkit/compat';
import { css, html, LitElement, unsafeCSS } from 'lit';
import { property, state } from 'lit/decorators.js';
import { createRef, type Ref, ref } from 'lit/directives/ref.js';
import { unsafeHTML } from 'lit/directives/unsafe-html.js';
import { HISTORY_KEY, MAX_HISTORY_ITEMS } from './constants';
import baseStyles from './styles/base';
export class SearchForm extends LitElement {
@property({ type: String })
baseUrl = '';
@property({ type: Object })
options = {};
@state()
private keyword = '';
@state()
private searchResult?: SearchResult;
@state()
private loading: boolean = false;
@state()
private selectedIndex = 0;
@state()
private historyHits: HaloDocument[] = [];
inputRef: Ref<HTMLInputElement> = createRef<HTMLInputElement>();
constructor() {
super();
this.addEventListener('keydown', this.handleKeydown);
this.historyHits = JSON.parse(
localStorage.getItem(HISTORY_KEY) || '[]'
) as HaloDocument[];
setTimeout(() => {
this.inputRef.value?.focus();
}, 0);
}
override render() {
return html`
<div class="p-3 z-1 bg-base sticky top-0 border-b border-divider">
<form
class="flex items-center ring-2 h-12 rounded-base px-2.5 ring-primary bg-base"
@submit=${(e: Event) => e.preventDefault()}
>
<span
class="shrink flex-none size-6 text-primary ${
this.loading
? 'i-lucide-loader-circle animate-spin'
: 'i-lucide-search'
}"
></span>
<input
@input="${this.onInput}"
placeholder=${msg('Enter keywords to search')}
autocomplete="off"
spellcheck="false"
${ref(this.inputRef)}
class="flex-1 min-w-0 outline-none text-content h-full px-2.5 bg-transparent"
/>
${
this.keyword
? html`
<span
@click=${this.handleClearInput}
class="flex-none cursor-pointer shrink i-lucide-x size-5 text-muted hover:text-content"
></span>
`
: ''
}
</form>
</div>
${this.keyword ? this.renderItems() : this.renderHistoryItems()}
<div
class="border-t border-divider p-3 bg-base sticky bottom-0 space-x-5 flex justify-end"
>
${[
{
text: html`${msg('Select')}`,
kbdIcons: ['i-lucide-arrow-up', 'i-lucide-arrow-down'],
},
{
text: html`${msg('Confirm')}`,
kbdIcons: ['i-lucide-corner-down-left'],
},
{
text: html`${msg('Close')}`,
kbdIcons: ['i-mdi-keyboard-esc'],
},
].map(
(item) => html`
<div class="flex items-center space-x-1.5">
${item.kbdIcons.map(
(icon) => html`
<kbd
class="inline-flex justify-center items-center py-1 px-1.5 bg-base border border-kbd font-mono text-sm text-content rounded-base shadow-kbd"
>
<i class="${icon}"></i>
</kbd>
`
)}
<span class="text-xs text-muted">${item.text}</span>
</div>
`
)}
</div>
`;
}
handleClearInput() {
this.keyword = '';
this.searchResult = undefined;
if (this.inputRef.value) {
this.inputRef.value.value = '';
this.inputRef.value.focus();
}
}
renderItems() {
return html`<div class="p-3">
${!this.searchResult?.hits?.length ? this.renderEmpty() : ''}
<ul class="space-y-1.5" role="list">
${this.searchResult?.hits?.map((hit, index) =>
this.renderListItem(hit, index, 'i-lucide-file-text')
)}
</ul>
</div>`;
}
renderEmpty() {
return html`<div class="flex py-4 justify-center text-sm text-muted">
<span>${msg('No search results')}</span>
</div>`;
}
renderHistoryItems() {
return html`
<div class="p-3">
${
this.historyHits.length
? html`<div class="flex justify-between items-center">
<h3 class="text-sm font-medium text-primary">
${msg('Recent')}
</h3>
<span
class="text-xs cursor-pointer text-muted hover:text-content"
@click=${this.handleClearHistory}
>
${msg('Clear')}
</span>
</div>
<ul class="mt-3 space-y-1.5" role="list">
${this.historyHits?.map((hit, index) =>
this.renderListItem(hit, index, 'i-lucide-history')
)}
</ul>`
: this.renderEmpty()
}
</div>
`;
}
renderListItem(hit: HaloDocument, index: number, listIcon: string) {
return html`
<li
@click="${() => this.handleOpenLink(hit)}"
@mouseenter=${() => {
this.selectedIndex = index;
}}
class="shadow-sm flex items-center space-x-3 rounded-base cursor-pointer p-3 bg-hit [&_mark]:text-primary [&_mark]:font-semibold [&_mark]:bg-transparent ${
index === this.selectedIndex
? '!bg-primary [&_mark]:!text-white [&_mark]:underline'
: ''
}"
data-index=${index}
>
<span
class="flex-none shrink ${listIcon} ${
this.selectedIndex === index ? 'text-white' : 'text-muted'
}"
></span>
<div class="flex-1 space-y-1.5 min-w-0">
<h2
class="text-sm font-medium ${
this.selectedIndex === index ? 'text-white' : 'text-content'
}"
>
${unsafeHTML(hit.title)}
</h2>
${
hit.description
? html`
<p
class="text-xs leading-6 ${
this.selectedIndex === index
? 'text-white/90'
: 'text-muted'
}"
>
${unsafeHTML(hit.description)}
</p>
`
: ''
}
</div>
<span
class="i-lucide-corner-down-left flex-none shrink text-white invisible ${
this.selectedIndex === index ? '!visible' : ''
}"
></span>
</li>
`;
}
onInput(e: InputEvent) {
const input = e.target as HTMLInputElement;
const value = input.value;
this.keyword = value || '';
this.selectedIndex = 0;
if (value === '') {
this.searchResult = undefined;
return;
}
this.loading = true;
this.fetchHits(value);
}
handleClearHistory() {
localStorage.removeItem(HISTORY_KEY);
this.historyHits = [];
}
fetchHits: DebouncedFunction<(keyword: string) => Promise<void>> = debounce(
async (keyword: string) => {
const searchOptions: SearchOption = {
...this.options,
highlightPostTag: '</mark>',
highlightPreTag: '<mark>',
keyword,
limit: 20,
};
const response = await fetch(
`/apis/api.halo.run/v1alpha1/indices/-/search`,
{
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(searchOptions),
method: 'post',
}
);
const data = (await response.json()) as SearchResult;
this.searchResult = data;
this.loading = false;
},
300
);
handleOpenLink(hit: HaloDocument) {
const updatedHistory = uniqBy([hit, ...this.historyHits], 'id').slice(
0,
MAX_HISTORY_ITEMS
);
localStorage.setItem(HISTORY_KEY, JSON.stringify(updatedHistory));
this.historyHits = updatedHistory;
window.location.href = hit.permalink;
}
handleKeydown = (e: KeyboardEvent) => {
const { key, ctrlKey } = e;
const hits = this.keyword ? this.searchResult?.hits : this.historyHits;
if (!hits) {
return;
}
if (key === 'ArrowUp' || (key === 'k' && ctrlKey)) {
this.selectedIndex = Math.max(0, this.selectedIndex - 1);
this.handleScrollIntoSelected();
e.preventDefault();
}
if (key === 'ArrowDown' || (key === 'j' && ctrlKey)) {
this.selectedIndex = Math.min(hits.length || 0, this.selectedIndex + 1);
this.handleScrollIntoSelected();
e.preventDefault();
}
if (key === 'Enter') {
const hit = hits[this.selectedIndex];
if (hit) {
this.handleOpenLink(hit);
}
}
};
handleScrollIntoSelected() {
const selectedElement = this.shadowRoot?.querySelector(
`[data-index="${this.selectedIndex}"]`
);
if (selectedElement) {
selectedElement.scrollIntoView({
behavior: 'smooth',
block: 'center',
});
}
}
static override styles = [
unsafeCSS(resetStyles),
baseStyles,
css`
@unocss-placeholder;
`,
];
}
customElements.get('search-form') ||
customElements.define('search-form', SearchForm);
declare global {
interface HTMLElementTagNameMap {
'search-form': SearchForm;
}
}
+188
View File
@@ -0,0 +1,188 @@
import resetStyles from '@unocss/reset/tailwind.css?inline';
import { css, html, LitElement, type PropertyValues, unsafeCSS } from 'lit';
import { property } from 'lit/decorators.js';
import { styleMap } from 'lit/directives/style-map.js';
import { OverlayScrollbars } from 'overlayscrollbars';
import overlayscrollbarsStyles from 'overlayscrollbars/styles/overlayscrollbars.css?inline';
import './search-form';
import baseStyles from './styles/base';
export class SearchModal extends LitElement {
@property({
type: Boolean,
})
open = false;
@property({ type: String })
baseUrl = '';
@property({ type: Object })
options = {};
constructor() {
super();
setTimeout(() => {
const modalContent = this.shadowRoot?.querySelector(
'.modal__content'
) as HTMLElement;
if (modalContent) {
OverlayScrollbars(modalContent, {
scrollbars: {
autoHide: 'scroll',
autoHideDelay: 600,
},
});
}
}, 0);
}
override willUpdate(changedProperties: PropertyValues) {
if (!changedProperties.has('open')) {
return;
}
if (this.open) {
document.body.style.overflow = 'hidden';
} else {
document.body.style.removeProperty('overflow');
}
}
override render() {
return html`<div
class="modal__wrapper"
style="${styleMap({ display: this.open ? 'flex' : 'none' })}"
>
<div class="modal__layer" @click="${this.close}"></div>
<div
data-overlayscrollbars-initialize
class="modal__content shadow-xl bg-modal"
>
${
this.open
? html`<search-form
.baseUrl=${this.baseUrl}
.options=${this.options}
></search-form>`
: ''
}
</div>
</div>`;
}
close() {
this.open = false;
}
handleKeydown = (e: KeyboardEvent) => {
const { key } = e;
if (key === 'Escape') {
this.close();
e.preventDefault();
}
};
override connectedCallback() {
super.connectedCallback();
window.addEventListener('keydown', this.handleKeydown);
}
override disconnectedCallback() {
super.disconnectedCallback();
window.removeEventListener('keydown', this.handleKeydown);
}
static override styles = [
unsafeCSS(resetStyles),
unsafeCSS(overlayscrollbarsStyles),
baseStyles,
css`
:host {
// @deprecated --halo-search-widget-color-modal-layer and
// --halo-search-widget-base-border-radius will be removed in future
--base-rounded: var(
--halo-search-widget-base-rounded,
var(--halo-search-widget-base-border-radius, 0.4em)
);
--modal-layer-color: var(
--halo-search-widget-modal-layer-color,
var(--halo-search-widget-color-modal-layer, rgb(107 114 128 / 0.75))
);
}
.modal__wrapper {
position: fixed;
left: 0px;
top: 0px;
display: flex;
height: 100%;
width: 100%;
flex-direction: row;
align-items: flex-start;
justify-content: center;
padding-top: 3em;
padding-bottom: 3em;
z-index: 999;
}
.modal__layer {
background-color: var(--modal-layer-color, rgb(107 114 128 / 0.75));
position: absolute;
top: 0px;
left: 0px;
height: 100%;
width: 100%;
flex: none;
animation: fadeIn 0.15s both;
}
.modal__content {
border-radius: var(--base-rounded, 0.4em);
position: relative;
display: flex;
flex-direction: column;
align-items: stretch;
width: calc(100vw - 20px);
max-height: calc(100vh - 5em);
max-width: 580px;
overflow: auto;
animation: fadeInUp 0.3s both;
}
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes fadeInUp {
from {
opacity: 0;
transform: translate3d(0, 10%, 0);
}
to {
opacity: 1;
transform: translate3d(0, 0, 0);
}
}
@unocss-placeholder;
`,
];
}
customElements.get('search-modal') ||
customElements.define('search-modal', SearchModal);
declare global {
interface HTMLElementTagNameMap {
'search-modal': SearchModal;
}
}
+19
View File
@@ -0,0 +1,19 @@
import { css } from 'lit';
const baseStyles = css`
:host {
font-size: var(--halo-search-widget-base-font-size, 1em);
font-family: var(
--halo-search-widget-base-font-family,
ui-sans-serif,
system-ui,
sans-serif,
'Apple Color Emoji',
'Segoe UI Emoji',
'Segoe UI Symbol',
'Noto Color Emoji'
);
}
`;
export default baseStyles;
+46
View File
@@ -0,0 +1,46 @@
import { css } from 'lit';
const varStyles = css`
:host {
--color-form-input-bg: var(--halo-search-widget-color-form-input-bg, #fff);
--color-form-input: var(--halo-search-widget-color-form-input, #333);
--color-form-input-placeholder: var(
--halo-search-widget-color-form-input-placeholder,
rgb(107 114 128)
);
--color-form-divider: var(
--halo-search-widget-color-form-divider,
rgb(243 244 246)
);
--color-result-empty: var(
--halo-search-widget-color-result-empty,
rgb(107 114 128)
);
--color-result-item-bg: var(
--halo-search-widget-color-result-item-bg,
rgb(249 250 251)
);
--color-result-item-hover-bg: var(
--halo-search-widget-color-result-item-hover-bg,
rgb(243 244 246)
);
--color-result-item-title: var(
--halo-search-widget-color-result-item-title,
#333
);
--color-result-item-content: var(
--halo-search-widget-color-result-item-content,
rgb(75, 85, 99)
);
--color-command-kbd-item: var(
--halo-search-widget-color-command-kbd-item,
#333
);
--color-command-kbd-border: var(
--halo-search-widget-color-command-kbd-border,
#e5e7eb
);
}
`;
export default varStyles;
+15
View File
@@ -0,0 +1,15 @@
export interface Hit {
name: string;
title: string;
content: string;
publishTimestamp: string;
permalink: string;
}
export interface Result {
hits: Hit[];
keyword: string;
total: number;
limit: number;
processingTimeMills: number;
}
+1
View File
@@ -0,0 +1 @@
/// <reference types="vite/client" />
+33
View File
@@ -0,0 +1,33 @@
{
"compilerOptions": {
"target": "es2021",
"module": "es2020",
"lib": ["es2021", "DOM", "DOM.Iterable"],
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"inlineSources": true,
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"noImplicitAny": true,
"noImplicitThis": true,
"moduleResolution": "node",
"allowSyntheticDefaultImports": true,
"experimentalDecorators": true,
"forceConsistentCasingInFileNames": true,
"noImplicitOverride": true,
"plugins": [
{
"name": "ts-lit-plugin",
"strict": true
}
]
},
"include": ["src/**/*.ts"],
"exclude": []
}
+64
View File
@@ -0,0 +1,64 @@
import {
defineConfig,
definePreset,
presetIcons,
presetUno,
presetWind3,
transformerCompileClass,
transformerDirectives,
} from 'unocss';
const remRE = /(-?[.\d]+)rem/g;
const presetRemToEm = definePreset(() => {
return {
name: '@unocss/preset-rem-to-em',
postprocess: (util) => {
util.entries.forEach((i) => {
const value = i[1];
if (typeof value === 'string' && remRE.test(value))
i[1] = value.replace(remRE, (_, p1) => `${p1}em`);
});
},
};
});
export default defineConfig({
presets: [presetWind3(), presetIcons(), presetRemToEm()],
transformers: [transformerDirectives(), transformerCompileClass()],
theme: {
colors: {
primary: 'var(--halo-search-widget-primary-color,#4CCBA0)',
muted: 'var(--halo-search-widget-muted-color,#475569)',
content:
'var(--halo-search-widget-content-color,var(--halo-search-widget-color-form-input,#0f172a))',
},
borderRadius: {
base: 'var(--halo-search-widget-base-rounded, 0.4em)',
},
},
shortcuts: {
'bg-modal':
'bg-[var(--halo-search-widget-modal-bg-color,var(--halo-search-widget-color-modal-content-bg,#f8fafc))]',
'bg-hit':
'bg-[var(--halo-search-widget-hit-bg-color,var(--halo-search-widget-color-result-item-bg,#fff))]',
'bg-base':
'bg-[var(--halo-search-widget-base-bg-color,var(--halo-search-widget-color-modal-content-bg,#fff))]',
'border-divider':
'border-[var(--halo-search-widget-divider-color,var(--halo-search-widget-color-form-divider,#f1f5f9))]',
'border-kbd':
'border-[var(--halo-search-widget-kbd-border-color,var(--halo-search-widget-color-command-kbd-border,#e2e8f0))]',
'shadow-kbd':
'shadow-[var(--halo-search-widget-kbd-shadow,0px_2px_0px_0px_rgba(0,0,0,0.08))]',
},
safelist: [
'i-lucide-arrow-up',
'i-lucide-arrow-down',
'i-mdi-keyboard-esc',
'i-lucide-corner-down-left',
'i-lucide-file-text',
'i-lucide-history',
'i-lucide-loader-circle',
'i-lucide-search',
],
});
+30
View File
@@ -0,0 +1,30 @@
@media (prefers-color-scheme: dark) {
.color-scheme-auto,
[data-color-scheme="auto"] {
color-scheme: dark;
--halo-search-widget-muted-color: #cbd5e1;
--halo-search-widget-content-color: #f1f5f9;
--halo-search-widget-hit-bg-color: #090a11;
--halo-search-widget-modal-bg-color: #15172a;
--halo-search-widget-modal-layer-color: #000000cc;
--halo-search-widget-base-bg-color: #090a11;
--halo-search-widget-divider-color: #1e293b;
--halo-search-widget-kbd-border-color: #334155;
--halo-search-widget-kbd-shadow: 0px 2px 0px 0px #ffffff1a;
}
}
.color-scheme-dark,
.dark,
[data-color-scheme="dark"] {
color-scheme: dark;
--halo-search-widget-muted-color: #cbd5e1;
--halo-search-widget-content-color: #f1f5f9;
--halo-search-widget-hit-bg-color: #090a11;
--halo-search-widget-modal-bg-color: #15172a;
--halo-search-widget-modal-layer-color: #000000cc;
--halo-search-widget-base-bg-color: #090a11;
--halo-search-widget-divider-color: #1e293b;
--halo-search-widget-kbd-border-color: #334155;
--halo-search-widget-kbd-shadow: 0px 2px 0px 0px #ffffff1a;
}
+19
View File
@@ -0,0 +1,19 @@
import UnoCSS from 'unocss/vite';
import { defineConfig, type UserConfig } from 'vite';
export default defineConfig({
plugins: [
UnoCSS({
mode: 'shadow-dom',
configFile: './uno.config.ts',
}),
],
server: {
proxy: {
'/apis': {
target: 'http://localhost:8090',
changeOrigin: true,
},
},
},
}) satisfies UserConfig;
+27
View File
@@ -0,0 +1,27 @@
import UnoCSS from 'unocss/vite';
import { defineConfig, type Plugin } from 'vite';
import dts from 'vite-plugin-dts';
export default defineConfig({
build: {
lib: {
entry: 'src/index.ts',
name: 'search-widget',
fileName: 'index',
formats: ['es'],
},
emptyOutDir: true,
rollupOptions: {
output: {
extend: true,
},
},
},
plugins: [
UnoCSS({
mode: 'shadow-dom',
configFile: './uno.config.ts',
}),
dts() as Plugin,
],
});
+35
View File
@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?>
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
<file target-language="es" source-language="en" original="lit-localize-inputs" datatype="plaintext">
<body>
<trans-unit id="sfc63e31ac7c956ed">
<source>Select</source>
<target>Seleccionar</target>
</trans-unit>
<trans-unit id="sb85774dc5d18ff0f">
<source>Confirm</source>
<target>Confirmar</target>
</trans-unit>
<trans-unit id="s5e8250fb85d64c23">
<source>Close</source>
<target>Cerrar</target>
</trans-unit>
<trans-unit id="sad5f82f8364e5a1c">
<source>Enter keywords to search</source>
<target>Introducir palabras clave para buscar</target>
</trans-unit>
<trans-unit id="sa84142fc34654130">
<source>No search results</source>
<target>Sin resultados de búsqueda</target>
</trans-unit>
<trans-unit id="sbce70cbdb856635e">
<source>Recent</source>
<target>Recientes</target>
</trans-unit>
<trans-unit id="sb4f1dffbb6be6302">
<source>Clear</source>
<target>Borrar</target>
</trans-unit>
</body>
</file>
</xliff>
+35
View File
@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?>
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
<file target-language="zh-CN" source-language="en" original="lit-localize-inputs" datatype="plaintext">
<body>
<trans-unit id="sfc63e31ac7c956ed">
<source>Select</source>
<target>选择</target>
</trans-unit>
<trans-unit id="sb85774dc5d18ff0f">
<source>Confirm</source>
<target>确认</target>
</trans-unit>
<trans-unit id="s5e8250fb85d64c23">
<source>Close</source>
<target>关闭</target>
</trans-unit>
<trans-unit id="sad5f82f8364e5a1c">
<source>Enter keywords to search</source>
<target>输入关键词以搜索</target>
</trans-unit>
<trans-unit id="sa84142fc34654130">
<source>No search results</source>
<target>没有搜索结果</target>
</trans-unit>
<trans-unit id="sbce70cbdb856635e">
<source>Recent</source>
<target>最近搜索</target>
</trans-unit>
<trans-unit id="sb4f1dffbb6be6302">
<source>Clear</source>
<target>清空</target>
</trans-unit>
</body>
</file>
</xliff>
+35
View File
@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?>
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
<file target-language="zh-TW" source-language="en" original="lit-localize-inputs" datatype="plaintext">
<body>
<trans-unit id="sfc63e31ac7c956ed">
<source>Select</source>
<target>選擇</target>
</trans-unit>
<trans-unit id="sb85774dc5d18ff0f">
<source>Confirm</source>
<target>確認</target>
</trans-unit>
<trans-unit id="s5e8250fb85d64c23">
<source>Close</source>
<target>關閉</target>
</trans-unit>
<trans-unit id="sad5f82f8364e5a1c">
<source>Enter keywords to search</source>
<target>輸入關鍵字以搜尋</target>
</trans-unit>
<trans-unit id="sa84142fc34654130">
<source>No search results</source>
<target>沒有搜尋結果</target>
</trans-unit>
<trans-unit id="sbce70cbdb856635e">
<source>Recent</source>
<target>最近搜尋</target>
</trans-unit>
<trans-unit id="sb4f1dffbb6be6302">
<source>Clear</source>
<target>清空</target>
</trans-unit>
</body>
</file>
</xliff>
+12
View File
@@ -0,0 +1,12 @@
{
"private": true,
"scripts": {
"build": "tsc && vite build",
"dev": "vite",
"lint": "eslint 'src/**/*.ts'",
"prettier": "prettier \"**/*.{cjs,html,js,json,md,ts}\" --ignore-path ./.gitignore --write"
},
"dependencies": {
"@halo-dev/search-widget": "workspace:*"
}
}
+16
View File
@@ -0,0 +1,16 @@
import { SearchModal } from '@halo-dev/search-widget';
import '@halo-dev/search-widget/var.css';
import type { SearchOption } from '@halo-dev/api-client';
export { SearchModal };
const searchModalElement = document.createElement(
'search-modal'
) as SearchModal;
document.body.append(searchModalElement);
export function open(options: SearchOption) {
searchModalElement.options = options;
searchModalElement.open = true;
}
+1
View File
@@ -0,0 +1 @@
/// <reference types="vite/client" />
+4
View File
@@ -0,0 +1,4 @@
{
"extends": "../../tsconfig.json",
"include": ["src"]
}
+30
View File
@@ -0,0 +1,30 @@
import path from 'node:path';
import { fileURLToPath, URL } from 'node:url';
import { defineConfig } from 'vite';
export default defineConfig({
experimental: {
enableNativePlugin: true,
},
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url)),
},
},
define: { 'process.env.NODE_ENV': "'production'" },
build: {
outDir: fileURLToPath(
new URL('../../src/main/resources/static', import.meta.url)
),
emptyOutDir: true,
cssCodeSplit: false,
lib: {
entry: path.resolve(__dirname, 'src/index.ts'),
formats: ['iife'],
name: 'SearchWidget',
fileName: (format) => `search-widget.${format}.js`,
cssFileName: 'style',
},
sourcemap: false,
},
});