30 Commits

Author SHA1 Message Date
joyqi
2fe1c0f26e fix request query parsing 2024-01-25 18:43:41 +08:00
joyqi
25cc1ff3dc fix autosave effect 2024-01-24 18:56:01 +08:00
joyqi
bd6a6a0e0e attach timestamp to image url 2024-01-24 17:54:56 +08:00
Lu Fei
d1e961af90 Upgrade actions/checkout to v4 (#1718) 2024-01-24 02:18:19 +08:00
joyqi
e3bdef645d fix: remove unnecessary DOCTYPE tags (#1715) 2024-01-23 18:30:17 +08:00
joyqi
de53b64880 fix: remove special chars (#1716) 2024-01-23 18:29:59 +08:00
joyqi
d0b62eabe9 fix: xmlrpc message args (#1714) 2024-01-22 14:36:10 +08:00
Fen
99ffd36648 Fix/classic22 (#1708)
* fix: use prefers-color-scheme control dark mode

* update color
2024-01-15 10:56:47 +08:00
电脑星人
4028d7d160 fix: getHeader for Content-Type & Content-Length (#1703)
* fix: Request::getHeader for Content-Type & Content-Length

* fix: get content-type through native api

---------

Co-authored-by: joyqi <joyqi@users.noreply.github.com>
2024-01-13 22:11:38 +08:00
joyqi
a30a6c122d fix: choose the correct key when setting widget value (#1702) 2024-01-10 15:37:05 +08:00
Fen
0d28025bf4 update issue template 2024-01-10 12:58:20 +08:00
Fen
8b75782619 update issue template (#1700) 2024-01-10 12:45:24 +08:00
Fen
c5ab9295f0 Fix/classic 22 (#1698)
* style: move customize color to theme.css
2024-01-09 16:33:40 +08:00
Lu Fei
c816efa26e fix trim error (#1697) 2024-01-09 14:56:25 +08:00
joyqi
7115a30301 fix attachment handle (#1696)
* fix #1694

* fix array dump

* add toColumn method
2024-01-09 14:31:15 +08:00
LibXZR
db5d8694c4 Fix unable to show attachment parent in manage-medias (#1693) 2024-01-08 22:56:39 +08:00
Fen
2051c040ec Fix/classic 22 (#1690)
* fix: auto dark mode

* improve: pagenav style, theme color

* improve: color schema name

* fix: icon color

* improve: page navigation style
2024-01-07 13:47:59 +08:00
joyqi
13282b5b84 fix undefined array key (#1688)
* fix undefined array key

* fix: change security email
2024-01-07 11:38:39 +08:00
Fen
af281422d3 update theme classic-22 color schema (#1689)
* fix: auto dark mode

* improve: pagenav style, theme color

* improve: color schema name

* fix: icon color
2024-01-07 00:39:10 +08:00
LibXZR
68026e0fbc Fix unable to preview revisions (#1687)
* Fix unable to preview revisions

* Improve revision preview fix

Co-authored-by: joyqi <joyqi@users.noreply.github.com>

---------

Co-authored-by: joyqi <joyqi@users.noreply.github.com>
2024-01-06 23:33:11 +08:00
joyqi
a9fa990124 replace str compare (#1685) 2024-01-06 12:20:41 +08:00
Lu Fei
9635a7a0ba Optimize applySlug (#1684)
* Optimize applySlug

* optimize code

* fix: more rigorous judgment

* improve slug generating

---------

Co-authored-by: joyqi <joyqi@segmentfault.com>
2024-01-05 15:55:34 +08:00
Lu Fei
9396eef2f9 Remove useless code (#1683) 2024-01-05 11:27:13 +08:00
Fen
cec6b9c62b update theme classic-22 (#1682)
* Add feed widget

* add feed render

* Add CommentPage widget

* New theme (#1390)

* 调整忽略目录

* add theme

* fix theme scss build

Co-authored-by: fen <f3nb0x@gmail.com>

* s/is_writeable/is_writable/g

* New upgrade method

* merge new fixes from master

* add pgsql ssl mode support (ref #1600) (#1623)

* Feat/code refactor (#1626)

* remove all magic methods, add type for class properties

* refactor codes

* fix all

* refactor code

* fix type

* fix all

* fix request is method

* fix all

* fix router

* fix get page

* fix 1.3.0 upgrade

* [feat] support high resolution avatar

* fix types in i18n component

* Implement Ctrl+S or Command+S for save draft (#1628)

* Implement Ctrl+S or Command+S for save draft

* rename

* add Typecho.savePost

* fix upload file size

* add new uploader

* replace new uploader

* fix textarea change

* fix preview

* refactor post edit

* fix issue

* fix page edit

---------

Co-authored-by: joyqi <joyqi@segmentfault.com>
Co-authored-by: joyqi <magike.net@gmail.com>

* fix #1632

* Add svg to image types

* Feat/tree pages (#1646)

* add tree trait

* finish category tree trait

* support select fields

* fix select fields

* refactor admin trait

* fix draft status

* Add new contents type "revision"

* minor refactor

* add more tree view abstracts

* add tree trait to pages

* get ready for tree view pages

* improve page edit

* fix revision

* fix slug

* add router params delegate

* fix params delegate

* fix

* fix

* fix all

* fix all

* fix tree

* fix page link

* fix feed

* fix page

* fix permalink

* fix permalink input

* fix offset query

* Support IDN (#1629)

* Support IDN

* use js

* Optimize code

* Optimize code

* fix URL script

* remove unnecessary use

---------

Co-authored-by: joyqi <joyqi@segmentfault.com>

* fix input element

* fix #1651, close #1653

* Use json instead of serialize (#1624)

* Use json instead of serialize

* Fix Upgrade code

* add tree trait

* finish category tree trait

* support select fields

* fix select fields

* refactor admin trait

* fix draft status

* Add new contents type "revision"

* minor refactor

* add more tree view abstracts

* add tree trait to pages

* get ready for tree view pages

* improve page edit

* fix revision

* fix slug

* add router params delegate

* fix params delegate

* fix

* fix

* fix all

* fix all

* fix tree

* fix page link

* fix feed

* fix page

* fix permalink

* fix permalink input

* fix offset query

* Fix typo

* remove proxy methods

* remove unnecessary useage

---------

Co-authored-by: joyqi <joyqi@segmentfault.com>
Co-authored-by: joyqi <magike.net@gmail.com>

* Fix Prevent XSS vulnerability in default theme (#1654)

* Fix Prevent XSS vulnerability in default theme

* Update var/Typecho/Db/Adapter/Pdo.php

* fix the getter

---------

Co-authored-by: joyqi <joyqi@segmentfault.com>

* add throwCallback to widget response

* fix: cut down fields when selecting recent posts

* fix typo errors

* fix typo errors

* fix http client cookie

* add throw finish

* fix theme lang

* fix default theme

* fix query

* add open graph and twitter card support
add canonical link

* fix canonical link meta

* fix theme classic-22

* remove unnecessary scss file when packaging

* init plugin signal

* improve: remove feather-icon js file

* fix: typo

* improve: post detail layout

* fix tags saving

* improve: nav search

* fix: theme screenshot

* fix: theme page layout

* remove php 7.2/7.3 env

* feat: pull request auto merge

* fix permission

* improve: comment style

* fix: search result

* improve: show site description in homepage title

---------

Co-authored-by: joyqi <magike.net@gmail.com>
Co-authored-by: joyqi <joyqi@users.noreply.github.com>
Co-authored-by: joyqi <joyqi@segmentfault.com>
Co-authored-by: Lu Fei <52o@qq52o.cn>
2024-01-05 00:11:38 +08:00
joyqi
f23e825b95 fix router 2024-01-04 23:13:16 +08:00
joyqi
1c3b86fc22 fix #1679 (#1680)
Detect the insertion point of reply form automatically.
2024-01-04 22:47:15 +08:00
Fen
540dbb3b21 Create SECURITY.md 2024-01-04 17:12:29 +08:00
joyqi
81ad2232bf fix #1671 (#1678) 2024-01-04 11:13:36 +08:00
Lu Fei
d520a556cf fix style error for code (#1677) 2024-01-02 13:47:29 +08:00
joyqi
77aebcbd0e fix #1674 (#1676) 2024-01-02 11:16:44 +08:00
54 changed files with 560 additions and 386 deletions

View File

@@ -1,14 +0,0 @@
### 1. 该问题的重现步骤是什么?
### 2. 你期待的结果是什么?实际看到的又是什么?
### 3. 问题出现的环境
- 操作系统版本:
- Apache/NGINX 版本:
- 数据库版本:
- PHP 版本:
- Typecho 版本:
- 浏览器版本:
[//]: # (如有图片请附上截图)

42
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@@ -0,0 +1,42 @@
---
name: 上报 BUG
about: 报告问题,帮助改进项目
title: ''
labels: bug
assignees: ''
---
## 描述这个 Bug
简明扼要地描述错误是什么。
### 复现方式
复现的步骤:
1. 前往 '...'
2. 点击 '....'
3. 滚动到 '....'
4. 看到错误
### 期望的结果
简明扼要地描述你期望看到的结果。
### 截图
如果可以的话,请添加截图或视频以帮助解释你的问题。
### 平台
- 操作系统版本:
- Apache/Nginx 版本:
- 数据库版本:
- PHP 版本:
- Typecho 版本:
- 浏览器版本:

5
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@@ -0,0 +1,5 @@
blank_issues_enabled: true
contact_links:
- name: 论坛支持
url: https://forum.typecho.org/
about: 交流使用心得,使用模板插件,寻求帮助等等

View File

@@ -0,0 +1,23 @@
---
name: 提改进建议
about: 提出改进建议,改进项目现有功能
title: ''
labels: enhancement
assignees: ''
---
## 你的改进是否与某个问题有关?请描述
简明扼要地描述问题产生的原因。例如:当 [...] 时,我总是很困惑。
## 描述你想要的解决方案
简明扼要地描述你希望的解决方案。
## 描述你考虑过的替代方案
简明扼要地描述你考虑过的任何替代解决方案或功能。

View File

@@ -0,0 +1,23 @@
---
name: 新功能提议
about: 为项目提出一个想法或可能的新功能
title: ''
labels: feature
assignees: ''
---
## 你的想法是否与某个问题有关?请描述
简明扼要地描述问题产生的原因。例如:当 [...] 时,我总是很困惑。
## 描述你想要的解决方案
简明扼要地描述你希望的解决方案。
## 描述你考虑过的替代方案
简明扼要地描述你考虑过的任何替代解决方案或功能。

View File

@@ -0,0 +1,7 @@
Fixes #
## Proposed Changes
-
-
-

View File

@@ -19,7 +19,7 @@ jobs:
php: ['7.4', '8.0', '8.1', '8.2']
steps:
- name: Checkout code
uses: actions/checkout@v2
uses: actions/checkout@v4
- name: Setup PHP only
uses: shivammathur/setup-php@v2
with:
@@ -35,7 +35,7 @@ jobs:
- php
steps:
- name: Checkout code
uses: actions/checkout@v2
uses: actions/checkout@v4
- name: Build
run: |
mkdir build

View File

@@ -8,7 +8,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Build
run: |
mkdir build

3
SECURITY.md Normal file
View File

@@ -0,0 +1,3 @@
# Security Policy
Vulnerabilities can be reported by emailing security@typecho.org

View File

@@ -38,7 +38,7 @@ $(document).ready(function () {
preview = $('<div id="wmd-preview" class="wmd-hidetab" />').insertAfter('.editor');
let isFullScreen = false;
const options = {}, isMarkdown = <?php echo intval($content->isMarkdown || !$content->have()); ?>;
const options = {}, isMarkdown = <?php echo json_encode(!$content->have() || $content->isMarkdown); ?>;
options.strings = {
bold: '<?php _e('加粗'); ?> <strong> Ctrl+B',

View File

@@ -176,14 +176,13 @@ $isAllPosts = ('on' == $request->get('__typecho_all_posts') || 'on' == \Typecho\
<td class="kit-hidden-mb"><a
href="<?php $options->adminUrl('manage-posts.php?__typecho_all_posts=off&uid=' . $posts->author->uid); ?>"><?php $posts->author(); ?></a>
</td>
<td class="kit-hidden-mb"><?php $categories = $posts->categories;
while ($categories->next()): ?>
<?php echo '<a href="';
$options->adminUrl('manage-posts.php?category=' . $categories->mid
<td class="kit-hidden-mb"><?php foreach($posts->categories as $index => $category): ?><!--
--><?php echo ($index > 0 ? ', ' : '') . '<a href="';
$options->adminUrl('manage-posts.php?category=' . $category['mid']
. (isset($request->uid) ? '&uid=' . $request->filter('encode')->uid : '')
. (isset($request->status) ? '&status=' . $request->filter('encode')->status : ''));
echo '">' . $categories->name . '</a>' . ($categories->sequence < $categories->length - 1 ? ', ' : ''); ?>
<?php endwhile; ?>
echo '">' . $category['name'] . '</a>'; ?><!--
--><?php endforeach; ?>
</td>
<td>
<?php if ('post_draft' == $posts->type || $posts->revision): ?>

View File

@@ -12,7 +12,7 @@ include 'menu.php';
<div class="row typecho-page-main">
<div class="col-mb-12 col-tb-8" role="main">
<?php if ($attachment->attachment->isImage): ?>
<p><img src="<?php $attachment->attachment->url(); ?>"
<p><img src="<?php $attachment->attachment->url(); ?>?<?php $attachment->modified(); ?>"
alt="<?php $attachment->attachment->name(); ?>" class="typecho-attachment-photo"/></p>
<?php endif; ?>

View File

@@ -79,6 +79,9 @@ $(document).ready(function() {
prePopulate : tagsPre,
onResult : function (result, query, val) {
// remove special chars
val = val.replace(/<|>|&|"|'/g, '');
if (!query) {
return result;
}
@@ -184,7 +187,11 @@ $(document).ready(function() {
cid = o.cid;
draftId = o.draftId;
idInput.val(cid);
autoSave.text('<?php _e('已保存'); ?>' + ' (' + o.time + ')').effect('highlight', 1000);
autoSave.text('<?php _e('已保存'); ?>' + ' (' + o.time + ')');
if (!$(document.body).hasClass('preview')) {
autoSave.effect('highlight', 1000);
}
cb && cb();
};

View File

@@ -44,7 +44,14 @@ while ($parents->next()) {
$permalink = ltrim($permalink, '/');
$permalink = preg_replace("/\[([_a-z0-9-]+)[^\]]*\]/i", "{\\1}", $permalink);
if ($page->have()) {
$permalink = str_replace('{cid}', $page->cid, $permalink);
$permalink = preg_replace_callback(
"/\{(cid)\}/i",
function ($matches) use ($page) {
$key = $matches[1];
return $page->getRouterParam($key);
},
$permalink
);
}
$input = '<input type="text" id="slug" name="slug" autocomplete="off" value="' . htmlspecialchars($page->slug ?? '') . '" class="mono" />';
?>

View File

@@ -33,11 +33,14 @@ $post = \Widget\Contents\Post\Edit::alloc()->prepare();
$permalink = ltrim($permalink, '/');
$permalink = preg_replace("/\[([_a-z0-9-]+)[^\]]*\]/i", "{\\1}", $permalink);
if ($post->have()) {
$permalink = str_replace([
'{cid}', '{category}', '{year}', '{month}', '{day}'
], [
$post->cid, $post->category, $post->year, $post->month, $post->day
], $permalink);
$permalink = preg_replace_callback(
"/\{(cid|category|year|month|day)\}/i",
function ($matches) use ($post) {
$key = $matches[1];
return $post->getRouterParam($key);
},
$permalink
);
}
$input = '<input type="text" id="slug" name="slug" autocomplete="off" value="' . htmlspecialchars($post->slug ?? '') . '" class="mono" />';
?>
@@ -95,7 +98,7 @@ $post = \Widget\Contents\Post\Edit::alloc()->prepare();
<label class="typecho-label"><?php _e('分类'); ?></label>
<?php \Widget\Metas\Category\Rows::alloc()->to($category); ?>
<ul>
<?php $categories = $post->categories->toArray('mid'); ?>
<?php $categories = array_column($post->categories, 'mid'); ?>
<?php while ($category->next()): ?>
<li><?php echo str_repeat('&nbsp;&nbsp;&nbsp;&nbsp;', $category->levels); ?><input
type="checkbox" id="category-<?php $category->mid(); ?>"

View File

@@ -10,7 +10,7 @@
'defaultAvatar' => 'identicon'
)); ?>
<?php $comments->pageNav('&laquo; 前一页', '后一页 &raquo;'); ?>
<nav><?php $comments->pageNav(_t('前一页'), _t('后一页'), 3, '...', array('wrapTag' => 'ul', 'itemTag' => 'li')); ?></nav>
<?php endif; ?>
@@ -23,7 +23,9 @@
<h5 id="response"><?php _e('你的评论'); ?></h5>
<form method="post" action="<?php $this->commentUrl() ?>" id="comment-form" role="form">
<textarea placeholder="<?php _e('评论内容...'); ?>" rows="4" cols="50" name="text" id="textarea" required><?php $this->remember('text'); ?></textarea>
<div class="grid">
<textarea placeholder="<?php _e('评论内容...'); ?>" rows="4" cols="300" name="text" id="textarea" required><?php $this->remember('text'); ?></textarea>
</div>
<?php if ($this->user->hasLogin()): ?>
<p>
<?php _e('登录身份:'); ?><a href="<?php $this->options->profileUrl(); ?>"><?php $this->user->screenName(); ?></a><span class="mx-2 text-muted">&middot;</span><a href="<?php $this->options->logoutUrl(); ?>"><?php _e('退出'); ?></a>
@@ -32,7 +34,7 @@
<div class="grid">
<input type="text" placeholder="<?php _e('名字'); ?>" name="author" id="author" value="<?php $this->remember('author'); ?>" required/>
<input type="email" placeholder="<?php _e('Email'); ?>" name="mail" id="mail" value="<?php $this->remember('mail'); ?>"<?php if ($this->options->commentsRequireMail): ?> required<?php endif; ?> />
<input type="url" placeholder="<?php _e('网站 http://'); ?><?php if (!($this->options->commentsRequireUrl)): ?><?php _e('(选填)'); ?><?php endif; ?>" name="url" id="url" placeholder="<?php _e('https://'); ?>" value="<?php $this->remember('url'); ?>"<?php if ($this->options->commentsRequireUrl): ?> required<?php endif; ?> />
<input type="url" placeholder="<?php _e('http://网站'); ?><?php if (!$this->options->commentsRequireUrl): ?><?php _e('(选填)'); ?><?php endif; ?>" name="url" id="url" value="<?php $this->remember('url'); ?>"<?php if ($this->options->commentsRequireUrl): ?> required<?php endif; ?> />
</div>
<?php endif; ?>
<button type="submit"><?php _e('提交评论'); ?></button>

View File

@@ -13,18 +13,20 @@ function themeConfig($form)
$form->addInput($logoUrl->addRule('url', _t('请填写正确的 URL 地址')));
$themeStyle = new \Typecho\Widget\Helper\Form\Element\Radio(
'themeStyle',
$colorSchema = new \Typecho\Widget\Helper\Form\Element\Select(
'colorSchema',
array(
'auto' => _t('自动'),
null => _t('自动'),
'light' => _t('浅色'),
'dark' => _t('深色')
'dark' => _t('深色'),
'customize' => _t('自定义'),
),
'auto',
_t('外观风格')
null,
_t('外观风格'),
_t('如果选择了自定义,主题将使用 theme.css 的样式')
);
$form->addInput($themeStyle);
$form->addInput($colorSchema);
}
function postMeta(

View File

@@ -1,14 +1,14 @@
<?php if (!defined('__TYPECHO_ROOT_DIR__')) exit; ?>
<!DOCTYPE html>
<html lang="zh-Hans" data-theme="<?php $this->options->themeStyle(); ?>">
<html lang="zh-Hans"<?php if ($this->options->colorSchema): ?> data-theme="<?php $this->options->colorSchema(); ?>"<?php endif; ?>>
<head>
<meta charset="<?php $this->options->charset(); ?>">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?php $this->archiveTitle('', '', ' | '); ?><?php $this->options->title(); ?><?php if ($this->is('index')): ?> | <?php $this->options->description() ?><?php endif; ?></title>
<link rel="stylesheet" href="<?php $this->options->themeUrl('static/css/style.css'); ?>">
<?php if ($this->options->colorSchema == 'customize'): ?>
<link rel="stylesheet" href="<?php $this->options->themeUrl('theme.css'); ?>">
<?php endif; ?>
<?php $this->header(); ?>
</head>
@@ -30,7 +30,9 @@
<ul>
<li>
<label for="nav-toggler" class="nav-toggler-btn"><img src="<?php $this->options->themeUrl('static/img/menu.svg'); ?>" alt="Menu"></label>
<label for="nav-toggler" class="nav-toggler-btn">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="3" y1="12" x2="21" y2="12" /><line x1="3" y1="6" x2="21" y2="6" /><line x1="3" y1="18" x2="21" y2="18" /></svg>
</label>
</li>
</ul>
</nav>

View File

@@ -25,24 +25,20 @@ $this->need('header.php');
</h4>
<?php endif; ?>
<?php while ($this->next()): ?>
<article class="post" itemscope itemtype="http://schema.org/BlogPosting">
<?php postMeta($this); ?>
<div class="entry-content fmt" itemprop="articleBody">
<?php $this->content(_t('阅读全文')); ?>
</div>
</article>
<hr class="post-separator">
<?php endwhile; ?>
<?php while ($this->next()): ?>
<article class="post" itemscope itemtype="http://schema.org/BlogPosting">
<?php postMeta($this); ?>
<div class="entry-content fmt" itemprop="articleBody">
<?php $this->content(_t('阅读全文')); ?>
</div>
</article>
<hr class="post-separator">
<?php endwhile; ?>
<nav><?php $this->pageNav(_t('前一页'), _t('后一页'), 2, '...', array('wrapTag' => 'ul', 'itemTag' => 'li')); ?></nav>
</div>
<!-- <div class="text-center">
<a href="#">&laquo; Older Posts</a>
<span class="mx-2 text-muted">&middot;</span>
<a href="#">Newer Posts &raquo;</a>
</div> -->
<?php $this->pageNav('← ' . _t('前一页'), _t('后一页') . ' →'); ?>
</main>
<?php $this->need('footer.php'); ?>

View File

@@ -12,14 +12,12 @@
</div>
</article>
<div class="grid post-next">
<div>
<?php $this->thePrev('%s', _t('没有了')); ?>
</div>
<div class="text-end">
<?php $this->theNext('%s', _t('没有了')); ?> →
</div>
</div>
<nav class="post-nav">
<ul class="page-navigator">
<li class="prev"><?php $this->thePrev('%s', _t('没有了')); ?></li>
<li class="next"><?php $this->theNext('%s', _t('没有了')); ?></li>
</ul>
</nav>
<?php $this->need('comments.php'); ?>
</div>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.4 KiB

After

Width:  |  Height:  |  Size: 13 KiB

File diff suppressed because one or more lines are too long

View File

@@ -1,15 +0,0 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="#fff"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<line x1="3" y1="12" x2="21" y2="12" />
<line x1="3" y1="6" x2="21" y2="6" />
<line x1="3" y1="18" x2="21" y2="18" />
</svg>

Before

Width:  |  Height:  |  Size: 324 B

View File

@@ -1,14 +0,0 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="#fff"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<circle cx="11" cy="11" r="8" />
<line x1="21" y1="21" x2="16.65" y2="16.65" />
</svg>

Before

Width:  |  Height:  |  Size: 284 B

View File

@@ -3,7 +3,7 @@
* Licensed under MIT
*/
// Config
// Customize
// $enable-responsive-typography: false;
// Grey
@@ -19,53 +19,18 @@ $grey-800: #1e293b;
$grey-900: #0f172a;
// Blue
$primary-50: #E9F2FC;
$primary-100: #D1E5FB;
$primary-200: #9BCCFD;
$primary-300: #51B4FF;
$primary-400: #029AE8;
$primary-500: #017FC0;
$primary-600: #02659A;
$primary-700: #014C75;
$primary-800: #033452;
$primary-900: #061E2F;
// Amber
$amber-50: #fffbeb;
$amber-100: #fef3c7;
$amber-200: #fde68a;
$amber-300: #fcd34d;
$amber-400: #fbbf24;
$amber-500: #f59e0b;
$amber-600: #d97706;
$amber-700: #b45309;
$amber-800: #92400e;
$amber-900: #78350f;
// Green
$green-50: #f0fdf4;
$green-100: #dcfce7;
$green-200: #bbf7d0;
$green-300: #86efac;
$green-400: #4ade80;
$green-500: #22c55e;
$green-600: #16a34a;
$green-700: #15803d;
$green-800: #166534;
$green-900: #14532d;
// Red
$red-50: #fef2f2;
$red-100: #fee2e2;
$red-200: #fecaca;
$red-300: #fca5a5;
$red-400: #f87171;
$red-500: #ef4444;
$red-600: #dc2626;
$red-700: #b91c1c;
$red-800: #991b1b;
$red-900: #7f1d1d;
$primary-50: #d6f0ff;
$primary-100: #b5e7ff;
$primary-200: #83d9ff;
$primary-300: #48c3ff;
$primary-400: #1ea2ff;
$primary-500: #0683ff;
$primary-600: #0064e6;
$primary-700: #0855c5;
$primary-800: #0d4b9b;
$primary-900: #0e2e5d;
// Config
@import "../../../../../tools/node_modules/@picocss/pico/scss/variables";
// Theming

View File

@@ -7,37 +7,32 @@
body {
cursor: auto;
text-underline-offset: 0.2rem;
}
// Theme
[data-theme="light"],
:root:not([data-theme="dark"]) {
--primary: #{$primary-500};
--primary-hover: #{$primary-600};
--muted-border-color: #{$grey-200};
--level-odd-color: #{$grey-50};
}
@media only screen and (prefers-color-scheme: dark) {
:root:not([data-theme]) {
--background-color: #{$grey-900};
--muted-border-color: #{$grey-700};
--level-odd-color: #{$grey-800};
}
}
[data-theme="dark"] {
--primary: #{$primary-500};
--primary-hover: #{$primary-400};
--muted-border-color: #{$grey-800};
.site-navbar {
background-color: #{$primary-600};
}
.comment-level-odd {
background-color: #{$grey-900};
}
--background-color: #{$grey-900};
--muted-border-color: #{$grey-700};
--level-odd-color: #{$grey-800};
}
// Content
h1, h2, h3, h4, h5 { line-height: 1.25; }
// h1 { --font-size: 2.5rem; }
// h2 { --font-size: 2rem; }
// h3 { --font-size: 1.75rem; }
// h4 { --font-size: 1.5rem; }
// h5 { --font-size: 1.25rem; }
// Icon Size
.is-sm {
@@ -131,12 +126,9 @@ h1, h2, h3, h4, h5 { line-height: 1.25; }
.site-navbar {
padding-top: .25rem;
padding-bottom: .25rem;
background-color: var(--primary);
a {
color: var(--primary-inverse);
// color: rgba(255, 255, 255);
// &:hover { text-decoration: underline; }
color: inherit;
}
.site-name {
@@ -146,10 +138,14 @@ h1, h2, h3, h4, h5 { line-height: 1.25; }
.brand {
font-size: 1.25rem;
font-weight: 700;
img {
max-height: 2rem;
}
}
.desc {
color: rgba(255, 255, 255, .5);
color: var(--muted-color);
display: none;
@if map-get($breakpoints, "sm") {
@media (min-width: map-get($breakpoints, "sm")) {
@@ -171,6 +167,10 @@ h1, h2, h3, h4, h5 { line-height: 1.25; }
}
}
.nav-toggler-btn {
cursor: pointer;
}
// Dropdown Menu
.nav-menu {
display: none;
@@ -185,14 +185,7 @@ h1, h2, h3, h4, h5 { line-height: 1.25; }
input[type=search] {
height: 50px;
background-image: url("../img/search.svg");
background-size: auto;
background-color: inherit;
color: var(--primary-inverse);
&:focus {
--form-element-focus-color: rgba(255, 255, 255, .5);
}
&:not(:focus) {
padding: 0;
@@ -200,19 +193,14 @@ h1, h2, h3, h4, h5 { line-height: 1.25; }
width: 30px;
padding-inline-start: 0;
background-position: center center;
background-color: inherit;
color: transparent;
cursor: pointer;
}
}
}
}
.nav-toggler-btn {
margin: calc(var(--spacing) * -1) calc(var(--spacing) * -0.5);
padding: var(--spacing) calc(var(--spacing) * 0.5);
color: rgba(255, 255, 255, 1.0);
cursor: pointer;
}
@if map-get($breakpoints, "lg") {
@media (min-width: map-get($breakpoints, "lg")) {
.site-navbar .container-inner,
@@ -279,13 +267,13 @@ h1, h2, h3, h4, h5 { line-height: 1.25; }
}
}
.post-next {
.post-nav {
border-top: 1px solid var(--muted-border-color);
padding-top: calc(var(--spacing) * 1.5);
padding-top: var(--spacing);
margin: var(--block-spacing-vertical) 0;
a {
color: var(--h5-color);
color: inherit;
}
}
@@ -307,11 +295,10 @@ h1, h2, h3, h4, h5 { line-height: 1.25; }
.comment-list {
list-style: none;
padding: 0;
// padding-left: calc(var(--spacing) * 4);
}
.comment-level-odd {
background-color: #{$grey-50};
background-color: var(--level-odd-color);
}
.comment-level-even {
background-color: var(--background-color);
@@ -385,9 +372,9 @@ h1, h2, h3, h4, h5 { line-height: 1.25; }
margin-bottom: calc(var(--spacing) * -1);
}
.comment-by-author {
// background-color: var(--mark-background-color);
}
// .comment-by-author {
// background-color: var(--mark-background-color);
// }
#response {
margin-bottom: var(--spacing);
@@ -395,11 +382,14 @@ h1, h2, h3, h4, h5 { line-height: 1.25; }
#cancel-comment-reply-link {
font-size: .875em;
color: var(--del-color);
}
.comment-body .respond {
margin-top: var(--spacing);
.respond {
margin-top: calc(var(--spacing) * 1.5);
.comment-body & {
margin-top: var(--spacing);
}
}
#comment-form textarea {
@@ -408,18 +398,27 @@ h1, h2, h3, h4, h5 { line-height: 1.25; }
// page nav
.page-navigator {
list-style: none;
padding: 0;
text-align: center;
margin: 0;
flex: 1;
li {
display: inline;
margin-left: calc(var(--spacing) / 2);
margin-right: calc(var(--spacing) / 2);
&.current a {
font-weight: 700;
color: var(--h5-color);
.current a {
text-decoration: underline;
color: inherit;
pointer-events: none;
}
.prev {
margin-right: auto;
& a::before {
content: "";
margin-right: .25rem;
}
}
}
.next {
margin-left: auto;
& a::after {
content: "";
margin-left: .25rem;
}
}
}

View File

@@ -0,0 +1,33 @@
/*!
* This is an example of a color scheme
* You can define your color scheme below
*/
[data-theme="customize"] {
--primary: #017FC0 !important;
--primary-hover: #02659A !important;
--background-color: #fffbeb !important;
--muted-border-color: rgba(0, 0, 0, .1) !important;
--form-element-border-color: rgba(0, 0, 0, .2) !important;
--level-odd-color: rgba(0, 0, 0, .025) !important;
--code-background-color: rgba(0, 0, 0, .05) !important;
}
.site-navbar {
background-color: var(--primary);
}
.site-navbar a,
.site-navbar .nav-toggler-btn,
.site-navbar input[type="search"] {
color: var(--primary-inverse);
}
.site-navbar .desc {
color: rgba(255, 255, 255, .5);
}
.site-navbar input[type="search"] {
border-color: rgba(255, 255, 255, .25);
--icon-search: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(255, 255, 255)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='11' cy='11' r='8'%3E%3C/circle%3E%3Cline x1='21' y1='21' x2='16.65' y2='16.65'%3E%3C/line%3E%3C/svg%3E");
}

View File

@@ -36,6 +36,7 @@ pre {
max-height: 400px;
}
pre code {
display: block;
padding: 3px;
color: #444;
}

View File

@@ -29,9 +29,9 @@ class Error
* 构造函数
*
* @param integer $code 错误代码
* @param string|null $message 错误消息
* @param string $message 错误消息
*/
public function __construct(int $code, ?string $message)
public function __construct(int $code, string $message)
{
$this->code = $code;
$this->message = $message;

View File

@@ -40,7 +40,7 @@ class Message
private array $currentStructName = []; // A stack as well
private string $currentTagContents;
private string $currentTagContents = '';
/**
* @param string $message
@@ -60,6 +60,25 @@ class Message
if (trim($this->message) == '') {
return false;
}
// remove the DOCTYPE, avoid using a regexp, so we can save memory
$count = 0;
while (true) {
// Fail if there is an endless loop
if ($count >= 10) {
return false;
}
$pos = strpos($this->message, '<!DOCTYPE');
if ($pos !== false) {
$this->message = substr($this->message, 0, $pos)
. substr($this->message, strpos($this->message, '>', $pos) + 1);
$count ++;
} else {
break;
}
}
$parser = xml_parser_create();
// Set XML parser to take the case of tags in to account
xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, false);

View File

@@ -17,7 +17,7 @@ class Value
* @param mixed $data
* @param string|null $type
*/
public function __construct($data, ?string $type)
public function __construct($data, ?string $type = null)
{
$this->data = $data;
if (!$type) {

View File

@@ -493,12 +493,12 @@ EOF;
* @access public
*
* @param string|null $str 需要生成缩略名的字符串
* @param string|null $default 默认的缩略名
* @param string $default 默认的缩略名
* @param integer $maxLength 缩略名最大长度
*
* @return string
*/
public static function slugName(?string $str, ?string $default = null, int $maxLength = 128): ?string
public static function slugName(?string $str, string $default = '', int $maxLength = 128): string
{
$str = trim($str ?? '');
@@ -677,6 +677,18 @@ EOF;
return $length < $iLength ? ($str . $trim) : $str;
}
/**
* 判断两个字符串是否为空并依次返回
*
* @param string|null $a
* @param string|null $b
* @return string|null
*/
public static function strBy(?string $a, ?string $b = null): ?string
{
return isset($a) && $a !== '' ? $a : $b;
}
/**
* 获取宽字符串长度函数
*

View File

@@ -187,7 +187,7 @@ class Request
}
} else {
$exists = false;
return $default;
return $value ?? $default;
}
}
@@ -367,10 +367,7 @@ class Request
*/
public function getContentType(): ?string
{
return $this->getServer(
'CONTENT_TYPE',
$this->getServer('HTTP_CONTENT_TYPE')
);
return $this->getHeader('Content-Type');
}
/**
@@ -420,8 +417,14 @@ class Request
*/
public function getHeader(string $key, ?string $default = null): ?string
{
$key = 'HTTP_' . strtoupper(str_replace('-', '_', $key));
return $this->getServer($key, $default);
$key = strtoupper(str_replace('-', '_', $key));
// Content-Type 和 Content-Length 这两个 header 还需要从不带 HTTP_ 的 key 尝试获取
if (in_array($key, ['CONTENT_TYPE', 'CONTENT_LENGTH'])) {
$default = $this->getServer($key, $default);
}
return $this->getServer('HTTP_' . $key, $default);
}
/**
@@ -542,7 +545,7 @@ class Request
$validated = true;
foreach ($params as $key => $val) {
$param = $this->get($key, null, $exists);
$validated = empty($val) ? $exists : ($val == $param);
$validated = $val === '' ? $exists : ($val === $param);
if (!$validated) {
break;

View File

@@ -303,6 +303,24 @@ abstract class Widget
}
}
/**
* @param string|array $column
* @return array|mixed|null
*/
public function toColumn($column)
{
if (is_array($column)) {
$item = [];
foreach ($column as $key) {
$item[$key] = $this->{$key};
}
return $item;
} else {
return $this->{$column};
}
}
/**
* @param string|array $column
* @return array
@@ -312,16 +330,7 @@ abstract class Widget
$result = [];
while ($this->next()) {
if (is_array($column)) {
$item = [];
foreach ($column as $key) {
$item[$key] = $this->{$key};
}
$result[] = $item;
} else {
$result[] = $this->{$column};
}
$result[] = $this->toColumn($column);
}
return $result;
@@ -365,6 +374,18 @@ abstract class Widget
return $value;
}
/**
* 将所有行的值压入堆栈
*
* @param array $values 所有行的值
*/
public function pushAll(array $values)
{
foreach ($values as $value) {
$this->push($value);
}
}
/**
* 根据余数输出
*
@@ -460,7 +481,14 @@ abstract class Widget
*/
public function __set(string $name, $value)
{
$this->row[$name] = $value;
$method = '___' . $name;
$key = '#' . $name;
if (isset($this->row[$key]) || method_exists($this, $method)) {
$this->row[$key] = $value;
} else {
$this->row[$name] = $value;
}
}
/**
@@ -471,7 +499,10 @@ abstract class Widget
*/
public function __isSet(string $name)
{
return isset($this->row[$name]);
$method = '___' . $name;
$key = '#' . $name;
return isset($this->row[$key]) || method_exists($this, $method) || isset($this->row[$name]);
}
/**

View File

@@ -187,6 +187,10 @@ class Archive extends Contents
/** 用于判断是路由调用还是外部调用 */
if (null == $parameter->type) {
if (!isset(Router::$current)) {
throw new WidgetException('Archive type is not set', 500);
}
$parameter->type = Router::$current;
} else {
$this->invokeFromOutside = true;
@@ -1028,53 +1032,85 @@ class Archive extends Contents
if ($this->options->commentsThreaded && $this->is('single')) {
if ('' != $allows['commentReply']) {
if (1 == $allows['commentReply']) {
$header .= "<script type=\"text/javascript\">
$header .= <<<EOF
<script type="text/javascript">
(function () {
window.TypechoComment = {
dom : function (id) {
return document.getElementById(id);
dom : function (sel) {
return document.querySelector(sel);
},
visiable: function (el, show) {
el.style.display = show ? '' : 'none';
},
create : function (tag, attr) {
var el = document.createElement(tag);
const el = document.createElement(tag);
for (var key in attr) {
for (const key in attr) {
el.setAttribute(key, attr[key]);
}
return el;
},
reply : function (cid, coid) {
var comment = this.dom(cid), parent = comment.parentNode,
response = this.dom('" . $this->respondId . "'), input = this.dom('comment-parent'),
form = 'form' == response.tagName ? response : response.getElementsByTagName('form')[0],
textarea = response.getElementsByTagName('textarea')[0];
if (null == input) {
inputParent: function (response, coid) {
const form = 'form' === response.tagName ? response : response.querySelector('form');
let input = form.querySelector('input[name=parent]');
if (null == input && coid) {
input = this.create('input', {
'type' : 'hidden',
'name' : 'parent',
'id' : 'comment-parent'
'name' : 'parent'
});
form.appendChild(input);
}
if (coid) {
input.setAttribute('value', coid);
} else if (input) {
input.parentNode.removeChild(input);
}
},
getChild: function (root, node) {
const parentNode = node.parentNode;
if (parentNode === null) {
return null;
} else if (parentNode === root) {
return node;
} else {
return this.getChild(root, parentNode);
}
},
input.setAttribute('value', coid);
reply : function (htmlId, coid, btn) {
const response = this.dom('#{$this->respondId}'),
textarea = response.querySelector('textarea[name=text]'),
comment = this.dom('#' + htmlId),
child = this.getChild(comment, btn);
if (null == this.dom('comment-form-place-holder')) {
var holder = this.create('div', {
'id' : 'comment-form-place-holder'
this.inputParent(response, coid);
if (this.dom('#{$this->respondId}-holder') === null) {
const holder = this.create('div', {
'id' : '{$this->respondId}-holder'
});
response.parentNode.insertBefore(holder, response);
}
if (child) {
comment.insertBefore(response, child.nextSibling);
} else {
comment.appendChild(response);
}
comment.appendChild(response);
this.dom('cancel-comment-reply-link').style.display = '';
this.visiable(this.dom('#cancel-comment-reply-link'), true);
if (null != textarea && 'text' == textarea.name) {
if (null != textarea) {
textarea.focus();
}
@@ -1082,25 +1118,23 @@ class Archive extends Contents
},
cancelReply : function () {
var response = this.dom('{$this->respondId}'),
holder = this.dom('comment-form-place-holder'), input = this.dom('comment-parent');
const response = this.dom('#{$this->respondId}'),
holder = this.dom('#{$this->respondId}-holder');
if (null != input) {
input.parentNode.removeChild(input);
}
this.inputParent(response, false);
if (null == holder) {
if (null === holder) {
return true;
}
this.dom('cancel-comment-reply-link').style.display = 'none';
this.visiable(this.dom('#cancel-comment-reply-link'), false);
holder.parentNode.insertBefore(response, holder);
return false;
}
};
})();
</script>
";
EOF;
} else {
$header .= '<script src="' . $allows['commentReply'] . '" type="text/javascript"></script>';
}
@@ -1111,45 +1145,41 @@ class Archive extends Contents
if ($this->options->commentsAntiSpam && $this->is('single')) {
if ('' != $allows['antiSpam']) {
if (1 == $allows['antiSpam']) {
$header .= "<script type=\"text/javascript\">
$shuffled = Common::shuffleScriptVar($this->security->getToken($this->request->getRequestUrl()));
$header .= <<<EOF
<script type="text/javascript">
(function () {
var event = document.addEventListener ? {
add: 'addEventListener',
triggers: ['scroll', 'mousemove', 'keyup', 'touchstart'],
load: 'DOMContentLoaded'
} : {
add: 'attachEvent',
triggers: ['onfocus', 'onmousemove', 'onkeyup', 'ontouchstart'],
load: 'onload'
}, added = false;
const events = ['scroll', 'mousemove', 'keyup', 'touchstart'];
let added = false;
document[event.add](event.load, function () {
var r = document.getElementById('{$this->respondId}'),
input = document.createElement('input');
input.type = 'hidden';
input.name = '_';
input.value = " . Common::shuffleScriptVar($this->security->getToken($this->request->getRequestUrl())) . "
document.addEventListener('DOMContentLoaded', function () {
const response = document.querySelector('#{$this->respondId}');
if (null != r) {
var forms = r.getElementsByTagName('form');
if (forms.length > 0) {
if (null != response) {
const form = 'form' === response.tagName ? response : response.querySelector('form');
const input = document.createElement('input');
input.type = 'hidden';
input.name = '_';
input.value = {$shuffled};
if (form) {
function append() {
if (!added) {
forms[0].appendChild(input);
form.appendChild(input);
added = true;
}
}
for (var i = 0; i < event.triggers.length; i ++) {
var trigger = event.triggers[i];
document[event.add](trigger, append);
window[event.add](trigger, append);
for (const event of events) {
window.addEventListener(event, append);
}
}
}
});
})();
</script>";
</script>
EOF;
} else {
$header .= '<script src="' . $allows['antiSpam'] . '" type="text/javascript"></script>';
}
@@ -1624,14 +1654,19 @@ class Archive extends Contents
$this->archiveTitle = $this->title;
/** 设置关键词 */
$this->archiveKeywords = implode(',', $this->tags->toArray('name'));
$this->archiveKeywords = implode(',', array_column($this->tags, 'name'));
/** 设置描述 */
$this->archiveDescription = $this->plainExcerpt;
}
/** 设置归档类型 */
[$this->archiveType] = explode('_', $this->type);
if ($this->parameter->preview && $this->type === 'revision') {
$parent = ContentsFrom::allocWithAlias($this->parent, ['cid' => $this->parent]);
$this->archiveType = $parent->type;
} else {
[$this->archiveType] = explode('_', $this->type);
}
/** 设置归档缩略名 */
$this->archiveSlug = ('post' == $this->archiveType || 'attachment' == $this->archiveType)

View File

@@ -79,17 +79,16 @@ class Comments extends Base implements QueryInterface, RowFilterInterface, Prima
$insertStruct = [
'cid' => $rows['cid'],
'created' => empty($rows['created']) ? $this->options->time : $rows['created'],
'author' => !isset($rows['author']) || strlen($rows['author']) === 0 ? null : $rows['author'],
'author' => Common::strBy($rows['author'] ?? null),
'authorId' => empty($rows['authorId']) ? 0 : $rows['authorId'],
'ownerId' => empty($rows['ownerId']) ? 0 : $rows['ownerId'],
'mail' => !isset($rows['mail']) || strlen($rows['mail']) === 0 ? null : $rows['mail'],
'url' => !isset($rows['url']) || strlen($rows['url']) === 0 ? null : $rows['url'],
'ip' => !isset($rows['ip']) || strlen($rows['ip']) === 0 ? $this->request->getIp() : $rows['ip'],
'agent' => !isset($rows['agent']) || strlen($rows['agent']) === 0
? $this->request->getAgent() : $rows['agent'],
'text' => !isset($rows['text']) || strlen($rows['text']) === 0 ? null : $rows['text'],
'type' => empty($rows['type']) ? 'comment' : $rows['type'],
'status' => empty($rows['status']) ? 'approved' : $rows['status'],
'mail' => Common::strBy($rows['mail'] ?? null),
'url' => Common::strBy($rows['url'] ?? null),
'ip' => Common::strBy($rows['ip'] ?? null, $this->request->getIp()),
'agent' => Common::strBy($rows['agent'] ?? null, $this->request->getAgent()),
'text' => Common::strBy($rows['text'] ?? null),
'type' => Common::strBy($rows['type'] ?? null, 'comment'),
'status' => Common::strBy($rows['status'] ?? null, 'approved'),
'parent' => empty($rows['parent']) ? 0 : $rows['parent'],
];
@@ -137,11 +136,11 @@ class Comments extends Base implements QueryInterface, RowFilterInterface, Prima
/** 构建插入结构 */
$preUpdateStruct = [
'author' => !isset($rows['author']) || strlen($rows['author']) === 0 ? null : $rows['author'],
'mail' => !isset($rows['mail']) || strlen($rows['mail']) === 0 ? null : $rows['mail'],
'url' => !isset($rows['url']) || strlen($rows['url']) === 0 ? null : $rows['url'],
'text' => !isset($rows['text']) || strlen($rows['text']) === 0 ? null : $rows['text'],
'status' => empty($rows['status']) ? 'approved' : $rows['status'],
'author' => Common::strBy($rows['author'] ?? null),
'mail' => Common::strBy($rows['mail'] ?? null),
'url' => Common::strBy($rows['url'] ?? null),
'text' => Common::strBy($rows['text'] ?? null),
'status' => Common::strBy($rows['status'] ?? null, 'approved'),
];
$updateStruct = [];

View File

@@ -57,8 +57,8 @@ if (!defined('__TYPECHO_ROOT_DIR__')) {
* @property-read Date $date
* @property-read string $dateWord
* @property-read string[] $directory
* @property-read Metas $tags
* @property-read Metas $categories
* @property-read array[] $tags
* @property-read array[] $categories
* @property-read string $excerpt
* @property-read string $plainExcerpt
* @property-read string $summary
@@ -95,7 +95,7 @@ class Contents extends Base implements QueryInterface, RowFilterInterface, Prima
case 'directory':
return implode('/', array_map('urlencode', $this->directory));
case 'category':
return urlencode($this->categories->slug);
return empty($this->categories) ? '' : urlencode($this->categories[0]['slug']);
case 'year':
return $this->date->year;
case 'month':
@@ -131,15 +131,15 @@ class Contents extends Base implements QueryInterface, RowFilterInterface, Prima
$insertStruct = [
'title' => !isset($rows['title']) || strlen($rows['title']) === 0
? null : htmlspecialchars($rows['title']),
'created' => !isset($rows['created']) ? $this->options->time : $rows['created'],
'created' => empty($rows['created']) ? $this->options->time : $rows['created'],
'modified' => $this->options->time,
'text' => !isset($rows['text']) || strlen($rows['text']) === 0 ? null : $rows['text'],
'text' => Common::strBy($rows['text'] ?? null),
'order' => empty($rows['order']) ? 0 : intval($rows['order']),
'authorId' => $rows['authorId'] ?? $this->user->uid,
'template' => empty($rows['template']) ? null : $rows['template'],
'type' => empty($rows['type']) ? 'post' : $rows['type'],
'status' => empty($rows['status']) ? 'publish' : $rows['status'],
'password' => !isset($rows['password']) || strlen($rows['password']) === 0 ? null : $rows['password'],
'template' => Common::strBy($rows['template'] ?? null),
'type' => Common::strBy($rows['type'] ?? null, 'post'),
'status' => Common::strBy($rows['status'] ?? null, 'publish'),
'password' => Common::strBy($rows['password'] ?? null),
'commentsNum' => empty($rows['commentsNum']) ? 0 : $rows['commentsNum'],
'allowComment' => !empty($rows['allowComment']) && 1 == $rows['allowComment'] ? 1 : 0,
'allowPing' => !empty($rows['allowPing']) && 1 == $rows['allowPing'] ? 1 : 0,
@@ -156,7 +156,7 @@ class Contents extends Base implements QueryInterface, RowFilterInterface, Prima
/** 更新缩略名 */
if ($insertId > 0) {
$this->applySlug(!isset($rows['slug']) || strlen($rows['slug']) === 0 ? null : $rows['slug'], $insertId);
$this->applySlug(Common::strBy($rows['slug'] ?? null), $insertId, $insertStruct['title']);
}
return $insertId;
@@ -167,10 +167,11 @@ class Contents extends Base implements QueryInterface, RowFilterInterface, Prima
*
* @param string|null $slug 缩略名
* @param mixed $cid 内容id
* @param string $title 标题
* @return string
* @throws Exception
*/
public function applySlug(?string $slug, $cid): string
public function applySlug(?string $slug, $cid, string $title = ''): string
{
if ($cid instanceof Query) {
$cid = $this->db->fetchObject($cid->select('cid')
@@ -178,6 +179,10 @@ class Contents extends Base implements QueryInterface, RowFilterInterface, Prima
}
/** 生成一个非空的缩略名 */
if ((!isset($slug) || strlen($slug) === 0) && preg_match_all("/\w+/", $title, $matches)) {
$slug = implode('-', $matches[0]);
}
$slug = Common::slugName($slug, $cid);
$result = $slug;
@@ -185,11 +190,10 @@ class Contents extends Base implements QueryInterface, RowFilterInterface, Prima
$draft = $this->db->fetchObject($this->db->select('type', 'parent')
->from('table.contents')->where('cid = ?', $cid));
if ('_draft' == substr($draft->type, - 6) && $draft->parent) {
if (preg_match("/_draft$/", $draft->type) && $draft->parent) {
$result = '@' . $result;
}
/** 判断是否在数据库中已经存在 */
$count = 1;
while (
@@ -226,11 +230,11 @@ class Contents extends Base implements QueryInterface, RowFilterInterface, Prima
'title' => !isset($rows['title']) || strlen($rows['title']) === 0
? null : htmlspecialchars($rows['title']),
'order' => empty($rows['order']) ? 0 : intval($rows['order']),
'text' => !isset($rows['text']) || strlen($rows['text']) === 0 ? null : $rows['text'],
'template' => empty($rows['template']) ? null : $rows['template'],
'type' => empty($rows['type']) ? 'post' : $rows['type'],
'status' => empty($rows['status']) ? 'publish' : $rows['status'],
'password' => empty($rows['password']) ? null : $rows['password'],
'text' => Common::strBy($rows['text'] ?? null),
'template' => Common::strBy($rows['template'] ?? null),
'type' => Common::strBy($rows['type'] ?? null, 'post'),
'status' => Common::strBy($rows['status'] ?? null, 'publish'),
'password' => Common::strBy($rows['password'] ?? null),
'allowComment' => !empty($rows['allowComment']) && 1 == $rows['allowComment'] ? 1 : 0,
'allowPing' => !empty($rows['allowPing']) && 1 == $rows['allowPing'] ? 1 : 0,
'allowFeed' => !empty($rows['allowFeed']) && 1 == $rows['allowFeed'] ? 1 : 0,
@@ -461,13 +465,11 @@ class Contents extends Base implements QueryInterface, RowFilterInterface, Prima
*/
public function category(string $split = ',', bool $link = true, ?string $default = null)
{
$categories = $this->categories;
if ($categories->have()) {
if (!empty($this->categories)) {
$result = [];
while ($categories->next()) {
$result[] = $link ? $categories->template('<a href="{permalink}">{name}</a>') : $categories->name;
foreach ($this->categories as $category) {
$result[] = $link ? "<a href=\"{$category['permalink']}\">{$category['name']}</a>" : $category['name'];
}
echo implode($split, $result);
@@ -513,13 +515,11 @@ class Contents extends Base implements QueryInterface, RowFilterInterface, Prima
*/
public function tags(string $split = ',', bool $link = true, ?string $default = null)
{
$tags = $this->tags;
if ($tags->have()) {
if (!empty($this->tags)) {
$result = [];
while ($tags->next()) {
$result[] = $link ? $tags->template('<a href="{permalink}">{name}</a>') : $tags->name;
foreach ($this->tags as $tag) {
$result[] = $link ? "<a href=\"{$tag['permalink']}\">{$tag['name']}</a>" : $tag['name'];
}
echo implode($split, $result);
@@ -658,32 +658,32 @@ class Contents extends Base implements QueryInterface, RowFilterInterface, Prima
{
$directory = [];
$category = $this->categories;
if ($category->have()) {
$directory = Rows::alloc()->getAllParentsSlug($category->mid);
$directory[] = $category->slug;
if (!empty($this->categories)) {
$directory = Rows::alloc()->getAllParentsSlug($this->categories[0]['mid']);
$directory[] = $this->categories[0]['slug'];
}
return $directory;
}
/**
* @return Metas
* @return array
*/
protected function ___categories(): Metas
protected function ___categories(): array
{
return CategoryRelated::allocWithAlias($this->cid, ['cid' => $this->cid]);
return CategoryRelated::allocWithAlias($this->cid, ['cid' => $this->cid])
->toArray(['mid', 'name', 'slug', 'description', 'order', 'parent', 'count', 'permalink']);
}
/**
* 将tags取出
*
* @return Metas
* @return array
*/
protected function ___tags(): Metas
protected function ___tags(): array
{
return TagRelated::allocWithAlias($this->cid, ['cid' => $this->cid]);
return TagRelated::allocWithAlias($this->cid, ['cid' => $this->cid])
->toArray(['mid', 'name', 'slug', 'description', 'count', 'permalink']);
}
/**

View File

@@ -414,7 +414,7 @@ class Archive extends Comments
if (!$plugged) {
echo '<a href="' . substr($this->permalink, 0, - strlen($this->theId) - 1) . '?replyTo=' . $this->coid .
'#' . $this->parameter->respondId . '" rel="nofollow" onclick="return TypechoComment.reply(\'' .
$this->theId . '\', ' . $this->coid . ');">' . $word . '</a>';
$this->theId . '\', ' . $this->coid . ', this);">' . $word . '</a>';
}
}
}

View File

@@ -62,7 +62,7 @@ class Admin extends Contents
protected function ___parentPost(): Config
{
return new Config($this->db->fetchRow(
$this->select()->where('table.contents.cid = ?', $this->parentId)->limit(1)
$this->select()->where('table.contents.cid = ?', $this->parent)->limit(1)
));
}
}

View File

@@ -95,7 +95,7 @@ class Edit extends Contents implements ActionInterface
/** 取出数据 */
$input = $this->request->from('name', 'slug', 'description');
$input['slug'] = Common::slugName(empty($input['slug']) ? $input['name'] : $input['slug']);
$input['slug'] = Common::slugName(Common::strBy($input['slug'] ?? null, $input['name']));
$attachment['title'] = $input['name'];
$attachment['slug'] = $input['slug'];
@@ -313,7 +313,7 @@ class Edit extends Contents implements ActionInterface
if ($this->isWriteable(clone $condition) && $this->delete($condition)) {
/** 删除文件 */
Upload::deleteHandle($row);
Upload::deleteHandle($this->toColumn(['cid', 'attachment', 'parent']));
/** 删除评论 */
$this->db->query($this->db->delete('table.comments')

View File

@@ -577,13 +577,14 @@ trait EditTrait
$realId = 0;
/** 是否是从草稿状态发布 */
$isDraftToPublish = preg_match("/_draft$/", $this->type);
$isBeforePublish = ('publish' == $this->status);
$isAfterPublish = ('publish' == $contents['status']);
$isDraftToPublish = false;
$isBeforePublish = false;
$isAfterPublish = 'publish' === $contents['status'];
/** 重新发布现有内容 */
if ($this->have()) {
$isDraftToPublish = preg_match("/_draft$/", $this->type);
$isBeforePublish = 'publish' === $this->status;
/** 如果它本身不是草稿, 需要删除其草稿 */
if (!$isDraftToPublish && $this->draft) {

View File

@@ -44,17 +44,17 @@ class Admin extends Contents
$this->parameter->setDefault('ignoreRequest=0');
if ($this->parameter->ignoreRequest) {
$this->stack = $this->getRows($this->orders, $this->parameter->ignore);
$this->pushAll($this->getRows($this->orders, $this->parameter->ignore));
} elseif ($this->request->is('keywords')) {
$select = $this->select('table.contents.cid')
->where('table.contents.type = ? OR table.contents.type = ?', 'page', 'page_draft');
$this->searchQuery($select);
$ids = array_column($this->db->fetchAll($select), 'cid');
$this->stack = $this->getRows($ids);
$this->pushAll($this->getRows($ids));
} else {
$this->parentId = $this->request->filter('int')->get('parent', 0);
$this->stack = $this->getRows($this->getChildIds($this->parentId));
$this->pushAll($this->getRows($this->getChildIds($this->parentId)));
}
}

View File

@@ -32,7 +32,7 @@ class Rows extends Contents
*/
public function execute()
{
$this->stack = $this->getRows($this->orders, $this->parameter->ignore);
$this->pushAll($this->getRows($this->orders, $this->parameter->ignore));
}
/**

View File

@@ -75,7 +75,7 @@ class Edit extends Contents implements ActionInterface
/** 发送ping */
$trackback = array_filter(
array_unique(preg_split("/(\r|\n|\r\n)/", trim($this->request->get('trackback'))))
array_unique(preg_split("/(\r|\n|\r\n)/", trim($this->request->get('trackback', ''))))
);
Service::alloc()->sendPing($this, $trackback);

View File

@@ -126,14 +126,14 @@ trait PrepareEditTrait
}
/**
* @return Metas
* @return array
*/
protected function ___categories(): Metas
protected function ___categories(): array
{
return $this->have() ? parent::___categories()
: MetasFrom::allocWithAlias(
'category:' . $this->options->defaultCategory,
['mid' => $this->options->defaultCategory]
);
)->toArray(['mid', 'name', 'slug']);
}
}

View File

@@ -181,7 +181,7 @@ class Feed extends Contents
'link' => $archive->permalink,
'author' => $archive->author,
'excerpt' => $archive->plainExcerpt,
'category' => $archive->categories->toArray(['name', 'permalink']),
'category' => $archive->categories,
'comments' => $archive->commentsNum,
'commentsFeedUrl' => Common::url($archive->path, $feed->getFeedUrl()),
'suffix' => $suffix

View File

@@ -31,7 +31,7 @@ class Admin extends Metas
public function execute()
{
$this->parentId = $this->request->filter('int')->get('parent', 0);
$this->stack = $this->getRows($this->getChildIds($this->parentId));
$this->pushAll($this->getRows($this->getChildIds($this->parentId)));
}
/**

View File

@@ -133,7 +133,7 @@ class Edit extends Metas implements ActionInterface
/** 取出数据 */
$category = $this->request->from('name', 'slug', 'description', 'parent');
$category['slug'] = Common::slugName(empty($category['slug']) ? $category['name'] : $category['slug']);
$category['slug'] = Common::slugName(Common::strBy($category['slug'] ?? null, $category['name']));
$category['type'] = 'category';
$category['order'] = $this->getMaxOrder('category', $category['parent']) + 1;
@@ -224,7 +224,7 @@ class Edit extends Metas implements ActionInterface
$submit->input->setAttribute('class', 'btn primary');
$form->addItem($submit);
if (isset($this->request->mid) && 'insert' != $action) {
if ($this->request->is('mid') && 'insert' != $action) {
/** 更新模式 */
$meta = $this->db->fetchRow($this->select()
->where('mid = ?', $this->request->mid)
@@ -284,7 +284,7 @@ class Edit extends Metas implements ActionInterface
/** 取出数据 */
$category = $this->request->from('name', 'slug', 'description', 'parent');
$category['mid'] = $this->request->get('mid');
$category['slug'] = Common::slugName(empty($category['slug']) ? $category['name'] : $category['slug']);
$category['slug'] = Common::slugName(Common::strBy($category['slug'] ?? null, $category['name']));
$category['type'] = 'category';
$current = $this->db->fetchRow($this->select()->where('mid = ?', $category['mid']));

View File

@@ -33,6 +33,6 @@ class Related extends Metas
return $orderA <=> $orderB;
});
$this->stack = $this->getRows($ids);
$this->pushAll($this->getRows($ids));
}
}

View File

@@ -32,7 +32,7 @@ class Rows extends Metas
*/
public function execute()
{
$this->stack = $this->getRows($this->orders, $this->parameter->ignore);
$this->pushAll($this->getRows($this->orders, $this->parameter->ignore));
}
/**

View File

@@ -132,7 +132,7 @@ class Edit extends Metas implements ActionInterface
/** 取出数据 */
$tag = $this->request->from('name', 'slug');
$tag['type'] = 'tag';
$tag['slug'] = Common::slugName(empty($tag['slug']) ? $tag['name'] : $tag['slug']);
$tag['slug'] = Common::slugName(Common::strBy($tag['slug'] ?? null, $tag['name']));
/** 插入数据 */
$tag['mid'] = $this->insert($tag);
@@ -254,7 +254,7 @@ class Edit extends Metas implements ActionInterface
/** 取出数据 */
$tag = $this->request->from('name', 'slug', 'mid');
$tag['type'] = 'tag';
$tag['slug'] = Common::slugName(empty($tag['slug']) ? $tag['name'] : $tag['slug']);
$tag['slug'] = Common::slugName(Common::strBy($tag['slug'] ?? null, $tag['name']));
/** 更新数据 */
$this->update($tag, $this->db->sql()->where('mid = ?', $this->request->filter('int')->get('mid')));

View File

@@ -93,7 +93,7 @@ if (!defined('__TYPECHO_ROOT_DIR__')) {
* @property int $commentsPostInterval
* @property string $commentsHTMLTagAllowed
* @property bool $allowRegister
* @property bool $allowXmlRpc
* @property int $allowXmlRpc
* @property int $postsListSize
* @property bool $feedFullText
* @property int $defaultCategory

View File

@@ -114,7 +114,8 @@ class Upload extends Contents implements ActionInterface
'table.contents.cid = ?',
$this->request->filter('int')->get('cid')
)
->where('table.contents.type = ?', 'attachment'), [$this, 'push']
->where('table.contents.type = ?', 'attachment'),
[$this, 'push']
);
if (!$this->have()) {
@@ -132,7 +133,7 @@ class Upload extends Contents implements ActionInterface
$file['name'] = urldecode($file['name']);
}
$result = self::modifyHandle($this->row, $file);
$result = self::modifyHandle($this->toColumn(['cid', 'attachment', 'parent']), $file);
if (false !== $result) {
self::pluginHandle()->call('beforeModify', $result);
@@ -315,7 +316,7 @@ class Upload extends Contents implements ActionInterface
'allowFeed' => 1
];
if (isset($this->request->cid)) {
if ($this->request->is('cid')) {
$cid = $this->request->filter('int')->get('cid');
if ($this->isWriteable($this->db->sql()->where('cid = ?', $cid))) {
@@ -341,7 +342,6 @@ class Upload extends Contents implements ActionInterface
'url' => $this->attachment->url,
'permalink' => $this->permalink
]]);
}
}
}

View File

@@ -14,6 +14,7 @@ use Typecho\Widget;
use Typecho\Widget\Exception as WidgetException;
use Widget\Base\Comments;
use Widget\Base\Contents;
use Widget\Contents\Attachment\Unattached;
use Widget\Contents\Page\Admin as PageAdmin;
use Widget\Contents\Post\Admin as PostAdmin;
use Widget\Contents\Attachment\Admin as AttachmentAdmin;
@@ -406,17 +407,16 @@ class XmlRpc extends Contents implements ActionInterface, Hook
}
/** 对未归档附件进行归档 */
$unattached = $this->db->fetchAll($this->select()->where('table.contents.type = ? AND
(table.contents.parent = 0 OR table.contents.parent IS NULL)', 'attachment'), [$this, 'filter']);
$unattached = Unattached::alloc();
if (!empty($unattached)) {
foreach ($unattached as $attach) {
if (false !== strpos($input['text'], $attach['attachment']->url)) {
if ($unattached->have()) {
while ($unattached->next()) {
if (false !== strpos($input['text'], $unattached->attachment->url)) {
if (!isset($input['attachment'])) {
$input['attachment'] = [];
}
$input['attachment'][] = $attach['cid'];
$input['attachment'][] = $unattached->cid;
}
}
}
@@ -449,9 +449,9 @@ class XmlRpc extends Contents implements ActionInterface, Hook
{
/** 开始接受数据 */
$input['name'] = $category['name'];
$input['slug'] = Common::slugName(empty($category['slug']) ? $category['name'] : $category['slug']);
$input['slug'] = Common::slugName(Common::strBy($category['slug'] ?? null, $category['name']));
$input['parent'] = $category['parent_id'] ?? ($category['parent'] ?? 0);
$input['description'] = $category['description'] ?? $category['name'];
$input['description'] = Common::strBy($category['description'] ?? null, $category['name']);
/** 调用已有组件 */
$categoryWidget = CategoryEdit::alloc(null, $input, function (CategoryEdit $category) {
@@ -1753,7 +1753,7 @@ class XmlRpc extends Contents implements ActionInterface, Hook
throw new Exception(_t('请求的地址不存在'), 404);
}
if (isset($this->request->rsd)) {
if ($this->request->get('rsd') !== null) {
echo
<<<EOF
<?xml version="1.0" encoding="{$this->options->charset}"?>
@@ -1771,7 +1771,7 @@ class XmlRpc extends Contents implements ActionInterface, Hook
</service>
</rsd>
EOF;
} elseif (isset($this->request->wlw)) {
} elseif ($this->request->get('wlw') !== null) {
echo
<<<EOF
<?xml version="1.0" encoding="{$this->options->charset}"?>