blog: support ToC
This commit is contained in:
parent
917d2622aa
commit
eeb84c5be1
@ -9,6 +9,7 @@ class Skin {
|
||||
protected array $langKeys = [];
|
||||
protected array $options = [
|
||||
'full_width' => false,
|
||||
'wide' => false,
|
||||
'dynlogo_enabled' => true,
|
||||
'logo_path_map' => [],
|
||||
'logo_link_map' => [],
|
||||
|
@ -65,10 +65,14 @@ class Auto extends RequestHandler {
|
||||
|
||||
$s->title = $post->title;
|
||||
|
||||
if ($post->toc)
|
||||
$s->setOptions(['wide' => true]);
|
||||
|
||||
return $s->renderPage('main/post',
|
||||
title: $post->title,
|
||||
id: $post->id,
|
||||
unsafe_html: $post->getHtml($this->isRetina(), \themes::getUserTheme()),
|
||||
unsafe_toc_html: $post->getToc(),
|
||||
date: $post->getFullDate(),
|
||||
tags: $tags,
|
||||
visible: $post->visible,
|
||||
|
@ -39,6 +39,7 @@ abstract class AutoAddOrEdit extends AdminRequestHandler {
|
||||
string $text = '',
|
||||
?array $tags = null,
|
||||
bool $visible = false,
|
||||
bool $toc = false,
|
||||
string $short_name = '',
|
||||
?string $error_code = null,
|
||||
bool $saved = false,
|
||||
@ -53,6 +54,7 @@ abstract class AutoAddOrEdit extends AdminRequestHandler {
|
||||
text: $text,
|
||||
tags: $tags ? implode(', ', $tags) : '',
|
||||
visible: $visible,
|
||||
toc: $toc,
|
||||
saved: $saved,
|
||||
short_name: $short_name,
|
||||
error_code: $error_code
|
||||
|
@ -16,12 +16,13 @@ class AutoEdit extends AutoAddOrEdit {
|
||||
if ($post) {
|
||||
$tags = $post->getTags();
|
||||
return $this->_get_postEdit($post,
|
||||
tags: $post->getTags(),
|
||||
saved: $saved,
|
||||
title: $post->title,
|
||||
text: $post->md,
|
||||
tags: $post->getTags(),
|
||||
visible: $post->visible,
|
||||
toc: $post->toc,
|
||||
short_name: $post->shortName,
|
||||
saved: $saved,
|
||||
);
|
||||
}
|
||||
|
||||
@ -30,8 +31,8 @@ class AutoEdit extends AutoAddOrEdit {
|
||||
return $this->_get_pageEdit($page,
|
||||
title: $page->title,
|
||||
text: $page->md,
|
||||
visible: $page->visible,
|
||||
saved: $saved,
|
||||
visible: $page->visible,
|
||||
);
|
||||
}
|
||||
|
||||
@ -45,8 +46,8 @@ class AutoEdit extends AutoAddOrEdit {
|
||||
if ($post) {
|
||||
csrf::check('editpost'.$post->id);
|
||||
|
||||
list($text, $title, $tags, $visible, $short_name)
|
||||
= $this->input('text, title, tags, b:visible, new_short_name');
|
||||
list($text, $title, $tags, $visible, $toc, $short_name)
|
||||
= $this->input('text, title, tags, b:visible, b:toc, new_short_name');
|
||||
|
||||
$tags = posts::splitStringToTags($tags);
|
||||
$error_code = null;
|
||||
@ -63,10 +64,11 @@ class AutoEdit extends AutoAddOrEdit {
|
||||
|
||||
if ($error_code)
|
||||
$this->_get_postEdit($post,
|
||||
text: $text,
|
||||
title: $title,
|
||||
text: $text,
|
||||
tags: $tags,
|
||||
visible: $visible,
|
||||
toc: $toc,
|
||||
short_name: $short_name,
|
||||
error_code: $error_code
|
||||
);
|
||||
@ -75,6 +77,7 @@ class AutoEdit extends AutoAddOrEdit {
|
||||
'title' => $title,
|
||||
'md' => $text,
|
||||
'visible' => (int)$visible,
|
||||
'toc' => (int)$toc,
|
||||
'short_name' => $short_name
|
||||
]);
|
||||
$tag_ids = posts::getTagIds($tags);
|
||||
|
@ -40,8 +40,8 @@ class PageAdd extends AutoAddOrEdit {
|
||||
if ($error_code) {
|
||||
return $this->_get_pageAdd(
|
||||
name: $name,
|
||||
text: $text,
|
||||
title: $title,
|
||||
text: $text,
|
||||
error_code: $error_code
|
||||
);
|
||||
}
|
||||
@ -53,8 +53,8 @@ class PageAdd extends AutoAddOrEdit {
|
||||
])) {
|
||||
return $this->_get_pageAdd(
|
||||
name: $name,
|
||||
text: $text,
|
||||
title: $title,
|
||||
text: $text,
|
||||
error_code: 'db_err'
|
||||
);
|
||||
}
|
||||
|
@ -91,6 +91,74 @@
|
||||
margin-top: 3px;
|
||||
}
|
||||
|
||||
.blog-post-wrap2 {
|
||||
display: table;
|
||||
table-layout: fixed;
|
||||
border: none;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
.blog-post-wrap1 {
|
||||
display: table-row;
|
||||
}
|
||||
.blog-post {
|
||||
display: table-cell;
|
||||
vertical-align: top;
|
||||
}
|
||||
.blog-post-toc {
|
||||
display: table-cell;
|
||||
vertical-align: top;
|
||||
font-size: $fs - 2px;
|
||||
|
||||
&-wrap {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
padding: 10px 0 0 20px;
|
||||
overflow-y: auto;
|
||||
max-height: 100vh;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
&-inner-wrap {
|
||||
border-left: 1px $border-color solid;
|
||||
padding-left: 20px;
|
||||
margin-bottom: 10px;
|
||||
|
||||
ul {
|
||||
list-style-type: none;
|
||||
margin: 5px 0;
|
||||
padding-left: 18px;
|
||||
}
|
||||
> ul {
|
||||
padding-left: 0 !important;
|
||||
}
|
||||
|
||||
li {
|
||||
margin: 2px 0;
|
||||
line-height: 150%;
|
||||
> a {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-title {
|
||||
font-weight: bold;
|
||||
padding: 6px 0;
|
||||
}
|
||||
}
|
||||
body.wide .blog-post {
|
||||
width: $base_width;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1150px) {
|
||||
.blog-post-toc {
|
||||
display: none;
|
||||
}
|
||||
body.wide .blog-post {
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.blog-post-title {
|
||||
margin: 0 0 16px;
|
||||
}
|
||||
@ -174,7 +242,7 @@
|
||||
}
|
||||
|
||||
blockquote {
|
||||
border-left: 3px #e0e0e0 solid;
|
||||
border-left: 3px $border-color solid;
|
||||
margin-left: 0;
|
||||
padding: 5px 0 5px 12px;
|
||||
color: $grey;
|
||||
|
@ -34,6 +34,11 @@ body.full-width .base-width {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
body.wide .base-width {
|
||||
max-width: $wide_width;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
input[type="text"],
|
||||
input[type="password"],
|
||||
|
@ -46,3 +46,6 @@ a.head-item:last-child > span {
|
||||
.blog-list.withtags {
|
||||
margin-right: 0;
|
||||
}
|
||||
//.blog-post-toc {
|
||||
// display: none;
|
||||
//}
|
@ -4,6 +4,7 @@ $ff: -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica, Arial, sans-serif;
|
||||
$ffMono: SFMono-Regular, Consolas, Liberation Mono, Menlo, Courier, monospace;
|
||||
|
||||
$base-width: 900px;
|
||||
$wide_width: 1240px;
|
||||
$side-padding: 25px;
|
||||
$base-padding: 18px;
|
||||
$footer-height: 64px;
|
||||
|
@ -19,6 +19,7 @@ return [
|
||||
'edit' => 'edit',
|
||||
'delete' => 'delete',
|
||||
'info_saved' => 'Information saved.',
|
||||
'toc' => 'Table of Contents',
|
||||
|
||||
// theme switcher
|
||||
'theme_auto' => 'auto',
|
||||
@ -53,6 +54,7 @@ return [
|
||||
'blog_write_form_enter_title' => 'Enter title..',
|
||||
'blog_write_form_tags' => 'Tags',
|
||||
'blog_write_form_visible' => 'Visible',
|
||||
'blog_write_form_toc' => 'ToC',
|
||||
'blog_write_form_short_name' => 'Short name',
|
||||
'blog_write_form_toggle_wrap' => 'Toggle wrap',
|
||||
'blog_write_form_options' => 'Options',
|
||||
|
@ -3,13 +3,18 @@
|
||||
class MyParsedown extends ParsedownExtended {
|
||||
|
||||
public function __construct(
|
||||
?array $opts = null,
|
||||
protected bool $useImagePreviews = false
|
||||
) {
|
||||
parent::__construct([
|
||||
$parsedown_opts = [
|
||||
'tables' => [
|
||||
'tablespan' => true
|
||||
]
|
||||
]);
|
||||
];
|
||||
if (!is_null($opts)) {
|
||||
$parsedown_opts = array_merge($parsedown_opts, $opts);
|
||||
}
|
||||
parent::__construct($parsedown_opts);
|
||||
|
||||
$this->InlineTypes['{'][] = 'FileAttach';
|
||||
$this->InlineTypes['{'][] = 'Image';
|
||||
|
@ -7,6 +7,19 @@ class markup {
|
||||
return $pd->text($md);
|
||||
}
|
||||
|
||||
public static function toc(string $md): string {
|
||||
$pd = new MyParsedown([
|
||||
'toc' => [
|
||||
'lowercase' => true,
|
||||
'transliterate' => true,
|
||||
'urlencode' => false,
|
||||
'headings' => ['h1', 'h2', 'h3']
|
||||
]
|
||||
]);
|
||||
$pd->text($md);
|
||||
return $pd->contentsList();
|
||||
}
|
||||
|
||||
public static function htmlToText(string $html): string {
|
||||
$text = html_entity_decode(strip_tags($html));
|
||||
$lines = explode("\n", $text);
|
||||
|
@ -8,10 +8,12 @@ class Post extends Model {
|
||||
public string $title;
|
||||
public string $md;
|
||||
public string $html;
|
||||
public string $tocHtml;
|
||||
public string $text;
|
||||
public int $ts;
|
||||
public int $updateTs;
|
||||
public bool $visible;
|
||||
public bool $toc;
|
||||
public string $shortName;
|
||||
|
||||
public function edit(array $data) {
|
||||
@ -26,6 +28,10 @@ class Post extends Model {
|
||||
$data['text'] = markup::htmlToText($data['html']);
|
||||
}
|
||||
|
||||
if ((isset($data['toc']) && $data['toc']) || $this->toc) {
|
||||
$data['toc_html'] = markup::toc($data['md']);
|
||||
}
|
||||
|
||||
parent::edit($data);
|
||||
$this->updateImagePreviews();
|
||||
}
|
||||
@ -87,6 +93,10 @@ class Post extends Model {
|
||||
return $html;
|
||||
}
|
||||
|
||||
public function getToc(): ?string {
|
||||
return $this->toc ? $this->tocHtml : null;
|
||||
}
|
||||
|
||||
public function isUpdated(): bool {
|
||||
return $this->updateTs && $this->updateTs != $this->ts;
|
||||
}
|
||||
|
@ -43,7 +43,9 @@ CREATE TABLE `posts` (
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `short_name` (`short_name`),
|
||||
KEY ` visible_ts_idx` (`visible`,`ts`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=66 DEFAULT CHARSET=utf8;
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
ALTER TABLE `posts` ADD `toc` TINYINT(1) NOT NULL DEFAULT '0' AFTER `short_name`;
|
||||
ALTER TABLE `posts` ADD `toc_html` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' AFTER `toc`;
|
||||
|
||||
CREATE TABLE `posts_tags` (
|
||||
`id` int(11) NOT NULL,
|
||||
|
@ -129,6 +129,7 @@ function postForm($ctx,
|
||||
$error_code = null,
|
||||
?bool $saved = null,
|
||||
?bool $visible = null,
|
||||
?bool $toc = null,
|
||||
string|Stringable|null $post_url = null,
|
||||
?int $post_id = null): array {
|
||||
$form_url = !$is_edit ? '/write/' : $post_url.'edit/';
|
||||
@ -173,6 +174,7 @@ $html = <<<HTML
|
||||
<div class="form-field-label">{$ctx->lang('blog_write_form_options')}</div>
|
||||
<div class="form-field">
|
||||
<label for="visible_cb"><input type="checkbox" id="visible_cb" name="visible"{$ctx->if_true($visible, ' checked="checked"')}> {$ctx->lang('blog_write_form_visible')}</label>
|
||||
<label for="toc_cb"><input type="checkbox" id="toc_cb" name="toc"{$ctx->if_true($toc, ' checked="checked"')}> {$ctx->lang('blog_write_form_toc')}</label>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
|
@ -13,6 +13,12 @@ $app_config = json_encode([
|
||||
'cookieHost' => $config['cookie_host'],
|
||||
]);
|
||||
|
||||
$body_class = [];
|
||||
if ($opts['full_width'])
|
||||
$body_class = 'full-width';
|
||||
else if ($opts['wide'])
|
||||
$body_class = 'wide';
|
||||
|
||||
return <<<HTML
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
@ -26,7 +32,7 @@ return <<<HTML
|
||||
{$ctx->renderMeta($meta)}
|
||||
{$ctx->renderStatic($static, $theme)}
|
||||
</head>
|
||||
<body{$ctx->if_true($opts['full_width'], ' class="full-width"')}>
|
||||
<body{$ctx->if_true($body_class, ' class="'.$body_class.'"')}>
|
||||
{$ctx->renderHeader($theme, renderLogo($ctx, $opts['logo_path_map'], $opts['logo_link_map']))}
|
||||
<div class="page-content base-width">
|
||||
<div class="page-content-inner">{$unsafe_body}</div>
|
||||
|
@ -144,9 +144,11 @@ HTML;
|
||||
// post page
|
||||
// ---------
|
||||
|
||||
function post($ctx, $id, $title, $unsafe_html, $date, $visible, $url, $tags, $email, $urlencoded_reply_subject) {
|
||||
function post($ctx, $id, $title, $unsafe_html, $unsafe_toc_html, $date, $visible, $url, $tags, $email, $urlencoded_reply_subject) {
|
||||
$html = <<<HTML
|
||||
<div class="blog-post">
|
||||
<div class="blog-post-wrap2">
|
||||
<div class="blog-post-wrap1">
|
||||
<div class="blog-post">
|
||||
<div class="blog-post-title">
|
||||
<h1>{$title}</h1>
|
||||
<div class="blog-post-date">
|
||||
@ -159,7 +161,11 @@ $html = <<<HTML
|
||||
</div>
|
||||
</div>
|
||||
<div class="blog-post-text">{$unsafe_html}</div>
|
||||
</div>
|
||||
{$ctx->if_true($unsafe_toc_html, $ctx->postToc, $unsafe_toc_html)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="blog-post-comments">
|
||||
{$ctx->langRaw('blog_comments_text', $email, $urlencoded_reply_subject)}
|
||||
</div>
|
||||
@ -168,6 +174,20 @@ HTML;
|
||||
return [$html, markdownThemeChangeListener()];
|
||||
}
|
||||
|
||||
function postToc($ctx, $unsafe_toc_html) {
|
||||
return <<<HTML
|
||||
<div class="blog-post-toc">
|
||||
<div class="blog-post-toc-wrap">
|
||||
<div class="blog-post-toc-inner-wrap">
|
||||
<div class="blog-post-toc-title">{$ctx->lang('toc')}</div>
|
||||
{$unsafe_toc_html}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
HTML;
|
||||
|
||||
}
|
||||
|
||||
function postAdminLinks($ctx, $url, $id) {
|
||||
return <<<HTML
|
||||
<a href="{$url}edit/">{$ctx->lang('edit')}</a>
|
||||
|
Loading…
x
Reference in New Issue
Block a user