aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEvgeny Zinoviev <me@ch1p.io>2022-07-11 15:01:02 +0300
committerEvgeny Zinoviev <me@ch1p.io>2022-07-11 15:01:02 +0300
commitc2f382aba86aaebb9806ff1b43c1af69992e9a10 (patch)
treeaee205721fd675926c5e7163eec0dddc448f7813
parent24982a48f570b89e537850dda4a4d1ac33ea919f (diff)
support dark mode for images with alpha channel
-rwxr-xr-xcli_util.php8
-rw-r--r--engine/Skin.php4
-rw-r--r--engine/themes.php38
-rw-r--r--handler/Auto.php4
-rw-r--r--htdocs/js/common/35-theme-switcher.js21
-rw-r--r--lib/MyParsedown.php1
-rw-r--r--lib/markup.php11
-rw-r--r--lib/posts.php3
-rw-r--r--model/Page.php5
-rw-r--r--model/Post.php18
-rw-r--r--model/Upload.php59
-rw-r--r--skin/admin.skin.php8
-rw-r--r--skin/main.skin.php33
-rw-r--r--skin/markdown.skin.php6
14 files changed, 164 insertions, 55 deletions
diff --git a/cli_util.php b/cli_util.php
index 97885ff..0c42bb5 100755
--- a/cli_util.php
+++ b/cli_util.php
@@ -57,6 +57,14 @@ function posts_html(): void {
}
}
+function posts_images(): void {
+ $kw = ['include_hidden' => true];
+ $posts = posts::getPosts(0, posts::getPostsCount(...$kw), ...$kw);
+ foreach ($posts as $p) {
+ $p->updateImagePreviews(true);
+ }
+}
+
function pages_html(): void {
$pages = pages::getAll();
foreach ($pages as $p) {
diff --git a/engine/Skin.php b/engine/Skin.php
index 917eef7..a8924d4 100644
--- a/engine/Skin.php
+++ b/engine/Skin.php
@@ -23,8 +23,8 @@ class Skin {
else
$js = null;
- $theme = ($_COOKIE['theme'] ?? 'auto');
- if (!in_array($theme, ['auto', 'dark', 'light']))
+ $theme = themes::getUserTheme();
+ if ($theme != 'auto' && !themes::themeExists($theme))
$theme = 'auto';
$layout_ctx = new SkinContext('\\skin\\base');
diff --git a/engine/themes.php b/engine/themes.php
new file mode 100644
index 0000000..839377f
--- /dev/null
+++ b/engine/themes.php
@@ -0,0 +1,38 @@
+<?php
+
+class themes {
+
+ public static array $Themes = [
+ 'dark' => [
+ 'bg' => 0x222222,
+ // 'alpha' => 0x303132,
+ 'alpha' => 0x222222,
+ ],
+ 'light' => [
+ 'bg' => 0xffffff,
+ // 'alpha' => 0xf2f2f2,
+ 'alpha' => 0xffffff,
+ ]
+ ];
+
+ public static function getThemes(): array {
+ return array_keys(self::$Themes);
+ }
+
+ public static function themeExists(string $name): bool {
+ return array_key_exists($name, self::$Themes);
+ }
+
+ public static function getThemeAlphaColorAsRGB(string $name): array {
+ $color = self::$Themes[$name]['alpha'];
+ $r = ($color >> 16) & 0xff;
+ $g = ($color >> 8) & 0xff;
+ $b = $color & 0xff;
+ return [$r, $g, $b];
+ }
+
+ public static function getUserTheme(): string {
+ return ($_COOKIE['theme'] ?? 'auto');
+ }
+
+} \ No newline at end of file
diff --git a/handler/Auto.php b/handler/Auto.php
index 9903993..361cc17 100644
--- a/handler/Auto.php
+++ b/handler/Auto.php
@@ -68,7 +68,7 @@ class Auto extends RequestHandler {
return $s->renderPage('main/post',
title: $post->title,
id: $post->id,
- unsafe_html: $post->getHtml($this->isRetina()),
+ unsafe_html: $post->getHtml($this->isRetina(), \themes::getUserTheme()),
date: $post->getFullDate(),
tags: $tags,
visible: $post->visible,
@@ -98,7 +98,7 @@ class Auto extends RequestHandler {
$this->skin->title = $page ? $page->title : '???';
return $this->skin->renderPage('main/page',
- unsafe_html: $page->getHtml($this->isRetina()),
+ unsafe_html: $page->getHtml($this->isRetina(), \themes::getUserTheme()),
page_url: $page->getUrl(),
short_name: $page->shortName);
}
diff --git a/htdocs/js/common/35-theme-switcher.js b/htdocs/js/common/35-theme-switcher.js
index e716e4b..c612152 100644
--- a/htdocs/js/common/35-theme-switcher.js
+++ b/htdocs/js/common/35-theme-switcher.js
@@ -15,6 +15,11 @@ var ThemeSwitcher = (function() {
var systemState = null;
/**
+ * @type {function[]}
+ */
+ var changeListeners = [];
+
+ /**
* @returns {boolean}
*/
function isSystemModeSupported() {
@@ -71,6 +76,13 @@ var ThemeSwitcher = (function() {
var onDone = function() {
window.requestAnimationFrame(function() {
removeClass(document.body, 'theme-changing');
+ changeListeners.forEach(function(f) {
+ try {
+ f(dark)
+ } catch (e) {
+ console.error('[ThemeSwitcher->changeTheme->onDone] error while calling user callback:', e)
+ }
+ })
})
};
@@ -171,7 +183,7 @@ var ThemeSwitcher = (function() {
currentModeIndex = (currentModeIndex + 1) % modes.length;
switch (modes[currentModeIndex]) {
case 'auto':
- if (systemState !== null)
+ if (systemState !== null && systemState !== isDarkModeApplied())
changeTheme(systemState);
break;
@@ -190,6 +202,13 @@ var ThemeSwitcher = (function() {
setCookie('theme', modes[currentModeIndex]);
return cancelEvent(e);
+ },
+
+ /**
+ * @param {function} f
+ */
+ addOnChangeListener: function(f) {
+ changeListeners.push(f);
}
};
})(); \ No newline at end of file
diff --git a/lib/MyParsedown.php b/lib/MyParsedown.php
index c2c0112..11e86d6 100644
--- a/lib/MyParsedown.php
+++ b/lib/MyParsedown.php
@@ -99,6 +99,7 @@ class MyParsedown extends ParsedownHighlight {
nolabel: $opts['nolabel'],
align: $opts['align'],
padding_top: round($h / $w * 100, 4),
+ may_have_alpha: $image->imageMayHaveAlphaChannel(),
url: $image_url,
direct_url: $image->getDirectUrl(),
diff --git a/lib/markup.php b/lib/markup.php
index 52ccf24..2f25c6c 100644
--- a/lib/markup.php
+++ b/lib/markup.php
@@ -16,12 +16,15 @@ class markup {
return $text;
}
- public static function htmlRetinaFix(string $html): string {
+ public static function htmlImagesFix(string $html, bool $is_retina, string $user_theme): string {
global $config;
+ $is_dark_theme = $user_theme === 'dark';
return preg_replace_callback(
- '/('.preg_quote($config['uploads_host'], '/').'\/\w{8}\/p)(\d+)x(\d+)(\.jpg)/',
- function($match) {
- return $match[1].(intval($match[2])*2).'x'.(intval($match[3])*2).$match[4];
+ '/('.preg_quote($config['uploads_host'], '/').'\/\w{8}\/)([ap])(\d+)x(\d+)(\.jpg)/',
+ function($match) use ($is_retina, $is_dark_theme) {
+ $mult = $is_retina ? 2 : 1;
+ $is_alpha = $match[2] == 'a';
+ return $match[1].$match[2].(intval($match[3])*$mult).'x'.(intval($match[4])*$mult).($is_alpha && $is_dark_theme ? '_dark' : '').$match[5];
},
$html
);
diff --git a/lib/posts.php b/lib/posts.php
index bf8d149..1537749 100644
--- a/lib/posts.php
+++ b/lib/posts.php
@@ -23,6 +23,9 @@ class posts {
return (int)$db->result($db->query($sql, $tag_id));
}
+ /**
+ * @return Post[]
+ */
public static function getPosts(int $offset = 0, int $count = -1, bool $include_hidden = false): array {
$db = getDb();
$sql = "SELECT * FROM posts";
diff --git a/model/Page.php b/model/Page.php
index 6711a2c..f516836 100644
--- a/model/Page.php
+++ b/model/Page.php
@@ -24,10 +24,9 @@ class Page extends Model {
return $this->updateTs && $this->updateTs != $this->ts;
}
- public function getHtml(bool $retina): string {
+ public function getHtml(bool $is_retina, string $user_theme): string {
$html = $this->html;
- if ($retina)
- $html = markup::htmlRetinaFix($html);
+ $html = markup::htmlImagesFix($html, $is_retina, $user_theme);
return $html;
}
diff --git a/model/Post.php b/model/Post.php
index 10a396b..b0360ac 100644
--- a/model/Post.php
+++ b/model/Post.php
@@ -22,8 +22,8 @@ class Post extends Model {
$data['update_ts'] = $cur_ts;
if ($data['md'] != $this->md) {
- $data['html'] = \markup::markdownToHtml($data['md']);
- $data['text'] = \markup::htmlToText($data['html']);
+ $data['html'] = markup::markdownToHtml($data['md']);
+ $data['text'] = markup::htmlToText($data['html']);
}
parent::edit($data);
@@ -31,15 +31,15 @@ class Post extends Model {
}
public function updateHtml() {
- $html = \markup::markdownToHtml($this->md);
+ $html = markup::markdownToHtml($this->md);
$this->html = $html;
getDb()->query("UPDATE posts SET html=? WHERE id=?", $html, $this->id);
}
public function updateText() {
- $html = \markup::markdownToHtml($this->md);
- $text = \markup::htmlToText($html);
+ $html = markup::markdownToHtml($this->md);
+ $text = markup::htmlToText($html);
$this->text = $text;
getDb()->query("UPDATE posts SET text=? WHERE id=?", $text, $this->id);
@@ -81,10 +81,9 @@ class Post extends Model {
return date('j F Y', $this->updateTs);
}
- public function getHtml(bool $retina): string {
+ public function getHtml(bool $is_retina, string $theme): string {
$html = $this->html;
- if ($retina)
- $html = markup::htmlRetinaFix($html);
+ $html = markup::htmlImagesFix($html, $is_retina, $theme);
return $html;
}
@@ -173,9 +172,8 @@ class Post extends Model {
foreach ($images[$u->randomId] as $s) {
list($w, $h) = $s;
list($w, $h) = $u->getImagePreviewSize($w, $h);
- if ($u->createImagePreview($w, $h, $update)) {
+ if ($u->createImagePreview($w, $h, $update, $u->imageMayHaveAlphaChannel()))
$images_affected++;
- }
}
}
diff --git a/model/Upload.php b/model/Upload.php
index 441a14d..06b348b 100644
--- a/model/Upload.php
+++ b/model/Upload.php
@@ -38,7 +38,8 @@ class Upload extends Model {
$h *= 2;
}
- return 'https://'.$config['uploads_host'].'/'.$this->randomId.'/p'.$w.'x'.$h.'.jpg';
+ $prefix = $this->imageMayHaveAlphaChannel() ? 'a' : 'p';
+ return 'https://'.$config['uploads_host'].'/'.$this->randomId.'/'.$prefix.$w.'x'.$h.'.jpg';
}
// TODO remove?
@@ -73,6 +74,12 @@ class Upload extends Model {
return in_array(extension($this->name), self::$ImageExtensions);
}
+ // assume all png images have alpha channel
+ // i know this is wrong, but anyway
+ public function imageMayHaveAlphaChannel(): bool {
+ return strtolower(extension($this->name)) == 'png';
+ }
+
public function isVideo(): bool {
return in_array(extension($this->name), self::$VideoExtensions);
}
@@ -94,36 +101,40 @@ class Upload extends Model {
return [$w, $h];
}
- /**
- * @param ?int $w
- * @param ?int $h
- * @param bool $update Whether to proceed if preview already exists
- * @return bool
- */
- public function createImagePreview(?int $w = null, ?int $h = null, bool $update = false): bool {
+ public function createImagePreview(?int $w = null,
+ ?int $h = null,
+ bool $force_update = false,
+ bool $may_have_alpha = false): bool {
global $config;
$orig = $config['uploads_dir'].'/'.$this->randomId.'/'.$this->name;
$updated = false;
- for ($mult = 1; $mult <= 2; $mult++) {
- $dw = $w * $mult;
- $dh = $h * $mult;
- $dst = $config['uploads_dir'].'/'.$this->randomId.'/p'.$dw.'x'.$dh.'.jpg';
+ foreach (themes::getThemes() as $theme) {
+ if (!$may_have_alpha && $theme == 'dark')
+ continue;
- if (file_exists($dst)) {
- if (!$update)
- continue;
- unlink($dst);
- }
+ for ($mult = 1; $mult <= 2; $mult++) {
+ $dw = $w * $mult;
+ $dh = $h * $mult;
+
+ $prefix = $may_have_alpha ? 'a' : 'p';
+ $dst = $config['uploads_dir'].'/'.$this->randomId.'/'.$prefix.$dw.'x'.$dh.($theme == 'dark' ? '_dark' : '').'.jpg';
- $img = imageopen($orig);
- imageresize($img, $dw, $dh, [255, 255, 255]);
- imagejpeg($img, $dst, $mult == 1 ? 93 : 67);
- imagedestroy($img);
+ if (file_exists($dst)) {
+ if (!$force_update)
+ continue;
+ unlink($dst);
+ }
- setperm($dst);
- $updated = true;
+ $img = imageopen($orig);
+ imageresize($img, $dw, $dh, themes::getThemeAlphaColorAsRGB($theme));
+ imagejpeg($img, $dst, $mult == 1 ? 93 : 67);
+ imagedestroy($img);
+
+ setperm($dst);
+ $updated = true;
+ }
}
return $updated;
@@ -138,7 +149,7 @@ class Upload extends Model {
$files = scandir($dir);
$deleted = 0;
foreach ($files as $f) {
- if (preg_match('/^p(\d+)x(\d+)\.jpg$/', $f)) {
+ if (preg_match('/^[ap](\d+)x(\d+)(?:_dark)?\.jpg$/', $f)) {
if (is_file($dir.'/'.$f))
unlink($dir.'/'.$f);
else
diff --git a/skin/admin.skin.php b/skin/admin.skin.php
index f03d7ce..5619dd0 100644
--- a/skin/admin.skin.php
+++ b/skin/admin.skin.php
@@ -26,9 +26,9 @@ $html = <<<HTML
</form>
HTML;
-$js = <<<JAVASCRIPT
+$js = <<<JS
ge('as_password').focus();
-JAVASCRIPT;
+JS;
return [$html, $js];
}
@@ -264,9 +264,9 @@ $html = <<<HTML
HTML;
$js_params = json_encode(['pages' => true, 'edit' => $is_edit]);
-$js = <<<JAVASCRIPT
+$js = <<<JS
AdminWriteForm.init({$js_params});
-JAVASCRIPT;
+JS;
return [$html, $js];
}
diff --git a/skin/main.skin.php b/skin/main.skin.php
index 5236bf0..552c61f 100644
--- a/skin/main.skin.php
+++ b/skin/main.skin.php
@@ -116,12 +116,14 @@ HTML;
// --------
function page($ctx, $page_url, $short_name, $unsafe_html) {
-return <<<HTML
+$html = <<<HTML
<div class="page">
{$ctx->if_admin($ctx->pageAdminLinks, $page_url, $short_name)}
<div class="blog-post-text">{$unsafe_html}</div>
</div>
HTML;
+
+return [$html, markdownThemeChangeListener()];
}
function pageAdminLinks($ctx, $url, $short_name) {
@@ -139,7 +141,7 @@ HTML;
// ---------
function post($ctx, $id, $title, $unsafe_html, $date, $visible, $url, $tags, $email, $urlencoded_reply_subject) {
-return <<<HTML
+$html = <<<HTML
<div class="blog-post">
<div class="blog-post-title">
<h1>{$title}</h1>
@@ -158,6 +160,8 @@ return <<<HTML
{$ctx->langRaw('blog_comments_text', $email, $urlencoded_reply_subject)}
</div>
HTML;
+
+return [$html, markdownThemeChangeListener()];
}
function postAdminLinks($ctx, $url, $id) {
@@ -171,7 +175,32 @@ function postTag($ctx, $url, $name) {
return <<<HTML
<a href="{$url}"><span>#</span>{$name}</a>
HTML;
+}
+function markdownThemeChangeListener() {
+return <<<JS
+ThemeSwitcher.addOnChangeListener(function(isDark) {
+ var nodes = document.querySelectorAll('.md-image-wrap');
+ for (var i = 0; i < nodes.length; i++) {
+ var node = nodes[i];
+ var alpha = parseInt(node.getAttribute('data-alpha'), 10);
+ if (!alpha)
+ continue;
+ var div = node.querySelector('a > div');
+ if (!div) {
+ console.warn('could not found a>div on this node:', node);
+ continue;
+ }
+ var style = div.getAttribute('style');
+ if (isDark) {
+ style = style.replace(/(a[\d]+x[\d]+)\.jpg/, '$1_dark.jpg');
+ } else {
+ style = style.replace(/(a[\d]+x[\d]+)_dark\.jpg/, '$1.jpg');
+ }
+ div.setAttribute('style', style);
+ }
+});
+JS;
}
diff --git a/skin/markdown.skin.php b/skin/markdown.skin.php
index 02d3a0f..58801f5 100644
--- a/skin/markdown.skin.php
+++ b/skin/markdown.skin.php
@@ -14,14 +14,14 @@ HTML;
function image($ctx,
// options
- $align, $nolabel, $w, $padding_top,
+ $align, $nolabel, $w, $padding_top, $may_have_alpha,
// image data
$direct_url, $url, $note) {
return <<<HTML
<div class="md-image align-{$align}">
- <div class="md-image-wrap">
+ <div class="md-image-wrap" data-alpha="{$ctx->if_then_else($may_have_alpha, '1', '0')}">
<a href="{$direct_url}">
- <div style="background: #f2f2f2 url('{$url}') no-repeat; background-size: contain; width: {$w}px; padding-top: {$padding_top}%;"></div>
+ <div style="background: url('{$url}') no-repeat; background-size: contain; width: {$w}px; padding-top: {$padding_top}%;"></div>
</a>
{$ctx->if_true(
$note != '' && !$nolabel,