Compare commits
36 Commits
6efab00e75
...
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 | |||
537bf9d613 | |||
eba6c8273c | |||
97fe722790 | |||
8ea7faf483 | |||
c010ea4ea7 | |||
991fd13184 | |||
3032a81915 | |||
cea48a6584 | |||
f96ce5706d | |||
5cf34e3467 | |||
34f64869c0 | |||
b9763e2124 | |||
001e48129b | |||
3165c3dccb | |||
0b569d2b86 |
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
|
||||
|
45
README.md
45
README.md
@ -1,13 +1,44 @@
|
||||
# Unduck
|
||||
# 💣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://unduck.link?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.
|
||||
|
||||
I solved this by doing all of the work client side. Once you've went to https://unduck.link once, the JS is all cache'd and will never need to be downloaded again. Your device does the redirects, not me.
|
||||
Theo solved this by doing all of the work client side. Once you've went to https://unduck.link once, the JS is all cache'd and will never need to be downloaded again. Your device does the redirects, not me.
|
||||
|
3
docker/.env.example
Normal file
3
docker/.env.example
Normal file
@ -0,0 +1,3 @@
|
||||
PORT=5000
|
||||
DOMAIN=bang.mydomain.com
|
||||
NETWORK=bridge
|
33
docker/Dockerfile
Normal file
33
docker/Dockerfile
Normal file
@ -0,0 +1,33 @@
|
||||
# Stage 1: Build the project
|
||||
FROM node:18 AS builder
|
||||
WORKDIR /app
|
||||
|
||||
# Copy package.json and pnpm-lock.yaml to the working directory
|
||||
COPY package.json pnpm-lock.yaml ./
|
||||
|
||||
# Install dependencies
|
||||
RUN npm install -g pnpm
|
||||
RUN pnpm install
|
||||
|
||||
# Copy project files into the docker image
|
||||
COPY . .
|
||||
|
||||
# Build the project
|
||||
RUN pnpm run prebuild
|
||||
RUN pnpm run build
|
||||
|
||||
# Stage 2: Serve the app using the same version of Node
|
||||
FROM node:18-alpine
|
||||
WORKDIR /app
|
||||
|
||||
# Install a simple http server
|
||||
RUN npm install -g serve
|
||||
|
||||
# Copy built assets from the builder stage
|
||||
COPY --from=builder /app/dist ./
|
||||
|
||||
# Expose port 5000 for the server
|
||||
EXPOSE 5000
|
||||
|
||||
# Start the server using the `serve` package
|
||||
CMD ["serve", "-s", ".", "-l", "5000"]
|
21
docker/docker-compose.yml
Normal file
21
docker/docker-compose.yml
Normal file
@ -0,0 +1,21 @@
|
||||
services:
|
||||
bang-web-server:
|
||||
build:
|
||||
context: ../
|
||||
dockerfile: docker/Dockerfile
|
||||
container_name: bang
|
||||
hostname: bang.gib
|
||||
domainname: bang.gbrown.org
|
||||
networks:
|
||||
- nginx-bridge
|
||||
#ports:
|
||||
#- 5000:5000
|
||||
tty: true
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- ../:/app
|
||||
command: serve -s /app/dist -l 5000
|
||||
|
||||
networks:
|
||||
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 ..
|
15
index.html
15
index.html
@ -2,7 +2,7 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/search.svg" />
|
||||
<link rel="icon" type="image/svg+xml" href="/bang.svg" />
|
||||
<link
|
||||
rel="preconnect"
|
||||
href="https://fonts.googleapis.com"
|
||||
@ -24,13 +24,14 @@
|
||||
media="print"
|
||||
onload="this.media='all'"
|
||||
/>
|
||||
<script
|
||||
defer
|
||||
data-domain="unduck.link"
|
||||
src="https://plausible.io/js/script.js"
|
||||
></script>
|
||||
<link
|
||||
rel="search"
|
||||
type="application/opensearchdescription+xml"
|
||||
title="Bang!"
|
||||
href="/opensearch.xml"
|
||||
/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Unduck</title>
|
||||
<title>Bang!</title>
|
||||
<meta
|
||||
name="description"
|
||||
content="A better default search engine (with bangs!)"
|
||||
|
19
package.json
19
package.json
@ -1,15 +1,28 @@
|
||||
{
|
||||
"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.2"
|
||||
},
|
||||
"packageManager": "pnpm@10.10.0+sha512.d615db246fe70f25dcfea6d8d73dee782ce23e2245e3c4f6f888249fb568149318637dca73c2c5c8ef2a4ca0d5657fb9567188bfab47f566d1ee6ce987815c39",
|
||||
"pnpm": {
|
||||
"onlyBuiltDependencies": [
|
||||
"esbuild"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
3825
pnpm-lock.yaml
generated
3825
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
53
public/bang.svg
Normal file
53
public/bang.svg
Normal file
@ -0,0 +1,53 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 25.2.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 128 128" style="enable-background:new 0 0 128 128;" xml:space="preserve">
|
||||
<g>
|
||||
<g>
|
||||
<polygon style="fill:#FFE36C;" points="81.13,29.99 62.79,30.17 78.47,21.62 74.76,8.38 85.17,16.28 95.64,8.27 92.89,19.05
|
||||
110.15,21.03 92.19,28.03 85.03,40.53 "/>
|
||||
<path style="fill:#FF8F00;" d="M92.27,13.37l-0.76,2.96l-1.13,4.45l4.56,0.52l6.94,0.79l-9.76,3.81l-1.32,0.51l-0.7,1.23
|
||||
l-4.68,8.17l-1.92-5.2l-0.98-2.65L79.7,28l-8.91,0.09l7.24-3.95l2.8-1.53l-0.86-3.07l-1.67-5.96l4.44,3.37l2.43,1.84l2.42-1.85
|
||||
L92.27,13.37 M85.16,13.76L72.49,4.15c-0.41-0.31-0.98,0.08-0.84,0.57l4.46,15.91l-19.44,10.6c-0.49,0.27-0.3,1.01,0.26,1
|
||||
l22.81-0.23l4.5,12.17c0.16,0.43,0.74,0.47,0.97,0.08l8.37-14.62l22.82-8.9c0.52-0.2,0.42-0.97-0.13-1.03L95.4,17.32l3.22-12.65
|
||||
c0.13-0.49-0.44-0.87-0.84-0.56L85.16,13.76z"/>
|
||||
</g>
|
||||
<path style="fill:#4D4D4D;" d="M94.59,44.29c-1.27-0.18-7.6,6.13-12.17,2.17c0,0-0.01,0.01-0.01,0.01
|
||||
c-2.63-2.13-5.53-3.99-8.72-5.51c-21.75-10.37-47.8-1.15-58.18,20.61c-10.37,21.75-1.15,47.8,20.61,58.18
|
||||
c21.75,10.37,47.8,1.15,58.18-20.61c3.37-7.06,4.67-14.57,4.14-21.85c-0.04-0.51-0.13-1.64,0.12-2.48c1.71-5.74,7.93-8.6,7.8-8.87
|
||||
C106.23,65.66,95.87,44.47,94.59,44.29z"/>
|
||||
|
||||
<ellipse transform="matrix(0.8667 -0.4988 0.4988 0.8667 -14.1049 57.4504)" style="fill:#757575;" cx="100.46" cy="55.12" rx="5.83" ry="12.33"/>
|
||||
|
||||
<ellipse transform="matrix(0.866 -0.5 0.5 0.866 -13.8369 58.5564)" style="fill:#212121;" cx="102.35" cy="55.1" rx="2.29" ry="3.52"/>
|
||||
<path style="opacity:0.9;fill:#757575;" d="M53.9,52.57c2.74,7.78-1.27,12.42-6.46,15.39c-2.48,1.42-5.36,2.04-7.87,3.41
|
||||
c-4.88,2.66-7.84,7.79-12.09,11.36c-1.06,0.89-2.27,1.71-3.65,1.86c-1.84,0.2-3.64-0.88-4.74-2.37C18,80.72,13.53,66.5,29.85,51.95
|
||||
C37.14,45.45,50.9,44.04,53.9,52.57z"/>
|
||||
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="42.079" y1="34.5524" x2="83.8122" y2="48.0625">
|
||||
<stop offset="0" style="stop-color:#FFF9C4;stop-opacity:0.1"/>
|
||||
<stop offset="1.516624e-03" style="stop-color:#FFF9C4;stop-opacity:0.1007"/>
|
||||
<stop offset="0.3781" style="stop-color:#FFFCA6;stop-opacity:0.2626"/>
|
||||
<stop offset="0.7007" style="stop-color:#FFFE94;stop-opacity:0.4014"/>
|
||||
<stop offset="0.93" style="stop-color:#FFFF8D;stop-opacity:0.5"/>
|
||||
</linearGradient>
|
||||
<path style="fill:url(#SVGID_1_);" d="M41.86,38.72c3.69,0.12,11.23,0.17,20.77,3.92c3.21,1.26,5.92,3.02,8.83,4.82
|
||||
c2.35,1.45,5.02,2.66,7.77,2.4c0.97-0.09,2.04-0.47,2.42-1.37c0.23-0.56,0.21-1.13,0.01-1.69c-0.24-0.66-1.06-1.58-2.32-2.61
|
||||
c-1.77-1.19-3.65-2.28-5.64-3.23C63.34,36.02,52.01,35.53,41.86,38.72z"/>
|
||||
<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="102.0288" y1="57.3021" x2="91.1394" y2="42.9353">
|
||||
<stop offset="0.1096" style="stop-color:#FFF9C4;stop-opacity:0"/>
|
||||
<stop offset="1" style="stop-color:#FFFF8D"/>
|
||||
</linearGradient>
|
||||
<path style="fill:url(#SVGID_2_);" d="M102.13,47.64c-0.05-0.05-0.1-0.11-0.15-0.16c-0.01-0.01-0.01-0.02-0.02-0.02
|
||||
c-0.23-0.23-0.46-0.45-0.69-0.67c-0.03-0.03-0.07-0.06-0.1-0.1c-2.53-2.29-5.12-3.26-6.86-2.27c-2.79,1.6-2.3,7.69,1.1,13.59
|
||||
c0.73,1.26,1.53,2.41,2.37,3.43c-0.1-0.29-3.84-11.52-1.51-14.53C98.26,44.37,101.78,47.34,102.13,47.64z"/>
|
||||
<path style="fill:#212121;" d="M88.16,74.53c-2.42-1.59-4.79-3.75-6.26-9.18c-0.19-0.69-0.83-4.12,1.02-4.67
|
||||
c1.6-0.48,2.79,2.15,4.17,4.46c1.5,2.53,3.93,4.72,6.54,6.15c0.76,0.41,4.29,2,1.97,4.22C94.07,76.96,89.58,75.47,88.16,74.53z"/>
|
||||
<g>
|
||||
<path style="fill:#C69461;" d="M100.85,53.44c0.1-0.04,10.25-4.65,9.37-11.34c-0.58-4.42-2.3-6.62-11.4-8.78
|
||||
c-8.9-2.11-13.25-6.24-13.43-6.42c-0.89-0.86-0.92-2.29-0.05-3.19c0.86-0.89,2.28-0.92,3.18-0.06c0.07,0.06,3.77,3.48,11.35,5.28
|
||||
c8.82,2.09,13.79,4.72,14.83,12.57c1.16,8.78-8.52,14.25-11.29,15.7C101.69,58.11,99.71,53.94,100.85,53.44z"/>
|
||||
</g>
|
||||
<path style="fill:#F6BC41;" d="M88.5,23.66c-0.89-0.86-2.32-0.83-3.18,0.06c-0.86,0.9-0.84,2.32,0.05,3.19
|
||||
c0.13,0.13,2.5,2.37,7.25,4.39l0.95-1.66l3.66-1.43C91.42,26.35,88.56,23.71,88.5,23.66z"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 4.1 KiB |
1
public/chrome.svg
Normal file
1
public/chrome.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 48 48" height="48" width="48"><defs><linearGradient id="a" x1="3.2173" y1="15" x2="44.7812" y2="15" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#d93025"/><stop offset="1" stop-color="#ea4335"/></linearGradient><linearGradient id="b" x1="20.7219" y1="47.6791" x2="41.5039" y2="11.6837" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#fcc934"/><stop offset="1" stop-color="#fbbc04"/></linearGradient><linearGradient id="c" x1="26.5981" y1="46.5015" x2="5.8161" y2="10.506" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#1e8e3e"/><stop offset="1" stop-color="#34a853"/></linearGradient></defs><circle cx="24" cy="23.9947" r="12" style="fill:#fff"/><path d="M3.2154,36A24,24,0,1,0,12,3.2154,24,24,0,0,0,3.2154,36ZM34.3923,18A12,12,0,1,1,18,13.6077,12,12,0,0,1,34.3923,18Z" style="fill:none"/><path d="M24,12H44.7812a23.9939,23.9939,0,0,0-41.5639.0029L13.6079,30l.0093-.0024A11.9852,11.9852,0,0,1,24,12Z" style="fill:url(#a)"/><circle cx="24" cy="24" r="9.5" style="fill:#1a73e8"/><path d="M34.3913,30.0029,24.0007,48A23.994,23.994,0,0,0,44.78,12.0031H23.9989l-.0025.0093A11.985,11.985,0,0,1,34.3913,30.0029Z" style="fill:url(#b)"/><path d="M13.6086,30.0031,3.218,12.006A23.994,23.994,0,0,0,24.0025,48L34.3931,30.0029l-.0067-.0068a11.9852,11.9852,0,0,1-20.7778.007Z" style="fill:url(#c)"/></svg>
|
After Width: | Height: | Size: 1.4 KiB |
@ -1 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-clipboard"><rect width="8" height="4" x="8" y="2" rx="1" ry="1"/><path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"/></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#bbb" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-clipboard"><rect width="8" height="4" x="8" y="2" rx="1" ry="1"/><path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"/></svg>
|
||||
|
Before Width: | Height: | Size: 357 B After Width: | Height: | Size: 350 B |
120
public/firefox.svg
Normal file
120
public/firefox.svg
Normal file
@ -0,0 +1,120 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
<svg width="77.42" height="79.97" version="1.1" viewBox="0 0 77.42 79.97" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
|
||||
<title>Firefox Browser logo</title>
|
||||
<defs>
|
||||
<linearGradient id="a" x1="70.79" x2="6.447" y1="12.39" y2="74.47" gradientTransform="translate(-1.3 -.004086)" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#fff44f" offset=".048"/>
|
||||
<stop stop-color="#ffe847" offset=".111"/>
|
||||
<stop stop-color="#ffc830" offset=".225"/>
|
||||
<stop stop-color="#ff980e" offset=".368"/>
|
||||
<stop stop-color="#ff8b16" offset=".401"/>
|
||||
<stop stop-color="#ff672a" offset=".462"/>
|
||||
<stop stop-color="#ff3647" offset=".534"/>
|
||||
<stop stop-color="#e31587" offset=".705"/>
|
||||
</linearGradient>
|
||||
<radialGradient id="b" cx="-7907" cy="-8515" r="80.8" gradientTransform="translate(7974,8524)" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#ffbd4f" offset=".129"/>
|
||||
<stop stop-color="#ffac31" offset=".186"/>
|
||||
<stop stop-color="#ff9d17" offset=".247"/>
|
||||
<stop stop-color="#ff980e" offset=".283"/>
|
||||
<stop stop-color="#ff563b" offset=".403"/>
|
||||
<stop stop-color="#ff3750" offset=".467"/>
|
||||
<stop stop-color="#f5156c" offset=".71"/>
|
||||
<stop stop-color="#eb0878" offset=".782"/>
|
||||
<stop stop-color="#e50080" offset=".86"/>
|
||||
</radialGradient>
|
||||
<radialGradient id="c" cx="-7937" cy="-8482" r="80.8" gradientTransform="translate(7974,8524)" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#960e18" offset=".3"/>
|
||||
<stop stop-color="#b11927" stop-opacity=".74" offset=".351"/>
|
||||
<stop stop-color="#db293d" stop-opacity=".343" offset=".435"/>
|
||||
<stop stop-color="#f5334b" stop-opacity=".094" offset=".497"/>
|
||||
<stop stop-color="#ff3750" stop-opacity="0" offset=".53"/>
|
||||
</radialGradient>
|
||||
<radialGradient id="d" cx="-7927" cy="-8533" r="58.53" gradientTransform="translate(7974,8524)" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#fff44f" offset=".132"/>
|
||||
<stop stop-color="#ffdc3e" offset=".252"/>
|
||||
<stop stop-color="#ff9d12" offset=".506"/>
|
||||
<stop stop-color="#ff980e" offset=".526"/>
|
||||
</radialGradient>
|
||||
<radialGradient id="e" cx="-7946" cy="-8461" r="38.47" gradientTransform="translate(7974,8524)" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#3a8ee6" offset=".353"/>
|
||||
<stop stop-color="#5c79f0" offset=".472"/>
|
||||
<stop stop-color="#9059ff" offset=".669"/>
|
||||
<stop stop-color="#c139e6" offset="1"/>
|
||||
</radialGradient>
|
||||
<radialGradient id="f" cx="-7936" cy="-8492" r="20.4" gradientTransform="matrix(.972 -.235 .275 1.138 10090 7834)" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#9059ff" stop-opacity="0" offset=".206"/>
|
||||
<stop stop-color="#8c4ff3" stop-opacity=".064" offset=".278"/>
|
||||
<stop stop-color="#7716a8" stop-opacity=".45" offset=".747"/>
|
||||
<stop stop-color="#6e008b" stop-opacity=".6" offset=".975"/>
|
||||
</radialGradient>
|
||||
<radialGradient id="g" cx="-7938" cy="-8518" r="27.68" gradientTransform="translate(7974,8524)" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#ffe226" offset="0"/>
|
||||
<stop stop-color="#ffdb27" offset=".121"/>
|
||||
<stop stop-color="#ffc82a" offset=".295"/>
|
||||
<stop stop-color="#ffa930" offset=".502"/>
|
||||
<stop stop-color="#ff7e37" offset=".732"/>
|
||||
<stop stop-color="#ff7139" offset=".792"/>
|
||||
</radialGradient>
|
||||
<radialGradient id="h" cx="-7916" cy="-8536" r="118.1" gradientTransform="translate(7974,8524)" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#fff44f" offset=".113"/>
|
||||
<stop stop-color="#ff980e" offset=".456"/>
|
||||
<stop stop-color="#ff5634" offset=".622"/>
|
||||
<stop stop-color="#ff3647" offset=".716"/>
|
||||
<stop stop-color="#e31587" offset=".904"/>
|
||||
</radialGradient>
|
||||
<radialGradient id="i" cx="-7927" cy="-8523" r="86.5" gradientTransform="matrix(.105 .995 -.653 .069 -4685 8470)" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#fff44f" offset="0"/>
|
||||
<stop stop-color="#ffe847" offset=".06"/>
|
||||
<stop stop-color="#ffc830" offset=".168"/>
|
||||
<stop stop-color="#ff980e" offset=".304"/>
|
||||
<stop stop-color="#ff8b16" offset=".356"/>
|
||||
<stop stop-color="#ff672a" offset=".455"/>
|
||||
<stop stop-color="#ff3647" offset=".57"/>
|
||||
<stop stop-color="#e31587" offset=".737"/>
|
||||
</radialGradient>
|
||||
<radialGradient id="j" cx="-7938" cy="-8508" r="73.72" gradientTransform="translate(7974,8524)" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#fff44f" offset=".137"/>
|
||||
<stop stop-color="#ff980e" offset=".48"/>
|
||||
<stop stop-color="#ff5634" offset=".592"/>
|
||||
<stop stop-color="#ff3647" offset=".655"/>
|
||||
<stop stop-color="#e31587" offset=".904"/>
|
||||
</radialGradient>
|
||||
<radialGradient id="k" cx="-7919" cy="-8504" r="80.69" gradientTransform="translate(7974,8524)" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#fff44f" offset=".094"/>
|
||||
<stop stop-color="#ffe141" offset=".231"/>
|
||||
<stop stop-color="#ffaf1e" offset=".509"/>
|
||||
<stop stop-color="#ff980e" offset=".626"/>
|
||||
</radialGradient>
|
||||
<linearGradient id="l" x1="70.01" x2="15.27" y1="12.06" y2="66.81" gradientTransform="translate(-1.3 -.004086)" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#fff44f" stop-opacity=".8" offset=".167"/>
|
||||
<stop stop-color="#fff44f" stop-opacity=".634" offset=".266"/>
|
||||
<stop stop-color="#fff44f" stop-opacity=".217" offset=".489"/>
|
||||
<stop stop-color="#fff44f" stop-opacity="0" offset=".6"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<g transform="matrix(.9819843 0 0 .9819843 .6974849 .7199239)">
|
||||
<path d="m74.62 26.83c-1.684-4.052-5.1-8.427-7.775-9.81a40.27 40.27 0 0 1 3.925 11.76l7e-3 0.065c-4.382-10.92-11.81-15.33-17.88-24.92-0.307-0.485-0.614-0.971-0.913-1.484-0.171-0.293-0.308-0.557-0.427-0.8a7.053 7.053 0 0 1-0.578-1.535 0.1 0.1 0 0 0-0.088-0.1 0.138 0.138 0 0 0-0.073 0c-5e-3 0-0.013 9e-3 -0.019 0.011s-0.019 0.011-0.028 0.015l0.015-0.026c-9.735 5.7-13.04 16.25-13.34 21.53a19.39 19.39 0 0 0-10.67 4.111 11.59 11.59 0 0 0-1-0.758 17.97 17.97 0 0 1-0.109-9.473 28.7 28.7 0 0 0-9.329 7.21h-0.018c-1.536-1.947-1.428-8.367-1.34-9.708a6.928 6.928 0 0 0-1.294 0.687 28.22 28.22 0 0 0-3.788 3.245 33.84 33.84 0 0 0-3.623 4.347v6e-3 -7e-3a32.73 32.73 0 0 0-5.2 11.74l-0.052 0.256c-0.073 0.341-0.336 2.049-0.381 2.42 0 0.029-6e-3 0.056-9e-3 0.085a36.94 36.94 0 0 0-0.629 5.343v0.2a38.76 38.76 0 0 0 76.95 6.554c0.065-0.5 0.118-0.995 0.176-1.5a39.86 39.86 0 0 0-2.514-19.47zm-44.67 30.34c0.181 0.087 0.351 0.181 0.537 0.264l0.027 0.017q-0.282-0.135-0.564-0.281zm8.878-23.38m31.95-4.934v-0.037l7e-3 0.041z" fill="url(#a)"/>
|
||||
<path d="m74.62 26.83c-1.684-4.052-5.1-8.427-7.775-9.81a40.27 40.27 0 0 1 3.925 11.76v0.037l7e-3 0.041a35.1 35.1 0 0 1-1.206 26.16c-4.442 9.531-15.19 19.3-32.02 18.82-18.18-0.515-34.2-14.01-37.19-31.68-0.545-2.787 0-4.2 0.274-6.465a28.88 28.88 0 0 0-0.623 5.348v0.2a38.76 38.76 0 0 0 76.95 6.554c0.065-0.5 0.118-0.995 0.176-1.5a39.86 39.86 0 0 0-2.514-19.47z" fill="url(#b)"/>
|
||||
<path d="m74.62 26.83c-1.684-4.052-5.1-8.427-7.775-9.81a40.27 40.27 0 0 1 3.925 11.76v0.037l7e-3 0.041a35.1 35.1 0 0 1-1.206 26.16c-4.442 9.531-15.19 19.3-32.02 18.82-18.18-0.515-34.2-14.01-37.19-31.68-0.545-2.787 0-4.2 0.274-6.465a28.88 28.88 0 0 0-0.623 5.348v0.2a38.76 38.76 0 0 0 76.95 6.554c0.065-0.5 0.118-0.995 0.176-1.5a39.86 39.86 0 0 0-2.514-19.47z" fill="url(#c)"/>
|
||||
<path d="m55.78 31.38c0.084 0.059 0.162 0.118 0.241 0.177a21.1 21.1 0 0 0-3.6-4.695c-12.05-12.05-3.157-26.12-1.658-26.84l0.015-0.022c-9.735 5.7-13.04 16.25-13.34 21.53 0.452-0.031 0.9-0.069 1.362-0.069a19.56 19.56 0 0 1 16.98 9.917z" fill="url(#d)"/>
|
||||
<path d="m38.82 33.79c-0.064 0.964-3.47 4.289-4.661 4.289-11.02 0-12.81 6.667-12.81 6.667 0.488 5.614 4.4 10.24 9.129 12.68 0.216 0.112 0.435 0.213 0.654 0.312q0.569 0.252 1.138 0.466a17.24 17.24 0 0 0 5.043 0.973c19.32 0.906 23.06-23.1 9.119-30.07a13.38 13.38 0 0 1 9.345 2.269 19.56 19.56 0 0 0-16.98-9.917c-0.46 0-0.91 0.038-1.362 0.069a19.39 19.39 0 0 0-10.67 4.111c0.591 0.5 1.258 1.168 2.663 2.553 2.63 2.591 9.375 5.275 9.39 5.59z" fill="url(#e)"/>
|
||||
<path d="m38.82 33.79c-0.064 0.964-3.47 4.289-4.661 4.289-11.02 0-12.81 6.667-12.81 6.667 0.488 5.614 4.4 10.24 9.129 12.68 0.216 0.112 0.435 0.213 0.654 0.312q0.569 0.252 1.138 0.466a17.24 17.24 0 0 0 5.043 0.973c19.32 0.906 23.06-23.1 9.119-30.07a13.38 13.38 0 0 1 9.345 2.269 19.56 19.56 0 0 0-16.98-9.917c-0.46 0-0.91 0.038-1.362 0.069a19.39 19.39 0 0 0-10.67 4.111c0.591 0.5 1.258 1.168 2.663 2.553 2.63 2.591 9.375 5.275 9.39 5.59z" fill="url(#f)"/>
|
||||
<path d="m24.96 24.36c0.314 0.2 0.573 0.374 0.8 0.531a17.97 17.97 0 0 1-0.109-9.473 28.7 28.7 0 0 0-9.329 7.21c0.189-5e-3 5.811-0.106 8.638 1.732z" fill="url(#g)"/>
|
||||
<path d="m0.354 42.16c2.991 17.67 19.01 31.17 37.19 31.68 16.83 0.476 27.58-9.294 32.02-18.82a35.1 35.1 0 0 0 1.206-26.16v-0.037c0-0.029-6e-3 -0.046 0-0.037l7e-3 0.065c1.375 8.977-3.191 17.67-10.33 23.56l-0.022 0.05c-13.91 11.33-27.22 6.834-29.91 5q-0.282-0.135-0.564-0.281c-8.109-3.876-11.46-11.26-10.74-17.6a9.953 9.953 0 0 1-9.181-5.775 14.62 14.62 0 0 1 14.25-0.572 19.3 19.3 0 0 0 14.55 0.572c-0.015-0.315-6.76-3-9.39-5.59-1.405-1.385-2.072-2.052-2.663-2.553a11.59 11.59 0 0 0-1-0.758c-0.23-0.157-0.489-0.327-0.8-0.531-2.827-1.838-8.449-1.737-8.635-1.732h-0.018c-1.536-1.947-1.428-8.367-1.34-9.708a6.928 6.928 0 0 0-1.294 0.687 28.22 28.22 0 0 0-3.788 3.245 33.84 33.84 0 0 0-3.638 4.337v6e-3 -7e-3a32.73 32.73 0 0 0-5.2 11.74c-0.019 0.079-1.396 6.099-0.717 9.221z" fill="url(#h)"/>
|
||||
<path d="m52.42 26.86a21.1 21.1 0 0 1 3.6 4.7c0.213 0.161 0.412 0.321 0.581 0.476 8.787 8.1 4.183 19.55 3.84 20.36 7.138-5.881 11.7-14.58 10.33-23.56-4.384-10.93-11.82-15.34-17.88-24.93-0.307-0.485-0.614-0.971-0.913-1.484-0.171-0.293-0.308-0.557-0.427-0.8a7.053 7.053 0 0 1-0.578-1.535 0.1 0.1 0 0 0-0.088-0.1 0.138 0.138 0 0 0-0.073 0c-5e-3 0-0.013 9e-3 -0.019 0.011s-0.019 0.011-0.028 0.015c-1.499 0.711-10.39 14.79 1.66 26.83z" fill="url(#i)"/>
|
||||
<path d="m56.6 32.04c-0.169-0.155-0.368-0.315-0.581-0.476-0.079-0.059-0.157-0.118-0.241-0.177a13.38 13.38 0 0 0-9.345-2.269c13.94 6.97 10.2 30.97-9.119 30.07a17.24 17.24 0 0 1-5.043-0.973q-0.569-0.213-1.138-0.466c-0.219-0.1-0.438-0.2-0.654-0.312l0.027 0.017c2.694 1.839 16 6.332 29.91-5l0.022-0.05c0.347-0.81 4.951-12.26-3.84-20.36z" fill="url(#j)"/>
|
||||
<path d="m21.35 44.74s1.789-6.667 12.81-6.667c1.191 0 4.6-3.325 4.661-4.289a19.3 19.3 0 0 1-14.55-0.572 14.62 14.62 0 0 0-14.25 0.572 9.953 9.953 0 0 0 9.181 5.775c-0.718 6.337 2.632 13.72 10.74 17.6 0.181 0.087 0.351 0.181 0.537 0.264-4.733-2.445-8.641-7.069-9.129-12.68z" fill="url(#k)"/>
|
||||
<path d="m74.62 26.83c-1.684-4.052-5.1-8.427-7.775-9.81a40.27 40.27 0 0 1 3.925 11.76l7e-3 0.065c-4.382-10.92-11.81-15.33-17.88-24.92-0.307-0.485-0.614-0.971-0.913-1.484-0.171-0.293-0.308-0.557-0.427-0.8a7.053 7.053 0 0 1-0.578-1.535 0.1 0.1 0 0 0-0.088-0.1 0.138 0.138 0 0 0-0.073 0c-5e-3 0-0.013 9e-3 -0.019 0.011s-0.019 0.011-0.028 0.015l0.015-0.026c-9.735 5.7-13.04 16.25-13.34 21.53 0.452-0.031 0.9-0.069 1.362-0.069a19.56 19.56 0 0 1 16.98 9.917 13.38 13.38 0 0 0-9.345-2.269c13.94 6.97 10.2 30.97-9.119 30.07a17.24 17.24 0 0 1-5.043-0.973q-0.569-0.213-1.138-0.466c-0.219-0.1-0.438-0.2-0.654-0.312l0.027 0.017q-0.282-0.135-0.564-0.281c0.181 0.087 0.351 0.181 0.537 0.264-4.733-2.446-8.641-7.07-9.129-12.68 0 0 1.789-6.667 12.81-6.667 1.191 0 4.6-3.325 4.661-4.289-0.015-0.315-6.76-3-9.39-5.59-1.405-1.385-2.072-2.052-2.663-2.553a11.59 11.59 0 0 0-1-0.758 17.97 17.97 0 0 1-0.109-9.473 28.7 28.7 0 0 0-9.329 7.21h-0.018c-1.536-1.947-1.428-8.367-1.34-9.708a6.928 6.928 0 0 0-1.294 0.687 28.22 28.22 0 0 0-3.788 3.245 33.84 33.84 0 0 0-3.623 4.347v6e-3 -7e-3a32.73 32.73 0 0 0-5.2 11.74l-0.052 0.256c-0.073 0.341-0.4 2.073-0.447 2.445a45.09 45.09 0 0 0-0.572 5.403v0.2a38.76 38.76 0 0 0 76.95 6.554c0.065-0.5 0.118-0.995 0.176-1.5a39.86 39.86 0 0 0-2.514-19.47zm-3.845 1.991 7e-3 0.041z" fill="url(#l)"/>
|
||||
</g>
|
||||
<metadata>
|
||||
<rdf:RDF>
|
||||
<cc:Work rdf:about="">
|
||||
<dc:title>Firefox Browser logo</dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
</svg>
|
After Width: | Height: | Size: 12 KiB |
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!');
|
232
src/bang.ts
232
src/bang.ts
@ -1,6 +1,191 @@
|
||||
// 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: SEARXNG_URL,
|
||||
r: 0,
|
||||
s: "Searxng",
|
||||
sc: "Search",
|
||||
t: "s",
|
||||
u: `${SEARXNG_URL}/?q={{{s}}}`,
|
||||
},
|
||||
{
|
||||
c: "Tech",
|
||||
d: GITEA_URL,
|
||||
r: 0,
|
||||
s: "Gitea",
|
||||
sc: "Cryptocurrency",
|
||||
t: "tea",
|
||||
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: OPENWEBUI_URL,
|
||||
r: 0,
|
||||
s: "OpenWebUI",
|
||||
sc: "AI",
|
||||
t: "ai",
|
||||
u: `${OPENWEBUI_URL}/?q={{{s}}}`,
|
||||
},
|
||||
{
|
||||
c: "AI",
|
||||
d: OPENWEBUI_URL,
|
||||
r: 0,
|
||||
s: "OpenWebUI",
|
||||
sc: "AI",
|
||||
t: "c",
|
||||
u: `${OPENWEBUI_URL}/?q={{{s}}}`,
|
||||
},
|
||||
{
|
||||
c: "AI",
|
||||
d: OPENWEBUI_URL,
|
||||
r: 0,
|
||||
s: "OpenWebUI",
|
||||
sc: "AI",
|
||||
t: "llama",
|
||||
u: `${OPENWEBUI_URL}/?models=${LLAMA_MODEL}&q={{{s}}}`,
|
||||
},
|
||||
{
|
||||
c: "AI",
|
||||
d: OPENWEBUI_URL,
|
||||
r: 0,
|
||||
s: "OpenWebUI",
|
||||
sc: "AI",
|
||||
t: "r1",
|
||||
u: `${OPENWEBUI_URL}/?models=${DEEPSEEK_MODEL}&q={{{s}}}`,
|
||||
},
|
||||
{
|
||||
c: "AI",
|
||||
d: OPENWEBUI_URL,
|
||||
r: 0,
|
||||
s: "OpenWebUI",
|
||||
sc: "AI",
|
||||
t: "r1-7b",
|
||||
u: `${OPENWEBUI_URL}/?models=deepseek-r1%3A7b&q={{{s}}}`,
|
||||
},
|
||||
{
|
||||
c: "AI",
|
||||
d: OPENWEBUI_URL,
|
||||
r: 0,
|
||||
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: `${OPENWEBUI_URL}/?models=o3-mini&q={{{s}}}`,
|
||||
},
|
||||
{
|
||||
c: "AI",
|
||||
d: OPENWEBUI_URL,
|
||||
r: 0,
|
||||
s: "OpenWebUI",
|
||||
sc: "AI",
|
||||
t: "o1",
|
||||
u: `${OPENWEBUI_URL}/?models=o1&q={{{s}}}`,
|
||||
},
|
||||
{
|
||||
c: "AI",
|
||||
d: OPENWEBUI_URL,
|
||||
r: 0,
|
||||
s: "OpenWebUI",
|
||||
sc: "AI",
|
||||
t: "4o-mini",
|
||||
u: `${OPENWEBUI_URL}/?models=gpt-4o-mini&q={{{s}}}`,
|
||||
},
|
||||
{
|
||||
c: "AI",
|
||||
d: OPENWEBUI_URL,
|
||||
r: 0,
|
||||
s: "OpenWebUI",
|
||||
sc: "AI",
|
||||
t: "4o",
|
||||
u: `${OPENWEBUI_URL}/?models=gpt-4o&q={{{s}}}`,
|
||||
},
|
||||
{
|
||||
c: "AI",
|
||||
d: "www.t3.chat",
|
||||
@ -10,24 +195,6 @@ export const bangs = [
|
||||
t: "t3",
|
||||
u: "https://www.t3.chat/new?q={{{s}}}",
|
||||
},
|
||||
{
|
||||
c: "Tech",
|
||||
d: "www.01net.com",
|
||||
r: 4,
|
||||
s: "01net",
|
||||
sc: "Downloads (apps)",
|
||||
t: "01net",
|
||||
u: "http://www.01net.com/recherche/recherche.php?searchstring={{{s}}}&chaine=home",
|
||||
},
|
||||
{
|
||||
c: "Entertainment",
|
||||
d: "www.bmw2002faq.com",
|
||||
r: 0,
|
||||
s: "BMW 2002 FAQ",
|
||||
sc: "Forum",
|
||||
t: "02faq",
|
||||
u: "https://www.bmw2002faq.com/search/?q={{{s}}}",
|
||||
},
|
||||
{
|
||||
c: "Entertainment",
|
||||
d: "boxofficemojo.com",
|
||||
@ -3184,7 +3351,7 @@ export const bangs = [
|
||||
r: 8351,
|
||||
s: "Duck.ai",
|
||||
sc: "Reference",
|
||||
t: "ai",
|
||||
t: "ddgai",
|
||||
u: "https://www.duckduckgo.com/?q={{{s}}}&ia=chat&bang=true ",
|
||||
},
|
||||
{
|
||||
@ -15988,7 +16155,7 @@ export const bangs = [
|
||||
r: 165,
|
||||
s: "CNET",
|
||||
sc: "Downloads",
|
||||
t: "c",
|
||||
t: "cnet",
|
||||
u: "http://www.cnet.com/1770-5_1-0.html?query={{{s}}}",
|
||||
},
|
||||
{
|
||||
@ -71306,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}}}",
|
||||
},
|
||||
{
|
||||
@ -73419,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",
|
||||
@ -78745,7 +78912,7 @@ export const bangs = [
|
||||
r: 0,
|
||||
s: "StackOverflow",
|
||||
sc: "Programming",
|
||||
t: "ov",
|
||||
t: "ovfl",
|
||||
u: "http://stackoverflow.com/search?q={{{s}}}",
|
||||
},
|
||||
{
|
||||
@ -93872,7 +94039,7 @@ export const bangs = [
|
||||
r: 46,
|
||||
s: "Sourcegraph",
|
||||
sc: "Programming",
|
||||
t: "sg",
|
||||
t: "srcg",
|
||||
u: "https://sourcegraph.com/search?q={{{s}}}",
|
||||
},
|
||||
{
|
||||
@ -98325,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}}}",
|
||||
},
|
||||
{
|
||||
@ -106698,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",
|
||||
@ -106866,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",
|
||||
|
177
src/global.css
177
src/global.css
@ -1,13 +1,10 @@
|
||||
/* @import url("https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap"); */
|
||||
|
||||
/* Font fallback that closely matches Inter metrics */
|
||||
/* --- Import and fonts unchanged --- */
|
||||
@font-face {
|
||||
font-family: "Inter Fallback";
|
||||
size-adjust: 107%;
|
||||
ascent-override: 90%;
|
||||
src: local("Arial");
|
||||
}
|
||||
|
||||
:root {
|
||||
font-family:
|
||||
Inter,
|
||||
@ -27,27 +24,41 @@
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
|
||||
/* --- Dark mode color variables --- */
|
||||
--bg-color: #121212; /* Dark background */
|
||||
--text-color: #e2e2e2; /* Light text */
|
||||
--input-bg-color: #1e1e1e; /* Dark input background */
|
||||
--input-border-color: #333; /* Darker border for inputs */
|
||||
--accent-color: #bbb; /* Accent color for links/buttons */
|
||||
--hover-bg-color: #2c2c2c; /* Hover state background color */
|
||||
--active-bg-color: #3a3a3a; /* Active state background color */
|
||||
--success-color: #28a745; /* Green for copy-success feedback */
|
||||
}
|
||||
|
||||
/* --- Normalized layout reset stays the same --- */
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
background-color: var(--bg-color);
|
||||
}
|
||||
|
||||
/* --- Modified to Dark mode --- */
|
||||
body {
|
||||
line-height: 1.5;
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
color: #1a1a1a;
|
||||
background-color: var(--bg-color); /* Changed: Added dark background */
|
||||
color: var(--text-color); /* Changed: Light text color */
|
||||
}
|
||||
|
||||
/* Header and fonts remain unchanged (already good contrast with dark mode) */
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
@ -58,26 +69,43 @@ h6 {
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
/* --- Link colors modified for contrast --- */
|
||||
a {
|
||||
color: #444444;
|
||||
color: var(--accent-color); /* Changed: Lighter accent/link color */
|
||||
}
|
||||
a:hover {
|
||||
color: #888888;
|
||||
color: white; /* Changed: Bright when hovered */
|
||||
}
|
||||
|
||||
/* --- Basic element reset unchanged --- */
|
||||
button {
|
||||
font: inherit;
|
||||
border: none;
|
||||
background: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
input,
|
||||
textarea {
|
||||
font: inherit;
|
||||
}
|
||||
.bang-title {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
/* Add these new styles */
|
||||
/* --- Container layout unchanged --- */
|
||||
.main-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
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;
|
||||
@ -85,65 +113,112 @@ textarea {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
/* Add this new style */
|
||||
.content-container {
|
||||
max-width: 36rem;
|
||||
/* --- URL input element styling adjustments --- */
|
||||
.url-input {
|
||||
padding: 8px 12px;
|
||||
border: 1px solid var(--input-border-color); /* Changed: Darker border */
|
||||
border-radius: 4px;
|
||||
width: 100%;
|
||||
background: var(--input-bg-color); /* Changed: Dark input bg */
|
||||
color: var(--text-color); /* Added: Input text color */
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* Update url-input width to be 100% since container will control max width */
|
||||
.url-input {
|
||||
padding: 8px 12px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
width: 100%;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
.copy-button {
|
||||
padding: 8px;
|
||||
color: #666;
|
||||
border-radius: 4px;
|
||||
/* --- Copy-button style adjustments --- */
|
||||
.copy-button, .copy-firefox, .copy-chrome {
|
||||
padding: 6px;
|
||||
color: var(--accent-color); /* Changed: Accent color instead of gray */
|
||||
transition: all 0.2s;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.copy-button:hover {
|
||||
background: #f0f0f0;
|
||||
.copy-button:hover, .copy-firefox:hover, .copy-chrome:hover {
|
||||
background: var(--hover-bg-color); /* Changed: Hover bg darker */
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.copy-button:active {
|
||||
background: #e5e5e5;
|
||||
.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-button.copied {
|
||||
background: #28a745;
|
||||
/* Copy feedback unchanged (Green stands out in dark mode as well) */
|
||||
.copy-button.copied, .copy-firefox.copied, .copy-chrome.copied {
|
||||
background: var(--success-color);
|
||||
color: white; /* Ensures contrast */
|
||||
}
|
||||
|
||||
/* Add footer styles */
|
||||
.footer {
|
||||
position: fixed;
|
||||
bottom: 16px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
text-align: center;
|
||||
.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;
|
||||
color: #666;
|
||||
width: 220px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.footer a {
|
||||
color: #666;
|
||||
text-decoration: none;
|
||||
font-weight: 500;
|
||||
.copy-textbox:focus, .firefox-textbox:focus, .chrome-textbox:focus {
|
||||
outline: none;
|
||||
border-color: var(--accent-color);
|
||||
}
|
||||
|
||||
.footer a:hover {
|
||||
color: #333;
|
||||
.settings-title {
|
||||
margin-top: 36px;
|
||||
margin-bottom: 16px;
|
||||
font-size: 20px;
|
||||
}
|
||||
.settings-links-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
108
src/main.ts
108
src/main.ts
@ -1,51 +1,122 @@
|
||||
import { bangs } from "./bang";
|
||||
import "./global.css";
|
||||
|
||||
const defaultBang = bangs.find((b) => b.t === "g");
|
||||
|
||||
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>Unduck</h1>
|
||||
<p>DuckDuckGo's bang redirects are too slow. Add the following URL as a custom search engine to your browser. Enables <a href="https://duckduckgo.com/bang.html" target="_blank">all of DuckDuckGo's bangs.</a></p>
|
||||
<h1 class="bang-title">💣 Bang!</h1>
|
||||
<p>
|
||||
Add the following URL as a custom search engine to your browser in order to enable
|
||||
<a href="https://duckduckgo.com/bang.html" target="_blank">
|
||||
all of DuckDuckGo's bangs
|
||||
</a>
|
||||
right from your browser's search bar!
|
||||
</p>
|
||||
<div class="url-container">
|
||||
<input
|
||||
type="text"
|
||||
class="url-input"
|
||||
value="https://unduck.link?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">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">
|
||||
<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>
|
||||
<footer class="footer">
|
||||
<a href="https://t3.chat" target="_blank">t3.chat</a>
|
||||
•
|
||||
<a href="https://x.com/theo" target="_blank">theo</a>
|
||||
•
|
||||
<a href="https://github.com/t3dotgg/unduck" target="_blank">github</a>
|
||||
</footer>
|
||||
</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 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() {
|
||||
const url = new URL(window.location.href);
|
||||
const query = url.searchParams.get("q")?.trim() ?? "";
|
||||
@ -54,19 +125,20 @@ function getBangredirectUrl() {
|
||||
return null;
|
||||
}
|
||||
|
||||
const match = query.match(/!([a-z0-9]+)/i);
|
||||
const match = query.match(/!(\S+)/i);
|
||||
|
||||
const bangCandidate = match?.[1]?.toLowerCase();
|
||||
const selectedBang = bangs.find((b) => b.t === bangCandidate) ?? defaultBang;
|
||||
|
||||
// Remove the first bang from the query
|
||||
const cleanQuery = query.replace(/![a-z0-9]+\s*/i, "").trim();
|
||||
const cleanQuery = query.replace(/!\S+\s*/i, "").trim();
|
||||
|
||||
// Format of the url is:
|
||||
// https://www.google.com/search?q={{{s}}}
|
||||
const searchUrl = selectedBang?.u.replace(
|
||||
"{{{s}}}",
|
||||
encodeURIComponent(cleanQuery)
|
||||
// Replace %2F with / to fix formats like "!ghr+t3dotgg/unduck"
|
||||
encodeURIComponent(cleanQuery).replace(/%2F/g, "/")
|
||||
);
|
||||
if (!searchUrl) return null;
|
||||
|
||||
|
10
vite.config.ts
Normal file
10
vite.config.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { defineConfig } from "vite";
|
||||
import { VitePWA } from "vite-plugin-pwa";
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
VitePWA({
|
||||
registerType: "autoUpdate",
|
||||
}),
|
||||
],
|
||||
});
|
Reference in New Issue
Block a user