Compare commits
25 commits
blog-imple
...
main
Author | SHA1 | Date | |
---|---|---|---|
|
84a3c7e21e | ||
|
51bb3e0df8 | ||
|
6efb5b5d6f | ||
|
08774dd6f4 | ||
|
a77f93842a | ||
|
c81dd751e9 | ||
|
37a628ad09 | ||
0f3895bebd | |||
|
aeb7bd7634 | ||
|
0254061fa4 | ||
|
492e0765af | ||
|
469903dc9c | ||
|
0bc34bdbf0 | ||
|
9b3b5a5419 | ||
|
50a1a14f41 | ||
|
f2b72cc1b8 | ||
|
4a13ba939e | ||
|
5892c0af38 | ||
|
d69f561c55 | ||
|
b890637222 | ||
|
980008d8e5 | ||
|
ef91b8a8ad | ||
|
635d6589c9 | ||
|
1d820b5590 | ||
03c7678499 |
34 changed files with 5985 additions and 5244 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -1 +1,3 @@
|
||||||
elements.html
|
elements.html
|
||||||
|
notified_entries.json
|
||||||
|
data/
|
||||||
|
|
48
README.md
48
README.md
|
@ -1,20 +1,52 @@
|
||||||
# Spitfire Browser Website
|
<p align="center">
|
||||||
|
<img src="https://weforgecode.xyz/Spitfire/Branding/raw/branch/main/icon5.svg" alt="Logo" width="64" height="64">
|
||||||
|
</p>
|
||||||
|
|
||||||
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.
|
<p align="center" style="font-size: 32px;">
|
||||||
|
<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 screenshots
|
- [ ] Add browser download/screenshots for this web browser website (optional)
|
||||||
|
|
||||||
- [ ] Add search-engine test
|
### Blog entries should be fromated this way:
|
||||||
|
|
||||||
- [ ] Add working downloads
|
```md
|
||||||
|
[HEADER]
|
||||||
|
t: TITLE
|
||||||
|
d: SHORT-DESC
|
||||||
|
p: 2024-08-16 15:04
|
||||||
|
a: AUTHOR
|
||||||
|
[END]
|
||||||
|
|
||||||
- [ ] Add blog/updates
|
# lorem ipsum
|
||||||
|
|
||||||
### Based on:
|
Vestibulum fermentum tortor id mi. Nullam at arcu a est sollicitudin euismod. Nullam faucibus mi quis velit. Mauris dictum facilisis augue. Nullam sapien sem, ornare ac, nonummy non, lobortis a enim. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Etiam commodo dui eget wisi. Mauris dictum facilisis augue. Etiam posuere lacus quis dolor. In sem justo, commodo ut, suscipit at, pharetra vitae, orci.
|
||||||
|
|
||||||
|
Vivamus luctus egestas leo. Phasellus faucibus molestie nisl. Etiam commodo dui eget wisi. Donec vitae arcu. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Etiam neque. Suspendisse sagittis ultrices augue. Suspendisse nisl. Etiam sapien elit, consequat eget, tristique non, venenatis quis, ante. Phasellus rhoncus. Maecenas libero.
|
||||||
|
```
|
||||||
|
|
||||||
|
*default path:*
|
||||||
|
|
||||||
|
```
|
||||||
|
/data/news/1.md
|
||||||
|
/data/news/2.md
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
### Based on HTML template:
|
||||||
|
|
||||||
|
[Stellar](https://html5up.net/stellar) by HTML5 UP
|
||||||
|
|
||||||
Stellar by HTML5 UP
|
|
||||||
html5up.net | @ajlkn
|
html5up.net | @ajlkn
|
||||||
|
|
||||||
### Licence:
|
### Licence:
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
# lorem ipsum
|
|
||||||
|
|
||||||
Vestibulum fermentum tortor id mi. Nullam at arcu a est sollicitudin euismod. Nullam faucibus mi quis velit. Mauris dictum facilisis augue. Nullam sapien sem, ornare ac, nonummy non, lobortis a enim. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Etiam commodo dui eget wisi. Mauris dictum facilisis augue. Etiam posuere lacus quis dolor. In sem justo, commodo ut, suscipit at, pharetra vitae, orci.
|
|
||||||
|
|
||||||
Vivamus luctus egestas leo. Phasellus faucibus molestie nisl. Etiam commodo dui eget wisi. Donec vitae arcu. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Etiam neque. Suspendisse sagittis ultrices augue. Suspendisse nisl. Etiam sapien elit, consequat eget, tristique non, venenatis quis, ante. Phasellus rhoncus. Maecenas libero.
|
|
|
@ -1,3 +0,0 @@
|
||||||
# lorem ipsum
|
|
||||||
|
|
||||||
Fusce nibh. Duis sapien nunc, commodo et, interdum suscipit, sollicitudin et, dolor. Aliquam in lorem sit amet leo accumsan lacinia. Fusce wisi. Curabitur sagittis hendrerit ante. Mauris elementum mauris vitae tortor. Nulla accumsan, elit sit amet varius semper, nulla mauris mollis quam, tempor suscipit diam nulla vel leo. Phasellus rhoncus. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Duis sapien nunc, commodo et, interdum suscipit, sollicitudin et, dolor. Aenean placerat. Nullam feugiat, turpis at pulvinar vulputate, erat libero tristique tellus, nec bibendum odio risus sit amet ante. Cras elementum. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Maecenas lorem. Nam libero tempore, cum soluta nobis est eligendi optio cumque nihil impedit quo minus id quod maxime placeat facere possimus, omnis voluptas assumenda est, omnis dolor repellendus.
|
|
|
@ -1,3 +0,0 @@
|
||||||
# lorem ipsum
|
|
||||||
|
|
||||||
Etiam sapien elit, consequat eget, tristique non, venenatis quis, ante. Nulla turpis magna, cursus sit amet, suscipit a, interdum id, felis. Etiam dui sem, fermentum vitae, sagittis id, malesuada in, quam. Nullam faucibus mi quis velit. Aliquam erat volutpat. Duis bibendum, lectus ut viverra rhoncus, dolor nunc faucibus libero, eget facilisis enim ipsum id lacus. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis risus. In laoreet, magna id viverra tincidunt, sem odio bibendum justo, vel imperdiet sapien wisi sed libero. Nam sed tellus id magna elementum tincidunt. Donec vitae arcu. Etiam bibendum elit eget erat.
|
|
|
@ -1,3 +0,0 @@
|
||||||
# lorem ipsum
|
|
||||||
|
|
||||||
Mauris dictum facilisis augue. Nullam at arcu a est sollicitudin euismod. Praesent dapibus. Suspendisse sagittis ultrices augue. Integer imperdiet lectus quis justo. Duis bibendum, lectus ut viverra rhoncus, dolor nunc faucibus libero, eget facilisis enim ipsum id lacus. Pellentesque sapien. Fusce dui leo, imperdiet in, aliquam sit amet, feugiat eu, orci. Maecenas ipsum velit, consectetuer eu lobortis ut, dictum at dui. Etiam dictum tincidunt diam. Vestibulum fermentum tortor id mi. Phasellus enim erat, vestibulum vel, aliquam a, posuere eu, velit. Pellentesque pretium lectus id turpis. In sem justo, commodo ut, suscipit at, pharetra vitae, orci. Maecenas aliquet accumsan leo. Morbi leo mi, nonummy eget tristique non, rhoncus non leo. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
|
|
|
@ -7,8 +7,6 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
const discordWebhookURL = "YOUR_DISCORD_WEBHOOK_URL" // Replace with your Discord webhook URL
|
|
||||||
|
|
||||||
type DiscordWebhookPayload struct {
|
type DiscordWebhookPayload struct {
|
||||||
Content string `json:"content"`
|
Content string `json:"content"`
|
||||||
}
|
}
|
||||||
|
|
2
go.mod
2
go.mod
|
@ -1,4 +1,4 @@
|
||||||
module my-web
|
module spitfire-browser-website
|
||||||
|
|
||||||
go 1.18
|
go 1.18
|
||||||
|
|
||||||
|
|
478
main.go
478
main.go
|
@ -1,10 +1,11 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
@ -21,40 +22,54 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
dataDir = "./data"
|
dataDir = "./data"
|
||||||
templateDir = "./templates"
|
templateDir = "./templates"
|
||||||
staticDir = "./static"
|
staticDir = "./static"
|
||||||
defaultPort = 8080
|
notifiedFilePath = "./notified_entries.json"
|
||||||
botTokenEnv = "YOUR_TELEGRAM_BOT_TOKEN" // Replace with your bot's token or set via environment variable
|
defaultPort = 8080
|
||||||
pageSize = 5 // Number of blog entries per page
|
pageSize = 5 // Number of blog entries per page
|
||||||
|
botTokenEnv = "YOUR_TELEGRAM_BOT_TOKEN" // Replace with your bot's token or set via environment variable
|
||||||
|
YOUR_TELEGRAM_CHAT_ID = 0
|
||||||
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
type BlogEntry struct {
|
type BlogEntry struct {
|
||||||
Content string
|
Title string
|
||||||
Date time.Time
|
Description string
|
||||||
Number int
|
Author string
|
||||||
|
Content string
|
||||||
|
Date time.Time
|
||||||
|
Number int
|
||||||
|
Notified bool // To track if the notification was sent
|
||||||
}
|
}
|
||||||
|
|
||||||
type PageData struct {
|
type PageData struct {
|
||||||
Title string
|
Title string
|
||||||
Description string
|
Date string
|
||||||
BlogLinks []string
|
Desc string
|
||||||
Content template.HTML
|
Author string
|
||||||
PrevLink string
|
Content template.HTML
|
||||||
NextLink string
|
PrevLink string
|
||||||
|
NextLink string
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
blogs []Blog
|
blogs []Blog
|
||||||
bot *tgbotapi.BotAPI
|
bot *tgbotapi.BotAPI
|
||||||
port int
|
port int
|
||||||
creationTimes = make(map[string]time.Time)
|
creationTimes = make(map[string]time.Time)
|
||||||
creationTimesM sync.Mutex
|
creationTimesM sync.Mutex
|
||||||
|
notifiedEntries = make(map[int]bool)
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
@ -63,13 +78,19 @@ func init() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
// Parse the flags
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
|
// Load the notified entries from the file
|
||||||
|
loadNotifiedEntries()
|
||||||
|
|
||||||
|
// Retrieve the Telegram bot token from the environment variable
|
||||||
botToken := os.Getenv("TELEGRAM_BOT_TOKEN")
|
botToken := os.Getenv("TELEGRAM_BOT_TOKEN")
|
||||||
if botToken == "" {
|
if botToken == "" {
|
||||||
botToken = botTokenEnv
|
botToken = botTokenEnv
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Initialize the Telegram bot
|
||||||
var err error
|
var err error
|
||||||
bot, err = tgbotapi.NewBotAPI(botToken)
|
bot, err = tgbotapi.NewBotAPI(botToken)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -78,60 +99,78 @@ func main() {
|
||||||
go startTelegramBot()
|
go startTelegramBot()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Retrieve blog entries from the data directory
|
||||||
blogs, err = getBlogs(dataDir)
|
blogs, err = getBlogs(dataDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
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
|
||||||
go watchForChanges(dataDir)
|
go watchForChanges(dataDir)
|
||||||
|
|
||||||
// Serve static files (CSS, JS, etc.) from the /assets 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))))
|
||||||
|
|
||||||
// Route Handlers
|
// Custom handler to serve only directories and their contents under /news-assets/
|
||||||
|
http.Handle("/news-assets/", http.StripPrefix("/news-assets/", http.HandlerFunc(serveDirectoriesOnly)))
|
||||||
|
|
||||||
|
// Serve downloads.html at /downloads
|
||||||
|
http.HandleFunc("/download", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
renderTemplate(w, "download.html", nil)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Serve download-linux.html at /download-linux
|
||||||
|
http.HandleFunc("/download-linux", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
renderTemplate(w, "download-linux.html", nil)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Route for generating the RSS feed for all blogs
|
||||||
|
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
|
||||||
|
generateAtomFeed(w, blogs, siteURL)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Route for generating the RSS feed for a specific blog
|
||||||
|
http.HandleFunc("/rss/", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
pathParts := strings.Split(r.URL.Path, "/")
|
||||||
|
if len(pathParts) < 3 {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
blogName := pathParts[2]
|
||||||
|
for _, blog := range blogs {
|
||||||
|
if blog.Name == blogName {
|
||||||
|
siteURL := fmt.Sprintf("http://%s", r.Host) // or you can use a fixed base URL
|
||||||
|
generateBlogAtomFeed(w, blog, siteURL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
http.NotFound(w, r)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Define route handlers
|
||||||
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.URL.Path == "/" {
|
if r.URL.Path == "/" {
|
||||||
renderIndex(w)
|
renderIndex(w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.URL.Path == "/download" {
|
|
||||||
renderTemplate(w, "download.html", nil)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.URL.Path == "/download-linux" {
|
|
||||||
renderTemplate(w, "download-linux.html", nil)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
pathParts := strings.Split(r.URL.Path, "/")
|
pathParts := strings.Split(r.URL.Path, "/")
|
||||||
if len(pathParts) < 2 {
|
if len(pathParts) < 2 {
|
||||||
http.NotFound(w, r)
|
http.NotFound(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
siteURL := fmt.Sprintf("http://localhost:%d", port) // Define siteURL here
|
|
||||||
|
|
||||||
if pathParts[1] == "rss" {
|
|
||||||
generateRSSFeed(w, blogs, siteURL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if pathParts[1] == "all" {
|
|
||||||
page, err := strconv.Atoi(r.URL.Query().Get("page"))
|
|
||||||
if err != nil || page < 1 {
|
|
||||||
page = 1
|
|
||||||
}
|
|
||||||
renderAllBlogs(w, r, page)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(pathParts) == 3 {
|
if len(pathParts) == 3 {
|
||||||
blogName := pathParts[1]
|
blogName := pathParts[1]
|
||||||
entryNumber, err := strconv.Atoi(pathParts[2])
|
entryNumber, err := strconv.Atoi(pathParts[2])
|
||||||
if err == nil {
|
if err == nil {
|
||||||
renderBlogEntry(w, blogName, entryNumber)
|
renderBlogEntry(w, r, blogName, entryNumber)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -147,11 +186,21 @@ func main() {
|
||||||
http.NotFound(w, r)
|
http.NotFound(w, r)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Start the HTTP server on the specified port
|
||||||
serverURL := fmt.Sprintf("http://localhost:%d", port)
|
serverURL := fmt.Sprintf("http://localhost:%d", port)
|
||||||
log.Printf("Starting server on %s", serverURL)
|
log.Printf("Starting server on %s", serverURL)
|
||||||
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)
|
||||||
|
@ -172,7 +221,42 @@ func renderIndex(w http.ResponseWriter) {
|
||||||
renderTemplate(w, "index.html", nil)
|
renderTemplate(w, "index.html", nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func renderBlogEntry(w http.ResponseWriter, blogName string, entryNumber int) {
|
// 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) {
|
||||||
var blog Blog
|
var blog Blog
|
||||||
for _, b := range blogs {
|
for _, b := range blogs {
|
||||||
if b.Name == blogName {
|
if b.Name == blogName {
|
||||||
|
@ -185,78 +269,43 @@ func renderBlogEntry(w http.ResponseWriter, blogName string, entryNumber int) {
|
||||||
var prevLink, nextLink string
|
var prevLink, nextLink string
|
||||||
for i, e := range blog.Entries {
|
for i, e := range blog.Entries {
|
||||||
if e.Number == entryNumber {
|
if e.Number == entryNumber {
|
||||||
|
// Check if the entry date is in the future
|
||||||
|
if time.Now().Before(e.Date) {
|
||||||
|
http.NotFound(w, r) // If the post date is in the future, do not show the entry
|
||||||
|
return
|
||||||
|
}
|
||||||
entry = e
|
entry = e
|
||||||
if i > 0 {
|
|
||||||
|
// Check if the previous entry is visible
|
||||||
|
if i > 0 && !time.Now().Before(blog.Entries[i-1].Date) {
|
||||||
prevLink = fmt.Sprintf("/%s/%d", blog.Name, blog.Entries[i-1].Number)
|
prevLink = fmt.Sprintf("/%s/%d", blog.Name, blog.Entries[i-1].Number)
|
||||||
}
|
}
|
||||||
if i < len(blog.Entries)-1 {
|
|
||||||
|
// Check if the next entry is visible
|
||||||
|
if i < len(blog.Entries)-1 && !time.Now().Before(blog.Entries[i+1].Date) {
|
||||||
nextLink = fmt.Sprintf("/%s/%d", blog.Name, blog.Entries[i+1].Number)
|
nextLink = fmt.Sprintf("/%s/%d", blog.Name, blog.Entries[i+1].Number)
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Convert .md to HTML
|
||||||
htmlContent := blackfriday.Run([]byte(entry.Content))
|
htmlContent := blackfriday.Run([]byte(entry.Content))
|
||||||
content := fmt.Sprintf("<div class=\"blog-entry\"><div>%s</div></div>", htmlContent)
|
|
||||||
|
// Double check "/news-assets/" in URL of images
|
||||||
|
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: "",
|
Title: entry.Title,
|
||||||
Description: "",
|
Date: entry.Date.Format("2006-01-02 15:04"),
|
||||||
BlogLinks: getBlogLinks(),
|
Desc: entry.Description,
|
||||||
Content: template.HTML(content),
|
Author: entry.Author,
|
||||||
PrevLink: prevLink,
|
Content: template.HTML(htmlContentWithLazyLoading),
|
||||||
NextLink: nextLink,
|
PrevLink: prevLink,
|
||||||
}
|
NextLink: nextLink,
|
||||||
|
|
||||||
// Use the existing renderTemplate to render with a specific PageData type.
|
|
||||||
renderTemplate(w, "news.html", pageData)
|
|
||||||
}
|
|
||||||
|
|
||||||
func renderAllBlogs(w http.ResponseWriter, r *http.Request, page int) {
|
|
||||||
var allEntries []BlogEntry
|
|
||||||
|
|
||||||
for _, blog := range blogs {
|
|
||||||
allEntries = append(allEntries, blog.Entries...)
|
|
||||||
}
|
|
||||||
|
|
||||||
sort.Slice(allEntries, func(i, j int) bool {
|
|
||||||
return allEntries[i].Date.After(allEntries[j].Date)
|
|
||||||
})
|
|
||||||
|
|
||||||
totalEntries := len(allEntries)
|
|
||||||
startIndex := (page - 1) * pageSize
|
|
||||||
endIndex := startIndex + pageSize
|
|
||||||
if startIndex >= totalEntries {
|
|
||||||
http.NotFound(w, r)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if endIndex > totalEntries {
|
|
||||||
endIndex = totalEntries
|
|
||||||
}
|
|
||||||
|
|
||||||
pagedEntries := allEntries[startIndex:endIndex]
|
|
||||||
|
|
||||||
var content strings.Builder
|
|
||||||
for _, entry := range pagedEntries {
|
|
||||||
htmlContent := blackfriday.Run([]byte(entry.Content))
|
|
||||||
content.WriteString(fmt.Sprintf("<div class=\"blog-entry\"><div>%s</div></div>", htmlContent))
|
|
||||||
}
|
|
||||||
|
|
||||||
var prevLink, nextLink string
|
|
||||||
if startIndex > 0 {
|
|
||||||
prevLink = fmt.Sprintf("/all?page=%d", page-1)
|
|
||||||
}
|
|
||||||
if endIndex < totalEntries {
|
|
||||||
nextLink = fmt.Sprintf("/all?page=%d", page+1)
|
|
||||||
}
|
|
||||||
|
|
||||||
pageData := PageData{
|
|
||||||
Title: "All Blogs",
|
|
||||||
Description: "Combined blog entries from all blogs.",
|
|
||||||
BlogLinks: getBlogLinks(),
|
|
||||||
Content: template.HTML(content.String()),
|
|
||||||
PrevLink: prevLink,
|
|
||||||
NextLink: nextLink,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
renderTemplate(w, "news.html", pageData)
|
renderTemplate(w, "news.html", pageData)
|
||||||
|
@ -265,7 +314,7 @@ func renderAllBlogs(w http.ResponseWriter, r *http.Request, page int) {
|
||||||
func getBlogs(dir string) ([]Blog, error) {
|
func getBlogs(dir string) ([]Blog, error) {
|
||||||
var blogs []Blog
|
var blogs []Blog
|
||||||
|
|
||||||
files, err := ioutil.ReadDir(dir)
|
files, err := os.ReadDir(dir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -283,46 +332,22 @@ func getBlogs(dir string) ([]Blog, error) {
|
||||||
return blogs, nil
|
return blogs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getBlogLinks() []string {
|
|
||||||
var links []string
|
|
||||||
for _, blog := range blogs {
|
|
||||||
links = append(links, blog.Name)
|
|
||||||
}
|
|
||||||
return links
|
|
||||||
}
|
|
||||||
|
|
||||||
func getBlogEntries(dir string) (Blog, error) {
|
func getBlogEntries(dir string) (Blog, error) {
|
||||||
var entries []BlogEntry
|
var entries []BlogEntry
|
||||||
|
|
||||||
files, err := ioutil.ReadDir(dir)
|
files, err := os.ReadDir(dir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Blog{}, err
|
return Blog{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, file := range files {
|
for _, file := range files {
|
||||||
if filepath.Ext(file.Name()) == ".md" {
|
if filepath.Ext(file.Name()) == ".md" {
|
||||||
content, err := ioutil.ReadFile(filepath.Join(dir, file.Name()))
|
entry, err := parseMarkdownFile(filepath.Join(dir, file.Name()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Blog{}, err
|
return Blog{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
date, err := getFileModTime(filepath.Join(dir, file.Name()))
|
|
||||||
if err != nil {
|
|
||||||
return Blog{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
number, err := strconv.Atoi(strings.TrimSuffix(file.Name(), filepath.Ext(file.Name())))
|
|
||||||
if err != nil {
|
|
||||||
return Blog{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
entry := BlogEntry{
|
|
||||||
Content: string(content),
|
|
||||||
Date: date,
|
|
||||||
Number: number,
|
|
||||||
}
|
|
||||||
entries = append(entries, entry)
|
entries = append(entries, entry)
|
||||||
sendDiscordNotification(fmt.Sprintf("New blog entry: %d", number))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -338,6 +363,74 @@ func getBlogEntries(dir string) (Blog, error) {
|
||||||
return blog, nil
|
return blog, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func parseMarkdownFile(path string) (BlogEntry, error) {
|
||||||
|
content, err := os.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return BlogEntry{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
scanner := bufio.NewScanner(strings.NewReader(string(content)))
|
||||||
|
|
||||||
|
var title, description, postTime, author string
|
||||||
|
var articleContent strings.Builder
|
||||||
|
var insideHeader bool
|
||||||
|
|
||||||
|
for scanner.Scan() {
|
||||||
|
line := strings.TrimSpace(scanner.Text())
|
||||||
|
|
||||||
|
if line == "[HEADER]" {
|
||||||
|
insideHeader = true
|
||||||
|
continue
|
||||||
|
} else if line == "[END]" {
|
||||||
|
insideHeader = false
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if insideHeader {
|
||||||
|
if strings.HasPrefix(line, "t:") {
|
||||||
|
title = strings.TrimSpace(line[2:])
|
||||||
|
} else if strings.HasPrefix(line, "d:") {
|
||||||
|
description = strings.TrimSpace(line[2:])
|
||||||
|
} else if strings.HasPrefix(line, "p:") {
|
||||||
|
postTime = strings.TrimSpace(line[2:])
|
||||||
|
} else if strings.HasPrefix(line, "a:") {
|
||||||
|
author = strings.TrimSpace(line[2:])
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
articleContent.WriteString(line + "\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
date := time.Now()
|
||||||
|
if postTime != "" {
|
||||||
|
date, err = time.Parse("2006-01-02 15:04", postTime)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error parsing date in file %s: %v", path, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
number, err := strconv.Atoi(strings.TrimSuffix(filepath.Base(path), filepath.Ext(path)))
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error extracting number from file %s: %v", path, err)
|
||||||
|
return BlogEntry{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the entry has already been notified
|
||||||
|
notified := notifiedEntries[number]
|
||||||
|
|
||||||
|
entry := BlogEntry{
|
||||||
|
Title: title,
|
||||||
|
Description: description,
|
||||||
|
Author: author,
|
||||||
|
Content: articleContent.String(),
|
||||||
|
Date: date,
|
||||||
|
Number: number,
|
||||||
|
Notified: notified,
|
||||||
|
}
|
||||||
|
|
||||||
|
return entry, nil
|
||||||
|
}
|
||||||
|
|
||||||
func watchForChanges(dir string) {
|
func watchForChanges(dir string) {
|
||||||
watcher, err := fsnotify.NewWatcher()
|
watcher, err := fsnotify.NewWatcher()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -389,6 +482,59 @@ 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()
|
||||||
|
@ -400,33 +546,22 @@ 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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateBlogEntries(blogName, path string) {
|
func updateBlogEntries(blogName, path string) {
|
||||||
for i, blog := range blogs {
|
for i, blog := range blogs {
|
||||||
if blog.Name == blogName {
|
if blog.Name == blogName {
|
||||||
content, err := ioutil.ReadFile(path)
|
entry, err := parseMarkdownFile(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error reading file: %v", err)
|
log.Printf("Error parsing markdown file: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
number, err := strconv.Atoi(strings.TrimSuffix(filepath.Base(path), filepath.Ext(path)))
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Error extracting number from file: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
entry := BlogEntry{
|
|
||||||
Content: string(content),
|
|
||||||
Date: creationTimes[path],
|
|
||||||
Number: number,
|
|
||||||
}
|
|
||||||
|
|
||||||
updated := false
|
updated := false
|
||||||
for j, e := range blogs[i].Entries {
|
for j, e := range blogs[i].Entries {
|
||||||
if e.Number == number {
|
if e.Number == entry.Number {
|
||||||
blogs[i].Entries[j] = entry
|
blogs[i].Entries[j] = entry
|
||||||
updated = true
|
updated = true
|
||||||
break
|
break
|
||||||
|
@ -441,43 +576,36 @@ func updateBlogEntries(blogName, path string) {
|
||||||
return blogs[i].Entries[a].Number > blogs[i].Entries[b].Number
|
return blogs[i].Entries[a].Number > blogs[i].Entries[b].Number
|
||||||
})
|
})
|
||||||
|
|
||||||
log.Printf("Updated blog %s with entry %d", blogName, number)
|
log.Printf("Updated blog %s with entry %d", blogName, entry.Number)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If blog not found, create new one
|
// If blog not found, create new one
|
||||||
content, err := ioutil.ReadFile(path)
|
entry, err := parseMarkdownFile(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error reading file: %v", err)
|
log.Printf("Error parsing markdown file: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
number, err := strconv.Atoi(strings.TrimSuffix(filepath.Base(path), filepath.Ext(path)))
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Error extracting number from file: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
entry := BlogEntry{
|
|
||||||
Content: string(content),
|
|
||||||
Date: creationTimes[path],
|
|
||||||
Number: number,
|
|
||||||
}
|
|
||||||
|
|
||||||
newBlog := Blog{
|
newBlog := Blog{
|
||||||
Name: blogName,
|
Name: blogName,
|
||||||
Entries: []BlogEntry{entry},
|
Entries: []BlogEntry{entry},
|
||||||
}
|
}
|
||||||
|
|
||||||
blogs = append(blogs, newBlog)
|
blogs = append(blogs, newBlog)
|
||||||
log.Printf("Created new blog %s with entry %d", blogName, number)
|
log.Printf("Created new blog %s with entry %d", blogName, entry.Number)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getFileModTime(path string) (time.Time, error) {
|
func sendNotifications(entry BlogEntry) {
|
||||||
info, err := os.Stat(path)
|
message := fmt.Sprintf("New blog post published!\nTitle: %s\nDescription: %s\nAuthor: %s\nDate: %s",
|
||||||
if err != nil {
|
entry.Title, entry.Description, entry.Author, entry.Date.Format("2006-01-02 15:04"))
|
||||||
return time.Time{}, err
|
|
||||||
}
|
// Send notification to Telegram
|
||||||
return info.ModTime(), nil
|
sendTelegramNotification(message)
|
||||||
|
|
||||||
|
// Send notification to Discord
|
||||||
|
sendDiscordNotification(message)
|
||||||
|
|
||||||
|
log.Printf("Sent notifications for entry %d: %s", entry.Number, entry.Title)
|
||||||
}
|
}
|
||||||
|
|
100
rss.go
100
rss.go
|
@ -3,65 +3,119 @@ 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"
|
||||||
)
|
)
|
||||||
|
|
||||||
func generateRSSFeed(w http.ResponseWriter, blogs []Blog, siteURL string) {
|
func generateAtomFeed(w http.ResponseWriter, blogs []Blog, siteURL string) {
|
||||||
feed := &feeds.Feed{
|
feed := &feeds.Feed{
|
||||||
Title: "My Blog",
|
Title: "Spitfire News",
|
||||||
Link: &feeds.Link{Href: siteURL},
|
Link: &feeds.Link{Href: siteURL},
|
||||||
Description: "A blog about various topics.",
|
Description: "Blog about Spitfire browser news/updates.", // Blog subtitle (Atom uses "subtitle" for description)
|
||||||
Author: &feeds.Author{Name: "Your Name", Email: "your-email@example.com"},
|
Author: &feeds.Author{Name: "Internet Addict", Email: "noone@none.no"},
|
||||||
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
|
||||||
|
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: fmt.Sprintf("Entry %d", entry.Number), // Use entry number as title
|
Title: entry.Title,
|
||||||
Link: &feeds.Link{Href: fmt.Sprintf("%s/%s/%d", siteURL, blog.Name, entry.Number)},
|
Link: &feeds.Link{Href: entryID, Rel: "alternate"},
|
||||||
Description: entry.Content,
|
Description: summary, // This can be used as the summary
|
||||||
Created: entry.Date,
|
Author: &feeds.Author{Name: entry.Author},
|
||||||
|
Id: entryID,
|
||||||
|
Updated: entry.Date,
|
||||||
|
Content: absoluteContent,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
rss, err := feed.ToRss()
|
// 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()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, "Error generating RSS feed", http.StatusInternalServerError)
|
http.Error(w, "Error generating Atom feed", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/rss+xml")
|
w.Write([]byte(atom))
|
||||||
w.Write([]byte(rss))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func generateBlogRSSFeed(w http.ResponseWriter, blog Blog, siteURL string) {
|
func generateBlogAtomFeed(w http.ResponseWriter, blog Blog, siteURL string) {
|
||||||
feed := &feeds.Feed{
|
feed := &feeds.Feed{
|
||||||
Title: blog.Name,
|
Title: blog.Name,
|
||||||
Link: &feeds.Link{Href: fmt.Sprintf("%s/%s", siteURL, blog.Name)},
|
Link: &feeds.Link{Href: fmt.Sprintf("%s/%s", siteURL, blog.Name)},
|
||||||
Description: blog.Name,
|
Description: blog.Name, // Blog subtitle (Atom uses "subtitle" for description)
|
||||||
Author: &feeds.Author{Name: "Your Name", Email: "your-email@example.com"},
|
Author: &feeds.Author{Name: "Internet Addict", Email: "noone@none.no"},
|
||||||
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
|
||||||
|
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: fmt.Sprintf("Entry %d", entry.Number), // Use entry number as title
|
Title: entry.Title,
|
||||||
Link: &feeds.Link{Href: fmt.Sprintf("%s/%s/%d", siteURL, blog.Name, entry.Number)},
|
Link: &feeds.Link{Href: entryID, Rel: "alternate"},
|
||||||
Description: entry.Content,
|
Description: summary, // This can be used as the summary
|
||||||
Created: entry.Date,
|
Author: &feeds.Author{Name: entry.Author},
|
||||||
|
Id: entryID,
|
||||||
|
Updated: entry.Date,
|
||||||
|
Content: absoluteContent,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
rss, err := feed.ToRss()
|
// 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()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, "Error generating RSS feed", http.StatusInternalServerError)
|
http.Error(w, "Error generating Atom feed", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/rss+xml")
|
w.Write([]byte(atom))
|
||||||
w.Write([]byte(rss))
|
|
||||||
}
|
}
|
||||||
|
|
21
run.bat
Normal file
21
run.bat
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
@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%
|
15
run.sh
15
run.sh
|
@ -1,3 +1,16 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Default values
|
||||||
|
PORT=8080
|
||||||
|
|
||||||
go run discord.go rss.go telegram.go main.go
|
# Parse command-line arguments
|
||||||
|
while [[ "$#" -gt 0 ]]; do
|
||||||
|
case $1 in
|
||||||
|
-p|--port) PORT="$2"; shift ;;
|
||||||
|
*) echo "Unknown parameter passed: $1"; exit 1 ;;
|
||||||
|
esac
|
||||||
|
shift
|
||||||
|
done
|
||||||
|
|
||||||
|
# Run the Go application with the parsed flags
|
||||||
|
go run discord.go rss.go telegram.go save.go main.go -p=$PORT
|
||||||
|
|
50
save.go
Normal file
50
save.go
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func loadNotifiedEntries() {
|
||||||
|
file, err := os.Open(notifiedFilePath)
|
||||||
|
if err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Fatalf("Error loading notified entries: %v", err)
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
err = json.NewDecoder(file).Decode(¬ifiedEntries)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Error decoding notified entries: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func saveNotifiedEntry(entryNumber int) {
|
||||||
|
notifiedEntries[entryNumber] = true
|
||||||
|
file, err := os.Create(notifiedFilePath)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Error saving notified entries: %v", err)
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
err = json.NewEncoder(file).Encode(notifiedEntries)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Error encoding notified entries: %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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
51
static/css/blog.css
Normal file
51
static/css/blog.css
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
/* Global settings for images and videos */
|
||||||
|
img, video {
|
||||||
|
display: block;
|
||||||
|
width: 85%;
|
||||||
|
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 {
|
||||||
|
text-align: left;
|
||||||
|
font-size: 1em;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Increase margin top for all headings */
|
||||||
|
.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;
|
||||||
|
}
|
|
@ -24,3 +24,19 @@
|
||||||
height: 48px;
|
height: 48px;
|
||||||
overflow: visible;
|
overflow: visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.the-slider img {
|
||||||
|
width: 90%;
|
||||||
|
height: auto;
|
||||||
|
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,64 +1,139 @@
|
||||||
.fancy-gallery {
|
/* Adjust the gallery wrapper */
|
||||||
display: grid;
|
.gallery-wrapper {
|
||||||
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%;
|
||||||
height: 100%;
|
max-width: 800px;
|
||||||
object-fit: cover;
|
margin: 0 auto; /* Center the gallery */
|
||||||
transition: transform 0.6s ease, filter 0.6s ease;
|
overflow: hidden; /* Ensure content is within bounds */
|
||||||
}
|
}
|
||||||
|
|
||||||
.gallery-item::before {
|
.gallery-wrapper img {
|
||||||
content: '';
|
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;
|
position: absolute;
|
||||||
top: 0;
|
top: 50%; /* Position vertically centered */
|
||||||
left: 0;
|
transform: translateY(-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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.gallery-item:hover {
|
.slick-prev {
|
||||||
transform: scale(1.05);
|
left: 0px;
|
||||||
filter: brightness(1.2);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.gallery-item:hover img {
|
.slick-next {
|
||||||
transform: scale(1.1) rotate(2deg);
|
right: 0px;
|
||||||
filter: grayscale(20%)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.gallery-item:hover::before {
|
/* Center the dots under the image */
|
||||||
opacity: 1;
|
.slick-dots {
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 15px; /* Space between image and dots */
|
||||||
|
padding-left: 0;
|
||||||
|
list-style: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes pulse {
|
.slick-dots li {
|
||||||
0%, 100% {
|
display: inline-block; /* Align dots horizontally */
|
||||||
transform: scale(1);
|
margin: 0 8px; /* Space between dots */
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.gallery-item {
|
.slick-dots li button {
|
||||||
animation: pulse 5s infinite;
|
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: '';
|
||||||
|
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;
|
||||||
|
left: 50%;
|
||||||
|
top: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.slick-dots li.slick-active button::before {
|
||||||
|
background-color: #fff; /* Active dot background color */
|
||||||
|
border-color: #fff; /* Active dot border color */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* This is what I get for global styling and using templates */
|
||||||
|
|
||||||
|
/* Override the border-radius to remove rounded corners */
|
||||||
|
.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 */
|
||||||
|
.slick-prev, .slick-next {
|
||||||
|
border-radius: 0 !important;
|
||||||
|
background: transparent !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ensure the active dot buttons are not rounded */
|
||||||
|
.slick-dots li button {
|
||||||
|
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;
|
||||||
}
|
}
|
Binary file not shown.
Before Width: | Height: | Size: 20 KiB |
|
@ -5,9 +5,9 @@
|
||||||
@import 'assets/webfonts/6xKydSBYKcSV-LCoeQqfX1RYOo3ik4zwlxdu.woff2';
|
@import 'assets/webfonts/6xKydSBYKcSV-LCoeQqfX1RYOo3ik4zwlxdu.woff2';
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Stellar by HTML5 UP
|
Spitfire Browser by Internet Addict (https://weforgecode.xyz/Spitfire/Website)
|
||||||
html5up.net | @ajlkn
|
Based on Stellar by HTML5 UP | @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, body, div, span, applet, object,
|
html, body, div, span, applet, object,
|
||||||
|
@ -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: 17pt;
|
font-size: 14pt;
|
||||||
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.2s ease, border-bottom 0.2s ease;
|
-moz-transition: color 0.3s ease, border-bottom 0.3s ease;
|
||||||
-webkit-transition: color 0.2s ease, border-bottom 0.2s ease;
|
-webkit-transition: color 0.3s ease, border-bottom 0.3s ease;
|
||||||
-ms-transition: color 0.2s ease, border-bottom 0.2s ease;
|
-ms-transition: color 0.3s ease, border-bottom 0.3s ease;
|
||||||
transition: color 0.2s ease, border-bottom 0.2s ease;
|
transition: color 0.3s ease, border-bottom 0.3s ease;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
border-bottom: dotted 1px;
|
border-bottom: dotted 1px;
|
||||||
color: inherit;
|
color: inherit;
|
||||||
|
@ -180,9 +180,7 @@ input, select, textarea {
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
}
|
}
|
||||||
|
|
||||||
p {
|
|
||||||
margin: 0 0 2em 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
p.content {
|
p.content {
|
||||||
-moz-columns: 20em 2;
|
-moz-columns: 20em 2;
|
||||||
|
@ -1838,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.2s ease-in-out, color 0.2s ease-in-out;
|
-moz-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;
|
-webkit-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;
|
-ms-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;
|
transition: background-color 0.3s ease-in-out, color 0.3s ease-in-out;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
border: 0;
|
border: 0;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
@ -2220,10 +2218,10 @@ input, select, textarea {
|
||||||
|
|
||||||
.icon {
|
.icon {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
-moz-transition: background-color 0.2s ease-in-out, color 0.2s ease-in-out;
|
-moz-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;
|
-webkit-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;
|
-ms-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;
|
transition: background-color 0.3s ease-in-out, color 0.3s ease-in-out;
|
||||||
border-bottom: none;
|
border-bottom: none;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
@ -2671,7 +2669,6 @@ input, select, textarea {
|
||||||
}
|
}
|
||||||
|
|
||||||
header.major p {
|
header.major p {
|
||||||
font-size: 1.25em;
|
|
||||||
letter-spacing: -0.025em;
|
letter-spacing: -0.025em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3223,10 +3220,10 @@ input, select, textarea {
|
||||||
/* Nav */
|
/* Nav */
|
||||||
|
|
||||||
#nav {
|
#nav {
|
||||||
-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;
|
-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;
|
||||||
-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;
|
-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;
|
||||||
-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;
|
-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;
|
||||||
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;
|
||||||
background-color: #0a0a0a;
|
background-color: #0a0a0a;
|
||||||
color: #fafafa;
|
color: #fafafa;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
@ -3280,10 +3277,10 @@ input, select, textarea {
|
||||||
}
|
}
|
||||||
|
|
||||||
#nav ul li {
|
#nav ul li {
|
||||||
-moz-transition: margin 0.2s ease;
|
-moz-transition: margin 0.3s ease;
|
||||||
-webkit-transition: margin 0.2s ease;
|
-webkit-transition: margin 0.3s ease;
|
||||||
-ms-transition: margin 0.2s ease;
|
-ms-transition: margin 0.3s ease;
|
||||||
transition: margin 0.2s ease;
|
transition: margin 0.3s ease;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin: 0 0.35em;
|
margin: 0 0.35em;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
@ -3291,10 +3288,10 @@ input, select, textarea {
|
||||||
}
|
}
|
||||||
|
|
||||||
#nav ul li a {
|
#nav ul li a {
|
||||||
-moz-transition: font-size 0.2s ease;
|
-moz-transition: font-size 0.3s ease;
|
||||||
-webkit-transition: font-size 0.2s ease;
|
-webkit-transition: font-size 0.3s ease;
|
||||||
-ms-transition: font-size 0.2s ease;
|
-ms-transition: font-size 0.3s ease;
|
||||||
transition: font-size 0.2s ease;
|
transition: font-size 0.3s ease;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
height: 2.25em;
|
height: 2.25em;
|
||||||
line-height: 2.25em;
|
line-height: 2.25em;
|
||||||
|
@ -3304,15 +3301,19 @@ 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 {
|
||||||
background-color: #000000;
|
transition: all 0.3s ease;
|
||||||
|
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;
|
||||||
|
|
2
static/css/slick.min.css
vendored
Normal file
2
static/css/slick.min.css
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
.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 */
|
BIN
static/images/favicon.png
Normal file
BIN
static/images/favicon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 81 KiB |
|
@ -1,7 +1,7 @@
|
||||||
/*
|
/*
|
||||||
Stellar by HTML5 UP
|
Spitfire Browser by Internet Addict (https://weforgecode.xyz/Spitfire/Website)
|
||||||
html5up.net | @ajlkn
|
Based on Stellar by HTML5 UP | @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)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
(function($) {
|
(function($) {
|
||||||
|
|
9
static/js/particles.min.js
vendored
Normal file
9
static/js/particles.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
110
static/js/particlesjs-config-alt.json
Normal file
110
static/js/particlesjs-config-alt.json
Normal file
|
@ -0,0 +1,110 @@
|
||||||
|
{
|
||||||
|
"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
|
||||||
|
}
|
110
static/js/particlesjs-config.json
Normal file
110
static/js/particlesjs-config.json
Normal file
|
@ -0,0 +1,110 @@
|
||||||
|
{
|
||||||
|
"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
|
||||||
|
}
|
16
static/js/slick-conf.js
Normal file
16
static/js/slick-conf.js
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
$(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
Normal file
1
static/js/slick.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
|
@ -1,7 +1,7 @@
|
||||||
///
|
///
|
||||||
/// Stellar by HTML5 UP
|
// Spitfire Browser by Internet Addict (https://weforgecode.xyz/Spitfire/Website)
|
||||||
/// html5up.net | @ajlkn
|
// Based on Stellar by HTML5 UP | @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 */
|
||||||
|
|
16
telegram.go
16
telegram.go
|
@ -24,3 +24,19 @@ func startTelegramBot() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func sendTelegramNotification(message string) {
|
||||||
|
if bot == nil {
|
||||||
|
log.Println("Telegram bot is not initialized")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace with the actual chat ID you want to send messages to
|
||||||
|
chatID := int64(YOUR_TELEGRAM_CHAT_ID)
|
||||||
|
|
||||||
|
msg := tgbotapi.NewMessage(chatID, message)
|
||||||
|
_, err := bot.Send(msg)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error sending Telegram notification: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,18 +1,19 @@
|
||||||
<!DOCTYPE HTML>
|
<!DOCTYPE HTML>
|
||||||
<!--
|
<!--
|
||||||
Stellar by HTML5 UP
|
Spitfire Browser by Internet Addict (https://weforgecode.xyz/Spitfire/Website)
|
||||||
html5up.net | @ajlkn
|
Based on Stellar by HTML5 UP | @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="favicon.png">
|
<link rel="icon" type="image/png" href="static/images/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/favicon.png" property="og:image" />
|
<meta content="https://spitfirebrowser.com/static/images/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 charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no" />
|
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no" />
|
||||||
|
@ -30,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; top: 0; width: 100%; z-index: 1000;">
|
<div style="background-color: red; color: white; text-align: center; padding: 10px 0; position: fixed; bottom: 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,62 +1,63 @@
|
||||||
<!DOCTYPE HTML>
|
<!DOCTYPE HTML>
|
||||||
<!--
|
<!--
|
||||||
Stellar by HTML5 UP
|
Spitfire Browser by Internet Addict (https://weforgecode.xyz/Spitfire/Website)
|
||||||
html5up.net | @ajlkn
|
Based on Stellar by HTML5 UP | @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="favicon.png">
|
<link rel="icon" type="image/png" href="static/images/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/favicon.png" property="og:image" />
|
<meta content="https://spitfirebrowser.com/static/images/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 charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no" />
|
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no" />
|
||||||
<link rel="stylesheet" href="static/css/main.css" />
|
<link rel="stylesheet" href="static/css/main.css" />
|
||||||
<link rel="stylesheet" href="static/css/stars.css" />
|
<link rel="stylesheet" href="static/css/stars.css" />
|
||||||
<noscript><link rel="stylesheet" href="static/css/noscript.css" /></noscript>
|
<noscript><link rel="stylesheet" href="static/css/noscript.css" /></noscript>
|
||||||
<style>
|
<style>
|
||||||
.download-section {
|
.download-section {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 2em 0;
|
padding: 2em 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.download-buttons {
|
.download-buttons {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
gap: 1em;
|
gap: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.download-button {
|
.download-button {
|
||||||
border-radius: 0.5em;
|
border-radius: 0.5em;
|
||||||
padding: 1em;
|
padding: 1em;
|
||||||
width: 220px;
|
width: 220px;
|
||||||
height: 220px;
|
height: 220px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
transition: background-color 0.3s ease;
|
transition: background-color 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.download-button:hover {
|
.download-button:hover {
|
||||||
background-color: #444;
|
background-color: #444;
|
||||||
}
|
}
|
||||||
|
|
||||||
.download-button img {
|
.download-button img {
|
||||||
width: 48px;
|
width: 48px;
|
||||||
height: 48px;
|
height: 48px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.download-button span {
|
.download-button span {
|
||||||
display: block;
|
display: block;
|
||||||
margin-top: 0.5em;
|
margin-top: 0.5em;
|
||||||
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 -->
|
||||||
|
@ -67,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; top: 0; width: 100%; z-index: 1000;">
|
<div style="background-color: red; color: white; text-align: center; padding: 10px 0; position: fixed; bottom: 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,18 +1,19 @@
|
||||||
<!DOCTYPE HTML>
|
<!DOCTYPE HTML>
|
||||||
<!--
|
<!--
|
||||||
Spitfire Browser by Internet Addict
|
Spitfire Browser by Internet Addict (https://weforgecode.xyz/Spitfire/Website)
|
||||||
Based on Stellar by HTML5 UP
|
Based on Stellar by HTML5 UP | @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="favicon.png">
|
<link rel="icon" type="image/png" href="static/images/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/favicon.png" property="og:image" />
|
<meta content="https://spitfirebrowser.com/static/images/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 charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no" />
|
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no" />
|
||||||
|
@ -20,19 +21,23 @@
|
||||||
<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" />
|
||||||
<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="stars"></div>
|
<div id="particles-js"></div>
|
||||||
<div id="stars2"></div>
|
<noscript>
|
||||||
<div id="stars3"></div>
|
<div id="stars"></div>
|
||||||
|
<div id="stars2"></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; bottom: 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>
|
||||||
|
|
||||||
|
@ -41,10 +46,13 @@
|
||||||
|
|
||||||
<!-- Header -->
|
<!-- Header -->
|
||||||
<header id="header" class="alt">
|
<header id="header" class="alt">
|
||||||
<span class="logo"><img src="/static/images/logo.svg" alt="Spitfire Logo" /></span>
|
<span class="logo">
|
||||||
|
<a href="/"><img src="/static/images/logo.svg" alt="Spitfire Logo" /></a>
|
||||||
|
</span>
|
||||||
<h1>Spitfire Browser</h1>
|
<h1>Spitfire Browser</h1>
|
||||||
<p>Fast. Secure. Elegant.</p>
|
<p>Fast. Secure. Elegant.</p>
|
||||||
<a href="download" class="button">Download Now</a>
|
<br>
|
||||||
|
<a href="/download" class="button">Download Now</a>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<!-- Nav -->
|
<!-- Nav -->
|
||||||
|
@ -52,8 +60,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>
|
||||||
|
|
||||||
|
@ -65,18 +73,21 @@
|
||||||
<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 Warp 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 Ocásek search engine.</p>
|
||||||
<div class="box alt">
|
<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="fancy-gallery">
|
<div class="gallery-wrapper">
|
||||||
<div class="gallery-item"><img src="/static/images/screenshots/1.png" alt="Screenshot 1" /></div>
|
<div class="the-slider"> <!-- Is this fucking centered? -->
|
||||||
<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 1"></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">
|
<!-- <ul class="actions js-disabled">
|
||||||
<li><a href="#features" class="button">More Screenshots</a></li>
|
<li><a href="#intro" class="button">See 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">
|
||||||
|
@ -111,11 +122,20 @@
|
||||||
<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="https://search.spitfirebrowser.com/">Warp search engine</a> allows you to browse Google results anonymously.</p>
|
<p><a herf="#search">Ocásek 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">
|
||||||
|
@ -189,13 +209,13 @@
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="#" class="icon alt">
|
<a href="https://weforgecode.xyz/Spitfire" 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="#" class="icon alt">
|
<a href="https://www.youtube.com/@Internet.Addict" 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>
|
||||||
|
@ -210,13 +230,23 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Scripts -->
|
<!-- Scripts -->
|
||||||
<script src="/static/js/jquery.min.js" defer></script>
|
<script src="/static/js/jquery.min.js"></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 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,68 +1,79 @@
|
||||||
<!DOCTYPE HTML>
|
<!DOCTYPE HTML>
|
||||||
<!--
|
<!--
|
||||||
Stellar by HTML5 UP
|
Spitfire Browser by Internet Addict (https://weforgecode.xyz/Spitfire/Website)
|
||||||
html5up.net | @ajlkn
|
Based on Stellar by HTML5 UP | @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="favicon.png">
|
<link rel="icon" type="image/png" href="static/images/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/favicon.png" property="og:image" />
|
<meta content="https://spitfirebrowser.com/static/images/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 charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no" />
|
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no" />
|
||||||
<link rel="stylesheet" href="/static/css/main.css" />
|
<link rel="stylesheet" href="/static/css/main.css" />
|
||||||
<link rel="stylesheet" href="/static/css/stars.css" />
|
<link rel="stylesheet" href="/static/css/stars.css" />
|
||||||
<link rel="stylesheet" href="/static/css/extras.css" />
|
<link rel="stylesheet" href="/static/css/extras.css" />
|
||||||
<noscript><link rel="stylesheet" href="/static/css/noscript.css" /></noscript>
|
<link rel="stylesheet" href="/static/css/blog.css" />
|
||||||
</head>
|
<noscript><link rel="stylesheet" href="/static/css/noscript.css" /></noscript>
|
||||||
|
</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="stars"></div>
|
<div id="particles-js"></div>
|
||||||
<div id="stars2"></div>
|
<noscript>
|
||||||
<div id="stars3"></div>
|
<div id="stars"></div>
|
||||||
|
<div id="stars2"></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">
|
||||||
|
|
||||||
<!-- Header -->
|
<!-- Header -->
|
||||||
<header id="header" class="alt">
|
<header id="header" class="alt">
|
||||||
<span class="logo"><img src="/static/images/logo.svg" alt="Spitfire Logo" /></span>
|
<span class="logo">
|
||||||
<h1>Spitfire Browser</h1>
|
<a href="/"><img src="/static/images/logo.svg" alt="Spitfire Logo" /></a>
|
||||||
<p>News</p>
|
</span>
|
||||||
|
<h1>Spitfire Browser News</h1>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<!-- Main -->
|
<!-- Main -->
|
||||||
<div id="main">
|
<div id="main">
|
||||||
<!-- Stable Release Section -->
|
<!-- Stable Release Section -->
|
||||||
<section id="cta" class="main special">
|
<section id="cta" class="main">
|
||||||
<div class="center-text">
|
<div class="center-text">
|
||||||
<header class="major">
|
<header class="major">
|
||||||
<h2>{{.Title}}</h2>
|
<div class="align-blog"> <p>{{.Content}}</p> </div>
|
||||||
<p>{{.Content}}</p>
|
|
||||||
</header>
|
</header>
|
||||||
<footer class="major">
|
<footer class="major">
|
||||||
<ul class="actions special">
|
<ul class="actions special">
|
||||||
{{if .PrevLink}}
|
<li>
|
||||||
<li><a href="{{.PrevLink}}" class="button primary small">Previous</a></li>
|
<a href="{{if .PrevLink}}{{.PrevLink}}{{else}}#{{end}}"
|
||||||
{{end}}
|
class="button primary small {{if not .PrevLink}}disabled{{end}}">
|
||||||
|
Previous
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
<li><a href="/" class="button primary small">Home</a></li>
|
<li><a href="/" class="button primary small">Home</a></li>
|
||||||
{{if .NextLink}}
|
<li>
|
||||||
<li><a href="{{.NextLink}}" class="button primary small">Next</a></li>
|
<a href="{{if .NextLink}}{{.NextLink}}{{else}}#{{end}}"
|
||||||
{{end}}
|
class="button primary small {{if not .NextLink}}disabled{{end}}">
|
||||||
|
Next
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
|
@ -108,6 +119,13 @@
|
||||||
<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