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

Vanilla Staticman JS & HTML reorganization #245

Merged
merged 4 commits into from
May 2, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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