Compare commits
21 Commits
0427300abe
...
main
Author | SHA1 | Date | |
---|---|---|---|
5032672341 | |||
eec2d36526 | |||
3f3c0d72f8 | |||
f6b6639905 | |||
3781ae935d | |||
ede223d9d5 | |||
dc1fa6d08a | |||
7df4b38d09 | |||
7644c2c810 | |||
b5e740a5c2 | |||
48a526f404 | |||
94a7880db3 | |||
6f336b18d3 | |||
1451ab0ef1 | |||
b35f479def | |||
740ef0f748 | |||
5d6e12c308 | |||
c75f426065 | |||
69239287b2 | |||
8f77173b8b | |||
cdcd418239 |
26
.env.example
Normal file
26
.env.example
Normal file
@ -0,0 +1,26 @@
|
||||
# All environment variables must be prefixed with VITE_ to be seen by client.
|
||||
|
||||
# Self-hosted Services URLs. Do not include the trailing slash.
|
||||
VITE_BANG_URL="https://bang."
|
||||
VITE_GITEA_URL="https://git."
|
||||
VITE_SEARXNG_URL="https://search."
|
||||
VITE_OPENWEBUI_URL="https://chat."
|
||||
VITE_NEXTCLOUD_URL="https://apps.cloud."
|
||||
VITE_PLEX_URL="https://plex."
|
||||
VITE_OVERSEERR_URL="https://overseerr."
|
||||
VITE_SONARR_URL="https://sonarr."
|
||||
VITE_RADARR_URL="https://radarr."
|
||||
VITE_LIDARR_URL="https://lidarr."
|
||||
|
||||
# Set default bang here. For example "g" would be Google.
|
||||
# Google is the default if you don't set this.
|
||||
# 's' is for your searxng instance, replacing startpage
|
||||
VITE_DEFAULT_BANG="s"
|
||||
|
||||
# SPECIFIC MODELS FOR OPENWEBUI
|
||||
# Local
|
||||
VITE_LLAMA_MODEL="llama3.1" # !llama
|
||||
VITE_DEEPSEEK_MODEL="deepseek-r1:8b" # !r1
|
||||
# Remote
|
||||
VITE_OPENAI_MODEL="gpt-4o-mini" # !openai
|
||||
VITE_CLAUDE_MODEL="anthropic/claude-3.5-sonnet" # !claude
|
8
.gitignore
vendored
8
.gitignore
vendored
@ -6,6 +6,7 @@ yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
public/opensearch.xml
|
||||
|
||||
node_modules
|
||||
dist
|
||||
@ -22,3 +23,10 @@ dist-ssr
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
# ENV
|
||||
docker/.env
|
||||
!docker/.env.example
|
||||
.env
|
||||
.env*
|
||||
!.env.example
|
||||
|
41
README.md
41
README.md
@ -1,14 +1,43 @@
|
||||
# Bang
|
||||
# 💣Bang
|
||||
|
||||
*This is a fork of [unduck](https://unduck.link) by [Theo](https://github.com/t3dotgg/unduck).*
|
||||
|
||||
## Why fork?
|
||||
|
||||
This fork allows anyone to easily self host this service & add bangs for their own self-hosted websites. Bang comes pre-configured to work with
|
||||
|
||||
- Searxng *!s*
|
||||
- Gitea *!tea*
|
||||
- OpenWebUI *!ai*
|
||||
- Plex *!plex*
|
||||
- Sonarr *!tv*
|
||||
- Radarr *!mv*
|
||||
- Lidarr *!mp3*
|
||||
- ~Overseerr *!ov*~ *Overseerr doesn't seem to work correctly*
|
||||
|
||||
*Note: We have replaced some of the default bangs from duckduckgo with our own bangs in order to have more simple bangs for the self-hosted websites.*
|
||||
|
||||
All you have to do is fill out the environment variables in the .env.example files located in the root directory & the docker directory & rename them to .env.
|
||||
|
||||
You can also easily add your own bangs by adding a new entry in the bangs.ts file.
|
||||
|
||||
## How to self host
|
||||
|
||||
1. Clone the repo & fill out the .env.example files in the root directory & the docker directory & rename them to .env.
|
||||
- *Note: Our docker compose assumes you plan to select an external network.*
|
||||
2. Run the bash script `host-bang` in the docker directory with the root directory of the project as an argument.
|
||||
- *Note: You can also simply run it from the root or docker directory without an argument.*
|
||||
|
||||
### How to update the website
|
||||
|
||||
1. Run the bash script `update-bang` in the docker directory with the root directory of the project as an argument.
|
||||
- *Note: You can also simply run it from the root or docker directory without an argument.*
|
||||
|
||||
## Theo's words
|
||||
|
||||
DuckDuckGo's bang redirects are too slow. Add the following URL as a custom search engine to your browser. Enables all of DuckDuckGo's bangs to work, but much faster.
|
||||
|
||||
```
|
||||
https://bang.gbrown.org?q=%s
|
||||
```
|
||||
|
||||
## How is it that much faster?
|
||||
### How is it that much faster?
|
||||
|
||||
DuckDuckGo does their redirects server side. Their DNS is...not always great. Result is that it often takes ages.
|
||||
|
||||
|
3
docker/.env.example
Normal file
3
docker/.env.example
Normal file
@ -0,0 +1,3 @@
|
||||
PORT=5000
|
||||
DOMAIN=bang.mydomain.com
|
||||
NETWORK=bridge
|
@ -13,6 +13,7 @@ RUN pnpm install
|
||||
COPY . .
|
||||
|
||||
# Build the project
|
||||
RUN pnpm run prebuild
|
||||
RUN pnpm run build
|
||||
|
||||
# Stage 2: Serve the app using the same version of Node
|
||||
|
@ -1,23 +1,21 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
bang-web-server:
|
||||
build:
|
||||
context: ../ # point to the parent directory where package.json and source code reside
|
||||
dockerfile: docker/Dockerfile # specific path to the Dockerfile
|
||||
context: ../
|
||||
dockerfile: docker/Dockerfile
|
||||
container_name: bang
|
||||
hostname: bang.gib
|
||||
domainname: bang.gbrown.org
|
||||
hostname: bang
|
||||
networks:
|
||||
- node_apps
|
||||
ports:
|
||||
- "5000:5000"
|
||||
- nginx-bridge
|
||||
#ports:
|
||||
#- 5000:5000
|
||||
tty: true
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- ../:/app # mount the parent directory to /app in the container
|
||||
- ../:/app
|
||||
command: serve -s /app/dist -l 5000
|
||||
|
||||
networks:
|
||||
node_apps:
|
||||
nginx-bridge:
|
||||
external: true
|
||||
|
51
docker/host-bang
Executable file
51
docker/host-bang
Executable file
@ -0,0 +1,51 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Function to check if we're in the correct directory
|
||||
check_directory() {
|
||||
if [ -d "docker" ] && [ -f "package.json" ]; then
|
||||
return 0 # We're in the root directory
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
# Initialize root_dir
|
||||
root_dir=""
|
||||
|
||||
# Check if argument is provided
|
||||
if [ $# -eq 1 ]; then
|
||||
# Use provided path
|
||||
if [ -d "$1" ]; then
|
||||
root_dir="$1"
|
||||
cd "$root_dir" || exit 1
|
||||
else
|
||||
echo "Error: Provided directory does not exist"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
# No argument provided, try to determine location
|
||||
current_dir=$(basename "$(pwd)")
|
||||
|
||||
if [ "$current_dir" = "docker" ]; then
|
||||
cd .. || exit 1
|
||||
elif [ "$current_dir" != "Bang" ]; then
|
||||
echo "Error: Not in the correct directory and no valid path provided"
|
||||
echo "Please either:"
|
||||
echo "1. Run this script from the Bang root directory"
|
||||
echo "2. Run this script from the docker directory"
|
||||
echo "3. Provide the path to the Bang root directory as an argument"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Verify we're in the correct directory
|
||||
if ! check_directory; then
|
||||
echo "Error: Not in the correct directory structure"
|
||||
echo "Make sure you're in a directory with 'docker' folder and package.json"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
pnpm install
|
||||
pnpm build
|
||||
cd docker || exit 1
|
||||
sudo docker compose up -d
|
||||
cd ..
|
52
docker/update-bang
Executable file
52
docker/update-bang
Executable file
@ -0,0 +1,52 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Function to check if we're in the correct directory
|
||||
check_directory() {
|
||||
if [ -d "docker" ] && [ -f "package.json" ]; then
|
||||
return 0 # We're in the root directory
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
# Initialize root_dir
|
||||
root_dir=""
|
||||
|
||||
# Check if argument is provided
|
||||
if [ $# -eq 1 ]; then
|
||||
# Use provided path
|
||||
if [ -d "$1" ]; then
|
||||
root_dir="$1"
|
||||
cd "$root_dir" || exit 1
|
||||
else
|
||||
echo "Error: Provided directory does not exist"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
# No argument provided, try to determine location
|
||||
current_dir=$(basename "$(pwd)")
|
||||
|
||||
if [ "$current_dir" = "docker" ]; then
|
||||
cd .. || exit 1
|
||||
elif [ "$current_dir" != "Bang" ]; then
|
||||
echo "Error: Not in the correct directory and no valid path provided"
|
||||
echo "Please either:"
|
||||
echo "1. Run this script from the Bang root directory"
|
||||
echo "2. Run this script from the docker directory"
|
||||
echo "3. Provide the path to the Bang root directory as an argument"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Verify we're in the correct directory
|
||||
if ! check_directory; then
|
||||
echo "Error: Not in the correct directory structure"
|
||||
echo "Make sure you're in a directory with 'docker' folder and package.json"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
git pull
|
||||
pnpm build
|
||||
cd docker || exit 1
|
||||
sudo docker compose down
|
||||
sudo docker compose up -d
|
||||
cd ..
|
@ -24,6 +24,12 @@
|
||||
media="print"
|
||||
onload="this.media='all'"
|
||||
/>
|
||||
<link
|
||||
rel="search"
|
||||
type="application/opensearchdescription+xml"
|
||||
title="Bang!"
|
||||
href="/opensearch.xml"
|
||||
/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Bang!</title>
|
||||
<meta
|
||||
|
14
package.json
14
package.json
@ -1,21 +1,25 @@
|
||||
{
|
||||
"name": "unduck",
|
||||
"name": "bang",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"generate-opensearch": "ts-node scripts/generateOpenSearch.ts",
|
||||
"prebuild": "pnpm generate-opensearch",
|
||||
"build": "tsc && vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "~5.7.2",
|
||||
"vite": "^6.1.0"
|
||||
"@types/node": "^22.15.17",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "~5.7.3",
|
||||
"vite": "^6.3.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"vite-plugin-pwa": "^0.21.1"
|
||||
"vite-plugin-pwa": "^0.21.2"
|
||||
},
|
||||
"packageManager": "pnpm@10.5.2+sha512.da9dc28cd3ff40d0592188235ab25d3202add8a207afbedc682220e4a0029ffbff4562102b9e6e46b4e3f9e8bd53e6d05de48544b0c57d4b0179e22c76d1199b",
|
||||
"packageManager": "pnpm@10.10.0+sha512.d615db246fe70f25dcfea6d8d73dee782ce23e2245e3c4f6f888249fb568149318637dca73c2c5c8ef2a4ca0d5657fb9567188bfab47f566d1ee6ce987815c39",
|
||||
"pnpm": {
|
||||
"onlyBuiltDependencies": [
|
||||
"esbuild"
|
||||
|
1904
pnpm-lock.yaml
generated
1904
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
10
public/opensearch.xml
Normal file
10
public/opensearch.xml
Normal file
@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/">
|
||||
<ShortName>Bang!</ShortName>
|
||||
<Description>A better default search engine with bangs</Description>
|
||||
<InputEncoding>UTF-8</InputEncoding>
|
||||
<Image width="16" height="16" type="image/svg+xml">https://bang.gbrown.org/bang.svg</Image>
|
||||
<Url type="text/html" method="get" template="https://bang.gbrown.org?q={searchTerms}"/>
|
||||
<Url type="application/opensearchdescription+xml" rel="self" template="https://bang.gbrown.org/opensearch.xml"/>
|
||||
<moz:SearchForm xmlns:moz="http://www.mozilla.org/2006/browser/search/">https://bang.gbrown.org</moz:SearchForm>
|
||||
</OpenSearchDescription>
|
@ -1,5 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="1200pt" height="1200pt" version="1.1" viewBox="0 0 1200 1200" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="1200" height="1200" fill="black"/>
|
||||
<path fill="white" d="m980.48 905.76-198.84-199.08c89.203-125.34 77.953-300.14-34.406-412.64-125.26-125.44-328.03-125.44-453.32 0-125.26 125.44-125.26 328.45 0 453.84 112.36 112.5 286.97 123.74 412.18 34.406l198.84 199.08c53.25 53.297 132.05-19.078 75.562-75.609zm-610.87-233.58c-83.391-83.531-83.391-218.86 0-302.39s218.58-83.531 302.02 0c83.391 83.531 83.391 218.86 0 302.39-83.438 83.484-218.63 83.484-302.02 0z"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 624 B |
98
scripts/files_to_clipboard
Executable file
98
scripts/files_to_clipboard
Executable file
@ -0,0 +1,98 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import os
|
||||
import sys
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
import pyperclip
|
||||
import questionary
|
||||
|
||||
# List of directories to exclude
|
||||
EXCLUDED_DIRS = {'node_modules', '.next', '.venv', '.git', '__pycache__', '.idea', '.vscode', 'ui'}
|
||||
|
||||
def collect_files(project_path):
|
||||
"""
|
||||
Collects files from the project directory, excluding specified directories and filtering by extensions.
|
||||
Returns a list of file paths relative to the project directory.
|
||||
"""
|
||||
collected_files = []
|
||||
|
||||
for root, dirs, files in os.walk(project_path):
|
||||
# Exclude specified directories
|
||||
dirs[:] = [d for d in dirs if d not in EXCLUDED_DIRS]
|
||||
|
||||
for file in files:
|
||||
file_path = Path(root) / file
|
||||
relative_path = file_path.relative_to(project_path)
|
||||
collected_files.append(relative_path)
|
||||
|
||||
return collected_files
|
||||
|
||||
def main():
|
||||
# Parse command-line arguments
|
||||
parser = argparse.ArgumentParser(description='Generate Markdown from selected files.')
|
||||
parser.add_argument('path', nargs='?', default='.', help='Path to the project directory')
|
||||
args = parser.parse_args()
|
||||
|
||||
project_path = Path(args.path).resolve()
|
||||
if not project_path.is_dir():
|
||||
print(f"Error: '{project_path}' is not a directory.")
|
||||
sys.exit(1)
|
||||
|
||||
# Collect files from the project directory
|
||||
file_list = collect_files(project_path)
|
||||
|
||||
if not file_list:
|
||||
print("No files found in the project directory with the specified extensions.")
|
||||
sys.exit(1)
|
||||
|
||||
# Sort file_list for better organization
|
||||
file_list.sort()
|
||||
|
||||
# Interactive file selection using questionary
|
||||
print("\nSelect the files you want to include:")
|
||||
selected_files = questionary.checkbox(
|
||||
"Press space to select files, and Enter when you're done:",
|
||||
choices=[str(f) for f in file_list]
|
||||
).ask()
|
||||
|
||||
if not selected_files:
|
||||
print("No files selected.")
|
||||
sys.exit(1)
|
||||
|
||||
# Generate markdown
|
||||
markdown_lines = []
|
||||
markdown_lines.append('')
|
||||
|
||||
for selected_file in selected_files:
|
||||
file_path = project_path / selected_file
|
||||
try:
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
# Determine the language for code block from file extension
|
||||
language = file_path.suffix.lstrip('.')
|
||||
markdown_lines.append(f'{selected_file}')
|
||||
markdown_lines.append(f'```{language}')
|
||||
markdown_lines.append(content)
|
||||
markdown_lines.append('```')
|
||||
markdown_lines.append('')
|
||||
except Exception as e:
|
||||
print(f"Error reading file {selected_file}: {e}")
|
||||
|
||||
# Copy markdown content to clipboard
|
||||
markdown_text = '\n'.join(markdown_lines)
|
||||
pyperclip.copy(markdown_text)
|
||||
print("Markdown content has been copied to the clipboard.")
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Check if required libraries are installed
|
||||
try:
|
||||
import questionary
|
||||
import pyperclip
|
||||
except ImportError as e:
|
||||
missing_module = e.name
|
||||
print(f"Error: Missing required module '{missing_module}'.")
|
||||
print(f"Please install it by running: pip install {missing_module}")
|
||||
sys.exit(1)
|
||||
|
||||
main()
|
33
scripts/generateOpenSearch.ts
Normal file
33
scripts/generateOpenSearch.ts
Normal file
@ -0,0 +1,33 @@
|
||||
import * as fs from 'node:fs';
|
||||
import * as path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
// Get the current file's directory
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
// Get the Bang URL from environment or use a default for local development
|
||||
const bangUrl = process.env.VITE_BANG_URL || 'https://bang.gbrown.org';
|
||||
|
||||
// Create the OpenSearch XML content
|
||||
const openSearchXml = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/">
|
||||
<ShortName>Bang!</ShortName>
|
||||
<Description>A better default search engine with bangs</Description>
|
||||
<InputEncoding>UTF-8</InputEncoding>
|
||||
<Image width="16" height="16" type="image/svg+xml">${bangUrl}/bang.svg</Image>
|
||||
<Url type="text/html" method="get" template="${bangUrl}?q={searchTerms}"/>
|
||||
<Url type="application/opensearchdescription+xml" rel="self" template="${bangUrl}/opensearch.xml"/>
|
||||
<moz:SearchForm xmlns:moz="http://www.mozilla.org/2006/browser/search/">${bangUrl}</moz:SearchForm>
|
||||
</OpenSearchDescription>`;
|
||||
|
||||
// Ensure the public directory exists
|
||||
const publicDir = path.resolve(__dirname, '../public');
|
||||
if (!fs.existsSync(publicDir)) {
|
||||
fs.mkdirSync(publicDir, { recursive: true });
|
||||
}
|
||||
|
||||
// Write the OpenSearch XML file
|
||||
fs.writeFileSync(path.join(publicDir, 'opensearch.xml'), openSearchXml);
|
||||
|
||||
console.log('OpenSearch XML file generated successfully!');
|
188
src/bang.ts
188
src/bang.ts
@ -1,113 +1,190 @@
|
||||
// This file was (mostly) ripped from https://duckduckgo.com/bang.js
|
||||
|
||||
const SEARXNG_URL = import.meta.env.VITE_SEARXNG_URL as string;
|
||||
const GITEA_URL = import.meta.env.VITE_GITEA_URL as string;
|
||||
const OPENWEBUI_URL = import.meta.env.VITE_OPENWEBUI_URL as string;
|
||||
const PLEX_URL = import.meta.env.VITE_PLEX_URL as string;
|
||||
const OVERSEERR_URL = import.meta.env.VITE_OVERSEERR_URL as string;
|
||||
const SONARR_URL = import.meta.env.VITE_SONARR_URL as string;
|
||||
const RADARR_URL = import.meta.env.VITE_RADARR_URL as string;
|
||||
const LIDARR_URL = import.meta.env.VITE_LIDARR_URL as string;
|
||||
|
||||
const LLAMA_MODEL = import.meta.env.VITE_LLAMA_MODEL.replace(":", "%3A") as string;
|
||||
const DEEPSEEK_MODEL = import.meta.env.VITE_DEEPSEEK_MODEL.replace(":", "%3A") as string;
|
||||
const OPENAI_MODEL = import.meta.env.VITE_OPENAI_MODEL as string;
|
||||
const CLAUDE_MODEL = import.meta.env.VITE_CLAUDE_MODEL.replace("/", "%2F") as string;
|
||||
|
||||
export const bangs = [
|
||||
{
|
||||
c: "Online Services",
|
||||
d: "search.gibbyb.com",
|
||||
d: SEARXNG_URL,
|
||||
r: 0,
|
||||
s: "GibbyB",
|
||||
s: "Searxng",
|
||||
sc: "Search",
|
||||
t: "sg",
|
||||
u: "https://search.gibbyb.com/?q={{{s}}}",
|
||||
t: "s",
|
||||
u: `${SEARXNG_URL}/?q={{{s}}}`,
|
||||
},
|
||||
{
|
||||
c: "Tech",
|
||||
d: "git.gibbyb.com",
|
||||
d: GITEA_URL,
|
||||
r: 0,
|
||||
s: "Gitea",
|
||||
sc: "Cryptocurrency",
|
||||
t: "tea",
|
||||
u: "https://git.gibbyb.com/?repo-search-query={{{s}}}",
|
||||
u: `${GITEA_URL}/?repo-search-query={{{s}}}`,
|
||||
},
|
||||
{
|
||||
c: "Entertainment",
|
||||
d: PLEX_URL,
|
||||
r: 0,
|
||||
s: "Plex",
|
||||
sc: "Movies",
|
||||
t: "plex",
|
||||
u: `${PLEX_URL}/web/index.html#!/search?pivot=top&query={{{s}}}`,
|
||||
},
|
||||
{
|
||||
c: "Entertainment",
|
||||
d: OVERSEERR_URL,
|
||||
r: 0,
|
||||
s: "Overseerr",
|
||||
sc: "Movies",
|
||||
t: "ov",
|
||||
u: `${OVERSEERR_URL}/search?query={{{s}}}`,
|
||||
},
|
||||
{
|
||||
c: "Entertainment",
|
||||
d: LIDARR_URL,
|
||||
r: 0,
|
||||
s: "Lidarr",
|
||||
sc: "Music",
|
||||
t: "mp3",
|
||||
u: `${LIDARR_URL}/add/new?term={{{s}}}`,
|
||||
},
|
||||
{
|
||||
c: "Entertainment",
|
||||
d: SONARR_URL,
|
||||
r: 0,
|
||||
s: "Sonarr",
|
||||
sc: "TV",
|
||||
t: "tv",
|
||||
u: `${SONARR_URL}/add/new?term={{{s}}}`,
|
||||
},
|
||||
{
|
||||
c: "Entertainment",
|
||||
d: RADARR_URL,
|
||||
r: 0,
|
||||
s: "Radarr",
|
||||
sc: "Movies",
|
||||
t: "mv",
|
||||
u: `${RADARR_URL}/add/new?term={{{s}}}`,
|
||||
},
|
||||
{
|
||||
c: "AI",
|
||||
d: "chat.gibbyb.com",
|
||||
d: OPENWEBUI_URL,
|
||||
r: 0,
|
||||
s: "GibbyB",
|
||||
s: "OpenWebUI",
|
||||
sc: "AI",
|
||||
t: "ai",
|
||||
u: "https://chat.gibbyb.com/?q={{{s}}}",
|
||||
u: `${OPENWEBUI_URL}/?q={{{s}}}`,
|
||||
},
|
||||
{
|
||||
c: "AI",
|
||||
d: "chat.gibbyb.com",
|
||||
d: OPENWEBUI_URL,
|
||||
r: 0,
|
||||
s: "GibbyB",
|
||||
s: "OpenWebUI",
|
||||
sc: "AI",
|
||||
t: "claude",
|
||||
u: "https://chat.gibbyb.com/?models=anthropic%2Fclaude-3.5-sonnet&q={{{s}}}",
|
||||
t: "c",
|
||||
u: `${OPENWEBUI_URL}/?q={{{s}}}`,
|
||||
},
|
||||
{
|
||||
c: "AI",
|
||||
d: "chat.gibbyb.com",
|
||||
d: OPENWEBUI_URL,
|
||||
r: 0,
|
||||
s: "GibbyB",
|
||||
s: "OpenWebUI",
|
||||
sc: "AI",
|
||||
t: "llama",
|
||||
u: "https://chat.gibbyb.com/?models=llama3.1&q={{{s}}}",
|
||||
u: `${OPENWEBUI_URL}/?models=${LLAMA_MODEL}&q={{{s}}}`,
|
||||
},
|
||||
{
|
||||
c: "AI",
|
||||
d: "chat.gibbyb.com",
|
||||
d: OPENWEBUI_URL,
|
||||
r: 0,
|
||||
s: "GibbyB",
|
||||
sc: "AI",
|
||||
t: "r17b",
|
||||
u: "https://chat.gibbyb.com/?models=deepseek-r1%3A7b&q={{{s}}}",
|
||||
},
|
||||
{
|
||||
c: "AI",
|
||||
d: "chat.gibbyb.com",
|
||||
r: 0,
|
||||
s: "GibbyB",
|
||||
s: "OpenWebUI",
|
||||
sc: "AI",
|
||||
t: "r1",
|
||||
u: "https://chat.gibbyb.com/?models=deepseek-r1%3A8b&q={{{s}}}",
|
||||
u: `${OPENWEBUI_URL}/?models=${DEEPSEEK_MODEL}&q={{{s}}}`,
|
||||
},
|
||||
{
|
||||
c: "AI",
|
||||
d: "chat.gibbyb.com",
|
||||
d: OPENWEBUI_URL,
|
||||
r: 0,
|
||||
s: "GibbyB",
|
||||
s: "OpenWebUI",
|
||||
sc: "AI",
|
||||
t: "r132b",
|
||||
u: "https://chat.gibbyb.com/?models=deepseek-r1%3A32b&q={{{s}}}",
|
||||
t: "r1-7b",
|
||||
u: `${OPENWEBUI_URL}/?models=deepseek-r1%3A7b&q={{{s}}}`,
|
||||
},
|
||||
{
|
||||
c: "AI",
|
||||
d: "chat.gibbyb.com",
|
||||
d: OPENWEBUI_URL,
|
||||
r: 0,
|
||||
s: "GibbyB",
|
||||
s: "OpenWebUI",
|
||||
sc: "AI",
|
||||
t: "r1-32b",
|
||||
u: `${OPENWEBUI_URL}/?models=deepseek-r1%3A32b&q={{{s}}}`,
|
||||
},
|
||||
{
|
||||
c: "AI",
|
||||
d: OPENWEBUI_URL,
|
||||
r: 0,
|
||||
s: "OpenWebUI",
|
||||
sc: "AI",
|
||||
t: "claude",
|
||||
u: `${OPENWEBUI_URL}/?models=${CLAUDE_MODEL}&q={{{s}}}`,
|
||||
},
|
||||
{
|
||||
c: "AI",
|
||||
d: OPENWEBUI_URL,
|
||||
r: 0,
|
||||
s: "OpenWebUI",
|
||||
sc: "AI",
|
||||
t: "openai",
|
||||
u: `${OPENWEBUI_URL}/?models=${OPENAI_MODEL}&q={{{s}}}`,
|
||||
},
|
||||
{
|
||||
c: "AI",
|
||||
d: OPENWEBUI_URL,
|
||||
r: 0,
|
||||
s: "OpenWebUI",
|
||||
sc: "AI",
|
||||
t: "o3-mini",
|
||||
u: "https://chat.gibbyb.com/?models=o3-mini&q={{{s}}}",
|
||||
u: `${OPENWEBUI_URL}/?models=o3-mini&q={{{s}}}`,
|
||||
},
|
||||
{
|
||||
c: "AI",
|
||||
d: "chat.gibbyb.com",
|
||||
d: OPENWEBUI_URL,
|
||||
r: 0,
|
||||
s: "GibbyB",
|
||||
s: "OpenWebUI",
|
||||
sc: "AI",
|
||||
t: "o1",
|
||||
u: "https://chat.gibbyb.com/?models=o1&q={{{s}}}",
|
||||
u: `${OPENWEBUI_URL}/?models=o1&q={{{s}}}`,
|
||||
},
|
||||
{
|
||||
c: "AI",
|
||||
d: "chat.gibbyb.com",
|
||||
d: OPENWEBUI_URL,
|
||||
r: 0,
|
||||
s: "GibbyB",
|
||||
s: "OpenWebUI",
|
||||
sc: "AI",
|
||||
t: "4o-mini",
|
||||
u: "https://chat.gibbyb.com/?models=gpt-4o-mini&q={{{s}}}",
|
||||
u: `${OPENWEBUI_URL}/?models=gpt-4o-mini&q={{{s}}}`,
|
||||
},
|
||||
{
|
||||
c: "AI",
|
||||
d: "chat.gibbyb.com",
|
||||
d: OPENWEBUI_URL,
|
||||
r: 0,
|
||||
s: "GibbyB",
|
||||
s: "OpenWebUI",
|
||||
sc: "AI",
|
||||
t: "4o",
|
||||
u: "https://chat.gibbyb.com/?models=gpt-4o&q={{{s}}}",
|
||||
u: `${OPENWEBUI_URL}/?models=gpt-4o&q={{{s}}}`,
|
||||
},
|
||||
{
|
||||
c: "AI",
|
||||
@ -71396,7 +71473,7 @@ export const bangs = [
|
||||
r: 10,
|
||||
s: "Myvideo",
|
||||
sc: "Video",
|
||||
t: "mv",
|
||||
t: "myvideo",
|
||||
u: "http://www.myvideo.de/Videos_A-Z?searchWord={{{s}}}",
|
||||
},
|
||||
{
|
||||
@ -73509,8 +73586,8 @@ export const bangs = [
|
||||
r: 0,
|
||||
s: "Nextcloud App Store",
|
||||
sc: "Sysadmin",
|
||||
t: "nextcloudapp",
|
||||
u: "https://apps.nextcloud.com/?search={{{s}}}",
|
||||
t: "cloudapp",
|
||||
u: "https://apps.nextcloud.com/search/?q={{{s}}}",
|
||||
},
|
||||
{
|
||||
c: "Entertainment",
|
||||
@ -78835,7 +78912,7 @@ export const bangs = [
|
||||
r: 0,
|
||||
s: "StackOverflow",
|
||||
sc: "Programming",
|
||||
t: "ov",
|
||||
t: "ovfl",
|
||||
u: "http://stackoverflow.com/search?q={{{s}}}",
|
||||
},
|
||||
{
|
||||
@ -98415,7 +98492,7 @@ export const bangs = [
|
||||
r: 12469,
|
||||
s: "startpage.com",
|
||||
sc: "Search",
|
||||
t: "s",
|
||||
t: "startpage",
|
||||
u: "http://startpage.com/do/metasearch.pl?query={{{s}}}",
|
||||
},
|
||||
{
|
||||
@ -106788,15 +106865,6 @@ export const bangs = [
|
||||
t: "tva",
|
||||
u: "https://tweakers.net/aanbod/zoeken/?keyword={{{s}}}",
|
||||
},
|
||||
{
|
||||
c: "Entertainment",
|
||||
d: "www.tv.com",
|
||||
r: 0,
|
||||
s: "TV.com",
|
||||
sc: "TV",
|
||||
t: "tvcom",
|
||||
u: "http://www.tv.com/search?q={{{s}}}",
|
||||
},
|
||||
{
|
||||
c: "Entertainment",
|
||||
d: "www.thetvdb.com",
|
||||
@ -106956,8 +107024,8 @@ export const bangs = [
|
||||
r: 31,
|
||||
s: "tv.com",
|
||||
sc: "TV",
|
||||
t: "tv",
|
||||
u: "http://www.tv.com/search?q= {{{s}}}",
|
||||
t: "tvcom",
|
||||
u: "http://www.tv.com/search?q={{{s}}}",
|
||||
},
|
||||
{
|
||||
c: "Shopping",
|
||||
|
@ -93,17 +93,25 @@ textarea {
|
||||
}
|
||||
|
||||
/* --- Container layout unchanged --- */
|
||||
.url-container {
|
||||
.main-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-top: 16px;
|
||||
justify-content: center;
|
||||
min-height: 100vh;
|
||||
padding: 20px 0px;
|
||||
}
|
||||
.content-container {
|
||||
max-width: 36rem;
|
||||
text-align: center;
|
||||
padding: 0 8px;
|
||||
}
|
||||
.url-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
/* --- URL input element styling adjustments --- */
|
||||
.url-input {
|
||||
@ -117,33 +125,50 @@ textarea {
|
||||
}
|
||||
|
||||
/* --- Copy-button style adjustments --- */
|
||||
.copy-button {
|
||||
padding: 8px;
|
||||
.copy-button, .copy-firefox, .copy-chrome {
|
||||
padding: 6px;
|
||||
color: var(--accent-color); /* Changed: Accent color instead of gray */
|
||||
border-radius: 4px;
|
||||
transition: all 0.2s;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.copy-button:hover {
|
||||
.copy-button:hover, .copy-firefox:hover, .copy-chrome:hover {
|
||||
background: var(--hover-bg-color); /* Changed: Hover bg darker */
|
||||
border-radius: 8px;
|
||||
}
|
||||
.copy-button:active {
|
||||
.copy-button:active, .copy-firefox:active, .copy-chrome:active {
|
||||
background: var(--active-bg-color); /* Changed: Active state even darker */
|
||||
}
|
||||
.copy-button img {
|
||||
.copy-button img, .copy-firefox img, .copy-chrome img {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
/* Copy feedback unchanged (Green stands out in dark mode as well) */
|
||||
.copy-button.copied {
|
||||
.copy-button.copied, .copy-firefox.copied, .copy-chrome.copied {
|
||||
background: var(--success-color);
|
||||
color: white; /* Ensures contrast */
|
||||
}
|
||||
|
||||
.copy-textbox, .firefox-textbox, .chrome-textbox {
|
||||
padding: 6px 10px;
|
||||
border: 1px solid var(--input-border-color);
|
||||
border-radius: 4px;
|
||||
background: var(--input-bg-color);
|
||||
color: var(--text-color);
|
||||
font-size: 14px;
|
||||
width: 220px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.copy-textbox:focus, .firefox-textbox:focus, .chrome-textbox:focus {
|
||||
outline: none;
|
||||
border-color: var(--accent-color);
|
||||
}
|
||||
|
||||
.settings-title {
|
||||
margin-top: 36px;
|
||||
margin-bottom: 24px;
|
||||
margin-bottom: 16px;
|
||||
font-size: 20px;
|
||||
}
|
||||
.settings-links-container {
|
||||
@ -151,5 +176,49 @@ textarea {
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 32px;
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.browser-link-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
.browser-instruction {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 14px;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
|
||||
.form-table {
|
||||
font-size: 14px;
|
||||
margin: 16px auto;
|
||||
text-align: left;
|
||||
}
|
||||
.form-table td {
|
||||
padding: 8px 6px;
|
||||
border: 1px solid var(--input-border-color);
|
||||
}
|
||||
.form-table td:first-child {
|
||||
background-color: var(--input-bg-color);
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
/* --- Mobile --- */
|
||||
@media (max-width: 500px) {
|
||||
.settings-links-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 32px;
|
||||
}
|
||||
.main-container {
|
||||
justify-content: flex-start;
|
||||
padding: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
|
98
src/main.ts
98
src/main.ts
@ -4,7 +4,7 @@ import "./global.css";
|
||||
function noSearchDefaultPageRender() {
|
||||
const app = document.querySelector<HTMLDivElement>("#app")!;
|
||||
app.innerHTML = `
|
||||
<div style="display: flex; flex-direction: column; align-items: center; justify-content: center; height: 100vh;">
|
||||
<main class="main-container">
|
||||
<div class="content-container">
|
||||
<h1 class="bang-title">💣 Bang!</h1>
|
||||
<p>
|
||||
@ -18,53 +18,103 @@ function noSearchDefaultPageRender() {
|
||||
<input
|
||||
type="text"
|
||||
class="url-input"
|
||||
value="https://bang.gbrown.org?q=%s"
|
||||
value="${import.meta.env.VITE_BANG_URL}?q=%s"
|
||||
readonly
|
||||
/>
|
||||
<button class="copy-button">
|
||||
<img src="/clipboard.svg" alt="Copy" />
|
||||
</button>
|
||||
</div>
|
||||
<h3 class="settings-title">Search Settings Links</h3>
|
||||
<h3 class="settings-title">How to add Bang! Search Engine</h3>
|
||||
<p style="margin-bottom: 8px; font-size: 14px;">
|
||||
Below are links to the search engine settings in your browser.
|
||||
<br />
|
||||
Copy the URL for the browser engine you are using & paste it into a new tab.
|
||||
<br />
|
||||
From there you should see an option to add a search engine. Copy the link above
|
||||
& fill out the form as follows.
|
||||
</p>
|
||||
<table class="form-table">
|
||||
<tr>
|
||||
<td><b>Name:</b></td>
|
||||
<td>Bang!</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Engine URL:</b></td>
|
||||
<td>${import.meta.env.VITE_BANG_URL}?q=%s</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Alias:</b></td>
|
||||
<td>@bang</td>
|
||||
</tr>
|
||||
</table>
|
||||
<div class="settings-links-container">
|
||||
<h3>
|
||||
<a href="about:preferences#search" target="_blank">
|
||||
<img
|
||||
src="/firefox.svg"
|
||||
alt="Firefox"
|
||||
width="30"
|
||||
/>
|
||||
</a>
|
||||
</h3>
|
||||
<h3>
|
||||
<a href="chrome://settings/searchEngines" target="_blank">
|
||||
<img
|
||||
src="/chrome.svg"
|
||||
alt="Chrome"
|
||||
width="30"
|
||||
/>
|
||||
</a>
|
||||
</h3>
|
||||
<div class="browser-link-container">
|
||||
<img src="/firefox.svg" alt="Firefox" width="24" />
|
||||
<input
|
||||
type="text"
|
||||
readonly value="about:preferences#search"
|
||||
class="firefox-textbox"
|
||||
title="Click to copy Firefox settings URL"
|
||||
/>
|
||||
<button class="copy-firefox">
|
||||
<img src="/clipboard.svg" alt="Copy" />
|
||||
</button>
|
||||
</div>
|
||||
<div class="browser-link-container">
|
||||
<img src="/chrome.svg" alt="Chrome" width="24" />
|
||||
<input
|
||||
type="text"
|
||||
readonly value="chrome://settings/searchEngines"
|
||||
class="chrome-textbox"
|
||||
title="Click to copy Chrome settings URL"
|
||||
/>
|
||||
<button class="copy-chrome">
|
||||
<img src="/clipboard.svg" alt="Copy" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
`;
|
||||
|
||||
const copyButton = app.querySelector<HTMLButtonElement>(".copy-button")!;
|
||||
const copyIcon = copyButton.querySelector("img")!;
|
||||
const urlInput = app.querySelector<HTMLInputElement>(".url-input")!;
|
||||
const copyFirefox = app.querySelector<HTMLInputElement>(".copy-firefox")!;
|
||||
const copyFirefoxIcon = copyFirefox.querySelector("img")!;
|
||||
const firefoxInput = app.querySelector<HTMLInputElement>(".firefox-textbox")!;
|
||||
const copyChrome = app.querySelector<HTMLInputElement>(".copy-chrome")!;
|
||||
const copyChromeIcon = copyChrome.querySelector("img")!;
|
||||
const chromeInput = app.querySelector<HTMLInputElement>(".chrome-textbox")!;
|
||||
|
||||
copyButton.addEventListener("click", async () => {
|
||||
await navigator.clipboard.writeText(urlInput.value);
|
||||
copyIcon.src = "/clipboard-check.svg";
|
||||
|
||||
setTimeout(() => {
|
||||
copyIcon.src = "/clipboard.svg";
|
||||
}, 2000);
|
||||
});
|
||||
|
||||
copyFirefox.addEventListener("click", async () => {
|
||||
await navigator.clipboard.writeText(firefoxInput.value);
|
||||
copyFirefoxIcon.src = "/clipboard-check.svg";
|
||||
setTimeout(() => {
|
||||
copyFirefoxIcon.src = "/clipboard.svg";
|
||||
}, 2000);
|
||||
});
|
||||
|
||||
copyChrome.addEventListener("click", async () => {
|
||||
await navigator.clipboard.writeText(chromeInput.value);
|
||||
copyChromeIcon.src = "/clipboard-check.svg";
|
||||
setTimeout(() => {
|
||||
copyChromeIcon.src = "/clipboard.svg";
|
||||
}, 2000);
|
||||
});
|
||||
}
|
||||
|
||||
const LS_DEFAULT_BANG = localStorage.getItem("default-bang") ?? "sg";
|
||||
const envDefaultBang = import.meta.env.VITE_DEFAULT_BANG ?? "g";
|
||||
const LS_DEFAULT_BANG = localStorage.getItem("default-bang") ?? envDefaultBang;
|
||||
const defaultBang = bangs.find((b) => b.t === LS_DEFAULT_BANG);
|
||||
|
||||
function getBangredirectUrl() {
|
||||
|
Reference in New Issue
Block a user