你需要先安裝一款使用者樣式管理器擴展,比如 Stylus ,才能安裝此樣式
你需要先安裝一款使用者樣式管理器擴展,比如 Stylus ,才能安裝此樣式
你需要先安裝一款使用者樣式管理器擴展,比如 Stylus ,才能安裝此樣式
你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式
你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式
你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式
(我已經安裝了使用者樣式管理器,讓我安裝!)
換行
// ==UserScript==
// @name AO3 Floaty Comment Box (Responsive)
// @namespace http://tampermonkey.net/
// @version 1.4
// @description AO3 Floaty Comment Box (Responsive) is a userscript created to facilitate commenting on the fly while reading on archiveofourown - specifically for mobile browsing
// @author Schildpath
// @match http://archiveofourown.org/*
// @match https://archiveofourown.org/*
// @match http://www.archiveofourown.org/*
// @match https://www.archiveofourown.org/*
// @grant none
// @license MIT
// ==/UserScript==
(function() {
'use strict';
console.log("AO3 Floaty Script Loaded");
let floatyCreated = false;
function createFloaty(commentBox) {
// Prevent double initialization or initialization without cause
if (floatyCreated || !commentBox) return;
floatyCreated = true;
// Floaty toggle button
const floatyButton = document.createElement('button');
floatyButton.innerHTML = '✍';
floatyButton.id = 'floaty-toggle-button';
floatyButton.setAttribute('aria-label', 'Toggle Comment Box');
floatyButton.style.cssText = `
position: fixed;
inset: auto 1rem 1rem auto;
z-index: 9999;
padding: 0.2em 0.4em;
border-radius: 0.4em;
font-family: inherit;
font-size: 1.3em;
color: inherit;
opacity: .9;
cursor: pointer;
`;
floatyButton.onfocus = (e) => e.target.blur(); // Prevent AO3 focus style
document.body.appendChild(floatyButton);
// Floaty insert quote button
const insertButton = document.createElement('button');
insertButton.innerHTML = '« »';
insertButton.id = 'floaty-insert-button';
insertButton.setAttribute('aria-label', 'Insert Quote');
insertButton.style.cssText = `
position: fixed;
inset: auto 1rem 3.7rem auto;
z-index: 9999;
padding: .4em;
padding-bottom:.6em;
border-radius: 0.4em;
font-family: inherit;
font-size: .9em;
color: inherit;
opacity: .9;
cursor: pointer;
`;
insertButton.onfocus = (e) => e.target.blur(); // Prevent AO3 focus style
document.body.appendChild(insertButton);
// Floaty box container
const floatyContainer = document.createElement('div');
floatyContainer.id = 'floaty-container';
floatyContainer.style.cssText = `
position: fixed;
bottom: 0;
left: 0;
right: 0;
width: 100vw;
height: 200px;
z-index: 9998;
display: none;
flex-direction: column;
background: inherit;
border-top: 1px solid currentColor;
font-family: inherit;
font-size: 0.8em;
color: inherit;
opacity: .95;
`;
document.body.appendChild(floatyContainer);
// Tips & about container
const infoContainer = document.createElement('div');
infoContainer.style.cssText = `
position: fixed;
bottom: 200px;
left: 0;
right: 0;
z-index: 9997;
display: none;
flex-direction: column;
padding: .9em;
gap: 0.5em;
font-family: inherit;
background: inherit;
border-top: 1px solid currentColor;
border-bottom: 1px solid currentColor;
`;
const closeAboutBtn = document.createElement('button');
closeAboutBtn.innerHTML = 'Close';
const aboutDiv = document.createElement('div');
aboutDiv.innerHTML = `<p><strong>About AO3 Floaty Comment Box (Responsive):</strong></p>` +
'<p>AO3 Floaty Comment Box (Responsive) was created to facilitate commenting while reading - to allow the user to copy paste favorite quotes and write down feelings & thoughts on the fly - specifically for mobile browsing.</p>' +
'<ul><li>🔛 <strong>Toggle function:</strong> You can minimize the floaty comment box as you read and reopen it to continue to edit your review.</li>' +
'<li>💬 <strong>Insert quotes:</strong> Select favorite quotes and use the « » button to insert them your comment: the selected text will be formatted in italics and put between quotation marks.</li>' +
'<li>🔄 <strong>Syncing:</strong> Everything that is typed in the floaty comment box will be automatically synced with the real comment box below the fic.</i>' +
'<li>💌 <strong>Submitting:</strong> Your comment will only be submitted once you submit it in the the real comment form below (scroll down with the ⇓ button).</li></ul>' +
`<p>© AO3 Floaty Comment Box (Responsive) was directly inspired (with permission) by an AO3 userscript originally developed by <a href="https://ravenel.tumblr.com/post/156555172141/i-saw-this-post-by-astropixie-about-how-itd-be">ravenel</a>.</p>`
;
aboutDiv.appendChild(closeAboutBtn);
const closeTipsBtn = document.createElement('button');
closeTipsBtn .innerHTML = 'Close';
const tipsDiv = document.createElement('div');
tipsDiv.innerHTML = `<p><strong>Suggestions for writing a comment:</strong></p>` +
'<ul><li>💬 Quotes you liked (select text and click « » to include)</li>'+
'<li>🎭 Scenes that you liked, or moved you, or surprised you</li>'+
'<li>😭 What is your feeling at the end of the chapter?</li>'+
'<li>👓 What are you most looking forward to next?</li>' +
'<li>🔮 Do you have any predictions for the next chapters you want to share?</li>'+
'<li>❓ Did this chapter give you any questions you can't wait to find out the answers for?</li>' +
'<li>✨ Is there something unique about the story that you like?</li>'+
'<li>🤹 Does the author have a style that really works for you?</li>' +
'<li>🎤 Did the author leave any comments in the notes that said what they wanted feedback on?</li>' +
'<li>🗣 Even if all you have are incoherent screams of delight, authors love to hear that as well.</li></ul>'
;
tipsDiv.appendChild(closeTipsBtn);
infoContainer.appendChild(aboutDiv);
infoContainer.appendChild(tipsDiv);
floatyContainer.appendChild(infoContainer);
// Floaty container header
const header = document.createElement('div');
header.style.cssText = `
display: flex;
justify-content: flex-start;
align-items: center;
align-content: stretch;
gap: 0.5em;
padding: .5em.9em;
font-size: 1em;
opacity: 1;
`;
const logo = document.createElement('div');
logo.innerHTML = '<span>✍</span>';
const insertBtn = document.createElement('button');
insertBtn.innerHTML = '« »';
const tipsBtn = document.createElement('button');
tipsBtn.innerHTML = '💡';
const aboutBtn = document.createElement('button');
aboutBtn.innerHTML = '❓';
const downBtn = document.createElement('button');
downBtn.innerHTML = '⇓';
const expandBtn = document.createElement('button');
expandBtn.innerHTML = '🗖';
const minimizeBtn = document.createElement('button');
minimizeBtn.innerHTML = '–';
const closeBtn = document.createElement('button');
closeBtn.innerHTML = '❯❯';
[insertBtn, aboutBtn, tipsBtn, downBtn, expandBtn, minimizeBtn, closeBtn].forEach(btn => {
btn.style.cssText = `
font-family: inherit;
color: inherit;
cursor: pointer;
padding: 0.2em 0.4em;
font-size: 1em;
height: 17px;
min-width: 17px;
`;
});
logo.style.cssText = `
cursor: initial;
font-size: 2em;
padding: 0em 0.2em;
`;
closeBtn.style.fontSize = '.8em';
if (window.innerWidth > 768) {
closeBtn.style.marginRight = '1.5em';
}
minimizeBtn.style.display = 'none';
const rightButtons = document.createElement('div');
rightButtons.style.cssText = `
margin-left: auto;
display: flex;
gap: 0.3em;
justify-content: flex-start;
align-items: center;
align-content: stretch;
`;
rightButtons.append(expandBtn, minimizeBtn, closeBtn);
header.append(logo, insertBtn, tipsBtn, aboutBtn, downBtn, rightButtons);
floatyContainer.appendChild(header);
// Textarea
const floatyBox = document.createElement('textarea');
floatyBox.placeholder = 'Work-in-progress review...';
floatyBox.id = 'floaty-box';
floatyBox.style.cssText = `
flex: 1;
padding: .9em;
font-size: 1em;
font-family: inherit;
color: inherit;
background: inherit;
border: none;
resize: none;
outline: none;
width: 100%;
box-sizing: border-box;
`;
floatyContainer.appendChild(floatyBox);
// Button actions
floatyButton.addEventListener('click', () => {
floatyContainer.style.display = 'flex';
floatyButton.style.display = 'none';
insertButton.style.display = 'none';
});
closeBtn.addEventListener('click', () => {
floatyContainer.style.display = 'none';
floatyButton.style.display = 'block';
insertButton.style.display = 'block';
aboutDiv.style.display = 'none';
tipsDiv.style.display = 'none';
});
[insertBtn, insertButton].forEach(btn => {
btn.addEventListener('click', () => {
const selected = window.getSelection().toString().trim();
if (selected) {
floatyBox.focus();
floatyBox.setRangeText(`<i>"${selected}"</i>\n`, floatyBox.selectionStart, floatyBox.selectionEnd, 'end');
floatyBox.dispatchEvent(new Event('input')); // Sync with real box
}
});
});
aboutBtn.addEventListener('click', () => {
infoContainer.style.display = infoContainer.style.display === 'none' ? 'flex' : 'none';
aboutDiv.style.display = 'block';
tipsDiv.style.display = 'none';
});
closeAboutBtn.addEventListener('click', () => {
infoContainer.style.display = 'none';
aboutDiv.style.display = 'none';
});
tipsBtn.addEventListener('click', () => {
infoContainer.style.display = infoContainer.style.display === 'none' ? 'flex' : 'none';
tipsDiv.style.display = 'block';
aboutDiv.style.display = 'none';
});
closeTipsBtn.addEventListener('click', () => {
infoContainer.style.display = 'none';
tipsDiv.style.display = 'none';
});
expandBtn.addEventListener('click', () => {
expandBtn.style.display = 'none';
minimizeBtn.style.display = 'block';
floatyContainer.style.height = '500px';
});
minimizeBtn.addEventListener('click', () => {
minimizeBtn.style.display = 'none';
expandBtn.style.display = 'block';
floatyContainer.style.height = '200px';
});
downBtn.addEventListener('click', () => {
document.querySelector('textarea[name="comment[comment_content]"]').scrollIntoView();
});
// Sync between floaty and real comment box
floatyBox.addEventListener('input', () => {
commentBox.value = floatyBox.value;
});
commentBox.addEventListener('input', () => {
if (commentBox.value !== floatyBox.value) {
floatyBox.value = commentBox.value;
}
});
// [Pending issues]
// Prevent unwanted scrolling
floatyBox.addEventListener('focus', (e) => {
// Prevent mobile "scroll-jump"
requestAnimationFrame(() => {
floatyBox.scrollIntoView({ block: 'nearest', behavior: 'instant' });
// fallback in case `preventScroll` isn’t supported
window.scrollTo(window.scrollX, window.scrollY);
});
});
};
function waitForCommentBox(attempts = 0) {
// This script only applies to story pages where you can comment, which we need to check for
const box = document.querySelector('textarea[name="comment[comment_content]"]');
if (box) {
createFloaty(box);
} else if (attempts < 20) {
setTimeout(() => waitForCommentBox(attempts + 1), 500);
} else {
console.log("Comment box not found after 20 attempts.");
}
}
function init() {
console.log("Init called");
waitForCommentBox();
}
window.addEventListener('load', init);
document.addEventListener('pjax:end', init);
setInterval(() => {
if (!floatyCreated) init();
}, 1500); // Safety net for mobile load quirks
})();