From 9afccbb9cd88c851b9c29545793060c56371fa49 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Wed, 30 Nov 2022 00:03:54 +1300 Subject: [PATCH] Support light/dark themes as per Fandom's styles --- breezewiki.rkt | 2 ++ dist.rkt | 2 ++ info.rkt | 2 +- src/application-globals.rkt | 39 ++++++++++++++++++++++++------ src/data.rkt | 34 +++++++++++++++++++++++++- src/dispatcher-tree.rkt | 1 + src/page-category.rkt | 3 +++ src/page-file.rkt | 7 ++++-- src/page-search.rkt | 5 ++-- src/page-set-user-settings.rkt | 18 ++++++++++++++ src/page-wiki.rkt | 12 ++++++---- static/icon-theme-dark.svg | 2 ++ static/icon-theme-default.svg | 2 ++ static/icon-theme-light.svg | 2 ++ static/main.css | 44 +++++++++++++++++++++++++++++++++- 15 files changed, 157 insertions(+), 18 deletions(-) create mode 100644 src/page-set-user-settings.rkt create mode 100644 static/icon-theme-dark.svg create mode 100644 static/icon-theme-default.svg create mode 100644 static/icon-theme-light.svg diff --git a/breezewiki.rkt b/breezewiki.rkt index a8b8c28..44d6771 100644 --- a/breezewiki.rkt +++ b/breezewiki.rkt @@ -17,6 +17,7 @@ (require-reloadable "src/page-proxy.rkt" page-proxy) (require-reloadable "src/page-redirect-wiki-home.rkt" redirect-wiki-home) (require-reloadable "src/page-search.rkt" page-search) +(require-reloadable "src/page-set-user-settings.rkt" page-set-user-settings) (require-reloadable "src/page-static.rkt" static-dispatcher) (require-reloadable "src/page-subdomain.rkt" subdomain-dispatcher) (require-reloadable "src/page-wiki.rkt" page-wiki) @@ -40,6 +41,7 @@ page-not-found page-proxy page-search + page-set-user-settings page-wiki page-file redirect-wiki-home diff --git a/dist.rkt b/dist.rkt index 777e81a..35e1824 100644 --- a/dist.rkt +++ b/dist.rkt @@ -11,6 +11,7 @@ (require (only-in "src/page-proxy.rkt" page-proxy)) (require (only-in "src/page-redirect-wiki-home.rkt" redirect-wiki-home)) (require (only-in "src/page-search.rkt" page-search)) +(require (only-in "src/page-set-user-settings.rkt" page-set-user-settings)) (require (only-in "src/page-static.rkt" static-dispatcher)) (require (only-in "src/page-subdomain.rkt" subdomain-dispatcher)) (require (only-in "src/page-wiki.rkt" page-wiki)) @@ -29,6 +30,7 @@ page-not-found page-proxy page-search + page-set-user-settings page-wiki page-file redirect-wiki-home diff --git a/info.rkt b/info.rkt index 46512df..dd34d49 100644 --- a/info.rkt +++ b/info.rkt @@ -1,3 +1,3 @@ #lang info -(define build-deps '("rackunit-lib" "web-server-lib" "http-easy-lib" "html-parsing" "html-writing" "json-pointer" "ini-lib" "memo")) +(define build-deps '("rackunit-lib" "web-server-lib" "http-easy-lib" "html-parsing" "html-writing" "json-pointer" "ini-lib" "memo" "net-cookies-lib")) diff --git a/src/application-globals.rkt b/src/application-globals.rkt index b139192..0fe26dc 100644 --- a/src/application-globals.rkt +++ b/src/application-globals.rkt @@ -1,8 +1,10 @@ #lang racket/base -(require racket/list +(require racket/file + racket/list racket/string json (prefix-in easy: net/http-easy) + html-parsing html-writing web-server/http "config.rkt" @@ -34,6 +36,11 @@ (header #"Link" (string->bytes/latin-1 link-header)))) (define timeouts (easy:make-timeout-config #:lease 5 #:connect 5)) +(define theme-icons + (for/hasheq ([theme '(default light dark)]) + (values theme + (html->xexp (file->string (format "static/icon-theme-~a.svg" theme) #:mode 'binary))))) + (define (application-footer source-url #:license [license-in #f]) (define license (or license-in license-default)) `(footer (@ (class "custom-footer")) @@ -98,24 +105,27 @@ (define (generate-wiki-page content + #:req req #:source-url source-url #:wikiname wikiname #:title title #:head-data [head-data-in #f] - #:siteinfo [siteinfo-in #f]) + #:siteinfo [siteinfo-in #f] + #:user-cookies [user-cookies-in #f]) (define siteinfo (or siteinfo-in siteinfo-default)) (define head-data (or head-data-in ((head-data-getter wikiname)))) + (define user-cookies (or user-cookies-in (user-cookies-getter req))) (define (required-styles origin) (map (λ (dest-path) (define url (format dest-path origin)) (if (config-true? 'strict_proxy) (u-proxy-url url) url)) - '(#;"~a/load.php?lang=en&modules=skin.fandomdesktop.styles&only=styles&skin=fandomdesktop" + `(#;"~a/load.php?lang=en&modules=skin.fandomdesktop.styles&only=styles&skin=fandomdesktop" #;"~a/load.php?lang=en&modules=ext.gadget.dungeonsWiki%2CearthWiki%2Csite-styles%2Csound-styles&only=styles&skin=fandomdesktop" #;"~a/load.php?lang=en&modules=site.styles&only=styles&skin=fandomdesktop" ; combine the above entries into a single request for potentially extra speed - fandom.com doesn't even do this! - "~a/wikia.php?controller=ThemeApi&method=themeVariables" + ,(format "~~a/wikia.php?controller=ThemeApi&method=themeVariables&variant=~a" (user-cookies^-theme user-cookies)) "~a/load.php?lang=en&modules=skin.fandomdesktop.styles%7Cext.fandom.PortableInfoboxFandomDesktop.css%7Cext.fandom.GlobalComponents.CommunityHeaderBackground.css%7Cext.gadget.site-styles%2Csound-styles%7Csite.styles&only=styles&skin=fandomdesktop"))) `(*TOP* (*DECL* DOCTYPE html) @@ -155,7 +165,22 @@ (label (@ (for "bw-search-input")) "Search ") (div (@ (id "bw-pr-search-input")) (input (@ (type "text") (name "q") (id "bw-search-input") (autocomplete "off")))) - (div (@ (class "bw-ss__container") (id "bw-pr-search-suggestions")))))) + (div (@ (class "bw-ss__container") (id "bw-pr-search-suggestions")))) + (div (@ (class "bw-theme__select")) + (span (@ (class "bw-theme__main-label")) "Page theme") + (div (@ (class "bw-theme__items")) + ,@(for/list ([theme '(default light dark)]) + (define class + (if (equal? theme (user-cookies^-theme user-cookies)) + "bw-theme__item bw-theme__item--selected" + "bw-theme__item")) + `(a (@ (href ,(user-cookies-setter-url + req + (struct-copy user-cookies^ user-cookies + [theme theme]))) (class ,class)) + (div (@ (class "bw-theme__icon-container")) + ,(hash-ref theme-icons theme)) + ,(format "~a" theme))))))) (div (@ (id "content") #;(class "page-content")) (div (@ (id "mw-content-text")) ,content)) @@ -179,11 +204,11 @@ page)))) "/proxy?dest=https%3A%2F%2Ftest.fandom.com"))) -(define (generate-redirect dest) +(define (generate-redirect dest #:headers [headers-in '()]) (define dest-bytes (string->bytes/utf-8 dest)) (response/output #:code 302 - #:headers (list (header #"Location" dest-bytes)) + #:headers (append (list (header #"Location" dest-bytes)) headers-in) (λ (out) (write-html `(html diff --git a/src/data.rkt b/src/data.rkt index f856710..e8a9e69 100644 --- a/src/data.rkt +++ b/src/data.rkt @@ -1,6 +1,9 @@ #lang racket/base (require racket/list racket/match + web-server/http/request-structs + net/url-string + (only-in net/cookies/server cookie-header->alist cookie->set-cookie-header make-cookie) (prefix-in easy: net/http-easy) memo "static-data.rkt" @@ -11,11 +14,16 @@ (struct-out siteinfo^) (struct-out license^) (struct-out head-data^) + (struct-out user-cookies^) siteinfo-fetch siteinfo-default license-default head-data-getter - head-data-default) + head-data-default + user-cookies-getter + user-cookies-default + user-cookies-setter + user-cookies-setter-url) (struct siteinfo^ (sitename basepage license) #:transparent) (struct license^ (text url) #:transparent) @@ -61,3 +69,27 @@ (set! this-data data)) ;; then no matter what, return the best information we have so far this-data)) + +(struct user-cookies^ (theme) #:prefab) +(define user-cookies-default (user-cookies^ 'default)) +(define (user-cookies-getter req) + (define cookie-header (headers-assq* #"cookie" (request-headers/raw req))) + (define cookies-alist (if cookie-header (cookie-header->alist (header-value cookie-header) bytes->string/utf-8) null)) + (define cookies-hash + (for/hasheq ([pair cookies-alist]) + (match pair + [(cons "theme" (and theme (or "light" "dark" "default"))) + (values 'theme (string->symbol theme))] + [_ (values #f #f)]))) + (user-cookies^ + (hash-ref cookies-hash 'theme (user-cookies^-theme user-cookies-default)))) + +(define (user-cookies-setter user-cookies) + (map (λ (c) (header #"Set-Cookie" (cookie->set-cookie-header c))) + (list (make-cookie "theme" (symbol->string (user-cookies^-theme user-cookies)) + #:path "/" + #:max-age (* 60 60 24 365 10))))) + +(define (user-cookies-setter-url req new-settings) + (format "/set-user-settings?~a" (params->query `(("ref" . ,(url->string (request-uri req))) + ("new_settings" . ,(format "~a" new-settings)))))) diff --git a/src/dispatcher-tree.rkt b/src/dispatcher-tree.rkt index 63c053d..251aa43 100644 --- a/src/dispatcher-tree.rkt +++ b/src/dispatcher-tree.rkt @@ -39,6 +39,7 @@ (pathprocedure:make "/" (hash-ref ds 'page-home)) (pathprocedure:make "/proxy" (hash-ref ds 'page-proxy)) (pathprocedure:make "/search" (hash-ref ds 'page-global-search)) + (pathprocedure:make "/set-user-settings" (hash-ref ds 'page-set-user-settings)) (pathprocedure:make "/buddyfight/wiki/It_Doesn't_Work!!" (hash-ref ds 'page-it-works)) (filter:make (pregexp (format "^/~a/wiki/Category:.+$" px-wikiname)) (lift:make (hash-ref ds 'page-category))) (filter:make (pregexp (format "^/~a/wiki/File:.+$" px-wikiname)) (lift:make (hash-ref ds 'page-file))) diff --git a/src/page-category.rkt b/src/page-category.rkt index 69d9f42..5933f3e 100644 --- a/src/page-category.rkt +++ b/src/page-category.rkt @@ -29,6 +29,7 @@ '#hasheq((batchcomplete . #t) (continue . #hasheq((cmcontinue . "page|4150504c45|41473") (continue . "-||"))) (query . #hasheq((categorymembers . (#hasheq((ns . 0) (pageid . 25049) (title . "Item (entity)")) #hasheq((ns . 0) (pageid . 128911) (title . "3D")) #hasheq((ns . 0) (pageid . 124018) (title . "A Very Fine Item")) #hasheq((ns . 0) (pageid . 142208) (title . "Amethyst Shard")) #hasheq((ns . 0) (pageid . 121612) (title . "Ankle Monitor"))))))))) (define (generate-results-page + #:req req #:source-url source-url #:wikiname wikiname #:title title @@ -38,6 +39,7 @@ #:siteinfo [siteinfo #f]) (define members (jp "/query/categorymembers" members-data)) (generate-wiki-page + #:req req #:source-url source-url #:wikiname wikiname #:title title @@ -96,6 +98,7 @@ (define page (html->xexp page-html)) (define head-data ((head-data-getter wikiname) page-data)) (define body (generate-results-page + #:req req #:source-url source-url #:wikiname wikiname #:title title diff --git a/src/page-file.rkt b/src/page-file.rkt index 1802568..d9dfd91 100644 --- a/src/page-file.rkt +++ b/src/page-file.rkt @@ -51,7 +51,8 @@ [(regexp-match? #rx"(?i:^video/)" content-type) `(video (@ (src ,maybe-proxied-url) (controls)))] [else `""])) -(define (generate-results-page #:source-url source-url +(define (generate-results-page #:req req + #:source-url source-url #:wikiname wikiname #:title title #:media-detail media-detail @@ -68,6 +69,7 @@ (define maybe-proxied-raw-image-url (if (config-true? 'strict_proxy) (u-proxy-url raw-image-url) raw-image-url)) (generate-wiki-page + #:req req #:source-url source-url #:wikiname wikiname #:title title @@ -125,7 +127,8 @@ #f (url-content-type (jp "/imageUrl" media-detail)))) (define body - (generate-results-page #:source-url source-url + (generate-results-page #:req req + #:source-url source-url #:wikiname wikiname #:title title #:media-detail media-detail diff --git a/src/page-search.rkt b/src/page-search.rkt index 0404233..214a3a2 100644 --- a/src/page-search.rkt +++ b/src/page-search.rkt @@ -25,9 +25,10 @@ (define search-json-data '#hasheq((batchcomplete . #t) (query . #hasheq((search . (#hasheq((ns . 0) (pageid . 219) (size . 1482) (snippet . "") (timestamp . "2022-08-21T08:54:23Z") (title . "Gacha Capsule") (wordcount . 214)) #hasheq((ns . 0) (pageid . 201) (size . 1198) (snippet . "") (timestamp . "2022-07-11T17:52:47Z") (title . "Badges") (wordcount . 181))))))))) -(define (generate-results-page dest-url wikiname query data #:siteinfo [siteinfo #f]) +(define (generate-results-page req dest-url wikiname query data #:siteinfo [siteinfo #f]) (define search-results (jp "/query/search" data)) (generate-wiki-page + #:req req #:source-url dest-url #:wikiname wikiname #:title query @@ -74,7 +75,7 @@ (define data (easy:response-json dest-res)) - (define body (generate-results-page dest-url wikiname query data #:siteinfo siteinfo)) + (define body (generate-results-page req dest-url wikiname query data #:siteinfo siteinfo)) (when (config-true? 'debug) ; used for its side effects ; convert to string with error checking, error will be raised if xexp is invalid diff --git a/src/page-set-user-settings.rkt b/src/page-set-user-settings.rkt new file mode 100644 index 0000000..3f37692 --- /dev/null +++ b/src/page-set-user-settings.rkt @@ -0,0 +1,18 @@ +#lang racket/base +(require racket/dict + net/url + web-server/http + "application-globals.rkt" + "data.rkt" + "url-utils.rkt" + "xexpr-utils.rkt") + +(provide + page-set-user-settings) + +(define (page-set-user-settings req) + (response-handler + (define ref (dict-ref (url-query (request-uri req)) 'ref)) + (define new-settings (read (open-input-string (dict-ref (url-query (request-uri req)) 'new_settings)))) + (define headers (user-cookies-setter new-settings)) + (generate-redirect ref #:headers headers))) diff --git a/src/page-wiki.rkt b/src/page-wiki.rkt index b2028d6..2cd14dc 100644 --- a/src/page-wiki.rkt +++ b/src/page-wiki.rkt @@ -276,6 +276,7 @@ (define (page-wiki req) (define wikiname (path/param-path (first (url-path (request-uri req))))) + (define user-cookies (user-cookies-getter req)) (define origin (format "https://~a.fandom.com" wikiname)) (define path (string-join (map path/param-path (cddr (url-path (request-uri req)))) "/")) (define source-url (format "https://~a.fandom.com/wiki/~a" wikiname path)) @@ -290,7 +291,9 @@ ("formatversion" . "2") ("format" . "json"))))) (log-outgoing dest-url) - (easy:get dest-url #:timeouts timeouts)] + (easy:get dest-url + #:timeouts timeouts + #:headers `#hasheq((cookie . ,(format "theme=~a" (user-cookies^-theme user-cookies)))))] [siteinfo (siteinfo-fetch wikiname)]) (cond @@ -307,6 +310,7 @@ (define body (generate-wiki-page (update-tree-wiki page wikiname) + #:req req #:source-url source-url #:wikiname wikiname #:title title @@ -317,9 +321,9 @@ (build-headers always-headers (when redirect-msg - (let* ([dest (get-attribute 'href (bits->attributes ((query-selector (λ (t a c) (eq? t 'a)) redirect-msg))))] - [value (bytes-append #"0;url=" (string->bytes/utf-8 dest))]) - (header #"Refresh" value))))) + (let* ([dest (get-attribute 'href (bits->attributes ((query-selector (λ (t a c) (eq? t 'a)) redirect-msg))))] + [value (bytes-append #"0;url=" (string->bytes/utf-8 dest))]) + (header #"Refresh" value))))) (when (config-true? 'debug) ; used for its side effects ; convert to string with error checking, error will be raised if xexp is invalid diff --git a/static/icon-theme-dark.svg b/static/icon-theme-dark.svg new file mode 100644 index 0000000..bc36ede --- /dev/null +++ b/static/icon-theme-dark.svg @@ -0,0 +1,2 @@ + + diff --git a/static/icon-theme-default.svg b/static/icon-theme-default.svg new file mode 100644 index 0000000..4f5655c --- /dev/null +++ b/static/icon-theme-default.svg @@ -0,0 +1,2 @@ + + diff --git a/static/icon-theme-light.svg b/static/icon-theme-light.svg new file mode 100644 index 0000000..cc19c3a --- /dev/null +++ b/static/icon-theme-light.svg @@ -0,0 +1,2 @@ + + diff --git a/static/main.css b/static/main.css index 0976e72..c6b2791 100644 --- a/static/main.css +++ b/static/main.css @@ -51,7 +51,7 @@ p { .custom-top { display: flex; flex-wrap: wrap; - align-items: baseline; + align-items: center; justify-content: space-between; } .page-title { @@ -227,6 +227,7 @@ figcaption, .lightbox-caption, .thumbcaption { display: grid; grid-template-columns: auto 1fr; grid-gap: 0px 5px; + align-items: baseline; } .bw-ss__container { grid-column: 2; @@ -282,6 +283,47 @@ figcaption, .lightbox-caption, .thumbcaption { text-align: left; } +/* (breezewiki) theme selector */ +.bw-theme__select { + display: grid; + grid-template-columns: auto auto; + grid-gap: 0px 5px; + justify-content: right; + align-items: baseline; + margin-top: 4px; +} +.bw-theme__items { + display: flex; +} +.bw-theme__item { + display: flex; + align-items: baseline; + padding: 2px; + border: 1px solid var(--theme-border-color); + border-right-width: 0px; + background-color: var(--custom-table-background); + color: var(--theme-page-text-color); + transition: none; +} +.bw-theme__item:hover, .bw-theme__item:focus { + /* background-color: var(--theme-page-background-color); */ + color: var(--theme-accent-color); +} +.bw-theme__item:first-child { + border-radius: 4px 0px 0px 4px; +} +.bw-theme__item:last-child { + border-radius: 0px 4px 4px 0px; + border-right-width: 1px; +} +.bw-theme__item--selected, .bw-theme__item--selected:hover, .bw-theme__item--selected:focus { + background-color: var(--theme-accent-color); + color: var(--theme-accent-label-color); +} +.bw-theme__icon-container svg { + vertical-align: middle; +} + /* nintendo independent wiki alliance notice */ .niwa__notice { background: #fdedd8;