Add stuff so we can figure out how to be able to add this in native firefox
This commit is contained in:
parent
3781ae935d
commit
f6b6639905
@ -1,24 +1,21 @@
|
|||||||
networks:
|
|
||||||
default:
|
|
||||||
name: ${NETWORK}
|
|
||||||
external: true
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
bang-web-server:
|
bang-web-server:
|
||||||
env_file:
|
|
||||||
- .env
|
|
||||||
build:
|
build:
|
||||||
context: ../
|
context: ../
|
||||||
dockerfile: docker/Dockerfile
|
dockerfile: docker/Dockerfile
|
||||||
container_name: bang
|
container_name: bang
|
||||||
domainname: ${DOMAIN}
|
hostname: bang.gib
|
||||||
|
domainname: bang.gbrown.org
|
||||||
networks:
|
networks:
|
||||||
- default
|
- nginx-bridge
|
||||||
hostname: bang
|
#ports:
|
||||||
ports:
|
#- 5000:5000
|
||||||
- ${PORT}:${PORT}
|
|
||||||
tty: true
|
tty: true
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
volumes:
|
volumes:
|
||||||
- ../:/app
|
- ../:/app
|
||||||
command: serve -s /app/dist -l 5000
|
command: serve -s /app/dist -l 5000
|
||||||
|
|
||||||
|
networks:
|
||||||
|
nginx-bridge:
|
||||||
|
external: true
|
||||||
|
280
output.md
Normal file
280
output.md
Normal file
@ -0,0 +1,280 @@
|
|||||||
|
|
||||||
|
docker/.env
|
||||||
|
```
|
||||||
|
PORT=5000
|
||||||
|
DOMAIN=bang.gbrown.org
|
||||||
|
NETWORK=node_apps
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
docker/Dockerfile
|
||||||
|
```
|
||||||
|
# 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 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"]
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
docker/docker-compose.yml
|
||||||
|
```yml
|
||||||
|
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
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
src/main.ts
|
||||||
|
```ts
|
||||||
|
import { bangs } from "./bang";
|
||||||
|
import "./global.css";
|
||||||
|
|
||||||
|
function noSearchDefaultPageRender() {
|
||||||
|
const app = document.querySelector<HTMLDivElement>("#app")!;
|
||||||
|
app.innerHTML = `
|
||||||
|
<main class="main-container">
|
||||||
|
<div class="content-container">
|
||||||
|
<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="${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>
|
||||||
|
</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() ?? "";
|
||||||
|
if (!query) {
|
||||||
|
noSearchDefaultPageRender();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
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(/!\S+\s*/i, "").trim();
|
||||||
|
|
||||||
|
// Format of the url is:
|
||||||
|
// https://www.google.com/search?q={{{s}}}
|
||||||
|
const searchUrl = selectedBang?.u.replace(
|
||||||
|
"{{{s}}}",
|
||||||
|
// Replace %2F with / to fix formats like "!ghr+t3dotgg/unduck"
|
||||||
|
encodeURIComponent(cleanQuery).replace(/%2F/g, "/")
|
||||||
|
);
|
||||||
|
if (!searchUrl) return null;
|
||||||
|
|
||||||
|
return searchUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
function doRedirect() {
|
||||||
|
const searchUrl = getBangredirectUrl();
|
||||||
|
if (!searchUrl) return;
|
||||||
|
window.location.replace(searchUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
doRedirect();
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
src/vite-env.d.ts
|
||||||
|
```ts
|
||||||
|
/// <reference types="vite/client" />
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
tsconfig.json
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2020",
|
||||||
|
"useDefineForClassFields": true,
|
||||||
|
"module": "ESNext",
|
||||||
|
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||||
|
"skipLibCheck": true,
|
||||||
|
|
||||||
|
/* Bundler mode */
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"moduleDetection": "force",
|
||||||
|
"noEmit": true,
|
||||||
|
|
||||||
|
/* Linting */
|
||||||
|
"strict": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"noUncheckedSideEffectImports": true
|
||||||
|
},
|
||||||
|
"include": ["src"]
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
vite.config.ts
|
||||||
|
```ts
|
||||||
|
import { defineConfig } from "vite";
|
||||||
|
import { VitePWA } from "vite-plugin-pwa";
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [
|
||||||
|
VitePWA({
|
||||||
|
registerType: "autoUpdate",
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
```
|
105
scripts/files_to_clipboard
Executable file
105
scripts/files_to_clipboard
Executable file
@ -0,0 +1,105 @@
|
|||||||
|
#!/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}")
|
||||||
|
|
||||||
|
markdown_text = '\n'.join(markdown_lines)
|
||||||
|
|
||||||
|
# Write markdown to file
|
||||||
|
output_file = 'output.md'
|
||||||
|
with open(output_file, 'w', encoding='utf-8') as f:
|
||||||
|
f.write(markdown_text)
|
||||||
|
print(f"\nMarkdown file '{output_file}' has been generated.")
|
||||||
|
|
||||||
|
# Copy markdown content to clipboard
|
||||||
|
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()
|
Loading…
x
Reference in New Issue
Block a user