こちらのページの内容をQooQに適用しました。
QooQはデフォルトでシェアボタンのまとまりが2つあります。
シェアボタンのならびに「いいねボタン」と「カウンター」を設置するカスタマイズです。
位置や色などはCSSで調整してください。...
概要
「いいねボタン」と「カウンター」をそれぞれ2か所に設置。それぞれ連動できるようにする。(片方のボタンが押されたら、もう片方のボタンも反応する。)
事前準備
「いいね」の追加・集計にはGoogleスプレッドシート、Google Apps Scriptを利用します。
具体的なGoogleスプレッドシート、Google Apps Scriptは以下のものを使います。
Google Apps Scriptで簡単作成!ブログにいいねボタンを設置する方法-すたすた式
また、Google Apps Scriptは「デプロイ」という作業をします。「デプロイ」がはじめてという方はこちらも一読しておくと作業がスムーズです。
初心者向けGoogle Apps Scriptでウェブアプリをデプロイする方法・手順-すたすた式
HTML
シェアボタンの場所に「いいねボタン」と「カウンター」を追加します。
シェアボタンの場所:
<b:includable id='shareButtons' var='post'>
<div class='single-share'>
<a class='single-share-twitter' expr:href='"https://twitter.com/intent/tweet?url=" + data:post.canonicalUrl + "&text=" + data:post.title' target='_blank' title='ツイッターでつぶやく'>t</a>
<a class='single-share-facebook' expr:href='"https://www.facebook.com/sharer/sharer.php?u=" + data:post.canonicalUrl + "&t=" + data:post.title' target='_blank' title='フェイスブックでシェア'>f</a>
<a class='single-share-hatena' expr:href='"http://b.hatena.ne.jp/add?mode=confirm&url=" + data:post.canonicalUrl' title='はてなブックマークに追加'>B!</a>
<a class='single-share-pocket' expr:href='"https://getpocket.com/edit?url=" + data:post.canonicalUrl + "&title=" + data:post.title' target='_blank' title='Pocketに保存'>P</a>
<a class='single-share-line' expr:href='"https://social-plugins.line.me/lineit/share?url=" + data:post.canonicalUrl' target='_blank' title='LINEで送る'>L</a>
</div>
</b:includable>
「いいねボタン」と「カウンター」を追加:
<b:includable id='shareButtons' var='post'>
<div class='single-share'>
<a class='single-share-twitter' expr:href='"https://twitter.com/intent/tweet?url=" + data:post.canonicalUrl + "&text=" + data:post.title' target='_blank' title='ツイッターでつぶやく'>t</a>
<a class='single-share-facebook' expr:href='"https://www.facebook.com/sharer/sharer.php?u=" + data:post.canonicalUrl + "&t=" + data:post.title' target='_blank' title='フェイスブックでシェア'>f</a>
<a class='single-share-hatena' expr:href='"http://b.hatena.ne.jp/add?mode=confirm&url=" + data:post.canonicalUrl' title='はてなブックマークに追加'>B!</a>
<a class='single-share-pocket' expr:href='"https://getpocket.com/edit?url=" + data:post.canonicalUrl + "&title=" + data:post.title' target='_blank' title='Pocketに保存'>P</a>
<a class='single-share-line' expr:href='"https://social-plugins.line.me/lineit/share?url=" + data:post.canonicalUrl' target='_blank' title='LINEで送る'>L</a>
<!--いいねボタンとカウンター-->
<button id="upVoteButton" class="upvote-button" title="この記事にいいね" aria-label="この記事にいいね" aria-pressed="false" type="button">いいね</button>
<div id="upVoteCounter" class="upvote-counter" aria-live="polite"></div>
</div>
</b:includable>
シェアボタンのまとまりは2か所ありますが変更箇所はこの1か所でOKです
JavaScript
gas_WebApps_URL
のところにデプロイしたウェブアプリのURLをコピペしてください。
scriptURL = "gas_WebApps_URL",
↓
scriptURL = "https://script.google.com/.../exec",
JavaScript:
/**
* ボタンがクリックされたとき、記事のタイトルとURLをGoogle Apps Script(GAS)経由でスプレッドシートに書き込みます。
* 書き込まれたURLは集計され、webサイトを開いたときにその集計された数が表示されます。
*
* QooQ用にカスタマイズ
*
* @param {Object} options - オプションオブジェクト
* @param {string} options.buttonSelector - ボタン要素のID
* @param {string} options.counterSelector - いいねの数を表示する要素のID
* @param {string} options.scriptURL - GASスクリプトのURL
*/
/*! Copyright:2025 sutajp | Released under the MIT license | https://sutasutashiki.blogspot.com/p/mit-license.html */
function setupUpVote(options) {
"use strict";
const {
buttonSelector = ".upvote-button",
counterSelector = ".upvote-counter",
scriptURL = "gas_WebApps_URL",
} = options || {};
const currentURL = window.location.origin + window.location.pathname;
const upVoteButtons = document.querySelectorAll(buttonSelector);
const upVoteCounters = document.querySelectorAll(counterSelector);
// countを宣言
let count = 0;
let previousCount = 0; // エラー発生前のカウントを保持
async function getUpVoteCount() {
const param = { currentURL };
const query = new URLSearchParams(param);
try {
const response = await fetch(`${scriptURL}?${query}`);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
count = await response.json(); // グローバルなcountを更新
upVoteCounters.forEach((counter) => {
counter.textContent = count;
});
} catch (error) {
console.error("Fetch Error:", error);
upVoteCounters.forEach((counter) => {
counter.textContent = "通信エラーが発生しました。";
});
}
}
async function sendTitleAndURL() {
upVoteButtons.forEach((btn) => {
if (btn.disabled) return;
btn.disabled = true;
btn.ariaPressed = true;
const upVoteIcon = btn.querySelector(".upvote-icon");
if (upVoteIcon) {
upVoteIcon.dataset.disabled = true;
}
});
previousCount = count; // エラー発生前のカウントを保存
upVoteCounters.forEach((cnt) => {
cnt.textContent = parseInt(cnt.textContent, 10) + 1; // 楽観的更新
});
const params = new URLSearchParams({
sendTitle: document.title,
sendURL: currentURL,
});
try {
const response = await fetch(scriptURL, {
method: "POST",
headers: {
"Accept": "application/json", //JSON形式のレスポンスを明示
"Content-Type": "application/x-www-form-urlencoded",
},
body: params,
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
console.log("いいね送信成功");
} catch (error) {
console.error("Fetch Error:", error); // エラーオブジェクト全体を表示
upVoteCounters.forEach((counter) => {
counter.textContent = "通信エラーが発生しました。";
});
upVoteCounters.forEach((cnt) => {
cnt.textContent = previousCount; // エラー発生前のカウントに復元
});
}
}
getUpVoteCount();
upVoteButtons.forEach((button) => {
button.addEventListener("click", sendTitleAndURL);
});
}
setupUpVote();
メモ
ボタン、カウンターがそれぞれ2個あるのでdocument.querySelectorAll()
でそれぞれ要素を取得。
forEach()
で繰り返し処理。
設置場所など
下のようにJavaScriptを<b:if cond='data:view.isSingleItem'>
ではさむと記事(投稿)のみでプログラムが実行します。
<b:if cond='data:view.isSingleItem'>
<script>
JavaScriptのコード
</script>
</b:if>
設置場所は</body>
の上付近にしました。
コードの一番最後setupUpVote();
でJavaScriptを実行しています。
bodyタグの上の方に設置する場合はsetupUpVote();
を下のコードに書き換えることを検討してください。
document.addEventListener("DOMContentLoaded", function () {
setupUpVote();
});
コメントなし: