initial commit
This commit is contained in:
commit
72fcb3e49f
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
node_modules
|
||||
dist
|
34
README.md
Normal file
34
README.md
Normal file
@ -0,0 +1,34 @@
|
||||
## Usage
|
||||
|
||||
Those templates dependencies are maintained via [pnpm](https://pnpm.io) via `pnpm up -Lri`.
|
||||
|
||||
This is the reason you see a `pnpm-lock.yaml`. That being said, any package manager will work. This file can be safely be removed once you clone a template.
|
||||
|
||||
```bash
|
||||
$ npm install # or pnpm install or yarn install
|
||||
```
|
||||
|
||||
### Learn more on the [Solid Website](https://solidjs.com) and come chat with us on our [Discord](https://discord.com/invite/solidjs)
|
||||
|
||||
## Available Scripts
|
||||
|
||||
In the project directory, you can run:
|
||||
|
||||
### `npm run dev` or `npm start`
|
||||
|
||||
Runs the app in the development mode.<br>
|
||||
Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
|
||||
|
||||
The page will reload if you make edits.<br>
|
||||
|
||||
### `npm run build`
|
||||
|
||||
Builds the app for production to the `dist` folder.<br>
|
||||
It correctly bundles Solid in production mode and optimizes the build for the best performance.
|
||||
|
||||
The build is minified and the filenames include the hashes.<br>
|
||||
Your app is ready to be deployed!
|
||||
|
||||
## Deployment
|
||||
|
||||
You can deploy the `dist` folder to any static host provider (netlify, surge, now, etc.)
|
25
index.html
Normal file
25
index.html
Normal file
@ -0,0 +1,25 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<link rel="shortcut icon" type="image/svg" href="/src/assets/favicon.svg" />
|
||||
<title>Search</title>
|
||||
<style>
|
||||
body {
|
||||
background-color: black;
|
||||
color: white;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
|
||||
<script src="/src/index.tsx" type="module"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
3053
index.json
Normal file
3053
index.json
Normal file
File diff suppressed because one or more lines are too long
1957
package-lock.json
generated
Normal file
1957
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
21
package.json
Normal file
21
package.json
Normal file
@ -0,0 +1,21 @@
|
||||
{
|
||||
"name": "vite-template-solid",
|
||||
"version": "0.0.0",
|
||||
"description": "",
|
||||
"scripts": {
|
||||
"start": "vite",
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"serve": "vite preview"
|
||||
},
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"solid-devtools": "^0.27.3",
|
||||
"typescript": "^5.1.3",
|
||||
"vite": "^4.3.9",
|
||||
"vite-plugin-solid": "^2.7.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"solid-js": "^1.7.6"
|
||||
}
|
||||
}
|
1190
pnpm-lock.yaml
Normal file
1190
pnpm-lock.yaml
Normal file
File diff suppressed because it is too large
Load Diff
45
src/App.module.css
Normal file
45
src/App.module.css
Normal file
@ -0,0 +1,45 @@
|
||||
.app {
|
||||
position: relative;
|
||||
--bg: #111111;
|
||||
--element-bg: #333333;
|
||||
|
||||
background-color: var(--bg);
|
||||
min-height: 100vh;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.appContainer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.loading {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.progress {
|
||||
height: 20px;
|
||||
background: red;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.progressOuter {
|
||||
position: relative;
|
||||
height: 20px;
|
||||
background: gray;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.results {
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.result>a {
|
||||
color: #88ccff;
|
||||
}
|
||||
|
||||
.result>a:visited {
|
||||
color: gray;
|
||||
}
|
116
src/App.tsx
Normal file
116
src/App.tsx
Normal file
@ -0,0 +1,116 @@
|
||||
import { createEffect, type Component, Show, createSignal, onMount, on, For } from 'solid-js';
|
||||
import { Icon, SvgIcon } from "@suid/material";
|
||||
|
||||
import styles from './App.module.css';
|
||||
|
||||
import Search from './components/Search';
|
||||
import { render } from 'solid-js/web';
|
||||
|
||||
interface SearchResult {
|
||||
name: string;
|
||||
url: string;
|
||||
}
|
||||
|
||||
interface Index {
|
||||
[key: string]: IndexEntry;
|
||||
}
|
||||
|
||||
interface IndexEntry {
|
||||
url: string;
|
||||
normalizedValue: string;
|
||||
}
|
||||
|
||||
function normalize(text: string): string {
|
||||
return text.toLowerCase().replace(/[\t\r\n ]/g, "");
|
||||
}
|
||||
|
||||
const App: Component = () => {
|
||||
const [loading, setLoading] = createSignal(true);
|
||||
const [progress, setProgress] = createSignal(0);
|
||||
const [index, setIndex] = createSignal({} as Index);
|
||||
const [results, setResults] = createSignal([] as SearchResult[]);
|
||||
const [query, setQuery] = createSignal("");
|
||||
|
||||
createEffect(on([index, query], () => {
|
||||
let results = [] as SearchResult[];
|
||||
|
||||
let idx = index();
|
||||
let normalizedQuery = normalize(query());
|
||||
|
||||
for (let key in idx) {
|
||||
let value = idx[key];
|
||||
if (key.includes(normalizedQuery) || value.normalizedValue.includes(normalizedQuery)) {
|
||||
results.push({
|
||||
name: key,
|
||||
url: value.url
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
setResults(results);
|
||||
}));
|
||||
|
||||
createEffect(async () => {
|
||||
const indexResult = await fetch("index.json");
|
||||
const totalLength = parseInt(indexResult.headers.get("Content-Length") || "-1");
|
||||
const reader = indexResult.body?.getReader();
|
||||
|
||||
if (!reader || totalLength == -1) {
|
||||
// Error msg
|
||||
return;
|
||||
}
|
||||
|
||||
const data: Uint8Array = new Uint8Array(totalLength);
|
||||
let position = 0;
|
||||
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) {
|
||||
setLoading(false);
|
||||
break;
|
||||
}
|
||||
|
||||
data.set(value, position);
|
||||
position += value.length;
|
||||
setProgress(progress() + value.length / totalLength);
|
||||
}
|
||||
|
||||
// Normalize index
|
||||
let rawIndex = JSON.parse(new TextDecoder("utf-8").decode(data));
|
||||
let normalizedIndex = {} as Index;
|
||||
for (let key in rawIndex) {
|
||||
normalizedIndex[normalize(key)] = {
|
||||
url: key,
|
||||
normalizedValue: normalize(rawIndex[key])
|
||||
}
|
||||
}
|
||||
|
||||
setIndex(normalizedIndex);
|
||||
});
|
||||
|
||||
return (
|
||||
<div class={styles.app}>
|
||||
<div class={styles.appContainer}>
|
||||
<h2>Search for something</h2>
|
||||
<Search placeholder='Search' onChange={setQuery} doneTypingTimeout={200} />
|
||||
|
||||
<Show when={loading()}>
|
||||
<div class={styles.loading}>
|
||||
<span>Loading search index...</span>
|
||||
<div class={styles.progressOuter}>
|
||||
<div class={styles.progress} style={{ width: (progress() * 100) + "%" }}></div>
|
||||
</div>
|
||||
</div>
|
||||
</Show>
|
||||
|
||||
<Show when={!loading()}>
|
||||
<ul class={styles.results}>
|
||||
<For each={results()}>{item => <li class={styles.result}><a href={item.url} target='_blank'>{item.name}</a></li>}</For>
|
||||
</ul>
|
||||
</Show>
|
||||
</div>
|
||||
</div >
|
||||
);
|
||||
};
|
||||
|
||||
export default App;
|
1
src/assets/favicon.svg
Normal file
1
src/assets/favicon.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#FFFFFF"><path d="M9.5,3A6.5,6.5 0 0,1 16,9.5C16,11.11 15.41,12.59 14.44,13.73L14.71,14H15.5L20.5,19L19,20.5L14,15.5V14.71L13.73,14.44C12.59,15.41 11.11,16 9.5,16A6.5,6.5 0 0,1 3,9.5A6.5,6.5 0 0,1 9.5,3M9.5,5C7,5 5,7 5,9.5C5,12 7,14 9.5,14C12,14 14,12 14,9.5C14,7 12,5 9.5,5Z" /></svg>
|
After Width: | Height: | Size: 352 B |
49
src/components/Search.module.css
Normal file
49
src/components/Search.module.css
Normal file
@ -0,0 +1,49 @@
|
||||
.searchLabel {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.search {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
--radius: 5px;
|
||||
}
|
||||
|
||||
.searchBox {
|
||||
border: none;
|
||||
outline: none;
|
||||
background-color: var(--element-bg);
|
||||
color: white;
|
||||
padding: 8px;
|
||||
border-radius: var(--radius);
|
||||
height: 25px;
|
||||
}
|
||||
|
||||
.searchBox[data-suggestions]:focus {
|
||||
border-radius: var(--radius) var(--radius) 0 0;
|
||||
}
|
||||
|
||||
.searchSuggestions {
|
||||
background-color: var(--element-bg);
|
||||
margin: 0;
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
border-radius: 0 0 var(--radius) var(--radius);
|
||||
}
|
||||
|
||||
.searchSuggestions>li {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 25px;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.searchSuggestions>li:hover {
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
.searchSuggestions:empty::after {
|
||||
content: "No suggestions";
|
||||
color: gray;
|
||||
font-style: italic;
|
||||
}
|
62
src/components/Search.tsx
Normal file
62
src/components/Search.tsx
Normal file
@ -0,0 +1,62 @@
|
||||
import { createSignal, type Component, JSX, For, createEffect, on } from "solid-js";
|
||||
|
||||
import styles from './Search.module.css'
|
||||
import { render } from "solid-js/web";
|
||||
|
||||
interface SearchProps {
|
||||
placeholder: string;
|
||||
onChange?: (value: string) => void;
|
||||
//style?: string | JSX.CSSProperties;
|
||||
suggestions?: string[];
|
||||
doneTypingTimeout?: number;
|
||||
};
|
||||
|
||||
const Search: Component<SearchProps> = (props: SearchProps) => {
|
||||
let [suggestions, setSuggestions] = createSignal(props.suggestions);
|
||||
let [suggestionsVisible, setSuggestionsVisible] = createSignal(false);
|
||||
let [query, setQuery] = createSignal("");
|
||||
|
||||
const doneTypingTimeout = props.doneTypingTimeout || 0;
|
||||
let doneTimeout = 0;
|
||||
|
||||
const onFocus: JSX.EventHandler<HTMLInputElement, FocusEvent> = e => {
|
||||
if (props.suggestions) setSuggestionsVisible(true);
|
||||
};
|
||||
|
||||
const onBlur: JSX.EventHandler<HTMLInputElement, FocusEvent> = e => {
|
||||
if (props.suggestions) setSuggestionsVisible(false);
|
||||
};
|
||||
|
||||
const submitValue = (query: string) => {
|
||||
console.log("change", query);
|
||||
props.onChange?.(query);
|
||||
|
||||
if (props.suggestions) {
|
||||
let newSuggestions = props.suggestions.filter(v => v.toLowerCase().includes(query.toLowerCase()));
|
||||
setSuggestions(newSuggestions);
|
||||
}
|
||||
};
|
||||
|
||||
createEffect(on(query, query => {
|
||||
if (doneTypingTimeout > 0) {
|
||||
clearTimeout(doneTimeout);
|
||||
doneTimeout = setTimeout(() => submitValue(query), doneTypingTimeout);
|
||||
return;
|
||||
}
|
||||
|
||||
submitValue(query);
|
||||
}));
|
||||
|
||||
return (
|
||||
<div class={styles.search}>
|
||||
<input class={styles.searchBox} placeholder={props.placeholder} data-suggestions={props.suggestions ? true : undefined} onInput={e => setQuery(e.currentTarget.value)} onFocus={onFocus} onBlur={onBlur} value={query()}></input>
|
||||
<ul class={styles.searchSuggestions} style={{ display: (suggestionsVisible() ? "unset" : "none") }}>
|
||||
<For each={suggestions()}>{item => <li onMouseDown={() => {
|
||||
setQuery(item);
|
||||
}}>{item}</li>}</For>
|
||||
</ul>
|
||||
</div >
|
||||
);
|
||||
};
|
||||
|
||||
export default Search;
|
13
src/index.css
Normal file
13
src/index.css
Normal file
@ -0,0 +1,13 @@
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
||||
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
||||
sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
||||
monospace;
|
||||
}
|
15
src/index.tsx
Normal file
15
src/index.tsx
Normal file
@ -0,0 +1,15 @@
|
||||
/* @refresh reload */
|
||||
import { render } from 'solid-js/web';
|
||||
|
||||
import './index.css';
|
||||
import App from './App';
|
||||
|
||||
const root = document.getElementById('root');
|
||||
|
||||
if (import.meta.env.DEV && !(root instanceof HTMLElement)) {
|
||||
throw new Error(
|
||||
'Root element not found. Did you forget to add it to your index.html? Or maybe the id attribute got misspelled?',
|
||||
);
|
||||
}
|
||||
|
||||
render(() => <App />, root!);
|
1
src/logo.svg
Normal file
1
src/logo.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 166 155.3"><path d="M163 35S110-4 69 5l-3 1c-6 2-11 5-14 9l-2 3-15 26 26 5c11 7 25 10 38 7l46 9 18-30z" fill="#76b3e1"/><linearGradient id="a" gradientUnits="userSpaceOnUse" x1="27.5" y1="3" x2="152" y2="63.5"><stop offset=".1" stop-color="#76b3e1"/><stop offset=".3" stop-color="#dcf2fd"/><stop offset="1" stop-color="#76b3e1"/></linearGradient><path d="M163 35S110-4 69 5l-3 1c-6 2-11 5-14 9l-2 3-15 26 26 5c11 7 25 10 38 7l46 9 18-30z" opacity=".3" fill="url(#a)"/><path d="M52 35l-4 1c-17 5-22 21-13 35 10 13 31 20 48 15l62-21S92 26 52 35z" fill="#518ac8"/><linearGradient id="b" gradientUnits="userSpaceOnUse" x1="95.8" y1="32.6" x2="74" y2="105.2"><stop offset="0" stop-color="#76b3e1"/><stop offset=".5" stop-color="#4377bb"/><stop offset="1" stop-color="#1f3b77"/></linearGradient><path d="M52 35l-4 1c-17 5-22 21-13 35 10 13 31 20 48 15l62-21S92 26 52 35z" opacity=".3" fill="url(#b)"/><linearGradient id="c" gradientUnits="userSpaceOnUse" x1="18.4" y1="64.2" x2="144.3" y2="149.8"><stop offset="0" stop-color="#315aa9"/><stop offset=".5" stop-color="#518ac8"/><stop offset="1" stop-color="#315aa9"/></linearGradient><path d="M134 80a45 45 0 00-48-15L24 85 4 120l112 19 20-36c4-7 3-15-2-23z" fill="url(#c)"/><linearGradient id="d" gradientUnits="userSpaceOnUse" x1="75.2" y1="74.5" x2="24.4" y2="260.8"><stop offset="0" stop-color="#4377bb"/><stop offset=".5" stop-color="#1a336b"/><stop offset="1" stop-color="#1a336b"/></linearGradient><path d="M114 115a45 45 0 00-48-15L4 120s53 40 94 30l3-1c17-5 23-21 13-34z" fill="url(#d)"/></svg>
|
After Width: | Height: | Size: 1.6 KiB |
15
tsconfig.json
Normal file
15
tsconfig.json
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"strict": true,
|
||||
"target": "ESNext",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "node",
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"esModuleInterop": true,
|
||||
"jsx": "preserve",
|
||||
"jsxImportSource": "solid-js",
|
||||
"types": ["vite/client"],
|
||||
"noEmit": true,
|
||||
"isolatedModules": true
|
||||
}
|
||||
}
|
21
vite.config.ts
Normal file
21
vite.config.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import { defineConfig } from 'vite';
|
||||
import solidPlugin from 'vite-plugin-solid';
|
||||
// import devtools from 'solid-devtools/vite';
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
/*
|
||||
Uncomment the following line to enable solid-devtools.
|
||||
For more info see https://github.com/thetarnav/solid-devtools/tree/main/packages/extension#readme
|
||||
*/
|
||||
// devtools(),
|
||||
solidPlugin(),
|
||||
],
|
||||
server: {
|
||||
port: 3000,
|
||||
},
|
||||
build: {
|
||||
target: 'esnext',
|
||||
},
|
||||
base: './'
|
||||
});
|
Loading…
Reference in New Issue
Block a user