From 045b3f3c21bf9c0f9b728fe14ce75e4d2a90944a Mon Sep 17 00:00:00 2001 From: ache Date: Sat, 10 Dec 2022 08:15:44 +0100 Subject: First implementation of Likes --- src/build/index.mjs | 4 +- src/css/_contenu.scss | 111 ++++++++++++++++++++++++++++++++++++++++++++ src/js/love.js | 113 +++++++++++++++++++++++++++++++++++++++++++++ src/templates/article.tmpl | 2 + src/templates/likes.tmpl | 17 +++++++ 5 files changed, 245 insertions(+), 2 deletions(-) create mode 100644 src/js/love.js create mode 100644 src/templates/likes.tmpl diff --git a/src/build/index.mjs b/src/build/index.mjs index a8fac14..54a0b02 100644 --- a/src/build/index.mjs +++ b/src/build/index.mjs @@ -103,6 +103,7 @@ const loadMD = (listFile, suffix) => { }; const leftPanelTmpl = fs.readFileSync('src/templates/left.tmpl', 'utf8'); +const likesTmpl = fs.readFileSync('src/templates/likes.tmpl', 'utf8'); const headerTmpl = fs.readFileSync('src/templates/header.tmpl', 'utf8'); const articleTmpl = fs.readFileSync('src/templates/article.tmpl', 'utf8'); const indexTmpl = fs.readFileSync('src/templates/index.tmpl', 'utf8'); @@ -113,13 +114,12 @@ const baseUrl = 'https://ache.one/'; const partials = { header: headerTmpl, leftPanel: leftPanelTmpl, + likesButton: likesTmpl, hid: hidTmpl, }; const svg = loadSVG(); - const articles = loadMD(listArticles, 'articles'); - const tagsArticle = new Map(); for (const article of articles) { diff --git a/src/css/_contenu.scss b/src/css/_contenu.scss index 4dab365..c261aff 100755 --- a/src/css/_contenu.scss +++ b/src/css/_contenu.scss @@ -281,6 +281,116 @@ code { } } +section.likes { + display: flex; + justify-content: center; + + .likesBox { + padding: 20px; + border-radius: 8px; + background-color: #CDE3EC; + display: flex; + + .likesTitle { + font: monospace; + font-family: Source Sans Pro,Segoe UI,Trebuchet MS,Helvetica,Helvetica Neue,Arial,sans-serif; + font-weight: 800; + } + .likesText { + font-size: 0.8em; + } + .likesNotes { + font-size: 0.8em; + color: #777; + &.err { + color: red; + } + } + + + .icon { + fill: transparent; + stroke: pink; + stroke-width: 20; + cursor: pointer; + position: relative; + svg { + overflow: visible; + width: 10rem; + } + + path { + stroke-dashoffset: 0; + stroke-dasharray: 1550; + transform-origin: center; + } + + .hear-main { + z-index: 2; + } + .heart-background { + position: absolute; + left: 0; + right: 0; + z-index: 1; + stroke: none; + } + .heart-main path.anim { + animation: stroke-animation 2s ease-in-out forwards; + } + + .heart-main ~ .heart-background path.anim-click { + animation: fade-animation 0.35s ease-in-out forwards; + } + .nbLikes { + font-family: Source Sans Pro,Segoe UI,Trebuchet MS,Helvetica,Helvetica Neue,Arial,sans-serif; + color: #00324D; + text-align: center; + position: relative; + top: -10px; + height: 0; + } + } + + @keyframes stroke-animation { + 0% { + fill: transparent; + transform: scale(1); + } + 50% { + fill: pink; + transform: scale(1.1); + } + 70% { + transform: scale(1); + } + 100% { + stroke-dashoffset: 0; + fill: pink; + } + } + + @keyframes fade-animation { + 0% { + fill: transparent; + transform: scale(1); + } + 13% { + fill: lightpink; + transform: scale(1.2); + opacity: 1; + } + 66% { + opacity: 0.8; + } + 100% { + transform: scale(2); + opacity: 0; + } + } + } +} + .tags { display: flex; gap: 10px; @@ -293,6 +403,7 @@ code { padding: 0px 8px; border-radius: 3px; } + } .inline-tag { position: relative; diff --git a/src/js/love.js b/src/js/love.js new file mode 100644 index 0000000..5706c95 --- /dev/null +++ b/src/js/love.js @@ -0,0 +1,113 @@ +window.addEventListener('DOMContentLoaded', () => { + // This script insert a love button at the end of an article page + + const articles = document.querySelectorAll('article'); + + const config = { + likesEndPointBase: 'localhost:3000', + }; + + function getLikeEndPoint() { + let currentArticleName = window.location.pathname.split('/')[2]; + if (currentArticleName.indexOf('.') > 0) { + currentArticleName = currentArticleName.slice(0, currentArticleName.lastIndexOf('.')) + } + return `${window.location.protocol}//${config.likesEndPointBase}/like/${currentArticleName}`; + } + + // The front page have multiple articles + if (articles.length === 1) { + const article = articles[0]; + const likes = article.querySelector('.likes'); + const nbLikes = article.querySelector('.nbLikes'); + const footnotes = article.querySelector('.footnotes'); + + likes.style="display: 'block';"; + const updateNbLikes = () => { + fetch(getLikeEndPoint(), { + method: 'GET', + headers: { + 'i-love-what-you-do': '<3', + }, + }).then((res) => { + if (res.ok) { + res.text().then((text) => nbLikes.textContent = text); + } else { + nbLikes.textContent = ""; + } + }); + }; + + updateNbLikes(); + + if (footnotes) { + // We want: article.insertBefore(likes, footnotes); + footnotes.before(likes); + } + + const icon = likes.querySelector('.icon'); + const messagesLike = likes.querySelector('.likesNotes'); + + icon.addEventListener('mouseover', () => { + for (const c of icon.children) { + const path = c.querySelector('path'); + path?.classList.add('anim'); + } + }); + + icon.addEventListener('mouseout', () => { + for (const c of icon.children) { + const path = c.querySelector('path'); + path?.classList.remove('anim'); + } + }); + + let timeOut; + let messageText; + icon.addEventListener('click', () => { + const c = icon.children[1]; + { + const path = c.querySelector('path'); + console.log(path) + path.classList.add('anim-click'); + path.getAnimations().forEach(anim => { + anim.cancel(); + anim.play(); + }); + + fetch(getLikeEndPoint(), { + method: 'POST', + headers: { + 'i-love-what-you-do': '<3', + }, + }).then(response => { + const currentText = messagesLike.textContent; + const t = response.text().then(text => { + messagesLike.textContent = text; + return text; + }); + + if (response.ok) { + messagesLike.classList.remove('err'); + t.then((t) => messageText = t); + updateNbLikes(); + } else { + if (!messageText) { + messageText = currentText; + } + + messagesLike.classList.add('err'); + setTimeout(() => { + messagesLike.classList.remove('err'); + messagesLike.textContent = messageText; + }, 3000); + } + }).catch(() => { + messagesLike.classList.add('err'); + messagesLike.textContent = 'Désolé, le service est indisponible'; + messageText = ''; + }); + } + }); + } +}); diff --git a/src/templates/article.tmpl b/src/templates/article.tmpl index e5267ff..6b1b628 100644 --- a/src/templates/article.tmpl +++ b/src/templates/article.tmpl @@ -6,7 +6,9 @@
{{# metaData.tags }}{{{ . }}}{{/ metaData.tags }}
+
{{ metaData.pubDateISO }}
{{{ content }}} + {{> likesButton }}
diff --git a/src/templates/likes.tmpl b/src/templates/likes.tmpl new file mode 100644 index 0000000..a0018d9 --- /dev/null +++ b/src/templates/likes.tmpl @@ -0,0 +1,17 @@ + -- cgit v1.2.3