Merge pull request #81 from zedeus/themes

Themes
This commit is contained in:
Zed 2019-10-24 00:03:54 +02:00 committed by GitHub
commit 510abb9b50
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
30 changed files with 252 additions and 119 deletions

View file

@ -10,7 +10,7 @@ Inspired by the [invidio.us](https://github.com/omarroth/invidious) project.
- Prevents Twitter from tracking your IP or JavaScript fingerprint - Prevents Twitter from tracking your IP or JavaScript fingerprint
- Unofficial API (no rate limits or developer account required) - Unofficial API (no rate limits or developer account required)
- AGPLv3 licensed, no proprietary instances permitted - AGPLv3 licensed, no proprietary instances permitted
- Dark theme - Themes
- Lightweight (for [@nim_lang](https://twitter.com/nim_lang), 36KB vs 580KB from twitter.com) - Lightweight (for [@nim_lang](https://twitter.com/nim_lang), 36KB vs 580KB from twitter.com)
- Native RSS feeds - Native RSS feeds
- Mobile support (responsive design) - Mobile support (responsive design)
@ -21,7 +21,6 @@ Inspired by the [invidio.us](https://github.com/omarroth/invidious) project.
- More caching (waiting for [moigagoo/norm#19](https://github.com/moigagoo/norm/pull/19)) - More caching (waiting for [moigagoo/norm#19](https://github.com/moigagoo/norm/pull/19))
- Simple account system with customizable feed - Simple account system with customizable feed
- Json API endpoints - Json API endpoints
- Themes
- Nitter logo - Nitter logo
- Emoji support (WIP, uses native font for now) - Emoji support (WIP, uses native font for now)

View file

@ -9,3 +9,6 @@ hostname = "nitter.net"
[Cache] [Cache]
directory = "./tmp" directory = "./tmp"
profileMinutes = 10 # how long to cache profiles profileMinutes = 10 # how long to cache profiles
[Config]
defaultTheme = "Dark"

View file

@ -11,7 +11,7 @@ bin = @["nitter"]
# Dependencies # Dependencies
requires "nim >= 0.19.9" requires "nim >= 0.19.9"
requires "norm >= 1.0.17" requires "norm#head"
requires "https://github.com/dom96/httpbeast#head" requires "https://github.com/dom96/httpbeast#head"
requires "jester >= 0.4.3" requires "jester >= 0.4.3"
requires "regex >= 0.11.2" requires "regex >= 0.11.2"

View file

@ -0,0 +1,3 @@
body {
/* uses default values */
}

View file

@ -0,0 +1,37 @@
body {
--bg_color: #E6ECF0;
--fg_color: #0F0F0F;
--fg_faded: #657786;
--fg_dark: var(--fg_faded);
--fg_nav: var(--accent);
--bg_panel: #FFFFFF;
--bg_elements: #FDFDFD;
--bg_overlays: #FFFFFF;
--bg_hover: #F5F8FA;
--grey: var(--fg_faded);
--dark_grey: #D6D6D6;
--darker_grey: #CECECE;
--darkest_grey: #ECECEC;
--border_grey: #E6ECF0;
--accent: #1DA1F2;
--accent_light: #A0EDFF;
--accent_dark: var(--accent);
--accent_border: #1DA1F296;
--play_button: #D84D4D;
--play_button_hover: #FF6C60;
--more_replies_dots: #0199F7;
--error_red: #FF7266;
--verified_blue: var(--accent);
--icon_text: ##F8F8F2;
--tab: var(--accent);
--tab_selected: #000000;
--profile_stat: var(--fg_dark);
}

View file

@ -21,5 +21,7 @@ proc getConfig*(path: string): Config =
hostname: cfg.get("Server", "hostname", "nitter.net"), hostname: cfg.get("Server", "hostname", "nitter.net"),
cacheDir: cfg.get("Cache", "directory", "/tmp/nitter"), cacheDir: cfg.get("Cache", "directory", "/tmp/nitter"),
profileCacheTime: cfg.get("Cache", "profileMinutes", 10) profileCacheTime: cfg.get("Cache", "profileMinutes", 10),
defaultTheme: cfg.get("Config", "defaultTheme", "Dark")
) )

View file

@ -14,6 +14,10 @@ static:
if missing.len > 0: if missing.len > 0:
raiseAssert("{$1} missing from the Prefs type" % missing.join(", ")) raiseAssert("{$1} missing from the Prefs type" % missing.join(", "))
template safeAddColumn(field: typedesc): untyped =
try: field.addColumn
except DbError: discard
dbFromTypes("prefs.db", "", "", "", [Prefs]) dbFromTypes("prefs.db", "", "", "", [Prefs])
withDb: withDb:
@ -21,10 +25,12 @@ withDb:
createTables() createTables()
except DbError: except DbError:
discard discard
Prefs.theme.safeAddColumn
proc getDefaultPrefs(hostname: string): Prefs = proc getDefaultPrefs(cfg: Config): Prefs =
result = genDefaultPrefs() result = genDefaultPrefs()
result.replaceTwitter = hostname result.replaceTwitter = cfg.hostname
result.theme = cfg.defaultTheme
proc cache*(prefs: var Prefs) = proc cache*(prefs: var Prefs) =
withDb: withDb:
@ -35,18 +41,18 @@ proc cache*(prefs: var Prefs) =
except AssertionError, KeyError: except AssertionError, KeyError:
prefs.insert() prefs.insert()
proc getPrefs*(id, hostname: string): Prefs = proc getPrefs*(id: string; cfg: Config): Prefs =
if id.len == 0: if id.len == 0:
return getDefaultPrefs(hostname) return getDefaultPrefs(cfg)
withDb: withDb:
try: try:
result.getOne("id = ?", id) result.getOne("id = ?", id)
except KeyError: except KeyError:
result = getDefaultPrefs(hostname) result = getDefaultPrefs(cfg)
proc resetPrefs*(prefs: var Prefs; hostname: string) = proc resetPrefs*(prefs: var Prefs; cfg: Config) =
var defPrefs = getDefaultPrefs(hostname) var defPrefs = getDefaultPrefs(cfg)
defPrefs.id = prefs.id defPrefs.id = prefs.id
cache(defPrefs) cache(defPrefs)
prefs = defPrefs prefs = defPrefs

View file

@ -51,6 +51,9 @@ const prefList*: OrderedTable[string, seq[Pref]] = {
], ],
"Display": @[ "Display": @[
Pref(kind: select, name: "theme", label: "Theme",
defaultOption: "Dark"),
Pref(kind: checkbox, name: "hideTweetStats", Pref(kind: checkbox, name: "hideTweetStats",
label: "Hide tweet stats (replies, retweets, likes)", label: "Hide tweet stats (replies, retweets, likes)",
defaultState: false), defaultState: false),
@ -94,10 +97,12 @@ macro genUpdatePrefs*(): untyped =
of input: of input:
result.add quote do: prefs.`ident` = xmltree.escape(strip(`value`)) result.add quote do: prefs.`ident` = xmltree.escape(strip(`value`))
of select: of select:
let name = pref.name
let options = pref.options let options = pref.options
let default = pref.defaultOption let default = pref.defaultOption
result.add quote do: result.add quote do:
if `value` in `options`: prefs.`ident` = `value` if `name` == "theme": prefs.`ident` = `value`
elif `value` in `options`: prefs.`ident` = `value`
else: prefs.`ident` = `default` else: prefs.`ident` = `default`
result.add quote do: result.add quote do:

View file

@ -1,4 +1,4 @@
import strutils, uri import strutils, uri, os, algorithm
import jester import jester
@ -8,13 +8,18 @@ import ../views/[general, preferences]
export preferences export preferences
proc findThemes*(dir: string): seq[string] =
for kind, path in walkDir(dir / "css" / "themes"):
result.add path.splitFile.name.capitalizeAscii
sort(result)
proc createPrefRouter*(cfg: Config) = proc createPrefRouter*(cfg: Config) =
router preferences: router preferences:
template savePrefs(): untyped = template savePrefs(): untyped =
setCookie("preferences", $prefs.id, daysForward(360), httpOnly=true, secure=cfg.useHttps) setCookie("preferences", $prefs.id, daysForward(360), httpOnly=true, secure=cfg.useHttps)
get "/settings": get "/settings":
let html = renderPreferences(cookiePrefs(), refPath()) let html = renderPreferences(cookiePrefs(), refPath(), findThemes(cfg.staticDir))
resp renderMain(html, request, cfg, "Preferences") resp renderMain(html, request, cfg, "Preferences")
get "/settings/@i?": get "/settings/@i?":
@ -28,7 +33,7 @@ proc createPrefRouter*(cfg: Config) =
post "/resetprefs": post "/resetprefs":
var prefs = cookiePrefs() var prefs = cookiePrefs()
resetPrefs(prefs, cfg.hostname) resetPrefs(prefs, cfg)
savePrefs() savePrefs()
redirect($(parseUri("/settings") ? filterParams(request.params))) redirect($(parseUri("/settings") ? filterParams(request.params)))

View file

@ -2,7 +2,7 @@ import ../utils, ../prefs
export utils, prefs export utils, prefs
template cookiePrefs*(): untyped {.dirty.} = template cookiePrefs*(): untyped {.dirty.} =
getPrefs(request.cookies.getOrDefault("preferences"), cfg.hostname) getPrefs(request.cookies.getOrDefault("preferences"), cfg)
template getPath*(): untyped {.dirty.} = template getPath*(): untyped {.dirty.} =
$(parseUri(request.path) ? filterParams(request.params)) $(parseUri(request.path) ? filterParams(request.params))

View file

@ -7,15 +7,15 @@
} }
.error-panel { .error-panel {
@include center-panel($error_red); @include center-panel(var(--error_red));
} }
.search-bar > form { .search-bar > form {
@include center-panel($darkest-grey); @include center-panel(var(--darkest_grey));
button { button {
background: #303030; background: var(--bg_elements);
color: $fg_color; color: var(--fg_color);
border: 0; border: 0;
border-radius: 3px; border-radius: 3px;
cursor: pointer; cursor: pointer;
@ -27,8 +27,8 @@
input { input {
font-size: 16px; font-size: 16px;
width: 100%; width: 100%;
background: $bg_elements; background: var(--bg_elements);
color: $fg_color; color: var(--fg_color);
border: 0; border: 0;
border-radius: 4px; border-radius: 4px;
padding: 4px; padding: 4px;

View file

@ -19,11 +19,11 @@
&:hover { &:hover {
.overlay-circle { .overlay-circle {
border-color: $accent; border-color: var(--play_button_hover);
} }
.overlay-triangle { .overlay-triangle {
border-color: transparent transparent transparent $accent; border-color: transparent transparent transparent var(--play_button_hover);
} }
} }
} }
@ -51,11 +51,11 @@
@mixin input-colors { @mixin input-colors {
&:hover { &:hover {
border-color: $accent; border-color: var(--accent);
} }
&:active { &:active {
border-color: $accent_light; border-color: var(--accent_light);
} }
} }

View file

@ -2,11 +2,13 @@
$bg_color: #0F0F0F; $bg_color: #0F0F0F;
$fg_color: #F8F8F2; $fg_color: #F8F8F2;
$fg_faded: #F8F8F2CF; $fg_faded: #F8F8F2CF;
$fg_dark: #9d9da0; $fg_dark: #FF6C60;
$fg_nav: #FF6C60;
$bg_panel: #161616; $bg_panel: #161616;
$bg_elements: #121212; $bg_elements: #121212;
$bg_overlays: #1F1F1F; $bg_overlays: #1F1F1F;
$bg_hover: #1A1A1A;
$grey: #888889; $grey: #888889;
$dark_grey: #404040; $dark_grey: #404040;
@ -19,11 +21,17 @@ $accent_light: #FFACA0;
$accent_dark: #8A3731; $accent_dark: #8A3731;
$accent_border: #FF6C6091; $accent_border: #FF6C6091;
$play_button_red: #D8574D; $play_button: #D8574D;
$play_button_hover: #FF6C60;
$more_replies_dots: #AD433B; $more_replies_dots: #AD433B;
$error_red: #420A05; $error_red: #420A05;
$verified_blue: #1DA1F2; $verified_blue: #1DA1F2;
$icon_text: $fg_color;
$tab: $fg_color;
$tab_selected: $accent;
$shadow: rgba(0,0,0,.6); $shadow: rgba(0,0,0,.6);
$shadow_dark: rgba(0,0,0,.2); $shadow_dark: rgba(0,0,0,.2);

View file

@ -9,8 +9,45 @@
@import 'search'; @import 'search';
body { body {
background-color: $bg_color; // colors
color: $fg_color; --bg_color: #{$bg_color};
--fg_color: #{$fg_color};
--fg_faded: #{$fg_faded};
--fg_dark: #{$fg_dark};
--fg_nav: #{$fg_nav};
--bg_panel: #{$bg_panel};
--bg_elements: #{$bg_elements};
--bg_overlays: #{$bg_overlays};
--bg_hover: #{$bg_hover};
--grey: #{$grey};
--dark_grey: #{$dark_grey};
--darker_grey: #{$darker_grey};
--darkest_grey: #{$darkest_grey};
--border_grey: #{$border_grey};
--accent: #{$accent};
--accent_light: #{$accent_light};
--accent_dark: #{$accent_dark};
--accent_border: #{$accent_border};
--play_button: #{$play_button};
--play_button_hover: #{$play_button_hover};
--more_replies_dots: #{$more_replies_dots};
--error_red: #{$error_red};
--verified_blue: #{$verified_blue};
--icon_text: #{$icon_text};
--tab: #{$fg_color};
--tab_selected: #{$accent};
--profile_stat: #{$fg_color};
background-color: var(--bg_color);
color: var(--fg_color);
font-family: $font_0, $font_1, $font_2, $font_3; font-family: $font_0, $font_1, $font_2, $font_3;
font-size: 14px; font-size: 14px;
line-height: 1.3; line-height: 1.3;
@ -36,7 +73,7 @@ p {
} }
a { a {
color: $accent; color: var(--accent);
&:hover { &:hover {
text-decoration: underline; text-decoration: underline;
@ -54,7 +91,7 @@ legend {
padding: .6em 0 .3em 0; padding: .6em 0 .3em 0;
border: 0; border: 0;
font-size: 16px; font-size: 16px;
border-bottom: 1px solid $border_grey; border-bottom: 1px solid var(--border_grey);
margin-bottom: 8px; margin-bottom: 8px;
} }
@ -80,7 +117,7 @@ ul {
width: 100%; width: 100%;
margin: 0 auto; margin: 0 auto;
margin-top: 10px; margin-top: 10px;
background-color: $bg_overlays; background-color: var(--bg_overlays);
padding: 10px 15px; padding: 10px 15px;
align-self: start; align-self: start;
@ -91,8 +128,8 @@ ul {
} }
.verified-icon { .verified-icon {
color: $fg_color; color: var(--icon_text);
background-color: $verified_blue; background-color: var(--verified_blue);
border-radius: 50%; border-radius: 50%;
flex-shrink: 0; flex-shrink: 0;
margin: 2px 0 3px 3px; margin: 2px 0 3px 3px;

View file

@ -3,9 +3,9 @@
button { button {
@include input-colors; @include input-colors;
background-color: $bg_elements; background-color: var(--bg_elements);
color: $fg_color; color: var(--fg_color);
border: 1px solid $accent_border; border: 1px solid var(--accent_border);
padding: 3px 6px; padding: 3px 6px;
font-size: 14px; font-size: 14px;
cursor: pointer; cursor: pointer;
@ -13,12 +13,13 @@ button {
} }
input[type="text"], input[type="text"],
input[type="date"] { input[type="date"],
select {
@include input-colors; @include input-colors;
background-color: $bg_elements; background-color: var(--bg_elements);
padding: 1px 4px; padding: 1px 4px;
color: $fg_color; color: var(--fg_color);
border: 1px solid $accent_border; border: 1px solid var(--accent_border);
border-radius: 0; border-radius: 0;
font-size: 14px; font-size: 14px;
} }
@ -40,8 +41,8 @@ input::-webkit-calendar-picker-indicator {
input::-webkit-datetime-edit-day-field:focus, input::-webkit-datetime-edit-day-field:focus,
input::-webkit-datetime-edit-month-field:focus, input::-webkit-datetime-edit-month-field:focus,
input::-webkit-datetime-edit-year-field:focus { input::-webkit-datetime-edit-year-field:focus {
background-color: $accent; background-color: var(--accent);
color: $fg_color; color: var(--fg_color);
outline: none; outline: none;
} }
@ -64,7 +65,7 @@ input::-webkit-datetime-edit-year-field:focus {
} }
.icon-button button { .icon-button button {
color: $accent; color: var(--accent);
text-decoration: none; text-decoration: none;
background: none; background: none;
border: none; border: none;
@ -73,7 +74,7 @@ input::-webkit-datetime-edit-year-field:focus {
padding-left: 4px; padding-left: 4px;
&:hover { &:hover {
color: $accent_light; color: var(--accent_light);
} }
} }
@ -83,8 +84,8 @@ input::-webkit-datetime-edit-year-field:focus {
right: 0; right: 0;
height: 17px; height: 17px;
width: 17px; width: 17px;
background-color: $bg_elements; background-color: var(--bg_elements);
border: 1px solid $accent_border; border: 1px solid var(--accent_border);
&:after { &:after {
content: ""; content: "";
@ -114,11 +115,11 @@ input::-webkit-datetime-edit-year-field:focus {
} }
&:hover input ~ .checkbox { &:hover input ~ .checkbox {
border-color: $accent; border-color: var(--accent);
} }
&:active input ~ .checkbox { &:active input ~ .checkbox {
border-color: $accent_light; border-color: var(--accent_light);
} }
.checkbox:after { .checkbox:after {
@ -143,6 +144,16 @@ input::-webkit-datetime-edit-year-field:focus {
padding-right: 135px; padding-right: 135px;
} }
select {
position: absolute;
top: 0;
right: 0;
display: block;
-moz-appearance: none;
-webkit-appearance: none;
appearance: none;
}
input[type="text"] { input[type="text"] {
position: absolute; position: absolute;
right: 0; right: 0;

View file

@ -4,12 +4,16 @@ nav {
display: flex; display: flex;
align-items: center; align-items: center;
position: fixed; position: fixed;
background-color: $bg_overlays; background-color: var(--bg_overlays);
box-shadow: 0 0 4px $shadow; box-shadow: 0 0 4px $shadow;
padding: 0; padding: 0;
width: 100%; width: 100%;
height: 50px; height: 50px;
z-index: 1000; z-index: 1000;
a, .icon-button button {
color: var(--fg_nav);
}
} }
.inner-nav { .inner-nav {
@ -26,7 +30,7 @@ nav {
font-weight: 600; font-weight: 600;
&:hover { &:hover {
color: $accent_light; color: var(--accent_light);
text-decoration: unset; text-decoration: unset;
} }
} }
@ -54,7 +58,7 @@ nav {
padding-left: 4px; padding-left: 4px;
&:hover { &:hover {
color: $accent_light; color: var(--accent_light);
text-decoration: unset; text-decoration: unset;
} }
} }

View file

@ -3,7 +3,7 @@
.profile-card { .profile-card {
flex-wrap: wrap; flex-wrap: wrap;
background: $bg_panel; background: var(--bg_panel);
padding: 12px; padding: 12px;
display: flex; display: flex;
} }
@ -20,14 +20,14 @@
.profile-card-username { .profile-card-username {
@include breakable; @include breakable;
color: $fg_color; color: var(--fg_color);
font-size: 14px; font-size: 14px;
display: block; display: block;
} }
.profile-card-fullname { .profile-card-fullname {
@include breakable; @include breakable;
color: $fg_color; color: var(--fg_color);
font-size: 16px; font-size: 16px;
font-weight: bold; font-weight: bold;
text-shadow: none; text-shadow: none;
@ -45,8 +45,8 @@
width: calc(100% - 8px); width: calc(100% - 8px);
height: 100%; height: 100%;
margin: 0; margin: 0;
border: 4px solid $darker_grey; border: 4px solid var(--darker_grey);
background: $bg_color; background: var(--bg_color);
} }
} }
@ -67,7 +67,7 @@
} }
.profile-joindate, .profile-location, profile-website { .profile-joindate, .profile-location, profile-website {
color: $fg_faded; color: var(--fg_faded);
margin: 2px 0; margin: 2px 0;
width: 100%; width: 100%;
} }
@ -94,10 +94,12 @@
.profile-stat-header { .profile-stat-header {
font-weight: bold; font-weight: bold;
color: var(--profile_stat);
} }
.profile-stat-num { .profile-stat-num {
display: block; display: block;
color: var(--profile_stat);
} }
@media(max-width: 600px) { @media(max-width: 600px) {

View file

@ -3,7 +3,7 @@
.photo-rail { .photo-rail {
&-card { &-card {
float: left; float: left;
background: $bg_panel; background: var(--bg_panel);
border-radius: 0 0 4px 4px; border-radius: 0 0 4px 4px;
width: 100%; width: 100%;
margin: 5px 0; margin: 5px 0;
@ -18,7 +18,7 @@
display: none; display: none;
width: calc(100% - 24px); width: calc(100% - 24px);
float: unset; float: unset;
color: $accent; color: var(--accent);
justify-content: space-between; justify-content: space-between;
} }

View file

@ -29,9 +29,9 @@
> label { > label {
display: inline; display: inline;
background-color: #121212; background-color: var(--bg_elements);
color: #F8F8F2; color: var(--fg_color);
border: 1px solid #FF6C6091; border: 1px solid var(--accent_border);
padding: 1px 6px 2px 6px; padding: 1px 6px 2px 6px;
font-size: 14px; font-size: 14px;
cursor: pointer; cursor: pointer;

View file

@ -5,15 +5,15 @@
} }
.timeline { .timeline {
background-color: $bg_panel; background-color: var(--bg_panel);
> div:not(:first-child) { > div:not(:first-child) {
border-top: 1px solid $border_grey; border-top: 1px solid var(--border_grey);
} }
} }
.timeline-header { .timeline-header {
background-color: $bg_panel; background-color: var(--bg_panel);
text-align: center; text-align: center;
padding: 8px; padding: 8px;
display: block; display: block;
@ -31,7 +31,7 @@
flex-wrap: wrap; flex-wrap: wrap;
list-style: none; list-style: none;
margin: 0 0 5px 0; margin: 0 0 5px 0;
background-color: $bg_panel; background-color: var(--bg_panel);
padding: 0; padding: 0;
} }
@ -42,7 +42,7 @@
a { a {
border-bottom: .1rem solid transparent; border-bottom: .1rem solid transparent;
color: inherit; color: var(--tab);
display: block; display: block;
padding: 8px 0; padding: 8px 0;
text-decoration: none; text-decoration: none;
@ -53,14 +53,14 @@
} }
&.active { &.active {
border-bottom-color: $accent; border-bottom-color: var(--tab_selected);
color: $accent; color: var(--tab_selected);
} }
} }
&.active a { &.active a {
border-bottom-color: $accent; border-bottom-color: var(--tab_selected);
color: $accent; color: var(--tab_selected);
} }
&.wide { &.wide {
@ -69,7 +69,7 @@
} }
.timeline-footer { .timeline-footer {
background-color: $bg_panel; background-color: var(--bg_panel);
padding: 6px 0; padding: 6px 0;
} }
@ -81,48 +81,48 @@
} }
h2 { h2 {
color: $accent; color: var(--accent);
font-size: 20px; font-size: 20px;
font-weight: 600; font-weight: 600;
} }
} }
.timeline-none { .timeline-none {
color: $accent; color: var(--accent);
font-size: 20px; font-size: 20px;
font-weight: 600; font-weight: 600;
text-align: center; text-align: center;
} }
.timeline-end { .timeline-end {
background-color: $bg_panel; background-color: var(--bg_panel);
color: $accent; color: var(--accent);
font-size: 16px; font-size: 16px;
font-weight: 600; font-weight: 600;
text-align: center; text-align: center;
} }
.show-more { .show-more {
background-color: $bg_panel; background-color: var(--bg_panel);
text-align: center; text-align: center;
padding: .75em 0; padding: .75em 0;
display: block !important; display: block !important;
a { a {
background-color: $darkest_grey; background-color: var(--darkest_grey);
display: inline-block; display: inline-block;
height: 2em; height: 2em;
padding: 0 2em; padding: 0 2em;
line-height: 2em; line-height: 2em;
&:hover { &:hover {
background-color: $darker_grey; background-color: var(--darker_grey);
} }
} }
} }
.top-ref { .top-ref {
background-color: #0f0f0f; background-color: var(--bg_color);
border-top: none !important; border-top: none !important;
.icon-down { .icon-down {
@ -132,7 +132,7 @@
text-decoration: none; text-decoration: none;
&:hover { &:hover {
color: $accent_light; color: var(--accent_light);
} }
&::before { &::before {

View file

@ -52,7 +52,7 @@
max-width: 80%; max-width: 80%;
font-size: 14px; font-size: 14px;
font-weight: 700; font-weight: 700;
color: $fg_color; color: var(--fg_color);
} }
.username { .username {
@ -68,10 +68,14 @@
margin-left: 4px; margin-left: 4px;
} }
.tweet-date a, .username, .show-more a {
color: var(--fg_dark);
}
.tweet-published { .tweet-published {
margin: 0; margin: 0;
margin-top: 5px; margin-top: 5px;
color: $grey; color: var(--grey);
pointer-events: all; pointer-events: all;
} }
@ -89,7 +93,7 @@
} }
.replying-to { .replying-to {
color: $fg_dark; color: var(--fg_faded);
margin: -2px 0 4px; margin: -2px 0 4px;
a { a {
@ -99,7 +103,7 @@
.retweet-header, .pinned, .tweet-stats { .retweet-header, .pinned, .tweet-stats {
align-content: center; align-content: center;
color: $grey; color: var(--grey);
display: flex; display: flex;
flex-shrink: 0; flex-shrink: 0;
flex-wrap: wrap; flex-wrap: wrap;
@ -134,9 +138,9 @@
width: 100%; width: 100%;
height: 100%; height: 100%;
padding: 12px; padding: 12px;
border: solid 1px $dark_grey; border: solid 1px var(--dark_grey);
border-radius: 10px; border-radius: 10px;
background-color: $bg_color; background-color: var(--bg_color);
} }
.tweet-link { .tweet-link {
@ -147,6 +151,6 @@
position: absolute; position: absolute;
&:hover { &:hover {
background-color: #1a1a1a; background-color: var(--bg_hover);
} }
} }

View file

@ -10,15 +10,15 @@
border-radius: 10px; border-radius: 10px;
border-width: 1px; border-width: 1px;
border-style: solid; border-style: solid;
border-color: $dark_grey; border-color: var(--dark_grey);
background-color: $bg_elements; background-color: var(--bg_elements);
overflow: hidden; overflow: hidden;
color: inherit; color: inherit;
display: flex; display: flex;
text-decoration: none !important; text-decoration: none !important;
&:hover { &:hover {
border-color: $grey; border-color: var(--grey);
} }
.attachments { .attachments {
@ -43,7 +43,7 @@
.card-destination { .card-destination {
@include ellipsis; @include ellipsis;
color: $grey; color: var(--grey);
display: block; display: block;
} }
@ -73,7 +73,7 @@
left: 0; left: 0;
bottom: 0; bottom: 0;
right: 0; right: 0;
background-color: $fg_color; background-color: var(--fg_color);
img { img {
width: 100%; width: 100%;
@ -107,7 +107,7 @@
.card-image { .card-image {
position: unset; position: unset;
border-style: solid; border-style: solid;
border-color: $dark_grey; border-color: var(--dark_grey);
border-width: 0; border-width: 0;
border-bottom-width: 1px; border-bottom-width: 1px;
} }

View file

@ -26,7 +26,7 @@
border-radius: 7px; border-radius: 7px;
overflow: hidden; overflow: hidden;
flex-flow: column; flex-flow: column;
background-color: $bg_color; background-color: var(--bg_color);
align-items: center; align-items: center;
pointer-events: all; pointer-events: all;
@ -81,13 +81,13 @@
.overlay-circle { .overlay-circle {
border-radius: 50%; border-radius: 50%;
background-color: $dark_grey; background-color: var(--dark_grey);
width: 40px; width: 40px;
height: 40px; height: 40px;
align-items: center; align-items: center;
display: flex; display: flex;
border-width: 5px; border-width: 5px;
border-color: $play_button_red; border-color: var(--play_button);
border-style: solid; border-style: solid;
} }
@ -96,7 +96,7 @@
height: 0; height: 0;
border-style: solid; border-style: solid;
border-width: 12px 0 12px 17px; border-width: 12px 0 12px 17px;
border-color: transparent transparent transparent $play_button_red; border-color: transparent transparent transparent var(--play_button);
margin-left: 14px; margin-left: 14px;
} }

View file

@ -5,7 +5,7 @@
position: relative; position: relative;
margin: 6px 0; margin: 6px 0;
height: 26px; height: 26px;
background: $bg_color; background: var(--bg_color);
border-radius: 5px; border-radius: 5px;
display: flex; display: flex;
align-items: center; align-items: center;
@ -14,7 +14,7 @@
.poll-choice-bar { .poll-choice-bar {
height: 100%; height: 100%;
position: absolute; position: absolute;
background: $dark_grey; background: var(--dark_grey);
} }
.poll-choice-value { .poll-choice-value {
@ -33,10 +33,10 @@
} }
.poll-info { .poll-info {
color: $grey; color: var(--grey);
pointer-events: all; pointer-events: all;
} }
.leader .poll-choice-bar { .leader .poll-choice-bar {
background: $accent_dark; background: var(--accent_dark);
} }

View file

@ -2,20 +2,20 @@
.quote { .quote {
margin-top: 10px; margin-top: 10px;
border: solid 1px $dark_grey; border: solid 1px var(--dark_grey);
border-radius: 10px; border-radius: 10px;
background-color: $bg_elements; background-color: var(--bg_elements);
overflow: auto; overflow: auto;
padding: 6px; padding: 6px;
position: relative; position: relative;
pointer-events: all; pointer-events: all;
&:hover { &:hover {
border-color: $grey; border-color: var(--grey);
} }
&.unavailable:hover { &.unavailable:hover {
border-color: $dark_grey; border-color: var(--dark_grey);
} }
} }
@ -85,7 +85,7 @@
} }
.quote-sensitive { .quote-sensitive {
background: $darker_grey; background: var(--darker_grey);
width: 102px; width: 102px;
height: 102px; height: 102px;
border-radius: 12px; border-radius: 12px;
@ -96,7 +96,7 @@
.quote-sensitive-icon { .quote-sensitive-icon {
font-size: 40px; font-size: 40px;
color: $grey; color: var(--grey);
} }
@media(max-width: 600px) { @media(max-width: 600px) {

View file

@ -11,7 +11,7 @@
.main-thread { .main-thread {
margin-bottom: 20px; margin-bottom: 20px;
background-color: $bg_panel; background-color: var(--bg_panel);
} }
.main-tweet, .replies { .main-tweet, .replies {
@ -24,14 +24,14 @@
} }
.reply { .reply {
background-color: $bg_panel; background-color: var(--bg_panel);
margin-bottom: 10px; margin-bottom: 10px;
} }
.thread-line { .thread-line {
.timeline-item::before, .timeline-item::before,
&.timeline-item::before { &.timeline-item::before {
background: $accent_dark; background: var(--accent_dark);
content: ''; content: '';
position: relative; position: relative;
min-width: 3px; min-width: 3px;
@ -53,7 +53,7 @@
.more-replies::before { .more-replies::before {
content: '...'; content: '...';
background: unset; background: unset;
color: $more_replies_dots; color: var(--more_replies_dots);
font-weight: bold; font-weight: bold;
font-size: 20px; font-size: 20px;
line-height: 0.25em; line-height: 0.25em;

View file

@ -177,6 +177,7 @@ type
hostname*: string hostname*: string
cacheDir*: string cacheDir*: string
profileCacheTime*: int profileCacheTime*: int
defaultTheme*: string
proc contains*(thread: Chain; tweet: Tweet): bool = proc contains*(thread: Chain; tweet: Tweet): bool =
thread.content.anyIt(it.id == tweet.id) thread.content.anyIt(it.id == tweet.id)

View file

@ -29,11 +29,13 @@ proc renderNavbar*(title, rss: string; req: Request): VNode =
proc renderMain*(body: VNode; req: Request; cfg: Config; titleText=""; desc=""; proc renderMain*(body: VNode; req: Request; cfg: Config; titleText=""; desc="";
rss=""; `type`="article"; video=""; images: seq[string] = @[]): string = rss=""; `type`="article"; video=""; images: seq[string] = @[]): string =
let prefs = getPrefs(req.cookies.getOrDefault("preferences"), cfg.hostname) let prefs = getPrefs(req.cookies.getOrDefault("preferences"), cfg)
let theme = "/css/themes/" & toLowerAscii(prefs.theme) & ".css"
let node = buildHtml(html(lang="en")): let node = buildHtml(html(lang="en")):
head: head:
link(rel="stylesheet", `type`="text/css", href="/css/style.css") link(rel="stylesheet", `type`="text/css", href="/css/style.css")
link(rel="stylesheet", `type`="text/css", href="/css/fontello.css") link(rel="stylesheet", `type`="text/css", href="/css/fontello.css")
link(rel="stylesheet", `type`="text/css", href=theme)
link(rel="apple-touch-icon", sizes="180x180", href="/apple-touch-icon.png") link(rel="apple-touch-icon", sizes="180x180", href="/apple-touch-icon.png")
link(rel="icon", type="image/png", sizes="32x32", href="/favicon-32x32.png") link(rel="icon", type="image/png", sizes="32x32", href="/favicon-32x32.png")

View file

@ -1,4 +1,4 @@
import tables, macros, strutils import tables, macros, strutils, os
import karax/[karaxdsl, vdom, vstyles] import karax/[karaxdsl, vdom, vstyles]
import renderutils import renderutils
@ -22,12 +22,16 @@ macro renderPrefs*(): untyped =
case pref.kind case pref.kind
of checkbox: discard of checkbox: discard
of select: stmt[0].add newLit(pref.options)
of input: stmt[0].add newLit(pref.placeholder) of input: stmt[0].add newLit(pref.placeholder)
of select:
if pref.name == "theme":
stmt[0].add ident("themes")
else:
stmt[0].add newLit(pref.options)
result[2].add stmt result[2].add stmt
proc renderPreferences*(prefs: Prefs; path: string): VNode = proc renderPreferences*(prefs: Prefs; path: string; themes: seq[string]): VNode =
buildHtml(tdiv(class="overlay-panel")): buildHtml(tdiv(class="overlay-panel")):
fieldset(class="preferences"): fieldset(class="preferences"):
form(`method`="post", action="/saveprefs"): form(`method`="post", action="/saveprefs"):

View file

@ -71,7 +71,7 @@ proc genInput*(pref, label, state, placeholder: string; class=""; autofocus=fals
verbatim &"<input name={pref} type=\"text\" placeholder=\"{p}\" value=\"{s}\" {a}/>" verbatim &"<input name={pref} type=\"text\" placeholder=\"{p}\" value=\"{s}\" {a}/>"
proc genSelect*(pref, label, state: string; options: seq[string]): VNode = proc genSelect*(pref, label, state: string; options: seq[string]): VNode =
buildHtml(tdiv(class="pref-group")): buildHtml(tdiv(class="pref-group pref-input")):
label(`for`=pref): text label label(`for`=pref): text label
select(name=pref): select(name=pref):
for opt in options: for opt in options: