惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

爱范儿
爱范儿
博客园_首页
W
WeLiveSecurity
S
Secure Thoughts
S
Security @ Cisco Blogs
Recent Commits to openclaw:main
Recent Commits to openclaw:main
Hugging Face - Blog
Hugging Face - Blog
www.infosecurity-magazine.com
www.infosecurity-magazine.com
H
Hacker News: Front Page
Project Zero
Project Zero
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
U
Unit 42
N
News and Events Feed by Topic
N
News and Events Feed by Topic
Hacker News - Newest:
Hacker News - Newest: "LLM"
Forbes - Security
Forbes - Security
T
Tor Project blog
I
Intezer
B
Blog
F
Full Disclosure
Security Archives - TechRepublic
Security Archives - TechRepublic
F
Fortinet All Blogs
Schneier on Security
Schneier on Security
T
Threat Research - Cisco Blogs
AI
AI
Google DeepMind News
Google DeepMind News
L
LINUX DO - 最新话题
Cloudbric
Cloudbric
L
Lohrmann on Cybersecurity
WordPress大学
WordPress大学
博客园 - 聂微东
雷峰网
雷峰网
P
Privacy International News Feed
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
PCI Perspectives
PCI Perspectives
Y
Y Combinator Blog
Spread Privacy
Spread Privacy
Simon Willison's Weblog
Simon Willison's Weblog
罗磊的独立博客
Vercel News
Vercel News
A
Arctic Wolf
The Register - Security
The Register - Security
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
Microsoft Azure Blog
Microsoft Azure Blog
H
Heimdal Security Blog
Know Your Adversary
Know Your Adversary
P
Proofpoint News Feed
C
Cybersecurity and Infrastructure Security Agency CISA
P
Proofpoint News Feed

講評世界

My App Defaults 2023 读书的意义 そして、次の曲が始まるのです 从零开始的 RSSHub Docker 私有化部署指南 使用 Homebrew 安装 Typora 的 0.11.18 版本 使用 TypeScript 为 Vue 组件的 prop 标注类型 【译文】Grid 用于布局, Flexbox 用于组件 【译文】IndexedDB 为什么这么慢?如何更好的使用呢? Hello 2022 「他山之石」零贰 「他山之石」零壹 「言論」 零壹 hexo 无法在本地实时预览 JavaScript 立即调用的函数表达式(IIFE) 解决 nvm 无法在 arm 架构下安装 V15 以下的 node 版本 的问题 m1 芯片安装 nvm 提示 command not found 如何在 JavaScript 完美的确定一个数据的类型 Cookie?小饼干! 使用 RSS 在推荐算法中获取主动权
给 icarus 主题增加所有文章的字数统计
Moeyua · 2021-11-18 · via 講評世界

看到苏卡卡大佬的 profile 上有一个统计所有文章的字数功能,感觉很有意思,于是本菜鸡也决定给自己搞一个。

虽然说菜,但是菜有菜的办法,先到 profile.jsx 这个文件看一下组件的源码:

const { Component } = require('inferno');
const gravatrHelper = require('hexo-util').gravatar;
const { cacheComponent } = require('hexo-component-inferno/lib/util/cache');

class Profile extends Component {
    renderSocialLinks(links) {
        if (!links.length) {
            return null;
        }
        return <div class="level is-mobile is-multiline">
            {links.filter(link => typeof link === 'object').map(link => {
                return <a class="level-item button is-transparent is-marginless"
                    target="_blank" rel="noopener" title={link.name} href={link.url}>
                    {'icon' in link ? <i class={link.icon}></i> : link.name}
                </a>;
            })}
        </div>;
    }

    render() {
        const {
            avatar,
            avatarRounded,
            author,
            authorTitle,
            location,
            counter,
            followLink,
            followTitle,
            socialLinks
        } = this.props;
        return <div class="card widget" data-type="profile">
            <div class="card-content">
                <nav class="level">
                    <div class="level-item has-text-centered flex-shrink-1">
                        <div>
                            <figure class="image is-128x128 mx-auto mb-2">
                                <img class={'avatar' + (avatarRounded ? ' is-rounded' : '')} src={avatar} alt={author} />
                            </figure>
                            {author ? <p class="title is-size-4 is-block" style={{'line-height': 'inherit'}}>{author}</p> : null}
                            {authorTitle ? <p class="is-size-6 is-block">{authorTitle}</p> : null}
                            {location ? <p class="is-size-6 is-flex justify-content-center">
                                <i class="fas fa-map-marker-alt mr-1"></i>
                                <span>{location}</span>
                            </p> : null}
                        </div>
                    </div>
                </nav>
                <nav class="level is-mobile">
                    <div class="level-item has-text-centered is-marginless">
                        <div>
                            <p class="heading">{counter.post.title}</p>
                            <a href={counter.post.url}>
                                <p class="title">{counter.post.count}</p>
                            </a>
                        </div>
                    </div>
                    <div class="level-item has-text-centered is-marginless">
                        <div>
                            <p class="heading">{counter.category.title}</p>
                            <a href={counter.category.url}>
                                <p class="title">{counter.category.count}</p>
                            </a>
                        </div>
                    </div>
                    <div class="level-item has-text-centered is-marginless">
                        <div>
                            <p class="heading">{counter.tag.title}</p>
                            <a href={counter.tag.url}>
                                <p class="title">{counter.tag.count}</p>
                            </a>
                        </div>
                    </div>
                </nav>
                {followLink ? <div class="level">
                    <a class="level-item button is-primary is-rounded" href={followLink} target="_blank" rel="noopener">{followTitle}</a>
                </div> : null}
                {socialLinks ? this.renderSocialLinks(socialLinks) : null}
            </div>
        </div>;
    }
}

Profile.Cacheable = cacheComponent(Profile, 'widget.profile', props => {
    const { site, helper, widget } = props;
    const {
        avatar,
        gravatar,
        avatar_rounded = false,
        author = props.config.author,
        author_title,
        location,
        follow_link,
        social_links
    } = widget;
    const { url_for, _p, __ } = helper;

    function getAvatar() {
        if (gravatar) {
            return gravatrHelper(gravatar, 128);
        }
        if (avatar) {
            return url_for(avatar);
        }
        return url_for('/img/avatar.png');
    }

    const postCount = site.posts.length;
    const categoryCount = site.categories.filter(category => category.length).length;
    const tagCount = site.tags.filter(tag => tag.length).length;

    const socialLinks = social_links ? Object.keys(social_links).map(name => {
        const link = social_links[name];
        if (typeof link === 'string') {
            return {
                name,
                url: url_for(link)
            };
        }
        return {
            name,
            url: url_for(link.url),
            icon: link.icon
        };
    }) : null;

    return {
        avatar: getAvatar(),
        avatarRounded: avatar_rounded,
        author,
        authorTitle: author_title,
        location,
        counter: {
            post: {
                count: postCount,
                title: _p('common.post', postCount),
                url: url_for('/archives')
            },
            category: {
                count: categoryCount,
                title: _p('common.category', categoryCount),
                url: url_for('/categories')
            },
            tag: {
                count: tagCount,
                title: _p('common.tag', tagCount),
                url: url_for('/tags')
            }
        },
        followLink: follow_link ? url_for(follow_link) : undefined,
        followTitle: __('widget.follow'),
        socialLinks
    };
});

module.exports = Profile;

很长,虽然没什么头猪但是关键的部分还是能看懂的,首先我们需要添加 html,改个名字,直接复制过来就好了:

<div class="level-item has-text-centered is-marginless">
  <div>
    <p class="heading">{counter.word.title}</p>
    <a href={counter.word.url}>
      <p class="title">{counter.word.count}</p>
    </a>
  </div>
<div>

很简单,但是这时我们需要看一下这个 counter 是什么:

counter: {
  post: {
    count: postCount,
    title: _p('common.post', postCount),
    url: url_for('/archives')
  },
  category: {
    count: categoryCount,
    title: _p('common.category', categoryCount),
    url: url_for('/categories')
  },
  tag: {
    count: tagCount,
    title: _p('common.tag', tagCount),
    url: url_for('/tags')
  }
}

这个不就是定义三个计数器的属性的嘛,我们也来一个就好了:

word: {
    count: wordCount,
    title: _p('字数', wordCount),
}

这里需要注意的是这里我们 title 直接写死,要不然还得去其他地方配置。同时我们不需要点击跳转,所以也就不需要给它 url。 ~~~想写也没有~~~

这样一来样式就没问题了,我们开始计算字数,这是个麻烦事,先看看其他三个 counter 是怎么写的吧:

const postCount = site.posts.length;
const categoryCount = site.categories.filter(category => category.length).length;
const tagCount = site.tags.filter(tag => tag.length).length;

看起来是在 site 这个对象中储存了一些网站的信息,我们打印一下它的 post 属性看看是什么。

看起来还挺多,terminal 都放不下了,都是文章的各种信息,而且因为无法显示全部信息,我们不知道这个对象的全部属性是什么,也就没有办法拿到文章内容了。这个时候就需要用到 Object.getOwnPropertyNames 这个方法了。

Object.getOwnPropertyNames 方法返回一个数组,成员是参数对象自身的全部属性的属性名,不管该属性是否可遍历。

这样以来我们就能够摸清楚这个对象的结构了,大概是这样子:

site: {
  posts: { 
    data: [
      {
        _content: 文章内容,
        ...,
        ...
      },
      {
        ...
      }
    ],
    length: num
  },
  categories: { ... },
  tags: { ... }
}

文章内容其实有好多个属性都有保存,但是这个格式相对较少,我们就选择这个来统计文章字数。

site.posts.data[0]._content.length

试一下,好像计算出来的结果有点不对,看起来正好是一倍,那这好办,也懒得管为什么,直接给它砍一半

site.posts.data[0]._content.length / 2

OK,这样以来就没有问题了,之后只需要遍历 data 对象,计算出所有文章的字数总和就好了,这里是我写的函数:

function getWords(site) {
    let posts = site.posts.data;
    let words = 0;
    for (const post of posts) {
        words = words + post._content.length / 2;
    }
    words = (words / 10000).toFixed(2);
    return words;
}

这里我选择的单位是 ,保留了两位小数。最后我们只需要调用这个函数就 ok 了。

  const postCount = site.posts.length;
  const categoryCount = site.categories.filter(category => category.length).length;
  const tagCount = site.tags.filter(tag => tag.length).length;
  const wordCount = getWords(site);

最后放一张完成后的截图: