-
-
Save xbreaker/37c0fd11111e514e406038394b812bb1 to your computer and use it in GitHub Desktop.
Staticman
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
.comment-thread { | |
box-sizing: border-box; | |
max-width: 100%; | |
margin: auto; | |
border: 1px solid transparent; /* Removes margin collapse */ | |
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; | |
line-height: 1.4; | |
font-size: 16px; | |
color: rgba(0, 0, 0, 0.85); | |
padding-top: 30px; | |
} | |
body.dark .comment-thread { | |
color: rgba(255, 255, 255, 0.85); | |
} | |
.comment-thread button { | |
-moz-appearance: none; | |
-webkit-appearance: none; | |
appearance: none; | |
font-size: 14px; | |
padding: 0px 8px; | |
color: rgba(0, 0, 0, 0.85); | |
border: 1px solid rgba(0, 0, 0, 0.2); | |
border-radius: 4px; | |
} | |
body.dark .comment-thread button { | |
background-color: #9d9d9d; | |
} | |
.comment-thread button:hover, | |
.comment-thread button:focus, | |
.comment-thread button:active { | |
cursor: pointer; | |
background-color: #ecf0f1; | |
} | |
body.dark .comment-thread button:hover, | |
body.dark .comment-thread button:focus, | |
body.dark .comment-thread button:active { | |
background-color: #838383; | |
} | |
.m-0 { | |
margin: 0; | |
} | |
.sr-only { | |
position: absolute; | |
left: -10000px; | |
top: auto; | |
width: 1px; | |
height: 1px; | |
overflow: hidden; | |
} | |
/* Comment */ | |
.comment { | |
position: relative; | |
margin: 15px auto; | |
} | |
.comment-heading { | |
display: flex; | |
align-items: center; | |
height: 50px; | |
font-size: 14px; | |
} | |
.comment-avatar { | |
width: 32px; | |
height: 32px; | |
} | |
.comment-avatar img { | |
width: 32px; | |
height: 32px; | |
border-radius: 4px; | |
} | |
.comment-info { | |
color: rgba(0, 0, 0, 0.5); | |
margin-left: 10px; | |
} | |
body.dark .comment-info { | |
color: rgba(255, 255, 255, 0.5); | |
} | |
.comment-author { | |
color: rgba(0, 0, 0, 0.85); | |
font-weight: bold; | |
text-decoration: none; | |
} | |
body.dark .comment-author { | |
color: rgba(255, 255, 255, 0.85); | |
} | |
.comment-author:hover { | |
text-decoration: underline; | |
} | |
.comment-owner { | |
color: #47c02e; | |
} | |
.replies { | |
margin-left: 20px; | |
} | |
/* Adjustments for the comment border links */ | |
.comment-border-link { | |
display: block; | |
position: absolute; | |
top: 50px; | |
left: 0; | |
width: 12px; | |
height: calc(100% - 50px); | |
border-left: 4px solid transparent; | |
border-right: 4px solid transparent; | |
background-color: rgba(0, 0, 0, 0.1); | |
background-clip: padding-box; | |
} | |
body.dark .comment-border-link { | |
background-color: rgba(255, 255, 255, 0.1); | |
} | |
.comment-border-link:hover { | |
background-color: rgba(0, 0, 0, 0.3); | |
} | |
.comment-body { | |
padding-left: 28px; | |
} | |
.comment-body p { | |
margin-block-start: 1em; | |
margin-block-end: 1em; | |
white-space: pre-line; | |
} | |
.comment-body a { | |
box-shadow: 0 1px; | |
} | |
.comment-body .reply-form button { | |
padding: 0px 8px;; | |
} | |
.replies { | |
margin-left: 28px; | |
} | |
/* Adjustments for toggleable comments */ | |
details.comment summary { | |
position: relative; | |
list-style: none; | |
cursor: pointer; | |
} | |
details.comment summary::-webkit-details-marker { | |
display: none; | |
} | |
details.comment:not([open]) .comment-heading { | |
border-bottom: 1px solid rgba(0, 0, 0, 0.2); | |
} | |
.comment-heading::after { | |
display: inline-block; | |
position: absolute; | |
right: 5px; | |
align-self: center; | |
font-size: 12px; | |
color: rgba(0, 0, 0, 0.55); | |
} | |
details.comment[open] .comment-heading::after { | |
content: "-"; | |
} | |
details.comment:not([open]) .comment-heading::after { | |
content: "+"; | |
} | |
@media screen and (-ms-high-contrast: active), (-ms-high-contrast: none) { | |
/* Resets cursor, and removes prompt text on Internet Explorer */ | |
.comment-heading { | |
cursor: default; | |
} | |
details.comment[open] .comment-heading::after, | |
details.comment:not([open]) .comment-heading::after { | |
content: " "; | |
} | |
} | |
.reply-form.loading { | |
opacity: 0.5; | |
cursor: not-allowed; | |
} | |
.reply-form .notice { | |
padding: 10px 5px; | |
margin: 5px 5px 0; | |
display: none; | |
border-radius: 3px; | |
} | |
.reply-form .notice.sending { | |
background-color: #fafa2e; | |
display: block; | |
} | |
.reply-form .notice.success { | |
background-color: #3bfa2e; | |
display: block; | |
} | |
.reply-form .notice.error { | |
background-color: #ff9871; | |
display: block; | |
} | |
.reply-form input, .reply-form textarea { | |
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; | |
font-size: 16px; | |
margin-top: 15px; | |
margin-bottom: 5px; | |
border: 1px solid rgb(188, 188, 188); | |
border-radius: 3px; | |
padding: 4px; | |
} | |
body.dark .reply-form input, | |
body.dark .reply-form textarea { | |
color: white; | |
} | |
.reply-form textarea { | |
width: 100%; | |
max-width: 100%; | |
margin-top: 15px; | |
margin-bottom: 5px; | |
padding: 4px; | |
} | |
.reply-form input { | |
width: 50%; | |
display: block; | |
} | |
.reply-form button { | |
margin: 15px 0 5px 0; | |
padding: 5px; | |
} | |
.d-none { | |
display: none; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<section id="comment-thread" class="js-comments comment-thread"> | |
{{ if $.Site.Data.comments }} | |
{{ $comments := index $.Site.Data.comments .Slug }} | |
{{ if $comments }} | |
{{ $c := len $comments }} | |
{{ if (and (eq (mod $c 10) 1) (ne (mod $c 100) 11)) }} | |
<h3>{{ $c }} комментарий</h3> | |
{{ else }} | |
{{ if (and (ge (mod $c 10) 2) (le (mod $c 10) 4) (or (lt (mod $c 100) 10) (ge (mod $c 100) 20))) }} | |
<h3>{{ $c }} комментария</h3> | |
{{ else }} | |
<h3>{{ $c }} комментариев</h3> | |
{{ end }} | |
{{ end }} | |
{{ end }} | |
{{ $.Scratch.Set "hasComments" 0 }} | |
{{ range $comments }} | |
{{ if not .thread }} | |
{{ $.Scratch.Add "hasComments" 1 }} | |
{{ $.Scratch.Set "hasReplies" 0 }} | |
{{ $.Scratch.Set "thread" ._id }} | |
{{ $.Scratch.SetInMap "replyIndices" ._id 0 }} | |
<details open class="comment" id="comment-{{ $.Scratch.Get "hasComments" }}" data-thread="{{._id}}" data-id="{{._id}}" data-name="{{ .name }}"> | |
<a href="#comment-{{ $.Scratch.Get "hasComments" }}" class="comment-border-link"> | |
<span class="sr-only">Перейти к первому комментарию</span> | |
</a> | |
<summary> | |
<div class="comment-heading"> | |
<div class="comment-avatar"> | |
<img src="https://www.gravatar.com/avatar/{{ .email }}?s=48&d=identicon" alt="{{ .name }}'s gravatar"> | |
</div> | |
<div class="comment-info"> | |
<span class="comment-author">{{ .name }}</span> | |
<p class="m-0"> | |
{{ if eq .email $.Site.Params.emailhash }}<span class="comment-owner">автор • </span>{{ end }}<time datetime="{{ .date }}">{{ dateFormat (default "2 Jan 2006 15:04:05" .Site.Params.dateformat) .date }}</time> | |
</p> | |
</div> | |
</div> | |
</summary> | |
<div class="comment-body"> | |
<p>{{ .message | markdownify }}</p> | |
<button data-toggle="reply-form">Ответить</button> | |
</div> | |
<div class="replies"> | |
{{ range $comments }} | |
{{ if eq .thread ($.Scratch.Get "thread") }} | |
{{ $.Scratch.Add "hasReplies" 1 }} | |
{{ $.Scratch.SetInMap "replyIndices" ._id ($.Scratch.Get "hasReplies") }} | |
<details open class="comment" id="comment-{{ $.Scratch.Get "hasComments" }}r{{ $.Scratch.Get "hasReplies" }}" data-thread="{{.thread}}" data-id="{{._id}}" data-name="{{ .name }}"> | |
<a href="#comment-{{ $.Scratch.Get "hasComments" }}r{{ $.Scratch.Get "hasReplies" }}" class="comment-border-link"> | |
<span class="sr-only">Перейти к первому комментарию</span> | |
</a> | |
{{- $replyTargetIndex := (index ($.Scratch.Get "replyIndices") .parent) -}} | |
{{- $replyLinkEnd := cond (eq $replyTargetIndex 0) "" (print "r" $replyTargetIndex) -}} | |
<summary> | |
<div class="comment-heading"> | |
<div class="comment-avatar"> | |
<img src="https://www.gravatar.com/avatar/{{ .email }}?s=48&d=identicon" alt="{{ .name }}'s gravatar"> | |
</div> | |
<div class="comment-info"> | |
<span class="comment-author">{{ .name }}</span> ответил <a class="comment-author" href='#comment-{{ $.Scratch.Get "hasComments" }}{{ $replyLinkEnd }}' class='comment-reply-target' title='{{ .parent }}'> {{ .parentName }}</a> | |
<p class="m-0"> | |
{{ if eq .email $.Site.Params.emailhash }}<span class="comment-owner">автор</span> • {{ end }}<time datetime="{{ .date }}">{{ dateFormat (default "2 Jan 2006 15:04:05" .Site.Params.dateformat) .date }}</time> | |
</p> | |
</div> | |
</div> | |
</summary> | |
<div class="comment-body"> | |
<p>{{ .message | markdownify }}</p> | |
<button data-toggle="reply-form">Ответить</button> | |
</div> | |
</details> | |
{{ end }} | |
{{ end }} | |
</div> | |
</details> | |
{{ $.Scratch.Delete "replyIndices" }} | |
{{ end }} | |
{{ end }} | |
{{ end }} | |
<form id="reply-form" class="js-form reply-form" method="post" action="{{ .Site.Params.staticman.api }}"> | |
<h3 id="reply-form-head">Добавить комментарий</h3> | |
<div id="notice" class="notice"></div> | |
<input type="hidden" name="options[slug]" value="{{ .Slug }}"> | |
<input type="hidden" name="options[parent]" value="{{ .Permalink }}"> | |
<input id="input-thread" type="hidden" name="fields[thread]" value=""> | |
<input id="input-parent" type="hidden" name="fields[parent]" value=""> | |
<input id="input-parentName" type="hidden" name="fields[parentName]" value=""> | |
{{ if .Site.Params.staticman.recaptcha.enabled }} | |
<input type="hidden" name="options[reCaptcha][siteKey]" value="{{ .Site.Params.staticman.recaptcha.sitekey }}"> | |
<input type="hidden" name="options[reCaptcha][secret]" value="{{ .Site.Params.staticman.recaptcha.secret }}"> | |
{{ end }} | |
<textarea name="fields[message]" placeholder="Вы можете использовать синтаксис Markdown" rows="6" required></textarea> | |
<input name="fields[name]" type="text" placeholder="имя" required/> | |
<input type="email" name="fields[email]" placeholder="email" required/> | |
{{ if .Site.Params.staticman.recaptcha.enabled }} | |
<div class="g-recaptcha" data-sitekey="{{ .Site.Params.staticman.recaptcha.sitekey }}"></div> | |
{{ end }} | |
<button id="submit">Отправить</button> | |
<button id="cancel-button" class="d-none" data-toggle="reply-form-cancel">Отменить</button> | |
</form> | |
</section> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
document.addEventListener( | |
"click", | |
function(event) { | |
const target = event.target, | |
I = function(id) { | |
return document.getElementById(id); | |
}, | |
C = function(cl) { | |
return document.getElementsByClassName(cl)[0]; | |
}; | |
if (target.matches("[data-toggle='reply-form']")) { | |
var cancel = I("cancel-button"), | |
comment = target.parentNode.parentNode, | |
author = comment.getAttribute("data-name"); | |
target.parentNode.appendChild(I("reply-form")); | |
target.classList.toggle("d-none"); | |
I("input-thread").value = comment.getAttribute("data-thread"); | |
I("input-parent").value = comment.getAttribute("data-id"); | |
I("input-parentName").value = author; | |
I("reply-form-head").innerHTML = "Ваш ответ " + author; | |
if(cancel.classList.contains("d-none")) | |
cancel.classList.toggle("d-none"); | |
} | |
if (target.matches("[data-toggle='reply-form-cancel']")) { | |
I("reply-form-head").innerHTML = "Добавить комментарий"; | |
C("d-none").classList.toggle("d-none"); | |
I("cancel-button").classList.toggle("d-none"); | |
I("comment-thread").appendChild(I("reply-form")); | |
} | |
}, | |
false | |
); | |
const form = document.querySelector("#reply-form"); | |
form.addEventListener("submit", submitEvent => { | |
submitEvent.preventDefault(); | |
const notice = document.getElementById("notice"); | |
const fd = new FormData(form); | |
const xhr = new XMLHttpRequest(); | |
const json = {} | |
fd.forEach(function(value, prop){ | |
json[prop] = value | |
}) | |
// convert json to urlencoded query string | |
// SOURCE: https://stackoverflow.com/a/37562814 (comments) | |
const formBody = Object.keys(json).map(key => encodeURIComponent(key) + '=' + encodeURIComponent(json[key])).join('&') | |
submitEvent.submitter.setAttribute("disabled", "disabled"); | |
form.classList.toggle("loading"); | |
notice.innerHTML = "Комментарий отправляется"; | |
notice.classList.add("sending"); | |
xhr.open("POST", form.action); | |
xhr.onreadystatechange = function (event) { | |
if (xhr.readyState == 4) { | |
if(xhr.status == 200) { | |
form.classList.toggle("loading"); | |
notice.innerHTML = "Комментарий отправлен успешно"; | |
notice.classList.remove("error"); | |
notice.classList.remove("sending"); | |
notice.classList.add("success"); | |
submitEvent.submitter.removeAttribute("disabled"); | |
form.reset(); | |
} else { | |
let response = JSON.parse(xhr.responseText) | |
form.classList.toggle("loading"); | |
if(response && response.errorCode === 'RECAPTCHA_INVALID_INPUT_RESPONSE') { | |
notice.innerHTML = "Ошибка отправки комментария (reCaptcha не пройдена)"; | |
} else { | |
notice.innerHTML = "Ошибка отправки комментария"; | |
} | |
notice.classList.remove("success"); | |
notice.classList.remove("sending"); | |
notice.classList.add("error"); | |
submitEvent.submitter.removeAttribute("disabled"); | |
} | |
} | |
}; | |
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); | |
xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); | |
xhr.send(formBody); | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{{- if (and (eq .Kind "page") (ne .Layout "archives") (ne .Layout "search")) }} | |
{{- if not .Site.Params.assets.disableFingerprinting }} | |
{{- $comments := slice (resources.Get "js/comments.js") | resources.Concat "assets/js/comments.js" | minify | fingerprint }} | |
<script defer crossorigin="anonymous" src="{{ $comments.RelPermalink }}" integrity="{{ $comments.Data.Integrity }}"></script> | |
{{- else }} | |
{{- $comments := slice (resources.Get "js/comments.js") | resources.Concat "assets/js/comments.js" | minify }} | |
<script defer crossorigin="anonymous" src="{{ $comments.RelPermalink }}"></script> | |
{{- end }} | |
{{- end }} | |
{{- if .Site.Params.staticman.recaptcha.enabled -}} | |
<script src='https://www.google.com/recaptcha/api.js'></script> | |
{{- end -}} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
comments: | |
allowedFields: ["name", "email", "message", "thread", "parentName", "parent"] | |
branch : "main" | |
commitMessage : "New comment from {fields.name} on {options.slug}" | |
path: "data/comments/{options.slug}" | |
filename : "comment-{@timestamp}" | |
format : "yaml" | |
moderation : true | |
requiredFields : ["name", "email", "message"] | |
pullRequestBody : "Dear human,\n\nHere's a new entry for your approval. :tada:\n\nMerge the pull request to accept it, or close it to send it away.\n\n:heart: Your friends [Staticman](https://staticman.net) && [@xbreaker](https://aybe.org) :muscle:\n\n---\n" | |
transforms: | |
email : md5 | |
generatedFields: | |
date: | |
type : "date" | |
options: | |
format : "timestamp-seconds" | |
reCaptcha: | |
enabled : false | |
siteKey : "" | |
secret : "" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment