dark theme support

This commit is contained in:
Evgeny Zinoviev 2022-07-10 01:30:05 +03:00
parent 8979719a1a
commit 1c524efbf7
34 changed files with 1504 additions and 319 deletions

96
build_static.php Executable file
View File

@ -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();

14
dark-theme-diff.js Executable file
View File

@ -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))

View File

@ -25,7 +25,12 @@ git reset --hard
git pull origin master git pull origin master
composer8.1 install --no-dev --optimize-autoloader --ignore-platform-reqs 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" . cp "$DEV_DIR/config-local.php" .
cat config-local.php | grep -v is_dev | tee config-local.php >/dev/null cat config-local.php | grep -v is_dev | tee config-local.php >/dev/null

View File

@ -39,11 +39,14 @@ class RequestDispatcher {
} }
$skin = new Skin(); $skin = new Skin();
$skin->static[] = '/css/common-bundle.css'; $skin->static[] = '/css/common.css';
$skin->static[] = '/js/common.js'; $skin->static[] = '/js/common.js';
$lang = LangData::getInstance();
$skin->addLangKeys($lang->search('/^theme_/'));
/** @var RequestHandler $handler */ /** @var RequestHandler $handler */
$handler = new $handler_class($skin, LangData::getInstance(), $router_input); $handler = new $handler_class($skin, $lang, $router_input);
$resp = $handler->beforeDispatch(); $resp = $handler->beforeDispatch();
if ($resp instanceof Response) { if ($resp instanceof Response) {
$resp->send(); $resp->send();

View File

@ -23,11 +23,16 @@ class Skin {
else else
$js = null; $js = null;
$theme = ($_COOKIE['theme'] ?? 'auto');
if (!in_array($theme, ['auto', 'dark', 'light']))
$theme = 'auto';
$layout_ctx = new SkinContext('\\skin\\base'); $layout_ctx = new SkinContext('\\skin\\base');
$lang = $this->getLang(); $lang = $this->getLang();
$lang = !empty($lang) ? json_encode($lang, JSON_UNESCAPED_UNICODE) : ''; $lang = !empty($lang) ? json_encode($lang, JSON_UNESCAPED_UNICODE) : '';
return new Response(200, $layout_ctx->layout( return new Response(200, $layout_ctx->layout(
static: $this->static, static: $this->static,
theme: $theme,
title: $this->title, title: $this->title,
opts: $this->options, opts: $this->options,
js: $js, js: $js,

View File

@ -53,10 +53,12 @@ class SkinContext extends SkinBase {
return call_user_func_array($fn, $arguments); return call_user_func_array($fn, $arguments);
} }
public function __get(string $name) { public function &__get(string $name) {
$fn = $this->ns.'\\'.$name; $fn = $this->ns.'\\'.$name;
if (function_exists($fn)) if (function_exists($fn)) {
return [$this, $name]; $f = [$this, $name];
return $f;
}
if (array_key_exists($name, $this->data)) if (array_key_exists($name, $this->data))
return $this->data[$name]; return $this->data[$name];

View File

@ -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>

Before

Width:  |  Height:  |  Size: 168 B

View File

@ -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 // AJAX
// //
@ -87,7 +120,7 @@ if (!String.prototype.endsWith) {
break; break;
} }
opts = extend({}, defaultOpts, opts); opts = Object.assign({}, defaultOpts, opts);
var xhr = createXMLHttpRequest(); var xhr = createXMLHttpRequest();
xhr.open(method, url); xhr.open(method, url);
@ -244,11 +277,11 @@ function setCookie(name, value, days) {
date.setTime(date.getTime() + (days*24*60*60*1000)); date.setTime(date.getTime() + (days*24*60*60*1000));
expires = "; expires=" + date.toUTCString(); expires = "; expires=" + date.toUTCString();
} }
document.cookie = name + "=" + (value || "") + expires + "; path=/"; document.cookie = name + "=" + (value || "") + expires + "; domain=" + window.appConfig.cookieHost + "; path=/";
} }
function unsetCookie(name) { function unsetCookie(name) {
document.cookie = name+'=; Max-Age=-99999999;'; document.cookie = name + '=; Max-Age=-99999999; domain=' + window.appConfig.cookieHost + "; path=/";
} }
function getCookie(name) { function getCookie(name) {
@ -312,6 +345,34 @@ function escape(str) {
return pre.innerHTML; 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'); 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);
}
};
})();

View File

@ -4,7 +4,14 @@ require __DIR__.'/../init.php';
global $config; global $config;
$name = $_REQUEST['name'] ?? ''; $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'); // logError(__FILE__.': access denied');
http_response_code(403); http_response_code(403);
exit; exit;

View File

@ -1,4 +1,4 @@
@import 'vars'; @import '../vars';
.blog-write-link-wrap { .blog-write-link-wrap {
margin-bottom: $base-padding; margin-bottom: $base-padding;
@ -81,7 +81,7 @@
margin-bottom: 2px; margin-bottom: 2px;
} }
.blog-upload-item-info { .blog-upload-item-info {
color: #888; color: $grey;
font-size: $fs - 2px; font-size: $fs - 2px;
} }
.blog-upload-item-note { .blog-upload-item-note {
@ -102,7 +102,7 @@
} }
.blog-post-date { .blog-post-date {
color: #888; color: $grey;
margin-top: 5px; margin-top: 5px;
font-size: $fs - 1px; font-size: $fs - 1px;
> a { > a {
@ -168,7 +168,7 @@
background-color: $code-block-bg; background-color: $code-block-bg;
span.term-prompt { span.term-prompt {
color: #999; color: $light-grey;
@include user-select(none); @include user-select(none);
} }
} }
@ -177,7 +177,7 @@
border-left: 3px #e0e0e0 solid; border-left: 3px #e0e0e0 solid;
margin-left: 0; margin-left: 0;
padding: 5px 0 5px 12px; padding: 5px 0 5px 12px;
color: #888; color: $grey;
} }
table.table-100 { table.table-100 {
@ -266,7 +266,7 @@
.blog-post-comments { .blog-post-comments {
margin-top: $base-padding; margin-top: $base-padding;
padding: 12px 15px; padding: 12px 15px;
border: 1px #e0e0e0 solid; border: 1px $border-color solid;
@include radius(3px); @include radius(3px);
} }
.blog-post-comments img { .blog-post-comments img {
@ -317,7 +317,7 @@ td.blog-item-date-cell {
padding-right: 10px; padding-right: 10px;
} }
.blog-item-date { .blog-item-date {
color: #777; color: $grey;
//text-transform: lowercase; //text-transform: lowercase;
} }
td.blog-item-title-cell { td.blog-item-title-cell {
@ -344,6 +344,8 @@ td.blog-item-title-cell {
padding-top: 0; padding-top: 0;
} }
} }
/*
a.blog-item-view-all-link { a.blog-item-view-all-link {
display: inline-block; display: inline-block;
padding: 4px 17px; padding: 4px 17px;
@ -356,7 +358,7 @@ a.blog-item-view-all-link:hover {
text-decoration: none; text-decoration: none;
background-color: #ededed; background-color: #ededed;
} }
*/
.blog-tags { .blog-tags {
float: right; float: right;
@ -374,7 +376,7 @@ a.blog-item-view-all-link:hover {
font-size: $fs - 1px; font-size: $fs - 1px;
} }
.blog-tag-item > a { .blog-tag-item > a {
color: #222; color: $fg;
} }
.blog-tag-item-count { .blog-tag-item-count {
color: #aaa; color: #aaa;

View File

@ -1,4 +1,4 @@
@import "vars"; @import "../vars";
.clearfix:after { .clearfix:after {
content: "."; content: ".";
@ -13,7 +13,7 @@ html, body {
padding: 0; padding: 0;
margin: 0; margin: 0;
border: 0; border: 0;
//background-color: $bg; background-color: $bg;
color: $fg; color: $fg;
height: 100%; height: 100%;
min-height: 100%; min-height: 100%;
@ -35,13 +35,15 @@ body.full-width .base-width {
margin-right: auto; margin-right: auto;
} }
input[type="text"], textarea { input[type="text"],
input[type="password"],
textarea {
-webkit-appearance: none; -webkit-appearance: none;
-moz-appearance: none; -moz-appearance: none;
appearance: none; appearance: none;
box-sizing: border-box; box-sizing: border-box;
border: 1px $input-border solid; border: 1px $input-border solid;
border-radius: 0px; border-radius: 0;
background-color: $input-bg; background-color: $input-bg;
color: $fg; color: $fg;
font-family: $ff; font-family: $ff;
@ -49,14 +51,16 @@ input[type="text"], textarea {
padding: 6px; padding: 6px;
outline: none; outline: none;
@include radius(3px); @include radius(3px);
&:focus {
border-color: $input-border-focused;
}
} }
textarea { textarea {
resize: vertical; resize: vertical;
} }
input[type="text"]:focus,
textarea:focus {
border-color: $input-border-focused;
}
//input[type="checkbox"] { //input[type="checkbox"] {
// margin-left: 0; // margin-left: 0;
//} //}
@ -110,137 +114,6 @@ p, p code {
padding: $base-padding 0; 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 { table.contacts {
border: 0; border: 0;
@ -257,17 +130,17 @@ table.contacts td {
table.contacts td.label { table.contacts td.label {
text-align: right; text-align: right;
width: 30%; width: 30%;
color: #777; color: $dark-grey;
} }
table.contacts td.value { table.contacts td.value {
text-align: left; text-align: left;
padding-left: 8px; padding-left: 8px;
} }
table.contacts td.value span { table.contacts td.value span {
background: #eee; background: $inline-code-block-bg;
padding: 3px 7px 4px; padding: 3px 7px 4px;
border-radius: 3px; border-radius: 3px;
color: #333; color: $fg;
font-family: $ffMono; font-family: $ffMono;
font-size: $fs - 1px; font-size: $fs - 1px;
} }
@ -283,13 +156,14 @@ table.contacts td pre {
table.contacts div.note { table.contacts div.note {
font-size: $fs - 3px; font-size: $fs - 3px;
padding-top: 2px; padding-top: 2px;
color: #777; color: $dark-grey;
> a { > a {
color: #777; color: $dark-grey;
border-bottom: 1px #ccc solid; border-bottom: 1px $border-color solid;
&:hover { &:hover {
text-decoration: none; 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; //font-weight: bold;
} }
.md-file-attach-size { .md-file-attach-size {
color: #888; color: $grey;
margin-left: 2px; margin-left: 2px;
} }
.md-file-attach-note { .md-file-attach-note {
color: #000; color: $fg;
margin-left: 2px; margin-left: 2px;
} }
@ -402,7 +276,7 @@ table.contacts div.note {
} }
.md-image-note { .md-image-note {
line-height: 150%; line-height: 150%;
color: #777; color: $dark-grey;
padding: 2px 0 4px; padding: 2px 0 4px;
} }

View File

@ -1,4 +1,4 @@
@import 'vars'; @import '../vars';
$form-field-label-width: 120px; $form-field-label-width: 120px;
@ -35,7 +35,7 @@ form { display: block; margin: 0; }
font-size: 12px; font-size: 12px;
letter-spacing: 0.5px; letter-spacing: 0.5px;
text-transform: uppercase; text-transform: uppercase;
color: #888; color: $grey;
} }
.form-field { .form-field {
//margin-left: $form-field-label-width + 10px; //margin-left: $form-field-label-width + 10px;

157
htdocs/scss/app/head.scss Normal file
View File

@ -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;
}

View File

@ -0,0 +1 @@
@import "../hljs/github.css";

View File

@ -1,4 +1,4 @@
@import 'vars'; @import '../vars';
textarea { textarea {
-webkit-overflow-scrolling: touch; -webkit-overflow-scrolling: touch;

View File

@ -0,0 +1,3 @@
.admin-page {
line-height: 155%;
}

View File

@ -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";
}

View File

@ -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;

View File

@ -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;

View File

@ -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";
}

View File

@ -0,0 +1,2 @@
@import '../../colors/dark';
@import '../../bundle_admin';

View File

@ -0,0 +1,2 @@
@import '../../colors/light';
@import '../../bundle_admin';

View File

@ -0,0 +1,2 @@
@import '../../colors/dark';
@import '../../bundle_common';

View File

@ -0,0 +1,2 @@
@import '../../colors/light';
@import '../../bundle_common';

View File

@ -1 +0,0 @@
@import "./hljs/github.css";

View File

@ -1,27 +1,21 @@
/*
github.com style (c) Vasily Polovnyov <vast@whiteants.net>
*/
.hljs { .hljs {
display: block; display: block;
overflow-x: auto; overflow-x: auto;
padding: 0.5em; padding: 0.5em;
color: #333; color: $hljs_fg;
background: #f8f8f8; background: $hljs_bg;
} }
.hljs-comment, .hljs-comment,
.hljs-quote { .hljs-quote {
color: #998; color: $hljs_quote;
font-style: italic; font-style: italic;
} }
.hljs-keyword, .hljs-keyword,
.hljs-selector-tag, .hljs-selector-tag,
.hljs-subst { .hljs-subst {
color: #333; color: $hljs_fg;
font-weight: bold; font-weight: bold;
} }
@ -30,18 +24,18 @@ github.com style (c) Vasily Polovnyov <vast@whiteants.net>
.hljs-variable, .hljs-variable,
.hljs-template-variable, .hljs-template-variable,
.hljs-tag .hljs-attr { .hljs-tag .hljs-attr {
color: #008080; color: $hljs_literal;
} }
.hljs-string, .hljs-string,
.hljs-doctag { .hljs-doctag {
color: #d14; color: $hljs_string;
} }
.hljs-title, .hljs-title,
.hljs-section, .hljs-section,
.hljs-selector-id { .hljs-selector-id {
color: #900; color: $hljs_title;
font-weight: bold; font-weight: bold;
} }
@ -51,43 +45,43 @@ github.com style (c) Vasily Polovnyov <vast@whiteants.net>
.hljs-type, .hljs-type,
.hljs-class .hljs-title { .hljs-class .hljs-title {
color: #458; color: $hljs_type;
font-weight: bold; font-weight: bold;
} }
.hljs-tag, .hljs-tag,
.hljs-name, .hljs-name,
.hljs-attribute { .hljs-attribute {
color: #000080; color: $hljs_tag;
font-weight: normal; font-weight: normal;
} }
.hljs-regexp, .hljs-regexp,
.hljs-link { .hljs-link {
color: #009926; color: $hljs_regexp;
} }
.hljs-symbol, .hljs-symbol,
.hljs-bullet { .hljs-bullet {
color: #990073; color: $hljs_symbol;
} }
.hljs-built_in, .hljs-built_in,
.hljs-builtin-name { .hljs-builtin-name {
color: #0086b3; color: $hljs_builtin;
} }
.hljs-meta { .hljs-meta {
color: #999; color: $hljs_meta;
font-weight: bold; font-weight: bold;
} }
.hljs-deletion { .hljs-deletion {
background: #fdd; background: $hljs_deletion;
} }
.hljs-addition { .hljs-addition {
background: #dfd; background: $hljs_addition;
} }
.hljs-emphasis { .hljs-emphasis {

View File

@ -4,42 +4,9 @@ $ff: -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica, Arial, sans-serif;
$ffMono: SFMono-Regular, Consolas, Liberation Mono, Menlo, Courier, monospace; $ffMono: SFMono-Regular, Consolas, Liberation Mono, Menlo, Courier, monospace;
$base-width: 900px; $base-width: 900px;
//$sb-width: 120px;
$side-padding: 25px; $side-padding: 25px;
$base-padding: 18px; $base-padding: 18px;
$footer-height: 64px; $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) { @mixin radius($radius) {
-o-border-radius: $radius; -o-border-radius: $radius;
@ -50,9 +17,9 @@ $success-block-fg: #2a6f34;
} }
@mixin transition($property, $duration, $easing: linear) { @mixin transition($property, $duration, $easing: linear) {
transition: $property $duration $easing; transition: $property $duration $easing;
-webkit-transition: $property $duration $easing; -webkit-transition: $property $duration $easing;
-moz-transition: $property $duration $easing; -moz-transition: $property $duration $easing;
} }
@mixin linearGradient($top, $bottom){ @mixin linearGradient($top, $bottom){

View File

@ -20,6 +20,11 @@ return [
'delete' => 'delete', 'delete' => 'delete',
'info_saved' => 'Information saved.', 'info_saved' => 'Information saved.',
// theme switcher
'theme_auto' => 'auto',
'theme_dark' => 'dark',
'theme_light' => 'light',
// contacts // contacts
'contacts_email' => 'email', 'contacts_email' => 'email',
'contacts_pgp' => 'OpenPGP public key', 'contacts_pgp' => 'OpenPGP public key',

View File

@ -142,7 +142,7 @@ class Upload extends Model {
if (is_file($dir.'/'.$f)) if (is_file($dir.'/'.$f))
unlink($dir.'/'.$f); unlink($dir.'/'.$f);
else else
logError('deleteAllImagePreviews: '.$dir.'/'.$f.' is not a file!'); logError(__METHOD__.': '.$dir.'/'.$f.' is not a file!');
$deleted++; $deleted++;
} }
} }

647
package-lock.json generated Normal file
View File

@ -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
}
}
}

7
package.json Normal file
View File

@ -0,0 +1,7 @@
{
"devDependencies": {
"clean-css": "^5.3.0",
"clean-css-cli": "^5.6.0",
"css-patch": "^1.2.0"
}
}

View File

@ -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();

View File

@ -2,7 +2,17 @@
namespace skin\base; 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 return <<<HTML
<!doctype html> <!doctype html>
<html lang="en"> <html lang="en">
@ -12,16 +22,16 @@ return <<<HTML
<link rel="shortcut icon" href="/favicon.ico?4" type="image/x-icon"> <link rel="shortcut icon" href="/favicon.ico?4" type="image/x-icon">
<link rel="alternate" type="application/rss+xml" href="/feed.rss"> <link rel="alternate" type="application/rss+xml" href="/feed.rss">
<title>{$title}</title> <title>{$title}</title>
<script type="text/javascript">window.appConfig = {$app_config};</script>
{$ctx->renderMeta($meta)} {$ctx->renderMeta($meta)}
{$ctx->renderStatic($static)} {$ctx->renderStatic($static, $theme)}
</head> </head>
<body{$ctx->if_true($opts['full_width'], ' class="full-width"')}> <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 base-width">
<div class="page-content-inner">{$unsafe_body}</div> <div class="page-content-inner">{$unsafe_body}</div>
</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> </body>
</html> </html>
<!-- <!--
@ -32,11 +42,18 @@ HTML;
} }
function renderScript($ctx, $unsafe_js, $unsafe_lang, $enable_dynlogo) { 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 return <<<HTML
<script type="text/javascript"> <script type="text/javascript">
StaticManager.setStyles({$styles}, {$versions});
{$ctx->if_true($unsafe_js, '(function(){'.$unsafe_js.'})();')} {$ctx->if_true($unsafe_js, '(function(){'.$unsafe_js.'})();')}
{$ctx->if_true($unsafe_lang, 'extend(__lang, '.$unsafe_lang.');')} {$ctx->if_true($unsafe_lang, 'extend(__lang, '.$unsafe_lang.');')}
{$ctx->if_true($enable_dynlogo, 'DynamicLogo.init();')} {$ctx->if_true($enable_dynlogo, 'DynamicLogo.init();')}
ThemeSwitcher.init();
</script> </script>
HTML; HTML;
} }
@ -53,26 +70,76 @@ function renderMeta($ctx, $meta) {
}, $meta)); }, $meta));
} }
function renderStatic($ctx, $static) { function renderStatic($ctx, $static, $theme) {
global $config; global $config;
$html = []; $html = [];
$dark = $theme == 'dark';
$ctx->styleNames = [];
foreach ($static as $name) { foreach ($static as $name) {
// list($name, $options) = $item; // list($name, $options) = $item;
$version = $config['is_dev'] ? time() : $config['static'][substr($name, 1)] ?? 'notfound'; $version = $config['is_dev'] ? time() : $config['static'][substr($name, 1)] ?? 'notfound';
if (str_ends_with($name, '.js')) if (str_ends_with($name, '.js'))
$html[] = jsLink($name, $version); $html[] = jsLink($name, $version);
else if (str_ends_with($name, '.css')) else if (str_ends_with($name, '.css')) {
$html[] = cssLink($name, $version/*, $options*/); $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); 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).'&amp;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 return <<<HTML
<div class="head base-width"> <div class="head base-width">
<div class="head-inner clearfix"> <div class="head-inner clearfix">
<div class="head-logo">{$unsafe_logo_html}</div> <div class="head-logo">{$unsafe_logo_html}</div>
<div class="head-items clearfix"> <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="/"><span><span>blog</span></span></a>
<a class="head-item" href="/projects/"><span><span>projects</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> <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 // TODO rewrite this fcking crap
function renderLogo($ctx, array $path_map = [], array $link_map = []): string { 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>'; $prompt_sign = '<span class="head-logo-dolsign">$</span>';
} else { } else {
$prompt_sign = '<span class="head-logo-dolsign is_root">#</span>'; $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; $last_pos = $pos + 1;
$close_tags++; $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++) { for ($i = count($path_parts)-1, $j = 0; $i >= 0; $i--, $j++) {
if (isset($path_map[$j])) { if (isset($path_map[$j])) {
@ -169,25 +236,16 @@ function renderLogo($ctx, array $path_map = [], array $link_map = []): string {
return $html; return $html;
} }
function jsLink(string $name, $version = null): string { function enterIcon() {
if ($version !== null) return <<<SVG
$name .= '?'.$version; <svg width="12" height="7" viewBox="0 0 9.6 5.172" xmlns="http://www.w3.org/2000/svg">
return '<script src="'.$name.'" type="text/javascript"></script>'; <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 { function renderMoonIcon($ctx) {
global $config; return <<<SVG
if ($config['is_dev']) { <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>
$bname = basename($name); SVG;
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;
} }