npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@ovojs/ovo

v0.0.1-next.31

Published

OvO is a svelte web component for comments

Readme

OvO

OvO 是一款小巧的 web 端评论插件。感谢 OωO 的启发。

age NPM gzip size

⚠️ ⚠️ ⚠️ OvO is NOT SAFE yet. Waiting for safe backend solutions!

使用

在浏览器中使用

OvO 使用 unpkg 提供的 CDN 服务存放打包文件。

先通过 scriptlink 标签导入 OvO:

<link rel="stylesheet" href="https://unpkg.com/@ovojs/ovo/dist/style.css">
<script src="https://unpkg.com/@ovojs/ovo"></script>

然后在再实例化一个 OvO 对象:

new OvO({
  target: document.getElementById('ovo'),
  props: {
    emoji: "emoji url",
    server: "server url",
    placeholder: "说点什么吧~",
    timeout: 10000
  }
})

其中,emojiserver 分别是表情资源路径和服务端接口,OvO 本身不提供服务端程序,需要由用户自己编写,或使用大佬们开发的 OvO 第三方服务端,具体服务端开发规范见 服务端

另外,OvO 提供一个默认的表情资源和一个用于测试的服务端接口地址,如下:

https://cdn.jsdelivr.net/gh/DejavuMoe/DejavuMoe/emoji.json
  • server
https://www.fastmock.site/mock/112f2e694fa5334c4d698ce22a512405/ovo

具体示例程序见 examples/vanilla

最后,placeholdertimeout 分别表示输入框中默认显示的文字和请求超时时间,若在 timeout 秒内,服务端没有响应,则 OvO 会中断请求。

作为 ESM 使用

OvO 使用 Svelte 开发,且支持 ES Module 导入方式,同时可在 React 和 Vue 中使用,如下表所示

| Package | Version | Gzip Size | | :------------------------------- | :----------------------------------------------------------- | :----------------------------------------------------------- | | @ovojs/ovo | NPM | gzip size | | @ovojs/react | NPM | gzip size | | @ovojs/vue | NPM | gzip size |

Svelte

npm i @ovojs/ovo
<script>
  import OvO from "@ovojs/ovo";
  import "@ovojs/ovo/dist/style.css";
  
  let server = "your server address";
  let placeholder = "说点什么吧~";
  let timeout = 10000;
</script>

<OvO {server} {placeholder} {timeout} />

React

npm i @ovojs/react
import OvO from "@ovojs/react";
import "@ovojs/react/dist/style.css";

function App() {
  return (
    <OvO
      server="your server address"
      placeholder="说点什么吧~"
      timeout={10000}
    />
  )
}

Vue

npm i @ovojs/vue
<template>
  <OvO :server="server" :placeholder="placeholder" :timeout="timeout" />
</template>

<script lang="ts">
import { defineComponent } from 'vue';
import OvO from "@ovojs/vue";
import "@ovojs/vue/dist/style.css";

export default defineComponent({
  name: 'App',
  components: {
    OvO
  },
  data() {
    return {
      server: "your server address",
      placeholder: "说点什么吧~",
      timeout: 10000
    }
  }
})
</script>

自定义主题色

OvO 直接使用 CSS 变量 来控制主题色,默认为亮色,在您的项目中,可以通过下面几个 CSS 变量来覆盖 OvO 的默认颜色配置。

--ovo-ft: #444;        // 文字颜色
--ovo-bdr: #ddd;       // 边框颜色
--ovo-bg: #fff;        // 背景颜色
--ovo-bg-hvr: #eee;    // hover 时的背景色
--ovo-pm: #08c;        // a 标签的文字颜色 
--ovo-tag: #f7f7f7;    // span 的文字颜色
--ovo-trans-sec: .5s;  // 颜色过度时间

一个暗黑主题色的示例

反馈

若您对 OvO 有什么改进建议或使用中遇到问题,欢迎在 Issues 处发言,或者直接加入 QQ 群 248037506 与大佬们进行互动 🤺。

服务端 (⚠️ Deprecated)

引入服务端的目的是避免将某些数据暴露给浏览器,如第三方数据存储平台的密钥等数据。同时在服务端可以实现限流和禁IP策略,从而防止用户通过接口恶意刷评论。进一步,将 XSS 过滤的代码放在服务端中,可以减小评论插件打包后的体积。另外,在服务端上,用可以自行实现一些个性化功能,如给被@的用户发送消息通知等等。

概念

OvO 服务端必须要实现 XSS 过滤和数据存储功能,可以实现消息通知功能。关于不开发官方服务端程序的原因:本人没(太)时(菜)间(了)!😁

  • XSS 过滤

XSS 过滤 (Cross-site scripting filtering) 主要为了防止用户在评论内容中使用一些 HTML 标签或 JS 脚本恶意攻击被评论网站,具体原理见 XSS MDN。当服务端收到来自插件的评论内容时,需要先擦除内容中像 scriptiframe 等标签内容,然后才能将过滤后的数据存入数据库中。

  • 数据存储

评论本质是一种树形的数据结构,容易想到的是将评论存储在像 MangoDB 这样的文档数据库中,数据结构如下 (为方便理解,只显示了核心字段)

{
  "_id": ObjectId,
  "content": "评论内容",
  "children": [
    {
      "_id": ObjectId,
      "content": "评论内容",
      "children": []
    }
  ]
}

这种存储方式对查询十分方便,只需要一次查找操作就能获取到该条评论和该条评论下的所有评论,且也很便于前端进行评论的树形渲染。

另一种存储方案,也是本人较推荐的一种方案,是将评论拆分为 评论回复 两类,分别存储在两张关系型数据库表中。如下表所示 (为方便理解,只显示了核心字段)

评论表:

| 字段 | 含义 | | :------ | :----------------------- | | id | 评论 ID (唯一) | | sid | 评论对象的 ID,如文章 ID | | content | 评论内容 |

回复表:

| 字段 | 含义 | | :------ | :-------------------------- | | id | 回复 ID (唯一) | | sid | 同评论表的 sid | | cid | 回复对象的 ID (评论表的 ID) | | rid | 回复对象的 ID (回复表的 ID) | | content | 回复内容 |

从表中可以看出,对文章的评论就是评论,对评论的回复就是回复,对回复的回复也是回复。在回复表中,当 rid 为空 cid 不为空时,表示该条是对某条评论的回复;当 rid 不为空时,表示该条是对某条评论下的某条回复的回复。

这种方案拥有更好的灵活性,因为我们不太可能会一下子就获取所有的评论数据进行显示,更多时候是先获取前几条评论,然后等待用户点击 ”显示更多评论“ 之类的按钮,再继续获取剩下的评论内容。

且我们也不太需要一下子就将所有的回复显示出来,当用户点在查看某条评论下的回复时再去请求对应的回复数据才是比较合理的。

另外,可以看到 OvO 对评论的显示,并不是完全按照评论层级关系嵌套显示的,而是将所有的回复 (包括对回复的回复) 都 “拍平” 显示在第二层,及树高不大于 2。这样从某种程度上牺牲了评论结构的可读性,但使得整个页面布局更加协调。

  • 消息通知

OvO 通过 @username 来表示需要被通知的用户,服务端在收到一条评论或回复请求时,需要解析出评论或回复内容中每个 @ 后的用户名,然后在数据库中查找这些用户的 Email 地址,然后将该条评论或回复的内容和一些上下文通过邮件发送给这些用户。邮箱地址来源于在插件中填写的邮箱地址。

Note: 根据上述逻辑,服务端需要在用户表中将用户名设置为唯一 (unique)。

接口

获取评论

  • 路径:/comment

  • 方法:GET

  • 请求示例:

GET https://yourserver.com/comment?domain=xjj.pub&path=/post/1&page=0
  • 响应示例:
{
  "commments": [{
    "id": 1,
    "domain": "xjj.pub", // 该条评论发布的网站域名,下同
    "path": "/post/1",   // 该条评论发布的网页路径,下同
    "content": "HTML格式评论内容",
    "ctime": "2021-07-01 08:00:00", // 发布时间,下同
    "user": {
      "name": "XJJ",
      "website": "https://xjj.pub"
    },
    "children": [{
      "id": 1,
      "cid": 1,    // 被回复评论的ID
      "rid": null, // 若rid为null,则说明该条回复的对象是评论,否则为回复
      "content": "HTML格式回复内容",
      "ctime": "2021-07-01 09:00:00",
      "user": {
        "name": "Dejavu",
        "website": "https://dejavu.moe"
      }
    }]
  }],
  "done": false    // 若已经请求到最后一条评论,则该字段为true
}
  • 限制:
    • 响应数据不能含有用户邮箱地址!
    • 每个请求获取的评论数不大于10!
    • 响应的 content 必须是经过XSS过滤后的!

发布评论

  • 路径:/comment
  • 方法:POST
  • 请求示例:
{
  "domain": "xjj.pub",
  "path": "/post/1",
  "content": "HTML格式的评论内容",
  "user": {
    "name": "XJJ",
    "website": "https://xjj.pub",
    "email": "[email protected]"
  }
}
  • 响应示例:
{
  "errmsg": null
}

发布回复

  • 路径:/reply
  • 方法:POST
  • 请求示例:
{
  "cid": 1,    // 被回复评论的ID
  "rid": null, // 如回复的对象是回复,则rid为该回复的ID,否则为null
  "domain": "xjj.pub",
  "path": "/post/1",
  "content": "HTML格式的评论内容",
  "user": {
    "name": "XJJ",
    "website": "https://xjj.pub",
    "email": "[email protected]"
  }
}
  • 响应示例:
{
  "errmsg": "用户名已存在"
}

第三方服务端

| Name | Language | Period | | :----------------------------------------------------------- | :------- | :------- | | ovo-everyday | Python3 | 测试阶段 |

Q&A

  1. 怎么删除评论?

不能删除,除非自己在服务端的数据库中删除。说出去的话就像泼出去的水,在这个熵增的宇宙中是收不回的 /doge。

最后

Have fun with OvO!