Compare commits
1 commit
main
...
add-search
Author | SHA1 | Date | |
---|---|---|---|
|
0d8b5adf67 |
32 changed files with 6077 additions and 5893 deletions
27
README.md
27
README.md
|
@ -1,22 +1,14 @@
|
||||||
<p align="center">
|
# Spitfire Browser Website
|
||||||
<img src="https://weforgecode.xyz/Spitfire/Branding/raw/branch/main/icon5.svg" alt="Logo" width="64" height="64">
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p align="center" style="font-size: 32px;">
|
Spitfire Browser is a fast, secure, and elegant web browser built on Firefox. This repository contains the source code for the Spitfire Browser's official website.
|
||||||
<strong>Spitfire Browser Website</strong>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p align="center">
|
|
||||||
Unlike some other browser sites flexing with their 98.8% TypeScript (yikes), i keep it cool with minimal JavaScript just enough to get the job done.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p align="center">
|
|
||||||
Spitfire Browser's website is built without Next.js, TypeScript, and Tailwind CSS or any other BS.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
## TO-DO:
|
## TO-DO:
|
||||||
|
|
||||||
- [ ] Add browser download/screenshots for this web browser website (optional)
|
- [ ] Add screenshots
|
||||||
|
- [ ] Add search-engine test
|
||||||
|
- [ ] Add working downloads
|
||||||
|
- [X] Add blog/updates
|
||||||
|
- [ ] Add config file
|
||||||
|
|
||||||
### Blog entries should be fromated this way:
|
### Blog entries should be fromated this way:
|
||||||
|
|
||||||
|
@ -43,10 +35,9 @@ Vivamus luctus egestas leo. Phasellus faucibus molestie nisl. Etiam commodo dui
|
||||||
...
|
...
|
||||||
```
|
```
|
||||||
|
|
||||||
### Based on HTML template:
|
### Based on:
|
||||||
|
|
||||||
[Stellar](https://html5up.net/stellar) by HTML5 UP
|
|
||||||
|
|
||||||
|
Stellar by HTML5 UP
|
||||||
html5up.net | @ajlkn
|
html5up.net | @ajlkn
|
||||||
|
|
||||||
### Licence:
|
### Licence:
|
||||||
|
|
2
go.mod
2
go.mod
|
@ -1,4 +1,4 @@
|
||||||
module spitfire-browser-website
|
module my-web
|
||||||
|
|
||||||
go 1.18
|
go 1.18
|
||||||
|
|
||||||
|
|
133
main.go
133
main.go
|
@ -6,6 +6,7 @@ import (
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
@ -33,11 +34,6 @@ const (
|
||||||
discordWebhookURL = "YOUR_DISCORD_WEBHOOK_URL"
|
discordWebhookURL = "YOUR_DISCORD_WEBHOOK_URL"
|
||||||
)
|
)
|
||||||
|
|
||||||
/*
|
|
||||||
Spitfire Browser by Internet Addict (https://spitfirebrowser.com)
|
|
||||||
Free for personal and commercial use under the CCA 3.0 license (html5up.net/license)
|
|
||||||
*/
|
|
||||||
|
|
||||||
type Blog struct {
|
type Blog struct {
|
||||||
Name string
|
Name string
|
||||||
Entries []BlogEntry
|
Entries []BlogEntry
|
||||||
|
@ -105,17 +101,14 @@ func main() {
|
||||||
log.Fatalf("Error getting blogs: %v", err)
|
log.Fatalf("Error getting blogs: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start the periodic notification checker
|
|
||||||
go startNotificationChecker(10 * time.Minute)
|
|
||||||
|
|
||||||
// Start watching for changes in the data directory
|
// Start watching for changes in the data directory
|
||||||
go watchForChanges(dataDir)
|
go watchForChanges(dataDir)
|
||||||
|
|
||||||
// Serve static files (CSS, JS, etc.) from the /static directory
|
// Serve static files (CSS, JS, etc.) from the /static directory
|
||||||
http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir(staticDir))))
|
http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir(staticDir))))
|
||||||
|
|
||||||
// Custom handler to serve only directories and their contents under /news-assets/
|
// Serve files in the /data/news/ directory under /news-assets/
|
||||||
http.Handle("/news-assets/", http.StripPrefix("/news-assets/", http.HandlerFunc(serveDirectoriesOnly)))
|
http.Handle("/news-assets/", http.StripPrefix("/news-assets/", http.FileServer(http.Dir(dataDir+"/news/"))))
|
||||||
|
|
||||||
// Serve downloads.html at /downloads
|
// Serve downloads.html at /downloads
|
||||||
http.HandleFunc("/download", func(w http.ResponseWriter, r *http.Request) {
|
http.HandleFunc("/download", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
@ -127,6 +120,8 @@ func main() {
|
||||||
renderTemplate(w, "download-linux.html", nil)
|
renderTemplate(w, "download-linux.html", nil)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
http.HandleFunc("/suggestions", handleSuggestions)
|
||||||
|
|
||||||
// Route for generating the RSS feed for all blogs
|
// Route for generating the RSS feed for all blogs
|
||||||
http.HandleFunc("/rss", func(w http.ResponseWriter, r *http.Request) {
|
http.HandleFunc("/rss", func(w http.ResponseWriter, r *http.Request) {
|
||||||
siteURL := fmt.Sprintf("http://%s", r.Host) // or you can use a fixed base URL
|
siteURL := fmt.Sprintf("http://%s", r.Host) // or you can use a fixed base URL
|
||||||
|
@ -192,15 +187,6 @@ func main() {
|
||||||
log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", port), nil))
|
log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", port), nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
func startNotificationChecker(interval time.Duration) {
|
|
||||||
ticker := time.NewTicker(interval)
|
|
||||||
defer ticker.Stop()
|
|
||||||
|
|
||||||
for range ticker.C {
|
|
||||||
checkAndSendNotifications()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func renderTemplate(w http.ResponseWriter, tmpl string, data interface{}) {
|
func renderTemplate(w http.ResponseWriter, tmpl string, data interface{}) {
|
||||||
tmplPath := filepath.Join(templateDir, tmpl)
|
tmplPath := filepath.Join(templateDir, tmpl)
|
||||||
t, err := template.ParseFiles(tmplPath)
|
t, err := template.ParseFiles(tmplPath)
|
||||||
|
@ -221,41 +207,6 @@ func renderIndex(w http.ResponseWriter) {
|
||||||
renderTemplate(w, "index.html", nil)
|
renderTemplate(w, "index.html", nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper function to add loading="lazy" to all img tags
|
|
||||||
func injectLazyLoading(htmlContent string) string {
|
|
||||||
// Split the content by <img tags to process them individually
|
|
||||||
parts := strings.Split(htmlContent, "<img")
|
|
||||||
|
|
||||||
// Start with the first part which is before any <img tag
|
|
||||||
modifiedContent := parts[0]
|
|
||||||
|
|
||||||
// Iterate over the remaining parts
|
|
||||||
for _, part := range parts[1:] {
|
|
||||||
// Find the closing bracket of the img tag
|
|
||||||
endOfTag := strings.Index(part, ">")
|
|
||||||
if endOfTag == -1 {
|
|
||||||
// If no closing bracket is found, add the remaining part as is
|
|
||||||
modifiedContent += "<img" + part
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract the actual <img tag
|
|
||||||
imgTag := part[:endOfTag]
|
|
||||||
|
|
||||||
// Check if loading="lazy" is already present
|
|
||||||
if !strings.Contains(imgTag, "loading=") {
|
|
||||||
// Insert loading="lazy" before the closing of the img tag
|
|
||||||
imgTag = " loading=\"lazy\"" + imgTag
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rebuild the full content with the modified img tag
|
|
||||||
modifiedContent += "<img" + imgTag + part[endOfTag:]
|
|
||||||
}
|
|
||||||
|
|
||||||
return modifiedContent
|
|
||||||
}
|
|
||||||
|
|
||||||
// For redering HTML Blogs
|
|
||||||
func renderBlogEntry(w http.ResponseWriter, r *http.Request, blogName string, entryNumber int) {
|
func renderBlogEntry(w http.ResponseWriter, r *http.Request, blogName string, entryNumber int) {
|
||||||
var blog Blog
|
var blog Blog
|
||||||
for _, b := range blogs {
|
for _, b := range blogs {
|
||||||
|
@ -289,32 +240,34 @@ func renderBlogEntry(w http.ResponseWriter, r *http.Request, blogName string, en
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert .md to HTML
|
|
||||||
htmlContent := blackfriday.Run([]byte(entry.Content))
|
htmlContent := blackfriday.Run([]byte(entry.Content))
|
||||||
|
|
||||||
// Double check "/news-assets/" in URL of images
|
|
||||||
htmlContent = bytes.ReplaceAll(htmlContent, []byte("src=\"./"), []byte(fmt.Sprintf("src=\"/news-assets/%d/", entryNumber)))
|
htmlContent = bytes.ReplaceAll(htmlContent, []byte("src=\"./"), []byte(fmt.Sprintf("src=\"/news-assets/%d/", entryNumber)))
|
||||||
|
|
||||||
// Apply lazy loading to the generated HTML content
|
|
||||||
htmlContentWithLazyLoading := injectLazyLoading(string(htmlContent))
|
|
||||||
|
|
||||||
pageData := PageData{
|
pageData := PageData{
|
||||||
Title: entry.Title,
|
Title: entry.Title,
|
||||||
Date: entry.Date.Format("2006-01-02 15:04"),
|
Date: entry.Date.Format("2006-01-02 15:04"),
|
||||||
Desc: entry.Description,
|
Desc: entry.Description,
|
||||||
Author: entry.Author,
|
Author: entry.Author,
|
||||||
Content: template.HTML(htmlContentWithLazyLoading),
|
Content: template.HTML(htmlContent),
|
||||||
PrevLink: prevLink,
|
PrevLink: prevLink,
|
||||||
NextLink: nextLink,
|
NextLink: nextLink,
|
||||||
}
|
}
|
||||||
|
|
||||||
renderTemplate(w, "news.html", pageData)
|
renderTemplate(w, "news.html", pageData)
|
||||||
|
|
||||||
|
// Handle notifications if they haven't been sent yet
|
||||||
|
if !entry.Notified {
|
||||||
|
sendNotifications(entry)
|
||||||
|
entry.Notified = true
|
||||||
|
saveNotifiedEntry(entry.Number)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getBlogs(dir string) ([]Blog, error) {
|
func getBlogs(dir string) ([]Blog, error) {
|
||||||
var blogs []Blog
|
var blogs []Blog
|
||||||
|
|
||||||
files, err := os.ReadDir(dir)
|
files, err := ioutil.ReadDir(dir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -335,7 +288,7 @@ func getBlogs(dir string) ([]Blog, error) {
|
||||||
func getBlogEntries(dir string) (Blog, error) {
|
func getBlogEntries(dir string) (Blog, error) {
|
||||||
var entries []BlogEntry
|
var entries []BlogEntry
|
||||||
|
|
||||||
files, err := os.ReadDir(dir)
|
files, err := ioutil.ReadDir(dir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Blog{}, err
|
return Blog{}, err
|
||||||
}
|
}
|
||||||
|
@ -364,7 +317,7 @@ func getBlogEntries(dir string) (Blog, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseMarkdownFile(path string) (BlogEntry, error) {
|
func parseMarkdownFile(path string) (BlogEntry, error) {
|
||||||
content, err := os.ReadFile(path)
|
content, err := ioutil.ReadFile(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return BlogEntry{}, err
|
return BlogEntry{}, err
|
||||||
}
|
}
|
||||||
|
@ -482,59 +435,6 @@ func watchForChanges(dir string) {
|
||||||
select {}
|
select {}
|
||||||
}
|
}
|
||||||
|
|
||||||
// serveDirectoriesOnly handles requests and only serves directories and their contents.
|
|
||||||
func serveDirectoriesOnly(w http.ResponseWriter, r *http.Request) {
|
|
||||||
requestedPath := filepath.Join(dataDir, "news", r.URL.Path)
|
|
||||||
|
|
||||||
// Check if the requested path is a directory
|
|
||||||
fileInfo, err := os.Stat(requestedPath)
|
|
||||||
if err != nil {
|
|
||||||
http.NotFound(w, r) // If the path doesn't exist, return a 404
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Block access to any files at the root level or the .git directory
|
|
||||||
if isRootLevel(requestedPath) || isRestrictedDirectory(requestedPath) {
|
|
||||||
http.NotFound(w, r) // Block access to root-level files like .md and the .git directory
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Block access to any .md files at the root level
|
|
||||||
if isRootLevel(requestedPath) && strings.HasSuffix(requestedPath, ".md") {
|
|
||||||
http.NotFound(w, r) // Block access to root-level .md files
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the path is a directory, serve its contents
|
|
||||||
if fileInfo.IsDir() {
|
|
||||||
http.FileServer(http.Dir(requestedPath)).ServeHTTP(w, r)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Serve the file within the subdirectory
|
|
||||||
http.FileServer(http.Dir(filepath.Join(dataDir, "news"))).ServeHTTP(w, r)
|
|
||||||
}
|
|
||||||
|
|
||||||
// isRootLevel checks if the file is at the root level of /news-assets/
|
|
||||||
func isRootLevel(path string) bool {
|
|
||||||
relPath, err := filepath.Rel(filepath.Join(dataDir, "news"), path)
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// The relative path should not contain any slashes if it's at the root level
|
|
||||||
return !strings.Contains(relPath, string(os.PathSeparator))
|
|
||||||
}
|
|
||||||
|
|
||||||
// isRestrictedDirectory checks if the path is within a restricted directory like .git
|
|
||||||
func isRestrictedDirectory(path string) bool {
|
|
||||||
// Normalize the path
|
|
||||||
cleanPath := filepath.Clean(path)
|
|
||||||
|
|
||||||
// Check if the path contains .git directory or other restricted directories
|
|
||||||
return strings.Contains(cleanPath, string(os.PathSeparator)+".git") || strings.HasSuffix(cleanPath, ".git")
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleFileChange(path string, isNew bool) {
|
func handleFileChange(path string, isNew bool) {
|
||||||
if filepath.Ext(path) == ".md" {
|
if filepath.Ext(path) == ".md" {
|
||||||
creationTimesM.Lock()
|
creationTimesM.Lock()
|
||||||
|
@ -546,7 +446,6 @@ func handleFileChange(path string, isNew bool) {
|
||||||
dir := filepath.Dir(path)
|
dir := filepath.Dir(path)
|
||||||
blogName := filepath.Base(dir)
|
blogName := filepath.Base(dir)
|
||||||
updateBlogEntries(blogName, path)
|
updateBlogEntries(blogName, path)
|
||||||
checkAndSendNotifications()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
52
printing.go
Normal file
52
printing.go
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var LogLevel = 1
|
||||||
|
|
||||||
|
// printDebug logs debug-level messages when LogLevel is set to 4.
|
||||||
|
func printDebug(format string, args ...interface{}) {
|
||||||
|
if LogLevel >= 4 {
|
||||||
|
logMessage("DEBUG", format, args...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// printInfo logs info-level messages when LogLevel is set to 3 or higher.
|
||||||
|
func printInfo(format string, args ...interface{}) {
|
||||||
|
if LogLevel >= 3 {
|
||||||
|
logMessage("INFO", format, args...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// printWarn logs warning-level messages when LogLevel is set to 2 or higher.
|
||||||
|
func printWarn(format string, args ...interface{}) {
|
||||||
|
if LogLevel >= 2 {
|
||||||
|
logMessage("WARN", format, args...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// printErr logs error-level messages regardless of LogLevel.
|
||||||
|
func printErr(format string, args ...interface{}) {
|
||||||
|
if LogLevel >= 1 {
|
||||||
|
logMessage("ERROR", format, args...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// printMessage logs messages without a specific log level (e.g., general output).
|
||||||
|
func printMessage(format string, args ...interface{}) {
|
||||||
|
logMessage("", format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// logMessage handles the actual logging logic without using the default logger's timestamp.
|
||||||
|
func logMessage(level string, format string, args ...interface{}) {
|
||||||
|
timestamp := time.Now().Format("2006-01-02 15:04:05")
|
||||||
|
message := fmt.Sprintf(format, args...)
|
||||||
|
if level != "" {
|
||||||
|
fmt.Printf("[%s %s] %s\n", timestamp, level, message)
|
||||||
|
} else {
|
||||||
|
fmt.Printf("[%s] %s\n", timestamp, message)
|
||||||
|
}
|
||||||
|
}
|
63
rss.go
63
rss.go
|
@ -3,11 +3,10 @@ package main
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gorilla/feeds"
|
"github.com/gorilla/feeds"
|
||||||
"github.com/russross/blackfriday/v2"
|
"github.com/russross/blackfriday/v2" // Import the Markdown library
|
||||||
)
|
)
|
||||||
|
|
||||||
func generateAtomFeed(w http.ResponseWriter, blogs []Blog, siteURL string) {
|
func generateAtomFeed(w http.ResponseWriter, blogs []Blog, siteURL string) {
|
||||||
|
@ -19,50 +18,30 @@ func generateAtomFeed(w http.ResponseWriter, blogs []Blog, siteURL string) {
|
||||||
Created: time.Now(),
|
Created: time.Now(),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add self link
|
|
||||||
feed.Link = &feeds.Link{Href: fmt.Sprintf("%s/rss", siteURL), Rel: "self"}
|
|
||||||
|
|
||||||
for _, blog := range blogs {
|
for _, blog := range blogs {
|
||||||
for _, entry := range blog.Entries {
|
for _, entry := range blog.Entries {
|
||||||
// Convert Markdown content to HTML
|
// Convert Markdown content to HTML
|
||||||
htmlContent := blackfriday.Run([]byte(entry.Content))
|
htmlContent := blackfriday.Run([]byte(entry.Content))
|
||||||
|
|
||||||
// Ensure all image paths are absolute URLs (Idiot proofing)
|
|
||||||
absoluteContent := strings.ReplaceAll(string(htmlContent), "src=\"/", fmt.Sprintf("src=\"%s/", siteURL))
|
|
||||||
|
|
||||||
// Ensure unique and stable ID
|
|
||||||
entryID := fmt.Sprintf("%s/%s/%d", siteURL, blog.Name, entry.Number)
|
|
||||||
|
|
||||||
// Create a summary if needed (using the first 200 characters of the content, for example)
|
|
||||||
summary := entry.Description
|
|
||||||
if summary == "" {
|
|
||||||
if len(entry.Content) > 200 {
|
|
||||||
summary = entry.Content[:200] + "..."
|
|
||||||
} else {
|
|
||||||
summary = entry.Content
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
feed.Items = append(feed.Items, &feeds.Item{
|
feed.Items = append(feed.Items, &feeds.Item{
|
||||||
Title: entry.Title,
|
Title: entry.Title,
|
||||||
Link: &feeds.Link{Href: entryID, Rel: "alternate"},
|
Link: &feeds.Link{Href: fmt.Sprintf("%s/%s/%d", siteURL, blog.Name, entry.Number), Rel: "alternate"},
|
||||||
Description: summary, // This can be used as the summary
|
Description: entry.Description, // This can be used as the summary
|
||||||
Author: &feeds.Author{Name: entry.Author},
|
Author: &feeds.Author{Name: entry.Author},
|
||||||
Id: entryID,
|
Id: fmt.Sprintf("%s/%s/%d", siteURL, blog.Name, entry.Number),
|
||||||
Updated: entry.Date,
|
Updated: entry.Date,
|
||||||
Content: absoluteContent,
|
Content: string(htmlContent),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate Atom feed with the correct content-type and charset
|
|
||||||
w.Header().Set("Content-Type", "application/atom+xml; charset=UTF-8")
|
|
||||||
atom, err := feed.ToAtom()
|
atom, err := feed.ToAtom()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, "Error generating Atom feed", http.StatusInternalServerError)
|
http.Error(w, "Error generating Atom feed", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/atom+xml")
|
||||||
w.Write([]byte(atom))
|
w.Write([]byte(atom))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,47 +54,27 @@ func generateBlogAtomFeed(w http.ResponseWriter, blog Blog, siteURL string) {
|
||||||
Created: time.Now(),
|
Created: time.Now(),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add self link
|
|
||||||
feed.Link = &feeds.Link{Href: fmt.Sprintf("%s/%s/rss", siteURL, blog.Name), Rel: "self"}
|
|
||||||
|
|
||||||
for _, entry := range blog.Entries {
|
for _, entry := range blog.Entries {
|
||||||
// Convert Markdown content to HTML
|
// Convert Markdown content to HTML
|
||||||
htmlContent := blackfriday.Run([]byte(entry.Content))
|
htmlContent := blackfriday.Run([]byte(entry.Content))
|
||||||
|
|
||||||
// Ensure all image paths are absolute URLs (Idiot proofing)
|
|
||||||
absoluteContent := strings.ReplaceAll(string(htmlContent), "src=\"/", fmt.Sprintf("src=\"%s/", siteURL))
|
|
||||||
|
|
||||||
// Ensure unique and stable ID
|
|
||||||
entryID := fmt.Sprintf("%s/%s/%d", siteURL, blog.Name, entry.Number)
|
|
||||||
|
|
||||||
// Create a summary if needed (using the first 200 characters of the content, for example)
|
|
||||||
summary := entry.Description
|
|
||||||
if summary == "" {
|
|
||||||
if len(entry.Content) > 200 {
|
|
||||||
summary = entry.Content[:200] + "..."
|
|
||||||
} else {
|
|
||||||
summary = entry.Content
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
feed.Items = append(feed.Items, &feeds.Item{
|
feed.Items = append(feed.Items, &feeds.Item{
|
||||||
Title: entry.Title,
|
Title: entry.Title,
|
||||||
Link: &feeds.Link{Href: entryID, Rel: "alternate"},
|
Link: &feeds.Link{Href: fmt.Sprintf("%s/%s/%d", siteURL, blog.Name, entry.Number), Rel: "alternate"},
|
||||||
Description: summary, // This can be used as the summary
|
Description: entry.Description, // This can be used as the summary
|
||||||
Author: &feeds.Author{Name: entry.Author},
|
Author: &feeds.Author{Name: entry.Author},
|
||||||
Id: entryID,
|
Id: fmt.Sprintf("%s/%s/%d", siteURL, blog.Name, entry.Number),
|
||||||
Updated: entry.Date,
|
Updated: entry.Date,
|
||||||
Content: absoluteContent,
|
Content: string(htmlContent),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate Atom feed with the correct content-type and charset
|
|
||||||
w.Header().Set("Content-Type", "application/atom+xml; charset=UTF-8")
|
|
||||||
atom, err := feed.ToAtom()
|
atom, err := feed.ToAtom()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, "Error generating Atom feed", http.StatusInternalServerError)
|
http.Error(w, "Error generating Atom feed", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/atom+xml")
|
||||||
w.Write([]byte(atom))
|
w.Write([]byte(atom))
|
||||||
}
|
}
|
||||||
|
|
21
run.bat
21
run.bat
|
@ -1,21 +0,0 @@
|
||||||
@echo off
|
|
||||||
|
|
||||||
REM Default values
|
|
||||||
set PORT=8080
|
|
||||||
|
|
||||||
REM Parse command-line arguments
|
|
||||||
:parse_args
|
|
||||||
if "%~1"=="" goto run_app
|
|
||||||
if "%~1"=="-p" (
|
|
||||||
set PORT=%~2
|
|
||||||
shift
|
|
||||||
shift
|
|
||||||
goto parse_args
|
|
||||||
) else (
|
|
||||||
echo Unknown parameter passed: %~1
|
|
||||||
exit /b 1
|
|
||||||
)
|
|
||||||
|
|
||||||
:run_app
|
|
||||||
REM Run the Go application with the parsed flags
|
|
||||||
go run discord.go rss.go telegram.go save.go main.go -p=%PORT%
|
|
2
run.sh
2
run.sh
|
@ -13,4 +13,4 @@ while [[ "$#" -gt 0 ]]; do
|
||||||
done
|
done
|
||||||
|
|
||||||
# Run the Go application with the parsed flags
|
# Run the Go application with the parsed flags
|
||||||
go run discord.go rss.go telegram.go save.go main.go -p=$PORT
|
go run printing.go suggestions.go discord.go rss.go telegram.go save.go main.go -p=$PORT
|
||||||
|
|
34
save.go
34
save.go
|
@ -2,49 +2,35 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func loadNotifiedEntries() {
|
func loadNotifiedEntries() {
|
||||||
file, err := os.Open(notifiedFilePath)
|
data, err := ioutil.ReadFile(notifiedFilePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
return
|
return // No file, no entries notified yet
|
||||||
}
|
}
|
||||||
log.Fatalf("Error loading notified entries: %v", err)
|
log.Fatalf("Error reading notified entries file: %v", err)
|
||||||
}
|
}
|
||||||
defer file.Close()
|
|
||||||
|
|
||||||
err = json.NewDecoder(file).Decode(¬ifiedEntries)
|
err = json.Unmarshal(data, ¬ifiedEntries)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Error decoding notified entries: %v", err)
|
log.Fatalf("Error parsing notified entries file: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func saveNotifiedEntry(entryNumber int) {
|
func saveNotifiedEntry(entryNumber int) {
|
||||||
notifiedEntries[entryNumber] = true
|
notifiedEntries[entryNumber] = true
|
||||||
file, err := os.Create(notifiedFilePath)
|
data, err := json.Marshal(notifiedEntries)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Error saving notified entries: %v", err)
|
log.Fatalf("Error serializing notified entries: %v", err)
|
||||||
}
|
}
|
||||||
defer file.Close()
|
|
||||||
|
|
||||||
err = json.NewEncoder(file).Encode(notifiedEntries)
|
err = ioutil.WriteFile(notifiedFilePath, data, 0644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Error encoding notified entries: %v", err)
|
log.Fatalf("Error writing notified entries file: %v", err)
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkAndSendNotifications() {
|
|
||||||
for _, blog := range blogs {
|
|
||||||
for _, entry := range blog.Entries {
|
|
||||||
if !entry.Notified && !time.Now().Before(entry.Date) {
|
|
||||||
sendNotifications(entry)
|
|
||||||
entry.Notified = true
|
|
||||||
saveNotifiedEntry(entry.Number)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,51 +1,13 @@
|
||||||
/* Global settings for images and videos */
|
img {
|
||||||
img, video {
|
|
||||||
display: block;
|
display: block;
|
||||||
width: 85%;
|
width: 100%;
|
||||||
height: auto;
|
|
||||||
margin: 20px auto; /* Centers the image/video horizontally and adds space above and below */
|
|
||||||
border-radius: 10px; /* Add rounded corners */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Exclude images inside the .icons class from global settings */
|
|
||||||
ul.icons img {
|
|
||||||
display: inline; /* Override display: block */
|
|
||||||
width: auto; /* Override width: 100% */
|
|
||||||
border-radius: 0; /* Remove rounded corners */
|
|
||||||
margin-top: 0; /* Remove top margin */
|
|
||||||
margin-bottom: 0; /* Remove bottom margin */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Align blog text and links */
|
|
||||||
.align-blog p, a, em {
|
.align-blog p, a, em {
|
||||||
text-align: left;
|
text-align: left;
|
||||||
font-size: 1em;
|
font-size: 1em;
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Increase margin top for all headings */
|
/* .align-blog h1, h2, h3, h4, h5 {
|
||||||
.align-blog h1,
|
} */
|
||||||
.align-blog h2,
|
|
||||||
.align-blog h3,
|
|
||||||
.align-blog h4,
|
|
||||||
.align-blog h5,
|
|
||||||
.align-blog h6 {
|
|
||||||
margin-top: 30px; /* Adjust this value to increase space above headings */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Icons specific styles */
|
|
||||||
ul.icons {
|
|
||||||
cursor: default;
|
|
||||||
list-style: none;
|
|
||||||
padding-left: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul.icons li {
|
|
||||||
display: inline-block;
|
|
||||||
padding: 0 0.65em 0 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul.icons li:last-child {
|
|
||||||
padding-right: 0 !important;
|
|
||||||
}
|
|
||||||
|
|
|
@ -25,18 +25,8 @@
|
||||||
overflow: visible;
|
overflow: visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
.the-slider img {
|
.reset-styles * {
|
||||||
width: 90%;
|
all: unset;
|
||||||
height: auto;
|
display: revert;
|
||||||
margin: 0 auto;
|
}
|
||||||
border-radius: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#particles-js {
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
z-index: 1; /* This value must be lower than other elements! */
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,139 +1,64 @@
|
||||||
/* Adjust the gallery wrapper */
|
.fancy-gallery {
|
||||||
.gallery-wrapper {
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
|
||||||
|
gap: 20px;
|
||||||
|
margin-top: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallery-item {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
border-radius: 15px;
|
||||||
|
box-shadow: 0 6px 15px rgba(0, 0, 0, 0.2);
|
||||||
|
transform: scale(1);
|
||||||
|
transition: transform 0.4s ease-in-out, filter 0.4s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallery-item img {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 800px;
|
height: 100%;
|
||||||
margin: 0 auto; /* Center the gallery */
|
object-fit: cover;
|
||||||
overflow: hidden; /* Ensure content is within bounds */
|
transition: transform 0.6s ease, filter 0.6s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.gallery-wrapper img {
|
.gallery-item::before {
|
||||||
display: block;
|
|
||||||
width: 100%;
|
|
||||||
height: auto;
|
|
||||||
border: 1px solid #dddddd3d;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Slick carousel specific styles */
|
|
||||||
.slick-prev, .slick-next {
|
|
||||||
background: #444; /* Button background color */
|
|
||||||
border: none; /* Remove any border */
|
|
||||||
border-radius: 50%; /* Make the buttons circular */
|
|
||||||
color: #fff; /* Text color */
|
|
||||||
font-size: 18px; /* Font size for the arrow */
|
|
||||||
height: 40px; /* Button height */
|
|
||||||
width: 40px; /* Button width */
|
|
||||||
line-height: 40px; /* Center text vertically */
|
|
||||||
text-align: center; /* Center text horizontally */
|
|
||||||
z-index: 1000; /* Make sure buttons are above the carousel images */
|
|
||||||
position: absolute;
|
|
||||||
top: 50%; /* Position vertically centered */
|
|
||||||
transform: translateY(-50%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.slick-prev {
|
|
||||||
left: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.slick-next {
|
|
||||||
right: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Center the dots under the image */
|
|
||||||
.slick-dots {
|
|
||||||
text-align: center;
|
|
||||||
margin-top: 15px; /* Space between image and dots */
|
|
||||||
padding-left: 0;
|
|
||||||
list-style: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.slick-dots li {
|
|
||||||
display: inline-block; /* Align dots horizontally */
|
|
||||||
margin: 0 8px; /* Space between dots */
|
|
||||||
}
|
|
||||||
|
|
||||||
.slick-dots li button {
|
|
||||||
background: #ccc; /* Dot background color */
|
|
||||||
border: none; /* Remove any border */
|
|
||||||
border-radius: 50%; /* Make the dots circular */
|
|
||||||
width: 12px; /* Dot width */
|
|
||||||
height: 12px; /* Dot height */
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.slick-dots li button::before {
|
|
||||||
content: '';
|
content: '';
|
||||||
display: block;
|
|
||||||
width: 16px; /* Adjusted circle width */
|
|
||||||
height: 16px; /* Adjusted circle height */
|
|
||||||
border-radius: 50%;
|
|
||||||
background-color: transparent;
|
|
||||||
border: 2px solid #ccc; /* Circle border color */
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 50%;
|
top: 0;
|
||||||
top: 50%;
|
left: 0;
|
||||||
transform: translate(-50%, -50%);
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(45deg, rgba(255, 0, 150, 0.3), rgba(0, 204, 255, 0.3));
|
||||||
|
mix-blend-mode: overlay;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.6s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.slick-dots li.slick-active button::before {
|
.gallery-item:hover {
|
||||||
background-color: #fff; /* Active dot background color */
|
transform: scale(1.05);
|
||||||
border-color: #fff; /* Active dot border color */
|
filter: brightness(1.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* This is what I get for global styling and using templates */
|
.gallery-item:hover img {
|
||||||
|
transform: scale(1.1) rotate(2deg);
|
||||||
/* Override the border-radius to remove rounded corners */
|
filter: grayscale(20%)
|
||||||
.slick-prev, .slick-next {
|
|
||||||
border-radius: 0 !important;
|
|
||||||
background: none !important;
|
|
||||||
box-shadow: none !important;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* If you want a specific shape, like a square or a different background */
|
.gallery-item:hover::before {
|
||||||
.slick-prev, .slick-next {
|
opacity: 1;
|
||||||
border-radius: 0 !important;
|
}
|
||||||
background: transparent !important;
|
/*
|
||||||
|
@keyframes pulse {
|
||||||
|
0%, 100% {
|
||||||
|
transform: scale(1);
|
||||||
|
box-shadow: 0 6px 15px rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
transform: scale(1.02);
|
||||||
|
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.3);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Ensure the active dot buttons are not rounded */
|
.gallery-item {
|
||||||
.slick-dots li button {
|
animation: pulse 5s infinite;
|
||||||
border-radius: 0 !important;
|
} */
|
||||||
background: none !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.slick-dots li button::before {
|
|
||||||
border-radius: 0 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* General button override */
|
|
||||||
button[type="button"][role="tab"] {
|
|
||||||
border-radius: 0 !important;
|
|
||||||
background: none !important;
|
|
||||||
box-shadow: none !important;
|
|
||||||
border: none !important;
|
|
||||||
color: inherit !important;
|
|
||||||
padding: 0 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Restore the round shape for the dots */
|
|
||||||
.slick-dots li button {
|
|
||||||
border-radius: 50% !important;
|
|
||||||
background: #ccc !important;
|
|
||||||
width: 12px;
|
|
||||||
height: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Ensure the pseudo-element is also circular */
|
|
||||||
.slick-dots li button::before {
|
|
||||||
border-radius: 50% !important;
|
|
||||||
background-color: transparent;
|
|
||||||
border: 2px solid #ccc;
|
|
||||||
width: 16px;
|
|
||||||
height: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Style for the active dot */
|
|
||||||
.slick-dots li.slick-active button::before {
|
|
||||||
background-color: #fff;
|
|
||||||
border-color: #fff;
|
|
||||||
}
|
|
BIN
static/css/images/overlay.png
Normal file
BIN
static/css/images/overlay.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 20 KiB |
|
@ -5,8 +5,8 @@
|
||||||
@import 'assets/webfonts/6xKydSBYKcSV-LCoeQqfX1RYOo3ik4zwlxdu.woff2';
|
@import 'assets/webfonts/6xKydSBYKcSV-LCoeQqfX1RYOo3ik4zwlxdu.woff2';
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Spitfire Browser by Internet Addict (https://weforgecode.xyz/Spitfire/Website)
|
Stellar by HTML5 UP
|
||||||
Based on Stellar by HTML5 UP | @ajlkn
|
html5up.net | @ajlkn
|
||||||
Free for personal and commercial use under the CCA 3.0 license (html5up.net/license)
|
Free for personal and commercial use under the CCA 3.0 license (html5up.net/license)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@ -129,7 +129,7 @@ input, select, textarea {
|
||||||
|
|
||||||
body, input, select, textarea {
|
body, input, select, textarea {
|
||||||
font-family: "Source Sans Pro", Helvetica, sans-serif;
|
font-family: "Source Sans Pro", Helvetica, sans-serif;
|
||||||
font-size: 14pt;
|
font-size: 17pt;
|
||||||
font-weight: 300;
|
font-weight: 300;
|
||||||
line-height: 1.65;
|
line-height: 1.65;
|
||||||
}
|
}
|
||||||
|
@ -159,10 +159,10 @@ input, select, textarea {
|
||||||
}
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
-moz-transition: color 0.3s ease, border-bottom 0.3s ease;
|
-moz-transition: color 0.2s ease, border-bottom 0.2s ease;
|
||||||
-webkit-transition: color 0.3s ease, border-bottom 0.3s ease;
|
-webkit-transition: color 0.2s ease, border-bottom 0.2s ease;
|
||||||
-ms-transition: color 0.3s ease, border-bottom 0.3s ease;
|
-ms-transition: color 0.2s ease, border-bottom 0.2s ease;
|
||||||
transition: color 0.3s ease, border-bottom 0.3s ease;
|
transition: color 0.2s ease, border-bottom 0.2s ease;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
border-bottom: dotted 1px;
|
border-bottom: dotted 1px;
|
||||||
color: inherit;
|
color: inherit;
|
||||||
|
@ -1836,10 +1836,10 @@ input, select, textarea {
|
||||||
-webkit-appearance: none;
|
-webkit-appearance: none;
|
||||||
-ms-appearance: none;
|
-ms-appearance: none;
|
||||||
appearance: none;
|
appearance: none;
|
||||||
-moz-transition: background-color 0.3s ease-in-out, color 0.3s ease-in-out;
|
-moz-transition: background-color 0.2s ease-in-out, color 0.2s ease-in-out;
|
||||||
-webkit-transition: background-color 0.3s ease-in-out, color 0.3s ease-in-out;
|
-webkit-transition: background-color 0.2s ease-in-out, color 0.2s ease-in-out;
|
||||||
-ms-transition: background-color 0.3s ease-in-out, color 0.3s ease-in-out;
|
-ms-transition: background-color 0.2s ease-in-out, color 0.2s ease-in-out;
|
||||||
transition: background-color 0.3s ease-in-out, color 0.3s ease-in-out;
|
transition: background-color 0.2s ease-in-out, color 0.2s ease-in-out;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
border: 0;
|
border: 0;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
@ -2218,10 +2218,10 @@ input, select, textarea {
|
||||||
|
|
||||||
.icon {
|
.icon {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
-moz-transition: background-color 0.3s ease-in-out, color 0.3s ease-in-out;
|
-moz-transition: background-color 0.2s ease-in-out, color 0.2s ease-in-out;
|
||||||
-webkit-transition: background-color 0.3s ease-in-out, color 0.3s ease-in-out;
|
-webkit-transition: background-color 0.2s ease-in-out, color 0.2s ease-in-out;
|
||||||
-ms-transition: background-color 0.3s ease-in-out, color 0.3s ease-in-out;
|
-ms-transition: background-color 0.2s ease-in-out, color 0.2s ease-in-out;
|
||||||
transition: background-color 0.3s ease-in-out, color 0.3s ease-in-out;
|
transition: background-color 0.2s ease-in-out, color 0.2s ease-in-out;
|
||||||
border-bottom: none;
|
border-bottom: none;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
@ -3220,10 +3220,10 @@ input, select, textarea {
|
||||||
/* Nav */
|
/* Nav */
|
||||||
|
|
||||||
#nav {
|
#nav {
|
||||||
-moz-transition: background-color 0.3s ease, border-top-left-radius 0.3s ease, border-top-right-radius 0.3s ease, padding 0.3s ease;
|
-moz-transition: background-color 0.2s ease, border-top-left-radius 0.2s ease, border-top-right-radius 0.2s ease, padding 0.2s ease;
|
||||||
-webkit-transition: background-color 0.3s ease, border-top-left-radius 0.3s ease, border-top-right-radius 0.3s ease, padding 0.3s ease;
|
-webkit-transition: background-color 0.2s ease, border-top-left-radius 0.2s ease, border-top-right-radius 0.2s ease, padding 0.2s ease;
|
||||||
-ms-transition: background-color 0.3s ease, border-top-left-radius 0.3s ease, border-top-right-radius 0.3s ease, padding 0.3s ease;
|
-ms-transition: background-color 0.2s ease, border-top-left-radius 0.2s ease, border-top-right-radius 0.2s ease, padding 0.2s ease;
|
||||||
transition: background-color 0.3s ease, border-top-left-radius 0.3s ease, border-top-right-radius 0.3s ease, padding 0.3s ease;
|
transition: background-color 0.2s ease, border-top-left-radius 0.2s ease, border-top-right-radius 0.2s ease, padding 0.2s ease;
|
||||||
background-color: #0a0a0a;
|
background-color: #0a0a0a;
|
||||||
color: #fafafa;
|
color: #fafafa;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
@ -3277,10 +3277,10 @@ input, select, textarea {
|
||||||
}
|
}
|
||||||
|
|
||||||
#nav ul li {
|
#nav ul li {
|
||||||
-moz-transition: margin 0.3s ease;
|
-moz-transition: margin 0.2s ease;
|
||||||
-webkit-transition: margin 0.3s ease;
|
-webkit-transition: margin 0.2s ease;
|
||||||
-ms-transition: margin 0.3s ease;
|
-ms-transition: margin 0.2s ease;
|
||||||
transition: margin 0.3s ease;
|
transition: margin 0.2s ease;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin: 0 0.35em;
|
margin: 0 0.35em;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
@ -3288,10 +3288,10 @@ input, select, textarea {
|
||||||
}
|
}
|
||||||
|
|
||||||
#nav ul li a {
|
#nav ul li a {
|
||||||
-moz-transition: font-size 0.3s ease;
|
-moz-transition: font-size 0.2s ease;
|
||||||
-webkit-transition: font-size 0.3s ease;
|
-webkit-transition: font-size 0.2s ease;
|
||||||
-ms-transition: font-size 0.3s ease;
|
-ms-transition: font-size 0.2s ease;
|
||||||
transition: font-size 0.3s ease;
|
transition: font-size 0.2s ease;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
height: 2.25em;
|
height: 2.25em;
|
||||||
line-height: 2.25em;
|
line-height: 2.25em;
|
||||||
|
@ -3301,19 +3301,15 @@ input, select, textarea {
|
||||||
box-shadow: inset 0 0 0 1px transparent;
|
box-shadow: inset 0 0 0 1px transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#nav ul li a:hover {
|
||||||
|
background-color: #080808;
|
||||||
|
}
|
||||||
|
|
||||||
#nav ul li a.active {
|
#nav ul li a.active {
|
||||||
transition: all 0.3s ease;
|
background-color: #000000;
|
||||||
background-color: #1a1a1a;
|
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
#nav ul li a:hover {
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
background-color: #000000;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#nav.alt {
|
#nav.alt {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 0;
|
||||||
|
|
237
static/css/search.css
Normal file
237
static/css/search.css
Normal file
|
@ -0,0 +1,237 @@
|
||||||
|
/* inter-300 - latin */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Inter';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 300;
|
||||||
|
src: local(''),
|
||||||
|
url('/static/fonts/inter-v12-latin-300.woff2') format('woff2'),
|
||||||
|
/* Chrome 26+, Opera 23+, Firefox 39+ */
|
||||||
|
url('/static/fonts/inter-v12-latin-300.woff') format('woff');
|
||||||
|
/* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* inter-regular - latin */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Inter';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
src: local(''),
|
||||||
|
url('/static/fonts/inter-v12-latin-regular.woff2') format('woff2'),
|
||||||
|
/* Chrome 26+, Opera 23+, Firefox 39+ */
|
||||||
|
url('/static/fonts/inter-v12-latin-regular.woff') format('woff');
|
||||||
|
/* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* inter-700 - latin */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Inter';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 700;
|
||||||
|
src: local(''),
|
||||||
|
url('/static/fonts/inter-v12-latin-700.woff2') format('woff2'),
|
||||||
|
/* Chrome 26+, Opera 23+, Firefox 39+ */
|
||||||
|
url('/static/fonts/inter-v12-latin-700.woff') format('woff');
|
||||||
|
/* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* material-icons-round-regular - latin */
|
||||||
|
@font-face {
|
||||||
|
font-display: swap;
|
||||||
|
/* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
|
||||||
|
font-family: 'Material Icons Round';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
src: url('/static/webfonts/material-icons-round-v108-latin-regular.woff2') format('woff2');
|
||||||
|
/* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
|
||||||
|
}
|
||||||
|
|
||||||
|
#search-wrapper-ico,
|
||||||
|
#clearSearch {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: var(--fg);
|
||||||
|
position: absolute;
|
||||||
|
top: 7px;
|
||||||
|
right: 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
#search-wrapper-ico:hover,
|
||||||
|
#clearSearch:hover {
|
||||||
|
transition: all .3s ease;
|
||||||
|
color: var(--blue);
|
||||||
|
}
|
||||||
|
|
||||||
|
#sub-search-wrapper-ico {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 17px;
|
||||||
|
/* will be set to 17px if icon pack can be loaded. */
|
||||||
|
padding-right: 0px;
|
||||||
|
margin-right: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-container {
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 10%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-container h1 {
|
||||||
|
font-size: 70px;
|
||||||
|
color: var(--font-fg);
|
||||||
|
font-family: 'Inter';
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-container input {
|
||||||
|
width: 90%;
|
||||||
|
color: var(--font-fg);
|
||||||
|
background-color: var(--search-bg-input);
|
||||||
|
font-size: inherit;
|
||||||
|
font-family: sans-serif;
|
||||||
|
border: none;
|
||||||
|
margin-right: 100%;
|
||||||
|
margin-left: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-container-results-btn {
|
||||||
|
display: flex;
|
||||||
|
color: var(--blue);
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-container-results-btn:hover button {
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
color: var(--blue);
|
||||||
|
}
|
||||||
|
|
||||||
|
#search-wrapper-ico,
|
||||||
|
#clearSearch {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: var(--fg);
|
||||||
|
position: absolute;
|
||||||
|
top: 7px;
|
||||||
|
right: 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-button-wrapper button:hover {
|
||||||
|
border: 1px solid #5f6368;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.wrapper {
|
||||||
|
margin: 0 auto;
|
||||||
|
background: var(--search-bg-input);
|
||||||
|
border-radius: 22px;
|
||||||
|
position: absolute;
|
||||||
|
width: 520px;
|
||||||
|
overflow: hidden;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: 2;
|
||||||
|
border: 1px solid var(--search-bg-input-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wrapper input {
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wrapper-results {
|
||||||
|
margin: 0 auto;
|
||||||
|
background: var(--search-bg-input);
|
||||||
|
border-radius: 22px;
|
||||||
|
position: absolute;
|
||||||
|
width: 628px;
|
||||||
|
overflow: hidden;
|
||||||
|
margin-top: 0px;
|
||||||
|
top: 18px;
|
||||||
|
left: 170px;
|
||||||
|
z-index: 2;
|
||||||
|
border: 1px solid var(--search-bg-input-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wrapper-results:hover,
|
||||||
|
.wrapper-results:focus-within,
|
||||||
|
.wrapper:hover,
|
||||||
|
.wrapper:focus-within {
|
||||||
|
box-shadow: 0px 6px 12px rgba(0, 0, 0, 0.24);
|
||||||
|
transition: all 0.3s cubic-bezier(.25, .8, .25, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.autocomplete {
|
||||||
|
padding: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.autocomplete ul {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.autocomplete ul li {
|
||||||
|
list-style: none;
|
||||||
|
opacity: 0;
|
||||||
|
display: none;
|
||||||
|
padding: 8px 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.show .autocomplete ul li {
|
||||||
|
opacity: 1;
|
||||||
|
display: block;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.show .autocomplete {
|
||||||
|
padding-top: 10px;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
color: var(--font-fg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.autocomplete ul li:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
background: var(--search-select);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@media only screen and (max-width: 750px) {
|
||||||
|
|
||||||
|
.wrapper {
|
||||||
|
width: 86%;
|
||||||
|
position: absolute;
|
||||||
|
float: none;
|
||||||
|
margin-top: 0px;
|
||||||
|
margin-bottom: 0px;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
display: block;
|
||||||
|
margin-top: 0px;
|
||||||
|
top: 110px;
|
||||||
|
left: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wrapper input {
|
||||||
|
padding: 10px;
|
||||||
|
max-width: 92%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.results-search-container {
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.results-search-container input {
|
||||||
|
width: 84%;
|
||||||
|
margin-right: 100%;
|
||||||
|
margin-left: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#search-wrapper-ico {
|
||||||
|
top: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
2
static/css/slick.min.css
vendored
2
static/css/slick.min.css
vendored
|
@ -1,2 +0,0 @@
|
||||||
.slick-slider{position:relative;display:block;box-sizing:border-box;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-touch-callout:none;-khtml-user-select:none;-ms-touch-action:pan-y;touch-action:pan-y;-webkit-tap-highlight-color:transparent}.slick-list{position:relative;display:block;overflow:hidden;margin:0;padding:0}.slick-list:focus{outline:0}.slick-list.dragging{cursor:pointer;cursor:hand}.slick-slider .slick-list,.slick-slider .slick-track{-webkit-transform:translate3d(0,0,0);-moz-transform:translate3d(0,0,0);-ms-transform:translate3d(0,0,0);-o-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}.slick-track{position:relative;top:0;left:0;display:block;margin-left:auto;margin-right:auto}.slick-track:after,.slick-track:before{display:table;content:''}.slick-track:after{clear:both}.slick-loading .slick-track{visibility:hidden}.slick-slide{display:none;float:left;height:100%;min-height:1px}[dir=rtl] .slick-slide{float:right}.slick-slide img{display:block}.slick-slide.slick-loading img{display:none}.slick-slide.dragging img{pointer-events:none}.slick-initialized .slick-slide{display:block}.slick-loading .slick-slide{visibility:hidden}.slick-vertical .slick-slide{display:block;height:auto;border:1px solid transparent}.slick-arrow.slick-hidden{display:none}
|
|
||||||
/*# sourceMappingURL=/sm/fb3ed351cd5c0f1f30f88778ee1f9b056598e6d25ac4fdcab1eebcd8be521cd9.map */
|
|
Binary file not shown.
Before Width: | Height: | Size: 81 KiB |
315
static/js/autocomplete.js
Normal file
315
static/js/autocomplete.js
Normal file
|
@ -0,0 +1,315 @@
|
||||||
|
/**
|
||||||
|
* @source: ./script.js (originally from araa-search on Github)
|
||||||
|
*
|
||||||
|
* @licstart The following is the entire license notice for the
|
||||||
|
* JavaScript code in this page.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2023 Extravi
|
||||||
|
*
|
||||||
|
* The JavaScript code in this page is free software: you can
|
||||||
|
* redistribute it and/or modify it under the terms of the GNU Affero
|
||||||
|
* General Public License as published by the Free Software Foundation,
|
||||||
|
* either version 3 of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* The code is distributed WITHOUT ANY WARRANTY; without even the
|
||||||
|
* implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||||
|
* See the GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* As additional permission under GNU Affero General Public License
|
||||||
|
* section 7, you may distribute non-source (e.g., minimized or compacted)
|
||||||
|
* forms of that code without the copy of the GNU Affero General Public
|
||||||
|
* License normally required by section 4, provided you include this
|
||||||
|
* license notice and a URL through which recipients can access the
|
||||||
|
* Corresponding Source.
|
||||||
|
*
|
||||||
|
* @licend The above is the entire license notice
|
||||||
|
* for the JavaScript code in this page.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Removes the 'Apply Settings' button for Javascript users,
|
||||||
|
// since changing any of the elements causes the settings to apply
|
||||||
|
// automatically.
|
||||||
|
let resultsSave = document.querySelector(".results-save");
|
||||||
|
if (resultsSave != null) {
|
||||||
|
resultsSave.style.display = "none";
|
||||||
|
}
|
||||||
|
|
||||||
|
const searchInput = document.getElementById('search-input');
|
||||||
|
const searchWrapper = document.querySelectorAll('.wrapper, .wrapper-results')[0];
|
||||||
|
const resultsWrapper = document.querySelector('.autocomplete');
|
||||||
|
const clearSearch = document.querySelector("#clearSearch");
|
||||||
|
|
||||||
|
async function getSuggestions(query) {
|
||||||
|
try {
|
||||||
|
const params = new URLSearchParams({ "q": query }).toString();
|
||||||
|
const response = await fetch(`/suggestions?${params}`);
|
||||||
|
const data = await response.json();
|
||||||
|
return data[1]; // Return only the array of suggestion strings
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let currentIndex = -1; // Keep track of the currently selected suggestion
|
||||||
|
|
||||||
|
let results = [];
|
||||||
|
searchInput.addEventListener('input', async () => {
|
||||||
|
let input = searchInput.value;
|
||||||
|
if (input.length) {
|
||||||
|
results = await getSuggestions(input);
|
||||||
|
}
|
||||||
|
renderResults(results);
|
||||||
|
currentIndex = -1; // Reset index when we return new results
|
||||||
|
});
|
||||||
|
|
||||||
|
searchInput.addEventListener("focus", async () => {
|
||||||
|
let input = searchInput.value;
|
||||||
|
if (results.length === 0 && input.length != 0) {
|
||||||
|
results = await getSuggestions(input);
|
||||||
|
}
|
||||||
|
renderResults(results);
|
||||||
|
})
|
||||||
|
|
||||||
|
clearSearch.style.visibility = "visible"; // Only show the clear search button for JS users.
|
||||||
|
clearSearch.addEventListener("click", () => {
|
||||||
|
searchInput.value = "";
|
||||||
|
searchInput.focus();
|
||||||
|
})
|
||||||
|
|
||||||
|
searchInput.addEventListener('keydown', (event) => {
|
||||||
|
if (event.key === 'ArrowUp' || event.key === 'ArrowDown') {
|
||||||
|
event.preventDefault(); // Prevent the cursor from moving in the search input
|
||||||
|
|
||||||
|
// Find the currently selected suggestion element
|
||||||
|
const selectedSuggestion = resultsWrapper.querySelector('.selected');
|
||||||
|
if (selectedSuggestion) {
|
||||||
|
selectedSuggestion.classList.remove('selected'); // Deselect the currently selected suggestion
|
||||||
|
}
|
||||||
|
|
||||||
|
// Increment or decrement the current index based on the arrow key pressed
|
||||||
|
if (event.key === 'ArrowUp') {
|
||||||
|
currentIndex--;
|
||||||
|
} else {
|
||||||
|
currentIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wrap around the index if it goes out of bounds
|
||||||
|
if (currentIndex < 0) {
|
||||||
|
currentIndex = resultsWrapper.querySelectorAll('li').length - 1;
|
||||||
|
} else if (currentIndex >= resultsWrapper.querySelectorAll('li').length) {
|
||||||
|
currentIndex = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Select the new suggestion
|
||||||
|
resultsWrapper.querySelectorAll('li')[currentIndex].classList.add('selected');
|
||||||
|
// Update the value of the search input
|
||||||
|
searchInput.value = resultsWrapper.querySelectorAll('li')[currentIndex].textContent;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function renderResults(results) {
|
||||||
|
if (!results || !results.length || !searchInput.value) {
|
||||||
|
return searchWrapper.classList.remove('show');
|
||||||
|
}
|
||||||
|
|
||||||
|
let content = '';
|
||||||
|
results.forEach((item) => {
|
||||||
|
content += `<li>${item}</li>`;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Only show the autocomplete suggestions if the search input has a non-empty value
|
||||||
|
if (searchInput.value) {
|
||||||
|
searchWrapper.classList.add('show');
|
||||||
|
}
|
||||||
|
resultsWrapper.innerHTML = `<ul>${content}</ul>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
resultsWrapper.addEventListener('click', (event) => {
|
||||||
|
if (event.target.tagName === 'LI') {
|
||||||
|
// Set the value of the search input to the clicked suggestion
|
||||||
|
searchInput.value = event.target.textContent;
|
||||||
|
// Reset the current index
|
||||||
|
currentIndex = -1;
|
||||||
|
// Submit the form
|
||||||
|
searchWrapper.querySelector('input[type="submit"]').click();
|
||||||
|
// Remove the show class from the search wrapper
|
||||||
|
searchWrapper.classList.remove('show');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
document.addEventListener("keypress", (event) => {
|
||||||
|
if (document.activeElement == searchInput) {
|
||||||
|
// Allow the '/' character to be pressed when searchInput is active
|
||||||
|
} else if (document.querySelector(".calc") != null) {
|
||||||
|
// Do nothing if the calculator is available, so the division keybinding
|
||||||
|
// will still work
|
||||||
|
}
|
||||||
|
else if (event.key == "/") {
|
||||||
|
event.preventDefault();
|
||||||
|
searchInput.focus();
|
||||||
|
searchInput.selectionStart = searchInput.selectionEnd = searchInput.value.length;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Add event listener to hide autocomplete suggestions when clicking outside of search-input or wrapper
|
||||||
|
document.addEventListener('click', (event) => {
|
||||||
|
// Check if the target of the event is the search-input or any of its ancestors
|
||||||
|
if (!searchInput.contains(event.target) && !searchWrapper.contains(event.target)) {
|
||||||
|
// Remove the show class from the search wrapper
|
||||||
|
searchWrapper.classList.remove('show');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Load material icons. If the file cannot be loaded,
|
||||||
|
// skip them and put a warning in the console.
|
||||||
|
const font = new FontFace('Material Icons Round', 'url("/fonts/material-icons-round-v108-latin-regular.woff2") format("woff2")');
|
||||||
|
font.load().then(() => {
|
||||||
|
const icons = document.getElementsByClassName('material-icons-round');
|
||||||
|
|
||||||
|
// Display all icons.
|
||||||
|
for (let icon of icons) {
|
||||||
|
icon.style.visibility = 'visible';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure icons for the different types of searches are sized correctly.
|
||||||
|
document.querySelectorAll('#sub-search-wrapper-ico').forEach((el) => {
|
||||||
|
el.style.fontSize = '17px';
|
||||||
|
});
|
||||||
|
}).catch(() => {
|
||||||
|
console.warn('Failed to load Material Icons Round. Hiding any icons using said pack.');
|
||||||
|
});
|
||||||
|
|
||||||
|
// load image after server side processing
|
||||||
|
window.addEventListener('DOMContentLoaded', function () {
|
||||||
|
var knoTitleElement = document.getElementById('kno_title');
|
||||||
|
var kno_title = knoTitleElement.dataset.knoTitle;
|
||||||
|
fetch(kno_title)
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
const pageId = Object.keys(data.query.pages)[0];
|
||||||
|
const thumbnailSource = data.query.pages[pageId].thumbnail.source;
|
||||||
|
const url = "/img_proxy?url=" + thumbnailSource;
|
||||||
|
|
||||||
|
// update the img tag with url and add kno_wiki_show
|
||||||
|
var imgElement = document.querySelector('.kno_wiki');
|
||||||
|
imgElement.src = url;
|
||||||
|
imgElement.classList.add('kno_wiki_show');
|
||||||
|
|
||||||
|
console.log(url);
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.log('Error fetching data:', error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
|
|
||||||
|
if (document.querySelectorAll(".search-active")[1].getAttribute("value") === "image") {
|
||||||
|
|
||||||
|
// image viewer for image search
|
||||||
|
const closeButton = document.querySelector('.image-close');
|
||||||
|
const imageView = document.querySelector('.image_view');
|
||||||
|
const images = document.querySelector('.images');
|
||||||
|
const viewImageImg = document.querySelector('.view-image-img');
|
||||||
|
const imageSource = document.querySelector('.image-source');
|
||||||
|
const imageFull = document.querySelector(".full-size");
|
||||||
|
const imageProxy = document.querySelector('.proxy-size');
|
||||||
|
const imageViewerLink = document.querySelector('.image-viewer-link');
|
||||||
|
const imageSize = document.querySelector('.image-size');
|
||||||
|
const fullImageSize = document.querySelector(".full-image-size");
|
||||||
|
const imageAlt = document.querySelector('.image-alt');
|
||||||
|
const openImageViewer = document.querySelectorAll('.open-image-viewer');
|
||||||
|
const imageBefore = document.querySelector('.image-before');
|
||||||
|
const imageNext = document.querySelector('.image-next');
|
||||||
|
let currentImageIndex = 0;
|
||||||
|
|
||||||
|
closeButton.addEventListener('click', function () {
|
||||||
|
imageView.classList.remove('image_show');
|
||||||
|
imageView.classList.add('image_hide');
|
||||||
|
for (const image of document.querySelectorAll(".image_selected")) {
|
||||||
|
image.classList = ['image'];
|
||||||
|
}
|
||||||
|
images.classList.add('images_viewer_hidden');
|
||||||
|
});
|
||||||
|
|
||||||
|
openImageViewer.forEach((image, index) => {
|
||||||
|
image.addEventListener('click', function (event) {
|
||||||
|
event.preventDefault();
|
||||||
|
currentImageIndex = index;
|
||||||
|
showImage();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener('keydown', function (event) {
|
||||||
|
if (searchInput == document.activeElement)
|
||||||
|
return;
|
||||||
|
if (event.key === 'ArrowLeft') {
|
||||||
|
currentImageIndex = (currentImageIndex - 1 + openImageViewer.length) % openImageViewer.length;
|
||||||
|
showImage();
|
||||||
|
}
|
||||||
|
else if (event.key === 'ArrowRight') {
|
||||||
|
currentImageIndex = (currentImageIndex + 1) % openImageViewer.length;
|
||||||
|
showImage();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
imageBefore.addEventListener('click', function () {
|
||||||
|
currentImageIndex = (currentImageIndex - 1 + openImageViewer.length) % openImageViewer.length;
|
||||||
|
showImage();
|
||||||
|
});
|
||||||
|
|
||||||
|
imageNext.addEventListener('click', function () {
|
||||||
|
currentImageIndex = (currentImageIndex + 1) % openImageViewer.length;
|
||||||
|
showImage();
|
||||||
|
});
|
||||||
|
|
||||||
|
function showImage() {
|
||||||
|
for (const image of document.querySelectorAll(".image_selected")) {
|
||||||
|
image.classList = ['image'];
|
||||||
|
}
|
||||||
|
const current_image = document.querySelectorAll(".image")[currentImageIndex];
|
||||||
|
current_image.classList.add("image_selected");
|
||||||
|
var rect = current_image.getBoundingClientRect();
|
||||||
|
if (!(rect.top >= 0 && rect.left >= 0 &&
|
||||||
|
rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
|
||||||
|
rect.right <= (window.innerWidth || document.documentElement.clientWidth))) {
|
||||||
|
current_image.scrollIntoView(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
const src = openImageViewer[currentImageIndex].getAttribute('src');
|
||||||
|
const alt = openImageViewer[currentImageIndex].getAttribute('alt');
|
||||||
|
const data = openImageViewer[currentImageIndex].getAttribute('data');
|
||||||
|
const clickableLink = openImageViewer[currentImageIndex].closest('.clickable');
|
||||||
|
const href = clickableLink.getAttribute('href');
|
||||||
|
viewImageImg.src = src;
|
||||||
|
imageProxy.href = src;
|
||||||
|
imageFull.href = data;
|
||||||
|
imageSource.href = href;
|
||||||
|
imageSource.textContent = href;
|
||||||
|
imageViewerLink.href = href;
|
||||||
|
images.classList.remove('images_viewer_hidden');
|
||||||
|
imageView.classList.remove('image_hide');
|
||||||
|
imageView.classList.add('image_show');
|
||||||
|
imageAlt.textContent = alt;
|
||||||
|
fullImageSize.textContent = document.querySelector(".image_selected .resolution").textContent;
|
||||||
|
|
||||||
|
getImageSize(src).then(size => {
|
||||||
|
imageSize.textContent = size;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getImageSize(url) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const img = new Image();
|
||||||
|
img.onload = function () {
|
||||||
|
const size = `${this.width} x ${this.height}`;
|
||||||
|
resolve(size);
|
||||||
|
};
|
||||||
|
img.onerror = function () {
|
||||||
|
reject('Error loading image');
|
||||||
|
};
|
||||||
|
img.src = url;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
Spitfire Browser by Internet Addict (https://weforgecode.xyz/Spitfire/Website)
|
Stellar by HTML5 UP
|
||||||
Based on Stellar by HTML5 UP | @ajlkn
|
html5up.net | @ajlkn
|
||||||
Free for personal and commercial use under the CCA 3.0 license (html5up.net/license)
|
Free for personal and commercial use under the CCA 3.0 license (html5up.net/license)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
9
static/js/particles.min.js
vendored
9
static/js/particles.min.js
vendored
File diff suppressed because one or more lines are too long
|
@ -1,110 +0,0 @@
|
||||||
{
|
|
||||||
"particles": {
|
|
||||||
"number": {
|
|
||||||
"value": 160,
|
|
||||||
"density": {
|
|
||||||
"enable": true,
|
|
||||||
"value_area": 800
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"color": {
|
|
||||||
"value": "#ffffff"
|
|
||||||
},
|
|
||||||
"shape": {
|
|
||||||
"type": "circle",
|
|
||||||
"stroke": {
|
|
||||||
"width": 0,
|
|
||||||
"color": "#000000"
|
|
||||||
},
|
|
||||||
"polygon": {
|
|
||||||
"nb_sides": 5
|
|
||||||
},
|
|
||||||
"image": {
|
|
||||||
"src": "img/github.svg",
|
|
||||||
"width": 100,
|
|
||||||
"height": 100
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"opacity": {
|
|
||||||
"value": 1,
|
|
||||||
"random": true,
|
|
||||||
"anim": {
|
|
||||||
"enable": true,
|
|
||||||
"speed": 1,
|
|
||||||
"opacity_min": 0,
|
|
||||||
"sync": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"size": {
|
|
||||||
"value": 3,
|
|
||||||
"random": true,
|
|
||||||
"anim": {
|
|
||||||
"enable": false,
|
|
||||||
"speed": 4,
|
|
||||||
"size_min": 0.3,
|
|
||||||
"sync": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"line_linked": {
|
|
||||||
"enable": false,
|
|
||||||
"distance": 150,
|
|
||||||
"color": "#ffffff",
|
|
||||||
"opacity": 0.4,
|
|
||||||
"width": 1
|
|
||||||
},
|
|
||||||
"move": {
|
|
||||||
"enable": true,
|
|
||||||
"speed": 1,
|
|
||||||
"direction": "none",
|
|
||||||
"random": true,
|
|
||||||
"straight": false,
|
|
||||||
"out_mode": "out",
|
|
||||||
"bounce": false,
|
|
||||||
"attract": {
|
|
||||||
"enable": false,
|
|
||||||
"rotateX": 600,
|
|
||||||
"rotateY": 600
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"interactivity": {
|
|
||||||
"detect_on": "canvas",
|
|
||||||
"events": {
|
|
||||||
"onhover": {
|
|
||||||
"enable": true,
|
|
||||||
"mode": "bubble"
|
|
||||||
},
|
|
||||||
"onclick": {
|
|
||||||
"enable": true,
|
|
||||||
"mode": "repulse"
|
|
||||||
},
|
|
||||||
"resize": true
|
|
||||||
},
|
|
||||||
"modes": {
|
|
||||||
"grab": {
|
|
||||||
"distance": 400,
|
|
||||||
"line_linked": {
|
|
||||||
"opacity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"bubble": {
|
|
||||||
"distance": 250,
|
|
||||||
"size": 0,
|
|
||||||
"duration": 2,
|
|
||||||
"opacity": 0,
|
|
||||||
"speed": 3
|
|
||||||
},
|
|
||||||
"repulse": {
|
|
||||||
"distance": 400,
|
|
||||||
"duration": 0.4
|
|
||||||
},
|
|
||||||
"push": {
|
|
||||||
"particles_nb": 4
|
|
||||||
},
|
|
||||||
"remove": {
|
|
||||||
"particles_nb": 2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"retina_detect": true
|
|
||||||
}
|
|
|
@ -1,110 +0,0 @@
|
||||||
{
|
|
||||||
"particles": {
|
|
||||||
"number": {
|
|
||||||
"value": 160,
|
|
||||||
"density": {
|
|
||||||
"enable": true,
|
|
||||||
"value_area": 800
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"color": {
|
|
||||||
"value": "#ffffff"
|
|
||||||
},
|
|
||||||
"shape": {
|
|
||||||
"type": "circle",
|
|
||||||
"stroke": {
|
|
||||||
"width": 0,
|
|
||||||
"color": "#000000"
|
|
||||||
},
|
|
||||||
"polygon": {
|
|
||||||
"nb_sides": 5
|
|
||||||
},
|
|
||||||
"image": {
|
|
||||||
"src": "img/github.svg",
|
|
||||||
"width": 10,
|
|
||||||
"height": 10
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"opacity": {
|
|
||||||
"value": 0.7,
|
|
||||||
"random": true,
|
|
||||||
"anim": {
|
|
||||||
"enable": true,
|
|
||||||
"speed": 0.47948982282851027,
|
|
||||||
"opacity_min": 0,
|
|
||||||
"sync": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"size": {
|
|
||||||
"value": 2,
|
|
||||||
"random": true,
|
|
||||||
"anim": {
|
|
||||||
"enable": false,
|
|
||||||
"speed": 4,
|
|
||||||
"size_min": 0.3,
|
|
||||||
"sync": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"line_linked": {
|
|
||||||
"enable": false,
|
|
||||||
"distance": 150,
|
|
||||||
"color": "#ffffff",
|
|
||||||
"opacity": 0.4,
|
|
||||||
"width": 1
|
|
||||||
},
|
|
||||||
"move": {
|
|
||||||
"enable": true,
|
|
||||||
"speed": 0,
|
|
||||||
"direction": "none",
|
|
||||||
"random": true,
|
|
||||||
"straight": false,
|
|
||||||
"out_mode": "out",
|
|
||||||
"bounce": false,
|
|
||||||
"attract": {
|
|
||||||
"enable": false,
|
|
||||||
"rotateX": 600,
|
|
||||||
"rotateY": 600
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"interactivity": {
|
|
||||||
"detect_on": "canvas",
|
|
||||||
"events": {
|
|
||||||
"onhover": {
|
|
||||||
"enable": true,
|
|
||||||
"mode": "bubble"
|
|
||||||
},
|
|
||||||
"onclick": {
|
|
||||||
"enable": true,
|
|
||||||
"mode": "repulse"
|
|
||||||
},
|
|
||||||
"resize": true
|
|
||||||
},
|
|
||||||
"modes": {
|
|
||||||
"grab": {
|
|
||||||
"distance": 400,
|
|
||||||
"line_linked": {
|
|
||||||
"opacity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"bubble": {
|
|
||||||
"distance": 250,
|
|
||||||
"size": 0,
|
|
||||||
"duration": 2,
|
|
||||||
"opacity": 0,
|
|
||||||
"speed": 3
|
|
||||||
},
|
|
||||||
"repulse": {
|
|
||||||
"distance": 400,
|
|
||||||
"duration": 0.4
|
|
||||||
},
|
|
||||||
"push": {
|
|
||||||
"particles_nb": 4
|
|
||||||
},
|
|
||||||
"remove": {
|
|
||||||
"particles_nb": 2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"retina_detect": true
|
|
||||||
}
|
|
|
@ -1,16 +0,0 @@
|
||||||
$(document).ready(function(){
|
|
||||||
$('.the-slider').slick({
|
|
||||||
dots: true,
|
|
||||||
infinite: true,
|
|
||||||
speed: 300,
|
|
||||||
slidesToShow: 1,
|
|
||||||
adaptiveHeight: true,
|
|
||||||
autoplay: true,
|
|
||||||
autoplaySpeed: 2000,
|
|
||||||
prevArrow: '<button type="button" class="slick-prev">❮</button>',
|
|
||||||
nextArrow: '<button type="button" class="slick-next">❯</button>',
|
|
||||||
customPaging: function(slider, i) {
|
|
||||||
return '<button type="button">' + '</button>';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
1
static/js/slick.min.js
vendored
1
static/js/slick.min.js
vendored
File diff suppressed because one or more lines are too long
|
@ -1,7 +1,7 @@
|
||||||
///
|
///
|
||||||
// Spitfire Browser by Internet Addict (https://weforgecode.xyz/Spitfire/Website)
|
/// Stellar by HTML5 UP
|
||||||
// Based on Stellar by HTML5 UP | @ajlkn
|
/// html5up.net | @ajlkn
|
||||||
// Free for personal and commercial use under the CCA 3.0 license (html5up.net/license)
|
/// Free for personal and commercial use under the CCA 3.0 license (html5up.net/license)
|
||||||
///
|
///
|
||||||
|
|
||||||
/* Icons */
|
/* Icons */
|
||||||
|
|
BIN
static/webfonts/material-icons-round-v108-latin-regular.woff2
Normal file
BIN
static/webfonts/material-icons-round-v108-latin-regular.woff2
Normal file
Binary file not shown.
160
suggestions.go
Normal file
160
suggestions.go
Normal file
|
@ -0,0 +1,160 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
)
|
||||||
|
|
||||||
|
func handleSuggestions(w http.ResponseWriter, r *http.Request) {
|
||||||
|
query := r.URL.Query().Get("q")
|
||||||
|
if query == "" {
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
fmt.Fprintf(w, `["",[]]`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define the fallback sequence with Google lower in the hierarchy
|
||||||
|
suggestionSources := []func(string) []string{
|
||||||
|
fetchDuckDuckGoSuggestions,
|
||||||
|
fetchEdgeSuggestions,
|
||||||
|
fetchBraveSuggestions,
|
||||||
|
fetchEcosiaSuggestions,
|
||||||
|
fetchQwantSuggestions,
|
||||||
|
fetchStartpageSuggestions,
|
||||||
|
// fetchGoogleSuggestions, // I advise against it, but you can use it if you want to
|
||||||
|
}
|
||||||
|
|
||||||
|
var suggestions []string
|
||||||
|
for _, fetchFunc := range suggestionSources {
|
||||||
|
suggestions = fetchFunc(query)
|
||||||
|
if len(suggestions) > 0 {
|
||||||
|
printDebug("Suggestions found using %T", fetchFunc)
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
printWarn("%T did not return any suggestions or failed.", fetchFunc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(suggestions) == 0 {
|
||||||
|
printErr("All suggestion services failed. Returning empty response.")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the final suggestions as JSON
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
fmt.Fprintf(w, `["",%s]`, toJSONStringArray(suggestions))
|
||||||
|
}
|
||||||
|
|
||||||
|
func fetchGoogleSuggestions(query string) []string {
|
||||||
|
encodedQuery := url.QueryEscape(query)
|
||||||
|
url := fmt.Sprintf("http://suggestqueries.google.com/complete/search?client=firefox&q=%s", encodedQuery)
|
||||||
|
printDebug("Fetching suggestions from Google: %s", url)
|
||||||
|
return fetchSuggestionsFromURL(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
func fetchDuckDuckGoSuggestions(query string) []string {
|
||||||
|
encodedQuery := url.QueryEscape(query)
|
||||||
|
url := fmt.Sprintf("https://duckduckgo.com/ac/?q=%s&type=list", encodedQuery)
|
||||||
|
printDebug("Fetching suggestions from DuckDuckGo: %s", url)
|
||||||
|
return fetchSuggestionsFromURL(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
func fetchEdgeSuggestions(query string) []string {
|
||||||
|
encodedQuery := url.QueryEscape(query)
|
||||||
|
url := fmt.Sprintf("https://api.bing.com/osjson.aspx?query=%s", encodedQuery)
|
||||||
|
printDebug("Fetching suggestions from Edge (Bing): %s", url)
|
||||||
|
return fetchSuggestionsFromURL(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
func fetchBraveSuggestions(query string) []string {
|
||||||
|
encodedQuery := url.QueryEscape(query)
|
||||||
|
url := fmt.Sprintf("https://search.brave.com/api/suggest?q=%s", encodedQuery)
|
||||||
|
printDebug("Fetching suggestions from Brave: %s", url)
|
||||||
|
return fetchSuggestionsFromURL(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
func fetchEcosiaSuggestions(query string) []string {
|
||||||
|
encodedQuery := url.QueryEscape(query)
|
||||||
|
url := fmt.Sprintf("https://ac.ecosia.org/?q=%s&type=list", encodedQuery)
|
||||||
|
printDebug("Fetching suggestions from Ecosia: %s", url)
|
||||||
|
return fetchSuggestionsFromURL(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
func fetchQwantSuggestions(query string) []string {
|
||||||
|
encodedQuery := url.QueryEscape(query)
|
||||||
|
url := fmt.Sprintf("https://api.qwant.com/v3/suggest?q=%s", encodedQuery)
|
||||||
|
printDebug("Fetching suggestions from Qwant: %s", url)
|
||||||
|
return fetchSuggestionsFromURL(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
func fetchStartpageSuggestions(query string) []string {
|
||||||
|
encodedQuery := url.QueryEscape(query)
|
||||||
|
url := fmt.Sprintf("https://startpage.com/suggestions?q=%s", encodedQuery)
|
||||||
|
printDebug("Fetching suggestions from Startpage: %s", url)
|
||||||
|
return fetchSuggestionsFromURL(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
func fetchSuggestionsFromURL(url string) []string {
|
||||||
|
resp, err := http.Get(url)
|
||||||
|
if err != nil {
|
||||||
|
printWarn("Error fetching suggestions from %s: %v", url, err)
|
||||||
|
return []string{}
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
body, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
printWarn("Error reading response body from %s: %v", url, err)
|
||||||
|
return []string{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log the Content-Type for debugging
|
||||||
|
contentType := resp.Header.Get("Content-Type")
|
||||||
|
printDebug("Response Content-Type from %s: %s", url, contentType)
|
||||||
|
|
||||||
|
// Check if the body is non-empty
|
||||||
|
if len(body) == 0 {
|
||||||
|
printWarn("Received empty response body from %s", url)
|
||||||
|
return []string{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attempt to parse the response as JSON regardless of Content-Type
|
||||||
|
var parsedResponse []interface{}
|
||||||
|
if err := json.Unmarshal(body, &parsedResponse); err != nil {
|
||||||
|
printErr("Error parsing JSON from %s: %v", url, err)
|
||||||
|
printDebug("Response body: %s", string(body))
|
||||||
|
return []string{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure the response structure is as expected
|
||||||
|
if len(parsedResponse) < 2 {
|
||||||
|
printWarn("Unexpected response format from %v: %v", url, string(body))
|
||||||
|
return []string{}
|
||||||
|
}
|
||||||
|
|
||||||
|
suggestions := []string{}
|
||||||
|
if items, ok := parsedResponse[1].([]interface{}); ok {
|
||||||
|
for _, item := range items {
|
||||||
|
if suggestion, ok := item.(string); ok {
|
||||||
|
suggestions = append(suggestions, suggestion)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
printErr("Unexpected suggestions format in response from: %v", url)
|
||||||
|
}
|
||||||
|
|
||||||
|
return suggestions
|
||||||
|
}
|
||||||
|
|
||||||
|
func toJSONStringArray(strings []string) string {
|
||||||
|
result := ""
|
||||||
|
for i, str := range strings {
|
||||||
|
result += fmt.Sprintf(`"%s"`, str)
|
||||||
|
if i < len(strings)-1 {
|
||||||
|
result += ","
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "[" + result + "]"
|
||||||
|
}
|
|
@ -1,17 +1,17 @@
|
||||||
<!DOCTYPE HTML>
|
<!DOCTYPE HTML>
|
||||||
<!--
|
<!--
|
||||||
Spitfire Browser by Internet Addict (https://weforgecode.xyz/Spitfire/Website)
|
Stellar by HTML5 UP
|
||||||
Based on Stellar by HTML5 UP | @ajlkn
|
html5up.net | @ajlkn
|
||||||
Free for personal and commercial use under the CCA 3.0 license (html5up.net/license)
|
Free for personal and commercial use under the CCA 3.0 license (html5up.net/license)
|
||||||
-->
|
-->
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<link rel="icon" type="image/png" href="static/images/favicon.png">
|
<link rel="icon" type="image/png" href="favicon.png">
|
||||||
<title>Spitfire Browser - Downloads</title>
|
<title>Spitfire Browser - Downloads</title>
|
||||||
<meta content="🌐 Spitfire Browser" property="og:title" />
|
<meta content="🌐 Spitfire Browser" property="og:title" />
|
||||||
<meta content="Privacy respecting user friendly web browser." property="og:description" />
|
<meta content="Privacy respecting user friendly web browser." property="og:description" />
|
||||||
<meta content="https://spitfirebrowser.com/" property="og:url" />
|
<meta content="https://spitfirebrowser.com/" property="og:url" />
|
||||||
<meta content="https://spitfirebrowser.com/static/images/favicon.png" property="og:image" />
|
<meta content="https://spitfirebrowser.com/favicon.png" property="og:image" />
|
||||||
<meta content="#f1f1f1" data-react-helmet="true" name="theme-color" />
|
<meta content="#f1f1f1" data-react-helmet="true" name="theme-color" />
|
||||||
<meta name="darkreader-lock">
|
<meta name="darkreader-lock">
|
||||||
|
|
||||||
|
@ -31,7 +31,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Top Floater Footer -->
|
<!-- Top Floater Footer -->
|
||||||
<div style="background-color: red; color: white; text-align: center; padding: 10px 0; position: fixed; bottom: 0; width: 100%; z-index: 1000;">
|
<div style="background-color: red; color: white; text-align: center; padding: 10px 0; position: fixed; top: 0; width: 100%; z-index: 1000;">
|
||||||
🚧👷♂️ This site is under construction! Please check back later. 👷♀️🚧
|
🚧👷♂️ This site is under construction! Please check back later. 👷♀️🚧
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -1,17 +1,17 @@
|
||||||
<!DOCTYPE HTML>
|
<!DOCTYPE HTML>
|
||||||
<!--
|
<!--
|
||||||
Spitfire Browser by Internet Addict (https://weforgecode.xyz/Spitfire/Website)
|
Stellar by HTML5 UP
|
||||||
Based on Stellar by HTML5 UP | @ajlkn
|
html5up.net | @ajlkn
|
||||||
Free for personal and commercial use under the CCA 3.0 license (html5up.net/license)
|
Free for personal and commercial use under the CCA 3.0 license (html5up.net/license)
|
||||||
-->
|
-->
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<link rel="icon" type="image/png" href="static/images/favicon.png">
|
<link rel="icon" type="image/png" href="favicon.png">
|
||||||
<title>Spitfire Browser - Downloads</title>
|
<title>Spitfire Browser - Downloads</title>
|
||||||
<meta content="🌐 Spitfire Browser" property="og:title" />
|
<meta content="🌐 Spitfire Browser" property="og:title" />
|
||||||
<meta content="Privacy respecting user friendly web browser." property="og:description" />
|
<meta content="Privacy respecting user friendly web browser." property="og:description" />
|
||||||
<meta content="https://spitfirebrowser.com/" property="og:url" />
|
<meta content="https://spitfirebrowser.com/" property="og:url" />
|
||||||
<meta content="https://spitfirebrowser.com/static/images/favicon.png" property="og:image" />
|
<meta content="https://spitfirebrowser.com/favicon.png" property="og:image" />
|
||||||
<meta content="#f1f1f1" data-react-helmet="true" name="theme-color" />
|
<meta content="#f1f1f1" data-react-helmet="true" name="theme-color" />
|
||||||
<meta name="darkreader-lock">
|
<meta name="darkreader-lock">
|
||||||
|
|
||||||
|
@ -57,7 +57,7 @@
|
||||||
font-size: 1.2em;
|
font-size: 1.2em;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body class="is-preload">
|
<body class="is-preload">
|
||||||
|
|
||||||
<!-- Star Background Divs -->
|
<!-- Star Background Divs -->
|
||||||
|
@ -68,7 +68,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Top Floater Footer -->
|
<!-- Top Floater Footer -->
|
||||||
<div style="background-color: red; color: white; text-align: center; padding: 10px 0; position: fixed; bottom: 0; width: 100%; z-index: 1000;">
|
<div style="background-color: red; color: white; text-align: center; padding: 10px 0; position: fixed; top: 0; width: 100%; z-index: 1000;">
|
||||||
🚧👷♂️ This site is under construction! Please check back later. 👷♀️🚧
|
🚧👷♂️ This site is under construction! Please check back later. 👷♀️🚧
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -1,17 +1,17 @@
|
||||||
<!DOCTYPE HTML>
|
<!DOCTYPE HTML>
|
||||||
<!--
|
<!--
|
||||||
Spitfire Browser by Internet Addict (https://weforgecode.xyz/Spitfire/Website)
|
Spitfire Browser by Internet Addict
|
||||||
Based on Stellar by HTML5 UP | @ajlkn
|
Based on Stellar by HTML5 UP
|
||||||
Free for personal and commercial use under the CCA 3.0 license (html5up.net/license)
|
Free for personal and commercial use under the CCA 3.0 license (html5up.net/license)
|
||||||
-->
|
-->
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<link rel="icon" type="image/png" href="static/images/favicon.png">
|
<link rel="icon" type="image/png" href="favicon.png">
|
||||||
<title>Spitfire Browser - Fast. Secure. Elegant.</title>
|
<title>Spitfire Browser - Fast. Secure. Elegant.</title>
|
||||||
<meta content="🌐 Spitfire Browser" property="og:title" />
|
<meta content="🌐 Spitfire Browser" property="og:title" />
|
||||||
<meta content="Privacy respecting user friendly web browser." property="og:description" />
|
<meta content="Privacy respecting user friendly web browser." property="og:description" />
|
||||||
<meta content="https://spitfirebrowser.com/" property="og:url" />
|
<meta content="https://spitfirebrowser.com/" property="og:url" />
|
||||||
<meta content="https://spitfirebrowser.com/static/images/favicon.png" property="og:image" />
|
<meta content="https://spitfirebrowser.com/favicon.png" property="og:image" />
|
||||||
<meta content="#f1f1f1" data-react-helmet="true" name="theme-color" />
|
<meta content="#f1f1f1" data-react-helmet="true" name="theme-color" />
|
||||||
<meta name="darkreader-lock">
|
<meta name="darkreader-lock">
|
||||||
|
|
||||||
|
@ -21,23 +21,20 @@
|
||||||
<link rel="stylesheet" href="/static/css/stars.css" />
|
<link rel="stylesheet" href="/static/css/stars.css" />
|
||||||
<link rel="stylesheet" href="/static/css/fancy-gallery.css" />
|
<link rel="stylesheet" href="/static/css/fancy-gallery.css" />
|
||||||
<link rel="stylesheet" href="/static/css/extras.css" />
|
<link rel="stylesheet" href="/static/css/extras.css" />
|
||||||
<link rel="stylesheet" href="/static/css/slick.min.css" />
|
<link rel="stylesheet" href="/static/css/search.css" />
|
||||||
<noscript><link rel="stylesheet" href="/static/css/noscript.css" /></noscript>
|
<noscript><link rel="stylesheet" href="/static/css/noscript.css" /></noscript>
|
||||||
</head>
|
</head>
|
||||||
<body class="is-preload">
|
<body class="is-preload">
|
||||||
|
|
||||||
<!-- Star Background Divs -->
|
<!-- Star Background Divs -->
|
||||||
<div id="star-background">
|
<div id="star-background">
|
||||||
<div id="particles-js"></div>
|
|
||||||
<noscript>
|
|
||||||
<div id="stars"></div>
|
<div id="stars"></div>
|
||||||
<div id="stars2"></div>
|
<div id="stars2"></div>
|
||||||
<div id="stars3"></div>
|
<div id="stars3"></div>
|
||||||
</noscript>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Top Floater Footer -->
|
<!-- Top Floater Footer -->
|
||||||
<div style="background-color: red; color: white; text-align: center; padding: 10px 0; position: fixed; bottom: 0; width: 100%; z-index: 1000;">
|
<div style="background-color: red; color: white; text-align: center; padding: 10px 0; position: fixed; top: 0; width: 100%; z-index: 1000;">
|
||||||
🚧👷♂️ This site is under construction! Please check back later. 👷♀️🚧
|
🚧👷♂️ This site is under construction! Please check back later. 👷♀️🚧
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -51,7 +48,6 @@
|
||||||
</span>
|
</span>
|
||||||
<h1>Spitfire Browser</h1>
|
<h1>Spitfire Browser</h1>
|
||||||
<p>Fast. Secure. Elegant.</p>
|
<p>Fast. Secure. Elegant.</p>
|
||||||
<br>
|
|
||||||
<a href="/download" class="button">Download Now</a>
|
<a href="/download" class="button">Download Now</a>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
|
@ -60,8 +56,8 @@
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="#intro" class="active">Introduction</a></li>
|
<li><a href="#intro" class="active">Introduction</a></li>
|
||||||
<li><a href="#features">Features</a></li>
|
<li><a href="#features">Features</a></li>
|
||||||
<li><a href="#search">Search</a></li>
|
|
||||||
<li><a href="#security">Security</a></li>
|
<li><a href="#security">Security</a></li>
|
||||||
|
<li><a href="#cta">Get Started</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
|
@ -73,21 +69,18 @@
|
||||||
<header class="major">
|
<header class="major">
|
||||||
<h2>Welcome to Spitfire Browser</h2>
|
<h2>Welcome to Spitfire Browser</h2>
|
||||||
</header>
|
</header>
|
||||||
<p>Spitfire Browser is your gateway to a fast, secure, and elegant browsing experience. Built on Firefox, Spitfire includes essential features like ad blocking, enhanced security, and anonymous browsing with Ocásek search engine.</p>
|
<p>Spitfire Browser is your gateway to a fast, secure, and elegant browsing experience. Built on Firefox, Spitfire includes essential features like ad blocking, enhanced security, and anonymous browsing with Warp search engine.</p>
|
||||||
<br> <!-- br? I call it super.fancy.tailor.r.css.react.native.way.to.add.super.padding.on.top.of.images.T3.js.express.lodash.moment.axios.jquery.vue.nft.d3.angular.bloat.typescript.webpack.web3.babel.parcel.T4 -->
|
<div class="box alt">
|
||||||
<div class="gallery-wrapper">
|
<div class="fancy-gallery">
|
||||||
<div class="the-slider"> <!-- Is this fucking centered? -->
|
<div class="gallery-item"><img src="/static/images/screenshots/1.png" alt="Screenshot 1" /></div>
|
||||||
<div><img src="/static/images/screenshots/1.png" alt="Screenshot 1"></div>
|
<div class="gallery-item"><img src="/static/images/screenshots/1.png" alt="Screenshot 2" /></div>
|
||||||
<div><img src="/static/images/screenshots/1.png" alt="Screenshot 2"></div>
|
|
||||||
<div><img src="/static/images/screenshots/1.png" alt="Screenshot 3"></div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- <ul class="actions js-disabled">
|
<ul class="actions">
|
||||||
<li><a href="#intro" class="button">See More Screenshots</a></li>
|
<li><a href="#features" class="button">More Screenshots</a></li>
|
||||||
</ul> -->
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- Features Section -->
|
<!-- Features Section -->
|
||||||
<section id="features" class="main special">
|
<section id="features" class="main special">
|
||||||
<header class="major">
|
<header class="major">
|
||||||
|
@ -122,20 +115,11 @@
|
||||||
<li>
|
<li>
|
||||||
<span class="icon solid major style6 fa-search"></span>
|
<span class="icon solid major style6 fa-search"></span>
|
||||||
<h3>Anonymous Search</h3>
|
<h3>Anonymous Search</h3>
|
||||||
<p><a herf="#search">Ocásek search engine</a> allows you to browse Google results anonymously.</p>
|
<p><a herf="https://search.spitfirebrowser.com/">Warp search engine</a> allows you to browse Google results anonymously.</p>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- Try Search -->
|
|
||||||
<section id="search" class="main special">
|
|
||||||
<header class="major">
|
|
||||||
<h2>Try Searching</h2>
|
|
||||||
<p></p>
|
|
||||||
</header>
|
|
||||||
<iframe src="https://search.spitfirebrowser.com" width="100%" height="700px" style="border:none; border-radius: 10px; overflow:hidden;"></iframe>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<!-- Security Section -->
|
<!-- Security Section -->
|
||||||
<section id="security" class="main special">
|
<section id="security" class="main special">
|
||||||
<header class="major">
|
<header class="major">
|
||||||
|
@ -181,6 +165,28 @@
|
||||||
</footer>
|
</footer>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<!-- Search -->
|
||||||
|
<section id="search" class="main special">
|
||||||
|
<div class="reset-styles">
|
||||||
|
<form action="https://search.spitfirebrowser.com/search" class="search-container" method="post" autocomplete="off">
|
||||||
|
<h1>Ocásek</h1>
|
||||||
|
<div class="wrapper">
|
||||||
|
<input type="text" name="q" autofocus id="search-input" placeholder="Type to search..." />
|
||||||
|
<button id="search-wrapper-ico" class="material-icons-round" name="t" value="text" type="submit">search</button>
|
||||||
|
<div class="autocomplete">
|
||||||
|
<ul>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="search-button-wrapper">
|
||||||
|
<input type="hidden" name="p" value="1">
|
||||||
|
<!-- <button name="t" value="text" type="submit">Search Text</button>
|
||||||
|
<button name="t" value="image" type="submit">Search Images</button> -->
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
<!-- Get Started -->
|
<!-- Get Started -->
|
||||||
<section id="cta" class="main special">
|
<section id="cta" class="main special">
|
||||||
<header class="major">
|
<header class="major">
|
||||||
|
@ -209,13 +215,13 @@
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="https://weforgecode.xyz/Spitfire" class="icon alt">
|
<a href="#" class="icon alt">
|
||||||
<img src="/static/images/icons/brands/git-alt.svg" alt="Forgejo">
|
<img src="/static/images/icons/brands/git-alt.svg" alt="Forgejo">
|
||||||
<span class="label">Forgejo</span>
|
<span class="label">Forgejo</span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="https://www.youtube.com/@Internet.Addict" class="icon alt">
|
<a href="#" class="icon alt">
|
||||||
<img src="/static/images/icons/brands/youtube.svg" alt="YouTube">
|
<img src="/static/images/icons/brands/youtube.svg" alt="YouTube">
|
||||||
<span class="label">YouTube</span>
|
<span class="label">YouTube</span>
|
||||||
</a>
|
</a>
|
||||||
|
@ -230,23 +236,14 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Scripts -->
|
<!-- Scripts -->
|
||||||
<script src="/static/js/jquery.min.js"></script>
|
<script src="/static/js/jquery.min.js" defer></script>
|
||||||
<script src="/static/js/slick.min.js"></script>
|
|
||||||
<script src="/static/js/jquery.scrollex.min.js" defer></script>
|
<script src="/static/js/jquery.scrollex.min.js" defer></script>
|
||||||
<script src="/static/js/jquery.scrolly.min.js" defer></script>
|
<script src="/static/js/jquery.scrolly.min.js" defer></script>
|
||||||
<script src="/static/js/browser.min.js" defer></script>
|
<script src="/static/js/browser.min.js" defer></script>
|
||||||
<script src="/static/js/breakpoints.min.js" defer></script>
|
<script src="/static/js/breakpoints.min.js" defer></script>
|
||||||
<script src="/static/js/util.js" defer></script>
|
<script src="/static/js/util.js" defer></script>
|
||||||
<script src="/static/js/main.js" defer></script>
|
<script src="/static/js/main.js" defer></script>
|
||||||
<script src="/static/js/slick-conf.js" defer></script>
|
<script defer src="/static/js/autocomplete.js"></script>
|
||||||
<script src="/static/js/particles.min.js"></script>
|
|
||||||
<script>
|
|
||||||
/* particlesJS.load(@dom-id, @path-json, @callback (optional)); */
|
|
||||||
particlesJS.load('particles-js', '/static/js/particlesjs-config.json', function() {
|
|
||||||
console.log('callback - particles.js config loaded');
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
</script>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -1,17 +1,17 @@
|
||||||
<!DOCTYPE HTML>
|
<!DOCTYPE HTML>
|
||||||
<!--
|
<!--
|
||||||
Spitfire Browser by Internet Addict (https://weforgecode.xyz/Spitfire/Website)
|
Stellar by HTML5 UP
|
||||||
Based on Stellar by HTML5 UP | @ajlkn
|
html5up.net | @ajlkn
|
||||||
Free for personal and commercial use under the CCA 3.0 license (html5up.net/license)
|
Free for personal and commercial use under the CCA 3.0 license (html5up.net/license)
|
||||||
-->
|
-->
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<link rel="icon" type="image/png" href="static/images/favicon.png">
|
<link rel="icon" type="image/png" href="favicon.png">
|
||||||
<title>{{.Title}}</title>
|
<title>{{.Title}}</title>
|
||||||
<meta content="🌐 {{.Title}}" property="og:title" />
|
<meta content="🌐 {{.Title}}" property="og:title" />
|
||||||
<meta content="Privacy respecting user friendly web browser." property="og:description" />
|
<meta content="Privacy respecting user friendly web browser." property="og:description" />
|
||||||
<meta content="https://spitfirebrowser.com/" property="og:url" />
|
<meta content="https://spitfirebrowser.com/" property="og:url" />
|
||||||
<meta content="https://spitfirebrowser.com/static/images/favicon.png" property="og:image" />
|
<meta content="https://spitfirebrowser.com/favicon.png" property="og:image" />
|
||||||
<meta content="#f1f1f1" data-react-helmet="true" name="theme-color" />
|
<meta content="#f1f1f1" data-react-helmet="true" name="theme-color" />
|
||||||
<meta name="darkreader-lock">
|
<meta name="darkreader-lock">
|
||||||
|
|
||||||
|
@ -22,23 +22,20 @@
|
||||||
<link rel="stylesheet" href="/static/css/extras.css" />
|
<link rel="stylesheet" href="/static/css/extras.css" />
|
||||||
<link rel="stylesheet" href="/static/css/blog.css" />
|
<link rel="stylesheet" href="/static/css/blog.css" />
|
||||||
<noscript><link rel="stylesheet" href="/static/css/noscript.css" /></noscript>
|
<noscript><link rel="stylesheet" href="/static/css/noscript.css" /></noscript>
|
||||||
</head>
|
</head>
|
||||||
<body class="is-preload">
|
<body class="is-preload">
|
||||||
|
|
||||||
<!-- Star Background Divs -->
|
<!-- Star Background Divs -->
|
||||||
<div id="star-background">
|
<div id="star-background">
|
||||||
<div id="particles-js"></div>
|
|
||||||
<noscript>
|
|
||||||
<div id="stars"></div>
|
<div id="stars"></div>
|
||||||
<div id="stars2"></div>
|
<div id="stars2"></div>
|
||||||
<div id="stars3"></div>
|
<div id="stars3"></div>
|
||||||
</noscript>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Top Floater Footer
|
<!-- Top Floater Footer -->
|
||||||
<div style="background-color: red; color: white; text-align: center; padding: 10px 0; position: fixed; top: 0; width: 100%; z-index: 1000;">
|
<div style="background-color: red; color: white; text-align: center; padding: 10px 0; position: fixed; top: 0; width: 100%; z-index: 1000;">
|
||||||
🚧👷♂️ This site is under construction! Please check back later. 👷♀️🚧
|
🚧👷♂️ This site is under construction! Please check back later. 👷♀️🚧
|
||||||
</div> -->
|
</div>
|
||||||
|
|
||||||
<!-- Wrapper -->
|
<!-- Wrapper -->
|
||||||
<div id="wrapper">
|
<div id="wrapper">
|
||||||
|
@ -61,19 +58,13 @@
|
||||||
</header>
|
</header>
|
||||||
<footer class="major">
|
<footer class="major">
|
||||||
<ul class="actions special">
|
<ul class="actions special">
|
||||||
<li>
|
{{if .PrevLink}}
|
||||||
<a href="{{if .PrevLink}}{{.PrevLink}}{{else}}#{{end}}"
|
<li><a href="{{.PrevLink}}" class="button primary small">Previous</a></li>
|
||||||
class="button primary small {{if not .PrevLink}}disabled{{end}}">
|
{{end}}
|
||||||
Previous
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li><a href="/" class="button primary small">Home</a></li>
|
<li><a href="/" class="button primary small">Home</a></li>
|
||||||
<li>
|
{{if .NextLink}}
|
||||||
<a href="{{if .NextLink}}{{.NextLink}}{{else}}#{{end}}"
|
<li><a href="{{.NextLink}}" class="button primary small">Next</a></li>
|
||||||
class="button primary small {{if not .NextLink}}disabled{{end}}">
|
{{end}}
|
||||||
Next
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
</ul>
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
|
@ -119,13 +110,6 @@
|
||||||
<script src="/static/js/breakpoints.min.js" defer></script>
|
<script src="/static/js/breakpoints.min.js" defer></script>
|
||||||
<script src="/static/js/util.js" defer></script>
|
<script src="/static/js/util.js" defer></script>
|
||||||
<script src="/static/js/main.js" defer></script>
|
<script src="/static/js/main.js" defer></script>
|
||||||
<script src="/static/js/particles.min.js"></script>
|
|
||||||
<script>
|
|
||||||
/* particlesJS.load(@dom-id, @path-json, @callback (optional)); */
|
|
||||||
particlesJS.load('particles-js', '/static/js/particlesjs-config.json', function() {
|
|
||||||
console.log('callback - particles.js config loaded');
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
Loading…
Reference in a new issue