// ==UserScript==
// @name [AO3] Mute Selectively
// @namespace http://tampermonkey.net/
// @version 1.1
// @description Allow more flexible muting without resorting to a site skin
// @license MIT
// @match *://*.archiveofourown.org/*
// @grant GM.getValue
// @grant GM.setValue
// @grant GM.deleteValue
// ==/UserScript==
// Many thanks to a certain other nonnie
const standardSize = {
titleFont: 30,
headerFont: 20,
tabFont: 19,
refreshFont: 15,
actionFont: 14,
menuWidth: 600,
menuHeight: 600
}
const mobileSize = {
titleFont: 24,
headerFont: 18,
tabFont: 14,
refreshFont: 13,
actionFont: 12,
menuWidth: 390,
menuHeight: 500
}
var fontSizing = {}
var commentMute = []
function getMenuRules() {
var rules = {};
rules['.AO3-MS-toggle'] = {
'background-color': 'lightgray',
'padding': '1px 4px',
'display': 'inline-block',
'border-style': 'solid',
'border': 'black',
};
rules['.AO3-MS-toggle a'] = {
'text-decoration': 'none',
'border': 'black',
'color': 'black',
};
rules['.AO3-MS-usermenu'] = {
'position': 'absolute',
'display': 'inline-block',
'background-color': 'lightgray',
'margin': '0px 0px 0px 4px',
'border-style': 'solid',
'border-color': 'black',
'z-index': '100',
};
rules['.AO3-MS-usermenu-item'] = {
'display': 'block',
'position': 'relative',
'padding': '3px 5px 2px 5px',
};
rules['.AO3-MS-usermenu-item-selected'] = {
'background-color': '#f1f1f1',
};
rules['.AO3-MS-usermenu-item a'] = {
'color': 'black',
'text-decoration': 'none',
'display': 'inline-block',
'width': '100%',
'border': 'black',
};
rules['.AO3-MS-usermenu-item:hover'] = {
'background-color': '#ddd',
};
rules['.AO3-MS-muted'] = {
'display': 'none !important',
};
rules['.AO3-MS-collapseBlurb'] = {
'padding': '20px',
}
rules['.AO3-MS-mutePhrase'] = {
'margin': '0px 4px',
}
rules['#AO3-MS-settings'] = {
'background-color': 'white',
'color': 'black',
'display': 'none',
'flex-flow': 'column',
'position': 'fixed',
'top': '50%',
'left': '50%',
'width': fontSizing.menuWidth + 'px',
'height': fontSizing.menuHeight + 'px',
'margin': '-' + (fontSizing.menuHeight / 2 ) + 'px -' + (fontSizing.menuWidth / 2) + 'px',
'box-shadow': '10px 10px 20px rgba(0, 0, 0, 0.5)',
'z-index': '100',
'border': '1px solid black',
'font-size': fontSizing.actionFont + 'px',
};
rules['#AO3-MS-title'] = {
'margin': '10px 0px 0px',
'text-align': 'center',
'font-size': fontSizing.titleFont + 'px',
};
rules['#AO3-MS-closeMenu'] = {
'position': 'absolute',
'top': '0px',
'right': '0px',
'margin': '5px',
'width': (fontSizing.actionFont + 10) + 'px',
'height': (fontSizing.actionFont + 10) + 'px',
'font-size': fontSizing.actionFont + 'px',
};
rules['#AO3-MS-tablist'] = {
'margin': '10px 0px',
'text-align': 'center',
'border-bottom': '1px solid black',
};
rules['.AO3-MS-tab'] = {
'display': 'inline-block',
'padding': '5px',
'height': fontSizing.tabFont + 'px',
'background': 'lightgray',
'color': 'black',
'border': '1px solid black',
'box-size': 'border-box',
};
rules['#AO3-MS-viewport'] = {
'display': 'flex',
'flex-flow': 'column',
'flex': '1 1 auto',
'bottom': '0px',
};
rules['.AO3-MS-view'] = {
'display': 'none',
};
rules['.AO3-MS-active.AO3-MS-view'] = {
'display': 'flex',
'flex-flow': 'column',
'height': '100%',
'bottom': '0px',
};
rules['.AO3-MS-active.AO3-MS-tab'] = {
'background': '#FFF',
};
rules['.AO3-MS-header'] = {
'position': 'inline-block',
'left': '0px',
'right': '0px',
'top': '0px',
'margin': '5px 10px',
'font-size': fontSizing.headerFont + 'px',
'text-align': 'center',
};
rules['.AO3-MS-list'] = {
'flex': '1 1 auto',
'height': '100px',
'overflow-y': 'scroll',
};
rules['.AO3-MS-refresh'] = {
'position': 'inline-block',
'font-size': fontSizing.refreshFont + 'px',
'margin': '0px 10px',
'text-align': 'center',
};
rules['#AO3-MS-checkboxes'] = {
'flex': '1 1 auto',
'height': '100px',
'padding': '10px',
'overflow-y': 'scroll',
};
rules['.AO3-MS-padding'] = {
'display': 'flex',
'flex-flow': 'column',
'flex': '1 1 auto',
'padding': '10px',
};
rules['.AO3-MS-textarea'] = {
'position': 'inline-block',
'left': '0px',
'width': '100%',
'display': 'flex',
'flex-flow': 'column',
'padding': '10px',
'height': '100%',
'resize': 'none',
'box-sizing': 'border-box',
};
rules['#AO3-MS-viewport input[type="button"]'] = {
'display': 'inline-block',
'flex': '0 1 auto',
'bottom': '0px',
'margin': '5px 0px 0px 0px',
'font-size': fontSizing.refreshFont + 'px',
'box-shadow': '2px 1px 2px gray',
};
rules['#AO3-MS-viewport input[type="button"]:active'] = {
'box-shadow': '0 0 0 white',
};
rules['#AO3-MS-css-reset,#AO3-MS-css-save'] = {
'width': '100%',
};
rules['#AO3-MS-import-button'] = {
'width': '100%',
};
rules['.AO3-MS-action'] = {
'font-size': fontSizing.actionFont + 'px',
'margin-left': '5px',
'color': 'blue !important',
};
rules['.AO3-MS-action:before'] = {
'content': '"("',
};
rules['.AO3-MS-action:after'] = {
'content': '")"',
};
rules['.AO3-MS-item'] = {
'padding': '10px',
};
rules['.AO3-MS-item a'] = {
'border': 'black',
};
rules['.AO3-MS-filter:before'] = {
'content': '\'"\'',
};
rules['.AO3-MS-filter:after'] = {
'content': '\'"\'',
};
return rules;
}
function addMuteRules(muteByStyle) {
var rules = {};
rules[muteByStyle] = {
'display': 'none !important',
};
return rules;
}
function makeStyleCode(rules) {
var contents = "";
for (var descriptor in rules) {
if (! rules.hasOwnProperty(descriptor)) continue;
contents += descriptor + " {\n";
for (var property in rules[descriptor]) {
if (! rules[descriptor].hasOwnProperty(property)) continue;
contents += " " + property + ": " + rules[descriptor][property] + ";\n";
}
contents += "}\n";
}
return contents;
}
function makeStyleSheet(str,identifier) {
if (document.querySelectorAll('#' + identifier).length) {
document.querySelector('#' + identifier).remove()
}
var sheet = document.createElement('style');
sheet.innerHTML = str;
sheet.id = identifier
document.body.appendChild(sheet);
}
async function changeReason(identifier,index,reason) {
let temp = await GM.getValue(identifier)
temp[index][3] = reason
await GM.setValue(identifier,temp)
let affectedReasons = document.querySelectorAll('.AO3-MS-collapsed-' + temp[index][0] + ' .AO3-MS-mutePhraseReason')
for (const r of affectedReasons) {
if (!reason) {
r.innerText = ''
} else {
r.innerText = ' (' + reason + ')'
}
}
var refresh = refreshFilterList(identifier)
refresh();
}
function makeBlurbFilterItem(identifier,index,value,refresh) {
var element = document.createElement("div");
element.className = "AO3-MS-item";
var filter = document.createElement("span");
filter.className = "AO3-MS-filter";
var aTitle = document.createElement('a');
aTitle.href = 'https://archiveofourown.org/works/' + value[0].split('work-')[1]
aTitle.appendChild(document.createTextNode(value[1]));
element.appendChild(aTitle)
element.appendChild(document.createTextNode(' by '))
var names = value[2].split(', ')
for (let i = 0; i < names.length; i++) {
let aName = document.createElement('a')
aName.href = 'https://archiveofourown.org/users/' + names[i]
aName.appendChild(document.createTextNode(names[i]))
element.appendChild(aName)
if (i < names.length - 1) {
element.appendChild(document.createTextNode(', '))
}
}
var remove = makeActionLink("unmute", async function() {
await delVal(identifier,value[0])
await refresh();
});
element.appendChild(remove)
var reason = document.createTextNode(' Reason: ' + value[3])
element.appendChild(reason)
var editReason = makeActionLink('edit reason', async function () {
var newReason = prompt("Reason for muting " + value[1] + ":", value[3]);
if (newReason === null) {
return null;
}
await changeReason(identifier,index,newReason)
await refresh();
});
element.appendChild(editReason)
return element;
}
function makeUserFilterItem(identifier, index, value, refresh) {
var element = document.createElement("div");
element.className = "AO3-MS-item";
var filter = document.createElement("span");
filter.className = "AO3-MS-filter";
var id = document.createTextNode('(UserID ' + value[0].split('user-')[1] + ') ')
var aName = document.createElement('a');
aName.href = 'https://archiveofourown.org/users/' + value[2]
aName.appendChild(document.createTextNode(value[2]));
var remove = document.createElement('a')
remove.className = "AO3-MS-action";
remove.href = "javascript:void(0)";
remove.appendChild(document.createTextNode('edit'));
remove.onclick = async function() {
await toggleUser(element,remove,value[0],value[2])
};
var reason = document.createTextNode(' Reason: ' + value[3])
var editReason = makeActionLink('edit reason', async function () {
var newReason = prompt("Reason for muting " + value[2] + ":", value[3]);
if (newReason !== null) {
return null;
}
await changeReason(identifier,index,newReason)
await refresh();
});
element.appendChild(id)
element.appendChild(aName)
element.appendChild(remove)
element.appendChild(reason)
element.appendChild(editReason)
return element;
}
function refreshFilterList(identifier) {
var refresh = async function() {
// Load the array first to prevent visual glitches.
var array = await GM.getValue(identifier,[]),
list = document.querySelector('#AO3-MS-list-' + identifier)
while (list.firstChild !== null) {
list.removeChild(list.firstChild);
}
if (identifier === 'blurblist') {
for (let i = 0; i < array.length; ++i) {
list.appendChild(makeBlurbFilterItem(identifier, i, array[i], refresh));
}
} else if (identifier === 'userlist') {
for (let i = 0; i < array.length; ++i) {
list.appendChild(makeUserFilterItem(identifier, i, array[i], refresh));
}
}
};
return refresh;
}
function makeFilterList(container, identifier, title) {
var header = document.createElement("div");
header.className = "AO3-MS-header";
var list = document.createElement("div");
list.id = "AO3-MS-list-" + identifier;
list.className = "AO3-MS-list";
var refresh = refreshFilterList(identifier)
header.appendChild(document.createTextNode(title));
refresh();
container.appendChild(header);
container.appendChild(list);
return refresh;
}
function makeBlurbTab(viewport) {
// Create the tab showing blocked entries
var mutelist = document.createElement("div");
mutelist.id = "AO3-MS-mutedWorks";
viewport.appendChild(mutelist);
var listRefresh = makeFilterList(mutelist, "blurblist", "Works & Series");
return async function() {
await listRefresh();
};
}
function makeUserTab(viewport) {
// Create the tab showing blocked entries
var mutelist = document.createElement("div");
mutelist.id = "AO3-MS-mutedUsers";
viewport.appendChild(mutelist);
var listRefresh = makeFilterList(mutelist, "userlist", "Users");
return async function() {
await listRefresh();
};
}
async function addCheckbox(container, identifier, caption, hover) {
var item = document.createElement("div");
var checkbox = document.createElement("input");
checkbox.type = "checkbox";
checkbox.id = "AO3-MS-settings-" + identifier;
checkbox.checked = await GM.getValue(identifier, false);
checkbox.onclick = async function() {
await GM.setValue(identifier, checkbox.checked);
};
var label = document.createElement("label");
label.appendChild(document.createTextNode(caption));
label.setAttribute('for', "AO3-MS-settings-" + identifier);
label.setAttribute('title', hover);
item.appendChild(checkbox);
item.appendChild(label);
container.appendChild(item);
var refresh = async function() {
checkbox.checked = await GM.getValue(identifier, false);
};
return refresh;
}
async function makeFilterTab(viewport) {
// Create the tab with the general settings.
var general = document.createElement("div");
general.id = "AO3-MS-general";
viewport.appendChild(general);
var header = document.createElement("div");
header.className = "AO3-MS-header";
header.appendChild(document.createTextNode("Filter Settings"));
general.appendChild(header);
var refresh = document.createElement("div");
refresh.className = "AO3-MS-refresh";
refresh.appendChild(document.createTextNode("Refresh the page to make new extension settings take effect."));
general.appendChild(refresh);
var checkboxes = document.createElement("div");
checkboxes.id = "AO3-MS-checkboxes";
general.appendChild(checkboxes);
var refreshList = [];
refreshList.push(
await addCheckbox(checkboxes, "addReason",
"Ask me for a reason whenever something/someone is muted.",
"When unchecked, it will mute in one click and leave the reason blank.\n" +
"When checked, it will prompt for a reason before the mute goes through."));
refreshList.push(
await addCheckbox(checkboxes, "mute",
"Remove 'Hidden content' messages (aka total mute).",
"When unchecked, the content vanishes without trace for works and series.\n"+
"When checked, it will display a message to indicate a work/series has been hidden.\n"+
"Note that this means you'll only be able to unmute items from the menu.\n"+
"Bookmarks and comments by a muted user will not have hidden content indicators."));
refreshList.push(
await addCheckbox(checkboxes, "showName",
"Show name/title in hidden content message for works and series.",
"If there is a hidden content message, it defaults to a generic 'Hidden content' message.\n"+
"If you check this box, then it will display the name of the user who posted the content.\n"+
"If the specific work/series was muted, it will also display the title."));
refreshList.push(
await addCheckbox(checkboxes, "showReason",
"Show reason for muting in hidden content message for works and series.",
"If there is a hidden content message, it defaults to a generic 'Hidden content' message.\n"+
"If this box is checked, it will display the reason you entered for muting the content."));
refreshList.push(
await addCheckbox(checkboxes, "forceMobile",
"Display in mobile-friendly view even on larger screens.",
"Displays a smaller window with smaller font."));
return async function() {
for (var i = 0; i < refreshList.length; ++i) {
await refreshList[i]();
}
};
}
// Unicode fix from https://developer.mozilla.org/en-US/docs/Web/API/WindowBase64/Base64_encoding_and_decoding#The_.22Unicode_Problem.22
function utf8_to_b64(str) {
return window.btoa(unescape(encodeURIComponent(str)));
}
function b64_to_utf8(str) {
return decodeURIComponent(escape(window.atob(str)));
}
async function getFilterSettings() {
var settings = {};
settings['userlist'] = await GM.getValue('userlist');
settings['blurblist'] = await GM.getValue('blurblist');
var booleanNames = [
'addReason',
'mute',
'showName',
'showReason',
'forceMobile',
];
for (var i = 0; i < booleanNames.length; ++i) {
var name = booleanNames[i];
settings[name] = await GM.getValue(name, false);
}
settings['custom-css'] = await loadCustomCSS();
return settings;
}
async function saveFilterSettings(settings) {
await GM.setValue('userlist',settings['userlist'])
await GM.setValue('blurblist',settings['blurblist'])
var booleanNames = [
'addReason',
'mute',
'showName',
'showReason',
'forceMobile',
];
for (let i = 0; i < booleanNames.length; ++i) {
var name = booleanNames[i];
if (settings.hasOwnProperty(name)) {
GM.setValue(name, settings[name]);
}
}
if (settings.hasOwnProperty('custom-css')) {
GM.setValue('custom-css', settings['custom-css']);
}
}
async function updateExport() {
var exportText = document.getElementById("AO3-MS-export");
var settings = await getFilterSettings();
exportText.value = utf8_to_b64(JSON.stringify(settings));
exportText.select();
exportText.focus();
}
async function doImport() {
var importText = document.getElementById("AO3-MS-import");
try {
var settings = JSON.parse(b64_to_utf8(importText.value));
await saveFilterSettings(settings);
updateExport();
} catch (e) {
alert("Invalid data.");
}
}
function makeImportTab(viewport) {
// Make tab to import settings & filters
var contMain = document.createElement("div");
contMain.id = "AO3-MS-importMain";
viewport.appendChild(contMain);
var header = document.createElement("div");
header.className = "AO3-MS-header";
header.appendChild(document.createTextNode("Import settings & filters"));
contMain.appendChild(header);
var refresh = document.createElement("div");
refresh.className = "AO3-MS-refresh";
refresh.appendChild(document.createTextNode("Refresh the page to make new extension settings take effect."));
contMain.appendChild(refresh);
var padder = document.createElement("div")
padder.className = "AO3-MS-padding"
contMain.appendChild(padder)
var importText = document.createElement("textarea");
importText.id = "AO3-MS-import";
importText.className = "AO3-MS-textarea";
padder.appendChild(importText);
var importButton = document.createElement("input");
importButton.id = "AO3-MS-import-button";
importButton.type = "button";
importButton.value = "Import";
importButton.onclick = doImport;
padder.appendChild(importButton);
}
function makeExportTab(viewport) {
// Create the tab with save and export boxes.
var contMain = document.createElement("div");
contMain.id = "AO3-MS-exportMain";
viewport.appendChild(contMain);
var exportHeader = document.createElement("div");
exportHeader.className = "AO3-MS-header";
exportHeader.appendChild(document.createTextNode("Export settings & filters"));
contMain.appendChild(exportHeader);
var padder = document.createElement("div")
padder.className = "AO3-MS-padding"
contMain.appendChild(padder)
var exportText = document.createElement("textarea");
exportText.id = "AO3-MS-export";
exportText.className = "AO3-MS-textarea";
exportText.readOnly = true;
padder.appendChild(exportText);
return updateExport;
}
function getDefaultRules() {
var rules = {};
var blockedBackground = 'none';
var blockedColor = 'none';
rules['.AO3-MS-collapseBlurb'] = {
'background-color': blockedBackground,
'color': blockedColor,
'padding': '20px',
}
return rules;
}
async function loadCustomCSS() {
var result = await GM.getValue('custom-css', null);
if (result === null) {
result = makeStyleCode(getDefaultRules());
}
return result;
}
async function updateCSS() {
var cssText = document.getElementById("AO3-MS-css");
cssText.value = await loadCustomCSS();
}
async function saveCSS() {
var cssText = document.getElementById("AO3-MS-css");
await GM.setValue('custom-css', cssText.value);
}
async function resetCSS() {
await GM.deleteValue('custom-css');
await updateCSS();
}
function makeColorsTab(viewport) {
// Create the tab with the custom CSS editor.
var colors = document.createElement("div");
colors.id = "AO3-MS-colors";
viewport.appendChild(colors);
var cssHeader = document.createElement("div");
cssHeader.className = "AO3-MS-header";
cssHeader.appendChild(document.createTextNode("Custom styling"));
colors.appendChild(cssHeader);
var refresh = document.createElement("div");
refresh.className = "AO3-MS-refresh";
refresh.appendChild(document.createTextNode("Refresh the page to make new extension settings take effect."));
colors.appendChild(refresh);
var padder = document.createElement("div")
padder.className = "AO3-MS-padding"
colors.appendChild(padder)
var css = document.createElement("textarea");
css.id = "AO3-MS-css";
css.className = "AO3-MS-textarea";
padder.appendChild(css);
var cssSave = document.createElement("input");
cssSave.id = "AO3-MS-css-save";
cssSave.className = "AO3-MS-css-button";
cssSave.type = "button";
cssSave.value = "Save";
cssSave.onclick = async function () {
await GM.setValue('custom-css',css.value)
};
padder.appendChild(cssSave);
var cssReset = document.createElement("input");
cssReset.id = "AO3-MS-css-reset";
cssReset.className = "AO3-MS-css-button";
cssReset.type = "button";
cssReset.value = "Reset to Default";
cssReset.onclick = resetCSS;
padder.appendChild(cssReset);
css.onchange = saveCSS;
return updateCSS;
}
function toggleSettings() {
var settings = document.getElementById("AO3-MS-settings");
if (settings === null) return;
if (settings.style.display == "flex") {
settings.style.top = "-50%";
settings.style.display = "none";
} else {
settings.style.display = "flex";
settings.style.top = "50%";
}
}
function addTab(container, name, view, active, action) {
var classView = "AO3-MS-view";
var classActive = "AO3-MS-active";
var classTab = "AO3-MS-tab";
var link = document.createElement("a");
link.className = classTab;
link.href = "javascript:void(0)";
var text = document.createTextNode(name);
link.appendChild(text);
container.appendChild(link);
var viewElement = document.getElementById(view);
viewElement.className = classView;
link.onclick = function(e) {
var views = document.getElementsByClassName(classView);
for (let i = 0; i < views.length; ++i) {
views[i].className = classView;
}
var tabs = document.getElementsByClassName(classTab);
for (let i = 0; i < tabs.length; ++i) {
tabs[i].className = classTab;
}
viewElement.className += " " + classActive;
link.className += " " + classActive;
if (action !== undefined && action !== null) {
action();
}
};
if (active) {
viewElement.className += " " + classActive;
link.className += " " + classActive;
}
}
function makeActionLink(text, action) {
var link = document.createElement("a");
link.href = "javascript:void(0)";
link.className = "AO3-MS-action";
link.onclick = action;
link.appendChild(document.createTextNode(text));
return link;
}
async function makeSettings() {
// Create the settings window.
var settings = document.createElement("div");
settings.id = "AO3-MS-settings";
var header = document.createElement("div");
header.id = "AO3-MS-title";
header.appendChild(document.createTextNode("Selective Muting"));
settings.appendChild(header);
var closeButton = document.createElement("input");
closeButton.id = "AO3-MS-closeMenu";
closeButton.type = "button";
closeButton.value = "X";
closeButton.onclick = function () {
toggleSettings()
};
header.appendChild(closeButton);
var tabs = document.createElement("div");
tabs.id = "AO3-MS-tablist";
settings.appendChild(tabs);
var viewport = document.createElement("div");
viewport.id = "AO3-MS-viewport";
settings.appendChild(viewport);
document.body.appendChild(settings);
// Create the tabs and add them.
var blurblistRefresh = await makeBlurbTab(viewport);
var userlistRefresh = await makeUserTab(viewport);
var generalRefresh = await makeFilterTab(viewport);
var colorsRefresh = await makeColorsTab(viewport);
var importRefresh = await makeImportTab(viewport);
var exportRefresh = await makeExportTab(viewport);
addTab(tabs, "Works/Series", "AO3-MS-mutedWorks", true, blurblistRefresh);
addTab(tabs, "Users", "AO3-MS-mutedUsers", false, userlistRefresh);
addTab(tabs, "Settings", "AO3-MS-general", false, generalRefresh);
addTab(tabs, "CSS", "AO3-MS-colors", false, colorsRefresh);
addTab(tabs, "Import", "AO3-MS-importMain", false, importRefresh);
addTab(tabs, "Export", "AO3-MS-exportMain", false, exportRefresh);
}
async function makeCollapseBlurb(blurb,blurbInfo) {
var container = document.createElement('li')
container.classList.add('AO3-MS-collapseBlurb')
container.classList.add('blurb')
container.classList.add('AO3-MS-collapsed-' + blurbInfo[0])
container.classList.add(blurb.id)
blurb.parentElement.insertBefore(container,blurb)
var mutePhrase = document.createElement('span')
mutePhrase.classList.add('AO3-MS-mutePhrase')
mutePhrase.innerText = 'Hidden content'
var reason = document.createElement('span')
reason.classList.add('AO3-MS-mutePhraseReason')
if (blurbInfo[0].split('work').length > 1 || blurbInfo[0].split('series').length > 1) {
makeTogglebox(blurb,'m',container)
if (await GM.getValue('showName')) {
mutePhrase.innerText += ': ' + blurbInfo[1] + ' by ' + blurbInfo[2]
}
if (await GM.getValue('showReason') && blurbInfo[3]) {
reason.innerText += ' (' + blurbInfo[3] + ')'
}
mutePhrase.appendChild(reason)
container.appendChild(mutePhrase)
} else if (!blurb.classList.contains('bookmark')) {
if (await GM.getValue('showName')) {
mutePhrase.innerText += ' by ' + blurbInfo[2]
}
if (await GM.getValue('showReason') && blurbInfo[3]) {
reason.innerText += ' (' + blurbInfo[3] + ')'
}
mutePhrase.appendChild(reason)
container.appendChild(mutePhrase)
makeTogglebox(blurb,'X',container)
}
}
function getBlurbs(arrayName,value) {
let blurbuser = arrayName.split('list')[0],
toHide = ''
if (blurbuser === 'blurb') {
toHide = '.' + value[0]
} else if (blurbuser === 'user') {
value[1].split(',').forEach(w => {
toHide += ',' + w + '.' + value[0]
})
toHide = toHide.substring(1)
}
return document.querySelectorAll(toHide)
}
async function addVal(arrayName,value) {
// Add the entry to the saved array
let temp = await GM.getValue(arrayName,[])
for (let i = 0; i < temp.length; ++i) {
if (temp[i][0] === value[0]) {
return null;
}
}
var reason = ''
if (await GM.getValue('addReason',false)) {
let muteObj = value[1]
if (arrayName === 'userlist') {
muteObj = value[2]
}
reason = prompt("Reason for muting " + muteObj + ":", '');
if (reason === null) {
return '';
}
}
value.push(reason)
temp.push(value);
await GM.setValue(arrayName,temp)
// Collapse/mute all works on the page that meet the criteria
let mute = await GM.getValue('mute', false)
let blurbs = getBlurbs(arrayName,value)
for (let blurb of blurbs) {
if (!mute && !document.querySelectorAll('.' + blurb.id).length &&
(arrayName === 'blurblist' || blurb.classList.contains('work') || blurb.classList.contains('series')) ) {
await makeCollapseBlurb(blurb,value)
}
blurb.classList.add('AO3-MS-muted')
}
var refresh = refreshFilterList(arrayName)
refresh()
return true
}
async function delVal(arrayName,key) {
// Remove the entry from the saved array
let temp = await GM.getValue(arrayName,false)
let value = [];
let i = 0;
while (i < temp.length) {
if (temp[i][0] === key) {
value = temp[i];
temp.splice(i, 1);
} else {
++i;
}
}
await GM.setValue(arrayName,temp)
// Uncollapse/unmute all works on the page that meet the criteria
let mute = await GM.getValue('mute',false)
let blurbs = getBlurbs(arrayName,value)
if (!mute) {
for (let c of document.querySelectorAll('.AO3-MS-collapsed-' + key)) {
c.remove();
}
}
for (let blurb of blurbs) {
blurb.classList.remove('AO3-MS-muted')
}
if(arrayName === 'userlist') {
let delMe = []
for (let i = 0; i < commentMute.length; i++) {
if (commentMute[i].includes(value[0])) {
let delMe = commentMute.splice(i,1)
delMe ? makeStyleSheet(makeStyleCode(addMuteRules(commentMute.join(','))),'AO3-MS-muteByStyle') : null
}
}
setInitialMute('blurblist')
}
var refresh = refreshFilterList(arrayName)
refresh()
}
async function getVal(arrayName,key) {
let temp = [];
if (await GM.getValue(arrayName)) {
temp = await GM.getValue(arrayName)
} else {
return null;
}
for (let i = 0; i < temp.length; ++i) {
if (temp[i][0] === key) {
return temp[i]
}
}
return null;
}
function getBlurbUserID(blurb) {
for (let e of blurb.classList) {
if (e.includes('user-')) {
return e;
}
}
return null;
}
async function toggleBlurb(blurb,toggleboxA) {
var blurbInfo = [];
if (blurb) {
for (let e of blurb.classList) {
if (e.includes('work-') || e.includes('series-')) {
blurbInfo.push(e)
}
}
// record title
blurbInfo.push(blurb.querySelectorAll('.heading a')[1].textContent)
// record creators
if (!blurb.querySelectorAll('h4.heading [rel="author"]').length) {
blurbInfo.push('(Anonymous creator)')
} else {
let authorNames = ''
blurb.querySelectorAll('h4.heading [rel="author"]').forEach (a => {
authorNames += ', ' + a.innerText
})
authorNames = authorNames.substring(2)
blurbInfo.push(authorNames)
}
} else if (document.querySelectorAll('h2.title.heading').length) {
blurbInfo.push(getIdFromWorkPage())
blurbInfo.push(document.querySelector('h2.title.heading').textContent.substring(1).trim())
blurbInfo.push(document.querySelector('a[rel="author"]').innerText)
} else if (document.querySelectorAll('.series-show h2.heading').length) {
blurbInfo.push(getIdFromSeriesPage())
blurbInfo.push(document.querySelector('.series-show h2.heading').textContent.substring(1).trim())
blurbInfo.push(document.querySelector('a[rel="author"]').innerText)
}
if(toggleboxA.textContent == 'M') {
await addVal('blurblist',blurbInfo) && !blurb ? toggleboxA.textContent = 'm' : ''
// The M->m change only actually matters on title pages; on blurblists M vs m are actually in two different blurbs
} else {
await delVal('blurblist',blurbInfo[0])
!blurb ? toggleboxA.textContent = 'M' : ''
}
}
async function toggleUser(blurb,togglebox,userID,username) {
if (togglebox.parentNode.querySelectorAll('.AO3-MS-usermenu').length){
togglebox.parentNode.querySelector('.AO3-MS-usermenu').remove()
return null;
}
var usermenu = document.createElement('div');
usermenu.id = 'AO3-MS-usermenu-' + userID;
usermenu.classList.add('AO3-MS-usermenu')
if(!blurb) {
// Prevent the menu from being enormous on profile pages
usermenu.style.fontSize = fontSizing.refreshFont + 'px'
usermenu.style.display = 'block'
}
let items = [['.work','Works'],
['.work,.series','Works & Series'],
['.comment','Comments'],
['','Mute all'],
['none','Unmute']]
async function createItem(item) {
var container = document.createElement('span')
container.classList.add('AO3-MS-usermenu-item')
usermenu.appendChild(container)
// check what the current option is, and highlight it
let currEntry = await getVal('userlist',userID)
if (currEntry) {
if (currEntry[1] === item[0]) {
container.classList.add('AO3-MS-usermenu-item-selected')
}
} else if (item[0] === 'none') {
container.classList.add('AO3-MS-usermenu-item-selected')
}
var a = document.createElement('a')
a.href = "javascript:void(0)";
a.appendChild(document.createTextNode(item[1]));
container.appendChild(a)
a.onclick = async function() {
let currEntry = await getVal('userlist',userID)
if (currEntry) {
if (item[0] != currEntry[1]) {
await delVal('userlist',userID)
}
}
if (item[0] != 'none') {
let newEntry = [userID,item[0],username];
await addVal('userlist',newEntry);
}
// remove all menues on the page because things have probably changed
let rl = document.querySelectorAll('.AO3-MS-usermenu')
for (let ri of rl) {
ri.remove()
}
}
item.push(container)
}
for (let i = 0; i < items.length; i++) {
await createItem(items[i])
}
togglebox.parentElement.appendChild(usermenu)
// console.log ('checking right: ' + togglebox.getBoundingClientRect().right)
// console.log ('checking parent right: ' + togglebox.parentElement.getBoundingClientRect().right)
// Fix if it's likely to get squished -- not great, but eh
if (blurb && (await GM.getValue('forceMobile',false) || window.innerWidth < 900)) {
usermenu.style.display = 'block'
}
}
function makeTogglebox(blurb, tType, pin) {
var togglebox = document.createElement('div');
var toggleboxA = document.createElement('a');
togglebox.classList.add('AO3-MS-toggle');
togglebox.appendChild(toggleboxA);
toggleboxA.href = "javascript:void(0)";
toggleboxA.appendChild(document.createTextNode(tType));
if (blurb) {
if (tType == 'X') {
toggleboxA.onclick = async function() {
let username = blurb.querySelector('.heading a[rel="author"]').textContent
await toggleUser(blurb,togglebox,getBlurbUserID(blurb),username)
};
} else {
toggleboxA.onclick = async function() {
await toggleBlurb(blurb,toggleboxA)
};
}
} else {
if (tType == 'X') {
toggleboxA.onclick = async function() {
let username = document.querySelector('.profile-show h2.heading').innerText.split(' ')[0]
await toggleUser(false,togglebox,getIdFromProfilePage(),username)
}
} else {
toggleboxA.onclick = async function() {
await toggleBlurb(false,toggleboxA)
};
}
}
if (tType == 'X') {
pin.append(togglebox)
} else {
pin.prepend(togglebox)
}
}
function addMuteButtonToBlurbs() {
const blurbs = document.querySelectorAll('.blurb.work,.blurb.series,.blurb.bookmark');
for (let blurb of blurbs) {
let blurbTitle = blurb.querySelector('.heading')
makeTogglebox(blurb,'M',blurbTitle)
if(blurb.querySelectorAll('h4.heading a').length == 3 && !blurb.classList.contains('bookmark')) {
makeTogglebox(blurb,'X',blurbTitle)
}
}
}
async function setInitialMute(identifier) {
let entries = await GM.getValue(identifier,[]);
if (!entries) {
return null;
}
let mute = await GM.getValue('mute',false),
blurbs = ''
for (const entry of entries) {
if (identifier === 'userlist') {
let typeToHide = ''
if (entry[1].includes('work')) {
entry[1].split(',').forEach(w => {
typeToHide += ',' + w + '.' + entry[0]
})
} else {
typeToHide = ',.work.' + entry[0] + ',.series.' + entry[0]
}
typeToHide = typeToHide.substring(1)
typeToHide ? blurbs = document.querySelectorAll(typeToHide) : null
} else {
blurbs = document.querySelectorAll('.' + entry[0])
}
for (let blurb of blurbs) {
if (!blurb.classList.contains('AO3-MS-muted')) {
if (!mute && !document.querySelectorAll('.' + blurb.id).length) {
await makeCollapseBlurb(blurb,entry)
}
blurb.classList.add('AO3-MS-muted')
}
}
}
}
function getIdFromWorkPage() {
if (document.querySelectorAll('li.download a').length > 1) {
if (isNaN(document.querySelectorAll('li.download a')[1].href.split('/')[4])) {
return null;
} else {
return 'work-' + document.querySelectorAll('li.download a')[1].href.split('/')[4]
}
}
return null;
}
function getIdFromSeriesPage() {
let parts = window.location.pathname.split('/')
for (let i = 0; i < parts.length - 1; i++) {
if (parts[i] === 'series' && !isNaN(parts[i+1])) {
return 'series-' + parts[i+1]
}
}
return null
}
function getIdFromProfilePage() {
if(document.querySelectorAll('.meta').length) {
let parts = document.querySelector('.meta').innerText.split('\n')
for (let i = 0; i < parts.length - 1; i++) {
if (parts[i] === 'My user ID is:' && !isNaN(parts[i+1])) {
return 'user-' + parts[i+1]
}
}
return null
}
}
async function setTitleMute() {
let blurbId = ''
async function determineMm(blurbId) {
if (await getVal('blurblist',blurbId)) {
return 'm'
}
return 'M'
}
if (document.querySelectorAll('h2.title.heading').length) { // work or chapter page
blurbId = getIdFromWorkPage()
blurbId ? makeTogglebox(false,await determineMm(blurbId),document.querySelector('h2.title.heading')) : null
} else if (document.querySelectorAll('.series-show h2.heading').length) { // series page
blurbId = getIdFromSeriesPage()
blurbId ? makeTogglebox(false,await determineMm(blurbId),document.querySelector('.series-show h2.heading')) : null
} else if (document.querySelectorAll('.profile-show h2.heading').length) { // profile page
blurbId = getIdFromProfilePage()
blurbId ? makeTogglebox(false,'X',document.querySelector('.profile-show h2.heading')) : null
}
}
function addToNavBar() {
let searchNode = document.querySelector('li.search'),
li = document.createElement('li'),
a = document.createElement('a')
a.href = 'javascript:void(0)'
a.appendChild(document.createTextNode('Muting'))
a.onclick = function() {
toggleSettings();
};
li.classList.add('dropdown')
li.appendChild(a)
searchNode.parentNode.insertBefore(li,searchNode)
}
(async function() {
'use strict';
var filterSettings = await getFilterSettings();
var useMobile = (window.innerWidth < 900 ? true : false)
fontSizing = standardSize
if (useMobile || await GM.getValue('forceMobile',false)) {fontSizing = mobileSize}
fontSizing.menuWidth > (window.innerWidth - 10) ? fontSizing.menuWidth = window.innerWidth -10 : ''
fontSizing.menuHeight > (window.innerHeight -10) ? fontSizing.menuHeight = window.innerHeight -10 : ''
addToNavBar();
makeSettings();
var userlist = await GM.getValue('userlist',[])
for (const u of userlist) {
if (u[1] === '.comment') {
commentMute.push('.comment.' + u[0])
} else if (!u[1]) {
commentMute.push('.' + u[0] + ':not(.work,.series)')
}
}
var muteByStyle = commentMute.join(',')
makeStyleSheet(makeStyleCode(getMenuRules()),'AO3-MS-mainStyle');
makeStyleSheet(filterSettings['custom-css'],'AO3-MS-custom-css');
makeStyleSheet(makeStyleCode(addMuteRules(commentMute.join(','))),'AO3-MS-muteByStyle')
await setInitialMute('userlist');
await setInitialMute('blurblist');
await setTitleMute();
addMuteButtonToBlurbs();
})();