M api/channels.js => api/channels.js +1 -1
@@ 24,7 24,7 @@ module.exports = [
video.watched = watchedVideos.includes(video.videoId)
})
}
- return render(200, "pug/channel.pug", {url, data, subscribed, instanceOrigin})
+ return render(200, "pug/channel.pug", {settings, url, data, subscribed, instanceOrigin})
}
}
]
M api/filters.js => api/filters.js +9 -6
@@ 9,8 9,7 @@ const {Matcher, PatternCompileError} = require("../utils/matcher")
const filterMaxLength = 160
const regexpEnabledText = constants.server_setup.allow_regexp_filters ? "" : "not"
-function getCategories(req) {
- const user = getUser(req)
+function getCategories(user) {
const filters = user.getFilters()
// Sort filters into categories for display. Titles are already sorted.
@@ 39,7 38,9 @@ function getCategories(req) {
module.exports = [
{
route: "/filters", methods: ["GET"], code: async ({req, url}) => {
- const categories = getCategories(req)
+ const user = getUser(req)
+ const categories = getCategories(user)
+ const settings = user.getSettingsOrDefaults()
let referrer = url.searchParams.get("referrer") || null
let type = null
@@ 54,7 55,7 @@ module.exports = [
label = url.searchParams.get("label")
}
- return render(200, "pug/filters.pug", {categories, type, contents, label, referrer, filterMaxLength, regexpEnabledText})
+ return render(200, "pug/filters.pug", {settings, categories, type, contents, label, referrer, filterMaxLength, regexpEnabledText})
}
},
{
@@ 100,8 101,10 @@ module.exports = [
return true
}, state => {
const {type, contents, label, compileError} = state
- const categories = getCategories(req)
- return render(400, "pug/filters.pug", {categories, type, contents, label, compileError, filterMaxLength, regexpEnabledText})
+ const user = getUser(req)
+ const categories = getCategories(user)
+ const settings = user.getSettingsOrDefaults()
+ return render(400, "pug/filters.pug", {settings, categories, type, contents, label, compileError, filterMaxLength, regexpEnabledText})
})
.last(state => {
const {type, contents, label} = state
M api/pages.js => api/pages.js +22 -3
@@ 1,16 1,35 @@
const {render} = require("pinski/plugins")
+const {getUser} = require("../utils/getuser")
module.exports = [
{
route: "/", methods: ["GET"], code: async ({req}) => {
const userAgent = req.headers["user-agent"] || ""
const mobile = userAgent.toLowerCase().includes("mobile")
- return render(200, "pug/home.pug", {mobile})
+ const user = getUser(req)
+ const settings = user.getSettingsOrDefaults()
+ return render(200, "pug/home.pug", {settings, mobile})
}
},
{
- route: "/js-licenses", methods: ["GET"], code: async () => {
- return render(200, "pug/js-licenses.pug")
+ route: "/(?:js-)?licenses", methods: ["GET"], code: async ({req}) => {
+ const user = getUser(req)
+ const settings = user.getSettingsOrDefaults()
+ return render(200, "pug/licenses.pug", {settings})
+ }
+ },
+ {
+ route: "/cant-think", methods: ["GET"], code: async ({req}) => {
+ const user = getUser(req)
+ const settings = user.getSettingsOrDefaults()
+ return render(200, "pug/cant-think.pug", {settings})
+ }
+ },
+ {
+ route: "/privacy", methods: ["GET"], code: async ({req}) => {
+ const user = getUser(req)
+ const settings = user.getSettingsOrDefaults()
+ return render(200, "pug/privacy.pug", {settings})
}
}
]
M api/search.js => api/search.js +1 -1
@@ 26,7 26,7 @@ module.exports = [
const filters = user.getFilters()
results = converters.applyVideoFilters(results, filters).videos
- return render(200, "pug/search.pug", {url, query, results, instanceOrigin})
+ return render(200, "pug/search.pug", {settings, url, query, results, instanceOrigin})
}
}
]
M api/video.js => api/video.js +4 -4
@@ 111,7 111,7 @@ module.exports = [
// Check if playback is allowed
const videoTakedownInfo = db.prepare("SELECT id, org, url FROM TakedownVideos WHERE id = ?").get(id)
if (videoTakedownInfo) {
- return render(451, "pug/takedown-video.pug", videoTakedownInfo)
+ return render(451, "pug/takedown-video.pug", Object.assign({settings}, videoTakedownInfo))
}
// Media fragment
@@ 129,7 129,7 @@ module.exports = [
// Work out how to fetch the video
if (req.method === "GET") {
if (settings.local) { // skip to the local fetching page, which will then POST video data in a moment
- return render(200, "pug/local-video.pug", {id})
+ return render(200, "pug/local-video.pug", {settings, id})
}
var instanceOrigin = settings.instance
var outURL = `${instanceOrigin}/api/v1/videos/${id}`
@@ 153,7 153,7 @@ module.exports = [
// automatically add the entry to the videos list, so it won't be fetched again
const args = {id, ...channelTakedownInfo}
db.prepare("INSERT INTO TakedownVideos (id, org, url) VALUES (@id, @org, @url)").run(args)
- return render(451, "pug/takedown-video.pug", channelTakedownInfo)
+ return render(451, "pug/takedown-video.pug", Object.assign({settings}, channelTakedownInfo))
}
// process stream list ordering
@@ 225,7 225,7 @@ module.exports = [
// Create appropriate formatted message
const message = render(0, `pug/errors/${errorType}.pug`, locals).content
- return render(500, "pug/video.pug", {video: {videoId: id}, error: true, message})
+ return render(500, "pug/video.pug", {video: {videoId: id}, error: true, message, settings})
}
}
}
R html/static/images/arrow-down-wide.svg => html/static/images/arrow-down-wide-dark.svg +0 -0
A html/static/images/arrow-down-wide-light.svg => html/static/images/arrow-down-wide-light.svg +1 -0
@@ 0,0 1,1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="22" height="8" viewBox="0 0 5.821 2.117"><path d="M1.269.53l.767.793h.161L2.964.53h.211v.265L2.117 1.852 1.058.794V.529z" fill="#202020" paint-order="markers stroke fill"/></svg>
M html/static/images/settings.svg => html/static/images/settings.svg +1 -1
@@ 1,1 1,1 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="25" height="25" viewBox="0 0 6.615 6.615"><path d="M2.64 0s-.03.94-.424 1.215C1.823 1.49.885.894.885.894L.112 2.232.109 2.23l.003.002c.01.006.797.499.838.974.042.478-.944.992-.944.992l.77 1.34s.83-.442 1.265-.24c.435.204.387 1.314.387 1.314l1.546.003s.032-.94.425-1.215c.393-.275 1.331.321 1.331.321l.775-1.338s-.798-.496-.84-.974c-.041-.478.944-.993.944-.993l-.77-1.34s-.83.443-1.265.24C4.14 1.113 4.187.002 4.187.002zm.688 2.25a1.106 1.106 0 110 2.211 1.106 1.106 0 010-2.21z" fill="#c4c4c4" paint-order="fill markers stroke"/></svg>>
\ No newline at end of file
+<svg fill="currentColor" xmlns="http://www.w3.org/2000/svg" width="25" height="25" viewBox="0 0 6.615 6.615"><title>Settings</title><path d="M2.64 0s-.03.94-.424 1.215C1.823 1.49.885.894.885.894L.112 2.232.109 2.23l.003.002c.01.006.797.499.838.974.042.478-.944.992-.944.992l.77 1.34s.83-.442 1.265-.24c.435.204.387 1.314.387 1.314l1.546.003s.032-.94.425-1.215c.393-.275 1.331.321 1.331.321l.775-1.338s-.798-.496-.84-.974c-.041-.478.944-.993.944-.993l-.77-1.34s-.83.443-1.265.24C4.14 1.113 4.187.002 4.187.002zm.688 2.25a1.106 1.106 0 110 2.211 1.106 1.106 0 010-2.21z" paint-order="fill markers stroke"/></svg>
M html/static/images/subscriptions.svg => html/static/images/subscriptions.svg +1 -1
@@ 1,1 1,1 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="30" height="25" viewBox="0 0 7.937 6.615"><path d="M2.373 0h3.191a.52.52 0 01.521.521H1.852A.52.52 0 012.373 0zm-.91.794h5.011c.371 0 .67.298.67.67v.123l-6.35-.016v-.108c0-.37.298-.67.67-.67zm-.405 1.058C.472 1.852 0 2.184 0 2.77v2.868c0 .586.472.977 1.058.977H6.88c.586 0 1.059-.39 1.059-.977V2.77c0-.586-.473-.918-1.059-.918zM5.3 4.017c.167.099.19.276.012.366l-2.098.985c-.131.077-.304-.002-.302-.19V3.29c0-.203.18-.333.34-.245z" fill="#c4c4c4" paint-order="fill markers stroke"/></svg>>
\ No newline at end of file
+<svg fill="currentColor" xmlns="http://www.w3.org/2000/svg" width="30" height="25" viewBox="0 0 7.937 6.615"><title>Subscriptions</title><path d="M2.373 0h3.191a.52.52 0 01.521.521H1.852A.52.52 0 012.373 0zm-.91.794h5.011c.371 0 .67.298.67.67v.123l-6.35-.016v-.108c0-.37.298-.67.67-.67zm-.405 1.058C.472 1.852 0 2.184 0 2.77v2.868c0 .586.472.977 1.058.977H6.88c.586 0 1.059-.39 1.059-.977V2.77c0-.586-.473-.918-1.059-.918zM5.3 4.017c.167.099.19.276.012.366l-2.098.985c-.131.077-.304-.002-.302-.19V3.29c0-.203.18-.333.34-.245z" paint-order="fill markers stroke"/></svg>
M pug/includes/layout.pug => pug/includes/layout.pug +4 -7
@@ 3,7 3,8 @@ html
head
meta(charset="utf-8")
meta(name="viewport" content="width=device-width, initial-scale=1")
- link(rel="stylesheet" type="text/css" href=getStaticURL("sass", "/main.sass"))
+ - const theme = settings && ["dark", "light", "edgeless-light"][settings.theme] || "dark"
+ link(rel="stylesheet" type="text/css" href=getStaticURL("sass", `/${theme}.sass`))
script(type="module" src=getStaticURL("html", "/static/js/focus.js"))
block head
@@ 15,13 16,9 @@ html
.links
a(href="/").link.home CloudTube
a(href="/subscriptions" title="Subscriptions").link.icon-link
- svg(width=30 height=25)
- image(href=getStaticURL("html", "/static/images/subscriptions.svg") alt="Subscriptions.").icon
- title Subscriptions
+ != icons.get("subscriptions")
a(href="/settings" title="Settings").link.icon-link
- svg(width=25 height=25)
- image(href=getStaticURL("html", "/static/images/settings.svg") alt="Settings.").icon
- title Settings
+ != icons.get("settings")
form(method="get" action="/search").search-form
input(type="text" placeholder="Search" aria-label="Search a video" name="q" autocomplete="off" value=query).search
M pug/settings.pug => pug/settings.pug +10 -0
@@ 36,6 36,16 @@ block content
form(method="post" action="/settings")
+fieldset("Settings")
+ +select({
+ id: "theme",
+ label: "Theme",
+ options: [
+ {value: "0", text: "Standard dark"},
+ {value: "1", text: "Standard light"},
+ {value: "2", text: "Edgeless light"}
+ ]
+ })
+
+input({
id: "instance",
label: "Instance",
M pug/subscriptions.pug => pug/subscriptions.pug +2 -2
@@ 31,8 31,8 @@ block content
if settings.save_history
input(type="checkbox" id="watched-videos-display")
- .watched-videos-display-container
- label(for="watched-videos-display").watched-videos-display-label Hide watched videos
+ .checkbox-hider__container
+ label(for="watched-videos-display").checkbox-hider__label Hide watched videos
each video in videos
+video_list_item("subscriptions-video", video, instanceOrigin, {showMarkWatched: settings.save_history && !video.watched})
M pug/video.pug => pug/video.pug +1 -0
@@ 21,6 21,7 @@ block content
noscript
meta(http-equiv="refresh" content=`${video.lengthSeconds+5};url=/watch?v=${first.videoId}&continuous=1&session-watched=${sessionWatchedNext}`)
.video-page(class={
+ "video-page--recommended-side": settings.recommended_mode === 0,
"video-page--recommended-below": settings.recommended_mode === 1,
"video-page--recommended-hidden": settings.recommended_mode === 2
})
A sass/dark.sass => sass/dark.sass +9 -0
@@ 0,0 1,9 @@
+@use "themes/dark" as *
+@use "includes/main" with ($_theme: $theme)
+
+@use "theme-modules/edgeless" with ($_theme: $theme)
+
+// navigation shadow
+.main-nav
+ position: relative // needed for box shadow to overlap related videos section
+ box-shadow: 0px 0px 20px 5px rgba(0, 0, 0, 0.1)
A sass/edgeless-light.sass => sass/edgeless-light.sass +4 -0
@@ 0,0 1,4 @@
+@use "themes/edgeless-light" as *
+@use "includes/main" with ($_theme: $theme)
+
+@use "theme-modules/edgeless" with ($_theme: $theme)
R sass/includes/base.sass => sass/includes/_base.sass +19 -14
@@ 1,8 1,10 @@
-@use "colors.sass" as c
+$_theme: () !default
+
+@use "sass:map"
body
- background-color: c.$bg-dark
- color: c.$fg-main
+ background-color: map.get($_theme, "bg-2")
+ color: map.get($_theme, "fg-main")
font-family: "Bariol", sans-serif
font-size: 18px
margin: 0
@@ 13,13 15,13 @@ body
flex-direction: column
a
- color: c.$link
+ color: map.get($_theme, "link")
pre, code
font-size: 0.88em
code
- background: c.$bg-darker
+ background: map.get($_theme, "bg-1")
padding: 3px 5px
border-radius: 4px
@@ 32,7 34,7 @@ button
cursor: pointer
::placeholder
- color: #c4c4c4
+ color: map.get($_theme, "placeholder")
opacity: 1
// focus section
@@ 48,19 50,20 @@ button
select:-moz-focusring
color: transparent
- text-shadow: 0 0 0 c.$fg-bright
+ text-shadow: 0 0 0 map.get($_theme, "fg-bright")
body.show-focus
a, select, button, input, video, summary
&:focus
- outline: 2px dotted #ddd
+ outline: 2px dotted map.get($_theme, "fg-main")
video
background-color: black
details
- background-color: c.$bg-accent-x
+ background-color: map.get($_theme, "bg-3")
padding: 12px
+ border: 1px solid map.get($_theme, "edge-grey")
border-radius: 8px
summary
@@ 68,20 71,22 @@ details
line-height: 1
margin-bottom: 0
user-select: none
- color: c.$fg-main
+ color: map.get($_theme, "fg-main")
&[open] summary
- margin-bottom: 16px
+ padding-bottom: 12px
+ border-bottom: 1px solid map.get($_theme, "edge-grey")
+ margin-bottom: 8px
table
- background-color: c.$bg-darker
+ background-color: map.get($_theme, "bg-1")
table, td, th
- border: 1px solid c.$edge-grey
+ border: 1px solid map.get($_theme, "edge-grey")
border-collapse: collapse
td, th
padding: 4px 8px
thead, tr:nth-child(even)
- background-color: c.$bg-darkest
+ background-color: map.get($_theme, "bg-0")
R sass/includes/buttons.sass => sass/includes/_buttons.sass +14 -12
@@ 1,10 1,12 @@
+$_theme: () !default
+
@use "sass:selector"
-@use "colors.sass" as c
+@use "sass:map"
@mixin button-base
-webkit-appearance: none
-moz-appearance: none
- color: c.$fg-bright
+ color: map.get($_theme, "fg-bright")
border: none
border-radius: 4px
padding: 8px
@@ 14,7 16,7 @@
@at-root #{selector.unify(&, "select")}
padding: 8px 27px 8px 8px
- background: url(/static/images/arrow-down-wide.svg) right 53% no-repeat c.$bg-accent-x
+ background: map.get($_theme, "image-dropdown") right 53% no-repeat map.get($_theme, "bg-4")
@at-root #{selector.unify(&, "a")}
padding: 7px 8px
@@ 31,12 33,12 @@
@mixin button-bg
@include button-base
- background-color: c.$bg-accent-x
+ background-color: map.get($_theme, "bg-4")
@mixin border-button
@include button-bg
- border: 1px solid c.$edge-grey
+ border: 1px solid map.get($_theme, "edge-grey")
@mixin button-size
margin: 4px
@@ 44,10 46,10 @@
@mixin button-hover
&:hover
- background-color: c.$bg-accent
+ background-color: map.get($_theme, "bg-3")
&:active
- background-color: c.$bg-dark
+ background-color: map.get($_theme, "bg-2")
.base-border-look
@include border-button
@@ 62,13 64,13 @@
@include button-size
-webkit-appearance: none
-moz-appearance: none
- color: c.$fg-bright
+ color: map.get($_theme, "fg-bright")
text-decoration: none
line-height: 1.25
margin: 0
padding: 8px 20px
- background: c.$bg-accent
- border: solid c.$bg-darker
+ background: map.get($_theme, "bg-3")
+ border: solid map.get($_theme, "edge-grey")
border-width: 1px 0px 0px
text-align: left
@@ 76,7 78,7 @@
border-width: 1px 0px 1px
&:hover
- background: c.$bg-accent-x
+ background: map.get($_theme, "bg-4")
&:active
- background: c.$bg-darker
+ background: map.get($_theme, "bg-1")
R sass/includes/cant-think-page.sass => sass/includes/_cant-think-page.sass +6 -4
@@ 1,5 1,7 @@
+$_theme: () !default
+
@use "sass:list"
-@use "colors.sass" as c
+@use "sass:map"
.cant-think-page
.main-nav
@@ 13,7 15,7 @@
box-sizing: border-box
.page-narration
- background-color: c.$bg-accent
+ background-color: map.get($_theme, "bg-3")
border: 1px solid #aaa
color: #fff
border-radius: 0
@@ 28,7 30,7 @@
.leave
margin: 26px 32px !important
- color: #aaa
+ color: map.get($_theme, "fg-dim")
$sizes: 14px 16px 21px 30px 72px
@each $size in $sizes
@@ 37,7 39,7 @@
&.leave__final
font-weight: bold
- color: #f2f2f2
+ color: map.get($_theme, "fg-bright")
text-align: center
.leave__actions
R sass/includes/channel-page.sass => sass/includes/_channel-page.sass +10 -7
@@ 1,6 1,8 @@
-@use "colors.sass" as c
-@use "video-list-item.sass" as *
-@use "_dimensions.sass" as dimensions
+$_theme: () !default
+
+@use "sass:map"
+@use "_dimensions" as dimensions
+@use "video-list-item" as *
.channel-page
padding: 40px 20px 20px
@@ 17,7 19,7 @@
align-self: flex-start
.channel-data
- background-color: c.$bg-darker
+ background-color: map.get($_theme, "bg-1")
padding: 24px
margin: 12px 0px 24px
border-radius: 8px
@@ 44,11 46,11 @@
.name
font-size: 30px
font-weight: normal
- color: c.$fg-bright
+ color: map.get($_theme, "fg-bright")
margin: 0
.subscribers
- color: c.$fg-main
+ color: map.get($_theme, "fg-main")
font-size: 18px
.subscribe-form
@@ 61,7 63,8 @@
line-height: 1
border-radius: 8px
font-size: 22px
- background-color: c.$power-deep
+ background-color: map.get($_theme, "power-deep")
+ color: map.get($_theme, "power-fg")
border: none
.description
R sass/includes/filters-page.sass => sass/includes/_filters-page.sass +11 -9
@@ 1,10 1,12 @@
-@use "colors.sass" as c
+$_theme: () !default
+
+@use "sass:map"
@mixin filter-notice
margin-top: 24px
padding: 12px
border-radius: 8px
- background-color: c.$bg-darker
+ background-color: map.get($_theme, "bg-1")
white-space: pre-line
.filters-page
@@ 20,23 22,23 @@
.filter-confirmation-notice
@include filter-notice
- color: c.$fg-warning
+ color: map.get($_theme, "fg-warning")
.filter-compile-error
@include filter-notice
&__header
- color: c.$fg-warning
+ color: map.get($_theme, "fg-warning")
&__trace
- background-color: c.$bg-darkest
+ background-color: map.get($_theme, "bg-0")
padding: 6px
.save-filter
margin-top: 12px
.border-look
- background-color: c.$bg-darker
+ background-color: map.get($_theme, "bg-1")
font-size: 22px
padding: 7px 16px 8px
font-size: 24px
@@ 48,17 50,17 @@
.filter
display: flex
padding: 5px 0
- border-top: 1px solid c.$edge-grey
+ border-top: 1px solid map.get($_theme, "edge-grey")
&:last-child
- border-bottom: 1px solid c.$edge-grey
+ border-bottom: 1px solid map.get($_theme, "edge-grey")
&__details
flex: 1
&__type
font-size: 15px
- color: c.$fg-dim
+ color: map.get($_theme, "fg-dim")
&__remove
flex-shrink: 0
R => +5 -2
@@ 1,4 1,6 @@
@use "./colors.sass" as c
$_theme: () !default
@use "sass:map"
.footer__container
flex: 1
@@ 10,9 12,10 @@
display: flex
flex-direction: column
align-items: center
background-color: c.$bg-darkest
background-color: map.get($_theme, "bg-dim")
margin: 40px 0 0
padding: 10px 10px 30px
border-top: 1px solid map.get($_theme, "edge-grey")
.footer__cols
display: flex
R sass/includes/forms.sass => sass/includes/_forms.sass +22 -19
@@ 1,7 1,9 @@
-@use "colors.sass" as c
+$_theme: () !default
+
+@use "sass:map"
@mixin disabled
- background-color: c.$bg-dark
+ background-color: map.get($_theme, "bg-2")
color: #808080
fieldset
@@ 20,7 22,7 @@ fieldset
font-size: 28px
font-weight: bold
padding: 0
- border-bottom: 1px solid #333
+ border-bottom: 1px solid map.get($_theme, "edge-grey") // TODO: originally contrasted more
line-height: 1.56
@media screen and (max-width: 400px)
@@ 36,7 38,7 @@ fieldset
position: relative
padding-bottom: 5px
margin-bottom: 5px
- border-bottom: 1px solid #999
+ border-bottom: 1px solid map.get($_theme, "edge-grey")
@media screen and (max-width: 400px)
flex-direction: column
@@ 52,7 54,7 @@ fieldset
&__label
grid-area: label
padding: 8px 8px 8px 0px
- color: #fff
+ color: map.get($_theme, "fg-main")
&__input
grid-area: input
@@ 63,7 65,7 @@ fieldset
white-space: pre-line
margin: 12px 0px 18px
font-size: 16px
- color: #ccc
+ color: map.get($_theme, "fg-dim")
line-height: 1.2
//
@@ 79,7 81,7 @@ fieldset
width: 16px
height: 16px
padding: 0px
- border: 1px solid #666
+ border: 1px solid map.get($_theme, "edge-grey")
border-radius: 3px
margin-left: 8px
position: relative
@@ 110,18 112,19 @@ fieldset
height: 42px
margin: 0
- .#{$base}-container
- position: relative
- display: grid // why does the default not work???
- top: -42px
- background: c.$bg-accent-x
- line-height: 1
- border-radius: 8px
- margin-bottom: -18px
-
- .#{$base}-label
- padding: 12px 0px 12px 32px
- cursor: pointer
+.checkbox-hider__container
+ position: relative
+ display: grid // why does the default not work???
+ top: -42px
+ background: map.get($_theme, "bg-3")
+ line-height: 1
+ border: 1px solid map.get($_theme, "edge-grey")
+ border-radius: 8px
+ margin-bottom: -18px
+
+ .checkbox-hider__label
+ padding: 12px 0px 12px 32px
+ cursor: pointer
@mixin single-button-form
display: inline-block
R sass/includes/home-page.sass => sass/includes/_home-page.sass +5 -3
@@ 1,4 1,6 @@
-@use "colors.sass" as c
+$_theme: () !default
+
+@use "sass:map"
.home-page
padding: 40px
@@ 18,8 20,8 @@
padding: 16px
border-radius: 4px
font-size: 20px
- background-color: c.$bg-darker
- color: c.$fg-main
+ background-color: map.get($_theme, "bg-1")
+ color: map.get($_theme, "fg-main")
p
margin: 0 32px
R sass/includes/licenses-page.sass => sass/includes/_licenses-page.sass +2 -0
@@ 1,3 1,5 @@
+$_theme: () !default
+
.js-licenses-page
max-width: 800px
margin: 0 auto
A sass/includes/_main.sass => sass/includes/_main.sass +32 -0
@@ 0,0 1,32 @@
+$_theme: () !default
+
+@use "sass:selector"
+
+// preload second-level includes with the theme (there will be conflicts due to reconfiguration they are loaded individually)
+// this isn't _exactly_ what @forward is supposed to be used for, but it's the best option here
+@forward "video-list-item" show _ with ($_theme: $_theme)
+@forward "forms" show _ with ($_theme: $_theme)
+@forward "buttons" show _ with ($_theme: $_theme)
+
+@use "base" with ($_theme: $_theme)
+@use "video-page" with ($_theme: $_theme)
+@use "search-page" with ($_theme: $_theme)
+@use "home-page" with ($_theme: $_theme)
+@use "channel-page" with ($_theme: $_theme)
+@use "subscriptions-page" with ($_theme: $_theme)
+@use "settings-page" with ($_theme: $_theme)
+@use "cant-think-page" with ($_theme: $_theme)
+@use "privacy-page" with ($_theme: $_theme)
+@use "licenses-page" with ($_theme: $_theme)
+@use "filters-page" with ($_theme: $_theme)
+@use "takedown-page" with ($_theme: $_theme)
+@use "nav" with ($_theme: $_theme)
+@use "footer" with ($_theme: $_theme)
+
+@font-face
+ font-family: "Bariol"
+ src: url(/static/fonts/bariol.woff?statichash=1)
+
+.button-container
+ display: flex
+ flex-wrap: wrap
R sass/includes/nav.sass => sass/includes/_nav.sass +17 -10
@@ 1,12 1,14 @@
-@use "colors.sass" as c
-@use "buttons.sass" as *
-@use "_dimensions.sass" as dimensions
+$_theme: () !default
+
+@use "sass:map"
+@use "buttons" as *
+@use "_dimensions" as dimensions
.main-nav
- background-color: c.$bg-accent
+ background-color: map.get($_theme, "bg-nav")
display: flex
padding: 8px
- box-shadow: 0px 0px 20px 5px rgba(0, 0, 0, 0.1)
+ border-bottom: 1px solid map.get($_theme, "edge-grey")
+dimensions.thin
display: block
@@ 30,10 32,16 @@
font-weight: bold
&, &:visited
- color: #fff
+ color: map.get($_theme, "fg-bright")
&:focus, &:hover
- background-color: c.$bg-accent-x
+ background-color: map.get($_theme, "bg-4")
+
+ &.icon-link
+ color: map.get($_theme, "fg-dim")
+
+ &:hover, &:focus
+ color: map.get($_theme, "fg-bright")
.search-form
display: flex
@@ 44,8 52,7 @@
@include button-bg
padding: 10px
flex: 1
- margin: 1px
+ border: 1px solid map.get($_theme, "bg-nav")
&:hover, &:focus
- border: 1px solid c.$edge-grey
- margin: 0px
+ border-color: map.get($_theme, "edge-grey")
R sass/includes/privacy-page.sass => sass/includes/_privacy-page.sass +2 -0
@@ 1,3 1,5 @@
+$_theme: () !default
+
.privacy-page
max-width: 600px
margin: 0 auto
R sass/includes/search-page.sass => sass/includes/_search-page.sass +4 -2
@@ 1,5 1,7 @@
-@use "video-list-item.sass" as *
-@use "colors.sass" as c
+$_theme: () !default
+
+@use "sass:map"
+@use "video-list-item" as *
.search-page
padding: 40px 20px 20px
R sass/includes/settings-page.sass => sass/includes/_settings-page.sass +7 -4
@@ 1,5 1,7 @@
-@use "forms.sass" as forms
-@use "colors.sass" as c
+$_theme: () !default
+
+@use "sass:map"
+@use "forms" as forms
.settings-page
padding: 40px 20px 20px
@@ 19,8 21,9 @@
.more-settings
margin-top: 24px
padding: 12px
+ border: 1px solid map.get($_theme, "edge-grey")
border-radius: 8px
- background-color: c.$bg-accent-x
+ background-color: map.get($_theme, "bg-3")
&__list
margin: 0
@@ 34,7 37,7 @@
margin-top: 24px
.delete-confirm-container
- background: c.$bg-darker
+ background: map.get($_theme, "bg-1")
margin-bottom: -36px
@include forms.checkbox-hider("delete-confirm")
R sass/includes/subscriptions-page.sass => sass/includes/_subscriptions-page.sass +6 -4
@@ 1,6 1,8 @@
-@use "colors.sass" as c
-@use "video-list-item.sass" as *
-@use "forms.sass" as forms
+$_theme: () !default
+
+@use "sass:map"
+@use "forms" as forms
+@use "video-list-item" as *
.subscriptions-page
padding: 40px 20px 20px
@@ 33,7 35,7 @@
.name
font-size: 22px
- color: c.$fg-main
+ color: map.get($_theme, "fg-main")
@include forms.checkbox-hider("watched-videos-display")
R sass/includes/takedown-page.sass => sass/includes/_takedown-page.sass +6 -4
@@ 1,4 1,6 @@
-@use "colors.sass" as c
+$_theme: () !default
+
+@use "sass:map"
.takedown-page
max-width: 700px
@@ 9,6 11,6 @@
.important-section
padding: 4px 20px
- border: 1px solid c.$edge-grey
- color: c.$fg-bright
- background-color: c.$bg-darker
+ border: 1px solid map.get($_theme, "edge-grey")
+ color: map.get($_theme, "fg-bright")
+ background-color: map.get($_theme, "bg-1")
R sass/includes/video-list-item.sass => sass/includes/_video-list-item.sass +18 -15
@@ 1,5 1,7 @@
-@use "colors.sass" as c
-@use "_dimensions.sass" as dimensions
+$_theme: () !default
+
+@use "sass:map"
+@use "_dimensions" as dimensions
// navigator hacks
.thumbnail > .thumbnail__options-container
@@ 30,6 32,7 @@
&__show-more
display: block
height: $more-size
+ color: #fff
line-height: 16px
font-size: 25px
text-align: center
@@ 52,7 55,7 @@
&__options-list
pointer-events: auto
display: grid
- background-color: c.$bg-accent
+ background-color: map.get($_theme, "bg-3")
padding: 8px 0px
border-radius: 8px
box-shadow: 0 2px 6px 2px #000
@@ 67,7 70,7 @@
right: 0
transform: translate(-6px, -1px) rotate(-45deg)
clip-path: polygon(-5% -20%, 120% -20%, 120% 125%)
- background-color: c.$bg-accent
+ background-color: map.get($_theme, "bg-3")
box-shadow: 0px 0px 4px 0px #000
pointer-events: none
@@ 80,7 83,7 @@
margin-bottom: 12px
@at-root .video-list-item--watched#{&}
- background: c.$bg-darker
+ background: map.get($_theme, "bg-dim")
padding: 4px 4px 0px
margin: -4px -4px 8px
@@ 93,7 96,7 @@
.thumbnail
position: relative
display: flex
- background: c.$bg-darkest
+ background: map.get($_theme, "bg-0")
&__link
font-size: 0 // remove whitespace around the image
@@ 106,7 109,7 @@
position: absolute
bottom: 3px
right: 3px
- color: c.$fg-bright
+ color: #fff
font-size: 14px
background: rgba(20, 20, 20, 0.85)
line-height: 1
@@ 119,20 122,20 @@
line-height: 1.2
.title-link
- color: c.$fg-main
+ color: map.get($_theme, "fg-main")
text-decoration: none
.author-line
margin-top: 4px
font-size: 15px
- color: c.$fg-dim
+ color: map.get($_theme, "fg-dim")
.author
- color: c.$fg-dim
+ color: map.get($_theme, "fg-dim")
text-decoration: none
&:hover, &:active
- color: c.$fg-bright
+ color: map.get($_theme, "fg-bright")
text-decoration: underline
@mixin recommendation-item
@@ 176,15 179,15 @@
.author-line
font-size: 15px
- color: c.$fg-main
+ color: map.get($_theme, "fg-main")
.author
- color: c.$fg-main
+ color: map.get($_theme, "fg-main")
.description
margin-top: 16px
font-size: 15px
- color: c.$fg-dim
+ color: map.get($_theme, "fg-dim")
+dimensions.thin
.description
@@ 195,7 198,7 @@
.description b
font-weight: normal
- color: c.$fg-main
+ color: map.get($_theme, "fg-main")
@mixin channel-video
@include large-item
R sass/includes/video-page.sass => sass/includes/_video-page.sass +30 -15
@@ 1,11 1,11 @@
-@use "colors.sass" as c
-@use "video-list-item.sass" as *
+$_theme: () !default
+
+@use "sass:map"
+@use "video-list-item" as *
.video-page
display: grid
grid-auto-flow: row
- padding: 20px
- grid-gap: 16px
@media screen and (min-width: 1000px)
grid-template-columns: 1fr 400px
@@ 13,10 13,25 @@
&--recommended-below, &--recommended-hidden
grid-template-columns: none
- &--recommended-hidden .related-videos
- display: none
+ &--recommended-side
+ .related-videos
+ border-left: 1px solid map.get($_theme, "edge-grey")
+ padding-left: 12px
+ padding-right: 20px
+ background-color: map.get($_theme, "bg-4")
+ padding-top: 12px
+
+ &--recommended-below
+ .related-videos
+ padding: 20px
+
+ &--recommended-hidden
+ .related-videos
+ display: none
.main-video-section
+ padding: 20px
+
.video-container
text-align: center
@@ 27,7 42,7 @@
max-height: 80vh
.stream-notice
- background: c.$bg-darkest
+ background: map.get($_theme, "bg-0")
padding: 4px
.info
@@ 45,15 60,15 @@
margin: 0px 0px 4px
font-size: 30px
font-weight: normal
- color: c.$fg-bright
+ color: map.get($_theme, "fg-bright")
word-break: break-word
.author-link
- color: c.$fg-main
+ color: map.get($_theme, "fg-main")
text-decoration: none
&:hover, &:active
- color: c.$fg-bright
+ color: map.get($_theme, "fg-bright")
text-decoration: underline
.info-secondary
@@ 75,7 90,7 @@
margin: 16px 4px
padding: 12px
border-radius: 4px
- background-color: c.$bg-darkest
+ background-color: map.get($_theme, "bg-0")
&__description
margin-left: 12px
@@ 85,7 100,7 @@
&__script-warning
font-size: 15px
- color: c.$fg-warning
+ color: map.get($_theme, "fg-warning")
&__buttons
display: flex
@@ 97,12 112,12 @@
line-height: 1.4
word-break: break-word
margin: 16px 4px 4px 4px
- background-color: c.$bg-accent-area
+ background-color: map.get($_theme, "bg-5")
padding: 12px
border-radius: 4px
- --regular-background: #{c.$bg-accent-area}
- --highlight-background: #{c.$bg-darker}
+ --regular-background: #{map.get($_theme, "bg-5")}
+ --highlight-background: #{map.get($_theme, "bg-1")}
.subscribe-form
display: inline-block
D sass/includes/colors.sass => sass/includes/colors.sass +0 -17
@@ 1,17 0,0 @@
-$bg-darkest: #202123
-$bg-darker: #303336
-$bg-dark: #36393f
-$bg-accent: #4f5359
-$bg-accent-x: #3f4247
-$bg-accent-area: #44474b
-
-$fg-bright: #fff
-$fg-main: #ddd
-$fg-dim: #bbb
-$fg-warning: #fdca6d
-
-$edge-grey: #a0a0a0
-
-$link: #8ac2f9
-
-$power-deep: #c62727
A sass/light.sass => sass/light.sass +2 -0
@@ 0,0 1,2 @@
+@use "themes/light" as *
+@use "includes/main" with ($_theme: $theme)
D sass/main.sass => sass/main.sass +0 -30
@@ 1,30 0,0 @@
-@use "includes/colors.sass" as c
-
-@use "includes/base.sass"
-@use "sass:selector"
-@use "includes/video-page.sass"
-@use "includes/search-page.sass"
-@use "includes/home-page.sass"
-@use "includes/channel-page.sass"
-@use "includes/subscriptions-page.sass"
-@use "includes/settings-page.sass"
-@use "includes/cant-think-page.sass"
-@use "includes/privacy-page.sass"
-@use "includes/licenses-page.sass"
-@use "includes/filters-page.sass"
-@use "includes/takedown-page.sass"
-@use "includes/forms.sass"
-@use "includes/nav.sass"
-@use "includes/footer.sass"
-
-@font-face
- font-family: "Bariol"
- src: url(/static/fonts/bariol.woff?statichash=1)
-
-.icon-link:hover, .icon-link:focus
- .icon
- filter: brightness(2)
-
-.button-container
- display: flex
- flex-wrap: wrap
A sass/theme-modules/_edgeless.sass => sass/theme-modules/_edgeless.sass +27 -0
@@ 0,0 1,27 @@
+$_theme: () !default
+
+@use "sass:map"
+
+// remove separating edges
+.main-nav, .footer__center, .video-page--recommended-side .related-videos
+ border: none
+
+// no background change to recommended videos sidebar
+.video-page--recommended-side .related-videos
+ background-color: map.get($_theme, "bg-2")
+
+// navigation shadow
+.main-nav
+ position: relative // needed for box shadow to overlap related videos section
+ box-shadow: 0px 0px 20px 5px rgba(0, 0, 0, 0.1)
+
+// thumbnail dropdown menu dividers
+.menu-look
+ border-color: map.get($_theme, "bg-0")
+
+// details areas
+details, .checkbox-hider__container, .more-settings
+ border: none
+details[open] summary
+ border: none
+ margin-bottom: 4px
A sass/themes/_dark.scss => sass/themes/_dark.scss +38 -0
@@ 0,0 1,38 @@
+// Defined in scss file instead of sass because indented syntax does not have multiline maps
+// https://github.com/sass/sass/issues/216
+
+@use "sass:map";
+
+// This section is for colour shades
+$theme: (
+ // darker
+ "bg-0": #252628,
+ "bg-1": #303336,
+ // regular
+ "bg-2": #36393f,
+ // lighter
+ "bg-3": #3f4247, // slightly
+ "bg-4": #44474b, // noticably
+ "bg-5": #4f5359, // brightly
+
+ "fg-bright": #fff,
+ "fg-main": #ddd,
+ "fg-dim": #bbb,
+ "fg-warning": #fdca6d,
+
+ "edge-grey": #a0a0a0,
+ "placeholder": #c4c4c4,
+
+ "link": #8ac2f9,
+
+ "power-deep": #c62727,
+ "power-fg": "#fff",
+
+ "image-dropdown": url(/static/images/arrow-down-wide-dark.svg)
+);
+
+// This section is for colour meanings
+$theme: map.merge($theme, (
+ "bg-dim": map.get($theme, "bg-0"),
+ "bg-nav": map.get($theme, "bg-5"),
+));
A sass/themes/_edgeless-light.scss => sass/themes/_edgeless-light.scss +8 -0
@@ 0,0 1,8 @@
+// extend regular light theme to change a couple of shades
+@use "light";
+@use "sass:map";
+
+// this section is for colour meanings
+$theme: map.merge(light.$theme, (
+ "edge-grey": #c0c0c0,
+));
A sass/themes/_light.scss => sass/themes/_light.scss +38 -0
@@ 0,0 1,38 @@
+// Defined in scss file instead of sass because indented syntax does not have multiline maps
+// https://github.com/sass/sass/issues/216
+
+@use "sass:map";
+
+// this section is for colour shades
+$theme: (
+ // lighter
+ "bg-0": #fff,
+ "bg-1": #fff,
+ // regular
+ "bg-2": #f2f2f2,
+ // darker
+ "bg-3": #e8e8e8, // slightly
+ "bg-4": #dadada, // noticably
+ "bg-5": #d0d0d0, // brightly
+
+ "fg-bright": #000,
+ "fg-main": #202020,
+ "fg-dim": #454545,
+ "fg-warning": #ce8600,
+
+ "edge-grey": #909090,
+ "placeholder": #636363,
+
+ "link": #0b51d4,
+
+ "power-deep": #c62727,
+ "power-fg": #fff,
+
+ "image-dropdown": url(/static/images/arrow-down-wide-light.svg)
+);
+
+// this section is for colour meanings
+$theme: map.merge($theme, (
+ "bg-dim": map.get($theme, "bg-4"),
+ "bg-nav": map.get($theme, "bg-0")
+));
M server.js => server.js +7 -5
@@ 1,9 1,11 @@
const {Pinski} = require("pinski")
const {setInstance} = require("pinski/plugins")
const constants = require("./utils/constants")
+const iconLoader = require("./utils/icon-loader").icons
;(async () => {
await require("./utils/upgradedb")()
+ const icons = await iconLoader
const server = new Pinski({
port: 10412,
@@ 13,19 15,19 @@ const constants = require("./utils/constants")
setInstance(server)
server.pugDefaultLocals.constants = constants
+ server.pugDefaultLocals.icons = icons
server.muteLogsStartingWith("/vi/")
server.muteLogsStartingWith("/favicon")
server.muteLogsStartingWith("/static")
- server.addSassDir("sass", ["sass/includes"])
- server.addRoute("/static/css/main.css", "sass/main.sass", "sass")
+ server.addSassDir("sass", ["sass/includes", "sass/themes", "sass/theme-modules"])
+ server.addRoute("/static/css/dark.css", "sass/dark.sass", "sass")
+ server.addRoute("/static/css/light.css", "sass/light.sass", "sass")
+ server.addRoute("/static/css/edgeless-light.css", "sass/edgeless-light.sass", "sass")
server.addPugDir("pug", ["pug/includes"])
server.addPugDir("pug/errors")
- server.addRoute("/cant-think", "pug/cant-think.pug", "pug")
- server.addRoute("/privacy", "pug/privacy.pug", "pug")
- server.addRoute("/licenses", "pug/licenses.pug", "pug")
server.addStaticHashTableDir("html/static/js")
server.addStaticHashTableDir("html/static/js/elemjs")
M utils/constants.js => utils/constants.js +4 -0
@@ 9,6 9,10 @@ let constants = {
type: "string",
default: "http://localhost:3000"
},
+ theme: {
+ type: "integer",
+ default: 0
+ },
save_history: {
type: "boolean",
default: false
A utils/icon-loader.js => utils/icon-loader.js +8 -0
@@ 0,0 1,8 @@
+const fs = require("fs").promises
+
+const names = ["subscriptions", "settings"]
+const icons = names.map(name => fs.readFile(`html/static/images/${name}.svg`, "utf8"))
+
+module.exports.icons = Promise.all(icons).then(resolvedIcons => {
+ return new Map(names.map((name, index) => [name, resolvedIcons[index]]))
+})
M utils/upgradedb.js => utils/upgradedb.js +6 -1
@@ 70,6 70,11 @@ const deltas = [
.run()
db.prepare("CREATE TABLE TakedownChannels (ucid TEXT NOT NULL, org TEXT, url TEXT, PRIMARY KEY (ucid))")
.run()
+ },
+ // 11: Settings +theme
+ function() {
+ db.prepare("ALTER TABLE Settings ADD COLUMN theme INTEGER DEFAULT 0")
+ .run()
}
]
@@ 82,7 87,7 @@ async function createBackup(entry) {
/**
* @param {number} entry
- * @param {boolean} log
+ * @param {boolean} [log]
*/
function runDelta(entry, log) {
process.stdout.write(`Upgrading database to version ${entry}... `)