diff --git a/classes/handler/ArticlesHandler.php b/classes/handler/ArticlesHandler.php new file mode 100644 index 0000000..3d78979 --- /dev/null +++ b/classes/handler/ArticlesHandler.php @@ -0,0 +1,19 @@ +skin->title = \LangData::getInstance()['articles']; + return $this->skin->renderPage('main/articles', + posts: $posts); + } +} \ No newline at end of file diff --git a/classes/handler/IndexHandler.php b/classes/handler/IndexHandler.php index e470f4a..81bddf6 100644 --- a/classes/handler/IndexHandler.php +++ b/classes/handler/IndexHandler.php @@ -8,11 +8,16 @@ use posts; class IndexHandler extends \RequestHandler { public function get(): \Response { - $page = \pages::getPageByName('index-wgm9Fkl'); + global $config; + + $page = \pages::getPageByName($config['index_page_id']); $this->skin->title = $page->title; $this->skin->fixedTitle = true; - return $this->skin->renderPage('main/index', - unsafe_content: $page->getHtml($this->isRetina(), \themes::getUserTheme())); + return $this->skin->renderPage('main/page', + unsafe_html: $page->getHtml($this->isRetina(), \themes::getUserTheme()), + page_url: $page->getUrl(), + short_name: $page->shortName); } -} \ No newline at end of file +} + diff --git a/classes/handler/admin/IndexHandler.php b/classes/handler/admin/IndexHandler.php index e177edf..78531bd 100644 --- a/classes/handler/admin/IndexHandler.php +++ b/classes/handler/admin/IndexHandler.php @@ -7,6 +7,7 @@ use Response; class IndexHandler extends AdminRequestHandler { public function get(): Response { + $this->skin->title = 'Admin'; return $this->skin->renderPage('admin/index'); } diff --git a/classes/handler/admin/UploadDeleteHandler.php b/classes/handler/admin/UploadDeleteHandler.php index 63cb7e2..a0508ed 100644 --- a/classes/handler/admin/UploadDeleteHandler.php +++ b/classes/handler/admin/UploadDeleteHandler.php @@ -13,13 +13,13 @@ class UploadDeleteHandler extends AdminRequestHandler { $upload = \uploads::get($id); if (!$upload) - return new RedirectResponse('/uploads/?error='.urlencode('upload not found')); + return new RedirectResponse('/admin/uploads/?error='.urlencode('upload not found')); csrf::check('delupl'.$id); \uploads::delete($id); - return new RedirectResponse('/uploads/'); + return new RedirectResponse('/admin/uploads/'); } } \ No newline at end of file diff --git a/classes/handler/admin/UploadEditNoteHandler.php b/classes/handler/admin/UploadEditNoteHandler.php index 020aca3..fe7c360 100644 --- a/classes/handler/admin/UploadEditNoteHandler.php +++ b/classes/handler/admin/UploadEditNoteHandler.php @@ -12,14 +12,14 @@ class UploadEditNoteHandler extends AdminRequestHandler { $upload = \uploads::get($id); if (!$upload) - return new \RedirectResponse('/uploads/?error='.urlencode('upload not found')); + return new \RedirectResponse('/admin/uploads/?error='.urlencode('upload not found')); csrf::check('editupl'.$id); $note = $_POST['note'] ?? ''; $upload->setNote($note); - return new \RedirectResponse('/uploads/'); + return new \RedirectResponse('/admin/uploads/'); } } \ No newline at end of file diff --git a/classes/handler/admin/UploadsHandler.php b/classes/handler/admin/UploadsHandler.php index da6b015..2360c27 100644 --- a/classes/handler/admin/UploadsHandler.php +++ b/classes/handler/admin/UploadsHandler.php @@ -29,7 +29,7 @@ class UploadsHandler extends AdminRequestHandler { list($custom_name, $note) = $this->input('name, note'); if (!isset($_FILES['files'])) - return new RedirectResponse('/uploads/?error='.urlencode('no file')); + return new RedirectResponse('/admin/uploads/?error='.urlencode('no file')); $files = []; for ($i = 0; $i < count($_FILES['files']['name']); $i++) { @@ -49,14 +49,14 @@ class UploadsHandler extends AdminRequestHandler { foreach ($files as $f) { if ($f['error']) - return new RedirectResponse('/uploads/?error='.urlencode('error code '.$f['error'])); + return new RedirectResponse('/admin/uploads/?error='.urlencode('error code '.$f['error'])); if (!$f['size']) - return new RedirectResponse('/uploads/?error='.urlencode('received empty file')); + return new RedirectResponse('/admin/uploads/?error='.urlencode('received empty file')); $ext = extension($f['name']); if (!\uploads::isExtensionAllowed($ext)) - return new RedirectResponse('/uploads/?error='.urlencode('extension not allowed')); + return new RedirectResponse('/admin/uploads/?error='.urlencode('extension not allowed')); $upload_id = \uploads::add( $f['tmp_name'], @@ -64,10 +64,10 @@ class UploadsHandler extends AdminRequestHandler { $note); if (!$upload_id) - return new RedirectResponse('/uploads/?error='.urlencode('failed to create upload')); + return new RedirectResponse('/admin/uploads/?error='.urlencode('failed to create upload')); } - return new RedirectResponse('/uploads/'); + return new RedirectResponse('/admin/uploads/'); } } \ No newline at end of file diff --git a/classes/markup.php b/classes/markup.php index f6ddd0f..d3c8e68 100644 --- a/classes/markup.php +++ b/classes/markup.php @@ -33,7 +33,7 @@ class markup { global $config; $is_dark_theme = $user_theme === 'dark'; return preg_replace_callback( - '/('.preg_quote($config['uploads_host'], '/').'\/\w{8}\/)([ap])(\d+)x(\d+)(\.jpg)/', + '/(uploads\/\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'; diff --git a/classes/model/Upload.php b/classes/model/Upload.php index 782159e..a924326 100644 --- a/classes/model/Upload.php +++ b/classes/model/Upload.php @@ -1,7 +1,6 @@ randomId.'/'.$this->name; + return $config['uploads_path'].'/'.$this->randomId.'/'.$this->name; } public function getDirectPreviewUrl(int $w, int $h, bool $retina = false): string { @@ -44,11 +43,11 @@ class Upload extends Model } $prefix = $this->imageMayHaveAlphaChannel() ? 'a' : 'p'; - return 'https://'.$config['uploads_host'].'/'.$this->randomId.'/'.$prefix.$w.'x'.$h.'.jpg'; + return $config['uploads_path'].'/'.$this->randomId.'/'.$prefix.$w.'x'.$h.'.jpg'; } // TODO remove? - public function incrementDownloads() { + public function incrementDownloads(): void { $db = getDb(); $db->query("UPDATE uploads SET downloads=downloads+1 WHERE id=?", $this->id); $this->downloads++; @@ -70,7 +69,7 @@ class Upload extends Model return $md; } - public function setNote(string $note) { + public function setNote(string $note): void { $db = getDb(); $db->query("UPDATE uploads SET note=? WHERE id=?", $note, $this->id); } diff --git a/classes/uploads.php b/classes/uploads.php index 81a16f3..cd76d1e 100644 --- a/classes/uploads.php +++ b/classes/uploads.php @@ -82,7 +82,7 @@ class uploads { public static function getAll(): array { $db = getDb(); $q = $db->query("SELECT * FROM uploads ORDER BY id DESC"); - return array_map('\Upload', $db->fetchAll($q)); + return array_map('model\Upload::create_instance', $db->fetchAll($q)); } public static function get(int $id): ?Upload { diff --git a/config.php b/config.php index b1f1244..ff58284 100644 --- a/config.php +++ b/config.php @@ -18,10 +18,10 @@ return [ 'password_salt' => '12345', 'csrf_token' => '12345', 'uploads_dir' => '/home/user/files.example.com', - 'uploads_host' => 'files.example.com', 'dirs_mode' => 0775, 'files_mode' => 0664, 'group' => 33, // id -g www-data 'is_dev' => false, + 'index_page_id' => 'index-wgm9Fkl' ]; diff --git a/htdocs/index.php b/htdocs/index.php index da5850e..fa8b6cb 100644 --- a/htdocs/index.php +++ b/htdocs/index.php @@ -6,20 +6,20 @@ $r = (new Router()) // route handler input // ----- ------- ----- ->add('/', 'index') - ->add('([a-zA-Z0-9\-]+)/', 'auto name=$(1)') - + ->add('([a-zA-Z0-9\-]+)/', 'auto name=$(1)') ->add('feed.rss', 'RSS') + //->add('articles/', 'articles') + ->add('articles/write/', 'admin/post_add') + // admin ->add('admin/', 'admin/index') ->add('admin/{login,logout,log}/', 'admin/${1}') - - ->add('([a-zA-Z0-9\-]+)/{delete,edit}/', 'admin/auto_${1} short_name=$(1)') - ->add('([a-zA-Z0-9\-]+)/create/', 'admin/page_add short_name=$(1)') - //->add('write/', 'admin/post_add') + ->add('([a-zA-Z0-9\-]+)/{delete,edit}/', 'admin/auto_${1} short_name=$(1)') + ->add('([a-zA-Z0-9\-]+)/create/', 'admin/page_add short_name=$(1)') ->add('admin/markdown-preview.ajax', 'admin/markdown_preview') - ->add('uploads/', 'admin/uploads') - ->add('uploads/{edit_note,delete}/(\d+)/','admin/upload_${1} id=$(1)') + ->add('admin/uploads/', 'admin/uploads') + ->add('admin/uploads/{edit_note,delete}/(\d+)/', 'admin/upload_${1} id=$(1)') ; (new RequestDispatcher($r))->dispatch(); \ No newline at end of file diff --git a/htdocs/js/common/03-dom.js b/htdocs/js/common/03-dom.js index d05bcd0..31a53b3 100644 --- a/htdocs/js/common/03-dom.js +++ b/htdocs/js/common/03-dom.js @@ -85,7 +85,6 @@ function cancelEvent(evt) { return false; } - // // Cookies // diff --git a/htdocs/js/common/35-theme-switcher.js b/htdocs/js/common/35-theme-switcher.js index c612152..01cf854 100644 --- a/htdocs/js/common/35-theme-switcher.js +++ b/htdocs/js/common/35-theme-switcher.js @@ -129,11 +129,14 @@ var ThemeSwitcher = (function() { } /** - * @param {string} mode + * @param {string} selectedMode */ - function setLabel(mode) { - var labelEl = ge('theme-switcher-label'); - labelEl.innerHTML = escape(lang('theme_'+mode)); + function setIcon(selectedMode) { + document.body.setAttribute('data-theme', selectedMode); + for (var i = 0; i < modes.length; i++) { + var mode = modes[i]; + document.getElementById('moon_'+mode).style.display = mode === selectedMode ? 'block': 'none'; + } } return { @@ -171,7 +174,7 @@ var ThemeSwitcher = (function() { onSystemChange(window.matchMedia('(prefers-color-scheme: dark)').matches === true); } - setLabel(modes[currentModeIndex]); + setIcon(modes[currentModeIndex]); }, next: function(e) { @@ -198,7 +201,7 @@ var ThemeSwitcher = (function() { break; } - setLabel(modes[currentModeIndex]); + setIcon(modes[currentModeIndex]); setCookie('theme', modes[currentModeIndex]); return cancelEvent(e); diff --git a/htdocs/scss/app/head.scss b/htdocs/scss/app/head.scss index ebc6d35..59d2f08 100644 --- a/htdocs/scss/app/head.scss +++ b/htdocs/scss/app/head.scss @@ -60,7 +60,19 @@ a.head-item { font-size: $fs - 1px; display: inline-block; padding: 8px 12px; - //margin-right: -16px; + vertical-align: middle; + + &.is-theme-switcher, + &.is-settings { + padding: 8px; + } + &.is-settings { + font-size: 0; + > svg { + width: 18px; + height: 18px; + } + } &:hover { border-radius: 4px; @@ -68,31 +80,13 @@ a.head-item { text-decoration: none; } - > span { - position: relative; - - > span { - padding: 2px 0; - - &.moon-icon { - padding: 0; - position: absolute; - top: 0; - left: 0; - - > svg path { - fill: $fg; - } - } - } - } - - &.is-theme-switcher > span { - padding-left: 20px; - } - //&:last-child > span { // border-right: 0; // padding-right: 1px; //} -} \ No newline at end of file +} + +body a.head-item.is-theme-switcher svg path, +body a.head-item.is-settings svg path { + fill: $fg; +} diff --git a/lang/en.php b/lang/en.php index 557408a..c900cc1 100644 --- a/lang/en.php +++ b/lang/en.php @@ -8,10 +8,9 @@ return [ 'posts' => 'posts', 'all_posts' => 'all posts', - 'blog' => 'blog', + 'articles' => 'Articles', 'contacts' => 'contacts', 'email' => 'email', - 'projects' => 'projects', 'unknown_error' => 'Unknown error', 'error' => 'Error', 'write' => 'Write', @@ -22,9 +21,9 @@ return [ 'toc' => 'Table of Contents', // theme switcher - 'theme_auto' => 'auto', - 'theme_dark' => 'dark', - 'theme_light' => 'light', + //'theme_auto' => 'auto', + //'theme_dark' => 'dark', + //'theme_light' => 'light', // contacts 'contacts_email' => 'email', @@ -40,7 +39,6 @@ return [ 'blog_post_hidden' => 'Hidden', 'blog_tag_title' => 'Posts tagged with "%s"', 'blog_tag_not_found' => 'No posts found.', - 'blog_comments_text' => 'If you have any comments, contact me by email.', 'blog_write_form_preview_btn' => 'Preview', 'blog_write_form_submit_btn' => 'Submit', diff --git a/model/Upload.php b/model/Upload.php index 06b348b..c7205e6 100644 --- a/model/Upload.php +++ b/model/Upload.php @@ -24,8 +24,7 @@ class Upload extends Model { } public function getDirectUrl(): string { - global $config; - return 'https://'.$config['uploads_host'].'/'.$this->randomId.'/'.$this->name; + return '/upload/'.$this->randomId.'/'.$this->name; } public function getDirectPreviewUrl(int $w, int $h, bool $retina = false): string { @@ -39,7 +38,7 @@ class Upload extends Model { } $prefix = $this->imageMayHaveAlphaChannel() ? 'a' : 'p'; - return 'https://'.$config['uploads_host'].'/'.$this->randomId.'/'.$prefix.$w.'x'.$h.'.jpg'; + return $config['uploads_dir'].'/'.$this->randomId.'/'.$prefix.$w.'x'.$h.'.jpg'; } // TODO remove? diff --git a/skin/admin.phps b/skin/admin.phps index b2d9bb4..17c60a2 100644 --- a/skin/admin.phps +++ b/skin/admin.phps @@ -40,7 +40,8 @@ return [$html, $js]; function index($ctx) { return << - + + Uploads
Sign out HTML; @@ -55,7 +56,7 @@ return <<if_true($error, $ctx->formError, $error)}
-
+
@@ -107,8 +108,8 @@ return << {$ctx->if_true($note, '
'.$note.'
')} diff --git a/skin/base.phps b/skin/base.phps index 3b40c05..3b83bc0 100644 --- a/skin/base.phps +++ b/skin/base.phps @@ -192,8 +192,8 @@ $items = [ ['url' => '/about/', 'label' => 'about'] ]; if (\admin::isAdmin()) - $items[] = ['url' => '/admin/', 'label' => 'admin']; -$items[] = ['url' => 'javascript:void(0)', 'label' => $theme, 'label_id' => 'theme-switcher-label', 'theme_switcher' => true]; + $items[] = ['url' => '/admin/', 'label' => $ctx->renderSettingsIcon(), 'type' => 'settings']; +$items[] = ['url' => 'javascript:void(0)', 'label' => $ctx->renderMoonIcons(), 'type' => 'theme-switcher', 'type_opts' => $theme]; // here, items are rendered using for_each, so that there are no gaps (whitespaces) between tags @@ -209,7 +209,12 @@ return <<
- {$ctx->for_each($items, fn($item) => $ctx->renderHeaderItem($item['url'], $item['label'], $item['label_id'] ?? null, $item['theme_switcher'] ?? false))} + {$ctx->for_each($items, fn($item) => $ctx->renderHeaderItem( + $item['url'], + $item['label'], + $item['type'] ?? false, + $item['type_opts'] ?? null, + ))}
@@ -219,22 +224,47 @@ HTML; function renderHeaderItem(SkinContext $ctx, string $url, - string $label, - ?Stringable $label_id, - bool $is_theme_switcher): string { + ?Stringable $unsafe_label, + ?string $type, + ?string $type_opts): string { +$args = ''; +$class = ''; +switch ($type) { + case 'theme-switcher': + $args = ' onclick="return ThemeSwitcher.next(event)"'; + $class = ' is-theme-switcher '.$type_opts; + break; + case 'settings': + $class = ' is-settings'; + break; +} return <<if_true($is_theme_switcher, ' onclick="return ThemeSwitcher.next(event)"')}> - - {$ctx->if_true($is_theme_switcher, ''.$ctx->renderMoonIcon().'')} - if_true($label_id, ' id="'.$label_id.'"')}>{$label} - - +{$unsafe_label} HTML; } -function renderMoonIcon(SkinContext $ctx): string { +function renderMoonIcons(SkinContext $ctx): string { return << + + + + + + + + + + SVG; + } + +function renderSettingsIcon(SkinContext $ctx): string { +return << + + +SVG; + +} \ No newline at end of file diff --git a/skin/main.phps b/skin/main.phps index 5060a8f..57c4eb7 100644 --- a/skin/main.phps +++ b/skin/main.phps @@ -13,25 +13,24 @@ function index($ctx, $unsafe_content) { HTML; } +//function articles($ctx): string { +//return << +// {$ctx->lang('blog_no')} +// {$ctx->if_admin(''.$ctx->lang('write').'')} +// +//HTML; +//} -function indexEmtpy($ctx): string { -return << - {$ctx->lang('blog_no')} - {$ctx->if_admin(''.$ctx->lang('write').'')} - -HTML; -} - -function indexBlog($ctx, array $posts): string { +function articles($ctx, array $posts): string { return <<
- all posts + {$ctx->if_admin( ' - new - uploads + new + uploads ' )}
@@ -132,10 +131,6 @@ $html = <<if_true($unsafe_toc_html, $ctx->postToc, $unsafe_toc_html)} - -
- {$ctx->langRaw('blog_comments_text', $email, $urlencoded_reply_subject)} -
HTML; return [$html, markdownThemeChangeListener()];