Begin work on outline page and route
This commit is contained in:
27
cmd/main.go
27
cmd/main.go
@@ -3,6 +3,7 @@ package main
|
||||
import (
|
||||
"embed"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
@@ -14,6 +15,7 @@ import (
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/gofiber/fiber/v2/middleware/basicauth"
|
||||
"github.com/gofiber/fiber/v2/middleware/favicon"
|
||||
"github.com/gofiber/template/html/v2"
|
||||
)
|
||||
|
||||
//go:embed favicon.ico
|
||||
@@ -22,6 +24,9 @@ var faviconData string
|
||||
//go:embed styles.css
|
||||
var cssData embed.FS
|
||||
|
||||
//go:embed script.js
|
||||
var scriptData embed.FS
|
||||
|
||||
//go:embed VERSION
|
||||
var version string
|
||||
|
||||
@@ -101,12 +106,21 @@ func main() {
|
||||
*prefork = true
|
||||
}
|
||||
|
||||
engine := html.New("./handlers", ".html")
|
||||
engine.AddFunc(
|
||||
// add unescape function
|
||||
"unescape", func(s string) template.HTML {
|
||||
return template.HTML(s)
|
||||
},
|
||||
)
|
||||
|
||||
app := fiber.New(
|
||||
fiber.Config{
|
||||
Prefork: *prefork,
|
||||
GETOnly: false,
|
||||
ReadBufferSize: 4096 * 4, // increase max header size
|
||||
DisableStartupMessage: true,
|
||||
Views: engine,
|
||||
},
|
||||
)
|
||||
|
||||
@@ -149,6 +163,18 @@ func main() {
|
||||
return c.Send(cssData)
|
||||
})
|
||||
|
||||
// TODO: move to handlers/script.go
|
||||
app.Get("/script.js", func(c *fiber.Ctx) error {
|
||||
scriptData, err := scriptData.ReadFile("script.js")
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).SendString("Internal Server Error")
|
||||
}
|
||||
|
||||
c.Set("Content-Type", "text/javascript")
|
||||
|
||||
return c.Send(scriptData)
|
||||
})
|
||||
|
||||
app.Get("ruleset", handlers.Ruleset)
|
||||
app.Get("raw/*", handlers.Raw)
|
||||
|
||||
@@ -158,6 +184,7 @@ func main() {
|
||||
}
|
||||
|
||||
app.Get("api/outline/*", handlers.NewAPIOutlineHandler("api/outline/*", proxyOpts))
|
||||
app.Get("outline/*", handlers.Outline("outline/*", proxyOpts))
|
||||
|
||||
app.Get("/*", handlers.NewProxySiteHandler(proxyOpts))
|
||||
app.Post("/*", handlers.NewProxySiteHandler(proxyOpts))
|
||||
|
||||
269
cmd/script.js
Normal file
269
cmd/script.js
Normal file
@@ -0,0 +1,269 @@
|
||||
const labels = document.querySelectorAll("label");
|
||||
const inputs = document.querySelectorAll('input[type="radio"]');
|
||||
const mainElement = document.querySelector("main");
|
||||
|
||||
const handleDOMContentLoaded = () => {
|
||||
handleFontChange();
|
||||
handleFontSizeChange();
|
||||
inputs.forEach((input) => {
|
||||
const storedValue = localStorage.getItem(input.name);
|
||||
if (storedValue === input.value) {
|
||||
input.checked = true;
|
||||
}
|
||||
});
|
||||
window.removeEventListener("DOMContentLoaded", handleDOMContentLoaded);
|
||||
};
|
||||
|
||||
function focusable_children(node) {
|
||||
const nodes = Array.from(
|
||||
node.querySelectorAll(
|
||||
'a[href], button, input, textarea, select, details, [tabindex]:not([tabindex="-1"])'
|
||||
)
|
||||
).filter((s) => s.offsetParent !== null);
|
||||
const index = nodes.indexOf(document.activeElement);
|
||||
const update = (d) => {
|
||||
let i = index + d;
|
||||
i += nodes.length;
|
||||
i %= nodes.length;
|
||||
nodes[i].focus();
|
||||
};
|
||||
return {
|
||||
next: (selector) => {
|
||||
const reordered = [
|
||||
...nodes.slice(index + 1),
|
||||
...nodes.slice(0, index + 1),
|
||||
];
|
||||
for (let i = 0; i < reordered.length; i += 1) {
|
||||
if (!selector || reordered[i].matches(selector)) {
|
||||
reordered[i].focus();
|
||||
return;
|
||||
}
|
||||
}
|
||||
},
|
||||
prev: (selector) => {
|
||||
const reordered = [
|
||||
...nodes.slice(index + 1),
|
||||
...nodes.slice(0, index + 1),
|
||||
];
|
||||
for (let i = reordered.length - 2; i >= 0; i -= 1) {
|
||||
if (!selector || reordered[i].matches(selector)) {
|
||||
reordered[i].focus();
|
||||
return;
|
||||
}
|
||||
}
|
||||
},
|
||||
update,
|
||||
};
|
||||
}
|
||||
|
||||
function trap(node) {
|
||||
const handle_keydown = (e) => {
|
||||
if (e.key === "Tab") {
|
||||
e.preventDefault();
|
||||
const group = focusable_children(node);
|
||||
if (e.shiftKey) {
|
||||
group.prev();
|
||||
} else {
|
||||
group.next();
|
||||
}
|
||||
}
|
||||
};
|
||||
node.addEventListener("keydown", handle_keydown);
|
||||
return {
|
||||
destroy: () => {
|
||||
node.removeEventListener("keydown", handle_keydown);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const toggleDropdown = () => {
|
||||
const dropdown = document.getElementById("dropdown");
|
||||
const dropdown_panel = document.getElementById("dropdown_panel");
|
||||
const focusTrap = trap(dropdown);
|
||||
|
||||
const closeDropdown = () => {
|
||||
dropdown_panel.classList.add("hidden");
|
||||
focusTrap.destroy();
|
||||
dropdown.removeEventListener("keydown", handleEscapeKey);
|
||||
document.removeEventListener("click", handleClickOutside);
|
||||
inputs.forEach((input) => {
|
||||
input.removeEventListener("change", handleInputChange);
|
||||
});
|
||||
labels.forEach((label) => {
|
||||
label.removeEventListener("click", handleLabelSelection);
|
||||
});
|
||||
};
|
||||
|
||||
const handleClickOutside = (e) => {
|
||||
if (!dropdown.contains(e.target)) {
|
||||
closeDropdown();
|
||||
}
|
||||
};
|
||||
|
||||
const handleEscapeKey = (e) => {
|
||||
if (e.key === "Escape") {
|
||||
dropdown_panel.classList.add("hidden");
|
||||
closeDropdown();
|
||||
}
|
||||
};
|
||||
|
||||
const handleInputChange = (e) => {
|
||||
if (e.target.checked) {
|
||||
localStorage.setItem(e.target.name, e.target.value);
|
||||
switch (e.target.name) {
|
||||
case "theme": {
|
||||
handleThemeChange();
|
||||
break;
|
||||
}
|
||||
case "font": {
|
||||
handleFontChange();
|
||||
break;
|
||||
}
|
||||
case "fontsize": {
|
||||
handleFontSizeChange();
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
console.error("Unknown event");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleLabelSelection = (e) => {
|
||||
if (e.key === "Enter" || e.key === " ") {
|
||||
e.preventDefault();
|
||||
const input = document.getElementById(e.target.getAttribute("for"));
|
||||
input.checked = true;
|
||||
input.dispatchEvent(new Event("change", { bubbles: true }));
|
||||
}
|
||||
};
|
||||
|
||||
if (dropdown_panel.classList.contains("hidden")) {
|
||||
dropdown_panel.classList.remove("hidden");
|
||||
dropdown.addEventListener("keydown", handleEscapeKey);
|
||||
inputs.forEach((input) => {
|
||||
input.addEventListener("change", handleInputChange);
|
||||
});
|
||||
labels.forEach((label) => {
|
||||
label.addEventListener("keydown", handleLabelSelection);
|
||||
});
|
||||
document.addEventListener("click", handleClickOutside);
|
||||
} else {
|
||||
closeDropdown();
|
||||
}
|
||||
};
|
||||
|
||||
const handleFontChange = () => {
|
||||
if (mainElement === null) {
|
||||
return;
|
||||
}
|
||||
const font = localStorage.getItem("font");
|
||||
if (font === "sans-serif") {
|
||||
mainElement.classList.add("font-sans");
|
||||
mainElement.classList.remove("font-serif");
|
||||
} else {
|
||||
mainElement.classList.add("font-serif");
|
||||
mainElement.classList.remove("font-sans");
|
||||
}
|
||||
};
|
||||
|
||||
const changeFontSize = (node, classes) => {
|
||||
const sizes = [
|
||||
"text-xs",
|
||||
"text-sm",
|
||||
"text-base",
|
||||
"text-lg",
|
||||
"text-xl",
|
||||
"text-2xl",
|
||||
"text-3xl",
|
||||
"text-4xl",
|
||||
"sm:text-3xl",
|
||||
"sm:text-4xl",
|
||||
"sm:text-5xl",
|
||||
];
|
||||
const currentClasses = sizes.filter((size) => node.classList.contains(size));
|
||||
node.classList.remove(...currentClasses);
|
||||
node.classList.add(...classes);
|
||||
};
|
||||
|
||||
const handleFontSizeChange = () => {
|
||||
if (mainElement === null) {
|
||||
return;
|
||||
}
|
||||
const fontSize = localStorage.getItem("fontsize");
|
||||
const nodes = document.querySelectorAll(
|
||||
"h1, h2, h3, h4, code, pre, kbd, table"
|
||||
);
|
||||
if (fontSize === "text-sm") {
|
||||
changeFontSize(mainElement, ["text-sm"]);
|
||||
} else if (fontSize === "text-lg") {
|
||||
changeFontSize(mainElement, ["text-lg"]);
|
||||
} else {
|
||||
changeFontSize(mainElement, ["text-base"]);
|
||||
}
|
||||
nodes.forEach((node) => {
|
||||
let classes = "";
|
||||
switch (node.tagName) {
|
||||
case "H1": {
|
||||
if (fontSize === "text-sm") {
|
||||
classes = ["text-2xl", "sm:text-3xl"];
|
||||
} else if (fontSize === "text-lg") {
|
||||
classes = ["text-4xl", "sm:text-5xl"];
|
||||
} else {
|
||||
classes = ["text-3xl", "sm:text-4xl"];
|
||||
}
|
||||
}
|
||||
case "H2": {
|
||||
if (fontSize === "text-sm") {
|
||||
classes = ["text-2xl"];
|
||||
} else if (fontSize === "text-lg") {
|
||||
classes = ["text-4xl"];
|
||||
} else {
|
||||
classes = ["text-3xl"];
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "H3": {
|
||||
if (fontSize === "text-sm") {
|
||||
classes = ["text-xl"];
|
||||
} else if (fontSize === "text-lg") {
|
||||
classes = ["text-3xl"];
|
||||
} else {
|
||||
classes = ["text-2xl"];
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "H4": {
|
||||
if (fontSize === "text-sm") {
|
||||
classes = ["text-lg"];
|
||||
} else if (fontSize === "text-lg") {
|
||||
classes = ["text-2xl"];
|
||||
} else {
|
||||
classes = ["text-xl"];
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "CODE":
|
||||
case "PRE":
|
||||
case "KBD":
|
||||
case "TABLE": {
|
||||
if (fontSize === "text-sm") {
|
||||
classes = ["text-xs"];
|
||||
} else if (fontSize === "text-lg") {
|
||||
classes = ["text-base"];
|
||||
} else {
|
||||
classes = ["text-sm"];
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
changeFontSize(node, classes);
|
||||
});
|
||||
};
|
||||
|
||||
window.addEventListener("DOMContentLoaded", handleDOMContentLoaded);
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user