// ==UserScript==
// @name Backloggd - Copy Review Link
// @namespace https://github.com/ceeprus
// @version 1.1
// @description Adds an icon-only "copy link" button next to the "Open review" button on Backloggd review cards
// @icon https://www.google.com/s2/favicons?sz=64&domain=backloggd.com
// @author Cee
// @match *://*.backloggd.com/*
// @grant none
// @run-at document-idle
// @downloadURL https://raw.githubusercontent.com/ceeprus/userscript/main/backloggd/copy-reviews.user.js
// @updateURL https://raw.githubusercontent.com/ceeprus/userscript/main/backloggd/copy-reviews.user.js
// ==/UserScript==
(function () {
'use strict';
const STYLE_ID = 'bglg-copy-review-link-style';
function injectStyles() {
if (document.getElementById(STYLE_ID)) return;
const style = document.createElement('style');
style.id = STYLE_ID;
style.textContent = `
.copy-review-link-btn {
display: inline-flex;
align-items: center;
justify-content: center;
height: 1.5em;
min-width: 1.5em;
padding: 0 0.45em;
border-radius: 4px;
cursor: pointer;
text-decoration: none;
line-height: 1;
background-color: transparent;
border: none;
color: inherit;
opacity: 0.75;
transition: background-color 0.2s ease, opacity 0.2s ease, color 0.2s ease;
}
.copy-review-link-btn:hover {
opacity: 1;
}
.copy-review-link-btn.copied {
background-color: rgba(40, 200, 90, 0.5);
opacity: 1;
color: #fff;
}
.copy-review-link-btn .label {
margin-left: 0.35em;
font-size: 0.85em;
white-space: nowrap;
}
`;
document.head.appendChild(style);
}
function fallbackCopy(text) {
const ta = document.createElement('textarea');
ta.value = text;
ta.style.position = 'fixed';
ta.style.opacity = '0';
document.body.appendChild(ta);
ta.select();
try {
document.execCommand('copy');
} catch (err) {
console.error('[Backloggd Copy Link] fallback copy failed', err);
}
document.body.removeChild(ta);
}
function createCopyButton(href) {
const btn = document.createElement('a');
btn.className = 'copy-review-link-btn';
btn.title = 'Copy review link';
btn.setAttribute('role', 'button');
btn.innerHTML = '';
btn.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
const fullUrl = new URL(href, location.origin).href;
const showCopied = () => {
btn.classList.add('copied');
btn.innerHTML = 'Link copied';
setTimeout(() => {
btn.classList.remove('copied');
btn.innerHTML = '';
}, 1500);
};
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(fullUrl).then(showCopied).catch(() => {
fallbackCopy(fullUrl);
showCopied();
});
} else {
fallbackCopy(fullUrl);
showCopied();
}
});
return btn;
}
function addCopyButtons() {
document.querySelectorAll('a.open-review-link').forEach((link) => {
const parentCol = link.closest('.col-auto');
if (!parentCol || parentCol.dataset.copyBtnAdded) return;
const href = link.getAttribute('href');
if (!href) return;
const wrapper = document.createElement('div');
wrapper.className = 'col-auto my-auto pr-1';
wrapper.appendChild(createCopyButton(href));
parentCol.insertAdjacentElement('afterend', wrapper);
parentCol.dataset.copyBtnAdded = 'true';
});
}
function init() {
injectStyles();
addCopyButtons();
}
init();
// Backloggd uses Turbo (Hotwire) for navigation
document.addEventListener('turbo:load', init);
document.addEventListener('turbo:render', init);
document.addEventListener('turbo:frame-load', init);
// Catch infinite-scroll / dynamically loaded review cards
const observer = new MutationObserver(() => addCopyButtons());
observer.observe(document.body, { childList: true, subtree: true });
})();