Skip to content

Commit

Permalink
feat(client): add wordLimit support (#219)
Browse files Browse the repository at this point in the history
  • Loading branch information
Mister-Hope committed Apr 29, 2021
1 parent ccf1e6b commit 97c47cb
Show file tree
Hide file tree
Showing 12 changed files with 215 additions and 1 deletion.
3 changes: 3 additions & 0 deletions babel.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = {
presets: ['@babel/preset-env'],
};
10 changes: 10 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
const { resolve } = require('path');

module.exports = {
rootDir: resolve(__dirname),
collectCoverage: true,
transform: {
'\\.js$': 'babel-jest',
},
testMatch: ['<rootDir>/packages/**/__tests__/**/*.spec.js'],
};
6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,20 @@
"changelog": "conventional-changelog -p angular -i CHANGELOG.md -s",
"lint": "prettier --check --write .",
"lint:check": "prettier --check .",
"prepare": "husky install"
"prepare": "husky install",
"test": "jest -i"
},
"devDependencies": {
"@babel/core": "^7.13.16",
"@commitlint/cli": "^12.1.1",
"@commitlint/config-conventional": "^12.1.1",
"@waline/client": "^0.14.8",
"babel-jest": "^26.6.3",
"commitizen": "^4.2.3",
"conventional-changelog-cli": "^2.1.1",
"cz-conventional-changelog": "^3.3.0",
"husky": "^6.0.0",
"jest": "^26.6.3",
"lint-staged": "^10.5.4",
"marked": "^1.2.5",
"prettier": "^2.2.1",
Expand Down
148 changes: 148 additions & 0 deletions packages/client/__tests__/word-count.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import { getWords, getChinese, getWordNumber } from '../src/utils/wordCount';

describe('Words test', () => {
it('Should count emplty content correctly', () => {
expect(getWordNumber('')).toEqual(0);
});
it('Should count english words correctly', () => {
expect(
getWordNumber(
'A simple comment system with backend support fork from Valine'
)
).toEqual(10);
});

it('Should pick chinese words correctly', () => {
const chineseWords = getChinese(
'Waline - 一款从 Valine 衍生的带后端评论系统。可以将 Waline 等价成 With backend Valine.'
);

expect(chineseWords.join('')).toEqual(
'一款从衍生的带后端评论系统可以将等价成'
);
});

it('Should count word correctly', () => {
expect(
getWordNumber(
'A simple comment system, with backend support fork from Valine.'
)
).toEqual(10);

expect(
getWordNumber(
'Waline - 一款从 Valine 衍生的带后端评论系统。可以将 Waline 等价成 With backend Valine.'
)
).toEqual(25);
});

it('Should omit other characters in Markdown', () => {
expect(getWordNumber('#$%^\t&*% /?=+[\n{|}]\r')).toEqual(0);

expect(
getWordNumber(
'\nA simple comment system,\n\n with _backend support_ fork from **Valine**.\n'
)
).toEqual(10);

expect(
getWordNumber(
'Waline - 一款从 **Valine** 衍生的带后端评论系统。\n\n可以将 Waline 等价成 _With backend Valine_.'
)
).toEqual(25);
});

it('Addtional counts with Markdown links and images', () => {
const linkAddress =
'//cdn.jsdelivr.net/npm/@waline/client/dist/Waline.min.js';
const linkMarkdown = `You can found Waline [here](${linkAddress}).`;
const imageMarkdown = `Here is a image.\n\n![Alt](https://a/fake/link)`;

const linkWords = getWords(linkAddress)
.map((word) => word.trim())
.filter((word) => word);

expect(linkWords).toEqual([
'cdn',
'jsdelivr',
'net',
'npm',
'waline',
'client',
'dist',
'Waline',
'min',
'js',
]);

expect(getWordNumber(linkAddress)).toEqual(10);
expect(getWordNumber(linkMarkdown)).toEqual(15);
expect(getWordNumber(imageMarkdown)).toEqual(9);
});

it('Can count code block', () => {
const codeBlock = `
\`\`\`html
<head>
..
<script src="//cdn.jsdelivr.net/npm/@waline/client/dist/Waline.min.js"></script>
...
</head>
<body>
...
<div id="waline"></div>
<script>
new Waline({
el: '#waline',
path: location.pathname,
serverURL: 'https://your-domain.vercel.app',
});
</script>
</body>
\`\`\`
`;

const codeBlockwords = getWords(codeBlock)
.map((word) => word.trim())
.filter((word) => word);

expect(codeBlockwords).toEqual([
'html',
'head',
'script src',
'cdn',
'jsdelivr',
'net',
'npm',
'waline',
'client',
'dist',
'Waline',
'min',
'js',
'script',
'head',
'body',
'div id',
'waline',
'div',
'script',
'new Waline',
'el',
'waline',
'path',
'location',
'pathname',
'serverURL',
'https',
'your',
'domain',
'vercel',
'app',
'script',
'body',
]);

expect(getWordNumber(codeBlock)).toEqual(37);
});
});
17 changes: 17 additions & 0 deletions packages/client/src/components/CommentBox.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import autosize from 'autosize';
import { ConfigContext } from '../context';
import { CancelReplyIcon, EmojiIcon, MarkdownIcon, PreviewIcon } from './Icons';
import { postComment } from '../utils/fetch';
import { getWordNumber } from '../utils/wordCount';

const CACHE_KEY = 'ValineCache';
const META = ['nick', 'mail', 'link'];
Expand Down Expand Up @@ -200,8 +201,11 @@ export default function ({
onPasteFactory(editorRef, ctx.uploadImage, insertAtCaret, onChange),
[]
);

const submitComment = useCallback(() => {
const isLogin = ctx.userInfo.token;
const { wordLimit } = ctx;

if (!isLogin) {
if (requiredFields.indexOf('nick') > -1 && comment.nick.length < 2) {
inputsRef.nick.current.focus();
Expand All @@ -227,6 +231,19 @@ export default function ({
comment.link = ctx.userInfo.url;
}

if (wordLimit) {
const wordCount = getWordNumber(comment.comment);

if (wordCount < wordLimit[0] || wordCount > wordLimit[1]) {
return alert(
ctx.locale.word
.replace('$0', wordLimit[0])
.replace('$1', wordLimit[1])
.replace('$2', wordCount)
);
}
}

comment.comment = parseEmoji(comment.comment, ctx.emojiMaps, ctx.emojiCDN);
if (replyId && rootId) {
comment.pid = replyId;
Expand Down
5 changes: 5 additions & 0 deletions packages/client/src/context.js
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,11 @@ export default function Context(props) {
locales,
locale,
lang: props.lang,
wordLimit: Array.isArray(props.wordLimit)
? props.wordLimit
: props.wordLimit === 0
? false
: [0, props.wordLimit],
emojiCDN: props.emojiCDN || emojiCDN,
emojiMaps: props.emojiMaps || emojiMaps,
gravatarSetting: {
Expand Down
2 changes: 2 additions & 0 deletions packages/client/src/i18n/en.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ export default {
login: 'Login',
logout: 'logout',
admin: 'Admin',
word:
'Please input comments between $0 and $1 words!\n Current word number: $2',
'code-98':
'Waline initialization failed, please check your version of av-min.js.',
'code-99':
Expand Down
2 changes: 2 additions & 0 deletions packages/client/src/i18n/jp.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ export default {
login: 'ログインする',
logout: 'ログアウト',
admin: '管理者',
word:
'コメントは $0 から $1 ワードの間でなければなりません!\n 現在の単語番号: $2',
'code-98': 'ロードエラーです。av-min.js のバージョンを確認してください.',
'code-99': 'ロードエラーです。initにある`el`エレメントを確認ください.',
'code-100': 'ロードエラーです。AppIdとAppKeyを確認ください.',
Expand Down
1 change: 1 addition & 0 deletions packages/client/src/i18n/zh-CN.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export default {
login: '登录',
logout: '退出',
admin: '博主',
word: '评论字数应在 $0 到 $1 字之间!\n当前字数:$2',
'code-98': 'Waline 初始化失败,请检查 av-min.js 版本',
'code-99': 'Waline 初始化失败,请检查init中的`el`元素.',
'code-100': 'Waline 初始化失败,请检查你的AppId和AppKey.',
Expand Down
1 change: 1 addition & 0 deletions packages/client/src/i18n/zh-TW.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export default {
login: '登錄',
logout: '退出',
admin: '博主',
word: '評論字數應在 $0 到 $1 字之間!\n當前字數:$2',
'code-98': 'Waline 初始化失敗,請檢查 av-min.js 版本',
'code-99': 'Waline 初始化失敗,請檢查init中的`el`元素.',
'code-100': 'Waline 初始化失敗,請檢查你的AppId和AppKey.',
Expand Down
2 changes: 2 additions & 0 deletions packages/client/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export function ReactComponent({
copyRight = true,
uploadImage,
anonymous,
wordLimit = 0,
} = {}) {
return (
<Context
Expand All @@ -39,6 +40,7 @@ export function ReactComponent({
avatarCDN={avatarCDN}
avatarFore={avatarForce}
uploadImage={uploadImage}
wordLimit={wordLimit}
>
<App
boxConfig={{
Expand Down
19 changes: 19 additions & 0 deletions packages/client/src/utils/wordCount.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/**
* The wordCount module should be lightweight as it's packed into client.
*
* So We just make a simple implement here
*
* Forked from https://github.com/vuepress-theme-hope/vuepress-theme-hope/blob/v1/packages/reading-time/src/node/reading-time.ts
*/

export const getWords = (content) =>
content.match(/[\w\d\s\u00C0-\u024F]+/giu) || [];

export const getChinese = (content) => content.match(/[\u4E00-\u9FA5]/gu) || [];

export const getWordNumber = (content) =>
getWords(content).reduce(
(accumulator, word) =>
accumulator + (word.trim() === '' ? 0 : word.trim().split(/\s+/u).length),
0
) + getChinese(content).length;

0 comments on commit 97c47cb

Please sign in to comment.