diff options
34 files changed, 1504 insertions, 319 deletions
diff --git a/build_static.php b/build_static.php new file mode 100755 index 0000000..a9ec027 --- /dev/null +++ b/build_static.php @@ -0,0 +1,96 @@ +#!/usr/bin/env php8.1 +<?php + +function gethash(string $path): string { + return substr(sha1(file_get_contents($path)), 0, 8); +} + +function sassc(string $src_file, string $dst_file): int { + $cmd = 'sassc -t compressed '.escapeshellarg($src_file).' '.escapeshellarg($dst_file); + exec($cmd, $output, $code); + return $code; +} + +function clean_css(string $file) { + $output = $file.'.out'; + if (file_exists($output)) + unlink($output); + + $cmd = ROOT.'/node_modules/clean-css-cli/bin/cleancss -O2 "all:on;mergeSemantically:on;restructureRules:on" '.escapeshellarg($file).' > '.escapeshellarg($output); + system($cmd); + + if (file_exists($output)) { + unlink($file); + rename($output, $file); + } else { + fwrite(STDERR, "error: could not cleancss $file\n"); + } +} + +function dark_diff(string $light_file, string $dark_file): void { + $temp_output = $dark_file.'.diff'; + $cmd = ROOT.'/dark-theme-diff.js '.escapeshellarg($light_file).' '.$dark_file.' > '.$temp_output; + exec($cmd, $output, $code); + if ($code != 0) { + fwrite(STDERR, "dark_diff failed with code $code\n"); + return; + } + + unlink($dark_file); + rename($temp_output, $dark_file); +} + +require __DIR__.'/init.php'; + +function build_static(): void { + $css_dir = ROOT.'/htdocs/css'; + $hashes = []; + + if (!file_exists($css_dir)) + mkdir($css_dir); + + // 1. scss -> css + $themes = ['light', 'dark']; + $entries = ['common', 'admin']; + foreach ($themes as $theme) { + foreach ($entries as $entry) { + $input = ROOT.'/htdocs/scss/entries/'.$entry.'/'.$theme.'.scss'; + $output = $css_dir.'/'.$entry.($theme == 'dark' ? '_dark' : '').'.css'; + if (sassc($input, $output) != 0) { + fwrite(STDERR, "error: could not compile entries/$entry/$theme.scss\n"); + continue; + } + + // 1.1. apply clean-css optimizations and transformations + clean_css($output); + } + } + + // 2. generate dark theme diff + foreach ($entries as $entry) { + $light_file = $css_dir.'/'.$entry.'.css'; + $dark_file = str_replace('.css', '_dark.css', $light_file); + dark_diff($light_file, $dark_file); + } + + // 3. calculate hashes + foreach (['css', 'js'] as $type) { + $reldir = ROOT.'/htdocs/'; + $entries = glob_recursive($reldir.$type.'/*.'.$type); + if (empty($entries)) { + continue; + } + foreach ($entries as $file) { + $name = preg_replace('/^'.preg_quote($reldir, '/').'/', '', $file); + $hashes[$name] = gethash($file); + } + } + + // 4. write config-static.php + $scfg = "<?php\n\n"; + $scfg .= "return ".var_export($hashes, true).";\n"; + + file_put_contents(ROOT.'/config-static.php', $scfg); +} + +build_static();
\ No newline at end of file diff --git a/dark-theme-diff.js b/dark-theme-diff.js new file mode 100755 index 0000000..5ca1945 --- /dev/null +++ b/dark-theme-diff.js @@ -0,0 +1,14 @@ +#!/usr/bin/env node +const {generateCSSPatch} = require('css-patch') +const fs = require('fs') + +const files = process.argv.slice(2) +if (files.length !== 2) { + console.log(`usage: ${process.argv[0]} file1 file2`) + process.exit() +} + +const css1 = fs.readFileSync(files[0], 'utf-8') +const css2 = fs.readFileSync(files[1], 'utf-8') + +console.log(generateCSSPatch(css1, css2))
\ No newline at end of file @@ -25,7 +25,12 @@ git reset --hard git pull origin master composer8.1 install --no-dev --optimize-autoloader --ignore-platform-reqs -$PHP prepare_static.php + +if [ ! -d node_modules ]; then + npm i +fi + +$PHP build_static.php cp "$DEV_DIR/config-local.php" . cat config-local.php | grep -v is_dev | tee config-local.php >/dev/null diff --git a/engine/RequestDispatcher.php b/engine/RequestDispatcher.php index 38b965d..3c3f684 100644 --- a/engine/RequestDispatcher.php +++ b/engine/RequestDispatcher.php @@ -39,11 +39,14 @@ class RequestDispatcher { } $skin = new Skin(); - $skin->static[] = '/css/common-bundle.css'; + $skin->static[] = '/css/common.css'; $skin->static[] = '/js/common.js'; + $lang = LangData::getInstance(); + $skin->addLangKeys($lang->search('/^theme_/')); + /** @var RequestHandler $handler */ - $handler = new $handler_class($skin, LangData::getInstance(), $router_input); + $handler = new $handler_class($skin, $lang, $router_input); $resp = $handler->beforeDispatch(); if ($resp instanceof Response) { $resp->send(); diff --git a/engine/Skin.php b/engine/Skin.php index 57f8b90..917eef7 100644 --- a/engine/Skin.php +++ b/engine/Skin.php @@ -23,11 +23,16 @@ class Skin { else $js = null; + $theme = ($_COOKIE['theme'] ?? 'auto'); + if (!in_array($theme, ['auto', 'dark', 'light'])) + $theme = 'auto'; + $layout_ctx = new SkinContext('\\skin\\base'); $lang = $this->getLang(); $lang = !empty($lang) ? json_encode($lang, JSON_UNESCAPED_UNICODE) : ''; return new Response(200, $layout_ctx->layout( static: $this->static, + theme: $theme, title: $this->title, opts: $this->options, js: $js, diff --git a/engine/SkinContext.php b/engine/SkinContext.php index cfb5068..c395453 100644 --- a/engine/SkinContext.php +++ b/engine/SkinContext.php @@ -53,10 +53,12 @@ class SkinContext extends SkinBase { return call_user_func_array($fn, $arguments); } - public function __get(string $name) { + public function &__get(string $name) { $fn = $this->ns.'\\'.$name; - if (function_exists($fn)) - return [$this, $name]; + if (function_exists($fn)) { + $f = [$this, $name]; + return $f; + } if (array_key_exists($name, $this->data)) return $this->data[$name]; diff --git a/htdocs/img/enter.svg b/htdocs/img/enter.svg deleted file mode 100644 index 6fe49ed..0000000 --- a/htdocs/img/enter.svg +++ /dev/null @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" width="15" height="9.474"><path d="M0 4.737l4.737 4.737 1.105-1.106L3 5.526h12V.79h-1.579v3.158H3l2.842-2.842L4.737 0z"/></svg> diff --git a/htdocs/js/common.js b/htdocs/js/common.js index 4e4199c..959b672 100644 --- a/htdocs/js/common.js +++ b/htdocs/js/common.js @@ -14,6 +14,39 @@ if (!String.prototype.endsWith) { }; } +if (!Object.assign) { + Object.defineProperty(Object, 'assign', { + enumerable: false, + configurable: true, + writable: true, + value: function(target, firstSource) { + 'use strict'; + if (target === undefined || target === null) { + throw new TypeError('Cannot convert first argument to object'); + } + + var to = Object(target); + for (var i = 1; i < arguments.length; i++) { + var nextSource = arguments[i]; + if (nextSource === undefined || nextSource === null) { + continue; + } + + var keysArray = Object.keys(Object(nextSource)); + for (var nextIndex = 0, len = keysArray.length; nextIndex < len; nextIndex++) { + var nextKey = keysArray[nextIndex]; + var desc = Object.getOwnPropertyDescriptor(nextSource, nextKey); + if (desc !== undefined && desc.enumerable) { + to[nextKey] = nextSource[nextKey]; + } + } + } + return to; + } + }); +} + + // // AJAX // @@ -87,7 +120,7 @@ if (!String.prototype.endsWith) { break; } - opts = extend({}, defaultOpts, opts); + opts = Object.assign({}, defaultOpts, opts); var xhr = createXMLHttpRequest(); xhr.open(method, url); @@ -244,11 +277,11 @@ function setCookie(name, value, days) { date.setTime(date.getTime() + (days*24*60*60*1000)); expires = "; expires=" + date.toUTCString(); } - document.cookie = name + "=" + (value || "") + expires + "; path=/"; + document.cookie = name + "=" + (value || "") + expires + "; domain=" + window.appConfig.cookieHost + "; path=/"; } function unsetCookie(name) { - document.cookie = name+'=; Max-Age=-99999999;'; + document.cookie = name + '=; Max-Age=-99999999; domain=' + window.appConfig.cookieHost + "; path=/"; } function getCookie(name) { @@ -312,6 +345,34 @@ function escape(str) { return pre.innerHTML; } +function parseUrl(uri) { + var parser = document.createElement('a'); + parser.href = uri; + + return { + protocol: parser.protocol, // => "http:" + host: parser.host, // => "example.com:3000" + hostname: parser.hostname, // => "example.com" + port: parser.port, // => "3000" + pathname: parser.pathname, // => "/pathname/" + hash: parser.hash, // => "#hash" + search: parser.search, // => "?search=test" + origin: parser.origin, // => "http://example.com:3000" + path: (parser.pathname || '') + (parser.search || '') + } +} + +function once(fn, context) { + var result; + return function() { + if (fn) { + result = fn.apply(context || this, arguments); + fn = null; + } + return result; + }; +} + // // @@ -390,3 +451,229 @@ window.__lang = {}; unsetCookie('is_retina'); } })(); + +var StaticManager = { + loadedStyles: [], + versions: {}, + + setStyles: function(list, versions) { + this.loadedStyles = list; + this.versions = versions; + }, + + loadStyle: function(name, theme, callback) { + var url; + if (!window.appConfig.devMode) { + var filename = name + (theme === 'dark' ? '_dark' : '') + '.css'; + url = '/css/'+filename+'?'+this.versions[filename]; + } else { + url = '/sass.php?name='+name+'&theme='+theme; + } + + var el = document.createElement('link'); + el.onerror = callback + el.onload = callback + el.setAttribute('rel', 'stylesheet'); + el.setAttribute('type', 'text/css'); + el.setAttribute('id', 'style_'+name+'_dark'); + el.setAttribute('href', url); + + document.getElementsByTagName('head')[0].appendChild(el); + } +}; + +var ThemeSwitcher = (function() { + /** + * @type {string[]} + */ + var modes = ['auto', 'dark', 'light']; + + /** + * @type {number} + */ + var currentModeIndex = -1; + + /** + * @type {boolean|null} + */ + var systemState = null; + + /** + * @returns {boolean} + */ + function isSystemModeSupported() { + try { + // crashes on: + // Mozilla/5.0 (Windows NT 6.2; ARM; Trident/7.0; Touch; rv:11.0; WPDesktop; Lumia 630 Dual SIM) like Gecko + // Mozilla/5.0 (iPhone; CPU iPhone OS 13_5_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.1.1 Mobile/15E148 Safari/604.1 + // Mozilla/5.0 (iPad; CPU OS 12_5_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.1.2 Mobile/15E148 Safari/604.1 + // + // error examples: + // - window.matchMedia("(prefers-color-scheme: dark)").addEventListener is not a function. (In 'window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change",this.onSystemSettingChange.bind(this))', 'window.matchMedia("(prefers-color-scheme: dark)").addEventListener' is undefined) + // - Object [object MediaQueryList] has no method 'addEventListener' + return !!window['matchMedia'] + && typeof window.matchMedia("(prefers-color-scheme: dark)").addEventListener === 'function'; + } catch (e) { + return false + } + } + + /** + * @returns {boolean} + */ + function isDarkModeApplied() { + var st = StaticManager.loadedStyles; + for (var i = 0; i < st.length; i++) { + var name = st[i]; + if (ge('style_'+name+'_dark')) + return true; + } + return false; + } + + /** + * @returns {string} + */ + function getSavedMode() { + var val = getCookie('theme'); + if (!val) + return modes[0]; + if (modes.indexOf(val) === -1) { + console.error('[ThemeSwitcher getSavedMode] invalid cookie value') + unsetCookie('theme') + return modes[0] + } + return val + } + + /** + * @param {boolean} dark + */ + function changeTheme(dark) { + addClass(document.body, 'theme-changing'); + + var onDone = function() { + window.requestAnimationFrame(function() { + removeClass(document.body, 'theme-changing'); + }) + }; + + window.requestAnimationFrame(function() { + if (dark) + enableDark(onDone); + else + disableDark(onDone); + }) + } + + /** + * @param {function} callback + */ + function enableDark(callback) { + var names = []; + StaticManager.loadedStyles.forEach(function(name) { + var el = ge('style_'+name+'_dark'); + if (el) + return; + names.push(name); + }); + + var left = names.length; + names.forEach(function(name) { + StaticManager.loadStyle(name, 'dark', once(function(e) { + left--; + if (left === 0) + callback(); + })); + }) + } + + /** + * @param {function} callback + */ + function disableDark(callback) { + StaticManager.loadedStyles.forEach(function(name) { + var el = ge('style_'+name+'_dark'); + if (el) + el.remove(); + }) + callback(); + } + + /** + * @param {string} mode + */ + function setLabel(mode) { + var labelEl = ge('theme-switcher-label'); + labelEl.innerHTML = escape(lang('theme_'+mode)); + } + + return { + init: function() { + var cur = getSavedMode(); + currentModeIndex = modes.indexOf(cur); + + var systemSupported = isSystemModeSupported(); + if (!systemSupported) { + if (currentModeIndex === 0) { + modes.shift(); // remove 'auto' from the list + currentModeIndex = 1; // set to 'light' + if (isDarkModeApplied()) + disableDark(); + } + } else { + /** + * @param {boolean} dark + */ + var onSystemChange = function(dark) { + var prevSystemState = systemState; + systemState = dark; + + if (modes[currentModeIndex] !== 'auto') + return; + + if (systemState !== prevSystemState) + changeTheme(systemState); + }; + + window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', function(e) { + onSystemChange(e.matches === true) + }); + + onSystemChange(window.matchMedia('(prefers-color-scheme: dark)').matches === true); + } + + setLabel(modes[currentModeIndex]); + }, + + next: function(e) { + if (hasClass(document.body, 'theme-changing')) { + console.log('next: theme changing is in progress, ignoring...') + return; + } + + currentModeIndex = (currentModeIndex + 1) % modes.length; + switch (modes[currentModeIndex]) { + case 'auto': + if (systemState !== null) + changeTheme(systemState); + break; + + case 'light': + if (isDarkModeApplied()) + changeTheme(false); + break; + + case 'dark': + if (!isDarkModeApplied()) + changeTheme(true); + break; + } + + setLabel(modes[currentModeIndex]); + setCookie('theme', modes[currentModeIndex]); + + return cancelEvent(e); + } + }; +})();
\ No newline at end of file diff --git a/htdocs/sass.php b/htdocs/sass.php index eb24962..186b706 100644 --- a/htdocs/sass.php +++ b/htdocs/sass.php @@ -4,7 +4,14 @@ require __DIR__.'/../init.php'; global $config; $name = $_REQUEST['name'] ?? ''; -if (!$config['is_dev'] || !$name || !file_exists($path = ROOT.'/htdocs/scss/'.$name.'.scss')) { +$theme = $_REQUEST['theme'] ?? ''; + +if ($theme != 'light' && $theme != 'dark') { + http_response_code(403); + exit; +} + +if (!$config['is_dev'] || !$name || !file_exists($path = ROOT.'/htdocs/scss/entries/'.$name.'/'.$theme.'.scss')) { // logError(__FILE__.': access denied'); http_response_code(403); exit; diff --git a/htdocs/scss/blog.scss b/htdocs/scss/app/blog.scss index 7641683..53ec7e4 100644 --- a/htdocs/scss/blog.scss +++ b/htdocs/scss/app/blog.scss @@ -1,4 +1,4 @@ -@import 'vars'; +@import '../vars'; .blog-write-link-wrap { margin-bottom: $base-padding; @@ -81,7 +81,7 @@ margin-bottom: 2px; } .blog-upload-item-info { - color: #888; + color: $grey; font-size: $fs - 2px; } .blog-upload-item-note { @@ -102,7 +102,7 @@ } .blog-post-date { - color: #888; + color: $grey; margin-top: 5px; font-size: $fs - 1px; > a { @@ -168,7 +168,7 @@ background-color: $code-block-bg; span.term-prompt { - color: #999; + color: $light-grey; @include user-select(none); } } @@ -177,7 +177,7 @@ border-left: 3px #e0e0e0 solid; margin-left: 0; padding: 5px 0 5px 12px; - color: #888; + color: $grey; } table.table-100 { @@ -266,7 +266,7 @@ .blog-post-comments { margin-top: $base-padding; padding: 12px 15px; - border: 1px #e0e0e0 solid; + border: 1px $border-color solid; @include radius(3px); } .blog-post-comments img { @@ -317,7 +317,7 @@ td.blog-item-date-cell { padding-right: 10px; } .blog-item-date { - color: #777; + color: $grey; //text-transform: lowercase; } td.blog-item-title-cell { @@ -344,6 +344,8 @@ td.blog-item-title-cell { padding-top: 0; } } + +/* a.blog-item-view-all-link { display: inline-block; padding: 4px 17px; @@ -356,7 +358,7 @@ a.blog-item-view-all-link:hover { text-decoration: none; background-color: #ededed; } - +*/ .blog-tags { float: right; @@ -374,7 +376,7 @@ a.blog-item-view-all-link:hover { font-size: $fs - 1px; } .blog-tag-item > a { - color: #222; + color: $fg; } .blog-tag-item-count { color: #aaa; diff --git a/htdocs/scss/common.scss b/htdocs/scss/app/common.scss index 644a080..b9c5959 100644 --- a/htdocs/scss/common.scss +++ b/htdocs/scss/app/common.scss @@ -1,4 +1,4 @@ -@import "vars"; +@import "../vars"; .clearfix:after { content: "."; @@ -13,7 +13,7 @@ html, body { padding: 0; margin: 0; border: 0; - //background-color: $bg; + background-color: $bg; color: $fg; height: 100%; min-height: 100%; @@ -35,13 +35,15 @@ body.full-width .base-width { margin-right: auto; } -input[type="text"], textarea { +input[type="text"], +input[type="password"], +textarea { -webkit-appearance: none; -moz-appearance: none; appearance: none; box-sizing: border-box; border: 1px $input-border solid; - border-radius: 0px; + border-radius: 0; background-color: $input-bg; color: $fg; font-family: $ff; @@ -49,14 +51,16 @@ input[type="text"], textarea { padding: 6px; outline: none; @include radius(3px); + + &:focus { + border-color: $input-border-focused; + } } + textarea { resize: vertical; } -input[type="text"]:focus, -textarea:focus { - border-color: $input-border-focused; -} + //input[type="checkbox"] { // margin-left: 0; //} @@ -110,137 +114,6 @@ p, p code { padding: $base-padding 0; } -.head { - padding: 0 $side-padding; -} -.head-inner { - //padding: 13px 0; - position: relative; - border-bottom: 2px $border-color solid; -} -.head-logo { - padding: 4px 0; - font-family: $ffMono; - font-size: 15px; - display: inline-block; - position: absolute; - left: 0; - background-color: transparent; - @include transition(background-color, 0.03s); -} -.head-logo { - padding: 16px 0; - background-color: #fff; -} -.head-logo:after { - content: ''; - display: block; - width: 40px; - position: absolute; - right: -40px; - top: 0; - bottom: 0; - border-left: 8px #fff solid; - box-sizing: border-box; - background: linear-gradient(to left, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 1) 100%); /* W3C */ -} -.head-logo > a { - color: $fg; - font-size: 14px; -} -.head-logo > a:hover { - text-decoration: none; -} -.head-logo-enter { - display: inline; - opacity: 0; - font-size: 11px; - position: relative; - background: #eee; - padding: 2px 5px; - color: #333; - font-weight: normal; - vertical-align: middle; - top: -1px; - @include transition(opacity, 0.03s); -} -.head-logo-enter-icon { - width: 12px; - height: 7px; - display: inline-block; - background: url(/img/enter.svg) 0 0 no-repeat; - background-size: 12px 7px; - margin-right: 5px; -} -.head-logo > a:hover .head-logo-enter { - opacity: 1; -} -.head-logo-path { - color: $fg; - font-weight: bold; - -webkit-font-smoothing: antialiased; - @include transition(color, 0.03s); -} -.head-logo > a:hover .head-logo-path:not(.alwayshover) { - color: #aaa; -} -.head-logo-path:not(.neverhover):hover { - color: #000 !important; -} -.head-logo-dolsign { - color: $head-green-color; - font-weight: normal; - &.is_root { - color: $head-red-color; - } -} -.head-logo-cd { - display: none; -} -.head-logo > a:hover .head-logo-cd { - display: inline; -} -.head-logo-path-mapped { - padding: 3px 5px; - background: #f1f1f1; - pointer-events: none; - @include radius(3px); - margin: 0 2px; -} - -.head-items { - float: right; - color: #777; // color of separators - //padding: 8px 0; -} -a.head-item { - color: $fg; - font-size: $fs - 1px; - display: block; - float: left; - padding: 16px 0; -} -a.head-item > span { - padding: 0 12px; - border-right: 1px #d0d0d0 solid; -} -a.head-item > span > span { - padding: 2px 0; -} -a.head-item:last-child > span { - border-right: 0; - padding-right: 1px; -} -/*a.head-item:first-child > span { - padding-left: 2px; -}*/ -a.head-item:hover { - //color: $link-color; - text-decoration: none; -} -a.head-item:hover > span > span { - border-bottom: 1px #d0d0d0 solid; -} table.contacts { border: 0; @@ -257,17 +130,17 @@ table.contacts td { table.contacts td.label { text-align: right; width: 30%; - color: #777; + color: $dark-grey; } table.contacts td.value { text-align: left; padding-left: 8px; } table.contacts td.value span { - background: #eee; + background: $inline-code-block-bg; padding: 3px 7px 4px; border-radius: 3px; - color: #333; + color: $fg; font-family: $ffMono; font-size: $fs - 1px; } @@ -283,13 +156,14 @@ table.contacts td pre { table.contacts div.note { font-size: $fs - 3px; padding-top: 2px; - color: #777; + color: $dark-grey; > a { - color: #777; - border-bottom: 1px #ccc solid; + color: $dark-grey; + border-bottom: 1px $border-color solid; &:hover { text-decoration: none; - border-bottom-color: #999; + color: $link-color; + border-bottom-color: $link-color; } } } @@ -360,11 +234,11 @@ table.contacts div.note { //font-weight: bold; } .md-file-attach-size { - color: #888; + color: $grey; margin-left: 2px; } .md-file-attach-note { - color: #000; + color: $fg; margin-left: 2px; } @@ -402,7 +276,7 @@ table.contacts div.note { } .md-image-note { line-height: 150%; - color: #777; + color: $dark-grey; padding: 2px 0 4px; } diff --git a/htdocs/scss/form.scss b/htdocs/scss/app/form.scss index 4faa4d1..197732c 100644 --- a/htdocs/scss/form.scss +++ b/htdocs/scss/app/form.scss @@ -1,4 +1,4 @@ -@import 'vars'; +@import '../vars'; $form-field-label-width: 120px; @@ -35,7 +35,7 @@ form { display: block; margin: 0; } font-size: 12px; letter-spacing: 0.5px; text-transform: uppercase; - color: #888; + color: $grey; } .form-field { //margin-left: $form-field-label-width + 10px; diff --git a/htdocs/scss/app/head.scss b/htdocs/scss/app/head.scss new file mode 100644 index 0000000..fed30d1 --- /dev/null +++ b/htdocs/scss/app/head.scss @@ -0,0 +1,157 @@ +.head { + padding: 0 $side-padding; +} +.head-inner { + //padding: 13px 0; + position: relative; + border-bottom: 2px $border-color solid; +} +.head-logo { + padding: 4px 0; + font-family: $ffMono; + font-size: 15px; + display: inline-block; + position: absolute; + left: 0; + background-color: transparent; +} +body:not(.theme-changing) .head-logo { + @include transition(background-color, 0.03s); +} +.head-logo { + padding: 16px 0; + background-color: $bg; +} +.head-logo:after { + content: ''; + display: block; + width: 40px; + position: absolute; + right: -40px; + top: 0; + bottom: 0; + border-left: 8px $bg solid; + box-sizing: border-box; + background: linear-gradient(to left, rgba($bg, 0) 0%, rgba($bg, 1) 100%); /* W3C */ +} +.head-logo > a { + color: $fg; + font-size: 14px; +} +.head-logo > a:hover { + text-decoration: none; +} +.head-logo-enter { + background: $code-block-bg; + color: $hljs_fg; + display: inline; + opacity: 0; + font-size: 11px; + position: relative; + padding: 2px 5px; + font-weight: normal; + vertical-align: middle; + top: -1px; +} +body:not(.theme-changing) .head-logo-enter { + @include transition(opacity, 0.03s); +} +.head-logo-enter-icon { + width: 12px; + height: 7px; + display: inline-block; + margin-right: 5px; + position: relative; + top: 1px; + + > svg { + path { + fill: $hljs_fg; + } + } +} +.head-logo > a:hover .head-logo-enter { + opacity: 1; +} +.head-logo-path { + color: $fg; + font-weight: bold; + -webkit-font-smoothing: antialiased; +} +body:not(.theme-changing) .head-logo-path { + @include transition(color, 0.03s); +} +.head-logo > a:hover .head-logo-path:not(.alwayshover) { + color: $light_grey; +} +.head-logo-path:not(.neverhover):hover { + color: $fg !important; +} +.head-logo-dolsign { + color: $head_green_color; + font-weight: normal; + &.is_root { + color: $head_red_color; + } +} +.head-logo-cd { + display: none; +} +.head-logo > a:hover .head-logo-cd { + display: inline; +} +.head-logo-path-mapped { + padding: 3px 5px; + background: #f1f1f1; + pointer-events: none; + @include radius(3px); + margin: 0 2px; +} + +.head-items { + float: right; + color: #777; // color of separators + //padding: 8px 0; +} +a.head-item { + color: $fg; + font-size: $fs - 1px; + display: block; + float: left; + padding: 16px 0; + + > span { + position: relative; + padding: 0 12px; + border-right: 1px $head-items-separator solid; + + > span { + padding: 2px 0; + + > span.moon-icon { + position: absolute; + left: 0; + + > svg path { + fill: $fg; + } + } + } + } + + &.is-theme-switcher > span { + padding-left: 20px; + } + + &:last-child > span { + border-right: 0; + padding-right: 1px; + } +} +a.head-item:hover { + //color: $link-color; + text-decoration: none; +} +a.head-item:hover > span > span { + border-bottom: 1px $head-items-separator solid; +}
\ No newline at end of file diff --git a/htdocs/scss/app/hljs.scss b/htdocs/scss/app/hljs.scss new file mode 100644 index 0000000..913c45e --- /dev/null +++ b/htdocs/scss/app/hljs.scss @@ -0,0 +1 @@ +@import "../hljs/github.css"; diff --git a/htdocs/scss/mobile.scss b/htdocs/scss/app/mobile.scss index d4d0d25..a765e25 100644 --- a/htdocs/scss/mobile.scss +++ b/htdocs/scss/app/mobile.scss @@ -1,4 +1,4 @@ -@import 'vars'; +@import '../vars'; textarea { -webkit-overflow-scrolling: touch; diff --git a/htdocs/scss/pages.scss b/htdocs/scss/app/pages.scss index 873a6ae..873a6ae 100644 --- a/htdocs/scss/pages.scss +++ b/htdocs/scss/app/pages.scss diff --git a/htdocs/scss/bundle_admin.scss b/htdocs/scss/bundle_admin.scss new file mode 100644 index 0000000..06808d0 --- /dev/null +++ b/htdocs/scss/bundle_admin.scss @@ -0,0 +1,3 @@ +.admin-page { + line-height: 155%; +} diff --git a/htdocs/scss/bundle_common.scss b/htdocs/scss/bundle_common.scss new file mode 100644 index 0000000..dd0bd6a --- /dev/null +++ b/htdocs/scss/bundle_common.scss @@ -0,0 +1,10 @@ +@import "./app/common"; +@import "./app/head"; +@import "./app/blog"; +@import "./app/form"; +@import "./app/pages"; +@import "./hljs/github.scss"; + +@media screen and (max-width: 600px) { + @import "./app/mobile"; +} diff --git a/htdocs/scss/colors/dark.scss b/htdocs/scss/colors/dark.scss new file mode 100644 index 0000000..c7c25cf --- /dev/null +++ b/htdocs/scss/colors/dark.scss @@ -0,0 +1,46 @@ +$head_green_color: #0bad19; +$head_red_color: #e23636; +$link-color: #71abe5; + +$grey: #798086; +$dark-grey: $grey; +$light-grey: $grey; +$fg: #eee; +$bg: #222; + +$code-block-bg: #394146; +$inline-code-block-bg: #394146; + +$light-bg: #464c4e; +$dark-bg: #272C2D; +$dark-fg: #999; + +$input-border: #48535a; +$input-border-focused: #48535a; +$input-bg: #30373b; +$border-color: #48535a; + +$error-block-bg: #f9eeee; +$error-block-fg: #d13d3d; + +$success-block-bg: #eff5f0; +$success-block-fg: #2a6f34; + +$head-items-separator: #5e6264; + +// colors from https://github.com/Kelbster/highlightjs-material-dark-theme/blob/master/css/materialdark.css +$hljs_fg: #CDD3D8; +$hljs_bg: #2B2B2D; +$hljs_quote: #6272a4; +$hljs_string: #f1fa8c; +$hljs_literal: #bd93f9; +$hljs_title: #75A5FF; +$hljs_keyword: #C792EA; +$hljs_type: #da4939; +$hljs_tag: #abb2bf; +$hljs_regexp: #F77669; +$hljs_symbol: #C792EA; +$hljs_builtin: #C792EA; +$hljs_meta: #75A5FF; +$hljs_deletion: #e6e1dc; +$hljs_addition: #144212;
\ No newline at end of file diff --git a/htdocs/scss/colors/light.scss b/htdocs/scss/colors/light.scss new file mode 100644 index 0000000..e6c14ba --- /dev/null +++ b/htdocs/scss/colors/light.scss @@ -0,0 +1,46 @@ +$head_green_color: #0bad19; +$head_red_color: #ce1a1a; +$link-color: #116fd4; + +$grey: #888; +$dark-grey: #777; +$light-grey: #999; +$fg: #222; +$bg: #fff; + +$code-block-bg: #f3f3f3; +$inline-code-block-bg: #f1f1f1; + +$light-bg: #464c4e; +$dark-bg: #272C2D; +$dark-fg: #999; + +$input-border: #e0e0e0; +$input-border-focused: #e0e0e0; +$input-bg: #f7f7f7; +$border-color: #e0e0e0; + +$error-block-bg: #f9eeee; +$error-block-fg: #d13d3d; + +$success-block-bg: #eff5f0; +$success-block-fg: #2a6f34; + +$head-items-separator: #d0d0d0; + +// github.com style (c) Vasily Polovnyov <vast@whiteants.net> +$hljs_fg: #333; +$hljs_bg: #f8f8f8; +$hljs_quote: #998; +$hljs_string: #d14; +$hljs_literal: #008080; +$hljs_title: #900; +$hljs_keyword: $hljs_fg; +$hljs_type: #458; +$hljs_tag: #000080; +$hljs_regexp: #009926; +$hljs_symbol: #990073; +$hljs_builtin: #0086b3; +$hljs_meta: #999; +$hljs_deletion: #fdd; +$hljs_addition: #dfd;
\ No newline at end of file diff --git a/htdocs/scss/common-bundle.scss b/htdocs/scss/common-bundle.scss deleted file mode 100644 index 397f0c3..0000000 --- a/htdocs/scss/common-bundle.scss +++ /dev/null @@ -1,9 +0,0 @@ -@import "./common.scss"; -@import "./blog.scss"; -@import "./form.scss"; -@import "./hljs/github.scss"; -@import "./pages.scss"; - -@media screen and (max-width: 600px) { - @import "./mobile.scss"; -} diff --git a/htdocs/scss/entries/admin/dark.scss b/htdocs/scss/entries/admin/dark.scss new file mode 100644 index 0000000..d704436 --- /dev/null +++ b/htdocs/scss/entries/admin/dark.scss @@ -0,0 +1,2 @@ +@import '../../colors/dark'; +@import '../../bundle_admin';
\ No newline at end of file diff --git a/htdocs/scss/entries/admin/light.scss b/htdocs/scss/entries/admin/light.scss new file mode 100644 index 0000000..4ac8bf0 --- /dev/null +++ b/htdocs/scss/entries/admin/light.scss @@ -0,0 +1,2 @@ +@import '../../colors/light'; +@import '../../bundle_admin';
\ No newline at end of file diff --git a/htdocs/scss/entries/common/dark.scss b/htdocs/scss/entries/common/dark.scss new file mode 100644 index 0000000..f983214 --- /dev/null +++ b/htdocs/scss/entries/common/dark.scss @@ -0,0 +1,2 @@ +@import '../../colors/dark'; +@import '../../bundle_common';
\ No newline at end of file diff --git a/htdocs/scss/entries/common/light.scss b/htdocs/scss/entries/common/light.scss new file mode 100644 index 0000000..a3044bd --- /dev/null +++ b/htdocs/scss/entries/common/light.scss @@ -0,0 +1,2 @@ +@import '../../colors/light'; +@import '../../bundle_common';
\ No newline at end of file diff --git a/htdocs/scss/hljs.scss b/htdocs/scss/hljs.scss deleted file mode 100644 index 36f52de..0000000 --- a/htdocs/scss/hljs.scss +++ /dev/null @@ -1 +0,0 @@ -@import "./hljs/github.css"; diff --git a/htdocs/scss/hljs/github.scss b/htdocs/scss/hljs/github.scss index 791932b..ddbfc0a 100644 --- a/htdocs/scss/hljs/github.scss +++ b/htdocs/scss/hljs/github.scss @@ -1,27 +1,21 @@ -/* - -github.com style (c) Vasily Polovnyov <vast@whiteants.net> - -*/ - .hljs { display: block; overflow-x: auto; padding: 0.5em; - color: #333; - background: #f8f8f8; + color: $hljs_fg; + background: $hljs_bg; } .hljs-comment, .hljs-quote { - color: #998; + color: $hljs_quote; font-style: italic; } .hljs-keyword, .hljs-selector-tag, .hljs-subst { - color: #333; + color: $hljs_fg; font-weight: bold; } @@ -30,18 +24,18 @@ github.com style (c) Vasily Polovnyov <vast@whiteants.net> .hljs-variable, .hljs-template-variable, .hljs-tag .hljs-attr { - color: #008080; + color: $hljs_literal; } .hljs-string, .hljs-doctag { - color: #d14; + color: $hljs_string; } .hljs-title, .hljs-section, .hljs-selector-id { - color: #900; + color: $hljs_title; font-weight: bold; } @@ -51,43 +45,43 @@ github.com style (c) Vasily Polovnyov <vast@whiteants.net> .hljs-type, .hljs-class .hljs-title { - color: #458; + color: $hljs_type; font-weight: bold; } .hljs-tag, .hljs-name, .hljs-attribute { - color: #000080; + color: $hljs_tag; font-weight: normal; } .hljs-regexp, .hljs-link { - color: #009926; + color: $hljs_regexp; } .hljs-symbol, .hljs-bullet { - color: #990073; + color: $hljs_symbol; } .hljs-built_in, .hljs-builtin-name { - color: #0086b3; + color: $hljs_builtin; } .hljs-meta { - color: #999; + color: $hljs_meta; font-weight: bold; } .hljs-deletion { - background: #fdd; + background: $hljs_deletion; } .hljs-addition { - background: #dfd; + background: $hljs_addition; } .hljs-emphasis { diff --git a/htdocs/scss/vars.scss b/htdocs/scss/vars.scss index e056740..cc67f04 100644 --- a/htdocs/scss/vars.scss +++ b/htdocs/scss/vars.scss @@ -4,42 +4,9 @@ $ff: -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica, Arial, sans-serif; $ffMono: SFMono-Regular, Consolas, Liberation Mono, Menlo, Courier, monospace; $base-width: 900px; -//$sb-width: 120px; $side-padding: 25px; $base-padding: 18px; - $footer-height: 64px; -$head-green-color: #0bad19; -$head-red-color: #ce1a1a; -$link-color: #116fd4; - -$bg: #f7f7f7; -$content-bg: #fff; -$code-block-bg: #f3f3f3; -$inline-code-block-bg: #f1f1f1; - -$fg: #222; -$blue1: #729fcf; -$blue2: #3465a4; -$blue3: #204a87; -$orange1: #fcaf3e; -$orange2: #f57900; -$orange3: #ce5c00; - -$light-bg: #464c4e; -$dark-bg: #272C2D; -$dark-fg: #999; - -$input-border: #e0e0e0; -$input-border-focused: #e0e0e0; -$input-bg: #f7f7f7; -$border-color: #e0e0e0; - -$error-block-bg: #f9eeee; -$error-block-fg: #d13d3d; - -$success-block-bg: #eff5f0; -$success-block-fg: #2a6f34; @mixin radius($radius) { -o-border-radius: $radius; @@ -50,9 +17,9 @@ $success-block-fg: #2a6f34; } @mixin transition($property, $duration, $easing: linear) { - transition: $property $duration $easing; - -webkit-transition: $property $duration $easing; - -moz-transition: $property $duration $easing; + transition: $property $duration $easing; + -webkit-transition: $property $duration $easing; + -moz-transition: $property $duration $easing; } @mixin linearGradient($top, $bottom){ diff --git a/lang/en.php b/lang/en.php index cf2af6c..5736104 100644 --- a/lang/en.php +++ b/lang/en.php @@ -20,6 +20,11 @@ return [ 'delete' => 'delete', 'info_saved' => 'Information saved.', + // theme switcher + 'theme_auto' => 'auto', + 'theme_dark' => 'dark', + 'theme_light' => 'light', + // contacts 'contacts_email' => 'email', 'contacts_pgp' => 'OpenPGP public key', diff --git a/model/Upload.php b/model/Upload.php index 586be24..441a14d 100644 --- a/model/Upload.php +++ b/model/Upload.php @@ -142,7 +142,7 @@ class Upload extends Model { if (is_file($dir.'/'.$f)) unlink($dir.'/'.$f); else - logError('deleteAllImagePreviews: '.$dir.'/'.$f.' is not a file!'); + logError(__METHOD__.': '.$dir.'/'.$f.' is not a file!'); $deleted++; } } diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..578f958 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,647 @@ +{ + "name": "www", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "devDependencies": { + "clean-css": "^5.3.0", + "clean-css-cli": "^5.6.0", + "css-patch": "^1.2.0" + } + }, + "node_modules/anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/clean-css": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.0.tgz", + "integrity": "sha512-YYuuxv4H/iNb1Z/5IbMRoxgrzjWGhOEFfd+groZ5dMCVkpENiMZmwspdrzBo9286JjM1gZJPAyL7ZIdzuvu2AQ==", + "dev": true, + "dependencies": { + "source-map": "~0.6.0" + }, + "engines": { + "node": ">= 10.0" + } + }, + "node_modules/clean-css-cli": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/clean-css-cli/-/clean-css-cli-5.6.0.tgz", + "integrity": "sha512-68vorNEG808D1QzeerO9AlwQVTuaR8YSK4aqwIsjJq0wDSyPH11ApHY0O+EQrdEGUZcN+d72v+Nn/gpxjAFewQ==", + "dev": true, + "dependencies": { + "chokidar": "^3.5.2", + "clean-css": "^5.3.0", + "commander": "7.x", + "glob": "^7.1.6" + }, + "bin": { + "cleancss": "bin/cleancss" + }, + "engines": { + "node": ">= 10.12.0" + } + }, + "node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/css-patch": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/css-patch/-/css-patch-1.2.0.tgz", + "integrity": "sha512-wCIyPGugTmf10KO39QLD3N+qIum0ljrj/8pJdULjjuXQ6oEeYd5+quMF7jIdnEL5Ftp0wmbvO8qPvAmzrw0EaA==", + "dev": true, + "dependencies": { + "diff": "^5.0.0", + "stylis": "^4.0.13" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/diff": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.1.0.tgz", + "integrity": "sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stylis": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.1.1.tgz", + "integrity": "sha512-lVrM/bNdhVX2OgBFNa2YJ9Lxj7kPzylieHd3TNjuGE0Re9JB7joL5VUKOVH1kdNNJTgGPpT8hmwIAPLaSyEVFQ==", + "dev": true + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + } + }, + "dependencies": { + "anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "requires": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "fsevents": "~2.3.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + } + }, + "clean-css": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.0.tgz", + "integrity": "sha512-YYuuxv4H/iNb1Z/5IbMRoxgrzjWGhOEFfd+groZ5dMCVkpENiMZmwspdrzBo9286JjM1gZJPAyL7ZIdzuvu2AQ==", + "dev": true, + "requires": { + "source-map": "~0.6.0" + } + }, + "clean-css-cli": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/clean-css-cli/-/clean-css-cli-5.6.0.tgz", + "integrity": "sha512-68vorNEG808D1QzeerO9AlwQVTuaR8YSK4aqwIsjJq0wDSyPH11ApHY0O+EQrdEGUZcN+d72v+Nn/gpxjAFewQ==", + "dev": true, + "requires": { + "chokidar": "^3.5.2", + "clean-css": "^5.3.0", + "commander": "7.x", + "glob": "^7.1.6" + } + }, + "commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "css-patch": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/css-patch/-/css-patch-1.2.0.tgz", + "integrity": "sha512-wCIyPGugTmf10KO39QLD3N+qIum0ljrj/8pJdULjjuXQ6oEeYd5+quMF7jIdnEL5Ftp0wmbvO8qPvAmzrw0EaA==", + "dev": true, + "requires": { + "diff": "^5.0.0", + "stylis": "^4.0.13" + } + }, + "diff": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.1.0.tgz", + "integrity": "sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==", + "dev": true + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "optional": true + }, + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true + }, + "is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true + }, + "picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true + }, + "readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "requires": { + "picomatch": "^2.2.1" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "stylis": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.1.1.tgz", + "integrity": "sha512-lVrM/bNdhVX2OgBFNa2YJ9Lxj7kPzylieHd3TNjuGE0Re9JB7joL5VUKOVH1kdNNJTgGPpT8hmwIAPLaSyEVFQ==", + "dev": true + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..3abc792 --- /dev/null +++ b/package.json @@ -0,0 +1,7 @@ +{ + "devDependencies": { + "clean-css": "^5.3.0", + "clean-css-cli": "^5.6.0", + "css-patch": "^1.2.0" + } +} diff --git a/prepare_static.php b/prepare_static.php deleted file mode 100755 index e3663f8..0000000 --- a/prepare_static.php +++ /dev/null @@ -1,48 +0,0 @@ -#!/usr/bin/env php8.1 -<?php - -function gethash(string $path): string { - return substr(sha1(file_get_contents($path)), 0, 8); -} - -function sassc(string $src_dir, string $dst_dir, string $file): int { - $cmd = 'sassc -t expanded '.escapeshellarg($src_dir.'/'.$file).' '.escapeshellarg($dst_dir.'/'.preg_replace('/\.scss$/', '.css', $file)); - exec($cmd, $output, $code); - return $code; -} - -require __DIR__.'/init.php'; -global $config; - -function build_static(): void { - $css_dir = ROOT.'/htdocs/css'; - $hashes = []; - - if (!file_exists($css_dir)) - mkdir($css_dir); - - $files = ['common-bundle.scss', 'admin.scss']; - foreach ($files as $file) { - if (sassc(ROOT.'/htdocs/scss', $css_dir, $file) != 0) - fwrite(STDERR, "error: could not compile $file\n"); - } - - foreach (['css', 'js'] as $type) { - $reldir = ROOT.'/htdocs/'; - $files = glob_recursive($reldir.$type.'/*.'.$type); - if (empty($files)) { - continue; - } - foreach ($files as $file) { - $name = preg_replace('/^'.preg_quote($reldir, '/').'/', '', $file); - $hashes[$name] = gethash($file); - } - } - - $scfg = "<?php\n\n"; - $scfg .= "return ".var_export($hashes, true).";\n"; - - file_put_contents(ROOT.'/config-static.php', $scfg); -} - -build_static();
\ No newline at end of file diff --git a/skin/base.skin.php b/skin/base.skin.php index ab7d689..2be71dc 100644 --- a/skin/base.skin.php +++ b/skin/base.skin.php @@ -2,7 +2,17 @@ namespace skin\base; -function layout($ctx, $title, $unsafe_body, $static, $meta, $js, $opts, $exec_time, $unsafe_lang) { +use admin; +use RequestDispatcher; + +function layout($ctx, $title, $unsafe_body, $static, $meta, $js, $opts, $exec_time, $unsafe_lang, $theme) { +global $config; +$app_config = json_encode([ + 'domain' => $config['domain'], + 'devMode' => $config['is_dev'], + 'cookieHost' => $config['cookie_host'], +]); + return <<<HTML <!doctype html> <html lang="en"> @@ -12,16 +22,16 @@ return <<<HTML <link rel="shortcut icon" href="/favicon.ico?4" type="image/x-icon"> <link rel="alternate" type="application/rss+xml" href="/feed.rss"> <title>{$title}</title> + <script type="text/javascript">window.appConfig = {$app_config};</script> {$ctx->renderMeta($meta)} - {$ctx->renderStatic($static)} + {$ctx->renderStatic($static, $theme)} </head> <body{$ctx->if_true($opts['full_width'], ' class="full-width"')}> - {$ctx->renderHeader(renderLogo($ctx, $opts['logo_path_map'], $opts['logo_link_map']))} + {$ctx->renderHeader($theme, renderLogo($ctx, $opts['logo_path_map'], $opts['logo_link_map']))} <div class="page-content base-width"> <div class="page-content-inner">{$unsafe_body}</div> </div> - {$ctx->if_true($js != '' || !empty($lang) || $opts['dynlogo_enabled'], - $ctx->renderScript, $js, $unsafe_lang, $opts['dynlogo_enabled'])} + {$ctx->renderScript($js, $unsafe_lang, $opts['dynlogo_enabled'])} </body> </html> <!-- @@ -32,11 +42,18 @@ HTML; } function renderScript($ctx, $unsafe_js, $unsafe_lang, $enable_dynlogo) { +global $config; + +$styles = json_encode($ctx->styleNames); +$versions = !$config['is_dev'] ? json_encode($config['static']) : '{}'; + return <<<HTML <script type="text/javascript"> +StaticManager.setStyles({$styles}, {$versions}); {$ctx->if_true($unsafe_js, '(function(){'.$unsafe_js.'})();')} {$ctx->if_true($unsafe_lang, 'extend(__lang, '.$unsafe_lang.');')} {$ctx->if_true($enable_dynlogo, 'DynamicLogo.init();')} +ThemeSwitcher.init(); </script> HTML; } @@ -53,26 +70,76 @@ function renderMeta($ctx, $meta) { }, $meta)); } -function renderStatic($ctx, $static) { +function renderStatic($ctx, $static, $theme) { global $config; $html = []; + $dark = $theme == 'dark'; + $ctx->styleNames = []; foreach ($static as $name) { // list($name, $options) = $item; $version = $config['is_dev'] ? time() : $config['static'][substr($name, 1)] ?? 'notfound'; if (str_ends_with($name, '.js')) $html[] = jsLink($name, $version); - else if (str_ends_with($name, '.css')) - $html[] = cssLink($name, $version/*, $options*/); + else if (str_ends_with($name, '.css')) { + $html[] = cssLink($name, 'light', $version, $style_name); + $ctx->styleNames[] = $style_name; + + if ($dark) + $html[] = cssLink($name, 'dark', $version, $style_name); + else if (!$config['is_dev']) + $html[] = cssPrefetchLink(str_replace('.css', '_dark.css', $name), $version); + } } return implode("\n", $html); } -function renderHeader($ctx, $unsafe_logo_html) { +function jsLink(string $name, $version = null): string { + if ($version !== null) + $name .= '?'.$version; + return '<script src="'.$name.'" type="text/javascript"></script>'; +} + +function cssLink(string $name, string $theme, $version = null, &$bname = null): string { + global $config; + + $dname = dirname($name); + $bname = basename($name); + if (($pos = strrpos($bname, '.'))) + $bname = substr($bname, 0, $pos); + + if ($config['is_dev']) { + $href = '/sass.php?name='.urlencode($bname).'&theme='.$theme; + } else { + $href = $dname.'/'.$bname.($theme == 'dark' ? '_dark' : '').'.css'.($version !== null ? '?'.$version : ''); + } + $id = 'style_'.$bname; + if ($theme == 'dark') + $id .= '_dark'; + return '<link rel="stylesheet" id="'.$id.'" type="text/css" href="'.$href.'">'; +} + +function cssPrefetchLink(string $name, $verison = null): string { +$url = $name; +if ($verison) + $url .= '?'.$verison; +return <<<HTML +<link rel="prefetch" href="{$url}" /> +HTML; +} + +function renderHeader($ctx, $theme, $unsafe_logo_html) { return <<<HTML <div class="head base-width"> <div class="head-inner clearfix"> <div class="head-logo">{$unsafe_logo_html}</div> <div class="head-items clearfix"> + <a class="head-item is-theme-switcher" href="javascript:void(0)" onclick="return ThemeSwitcher.next(event)"> + <span> + <span> + <span class="moon-icon">{$ctx->renderMoonIcon()}</span><span id="theme-switcher-label">{$theme}</span> + </span> + </span> + </a> <a class="head-item" href="/"><span><span>blog</span></span></a> <a class="head-item" href="/projects/"><span><span>projects</span></span></a> <a class="head-item" href="https://git.ch1p.io/?s=idle"><span><span>git</span></span></a> @@ -87,9 +154,9 @@ HTML; // TODO rewrite this fcking crap function renderLogo($ctx, array $path_map = [], array $link_map = []): string { - $uri = \RequestDispatcher::path(); + $uri = RequestDispatcher::path(); - if (!\admin::isAdmin()) { + if (!admin::isAdmin()) { $prompt_sign = '<span class="head-logo-dolsign">$</span>'; } else { $prompt_sign = '<span class="head-logo-dolsign is_root">#</span>'; @@ -143,7 +210,7 @@ function renderLogo($ctx, array $path_map = [], array $link_map = []): string { $last_pos = $pos + 1; $close_tags++; } - $html .= str_repeat('</span>', $close_tags).' '.$prompt_sign.' <span class="head-logo-cd">cd <span id="head_cd_text">~</span> <span class="head-logo-enter"><span class="head-logo-enter-icon"></span>Enter</span></span></a>'; + $html .= str_repeat('</span>', $close_tags).' '.$prompt_sign.' <span class="head-logo-cd">cd <span id="head_cd_text">~</span> <span class="head-logo-enter"><span class="head-logo-enter-icon">'.enterIcon().'</span>Enter</span></span></a>'; for ($i = count($path_parts)-1, $j = 0; $i >= 0; $i--, $j++) { if (isset($path_map[$j])) { @@ -169,25 +236,16 @@ function renderLogo($ctx, array $path_map = [], array $link_map = []): string { return $html; } -function jsLink(string $name, $version = null): string { - if ($version !== null) - $name .= '?'.$version; - return '<script src="'.$name.'" type="text/javascript"></script>'; +function enterIcon() { +return <<<SVG +<svg width="12" height="7" viewBox="0 0 9.6 5.172" xmlns="http://www.w3.org/2000/svg"> + <path d="M.4 2.586l2.779 2.8.648-.654-1.667-1.68H9.2V.253h-.926V2.12H2.16L3.827.44 3.18-.214z"/> +</svg> +SVG; } -function cssLink(string $name, $version = null/*, $options = null*/): string { - global $config; - if ($config['is_dev']) { - $bname = basename($name); - if (($pos = strrpos($bname, '.'))) - $bname = substr($bname, 0, $pos); - $href = '/sass.php?name='.urlencode($bname); - } else { - $href = $name.($version !== null ? '?'.$version : ''); - } - $s = '<link rel="stylesheet" type="text/css" href="'.$href.'"'; - // if (!is_null($options)) - // $s .= ' media="'.$options.'"'; - $s .= '>'; - return $s; +function renderMoonIcon($ctx) { +return <<<SVG +<svg width="18" height="18" xmlns="http://www.w3.org/2000/svg"><path d="M14.54 10.37a5.4 5.4 0 01-6.91-6.91.59.59 0 00-.74-.75 6.66 6.66 0 00-2.47 1.54 6.6 6.6 0 1010.87 6.86.59.59 0 00-.75-.74zm-1.61 2.39a5.44 5.44 0 01-7.69-7.69 5.58 5.58 0 011-.76 6.55 6.55 0 007.47 7.47 5.15 5.15 0 01-.78.98z" fill-rule="evenodd" /></svg> +SVG; }
\ No newline at end of file |