您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
AO3 Floaty Comment Box (Responsive) is a userscript created to facilitate commenting on the fly while reading on archiveofourown - specifically for mobile browsing
当前为
// ==UserScript== // @name AO3 Floaty Comment Box (Responsive) // @namespace http://tampermonkey.net/ // @version 1.8.1 // @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; } }); // Prevent touchstart to catch scroll-input bug on firefox mobile floatyBox.addEventListener('touchstart', e => { e.stopPropagation(); e.preventDefault(); }, { passive: true }); } 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 })();