diff --git a/cmd/main.go b/cmd/main.go index ab9d8bf..926fd97 100644 --- a/cmd/main.go +++ b/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)) diff --git a/cmd/script.js b/cmd/script.js new file mode 100644 index 0000000..ce87a46 --- /dev/null +++ b/cmd/script.js @@ -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); diff --git a/cmd/styles.css b/cmd/styles.css index 33803d8..2d9be53 100644 --- a/cmd/styles.css +++ b/cmd/styles.css @@ -1 +1 @@ -*,::after,::before{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}::after,::before{--tw-content:''}html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-feature-settings:normal;font-variation-settings:normal}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]{display:none}*,::after,::before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgb(59 130 246 / 0.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgb(59 130 246 / 0.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }.absolute{position:absolute}.relative{position:relative}.inset-y-0{top:0;bottom:0}.right-0{right:0}.mx-4{margin-left:1rem;margin-right:1rem}.mx-auto{margin-left:auto;margin-right:auto}.mt-10{margin-top:2.5rem}.block{display:block}.grid{display:grid}.hidden{display:none}.w-full{width:100%}.max-w-3xl{max-width:48rem}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.items-center{align-items:center}.gap-4{gap:1rem}.rounded-md{border-radius:.375rem}.bg-white{--tw-bg-opacity:1;background-color:rgb(255 255 255 / var(--tw-bg-opacity))}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.pl-2{padding-left:.5rem}.pr-2{padding-right:.5rem}.pr-3{padding-right:.75rem}.pt-10{padding-top:2.5rem}.text-center{text-align:center}.text-3xl{font-size:1.875rem;line-height:2.25rem}.text-sm{font-size:.875rem;line-height:1.25rem}.font-extrabold{font-weight:800}.leading-6{line-height:1.5rem}.tracking-tight{letter-spacing:-.025em}.text-slate-400{--tw-text-opacity:1;color:rgb(148 163 184 / var(--tw-text-opacity))}.text-slate-500{--tw-text-opacity:1;color:rgb(100 116 139 / var(--tw-text-opacity))}.text-slate-600{--tw-text-opacity:1;color:rgb(71 85 105 / var(--tw-text-opacity))}.text-slate-900{--tw-text-opacity:1;color:rgb(15 23 42 / var(--tw-text-opacity))}.underline-offset-2{text-underline-offset:2px}.antialiased{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.shadow-sm{--tw-shadow:0 1px 2px 0 rgb(0 0 0 / 0.05);--tw-shadow-colored:0 1px 2px 0 var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.ring-1{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.ring-slate-900\/10{--tw-ring-color:rgb(15 23 42 / 0.1)}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(0.4,0,0.2,1);transition-duration:150ms}.duration-300{transition-duration:.3s}.hover\:text-blue-500:hover{--tw-text-opacity:1;color:rgb(59 130 246 / var(--tw-text-opacity))}.hover\:text-slate-400:hover{--tw-text-opacity:1;color:rgb(148 163 184 / var(--tw-text-opacity))}.hover\:underline:hover{text-decoration-line:underline}.hover\:ring-slate-300:hover{--tw-ring-opacity:1;--tw-ring-color:rgb(203 213 225 / var(--tw-ring-opacity))}@media (prefers-color-scheme:dark){.dark\:bg-slate-800{--tw-bg-opacity:1;background-color:rgb(30 41 59 / var(--tw-bg-opacity))}.dark\:bg-slate-900{--tw-bg-opacity:1;background-color:rgb(15 23 42 / var(--tw-bg-opacity))}.dark\:text-slate-200{--tw-text-opacity:1;color:rgb(226 232 240 / var(--tw-text-opacity))}.dark\:text-slate-400{--tw-text-opacity:1;color:rgb(148 163 184 / var(--tw-text-opacity))}.dark\:hover\:bg-slate-700:hover{--tw-bg-opacity:1;background-color:rgb(51 65 85 / var(--tw-bg-opacity))}.hover\:dark\:text-slate-300:hover{--tw-text-opacity:1;color:rgb(203 213 225 / var(--tw-text-opacity))}}@media (min-width:640px){.sm\:text-4xl{font-size:2.25rem;line-height:2.5rem}} +*,::after,::before{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}::after,::before{--tw-content:''}html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-feature-settings:normal;font-variation-settings:normal}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]{display:none}*,::after,::before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgb(59 130 246 / 0.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgb(59 130 246 / 0.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }a{--tw-text-opacity:1;color:rgb(71 85 105 / var(--tw-text-opacity));text-underline-offset:2px;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(0.4,0,0.2,1);transition-duration:.3s}a:hover{--tw-text-opacity:1;color:rgb(59 130 246 / var(--tw-text-opacity));text-decoration-line:underline}@media (prefers-color-scheme:dark){a{--tw-text-opacity:1;color:rgb(148 163 184 / var(--tw-text-opacity))}}h1{font-size:1.875rem;line-height:2.25rem;font-weight:800;letter-spacing:-.025em;--tw-text-opacity:1;color:rgb(15 23 42 / var(--tw-text-opacity))}@media (prefers-color-scheme:dark){h1{--tw-text-opacity:1;color:rgb(226 232 240 / var(--tw-text-opacity))}}@media (min-width:640px){h1{font-size:2.25rem;line-height:2.5rem}}h2{scroll-margin:5rem;border-bottom-width:1px;padding-bottom:.5rem;font-size:1.875rem;line-height:2.25rem;font-weight:600;letter-spacing:-.025em;--tw-text-opacity:1;color:rgb(15 23 42 / var(--tw-text-opacity))}h2:first-child{margin-top:0}@media (prefers-color-scheme:dark){h2{--tw-text-opacity:1;color:rgb(226 232 240 / var(--tw-text-opacity))}}h3{scroll-margin:5rem;font-size:1.5rem;line-height:2rem;font-weight:600;letter-spacing:-.025em;--tw-text-opacity:1;color:rgb(15 23 42 / var(--tw-text-opacity))}@media (prefers-color-scheme:dark){h3{--tw-text-opacity:1;color:rgb(226 232 240 / var(--tw-text-opacity))}}h4,h5,h6{scroll-margin:5rem;font-size:1.25rem;line-height:1.75rem;font-weight:600;letter-spacing:-.025em;--tw-text-opacity:1;color:rgb(15 23 42 / var(--tw-text-opacity))}@media (prefers-color-scheme:dark){h4,h5,h6{--tw-text-opacity:1;color:rgb(226 232 240 / var(--tw-text-opacity))}}p{line-height:1.75rem;--tw-text-opacity:1;color:rgb(15 23 42 / var(--tw-text-opacity))}@media (prefers-color-scheme:dark){p{--tw-text-opacity:1;color:rgb(226 232 240 / var(--tw-text-opacity))}}p:not(:first-child){margin-top:1.5rem}code,kbd{position:relative;border-radius:.25rem;--tw-bg-opacity:1;background-color:rgb(226 232 240 / var(--tw-bg-opacity));padding-left:.3rem;padding-right:.3rem;padding-top:.2rem;padding-bottom:.2rem;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:.875rem;line-height:1.25rem;font-weight:600}@media (prefers-color-scheme:dark){code,kbd{--tw-bg-opacity:1;background-color:rgb(30 41 59 / var(--tw-bg-opacity))}}blockquote{margin-top:1.5rem;border-left-width:2px;padding-left:1.5rem;font-style:italic}ul{margin-top:1.5rem;margin-bottom:1.5rem;margin-left:1.5rem;list-style-type:disc;--tw-text-opacity:1;color:rgb(15 23 42 / var(--tw-text-opacity))}@media (prefers-color-scheme:dark){ul{--tw-text-opacity:1;color:rgb(226 232 240 / var(--tw-text-opacity))}}ul>li{margin-top:.5rem}ol{margin-top:1.5rem;margin-bottom:1.5rem;margin-left:1.5rem;list-style-type:decimal;--tw-text-opacity:1;color:rgb(15 23 42 / var(--tw-text-opacity))}@media (prefers-color-scheme:dark){ol{--tw-text-opacity:1;color:rgb(226 232 240 / var(--tw-text-opacity))}}ol>li{margin-top:.5rem}dl{margin-top:1.5rem;margin-bottom:1.5rem;font-weight:700;--tw-text-opacity:1;color:rgb(15 23 42 / var(--tw-text-opacity))}@media (prefers-color-scheme:dark){dl{--tw-text-opacity:1;color:rgb(226 232 240 / var(--tw-text-opacity))}}dl>dd{margin-left:1.5rem;font-weight:400}dl>dt{margin-top:.75rem}li{--tw-text-opacity:1;color:rgb(15 23 42 / var(--tw-text-opacity))}@media (prefers-color-scheme:dark){li{--tw-text-opacity:1;color:rgb(226 232 240 / var(--tw-text-opacity))}}table{width:100%;caption-side:bottom;font-size:.875rem;line-height:1.25rem}thead tr{border-bottom-width:1px}tbody tr:last-child{border-width:0}tfoot{border-top-width:1px;--tw-border-opacity:1;border-color:rgb(148 163 184 / var(--tw-border-opacity));background-color:rgb(51 65 85 / .5);font-weight:500}@media (prefers-color-scheme:dark){tfoot{--tw-border-opacity:1;border-color:rgb(51 65 85 / var(--tw-border-opacity));background-color:rgb(226 232 240 / .5)}}tfoot:last-child>tr{border-bottom-width:0}tr{border-bottom-width:1px;--tw-border-opacity:1;border-color:rgb(148 163 184 / var(--tw-border-opacity));transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(0.4,0,0.2,1);transition-duration:150ms}tr:hover{background-color:rgb(226 232 240 / .5)}tr[data-state=selected]{--tw-bg-opacity:1;background-color:rgb(51 65 85 / var(--tw-bg-opacity))}@media (prefers-color-scheme:dark){tr{--tw-border-opacity:1;border-color:rgb(51 65 85 / var(--tw-border-opacity))}tr:hover{background-color:rgb(51 65 85 / .5)}tr[data-state=selected]{--tw-bg-opacity:1;background-color:rgb(226 232 240 / var(--tw-bg-opacity))}}th{height:3rem;padding-left:1rem;padding-right:1rem;text-align:left;vertical-align:middle;font-weight:500;--tw-text-opacity:1;color:rgb(71 85 105 / var(--tw-text-opacity))}@media (prefers-color-scheme:dark){th{--tw-text-opacity:1;color:rgb(226 232 240 / var(--tw-text-opacity))}}th:has([role=checkbox]){padding-right:0}td{padding:1rem;vertical-align:middle}td:has([role=checkbox]){padding-right:0}caption{margin-top:1rem;font-size:.875rem;line-height:1.25rem;--tw-text-opacity:1;color:rgb(71 85 105 / var(--tw-text-opacity))}@media (prefers-color-scheme:dark){caption{--tw-text-opacity:1;color:rgb(226 232 240 / var(--tw-text-opacity))}}img{margin-left:auto;margin-right:auto;height:auto;width:auto;max-width:100%;border-radius:.375rem;-o-object-fit:cover;object-fit:cover;--tw-shadow:0 4px 6px -1px rgb(0 0 0 / 0.1),0 2px 4px -2px rgb(0 0 0 / 0.1);--tw-shadow-colored:0 4px 6px -1px var(--tw-shadow-color),0 2px 4px -2px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}@media (prefers-color-scheme:dark){img{--tw-shadow-color:#334155;--tw-shadow:var(--tw-shadow-colored)}}figcaption{margin-top:.5rem;font-size:.875rem;line-height:1.25rem;--tw-text-opacity:1;color:rgb(71 85 105 / var(--tw-text-opacity))}@media (prefers-color-scheme:dark){figcaption{--tw-text-opacity:1;color:rgb(148 163 184 / var(--tw-text-opacity))}}hr{height:1px;width:100%;flex-shrink:0;--tw-bg-opacity:1;background-color:rgb(226 232 240 / var(--tw-bg-opacity))}@media (prefers-color-scheme:dark){hr{--tw-bg-opacity:1;background-color:rgb(51 65 85 / var(--tw-bg-opacity))}}.absolute{position:absolute}.relative{position:relative}.inset-y-0{top:0;bottom:0}.right-0{right:0}.z-10{z-index:10}.mx-4{margin-left:1rem;margin-right:1rem}.mx-auto{margin-left:auto;margin-right:auto}.my-2{margin-top:.5rem;margin-bottom:.5rem}.-ml-2{margin-left:-.5rem}.mr-1{margin-right:.25rem}.mt-10{margin-top:2.5rem}.mt-2{margin-top:.5rem}.block{display:block}.flex{display:flex}.inline-flex{display:inline-flex}.grid{display:grid}.hidden{display:none}.h-10{height:2.5rem}.h-12{height:3rem}.h-5{height:1.25rem}.h-8{height:2rem}.h-\[1px\]{height:1px}.w-10{width:2.5rem}.w-5{width:1.25rem}.w-52{width:13rem}.w-full{width:100%}.max-w-3xl{max-width:48rem}.shrink-0{flex-shrink:0}.cursor-pointer{cursor:pointer}.select-none{-webkit-user-select:none;-moz-user-select:none;user-select:none}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.flex-col{flex-direction:column}.place-items-center{place-items:center}.items-end{align-items:flex-end}.items-center{align-items:center}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-1{gap:.25rem}.gap-2{gap:.5rem}.gap-4{gap:1rem}.space-y-3>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.75rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.75rem * var(--tw-space-y-reverse))}.whitespace-nowrap{white-space:nowrap}.rounded-full{border-radius:9999px}.rounded-md{border-radius:.375rem}.border{border-width:1px}.border-slate-400{--tw-border-opacity:1;border-color:rgb(148 163 184 / var(--tw-border-opacity))}.bg-slate-200{--tw-bg-opacity:1;background-color:rgb(226 232 240 / var(--tw-bg-opacity))}.bg-slate-400{--tw-bg-opacity:1;background-color:rgb(148 163 184 / var(--tw-bg-opacity))}.bg-white{--tw-bg-opacity:1;background-color:rgb(255 255 255 / var(--tw-bg-opacity))}.p-2{padding:.5rem}.px-4{padding-left:1rem;padding-right:1rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-2\.5{padding-top:.625rem;padding-bottom:.625rem}.pl-2{padding-left:.5rem}.pr-2{padding-right:.5rem}.pr-3{padding-right:.75rem}.pt-10{padding-top:2.5rem}.text-left{text-align:left}.text-center{text-align:center}.align-middle{vertical-align:middle}.font-sans{font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji"}.font-serif{font-family:ui-serif,Georgia,Cambria,"Times New Roman",Times,serif}.text-2xl{font-size:1.5rem;line-height:2rem}.text-3xl{font-size:1.875rem;line-height:2.25rem}.text-4xl{font-size:2.25rem;line-height:2.5rem}.text-base{font-size:1rem;line-height:1.5rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-xs{font-size:.75rem;line-height:1rem}.font-extrabold{font-weight:800}.font-medium{font-weight:500}.leading-6{line-height:1.5rem}.leading-8{line-height:2rem}.tracking-tight{letter-spacing:-.025em}.text-\[\#7AA7D1\]{--tw-text-opacity:1;color:rgb(122 167 209 / var(--tw-text-opacity))}.text-slate-400{--tw-text-opacity:1;color:rgb(148 163 184 / var(--tw-text-opacity))}.text-slate-500{--tw-text-opacity:1;color:rgb(100 116 139 / var(--tw-text-opacity))}.text-slate-600{--tw-text-opacity:1;color:rgb(71 85 105 / var(--tw-text-opacity))}.text-slate-900{--tw-text-opacity:1;color:rgb(15 23 42 / var(--tw-text-opacity))}.underline-offset-2{text-underline-offset:2px}.antialiased{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.shadow-md{--tw-shadow:0 4px 6px -1px rgb(0 0 0 / 0.1),0 2px 4px -2px rgb(0 0 0 / 0.1);--tw-shadow-colored:0 4px 6px -1px var(--tw-shadow-color),0 2px 4px -2px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-sm{--tw-shadow:0 1px 2px 0 rgb(0 0 0 / 0.05);--tw-shadow-colored:0 1px 2px 0 var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.ring-1{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.ring-slate-900\/10{--tw-ring-color:rgb(15 23 42 / 0.1)}.ring-offset-2{--tw-ring-offset-width:2px}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(0.4,0,0.2,1);transition-duration:150ms}.duration-300{transition-duration:.3s}.first-of-type\:rounded-t-md:first-of-type{border-top-left-radius:.375rem;border-top-right-radius:.375rem}.last-of-type\:rounded-b-md:last-of-type{border-bottom-right-radius:.375rem;border-bottom-left-radius:.375rem}.hover\:bg-slate-200:hover{--tw-bg-opacity:1;background-color:rgb(226 232 240 / var(--tw-bg-opacity))}.hover\:text-blue-500:hover{--tw-text-opacity:1;color:rgb(59 130 246 / var(--tw-text-opacity))}.hover\:text-slate-400:hover{--tw-text-opacity:1;color:rgb(148 163 184 / var(--tw-text-opacity))}.hover\:text-slate-500:hover{--tw-text-opacity:1;color:rgb(100 116 139 / var(--tw-text-opacity))}.hover\:underline:hover{text-decoration-line:underline}.hover\:no-underline:hover{text-decoration-line:none}.hover\:ring-slate-300:hover{--tw-ring-opacity:1;--tw-ring-color:rgb(203 213 225 / var(--tw-ring-opacity))}.hover\:drop-shadow-\[0_0px_4px_rgba\(122\2c 167\2c 209\2c \.3\)\]:hover{--tw-drop-shadow:drop-shadow(0 0px 4px rgba(122,167,209,.3));filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.focus\:border-\[\#7AA7D1\]:focus{--tw-border-opacity:1;border-color:rgb(122 167 209 / var(--tw-border-opacity))}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.focus\:ring:focus{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.focus-visible\:outline-none:focus-visible{outline:2px solid transparent;outline-offset:2px}.focus-visible\:ring-2:focus-visible{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.focus-visible\:ring-offset-2:focus-visible{--tw-ring-offset-width:2px}.disabled\:pointer-events-none:disabled{pointer-events:none}.disabled\:opacity-50:disabled{opacity:.5}.peer:checked~.peer-checked\:bg-slate-200{--tw-bg-opacity:1;background-color:rgb(226 232 240 / var(--tw-bg-opacity))}@media (prefers-color-scheme:dark){.dark\:border-slate-700{--tw-border-opacity:1;border-color:rgb(51 65 85 / var(--tw-border-opacity))}.dark\:bg-slate-700{--tw-bg-opacity:1;background-color:rgb(51 65 85 / var(--tw-bg-opacity))}.dark\:bg-slate-800{--tw-bg-opacity:1;background-color:rgb(30 41 59 / var(--tw-bg-opacity))}.dark\:bg-slate-900{--tw-bg-opacity:1;background-color:rgb(15 23 42 / var(--tw-bg-opacity))}.dark\:text-slate-200{--tw-text-opacity:1;color:rgb(226 232 240 / var(--tw-text-opacity))}.dark\:text-slate-400{--tw-text-opacity:1;color:rgb(148 163 184 / var(--tw-text-opacity))}.dark\:hover\:bg-slate-700:hover{--tw-bg-opacity:1;background-color:rgb(51 65 85 / var(--tw-bg-opacity))}.dark\:hover\:text-slate-200:hover{--tw-text-opacity:1;color:rgb(226 232 240 / var(--tw-text-opacity))}.hover\:dark\:text-slate-300:hover{--tw-text-opacity:1;color:rgb(203 213 225 / var(--tw-text-opacity))}.peer:checked~.dark\:peer-checked\:bg-slate-700{--tw-bg-opacity:1;background-color:rgb(51 65 85 / var(--tw-bg-opacity))}}@media (min-width:640px){.sm\:text-3xl{font-size:1.875rem;line-height:2.25rem}.sm\:text-4xl{font-size:2.25rem;line-height:2.5rem}.sm\:text-5xl{font-size:3rem;line-height:1}}@media (min-width:1024px){.lg\:mx-auto{margin-left:auto;margin-right:auto}} diff --git a/go.mod b/go.mod index d751fb8..2e8eb23 100644 --- a/go.mod +++ b/go.mod @@ -20,6 +20,8 @@ require ( github.com/elliotchance/pie/v2 v2.8.0 // indirect github.com/forPelevin/gomoji v1.1.8 // indirect github.com/go-shiori/go-readability v0.0.0-20231029095239-6b97d5aba789 // indirect + github.com/gofiber/template v1.8.2 // indirect + github.com/gofiber/utils v1.1.0 // indirect github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f // indirect github.com/hablullah/go-hijri v1.0.2 // indirect github.com/hablullah/go-juliandays v1.0.0 // indirect @@ -40,8 +42,10 @@ require ( ) require ( + github.com/PuerkitoBio/goquery v1.8.1 github.com/andybalholm/brotli v1.0.6 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/gofiber/template/html/v2 v2.0.5 github.com/google/uuid v1.4.0 // indirect github.com/klauspost/compress v1.17.3 // indirect github.com/mattn/go-colorable v0.1.13 // indirect diff --git a/go.sum b/go.sum index 72701cf..01208f7 100644 --- a/go.sum +++ b/go.sum @@ -1,9 +1,12 @@ +github.com/PuerkitoBio/goquery v1.8.1 h1:uQxhNlArOIdbrH1tr0UXwdVFgDcZDrZVdcpygAcwmWM= +github.com/PuerkitoBio/goquery v1.8.1/go.mod h1:Q8ICL1kNUJ2sXGoAhPGUdYDJvgQgHzJsnnd3H7Ho5jQ= github.com/abadojack/whatlanggo v1.0.1 h1:19N6YogDnf71CTHm3Mp2qhYfkRdyvbgwWdd2EPxJRG4= github.com/abadojack/whatlanggo v1.0.1/go.mod h1:66WiQbSbJBIlOZMsvbKe5m6pzQovxCH9B/K8tQB2uoc= github.com/akamensky/argparse v1.4.0 h1:YGzvsTqCvbEZhL8zZu2AiA5nq805NZh75JNj4ajn1xc= github.com/akamensky/argparse v1.4.0/go.mod h1:S5kwC7IuDcEr5VeXtGPRVZ5o/FdhcMlQz4IZQuw64xA= github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI= github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= +github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA= github.com/andybalholm/cascadia v1.3.2 h1:3Xi6Dw5lHF15JtdcmAHD3i1+T8plmv7BQ/nsViSLyss= github.com/andybalholm/cascadia v1.3.2/go.mod h1:7gtRlve5FxPPgIgX36uWBX58OdBsSS6lUvCFb+h7KvU= github.com/bogdanfinn/fhttp v0.5.24 h1:OlyBKjvJp6a3TotN3wuj4mQHHRbfK7QUMrzCPOZGhRc= @@ -27,6 +30,12 @@ github.com/go-shiori/go-readability v0.0.0-20231029095239-6b97d5aba789/go.mod h1 github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gofiber/fiber/v2 v2.50.0 h1:ia0JaB+uw3GpNSCR5nvC5dsaxXjRU5OEu36aytx+zGw= github.com/gofiber/fiber/v2 v2.50.0/go.mod h1:21eytvay9Is7S6z+OgPi7c7n4++tnClWmhpimVHMimw= +github.com/gofiber/template v1.8.2 h1:PIv9s/7Uq6m+Fm2MDNd20pAFFKt5wWs7ZBd8iV9pWwk= +github.com/gofiber/template v1.8.2/go.mod h1:bs/2n0pSNPOkRa5VJ8zTIvedcI/lEYxzV3+YPXdBvq8= +github.com/gofiber/template/html/v2 v2.0.5 h1:BKLJ6Qr940NjntbGmpO3zVa4nFNGDCi/IfUiDB9OC20= +github.com/gofiber/template/html/v2 v2.0.5/go.mod h1:RCF14eLeQDCSUPp0IGc2wbSSDv6yt+V54XB/+Unz+LM= +github.com/gofiber/utils v1.1.0 h1:vdEBpn7AzIUJRhe+CiTOJdUcTg4Q9RK+pEa0KPbLdrM= +github.com/gofiber/utils v1.1.0/go.mod h1:poZpsnhBykfnY1Mc0KeEa6mSHrS3dV0+oBWyeQmb2e0= github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f h1:3BSP1Tbs2djlpprl7wCLuiqMaUh5SJkkzI2gDs+FgLs= github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f/go.mod h1:Pcatq5tYkCW2Q6yrR2VRHlbHpZ/R4/7qyL1TCF7vl14= github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= @@ -105,8 +114,10 @@ golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91 golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= @@ -115,6 +126,7 @@ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -134,6 +146,7 @@ golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4= golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= diff --git a/handlers/outline.go b/handlers/outline.go new file mode 100644 index 0000000..ccf6a97 --- /dev/null +++ b/handlers/outline.go @@ -0,0 +1,55 @@ +package handlers + +import ( + "ladder/proxychain" + rx "ladder/proxychain/requestmodifers" + tx "ladder/proxychain/responsemodifers" + "log" + + "github.com/gofiber/fiber/v2" +) + +func Outline(path string, opts *ProxyOptions) fiber.Handler { + + // TODO: implement ruleset logic + /* + var rs ruleset.RuleSet + if opts.RulesetPath != "" { + r, err := ruleset.NewRuleset(opts.RulesetPath) + if err != nil { + panic(err) + } + rs = r + } + */ + + return func(c *fiber.Ctx) error { + result, err := proxychain. + NewProxyChain(). + WithAPIPath(path). + SetDebugLogging(opts.Verbose). + SetRequestModifications( + rx.MasqueradeAsGoogleBot(), + rx.ForwardRequestHeaders(), + rx.SpoofReferrerFromGoogleSearch(), + ). + AddResponseModifications( + tx.DeleteIncomingCookies(), + tx.RewriteHTMLResourceURLs(), + tx.APIOutline(), + ). + SetFiberCtx(c). + ExecuteForAPI() + + if err != nil { + log.Fatal(err) + } + + return c.Render("outline", fiber.Map{ + "Success": true, + "Params": c.Params("*"), + "Title": "Outline", + "Body": result, + }) + } +} diff --git a/handlers/outline.html b/handlers/outline.html new file mode 100644 index 0000000..85c7665 --- /dev/null +++ b/handlers/outline.html @@ -0,0 +1,387 @@ + + + + + + + + ladder | {{ .Title }} + + + +
+
+ + +
+ +
+
+ +
+ {{ if not .Success}} +

+ Error +

+

+ There was a problem querying + {{ .Params }} +

+ + {{ .Type }}: {{ .Message }} +
Cause:
+ {{ .Cause }} +
+ {{else}} +
+
+ + {{.Url}} + +
+

+ {{.Title}} +

+ {{ if ne .Date "" }} + {{.date}} + {{ end }} {{ if ne .Author "" }} + {{.Author}} + {{ end }} +
+ +
+
{{ .Params }}
+
{{ unescape .Body }}
+ {{ end }} + + +
+ +
+ +
+
+ + + + diff --git a/proxychain/proxychain.go b/proxychain/proxychain.go index afc2e53..0657f01 100644 --- a/proxychain/proxychain.go +++ b/proxychain/proxychain.go @@ -518,3 +518,32 @@ func (chain *ProxyChain) Execute() error { // return chain.Context.SendStream(body) } + +func (chain *ProxyChain) ExecuteForAPI() (string, error) { + defer chain._reset() + body, err := chain._execute() + if err != nil { + log.Println(err) + return "", err + } + if chain.Context == nil { + return "", errors.New("no context set") + } + + // in case api user did not set or forward content-type, we do it for them + /* + if chain.Context.Get("content-type") == "" { + chain.Context.Set("content-type", chain.Response.Header.Get("content-type")) + } + */ + + // Capture the HTML content in a variable + htmlContent, err := io.ReadAll(body) + if err != nil { + log.Println(err) + return "", err + } + + // Return the HTML content to the client + return string(htmlContent), nil +} diff --git a/proxychain/responsemodifers/outline.go b/proxychain/responsemodifers/outline.go index be1f6cb..b239229 100644 --- a/proxychain/responsemodifers/outline.go +++ b/proxychain/responsemodifers/outline.go @@ -1,12 +1,13 @@ package responsemodifers import ( - "bytes" - "encoding/json" "io" + "strings" //"github.com/go-shiori/dom" + "github.com/go-shiori/dom" "github.com/markusmobius/go-trafilatura" + //"golang.org/x/net/html" "ladder/proxychain" @@ -37,15 +38,9 @@ func APIOutline() proxychain.ResponseModification { return nil } - doc := api.ExtractResultToAPIResponse(result) - jsonData, err := json.MarshalIndent(doc, "", "\t") - if err != nil { - chain.Response.Body = api.CreateAPIErrReader(err) - return nil - } - - buf := bytes.NewBuffer(jsonData) - chain.Response.Body = io.NopCloser(buf) + doc := trafilatura.CreateReadableDocument(result) + reader := io.NopCloser(strings.NewReader(dom.OuterHTML(doc))) + chain.Response.Body = reader return nil } } diff --git a/styles/input.css b/styles/input.css index bd6213e..6c8deed 100644 --- a/styles/input.css +++ b/styles/input.css @@ -1,3 +1,78 @@ @tailwind base; @tailwind components; -@tailwind utilities; \ No newline at end of file +@tailwind utilities; + +@layer components { + a { + @apply text-slate-600 dark:text-slate-400 hover:text-blue-500 hover:underline underline-offset-2 transition-colors duration-300; + } + h1 { + @apply text-3xl sm:text-4xl font-extrabold tracking-tight text-slate-900 dark:text-slate-200; + } + h2 { + @apply scroll-m-20 border-b pb-2 text-3xl font-semibold tracking-tight first:mt-0 text-slate-900 dark:text-slate-200; + } + h3 { + @apply scroll-m-20 text-2xl font-semibold tracking-tight text-slate-900 dark:text-slate-200; + } + h4, + h5, + h6 { + @apply scroll-m-20 text-xl font-semibold tracking-tight text-slate-900 dark:text-slate-200; + } + p { + @apply leading-7 [&:not(:first-child)]:mt-6 text-slate-900 dark:text-slate-200; + } + kbd, + code { + @apply relative rounded bg-slate-200 dark:bg-slate-800 px-[0.3rem] py-[0.2rem] font-mono text-sm font-semibold; + } + blockquote { + @apply mt-6 border-l-2 pl-6 italic; + } + ul { + @apply my-6 ml-6 list-disc [&>li]:mt-2 text-slate-900 dark:text-slate-200; + } + ol { + @apply my-6 ml-6 list-decimal [&>li]:mt-2 text-slate-900 dark:text-slate-200; + } + dl { + @apply my-6 text-slate-900 dark:text-slate-200 font-bold [&>dd]:font-normal [&>dd]:ml-6 [&>dt]:mt-3; + } + li { + @apply text-slate-900 dark:text-slate-200; + } + table { + @apply w-full caption-bottom text-sm; + } + thead { + @apply [&_tr]:border-b; + } + tbody { + @apply [&_tr:last-child]:border-0; + } + tfoot { + @apply border-t border-slate-400 dark:border-slate-700 bg-slate-700/50 dark:bg-slate-200/50 font-medium [&>tr]:last:border-b-0; + } + tr { + @apply border-b border-slate-400 dark:border-slate-700 transition-colors hover:bg-slate-200/50 dark:hover:bg-slate-700/50 data-[state=selected]:bg-slate-700 dark:data-[state=selected]:bg-slate-200; + } + th { + @apply h-12 px-4 text-left align-middle font-medium text-slate-600 dark:text-slate-200 [&:has([role=checkbox])]:pr-0; + } + td { + @apply p-4 align-middle [&:has([role=checkbox])]:pr-0; + } + caption { + @apply mt-4 text-sm text-slate-600 dark:text-slate-200; + } + img { + @apply h-auto w-auto object-cover max-w-full mx-auto rounded-md shadow-md dark:shadow-slate-700; + } + figcaption { + @apply mt-2 text-sm text-slate-600 dark:text-slate-400; + } + hr { + @apply shrink-0 bg-slate-200 dark:bg-slate-700 h-[1px] w-full; + } +}