Archived
search providers & more
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
import { createResource } from 'solid-js';
|
||||
import { SearchProvider } from '~/types';
|
||||
|
||||
async function getSearchProviders(): Promise<SearchProvider[]> {
|
||||
return await import('../../data/search-providers.json').then((m) => m.default);
|
||||
}
|
||||
|
||||
export function createSearchProviders() {
|
||||
const [searchProviders] = createResource<SearchProvider[]>(getSearchProviders);
|
||||
return searchProviders;
|
||||
}
|
||||
@@ -7,14 +7,19 @@
|
||||
.Header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: max(1vw, 1rem);
|
||||
margin-bottom: 1rem;
|
||||
padding-bottom: 8px;
|
||||
font-size: 2rem;
|
||||
border-bottom: 1px solid var(--color-text);
|
||||
font-size: max(1.8vw, 1.3rem);
|
||||
text-shadow: 1px 1px 5px #000;
|
||||
}
|
||||
|
||||
.Close {
|
||||
font-size: 2rem;
|
||||
cursor: pointer;
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.AppList {
|
||||
display: flex;
|
||||
gap: .5rem;
|
||||
|
||||
@@ -21,10 +21,10 @@ export function AppList({
|
||||
return (
|
||||
<div class={style.AppListWrap}>
|
||||
{tag?.() ? (
|
||||
<div>
|
||||
<div style={{ width: '100%' }}>
|
||||
<h2 class={style.Header}>
|
||||
{tag()}
|
||||
<span style={{ cursor: 'pointer' }} onClick={resetTag}>
|
||||
<span class={style.Close} onClick={resetTag}>
|
||||
×
|
||||
</span>
|
||||
</h2>
|
||||
|
||||
@@ -12,15 +12,15 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
text-align: right;
|
||||
gap: max(1vw, .5rem);
|
||||
font-size: max(1.8vw, 1.3rem);
|
||||
gap: clamp(.5rem, 1vw, .5rem);
|
||||
font-size: clamp(1.3rem, 1.8vw, 3rem);
|
||||
}
|
||||
|
||||
.Menu li {
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
text-shadow: 1px 1px 5px #000;
|
||||
padding: max(1vw, .5rem) 1.5rem;
|
||||
padding: clamp(.5rem, 1vw, 1.5rem) 1.5rem;
|
||||
}
|
||||
|
||||
.Menu li::after {
|
||||
@@ -37,6 +37,6 @@
|
||||
transition: height 0.2s ease;
|
||||
}
|
||||
|
||||
.Menu li:hover::after {
|
||||
.Menu li:hover::after, .Menu li.Active::after {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,8 @@ import { createTagOrder } from '~/api/get-apps';
|
||||
import style from './Sidebar.module.css';
|
||||
|
||||
type SidebarProps = {
|
||||
onSelectTag?(tag: string): void;
|
||||
activeTag?: string | null;
|
||||
onSelectTag?(tag: string, mode: 'permanent' | 'temporary'): void;
|
||||
};
|
||||
|
||||
export function Sidebar(props: SidebarProps) {
|
||||
@@ -13,8 +14,9 @@ export function Sidebar(props: SidebarProps) {
|
||||
<ul class={style.Menu}>
|
||||
{tagOrder()?.map((tag) => (
|
||||
<li
|
||||
onClick={() => props.onSelectTag?.(tag)}
|
||||
onMouseEnter={() => props.onSelectTag?.(tag)}
|
||||
class={props.activeTag === tag ? style.Active : ''}
|
||||
onClick={() => props.onSelectTag?.(tag, 'permanent')}
|
||||
onMouseEnter={() => props.onSelectTag?.(tag, 'temporary')}
|
||||
>
|
||||
{tag}
|
||||
</li>
|
||||
|
||||
@@ -1,6 +1,24 @@
|
||||
.SearchWrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1px;
|
||||
width: clamp(300px, 40vw, 600px);
|
||||
}
|
||||
|
||||
.Provider {
|
||||
text-transform: uppercase;
|
||||
backdrop-filter: blur(10px);
|
||||
padding: 11px 20px;
|
||||
width: 100px;
|
||||
text-align: center;
|
||||
background-color: rgba(0, 0, 0, 0.2);
|
||||
border-bottom: 2px solid rgba(0, 0, 0, 0.2);
|
||||
font-family: Nova Mono;
|
||||
}
|
||||
|
||||
.Search {
|
||||
flex-grow: 1;
|
||||
padding: 10px 20px;
|
||||
width: max(40vw, 300px);
|
||||
border: 0;
|
||||
font-size: 1rem;
|
||||
backdrop-filter: blur(10px);
|
||||
|
||||
@@ -1,24 +1,59 @@
|
||||
import { createSignal } from 'solid-js';
|
||||
import { createComputed, createMemo, createSignal } from 'solid-js';
|
||||
import { createSearchProviders } from '~/api/get-search-providers';
|
||||
|
||||
import style from './Search.module.css';
|
||||
import { SearchProvider } from '~/types';
|
||||
|
||||
export function Search() {
|
||||
const [term, setTerm] = createSignal('');
|
||||
|
||||
const searchProviders = createSearchProviders();
|
||||
|
||||
const defaultProvider = createMemo(
|
||||
() => searchProviders()?.find((provider) => provider.default) ?? searchProviders()?.[0]
|
||||
);
|
||||
|
||||
const searchPerPrefix = createMemo(() =>
|
||||
searchProviders()?.reduce<Record<string, SearchProvider>>((acc, provider) => {
|
||||
acc[provider.prefix] = provider;
|
||||
return acc;
|
||||
}, {})
|
||||
);
|
||||
|
||||
const activeProvider = createMemo(() => {
|
||||
if (term().startsWith('!') && searchPerPrefix()?.[term().charAt(1)]) {
|
||||
return searchPerPrefix()?.[term().charAt(1)];
|
||||
} else {
|
||||
return defaultProvider();
|
||||
}
|
||||
});
|
||||
|
||||
const searchTerm = createMemo(() => {
|
||||
if (term().startsWith('!') && searchPerPrefix()?.[term().charAt(1)]) {
|
||||
return term().slice(2);
|
||||
} else {
|
||||
return term();
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<div class={style.SearchWrapper}>
|
||||
<div class={style.Provider}>{activeProvider()?.name}</div>
|
||||
<input
|
||||
id="search"
|
||||
class={style.Search}
|
||||
type="text"
|
||||
placeholder="Search kagi..."
|
||||
placeholder={`Lorem ipsum`}
|
||||
value={term()}
|
||||
onInput={(e) => setTerm(e.target.value)}
|
||||
onKeyPress={(e) => {
|
||||
e.stopPropagation();
|
||||
if (e.key === 'Enter') {
|
||||
window.open(`https://kagi.com/search?q=${term()}`, '_blank', 'noreferrer');
|
||||
window.open(`${activeProvider()?.url}${searchTerm()}`, '_blank', 'noreferrer');
|
||||
setTerm('');
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
import { format } from 'date-fns';
|
||||
|
||||
export const getDate = () => format(new Date(), 'EEEE, dd MMMM yyyy');
|
||||
export const getDate = () => format(new Date(), 'EEEE, dd MMM yyyy');
|
||||
|
||||
export const getTime = () => format(new Date(), 'HH:mm:ss');
|
||||
export const getTime = (time: Date | number = new Date()) => format(time, 'HH:mm:ss');
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
gap: 2rem;
|
||||
overflow: scroll;
|
||||
|
||||
padding: 0 max(10vw, 2rem);
|
||||
padding: 0 clamp(1rem, 10vw, 10rem);
|
||||
}
|
||||
|
||||
.Widgets {
|
||||
|
||||
@@ -21,11 +21,13 @@ export default function Home() {
|
||||
const [activeTag, setActiveTag] = createSignal<string | null>(null);
|
||||
const [timeout, storeTimeout] = createSignal<NodeJS.Timeout | null>(null);
|
||||
|
||||
const switchToTag = (tag: string) => {
|
||||
const switchToTag = (tag: string, mode: 'permanent' | 'temporary') => {
|
||||
setActiveTag(tag);
|
||||
clearTimeout(timeout() as NodeJS.Timeout);
|
||||
|
||||
if (mode === 'temporary') {
|
||||
storeTimeout(setTimeout(() => setActiveTag(null), 3000));
|
||||
}
|
||||
};
|
||||
|
||||
const resetTag = () => {
|
||||
@@ -59,15 +61,15 @@ export default function Home() {
|
||||
|
||||
return (
|
||||
<main class={style.SidebarPage}>
|
||||
<Sidebar onSelectTag={switchToTag} />
|
||||
<Sidebar onSelectTag={switchToTag} activeTag={activeTag()} />
|
||||
<div class={style.ContentWrapper}>
|
||||
<div class={style.Widgets}>
|
||||
{activeTag() ? (
|
||||
<AppList apps={apps() ?? []} tag={activeTag} resetTag={resetTag} />
|
||||
) : (
|
||||
<>
|
||||
<Search />
|
||||
<DateView />
|
||||
<Search />
|
||||
<Weather />
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -7,6 +7,13 @@ export type AppDefinition = {
|
||||
shortcut?: string;
|
||||
};
|
||||
|
||||
export type SearchProvider = {
|
||||
name: string;
|
||||
prefix: string;
|
||||
url: string;
|
||||
default?: boolean;
|
||||
};
|
||||
|
||||
export type WeatherData = {
|
||||
id: string;
|
||||
main: string;
|
||||
|
||||
Reference in New Issue
Block a user