typecho主题handsome

目录

为typecho主题handsome添加文章分页功能

功能思路来源于插件 SplitArchivePage,只需要修改handsome主题的文件 typecho/usr/themes/handsome/libs/Content.php

1. 在文件最后一个 } 前添加如下两个私有函数

请注意:添加代码前删掉如下代码中第4行 splitword变量的值中 page 前后的两个空格。

    // 文章分页
    private static function parse($text)
    {
        $pagebar = '';
        $content = $text;
        $splitword = '= page =';
        if( strpos( $text , $splitword) !== false){
            $contents = explode($splitword , $text );
            $page = isset($_GET['page'])?intval($_GET['page']):1;
            $content = $contents[$page-1];
            $request = Typecho_Request::getInstance();
            $_GET['page'] = '{page}';
            $pagebar = self::setPageBar(count($contents),$page,$request->getPathinfo()."?".  http_build_query($_GET));  
        }

        $text = $content.$pagebar;
        return $text;
    }

    private static function setPageBar($pageTotals,$page,$pageTemplate)
    {
        $isRewrite = Typecho_Widget::widget('Widget_Options')->rewrite;
        $siteUrl = Typecho_Widget::widget('Widget_Options')->siteUrl;
        $pageTemplate = ($isRewrite ? rtrim($siteUrl, '/') : $siteUrl."index.php") . $pageTemplate;
        $splitPage = 3;
        $pageHolder = array('{page}', '%7Bpage%7D');
        if ($pageTotals < 1) { return; }

        $pageBar .= '<nav class="text-center m-t-lg m-b-lg" role="navigation"><ol class="page-navigator">';

        if ($page > 1) {
            $pageBar .= '<li class="prev"><a href="' . str_replace($pageHolder, $page - 1, $pageTemplate) . '">' . '<i class="fontello fontello-chevron-left"></i></a></li>';
        }

        for ($i = 1; $i <= $pageTotals; $i ++) {
            if($page==$i){
                $pageBar .= '<li class="current"><a href="' . str_replace($pageHolder, $i, $pageTemplate) . '" ' . ($i != $page ? '' : '') . '>' . $i . '</a></li>';
            }else
            {if((($i==$page-3) and ($i!=1)) or (($i==$page+3) and ($i!=$pageTotals)))
                {
                    $pageBar .= '<li><span>...</span></li>';
                }else{
                    if((($i<$page-3) and ($i!=1)) or (($i>$page+3) and ($i!=$pageTotals)))
                    {}else{
                        $pageBar .= '<li><a href="' . str_replace($pageHolder, $i, $pageTemplate) . '" ' . ($i != $page ? '' : '') . '>' . $i . '</a></li>';
                    }
                }
            }
        }
        if ($page < $pageTotals) {
            $pageBar .= '<li class="next"><a href="' . str_replace($pageHolder, $page + 1, $pageTemplate) . '">' . '<i class="fontello fontello-chevron-right"></i></a></li>';
        }
        $pageBar .='</ol></nav><style>.page-navigator>li>a, .page-navigator>li>span{ background: #EFEFEF; line-height: 1.42857143; padding: 6px 12px; border-bottom-style:none !important; }</style>';

        return $pageBar;
    }

2.修改 parseContentPublic函数的定义,添加一个参数 $need2pagination 并提供默认值

在文件 Content.php 的1987行前后。

默认值为 False , 所以默认情况下不会调用内容分页函数避免对handsome原来的处理逻辑造成影响,比如评论者在评论内容中插入分页符就比较尴尬了 😈

   public static function parseContentPublic($content,$need2pagination=False)

3. 在 parseContentPublic 函数中调用分页函数

在文件 Content.php 的2092行前后,语句 return $content; 之前增加一行代码,代码片段:

        //文章分页
        if ($need2pagination==True) { $content=self::parse($content);}

        return $content;
    }

4. 对于需要分页的内容,修改调用parseContentPublic 函数,添加 need2pagination参数

比如需要提供 post 的分页功能,仅需要修改如下函数 public static function postContent($obj, $status, $way = "origin"), 搜索文本 该部分仅登录用户可见 , 在文件 Content.php 的2092行前后:

            // $content = Content::parseContentPublic($content); //该行修改前语句,下一行为修改后语句
            $content = Content::parseContentPublic($content,True);
        }
        return trim($content);
    }

目前只测过对 post 分页,page等其他内容的分页没测试过。其他内容如果需要分页,同样可以用 ($content,True) 参数试试,风险自担。

5.在文章中分页

文章需要分页处添加行 = page = (注意去掉page前后的空格)。 当然也可以修改代码换成其他标识。

效果:

👾 这里是第1页内容 👾

=page=

👾 这里是第2页内容 👾

=page=

👾 这里是第3页内容 👾

=page=

👾 这里是第4页内容 👾

=page=

👾 这里是第5页内容 👾

=page=

👾 这里是第6页内容 👾

=page=

👾 这里是第7页内容 👾

=page=

👾 这里是第8页内容 👾

=page=

👾 这里是第9页内容 👾

=page=

👾 这里是第10页内容 👾

=page=

👾 这里是第11页内容 👾

=page=

👾 这里是第12页内容 👾

左侧导航栏支持的其中2种图标和使用方式

  1. bootcss自带的Glyphicons字体图标

使用:

{"name":"音乐","class":"glyphicon glyphicon-music","link":"xxx.com"},

2.feather图标

使用:

{"name":"音乐2","feather":"music","link":"xxx.com"},

网站美化

参考 handsome主题美化记录 - 持续更新 修改的内容

以下代码的后台使用的是 PostgreSQL数据库

1. 访客总数统计

vi /typecho/usr/themes/handsome/functions.php ,文件中添加以下统计代码.

//总访问量
    function theAllViews()
        {
            $db = Typecho_Db::get();
            $row = $db->fetchAll('SELECT SUM(VIEWS) FROM typecho_contents');
            $result= implode(",",$row[0]);
            echo $result;
        }

vi /typecho/usr/themes/handsome/component/sidebar.php 文件中插入以下调用代码, 在博客信息,评论数目的下方.

<li class="list-group-item"> <i class="glyphicon glyphicon-user text-muted"></i> <span class="badge
pull-right"><?php  theAllViews();?></span><?php _me("访客总数") ?></li>

如果使用了静态缓存,显示的不是实时数据,可以考虑不用该功能。

2.去除顶部博客名称

vi /typecho/usr/themes/handsome/index.php文件,位于公告位置下方,删除以下代码:

<h1 class="m-n font-thin h3 text-black l-h"><?php $this->options->title(); ?></h1>

3.网站加载耗时

vi /typecho/usr/themes/handsome/functions.php ,文件中添加以下统计代码

//加载耗时
    function timer_start() {
        global $timestart;
        $mtime     = explode( ' ', microtime() );
        $timestart = $mtime[1] + $mtime[0];
        return true;
    }
    timer_start();
    function timer_stop( $display = 0, $precision = 3 ) {
        global $timestart, $timeend;
        $mtime     = explode( ' ', microtime() );
        $timeend   = $mtime[1] + $mtime[0];
        $timetotal = number_format( $timeend - $timestart, $precision );
        $r         = $timetotal < 1 ? $timetotal * 1000 . " ms" : $timetotal . " s";
        if ( $display ) {
            echo $r;
        }
        return $r;
    }

vi /typecho/usr/themes/handsome/component/sidebar.php 文件中插入以下调用代码,在最后活动的后方:

<li class="list-group-item"> <i class="glyphicon glyphicon-time text-muted"></i> <span class="badge
pull-right"><?php echo timer_stop();?></span><?php _me("加载耗时") ?></li>

如果使用了静态缓存,显示的不是实时数据,可以考虑不用该功能。

4.彩色标签云

参考Typecho handsome主题自定义修改标签云颜色

更改方法:打开后台-更改外观-设置外观-开发者设置-选择好需要的颜色粘贴至自定义JavaScript即可.颜色也可以自行设定,修改代码中的十六进制颜色值即可

<!--纯黑标签云-->
let tags = document.querySelectorAll("#tag_cloud-2 a");
let colorArr = ["#000000", "#000000", "#000000", "#000000", "#000000", "#000000"];
tags.forEach(tag => {
    tagsColor = colorArr[Math.floor(Math.random() * colorArr.length)];
    tag.style.backgroundColor = tagsColor;
});

<!--银白标签云-->
let tags = document.querySelectorAll("#tag_cloud-2 a");
let colorArr = ["#C0C0C0", "#C0C0C0", "#C0C0C0", "#C0C0C0", "#C0C0C0", "#C0C0C0"];
tags.forEach(tag => {
    tagsColor = colorArr[Math.floor(Math.random() * colorArr.length)];
    tag.style.backgroundColor = tagsColor;
});

<!--淡蓝标签云-->
let tags = document.querySelectorAll("#tag_cloud-2 a");
let colorArr = ["#ADD8E6", "#ADD8E6", "#ADD8E6", "#ADD8E6", "#ADD8E6", "#ADD8E6"];
tags.forEach(tag => {
    tagsColor = colorArr[Math.floor(Math.random() * colorArr.length)];
    tag.style.backgroundColor = tagsColor;
});

<!--彩色标签云-->
let tags = document.querySelectorAll("#tag_cloud-2 a");
let colorArr = ["#428BCA", "#AEDCAE", "#ECA9A7", "#DA99FF", "#FFB380", "#D9B999"];
tags.forEach(tag => {
    tagsColor = colorArr[Math.floor(Math.random() * colorArr.length)];
    tag.style.backgroundColor = tagsColor;
});

<!--天蓝标签云-->
let tags = document.querySelectorAll("#tag_cloud-2 a");
let colorArr = ["#00BFFF", "#00BFFF", "#00BFFF", "#00BFFF", "#00BFFF", "#00BFFF"];
tags.forEach(tag => {
    tagsColor = colorArr[Math.floor(Math.random() * colorArr.length)];
    tag.style.backgroundColor = tagsColor;
});

说明:如果主题中启用了pjax,还需要将上面代码添加到pjax-pjax回调函数中

5.页面尽量占满屏幕

  • 方案一: handsome主题后台添加如下类似的自定义css,推荐该方案
  • 方案二: 修改 handsome/assets/css/handsome.min.css
@media (min-width:1200px) {
    .app.container {
        width: 1170px
    }

    .app.container .app-aside,
    .app.container .app-header {
        /* max-width: 1170px */
        max-width: 1920px
    }

    .app.container .app-footer-fixed {
        /* max-width: 970px */
        max-width: 1920px
    }

    .app.container.app-aside-folded .app-footer-fixed {
        /* max-width: 1110px */
        max-width: 1920px
    }

    .app.container.app-aside-dock .app-footer-fixed {
        /* max-width: 1170px */
        max-width: 1920px
    }
}

@media (min-width:1800px) {
    .app.container {
        /* width: 1300px */
        width: 1800px
    }

    .app.container .app-aside,
    .app.container .app-header {
        /* max-width: 1300px */
        max-width: 3840px
    }

    .index-image,
    .item-thumb {
        min-height: 280px
    }
}

6.不显示隐藏内容提示

对于用 [login] [/login] 隐藏的内容,不显示 该部分仅登录用户可见

编辑: /typecho/usr/themes/handsome/libs/Content.php

//仅登录用户可查看的内容
            if (strpos($content, '[login') !== false) {//提高效率,避免每篇文章都要解析
                $pattern = self::get_shortcode_regex(array('login'));
                $isLogin = $status;
                $content = Utils::handle_preg_replace_callback("/$pattern/", function ($matches) use ($isLogin) {
                    // 不解析类似 [[player]] 双重括号的代码
                    if ($matches[1] == '[' && $matches[6] == ']') {
                        return substr($matches[0], 1, -1);
                    }
                    if ($isLogin) {
                        return '<p>' . $matches[5] . '</p>';
                        // return '<div class="hideContent">' . $matches[5] . '</div>';
                    } //else {
                        // return '<div class="hideContent">' . _mt("该部分仅登录用户可见") . '</div>';
                    //}
                }, $content);
            }

7.删除 闲言碎语

vi /typecho/usr/themes/handsome/component/headnav.php ,删除 <?php echo $headerItemsOutput; ?> 下的如下语句:

        <?php echo $headerItemsOutput; ?>
        <?php if (!$hideTalkItem): ?>
            <!--闲言碎语-->

                ......

            <!--/闲言碎语-->
        <?php endif; ?>

搜索不到结果,一直提示「无相关搜索结果」?

需要在Handsome插件里面构建一下搜索的索引.

构建索引时提示 Handsome插件下的cache文件夹没有写入权限,查看Handsome下是否有cache文件夹,并给777权限

chmod -R 777 /typecho/usr/plugins/Handsome/cache

百度自动推动代码

参考 百度帮助文档, 在handsome主题设置的 自定义 JavaScript 添加如下代码:

<script>
(function(){
    var bp = document.createElement('script');
    var curProtocol = window.location.protocol.split(':')[0];
    if (curProtocol === 'https'){
   bp.src = 'https://zz.bdstatic.com/linksubmit/push.js';
  }
  else{
  bp.src = 'http://push.zhanzhang.baidu.com/push.js';
  }
    var s = document.getElementsByTagName("script")[0];
    s.parentNode.insertBefore(bp, s);
})();
</script>

统计代码

不要按百度,google或handsome主题的提示将js代码放head部分, 要放在 </body> 标签前,否则严重拖累页面加载速度。

1.百度统计代码

在handsome主题设置的 自定义 JavaScript 输入统计代码.

百度统计 获取代码, 安装位置不要参考百度说明

2.google统计代码:类似,放在页面尾部.

备案信息

在主题设置的 博客底部左侧信息 插入类似如下的代码(不含 div):

&nbsp; &nbsp;<a target="_blank" href="http://www.beian.gov.cn/portal/registerSystemInfo?recordcode=51019002002885" style="display:inline-block;text-decoration:none;"><img src="/attachment/2010/beian_ghs.png" style="float:left;"/>川公网安备 51019002002885号</a>&nbsp; &nbsp;<a href="http://www.beian.miit.gov.cn" rel="external nofollow" target="_blank">蜀ICP备20011690号</a>

删除版权信息: Powered by Typecho | Theme by handsome

vi /typecho/usr/themes/handsome/component/footer.php

<span class="pull-right hidden-xs text-ellipsis">
      <?php $this->options->BottomInfo(); ?>
      <!--
        Powered by <a target="_blank" href="http://www.typecho.org">Typecho</a>&nbsp;|&nbsp;Theme by <a target="_blank"
                                                                                                     href="https://www.ihewro.com/archives/489/">handsome</a>
      -->
        </span>

其他

  • pjax回调函数不要加标签 <script>,</script>

typecho主题handsome自定义头图

仅需要修改文件 usr/themes/handsome/libs/Content.php

1.修改函数 whenSwitchHeaderImgSrc

查找函数: 搜索文字 public static function whenSwitchHeaderImgSrc( .

修改变量 $random 的生成方式,代码片段:

    public static function whenSwitchHeaderImgSrc($widget, $index = 0, $howToThumb, $attach, $content, $thumbField)
    {

        // $randomNum = unserialize(INDEX_IMAGE_ARRAY);

        // 随机缩略图路径
        // $random = STATIC_PATH . 'img/sj/' . @$randomNum[$index] . '.jpg';//如果有文章置顶,这里可能会导致index not undefined
        $num = mt_rand( 1, 23);  // 23 为自定义的图片数量

        switch( $num )
        {
        case 1: $image_file = "https://xxx.jpg";break; //自定义的图片地址
        case 2: $image_file = "https://yyy.jpg";break;
        ......
        case 23: $image_file = "https://zzz.jpg";break;
        };
        $random = $image_file;

2.修改函数 echoPostList

首页等位置的图片存在重复的情况,继续修改函数 exportHeaderImg,查找文字 public static function echoPostList(.

while ($obj->next()) { 语句前定义可用图片列表, 可以和 函数 whenSwitchHeaderImgSrc 的图片列表完全不同,代码片段

        // 可用头图列表
        $imgSrcUnused = array("https://aaaa.jpg",
        "bbb",
        ......
        "https://ccc.jpg");


        while ($obj->next()) {

替换语句 $parameterArray['imgSrc'] = Content::returnHeaderImgSrc($obj, "index", $index);, 代码片段:

            // $parameterArray['imgSrc'] = Content::returnHeaderImgSrc($obj, "index", $index);
            $imgSrcID = array_rand($imgSrcUnused);
            $parameterArray['imgSrc'] = $imgSrcUnused[$imgSrcID];
            unset($imgSrcUnused[$imgSrcID]);

            $parameterArray['linkUrl'] = $obj->permalink;

3.其他:图床的选择

typecho主题handsome更换markdown解析器为 parsedown

typecho的默认解析器有很多基本的功能支持的并不好,比如不支持行内html标签,必须用 !!! 标识.

方式一:直接使用插件,推荐

插件: https://github.com/mrgeneralgoo/typecho-markdown

方式二:使用erusev 提供的parsedown, 没有方式一魔改后支持的功能多

1.从 这里 下载 Parsedown.php ; 从 这里 下载ParsedownExtra.php

2.将 Parsedown.phpParsedownExtra.php 两个文件放到 functions.php 同一目录下

3.修改主题的 functions.php, 添加 class theme_plugin:

/*表单组件*/
require("libs/admin/FormElements.php");
require('libs/admin/Checkbox.php');
require('libs/admin/Text.php');
require('libs/admin/Radio.php');
require('libs/admin/Select.php');
require('libs/admin/Textarea.php');

// 如下替换md解析器
class theme_plugin{
    public static function markdown($text){
        require_once 'Parsedown.php'; 
        require_once 'ParsedownExtra.php';
        return Parsedown::instance()->setBreaksEnabled(true)->text($text);
    }
}

4.修改 functions_mine.php,增加如下两行:

function themeInit($archive)
{
    // 如下两行替换md解析器
    Typecho_Plugin::factory('Widget_Abstract_Contents')->markdown = ['theme_plugin', 'markdown'];
    Typecho_Plugin::factory('Widget_Abstract_Comments')->markdown = ['theme_plugin', 'markdown'];

基于PostgreSQL安装typecho(主题handsome)

ubuntu20.04 下安装 postgresql13

参考: https://www.postgresql.org/download/linux/ubuntu/

sudo sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list'

# Import the repository signing key:
wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add -

# Update the package lists:
sudo apt-get update

# Install the latest version of PostgreSQL.
# If you want a specific version, use 'postgresql-12' or similar instead of 'postgresql':
sudo apt-get -y install postgresql-13 php-pgsql

安装完成后,默认会:

(1)创建名为"postgres"的Linux用户

(2)创建名为"postgres"、不带密码的默认数据库账号作为数据库管理员

(3)创建名为"postgres"的表

修改数据库密码:

Step1: 切换用户为postgres

  sudo su postgres

Step2: 用postgres连接postgreSQL

  psql -U postgres

Step3: 修改postgres密码

  alter user postgres with password 'new password';

现在postgres用户就拥有了新密码

windows10下安装 postgresql13

安装版下载: https://www.enterprisedb.com/downloads/postgres-postgresql-downloads

安装目录: C:\PostgreSQL\13 ,数据目录: C:\PostgreSQL\13\data ,安装信息:

Installation Directory: C:\PostgreSQL\13
Server Installation Directory: C:\PostgreSQL\13
Data Directory: C:\PostgreSQL\13\data
Database Port: 5432
Database Superuser: postgres
Operating System Account: NT AUTHORITY\NetworkService
Database Service: postgresql-x64-13
Command Line Tools Installation Directory: C:\PostgreSQL\13
pgAdmin4 Installation Directory: C:\PostgreSQL\13\pgAdmin 4
Stack Builder Installation Directory: C:\PostgreSQL\13

安装 php,nginx

参考 https://yinhe.co/archives/20201022_typecho.html 安装

安装typecho

一定要从github下载master分支代码安装, typecho的稳定版本对postgresql支持有问题,很多bug是在master版本中修复的。

handsome主题500错误

错误提示:Database Query Error

主题官方文档帮助信息:

安装完主题后,页面中间部分空白/安装后首页报错,500错误
依次检查主题文件夹名称是否为handsome,插件文件夹名称为Handsome(首字母大写)
是否已经安装并且启用主题必要的插件Handsome
php版本5.5+,必须安装mbstring和openssl扩展,否则无法使用(正确检查方式是在你的服务器新建一个test.php,然后复制粘贴该代码<?php phpinfo(); ?>,最后在浏览器访问该文件可以查看服务器的php信息)
主题目录下面的lisence文件给777权限,包括递归子文件夹和子文件,也可以尝试给644或者755权限。因为有的服务器上传文件之后,默认给的权限php都没办法执行的
最后确保自己上传的文件没有缺失,比如handsome文件夹下有fucntions.php,Handsome文件夹下有Plugin.php 则是最起码的
初次使用主题必须保证博客有一篇文章,如果一篇文章都没有的话会导致向数据库中添加view(浏览次数字段)失败
PostgreSql数据库安装links插件时,需要手动新建typechos_links表。(会在下一个版本修复)

解决方式:

CREATE TABLE typecho_contents ( "cid" INTEGER NOT NULL PRIMARY KEY,
"title" varchar(200) default NULL ,
"slug" varchar(200) default NULL ,
"created" int(10) default '0' ,
"modified" int(10) default '0' ,
"text" text ,
"order" int(10) default '0' ,
"authorId" int(10) default '0' ,
"template" varchar(32) default NULL ,
"type" varchar(16) default 'post' ,
"status" varchar(16) default 'publish' ,
"password" varchar(32) default NULL ,
"commentsNum" int(10) default '0' ,
"allowComment" char(1) default '0' ,
"allowPing" char(1) default '0' ,
"allowFeed" char(1) default '0' ,
"parent" int(10) default '0' , `views` INT(10) DEFAULT 0, `agree` INT(10) NOT NULL DEFAULT 0)

CREATE TABLE `typecho_links` (
  `lid` INTEGER NOT NULL PRIMARY KEY,
  `name` varchar(200) default NULL,
  `url` varchar(200) default NULL,
  `sort` varchar(200) default NULL,
  `image` varchar(200) default NULL,
  `description` varchar(200) default NULL,
  `user` varchar(200) default NULL,
  `order` int(10) default '0'
)

2.供创建和修改表参考的:PostgreSQL中contents表创建信息

-- Table: public.typecho_contents

-- DROP TABLE public.typecho_contents;

CREATE TABLE public.typecho_contents
(
    cid integer NOT NULL DEFAULT nextval('typecho_contents_seq'::regclass),
    title character varying(150) COLLATE pg_catalog."default" DEFAULT NULL::character varying,
    slug character varying(150) COLLATE pg_catalog."default" DEFAULT NULL::character varying,
    created integer DEFAULT 0,
    modified integer DEFAULT 0,
    text text COLLATE pg_catalog."default",
    "order" integer DEFAULT 0,
    "authorId" integer DEFAULT 0,
    template character varying(32) COLLATE pg_catalog."default" DEFAULT NULL::character varying,
    type character varying(16) COLLATE pg_catalog."default" DEFAULT 'post'::character varying,
    status character varying(16) COLLATE pg_catalog."default" DEFAULT 'publish'::character varying,
    password character varying(32) COLLATE pg_catalog."default" DEFAULT NULL::character varying,
    "commentsNum" integer DEFAULT 0,
    "allowComment" character(1) COLLATE pg_catalog."default" DEFAULT '0'::bpchar,
    "allowPing" character(1) COLLATE pg_catalog."default" DEFAULT '0'::bpchar,
    "allowFeed" character(1) COLLATE pg_catalog."default" DEFAULT '0'::bpchar,
    parent integer DEFAULT 0,
    CONSTRAINT typecho_contents_pkey PRIMARY KEY (cid),
    CONSTRAINT typecho_contents_slug_key UNIQUE (slug)
)

TABLESPACE pg_default;

ALTER TABLE public.typecho_contents
    OWNER to postgres;
-- Index: typecho_contents_created

-- DROP INDEX public.typecho_contents_created;

CREATE INDEX typecho_contents_created
    ON public.typecho_contents USING btree
    (created ASC NULLS LAST)
    TABLESPACE pg_default;

3.修改pg中的contents表

ALTER TABLE typecho_contents ADD COLUMN IF NOT EXISTS views integer DEFAULT 0;
CREATE SEQUENCE typecho_links_seq
    INCREMENT 1
    START 1
    MINVALUE 1
    MAXVALUE 9223372036854775807
    CACHE 1;

CREATE TABLE typecho_links (
  lid integer NOT NULL DEFAULT nextval('typecho_links_seq'::regclass),
  name character varying(150) COLLATE pg_catalog."default" DEFAULT NULL::character varying,  
  url character varying(150) COLLATE pg_catalog."default" DEFAULT NULL::character varying,
  sort character varying(150) COLLATE pg_catalog."default" DEFAULT NULL::character varying,
  image character varying(150) COLLATE pg_catalog."default" DEFAULT NULL::character varying,
  description character varying(150) COLLATE pg_catalog."default" DEFAULT NULL::character varying,
  "user" character varying(150) COLLATE pg_catalog."default" DEFAULT NULL::character varying,
  "order"  integer DEFAULT 0
);

5.handsome主题下加载首页继续报错

错误信息类似:

SQLSTATE[22003]: Numeric value out of range: 7 错误:  值 "-8639999998396321618" 超出类型 integer 的范围
LINE 1: ...ECT *  FROM typecho_contents WHERE  ("created" >= '-86399999...
                                                             ^
Typecho_Db_Query_Exception: SQLSTATE[22003]: Numeric value out of range: 7 错误:  值 "-8639999998396321618" 超出类型 integer 的范围
LINE 1: ...ECT *  FROM typecho_contents WHERE  ("created" >= '-86399999...
                                                             ^ in D:\博客\typecho_local\typecho\var\Typecho\Db\Adapter\Pdo.php:105
Stack trace:
#0 D:\博客\typecho_local\typecho\var\Typecho\Db\Adapter\Pdo\Pgsql.php(111): Typecho_Db_Adapter_Pdo->query('SELECT *  FROM ...', Object(PDO), 1, 'SELECT', 'typecho_content...')
#1 D:\博客\typecho_local\typecho\var\Typecho\Db.php(367): Typecho_Db_Adapter_Pdo_Pgsql->query('SELECT *  FROM ...', Object(PDO), 1, 'SELECT', 'typecho_content...')
#2 D:\博客\typecho_local\typecho\var\Typecho\Db.php(398): Typecho_Db->query(Object(Typecho_Db_Query), 1)
#3 D:\博客\typecho_local\typecho\usr\themes\handsome\libs\Content.php(3068): Typecho_Db->fetchAll(Object(Typecho_Db_Query))
#4 D:\博客\typecho_local\typecho\usr\themes\handsome\component\sidebar.php(24): Content::returnHotPosts(Object(Widget_Archive))
#5 D:\博客\typecho_local\typecho\var\Widget\Archive.php(1952): require('D:\\\xE5\x8D\x9A\xE5\xAE\xA2\\typec...')
#6 D:\博客\typecho_local\typecho\usr\themes\handsome\index.php(84): Widget_Archive->need('component/sideb...')
#7 D:\博客\typecho_local\typecho\var\Widget\Archive.php(2037): require_once('D:\\\xE5\x8D\x9A\xE5\xAE\xA2\\typec...')
#8 D:\博客\typecho_local\typecho\var\Typecho\Router.php(138): Widget_Archive->render()
#9 D:\博客\typecho_local\typecho\index.php(23): Typecho_Router::dispatch()
#10 {main}

handsome 7.3.1 主题bug, 修改 typecho\usr\themes\handsome\libs\Content.php 3043 行前后

public static function returnHotPosts($hot)
    {
        $options = mget();
        //$days = 99999999999999;
        // 365*40 = 14600
        $days = 14600;

为handsome主题部署PostgreSQL全文检索

结巴分词

参考:https://github.com/fxsjy/jieba

安装: pip install jieba

支持四种分词模式,后续选择搜索引擎模式

  • 精确模式,试图将句子最精确地切开,适合文本分析;
  • 全模式,把句子中所有的可以成词的词语都扫描出来, 速度非常快,但是不能解决歧义;
  • 搜索引擎模式,在精确模式的基础上,对长词再次切分,提高召回率,适合用于搜索引擎分词。
  • paddle模式,利用PaddlePaddle深度学习框架,训练序列标注(双向GRU)网络模型实现分词。同时支持词性标注

示例:

# -*- coding: utf-8 -*-
"""
Spyder Editor

This is a temporary script file.
"""

import jieba

aStr = "小明硕士毕业于中国科学院计算所,后在日本京都大学深造"

# 搜索引擎模式
seg_list = jieba.cut_for_search(aStr)
print(','.join(seg_list))

'''
小明,硕士,毕业,于,中国,科学,学院,科学院,中国科学院,计算,计算所,,,后,在,日本,京都,大学,日本京都大学,深造
'''

为handsome主题部署PostgreSQL全文检索

参考:

这里为使用PostgreSQL部署的typecho添加全文检索功能, typecho主题为 handsome(主题返回的搜索字符串会合并,不会用到很复杂的搜索).

1.修改表结构

ALTER TABLE typecho_contents ADD COLUMN tsv tsvector;
CREATE INDEX tsv_idx ON typecho_contents USING gin(tsv);

2.填充 tsv 列

tsvector 数据类型的格式类似:

'a':1,6,10 'and':8 'ate':9 'cat':3 'fat':2,11 'mat':7 'on':5 'rat':12 'sat':4
  • 'a':分词后的关键词
  • 1,6,10: 一个位置通常表示源词在文档中的定位。位置信息可以被用于邻近排名。位置值可以从 1 到 16383,更大的数字会被 16383替换。对于相同的词位出现的重复位置将被丢弃。
  • 关键词之间有空格:'a':1,6,10'and':8 之间有空格
  • 插入tsvector 数据类型数据时, 单引号要转义成双引号.
  • 嵌入的引号和反斜线必须被双写

具有位置的词位可以进一步地被标注一个权重,它可以是A、 B、C或D。 D是默认值并且因此在输出中不会显示:

 'a':1A 'cat':5 'fat':2B,4C

权重通常被用来反映文档结构,例如将主题词标记成与正文词不同。文本搜索排名函数可以为不同的权重标记器分配不同的优先级。

根据 typecho 的 title,tags,text生成 tsv,其中标题和tag对应分级A,内容对应分级B:

def segList2tsv(aStr,segList,weight):
    # 根据字符串生成tsv字符串的函数,位置仅找第一个
    # weight 取值 A,B,C
    tsv_list = []    
    if len(segList)>0:
        for word in segList:
            if word not in ["'",'"','\\',' ']:
                place = aStr.index(word)
                if place > 16383:
                    place=16383
                if place==0:
                    place=1

                word=word.replace("'","''").replace(r"\\",r"\\\\")
                tsv_list.append("''" + word + "'':" + str(place) + weight)

    if len(tsv_list) >0:
        tsv_str = " ".join(tsv_list)
        return tsv_str

    return ''

def get_seg(text):
    # 获取分词列表
    text = text.lower()

    words = []
    digitals = []

    lines = text.split('\n')

    for line in lines:
        # 获取英文单词
        temp_line = re.sub(r"[^A-Za-z]", " ", line.strip())
        temp_words = temp_line.split()
        if len(temp_words)>0:
            for word in temp_words:
                if len(word)>=2:
                    words.append(word)

        # 获取数字
        temp_line = re.sub(r"[^0-9]", " ", line.strip())
        temp_digitals = temp_line.split()
        if len(temp_digitals)>0:
            for d in temp_digitals:
                digitals.append(d)

    seg_list = list(jieba.cut_for_search(text))
    seg_list.extend(words)
    seg_list.extend(digitals)
    seg_list = list(set(seg_list))

    list_to_return = []
    for s in seg_list:
        if len(s)>1:
            list_to_return.append(s)

    return list_to_return

def tsv4typecho(title,tags,text):
    # 生成填充 typecho_contents 表的 tsv字段的值
    text_title_tags = title + ' '
    if len(tags)>0:
        for t in tags: 
            text_title_tags += t

    text_title_tags = text_title_tags.lower()

    # 隐藏 contents表text字段开头的文字
    text=text.replace('\n',' ').replace('<!--markdown-->','').lower()

    segList_title_tags = get_seg(text_title_tags)
    seg_list_text = get_seg(text)
    segList_text = list(set(seg_list_text) - set(segList_title_tags))

    # get tsv_list
    tsv_str=''
    tsv_str_title_tags = segList2tsv(text_title_tags,segList_title_tags,"A")
    tsv_str_text = segList2tsv(text,segList_text,"B")

    if (len(tsv_str_title_tags)) >0 :
        tsv_str += tsv_str_title_tags

    if (len(tsv_str_text)) >0 :
        if (len(tsv_str_title_tags)) >0 :
            tsv_str += ' ' + tsv_str_text   
        else:
            tsv_str += tsv_str_text

    return tsv_str

新建或更新 post时,调用该函数更新 tsv数据列。

3.查询语法

搜索语句:

SELECT * FROM typecho_contents WHERE tsv @@ '搜索字符串'

搜索字符串由布尔运算符 & (AND), | (OR)! (NOT)分离的单个标记组成.

实际搜索时, 可以考虑空格分离的各个单词之间是用 & 连接的:

def SQLgenSearchStr(inputStr):
    # 将输入的搜索字符串转换为 sql语句,用 &连接
    keywords = inputStr.split(' ')
    keywords_new = []
    for k in keywords:
        temp_str=k.strip().replace("'","")
        if temp_str!='':
            keywords_new.append(temp_str)
    if len(keywords_new)>0:
        keywordsStr = ' & '.join(keywords_new)
        sql = "select * from typecho_contents WHERE tsv @@ '" + keywordsStr + "';"
        return sql
    return ''

SQL片段示例:

select * from typecho_contents WHERE tsv @@ 'a & b | c ';

在handsome主题部署时,目前仅支持一种复杂查询 &

4.在handsome主题上部署搜索

1.编辑文件 themes/handsome/libs/interface/Ajax.php , 修改函数 searchGetResult

//增加了参数 $currGroup=''
function searchGetResult($thisText,$summaryNam =20,$currGroup=''){
    // $filePath = __TYPECHO_ROOT_DIR__ . __TYPECHO_PLUGIN_DIR__ . DIRECTORY_SEPARATOR.'Handsome'.DIRECTORY_SEPARATOR.'cache'.DIRECTORY_SEPARATOR.'search.json';
    // $file = file_get_contents($filePath);

    // $cache = json_decode($file,true);
    // $html = "";

    // $resultLength = 0;
    $searchResultArray = [];//搜索结果
    if (trim($thisText) !== ""){
        // $searchArray = mb_split(" ",$thisText);
        // $searchArray[] = $thisText;
        // foreach ($searchArray as $thisText){
        //     if (trim($thisText) != ""){
        //         foreach ($cache as $item) {
        //             $content_ok = mb_stripos($item['content'], $thisText);
        //             if ($content_ok!==false){//内容中有匹配的结果
        //                 //高亮内容
        //                 $contentMatch = mb_substr($item['content'],max(0,$content_ok -$summaryNam/2),min($summaryNam,mb_strlen
        //                     ($item['content']) -$content_ok));
        //                 $contentMatch = str_ireplace($thisText,"<mark class='text_match'>".$thisText."</mark>",
        //                     $contentMatch);
        //                 $searchResultArray [] = array(
        //                     "path" => $item["path"],
        //                     "title" => $item["title"],
        //                     "content" => $contentMatch
        //                 );

        //                 print_r(array(
        //                     "path" => $item["path"],
        //                     "title" => $item["title"],
        //                     "content" => $contentMatch
        //                 ));

        //                 print_r('<br />');

        //                 $resultLength ++;
        //             }else{
        //                 //高亮标题
        //                 $title_ok = mb_stripos($item['title'], $thisText);;
        //                 if ($title_ok!== false){//标题中有匹配的结果
        //                     $contentMatch = mb_substr($item['content'],0,min(30,mb_strlen($item['content']) - $title_ok));

        //                     $contentMatch = str_ireplace($thisText,"<mark class='text_match'>".$thisText."</mark>",
        //                         $contentMatch);
        //                     $searchResultArray [] = array(
        //                         "path" => $item["path"],
        //                         "title" => $item["title"],
        //                         "content" => $contentMatch
        //                     );
        //                     $resultLength ++;
        //                 }else{
        //                     //匹配不是
        //                     continue;
        //                 }
        //             }
        //         }
        //     }
        // }

        // ==> pg FTS
        $db = Typecho_Db::get();
        $strSearch=strtolower(trim($thisText));
        // 多个连续空格只保留一个
        $strSearch = preg_replace("/\s(?=\s)/","\\1",$strSearch);
        // 支持多关键字查询
        $strSearch=str_replace(" "," & ",$strSearch);

        //判断当前用户角色是否为管理员,如果不是管理员,仅搜索publish文档
        if ($currGroup == "administrator"){
            $query= $db->select('slug','title')->from('table.contents')->where('tsv @@ ?', $strSearch);

        }else{
            $query= $db->select('slug','title')->from('table.contents')->where('tsv @@ ?', $strSearch)
            ->where('status = ?', 'publish');
        }

        $results = $db->fetchAll($query);
        foreach ( $results as $result){
            $searchResultArray [] = array(
                "path" => "/archives/".$result["slug"].".html",
                "title" => $result["title"],
                "content" => ""
            );

        }
        // <== pg FTS

        $searchResultArray = Utils::array_unset_tt($searchResultArray,"path");

    }
    return $searchResultArray;
}

编辑文件 themes/handsome/search.php, 计算 searchGetResult 函数需要的参数 currGroup 并传入参数:

//从自己的接口中获取搜索内容
//计算 searchGetResult 函数需要的参数 currGroup 并传入参数
$currGroup = get_object_vars($this->user) ['row']['group'];
$array = searchGetResult($this->request->keywords,100,$currGroup);

2.关闭 Ajax搜索请求

编辑 usr/themes/handsome/assets/js/core.min.js,将如下代码:

{var a=$("#search_input").val();if(""!==a.trim()){$("#spin-search").addClass("show inline"),$("#icon-search").addClass("hide"),null!==b&&(b.abort(),b=null),b=$.getJSON("?action=ajax_search&content="+a,function(a){$("#search_tips_drop").text(""),$("#search_tips_drop").removeClass("hide"),$(a.results).appendTo("#search_tips_drop"),$("#spin-search").removeClass("show inline"),$("#icon-search").removeClass("hide"),b=null})}

替换为如下代码,关闭掉 action=ajax_search :

{var a=$("#search_input").val();if(""!==a.trim()){$("#icon-search").addClass("show inline"),$("#icon-search").addClass("hide"),null!==b&&(b.abort(),b=null)}

3.支持多关键字查询

为了支持多关键字查询,修改 typecho/var/Widget/Archive.php ,将 $keywords = $this->request->filter('url', 'search')->keywords; 替换为 $keywords = $this->request->keywords;, 并增加相关安全校验:

    private function searchHandle(Typecho_Db_Query $select, &$hasPushed)
    {
        /** 增加自定义搜索引擎接口 */
        //~ fix issue 40
        // $keywords = $this->request->filter('url', 'search')->keywords;
        // 空过滤
        $keywords = trim($this->request->keywords);
        // PHP、HTML标签过滤
        $keywords = strip_tags($keywords);
        // xss
        $keywords = htmlspecialchars($keywords,ENT_QUOTES,'UTF-8');
        $keywords = $this->clean_input($keywords);

同时在该文件中增加函数 clean_input() 的实现,在文件的最后一个 } 之前添加如下代码:

    /**
     * 修改自:xss_clean.php,参考: https://gist.github.com/mbijon/1098477
     *
     * @access public
     * @param string $input 
     * @param int $safe_level
     * @return string $output
     */
    public function clean_input( $input, $safe_level = 0 ) {

        $output = $input;
        do {
            // Treat $input as buffer on each loop, faster than new var
            $input = $output;

            // Remove unwanted tags
            // $output = $this->strip_tags( $input );
            $output = $this->strip_encoded_entities( $output );

            // Use 2nd input param if not empty or '0'
            if ( $safe_level !== 0 ) {
                $output = $this->strip_base64( $output );
            }

        } while ( $output !== $input );

        return $output;

    }

    /**
     * @access private
     * @param string $input 
     * @return string $input
     */
    private function strip_encoded_entities( $input ) {

        // Fix &entity\n;
        $input = str_replace(array('&amp;','&lt;','&gt;'), array('&amp;amp;','&amp;lt;','&amp;gt;'), $input);
        $input = preg_replace('/(&#*\w+)[\x00-\x20]+;/u', '$1;', $input);
        $input = preg_replace('/(&#x*[0-9A-F]+);*/iu', '$1;', $input);
        $input = html_entity_decode($input, ENT_COMPAT, 'UTF-8');

        // Remove any attribute starting with "on" or xmlns
        $input = preg_replace('#(<[^>]+?[\x00-\x20"\'])(?:on|xmlns)[^>]*+[>\b]?#iu', '$1>', $input);

        // Remove javascript: and vbscript: protocols
        $input = preg_replace('#([a-z]*)[\x00-\x20]*=[\x00-\x20]*([`\'"]*)[\x00-\x20]*j[\x00-\x20]*a[\x00-\x20]*v[\x00-\x20]*a[\x00-\x20]*s[\x00-\x20]*c[\x00-\x20]*r[\x00-\x20]*i[\x00-\x20]*p[\x00-\x20]*t[\x00-\x20]*:#iu', '$1=$2nojavascript...', $input);
        $input = preg_replace('#([a-z]*)[\x00-\x20]*=([\'"]*)[\x00-\x20]*v[\x00-\x20]*b[\x00-\x20]*s[\x00-\x20]*c[\x00-\x20]*r[\x00-\x20]*i[\x00-\x20]*p[\x00-\x20]*t[\x00-\x20]*:#iu', '$1=$2novbscript...', $input);
        $input = preg_replace('#([a-z]*)[\x00-\x20]*=([\'"]*)[\x00-\x20]*-moz-binding[\x00-\x20]*:#u', '$1=$2nomozbinding...', $input);

        // Only works in IE: <span style="width: expression(alert('Ping!'));"></span>
        $input = preg_replace('#(<[^>]+?)style[\x00-\x20]*=[\x00-\x20]*[`\'"]*.*?expression[\x00-\x20]*\([^>]*+>#i', '$1>', $input);
        $input = preg_replace('#(<[^>]+?)style[\x00-\x20]*=[\x00-\x20]*[`\'"]*.*?behaviour[\x00-\x20]*\([^>]*+>#i', '$1>', $input);
        $input = preg_replace('#(<[^>]+?)style[\x00-\x20]*=[\x00-\x20]*[`\'"]*.*?s[\x00-\x20]*c[\x00-\x20]*r[\x00-\x20]*i[\x00-\x20]*p[\x00-\x20]*t[\x00-\x20]*:*[^>]*+>#iu', '$1>', $input);

        return $input;

    }

    /**
     * @access private
     * @param string $input 
     * @return string $input
     */
    private function strip_base64( $input ) {

        $decoded = base64_decode( $input );

        $decoded = $this->strip_tags( $decoded );
        $decoded = $this->strip_encoded_entities( $decoded );

        $output = base64_encode( $decoded );

        return $output;
    }

检索示例:

如果看不到效果,可能的原因:

  • 没有刷新hansome插件中的离线缓存,js文件没更新
  • nginx设置了js缓存
  • 浏览器缓存了js,清除浏览器历史

4.其他

可以考虑关闭handsome主题的实时索引构建。

优化结巴分词

1.英文词库

推荐COCA词库:

链接: https://pan.baidu.com/s/1v2BjI-4Xbpyc-Ot8diE4MQ 提取码: dn2y

结巴分词对字典的要求:词典格式和 dict.txt 一样,一个词占一行;每一行分三部分:词语、词频(可省略)、词性(可省略),用空格隔开,顺序不可颠倒。file_name 若为路径或二进制方式打开的文件,则文件必须为 UTF-8 编码。

根据EXCEL生成字典脚本:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# coca.py

import xlrd  # conda install xlrd

file= 'COCA60000.xlsx'
data = xlrd.open_workbook(file)
table = data.sheet_by_index(0)
nrows = table.nrows
# 列名:RANK # PoS   WORD  TOTAL   SPOKEN  FICTION MAGAZINE    NEWSPAPER   ACADEMIC
# 取 word 和 total
excel_list = []
for row in range(1, nrows):
    excel_list.append((table.cell(row, 2).value).strip().lower() + ' ' + str(int(table.cell(row, 3).value)))

txt=('\n').join(excel_list)
dict_name='dict_coca60000.txt'

# 字典文件 820k左右,不到1MB
with open(dict_name, 'w',encoding='utf-8') as f:
    f.write(txt)

使用:

jieba.load_userdict('dict_coca60000.txt') 

2.中文词库

funNLP:https://github.com/fighting41love/funNLP

© Licensed under CC BY-NC-SA 4.0

价值投资不能保证我们盈利, 但价值投资给我们提供了通向成功的唯一机会。——巴菲特

发表我的评论
取消评论
表情

Hi,您需要填写昵称和邮箱!