Skip to content

Commit

Permalink
feat: add support for Staticman comments (#725)
Browse files Browse the repository at this point in the history
* feat: add support for Staticman comments

* chore(staticman): replace baseURL with endpoint, and add the service parameter to use v3 API

* i18n(staticman): add translations

* feat(staticman): format comment date distance from now

* chore: clean up

* feat(staticman): add support for reCaptcha v2

* feat(staticman): add extra fields

* fix(staticman): avoid submitting an invalid form

* fix(staticman): filter invalid message

* fix(staticman): verify reCaptcha

* style(table): add gaps between responsive table and others

* docs: document the Staticman comments engine

* docs: update Staticman advantages and disadvantages

* chore: update backers

Add Wilson E. Alvarez as a backer

* docs: update Staticman advantages

* feat(staticman): paginate comments pages

* docs: document the staticman.paginate parameter

* feat(staticman): add the staticman.requiredFields parameter

* chore(staticman): use the mystery-person image as the default avatar

* feat(staticman): add the staticman.moderation parameter

* docs: clean up

* style: change the comment name color

* chore: show comments pagination when total pages greater than 1

* fix(staticman): save comments to data folder

* fix(staticman): correct comments pages title

* fix(staticman): reset reCaptcha after submitting

* fix(static): do not reset submit button after submitting when recaptcha is enabled

* fix(staticman): do not disabled submit button if reCaptcha is disabled

* chore(staticman): display the API error message if possible

* docs: update Staticman pages

* fix(staticman): adjust reCaptcha theme on first load

* style(staticman): change fields width as full width on small screens

* docs: fix requiredFields and empty siteKey for staticman.yml

Co-authored-by: Wilson E. Alvarez <wilson.e.alvarez1@gmail.com>
  • Loading branch information
razonyang and Rubonnek committed Sep 22, 2022
1 parent f8d1392 commit 1ce3d63
Show file tree
Hide file tree
Showing 37 changed files with 1,281 additions and 59 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,4 @@ Sponsors will appear on our website with their brands and logos.
- [Arvind Singh](https://github.com/ArvindRSingh)
- [Vri](https://github.com/vrifox)
- [Natenom](https://github.com/Natenom)
- [Wilson E. Alvarez](https://github.com/Rubonnek)
100 changes: 47 additions & 53 deletions assets/icons/index.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,50 @@
import { config, dom, library } from '@fortawesome/fontawesome-svg-core';
import {
faAlipay,
faArtstation,
faBitbucket,
faCreativeCommons,
faCreativeCommonsBy,
faCreativeCommonsNc,
faCreativeCommonsNd,
faCreativeCommonsSa,
faDiscord,
faDiscourse,
faDocker,
faFacebookF,
faFacebookSquare,
faGithub,
faGitlab,
faInstagram,
faKaggle,
faLastfm,
faLinkedinIn,
faMastodon,
faMediumM,
faPatreon,
faPaypal,
faPinterest,
faQq,
faQuora,
faReddit,
faStackOverflow,
faTelegramPlane,
faTiktok,
faTumblr,
faTwitter,
faWeibo,
faWeixin,
faXing,
faYoutube,
faZhihu
} from '@fortawesome/free-brands-svg-icons';
import { faStar } from '@fortawesome/free-regular-svg-icons';
import {
faAdjust,
faArrowsAltV,
faBars,
faBuilding,
faCheckCircle,
faChevronDown,
faChevronCircleDown,
faCode,
faCheckCircle, faChevronCircleDown, faChevronDown, faCode,
faCoffee,
faCog,
faColumns,
Expand All @@ -24,11 +61,8 @@ import {
faFileAlt,
faFileArchive,
faFolder,
faFont,
faHistory,
faHome,
faGlobe,
faInfoCircle,
faFont, faGlobe, faHistory,
faHome, faInfoCircle,
faLanguage,
faLink,
faList,
Expand All @@ -38,8 +72,7 @@ import {
faPalette,
faQuestion,
faQuestionCircle,
faRedoAlt,
faRss,
faRedoAlt, faReply, faRss,
faSearch,
faShareAlt,
faSkullCrossbones,
Expand All @@ -51,50 +84,10 @@ import {
faTimes,
faTv,
faUndo,
faUser,
faUser
} from '@fortawesome/free-solid-svg-icons';
import { faStar } from '@fortawesome/free-regular-svg-icons';
import {
faAlipay,
faArtstation,
faBitbucket,
faCreativeCommons,
faCreativeCommonsBy,
faCreativeCommonsNc,
faCreativeCommonsNd,
faCreativeCommonsSa,
faDiscord,
faDiscourse,
faDocker,
faFacebookF,
faFacebookSquare,
faGithub,
faGitlab,
faInstagram,
faKaggle,
faLastfm,
faLinkedinIn,
faMastodon,
faMediumM,
faPatreon,
faPaypal,
faPinterest,
faQq,
faQuora,
faReddit,
faStackOverflow,
faTelegramPlane,
faTiktok,
faTumblr,
faTwitter,
faWeibo,
faWeixin,
faXing,
faYoutube,
faZhihu,
} from '@fortawesome/free-brands-svg-icons';
import { faBilibili, faLiberapay, faOffline, faTipeee } from './icons';
import { default as customIcons } from './custom';
import { faBilibili, faLiberapay, faOffline, faTipeee } from './icons';

let icons = [
// Solid Icons
Expand Down Expand Up @@ -134,6 +127,7 @@ let icons = [
faQuestion,
faQuestionCircle,
faRedoAlt,
faReply,
faRss,
faSearch,
faShareAlt,
Expand Down
87 changes: 87 additions & 0 deletions assets/js/staticman/client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { default as params } from '@params';
import snackbar from 'js/snackbar';

class Client
{
private apiUrl: string;

private reCaptchaKey = '';
private reCaptchaSecret = '';

private moderation: boolean;

constructor()
{
const endpoint = params.staticman.endpoint.replace(/\/*$/, "")
const service = params.staticman.service ? params.staticman.service : 'github'
const repo = params.staticman.repo
const branch = params.staticman.branch ? params.staticman.branch : 'master'
const property = params.staticman.property ? params.staticman.property : 'comments'
this.reCaptchaKey = params.staticman.recaptchakey ? params.staticman.recaptchakey : '';
this.reCaptchaSecret = params.staticman.recaptchasecret ? params.staticman.recaptchasecret : '';
this.moderation = "moderation" in params.staticman ? params.staticman.moderation : true;
this.apiUrl = `${endpoint}/v3/entry/${service}/${repo}/${branch}/${property}`
}

send(form: FormData) {
const reCaptchaToken = form.get('reCaptchaToken')
if (this.reCaptchaKey && !reCaptchaToken) {
throw new Error('reCaptcha token missing')
}

let slug = form.get('slug')

const data = {
'g-recaptcha-response': reCaptchaToken,
options: {
slug: slug,
reCaptcha: {
siteKey: this.reCaptchaKey,
secret: this.reCaptchaSecret,
},
},
fields: {
reply_to: form.get('reply_to'),
root_id: form.get('root_id'),
name: form.get('name'),
email: form.get('email'),
message: form.get('message'),
url: form.get('url'),
},
}

return fetch(this.apiUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data),
}).then((response) => {
return response.json();
}).then((data) => {
if (data.success === true) {
snackbar.show(this.sucessMessage(), 5000);
} else if (data.message) {
snackbar.show(data.message);
} else {
snackbar.show(data.error.text);
}
}).catch((err) => {
console.error(err)
snackbar.show('Comment failed.');
})
}

sucessMessage(): string
{
if (this.moderation) {
return 'Comment successfully submitted! Your message will be shown once our moderators review it.';
}

return 'Comment successfully submitted! Your message will be shown in a few minutes.';
}
}

const client = new Client();

export default client;
45 changes: 45 additions & 0 deletions assets/js/staticman/date-renderer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { formatDistance } from 'date-fns';
import { enUS, zhCN, zhTW } from 'date-fns/locale';

const locales = {
'en': enUS,
'en-us': enUS,
'zh-cn': zhCN,
'zh-hans': zhCN,
'zh-tw': zhTW,
'zh-hk': zhTW,
'zh-hant': zhTW,
};

class DateRenderer
{
private items: Array<HTMLElement>;
private lang: string;

constructor(items: string)
{
this.items = Array.from(document.querySelectorAll(items)) as Array<HTMLElement>
this.lang = document.documentElement.getAttribute('lang').toLowerCase();
}

run()
{
if (!this.items) {
return
}

const now = new Date();
this.items.forEach((item: HTMLElement) => {
const timestamp = parseInt(item.getAttribute('data-timestamp'));
const date = new Date(timestamp * 1000);
item.innerText = formatDistance(now, date, { addSuffix: true, locale: this.getLocale() })
})
}

getLocale()
{
return this.lang in locales ? locales[this.lang] : enUS;
}
}

export default DateRenderer;
67 changes: 67 additions & 0 deletions assets/js/staticman/form.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import Component from "js/component";

abstract class Form implements Component
{
protected form: HTMLFormElement;

private btnSubmit: HTMLButtonElement;

private locked = false;

constructor(form: string)
{
this.form = document.getElementById(form) as HTMLFormElement
}

run() {
if (!this.form) {
return;
}

this.btnSubmit = this.form.querySelector('button[type="submit"]');

this.form.addEventListener('submit', (e) => {
e.preventDefault()
if (!this.form.checkValidity()) {
e.stopPropagation()
return;
}

if (this.locked !== false) {
return;
}

this.lock();

this.submit(new FormData(this.form)).finally(() => {
this.unlock();
this.reset();
});
})
}

abstract submit(data: FormData): Promise<void>;

lock() {
this.locked = true
this.btnSubmit.setAttribute('disabled', 'true');
}

unlock() {
this.locked = false
if (!this.form.hasAttribute('data-g-recaptcha-id')) {
this.btnSubmit.removeAttribute('disabled');
}
}

reset() {
this.form.reset();

const reCaptchaWidgetId = this.form.getAttribute('data-g-recaptcha-id');
if (reCaptchaWidgetId) {
window.grecaptcha.reset(reCaptchaWidgetId)
}
}
}

export default Form;
18 changes: 18 additions & 0 deletions assets/js/staticman/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import client from "js/staticman/client";
import DateRenderer from "js/staticman/date-renderer";
import Form from "js/staticman/form";
import Reply from "js/staticman/reply";

class Staticman extends Form
{
submit(data: FormData): Promise<void>
{
return client.send(data);
}
}

(new Staticman('staticman-comment-form')).run();

(new Reply('staticman-reply-form', '.staticman-reply-button')).run();

(new DateRenderer('.staticman-comment-date')).run();
Loading

0 comments on commit 1ce3d63

Please sign in to comment.