Compare commits

...

21 Commits

Author SHA1 Message Date
5032672341 Add opensearch.xml to gitignore 2025-05-09 10:03:39 -05:00
eec2d36526 Fixes before testing & deploying 2025-05-09 10:01:56 -05:00
Gib
3f3c0d72f8 Add opensearch.xml to site 2025-05-09 09:54:18 -05:00
f6b6639905 Add stuff so we can figure out how to be able to add this in native firefox 2025-05-09 09:06:15 -05:00
3781ae935d Revert nextcloud stuff because it was actually right the first time 2025-02-28 15:33:25 -06:00
ede223d9d5 Update nextcloud to work 2025-02-28 15:29:03 -06:00
dc1fa6d08a fix plex. overseerr broken but idk what I can do 2025-02-28 15:09:59 -06:00
7df4b38d09 Update README.md 2025-02-28 15:02:19 -06:00
7644c2c810 Add host-bang script as well to make things super easy 2025-02-28 14:56:22 -06:00
b5e740a5c2 Fixed network env issues in docker compose 2025-02-28 14:49:19 -06:00
48a526f404 Update README.md instructions. Clean up docker compose & add .env for docker compose file. 2025-02-28 14:38:45 -06:00
94a7880db3 Update README.md instructions. Clean up docker composee & add .env for docker compose file. 2025-02-28 14:34:26 -06:00
6f336b18d3 Add a bunch of self hosted services 2025-02-28 12:59:09 -06:00
1451ab0ef1 Update README.md 2025-02-28 12:05:47 -06:00
b35f479def Update README.md 2025-02-28 12:04:52 -06:00
740ef0f748 Update README.md 2025-02-28 12:01:28 -06:00
5d6e12c308 update readme 2025-02-28 11:57:34 -06:00
c75f426065 Add environment variables to make it easy for anyone to host 2025-02-28 11:50:53 -06:00
69239287b2 Make prettier 2025-02-28 10:06:04 -06:00
8f77173b8b Remove search.svg 2025-02-28 09:45:48 -06:00
cdcd418239 Make it prettier 2025-02-28 09:44:56 -06:00
18 changed files with 1638 additions and 1011 deletions

26
.env.example Normal file
View 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
View File

@ -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

View File

@ -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
View File

@ -0,0 +1,3 @@
PORT=5000
DOMAIN=bang.mydomain.com
NETWORK=bridge

View File

@ -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

View File

@ -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
View 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
View 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 ..

View File

@ -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

View File

@ -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

File diff suppressed because it is too large Load Diff

10
public/opensearch.xml Normal file
View 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>

View File

@ -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
View 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()

View 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!');

View File

@ -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",

View File

@ -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;
}
}

View File

@ -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() {