// ==UserScript== // @name AO3: Floating Comment Box - Redux // @namespace https://greasyfork.org/en/users/906106-escctrl // @version 0.1 // @description my version of the floating comment box script by ScriptMouse // @author escctrl // @license MIT // @match *://archiveofourown.org/works/* // @exclude *://archiveofourown.org/works/*/new // @exclude *://archiveofourown.org/works/*/edit // @exclude *://archiveofourown.org/works/new* // @require https://ajax.googleapis.com/ajax/libs/jquery/3.7.0/jquery.min.js // @require https://ajax.googleapis.com/ajax/libs/jqueryui/1.13.2/jquery-ui.min.js // @require https://cdnjs.cloudflare.com/ajax/libs/jqueryui-touch-punch/0.2.3/jquery.ui.touch-punch.min.js // @grant none // @downloadURL none // ==/UserScript== (function($) { 'use strict'; // done: get a floating comment box that is not modal (you can move it around) and resizable // and make it work on mobile XD // GOAL: make it work with my Comment Formatting script (that means adapting the other script) // done: make it submit the comment directly, no need to copy it elsewhere (like owl's comment from bins) // done: cache comment text // GOAL: choices of pseud and chapter (if viewing multiple chapters) // done: insert highlighted text directly in comment (in italics or blockquote) // done: character counter // done: open it from a nicely placed button // done: while submitting, show some sort of progress & load the page to the new comment if possible // done: make it open at the position where it was last closed // button at top of work to open the modal let cmtButton = `
${window.getSelection().toString().trim()}`; $(area).val($(area).val() + highlighted); // insert new text at the end whenTextChanges(area); // trigger an update for the counter } } // update the stored cache (called on any text change) function storeCache() { let cachemap = new Map(JSON.parse(localStorage.getItem('floatcmt'))); // cache is stored per page: path -> text, path-date -> last update date let path = new URL(window.location.href).pathname; // update current values in Map() and localStorage immediately cachemap.set(path, $('#float_cmt_userinput textarea').val()).set(path+"-date", Date.now()); localStorage.setItem('floatcmt', JSON.stringify( Array.from(cachemap.entries()) )); } // on page load, retrieve previously stored cached text function loadCache() { let cachemap = new Map(JSON.parse(localStorage.getItem('floatcmt'))); // cache is stored per page: path -> text, path-date -> last update date let path = new URL(window.location.href).pathname; // is cache outdated? we keep it for 1 month to avoid storage limit issues let cachedate = new Date(cachemap.get(path+"-date") || '1970'); let maxdate = createDate(0, -1, 0); if (cachedate < maxdate) deleteCache(path); let cache = cachemap.get(path); if (!cache) return ""; // if there's nothing stored yet for this path else return cache; } // clean up cache for this page function deleteCache() { let cachemap = new Map(JSON.parse(localStorage.getItem('floatcmt'))); // cache is stored per page: path -> text, path-date -> last update date let path = new URL(window.location.href).pathname; cachemap.delete(path); cachemap.delete(path+'-date'); localStorage.setItem('floatcmt', JSON.stringify( Array.from(cachemap.entries()) )); } // removes all traces of the comment for this page function discardComment() { $('#float_cmt_userinput textarea').val(""); // resets the textarea to blank whenTextChanges($('#float_cmt_userinput textarea')); // updates the counter accordingly deleteCache(); // deletes the cached data closeCommentBox(); // and hides the dialog } // assemble the form data needed to submit the comment function submitComment() { let pseud_id = $("#add_comment_placeholder input[name='comment[pseud_id]']").val(); // need to pick up the selected pseud let action = $("#add_comment_placeholder form").attr("action"); // already contains work ID // consolidating the fields we need for submitting a comment var fd = new FormData(); fd.set("authenticity_token", $("#add_comment_placeholder input[name='authenticity_token']").val()); fd.set("comment[pseud_id]", pseud_id); fd.set("comment[comment_content]", $(dlg).find('textarea').val()); fd.set("controller_name", "works"); console.log(action, fd); // turn buttons into a loading indicator $(dlg).dialog( "option", "buttons", [{ text: "Posting Comment...", click: function() { return false; } }]); // post the comment and reload the page to show it grabResponse(action, fd); } // actually submit the comment in a POST request async function grabResponse(action, fd) { // post the comment! this uses the Fetch API to POST the form data const response = await fetch(action, { method: "POST", body: fd }); // response might be not OK in case of retry later (427) if (!response.ok) { // show an error to the user $(dlg).dialog( "option", "buttons", [{ text: "Error saving comment!", click: function() { return false; } }]); return false; // stop all processing (comment is still cached) } // eff this, there's no way to get the original redirected location of the POST (which includes the new #comment_id at the end) // so all we can do is look at the response page with comments shown (per the redirected GET) // puzzling together the reponse stream until we have a full HTML page (to avoid another background pageload) let responseBody = ""; for await (const chunk of response.body) { let chunktext = new TextDecoder().decode(chunk); // turns it from uint8array to text responseBody += chunktext; } // find out if there's multiple pages of comments now, based on the comment pagination (pick the last page) let lastpage = $(responseBody).find('#comments_placeholder ol.pagination').first().children().eq(-2).find('a').attr('href'); // if there's no pagination, just use the redirect URL; either way scroll that to the footer lastpage = (lastpage > "") ? lastpage.slice(0, -9)+'#footer' : response.url+'#footer'; discardComment(); // clean up since it's now posted // redirect us to where we're hopefully seeing the comment we just posted window.location.href = lastpage; } })(jQuery); function createDate(days, months, years) { var date = new Date(); date.setFullYear(date.getFullYear() + years); date.setMonth(date.getMonth() + months); date.setDate(date.getDate() + days); return date; }