2022年12月

点赞数功能没启用,主要是懒得弄了(要算一下点击、写一个ajax脚本,还要判断是否重复点击重复点击一般只算当前浏览器进程,否则还得再建一个表来存用户

插件启用后就是create table pagesviews,就是cid,views,likes建一个表而已,totalViews则是偷个懒,select sum(views) as views

于是一个最简单的插件就完成了。

其实本来是用fields的,在皮肤的themeConfig中就可以定位,然后会在每个文章里增加一个自定义字段,比如:浏览量,但想想没啥大必要(当然其实这么操作也行),适合当前文章不多的情况。如果你本身已经有几十上百篇文章,还是新建表更合理一些。

用fields的好处是直接在列表中就能够显示当前文章的阅读数,因为读fields是系统本身的功能,总之,各取所需吧

有时候看到别人的博客上都有一个当前文章总字数xxxx,阅读时长xx分钟,就想着是不是其实也可以为typecho加一个。看了一下还算简单,因为$this->content(),是echo后的输出,但事实上,你也能访问 $this->content ,于是代码就出来了

总字数:<?php echo mb_strlen($this->content);?>,阅读时长:<?php echo round(mb_strlen($this->content)/300,1);?>分钟

/300,代表我们认为每300个字,我们大约需要1分钟左右可以看完,如果你觉得1分钟可以看500字,可以改成/500
对于标准模板来说,这里 mb_strlen 运行了两次,如果是blade模板就方便了

@set($contentLength = mb_strlen($this->content))
总字数:{{$contentLength}},阅读时长{{ round($contentLength / 300 ,1)}}分钟

只是当我写完的时候,我想起来,其实php也可以设置变量啊。。。但也不想改上面的内容了。将就着看看吧

先申明这个插件不会放出来了,因为他已经不能算是一个真正意义上的插件了,因为懒得对着S3协议重写,以及设置一些变量的关系(laravel用惯了,而且就算之前在用Yii的时候,也大量采用了dotenv),所以,相当于嵌入太深,就自用算了。。。

如果你也有和和一样,typecho是部署在自己的服务器上,而不是虚拟空间的,可以运行composer的,那倒是可以尝试一下
1、composer require league/flysystem-aws-s3-v3
2、composer require vlucas/phpdotenv
基本上只要这两个就完事了
当然还需要在index.php和admin/common.php中引入 vendor和 dotenv

include 'vendor/autoload.php'; //admin中引用的时候,要注意路径
$repository = Dotenv\Repository\RepositoryBuilder::createWithNoAdapters()
                                                 ->addAdapter(Dotenv\Repository\Adapter\EnvConstAdapter::class)
                                                 ->addWriter(Dotenv\Repository\Adapter\PutenvAdapter::class)
                                                 ->immutable()
                                                 ->make();
$dotenv = Dotenv\Dotenv::create($repository, __DIR__);//这里同样要注意
$dotenv->load();

于是你就可以在你的任意代码里开始使用s3的代码了。
为什么非要用flysystem,也是因为他有一个manager的概念,可以直接将所有支持的adapter一起管理起来,比如:local/oss/cos/qiniu,我想到存哪里的时候就存到哪里。

$manager = new \League\Flysystem\MountManager([
                'local' => $this->getLocalAdapter(),
                'oss'   => $this->getOssAdapter(),
                'cos'   => $this->getCosAdapter(),
                'qiniu' => $this->getQiniuAdapter(),
            ]);

在通过扩展upload中对应的handle就可以将文件存到不同的地方了,为了侵入性更小一点,我只触发了其中的三项

//\Typecho\Plugin::factory('Widget_Upload')->uploadHandle = __CLASS__."::upload";
\Typecho\Plugin::factory('Widget_Upload')->deleteHandle = __CLASS__."::delete"; //用于接管删除附件
\Typecho\Plugin::factory('Widget_Upload')->attachmentHandle = __CLASS__."::attachment";//用于上传后显示对应的云存储的网址
//        \Typecho\Plugin::factory('Widget_Upload')->attachmentDataHandle = __CLASS__."::attachmentdata";
\Typecho\Plugin::factory('Widget_Upload')->beforeUpload = __CLASS__."::beforeupload";//在存入数据库前传到云平台
//        \Typecho\Plugin::factory('Widget_Upload')->upload = __CLASS__."::afterUpload";

就这样,一个附件上传到兼容s3协议的云存储就搞定了
目前国内支持S3协议的大致有:oss/cos/bos?/qiniu,国外有r2等,
可能还有一些其他的,但也没有细研究,除了cos的配置和其他有点不太一样外,都是标准协议,可以放心大胆的用

标题所示插件的具体效果应该通过首页可以看到了,用法很简单。如果是不想修改直接拿来用的话,直接解压缩,然后放到usr/plugins/的CalendarGraph目录下

直接使用的话:

在需要显示的位置:<?php \Typecho\Plugin::factory('index.php')->renderGraph() ;?> 
<?php \Typecho\Plugin::factory('index.php')->renderCalendar() ;?> 

但在实际应用中,我的这个插件样式肯定不可能和你们现有的模版皮肤相配套,所以其实你们可以参考我的这两个函数的写法,直接在模板里面写上

<style>
    #tooltip {zindex: 999;display: none;position: fixed;left: 0;top: 0;height: 25px;background-color: rgba(0, 0, 0, .8);color: #fff;padding: 4px 10px;border-radius: 3px;font-size: 12px}
    #tooltip:after {display: block;position: absolute;content: '';bottom: -6px;left: 50%;margin-left: -6px;width: 0; height: 0; border-left: 6px solid transparent;border-right: 6px solid transparent;border-top: 6px solid rgba(0, 0, 0, .8);}
    svg {width: 100%}</style>
<div id='svg' ></div><div id='tooltip'></div>
<?php
        $item = json_encode(\TypechoPlugin\CalendarGraph::getGraphData());
        echo "<script>const graphOption = {tooptipId: '#tooltip',graphId : '#svg' ,tooltipFormat: '{1}篇内容于{0}发表',graphData:{$item}, option: {}};</script>";
?>
<script src='<?php echo Options::alloc()->rootUrl;?>.'/usr/plugins/CalendarGraph/bundle.js' defer></script>

就是这样简单,下载地址为:CalendarGraph.zip

开篇

在博客开博之前,我就写了一篇:新平台,启用blade模板,是的,你看到的现在的皮肤感觉和官方没有两样,其实是我使用blade模板完全重新制作了一份。

Blade模板功能介绍

目录结构:

.
|____functions.php
|____404.php
|____index.php
|____archive.php
|____cache
| |____.gitignore
|____post.php
|____grid.css
|____screenshot.png
|____img
| |____icon-search.png
| |____icon-search@2x.png
|____style.css
|____page.php
|____views
| |____index.blade.php
| |____404.blade.php
| |____comments.blade.php
| |____post.blade.php
| |____page.blade.php
| |____archive.blade.php
| |____sidebar.blade.php
| |____layouts.blade.php
|____normalize.css
|____blade.php

抛开css/image和functions.php这些内容不看,你看到的,似于是和官方的相比只多了两个目录+一个blade.php文件,这其中,cache是blade用来生成编译后的缓存目录,views目录就是我们的blade模板。

Blade

打开blade.php,会发现其中的代码不多,就几行

<?php
if (!defined('__TYPECHO_ROOT_DIR__')) {
    exit;
}
if (!class_exists('TypechoPlugin\BladeOne') && !Typecho\Plugin::exists('Blade')) {
    exit('请先启用Blade模板插件');
}

use TypechoPlugin\BladeOne;


try {
    $views = __DIR__.'/views';
    $cache = __DIR__.'/cache';
    $blade = new BladeOne($views, $cache, BladeOne::MODE_DEBUG);
    $blade->includeScope = true;
    $blade->pipeEnable = true;
    $blade->setBaseUrl($this->options->themeUrl);
    echo $blade->run((isset($viewFile) ? $viewFile : 'index'), [
        'view'      => $this,
        'options'   => $this->options,
        'user'      => $this->user,
//        'converter' => $converter,
    ]);
} catch (Exception $e) {
    exit($e->getMessage());
}

这个BladeOne,来自于EFTEC/BladeOne,这个模板是我从yii时代就开始用了,因为那时候用过laravel后,就感觉离不开这玩意了,于是我就自己抠了一份laravel的blade库,但后面发现要改的内容太多了,这时候发现了这个EFTEC/BladeOne后,就在非laravel项目中使用上了它。

对于普通用户来说,我们只需要下载他的lib目录下的BladeOne.php这一个文件就行了,如果你不想单独写插件,就将文件放到usr/plugins目录下,同时修改一下BladeOne.php的命名空间,以及对应HelloWorld插件的标准typecho插件注释,这时候你到后台管理插件模块下,你就会发现BladeOne是一个即插即用插件了。

插件是启用了,但模板并不知道,对吧?所以我们在blade.php中加了一个判断 if (!class_exists('TypechoPlugin\BladeOne') || !Typecho\Plugin::exists('Blade')),如果没有引入Blade,就直接不能使用,这样也挺方便。

在上面的blade.php中,有几个变量和参数

$views / $cache 代表了模板目录和编译后的缓存目录,这个可以自己随便定义,我只是用了一个相对标准的目录罢了
BladeOne的第三个参数,默认是使用BladeOne::MODE_DEBUG,事实上他有4个参数,如果觉得没问题或者已经修改的很稳定了。就使用MODE_FAST,否则MODE_AUTO也OK

接下来的三个参数就是blade中的用法了

$blade->includeScope = true。这个默认是false,你也可以删除这一行,删除后,传入的变量就只能在当前的blade模板里使用。https://github.com/EFTEC/BladeOne/wiki/Template-inheritance
$blade->pipeEnable 在linux下面大家都知道,管道的用法,例如最常见的 cat xxx.log|grep "php",这个也是类似的用法,比如 {{$name | ucwords }}就可以直接首字母大写了,是不是很方便?
$blade->setBaseUrl($this->options->themeUrl),这个是为了使用 @asset / @relative等方法,参考:asset官方用法,一般情况下,我们其实不会调用其他项目目录里的文件,所以 @asset 就足够了,例如当前目录下的 img/logo.png,正常写法是 <?php echo $this->options->themeUrl('img/logo.png');?> ,在使用 blade并且设置了baseUrl时,就可以 @asset('img/logo.png'),一个模板文件下来,至少少写上百个字符了。

最重要的赋值

    echo $blade->run((isset($viewFile) ? $viewFile : 'index'), [
        'view'      => $this,
        'options'   => $this->options,
        'user'      => $this->user,
    ]);

在这里我定义了一个变量$viewFile,如果这个变量不存在,默认就是使用index.blade.php模板,然后传入了3个参数 view/options/user,因为这三个变量确实是几乎每个模板都会用得到。为什么要传入呢?主要还是由于blade已经不再是标准PHP模板了,所以他里面就不能用 $this 变量(以前在Yii中也是这样的用法)

其他文件的写法

着重介绍完blade.php的内容,那么其他和官方模板一致文件名的内容又是怎样的呢?这里就只介绍index.php了

<?php
if (!defined('__TYPECHO_ROOT_DIR__')) {
    exit;
}
$viewFile = "index";
include 'blade.php';

LOOK,是不是很简单?去掉那个判断,其实就2行代码,甚至if判断都可以不需要,因为在blade.php中也有判断。这里其实就只定义一下$viewFile,代表告诉入口文件,我应该用哪个模板。

结尾

blade的引擎,刚才提到的BladeOne的github上面,有详细的用法,如果你觉得不够详细,也可以看laravel关于blade的文档
本篇就不做详细介绍了