Skip to content
This repository has been archived by the owner on Apr 5, 2022. It is now read-only.

Commit

Permalink
Vanilla Staticman JS & HTML reorganization (#245)
Browse files Browse the repository at this point in the history
* vanilla staticman.js & <article> 4 cmt

* try fixing JSON structure

* fixed threadID in data attrib

* xhrObj.options.reCaptcha initiated only once
  • Loading branch information
VincentTam authored May 2, 2021
1 parent 9bd8ab8 commit d8c65c2
Show file tree
Hide file tree
Showing 3 changed files with 154 additions and 137 deletions.
204 changes: 109 additions & 95 deletions assets/js/staticman.js
Original file line number Diff line number Diff line change
@@ -1,108 +1,122 @@
// Static comments
// from: /~https://github.com/eduardoboucas/popcorn/blob/gh-pages/js/main.js
$(document).ready(function() {
$('.new-comment').submit(function () {
var form = this;
(function() {
let form = document.querySelector('.new-comment');
if (form) {
form.addEventListener('submit', function () {
form.classList.add('loading');
form.querySelector('input[type="submit"]:enabled').classList.add('hidden'); // hide "submit"
form.querySelector('input[type="submit"]:disabled').classList.remove('hidden'); // show "submitted"

$(form).addClass('loading');
$('input[type="submit"]:enabled').addClass('hidden'); // hide "submit"
$('input[type="submit"]:disabled').removeClass('hidden'); // show "submitted"
// Construct form action URL form JS to avoid spam
let api = '{{ .api }}';
let gitProvider = '{{ .gitprovider }}';
let username = '{{ .username }}';
let repo = '{{ .repo }}';
let branch = '{{ .branch }}';
let url = ['https:/', api, 'v3/entry', gitProvider, username, repo, branch, 'comments'].join('/');

// Construct form action URL form JS to avoid spam
var api = '{{ .api }}';
var gitProvider = '{{ .gitprovider }}';
var username = '{{ .username }}';
var repo = '{{ .repo }}';
var branch = '{{ .branch }}';
// Convert form fields to a JSON-friendly string
let formObj = Object.fromEntries(new FormData(form));
let xhrObj = {fields: {}, options: {}};
Object.entries(formObj).forEach(([key, value]) => {
let a = key.indexOf('['), b = key.indexOf('reCaptcha');
if (a == -1) { // key = "g-recaptcha-response"
xhrObj[key] = value;
} else if (a == 6 || (a == 7 && b == -1)) { // key = "fields[*]", "options[*]"
xhrObj[key.slice(0, a)][key.slice(a + 1, -1)] = value;
} else { // key = "options[reCaptcha][*]"
// define xhrObj.options.reCaptcha if it doesn't exist
xhrObj.options.reCaptcha = xhrObj.options.reCaptcha || {};
xhrObj.options.reCaptcha[key.slice(b + 11, -1)] = value;
}
});
let formData = JSON.stringify(xhrObj); // some API don't accept FormData objects

$.ajax({
type: $(this).attr('method'),
url: ['https:/', api, 'v3/entry', gitProvider, username, repo, branch, 'comments'].join('/'),
data: $(this).serialize(),
contentType: 'application/x-www-form-urlencoded',
success: function (data) {
showAlert('success');
setTimeout(function(){ clearForm() }, 3000); // display success message for 3s
$(form).removeClass('loading');
},
error: function (err) {
console.log(err);
showAlert('failed');
$(form).removeClass('loading');
}

let xhr = new XMLHttpRequest();
xhr.open('POST', url);
xhr.setRequestHeader('Content-Type', 'application/json;charset=UTF-8');
xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
xhr.onreadystatechange = function () {
if (xhr.readyState === XMLHttpRequest.DONE) {
let status = xhr.status;
if (status >= 200 && status < 400) {
formSubmitted();
} else {
formError(err);
}
}
};
xhr.send(formData);

return false;
});

return false;
});
function formSubmitted() {
showAlert('success');
setTimeout(function(){ clearForm() }, 3000); // display success message for 3s
form.classList.remove('loading');
}

function showAlert(msg) {
if (msg == 'success') {
$('.submit-notice').addClass('submit-success')
$('.new-comment .submit-success').removeClass('hidden'); // show submit success message
$('.new-comment .submit-failed').addClass('hidden'); // hide submit failed message
} else {
$('.submit-notice').addClass('submit-failed')
$('.new-comment .submit-success').addClass('hidden'); // hide submit success message
$('.new-comment .submit-failed').removeClass('hidden'); // show submit failed message
function formError(err) {
console.log(err);
showAlert('failed');
form.classList.remove('loading');
}
$('input[type="submit"]:enabled').removeClass('hidden'); // show "submit"
$('input[type="submit"]:disabled').addClass('hidden'); // hide "submitted"
}

function clearForm() {
resetReplyTarget();
$('.new-comment input')
.filter(function() {
return this.name.match(/^fields\[.*\]$/);
})
.val(''); // empty all text & hidden fields but not options
$('.new-comment textarea').val(''); // empty text area
$('.submit-notice').removeClass('.submit-success').removeClass('.submit-failed');
$('.new-comment .submit-success').addClass('hidden'); // hide submission status
$('.new-comment .submit-failed').addClass('hidden'); // hide submission status
}
function showAlert(msg) {
if (msg == 'success') {
form.querySelector('.submit-notice').classList.add('submit-success')
form.querySelector('.submit-success').classList.remove('hidden'); // show submit success message
form.querySelector('.submit-failed').classList.add('hidden'); // hide submit failed message
} else {
form.querySelector('.submit-notice').classList.add('submit-failed')
form.querySelector('.submit-success').classList.add('hidden'); // hide submit success message
form.querySelector('.submit-failed').classList.remove('hidden'); // show submit failed message
}
form.querySelector('input[type="submit"]:enabled').classList.remove('hidden'); // show "submit"
form.querySelector('input[type="submit"]:disabled').classList.add('hidden'); // hide "submitted"
}

function resetReplyTarget() {
$('.new-comment .reply-notice .reply-name').text('');
$('.new-comment .reply-notice .comment-avatar').remove();
$('.new-comment .reply-notice .reply-close-btn').remove();
$('.new-comment .reply-notice').addClass('hidden'); // hide reply target display
$('.new-comment input[type="hidden"]')
.filter(function() {
return this.name.match(/^fields\[reply[a-zA-Z]*\]$/);
})
.val(''); // empty all hidden fields whose name starts from "reply"
}
// empty all text & hidden fields but not options
function clearForm() {
resetReplyTarget();
form.querySelector('.submit-notice').classList.remove('.submit-success'); // IE10 compatibility
form.querySelector('.submit-notice').classList.remove('.submit-failed');
form.querySelector('.submit-success').classList.add('hidden'); // hide submission status
form.querySelector('.submit-failed').classList.add('hidden'); // hide submission status
}

// record reply target when "reply to this comment" is pressed
$('.comment').on('click', '.comment-reply-btn', function (evt){
resetReplyTarget();
var cmt = $(evt.delegateTarget);
var replyThread = cmt.find('.comment-threadID').text();
$('.new-comment input[name="fields[replyThread]"]').val(replyThread);
$('.new-comment input[name="fields[replyID]"]').val(cmt.attr("id"));
authorTag = cmt.find('.comment-author');
replyName = authorTag.text();
$('.new-comment input[name="fields[replyName]"]').val(replyName);
function resetReplyTarget() {
form.querySelector('.reply-notice .reply-name').innerText = '';
form.querySelector('.reply-notice').classList.add('hidden'); // hide reply target display
// empty all hidden fields whose name starts from "reply"
Array.from(form.elements).filter(e => e.name.indexOf('fields[reply') === 0).forEach(e => e.value = '');
}

// display reply target avatar and name
$('.new-comment .reply-notice').removeClass('hidden');
$('.new-comment .reply-name').text(replyName);
avatarTag = cmt.find('.comment-avatar');
// use clone to avoid removal of avatar in comments by resetReplyTarget()
$('.new-comment .reply-arrow').after(avatarTag.clone());
// add button for removing reply target (static method would give error msg)
closeBtn = $("<a class='reply-close-btn button'><i class='fas fa-times'></i></a>");
$('.new-comment .reply-notice').append(closeBtn);
});
// record reply target when one of the "reply" buttons is pressed
document.querySelector('.comments-container').addEventListener('click', function (evt) {
let target = evt.target;
if (target.matches('.comment-reply-btn')){
resetReplyTarget();
let cmt = target;
while (!cmt.matches('.comment')) { // find the comment containing the clicked "reply" button
cmt = cmt.parentNode;
}
form.querySelector('input[name="fields[replyThread]"]').value = cmt.dataset.replyThread;
form.querySelector('input[name="fields[replyID]"]').value = cmt.getAttribute('id');
let replyName = cmt.querySelector('.comment-author').innerText
form.querySelector('input[name="fields[replyName]"]').value = replyName;

// handle removal of reply target when '×' is pressed
$('.new-comment .reply-notice').on('click', '.reply-close-btn', function(){
resetReplyTarget();
});
// display reply name
form.querySelector('.reply-notice').classList.remove('hidden');
form.querySelector('.reply-name').innerText = replyName;
}
});

// handle removal of reply target when '×' is pressed
form.querySelector('.reply-close-btn').addEventListener('click', resetReplyTarget);

// clear form when reset button is clicked
$('.new-comment input[type="reset"]').click(function (){
clearForm();
});
});
// clear form when reset button is clicked
form.querySelector('input[type="reset"]').addEventListener('click', clearForm);
}
})();
57 changes: 29 additions & 28 deletions layouts/_default/comments.html
Original file line number Diff line number Diff line change
@@ -1,35 +1,36 @@
{{ if .Site.DisqusShortname }}
<article class="post">
<div class='post'>
{{ template "_internal/disqus.html" . }}
</article>
</div>

{{ else if .Site.Params.utterances.enabled }}
<script src="https://utteranc.es/client.js"
repo="{{ .Site.Params.utterances.repo }}"
issue-term="{{ .Site.Params.utterances.issueTerm }}"
issue-number="{{ .Site.Params.utterances.issueNumber }}"
label="{{ .Site.Params.utterances.label }}"
theme="{{ .Site.Params.utterances.theme }}"
crossorigin="anonymous"
<script src='https://utteranc.es/client.js'
repo='{{ .Site.Params.utterances.repo }}'
issue-term='{{ .Site.Params.utterances.issueTerm }}'
issue-number='{{ .Site.Params.utterances.issueNumber }}'
label='{{ .Site.Params.utterances.label }}'
theme='{{ .Site.Params.utterances.theme }}'
crossorigin='anonymous'
async>
</script>

{{/* Backwards compatibility for deprecated parameter ".Site.Params.staticman.staticman" */}}
{{ else if or .Site.Params.staticman.enabled .Site.Params.staticman.staticman }}
<article class="post">
<div class='post'>
{{/* $entryId stores the transformed current path for sorting Staticman data file */}}
{{- $entryId := .File.UniqueID -}}
{{/* Additional div wrapper for proper consistent margin */}}
<div>
<h2 id="say-something">{{ i18n "say_something" }}</h2>
<form id="comment-form" class="new-comment" method="POST">
<h2 id='say-something'>{{ i18n "say_something" }}</h2>
<form id='comment-form' class='new-comment' method='POST'>
{{/* Display reply target */}}
<h3 class='reply-notice hidden'>
<span class='reply-name'></span>
<a class='reply-close-btn button'><i class='fas fa-times'></i></a>
</h3>

{{/* Hidden fields */}}
<input type="hidden" name="options[entryId]" value="{{ $entryId }}">
<input type='hidden' name='options[entryId]' value='{{ $entryId }}'>
<input type='hidden' name='fields[replyThread]' value=''>
<input type='hidden' name='fields[replyID]' value=''>
<input type='hidden' name='fields[replyName]' value=''>
Expand All @@ -43,9 +44,9 @@ <h3 class='reply-notice hidden'>
{{/* reCAPTHCA v2 */}}
{{ if and .Site.Params.staticman.recaptcha.siteKey .Site.Params.staticman.recaptcha.encryptedKey }}
{{ with .Site.Params.staticman.recaptcha }}
<input hidden name="options[reCaptcha][siteKey]" value="{{ .sitekey }}">
<input hidden name="options[reCaptcha][secret]" value="{{ .encryptedkey }}">
<div class="g-recaptcha" data-sitekey="{{ .sitekey }}"></div>
<input hidden name='options[reCaptcha][siteKey]' value='{{ .sitekey }}'>
<input hidden name='options[reCaptcha][secret]' value='{{ .encryptedkey }}'>
<div class='g-recaptcha' data-sitekey='{{ .sitekey }}'></div>
{{ end }}
{{ end }}

Expand All @@ -63,7 +64,7 @@ <h3 class='reply-notice hidden'>
</div>

{{/* Additional div wrapper for proper consistent margin */}}
<div>
<div class='comments-container'>
<h2>{{ i18n "comments" }}</h2>

{{- if .Site.Data.comments -}}
Expand All @@ -77,37 +78,37 @@ <h2>{{ i18n "comments" }}</h2>
{{- $threadID := ._id }}
{{- range $comments -}}
{{ if or (in ._id $threadID) (in .replyThread $threadID) }}
<div id="{{ ._id }}" class="comment{{ with .replyThread }} comment-reply{{ end }}">
<article id='{{ ._id }}' class='comment{{ with .replyThread }} comment-reply{{ end }}' data-reply-thread='{{ $threadID }}'>
<header>
<img class="comment-avatar circle" src="https://www.gravatar.com/avatar/{{ .email }}?s=100" alt="{{ .name }}'s Gravatar">
<img class='comment-avatar circle' src='https://www.gravatar.com/avatar/{{ .email }}?s=100' alt="{{ .name }}'s Gravatar">
<div>
<div class="comment-author-container">
<h3 class="comment-author">
<div class='comment-author-container'>
<h3 class='comment-author'>
{{- if .website -}}
<a rel="external nofollow" href="{{ .website }}">{{ .name }}</a>
<a rel='external nofollow' href='{{ .website }}'>{{ .name }}</a>
{{- else -}}
{{- .name -}}
{{- end -}}
</h3>
<a class="comment-date" href="#{{ ._id }}" title="Permalink to this comment">
<time datetime="{{ .date }}">{{ dateFormat "02 Jan 06 15:04" .date }}</time>
<a class='comment-date' href='#{{ ._id }}' title='Permalink to this comment'>
<time datetime='{{ .date }}'>{{ dateFormat "02 Jan 06 15:04" .date }}</time>
</a>
</div>
<!-- TODO: Assess Value (ref ln#73): <a href='#{{ .replyID }}' class='reply-target'>{{ .replyName }}</a> -->
<!-- TODO: Assess Value: <span class ="comment-threadID hidden">{{ .replyThread }}</span> -->
<a class="comment-reply-btn" href="#say-something">{{ i18n "reply" }}</a>
<a class='comment-reply-btn' href='#say-something'>{{ i18n "reply" }}</a>
</div>
</header>
<div class="comment-content">
<div class='comment-content'>
{{ .body | markdownify }}
</div>
</div>
</article>
{{ end }}
{{ end }}
{{ end }}
{{ end }}
{{ end }}
{{ end }}
</div>
</article>
</div>
{{ end }}
30 changes: 16 additions & 14 deletions layouts/_default/single.html
Original file line number Diff line number Diff line change
@@ -1,20 +1,22 @@
{{ define "main" }}
<article class="post">
{{ .Render "header" }}
<div id="socnet-share">
{{ partial "share-buttons" . }}
<article>
<div class="post">
{{ .Render "header" }}
<div id="socnet-share">
{{ partial "share-buttons" . }}
</div>
<div class="content">
{{ .Render "featured" }}
{{ .Content }}
</div>
<footer>
{{ .Render "stats" }}
</footer>
</div>
<div class="content">
{{ .Render "featured" }}
{{ .Content }}
</div>
<footer>
{{ .Render "stats" }}
</footer>
{{ if not ( eq .Params.comments false) }}
{{ .Render "comments" }}
{{ end }}
</article>
{{ if not ( eq .Params.comments false) }}
{{ .Render "comments" }}
{{ end }}
<div class="pagination">
{{ if .NextInSection }}
<a href="{{ .NextInSection.RelPermalink }}" class="button left"><span>{{ .NextInSection.Title }}</span></a>
Expand Down

0 comments on commit d8c65c2

Please sign in to comment.