Fix "new" reddit behaviour: long-lived page, internal links, moderator page
目前為
// ==UserScript==
// @name Reddit - fix "new"
// @namespace https://github.com/Procyon-b
// @version 0.2.3
// @description Fix "new" reddit behaviour: long-lived page, internal links, moderator page
// @author Achernar
// @match https://new.reddit.com/*
// @match https://www.reddit.com/*
// @match https://mod.reddit.com/*
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_deleteValue
// @grant GM.registerMenuCommand
// @run-at document-start
// @noframes
// ==/UserScript==
(function() {
"use strict";
var react, tit={}, options={};
const defOpts={
enable_LLP: true,
timed_LLP: false,
LLP_TO: 1000,
hide_NP: false,
no_tit: false,
lnk_rw_intf: true,
lnk_rw_cmt_www: true,
lnk_rw_cmt_old: false,
stay_new: true,
force_new: false,
mod_modLog_r: true,
mod_modLog_hide: false,
mod_fix_modmail: true,
misc_mark: false,
misc_rst_o: false,
},
dlgT=[
['{','"Long-lived" page:'],
['enable_LLP', 'Trigger long-lived page', 'checkbox'],
['-'],
['{'],
['timed_LLP', 'Delay "new post" closing', 'checkbox'],
['LLP_TO', '"new post" display time (ms)', 'text', 'width: 4em;', ['onchange','this.value=/^\\D*(\\d+).*$/.exec(this.value)[1]'] ],
['}'],
['-'],
['hide_NP', 'Hide "new post" form during the trick (empty screen)', 'checkbox'],
['no_tit', 'Don\'t try to fix incorrect page title', 'checkbox'],
['}'],
['-'],
['{', 'Rewrite links:'],
['', 'Notifications links are already rewritten to "new." by default. You can change the behavior of other links.'],
['-'],
['lnk_rw_intf', 'Rewrite interface links', 'checkbox'],
['lnk_rw_cmt_www', 'Rewrite "www." links in posts & comments', 'checkbox'],
['lnk_rw_cmt_old', 'Rewrite "old." links in posts & comments', 'checkbox'],
['-'],
['stay_new', 'Try to keep navigating on "new."', 'checkbox'],
['force_new', 'Force-redirect all access on "www." to "new."', 'checkbox'],
['}'],
['-'],
['{','Moderation page:'],
['mod_modLog_r', 'Add menu item to old "Mod Log" page', 'checkbox'],
['mod_modLog_hide', 'Hide current "Mod Log" menu item', 'checkbox'],
['mod_fix_modmail', 'Also fix links in Modmail', 'checkbox'],
['}'],
['-'],
['{','This dialog:'],
['misc_mark', 'Mark option difference from current and default value', 'checkbox'],
['misc_rst_o', 'Clicking on the diff mark resets the value', 'checkbox'],
['}'],
['-'],
['{','Infos:'],
['', '<a href="https://procyon-b.github.io/programming/" target="_blank">Homepage</a> – <a href="https://greasyfork.org/scripts/493986" target="_blank">Script page</a>'],
['}'],
];
function getOpts() {
try{
options=GM_getValue('options');
options=Object.assign({}, defOpts, options);
}catch(e){
Object.assign(options, defOpts);
}
}
function saveOpts(options = options) {
var o, k;
for (k in options) {
if (options[k] != defOpts[k]) {
if (!o) o={};
o[k]=options[k];
}
}
if (o) GM_setValue('options', o);
else GM_deleteValue('options');
}
getOpts();
var st=document.createElement('style');
function ins() {
document.removeEventListener('DOMContentLoaded', ins);
window.removeEventListener('load', ins);
document.documentElement.appendChild(st);
document.body.addEventListener('click', function(ev){
if (ev.target.classList.contains('icon-views')) {
setOptions();
}
});
}
if (document.readyState != 'loading') ins();
else {
document.addEventListener('DOMContentLoaded', ins);
window.addEventListener('load', ins);
}
st.textContent=`.icon-views {cursor: pointer;}
body.trick #SHORTCUT_FOCUSABLE_DIV > div:has( #AppRouter-main-content .ListingLayout-outerContainer > ._3ozFtOe6WpJEMUtxDOIvtU > div:first-child:empty + div[style] + div[style*="px"]) ~ div > ._1DK52RbaamLOWw5UPaht_S,
body.trick #AppRouter-main-content .ListingLayout-outerContainer > ._3ozFtOe6WpJEMUtxDOIvtU > div:first-child:empty + div[style] + div[style*="px"] {display: none;}
`;
if (!location.host.startsWith('www.'))
GM.registerMenuCommand('Settings', function(){
setOptions();
});
var dlg=false;
function setOptions() {
if (dlg) return;
dlg=document.createElement('div');
dlg.id='triggerLLP-dialog';
dlg.className="triggerLLP-bg";
dlg.innerHTML=`<style>
#triggerLLP-options {
position: relative;
--margins: 40px;
max-height: calc( 100vh - 2 * var(--margins) );
max-width: 530px;
margin: var(--margins) auto;
background: var( --color-neutral-background, var(--color-tone-8) );
border: 2px solid gray;
box-shadow: var( --boxshadow-modal );
box-sizing: border-box;
}
.triggerLLP-bg {
position: fixed;
z-index: 999998;
width: calc( 100vw + 40px);
height: 100vh;
top: 0;
left: 0;
overflow: auto;
overscroll-behavior: contain;
}
span.tLLP-close {
position: absolute;
right: 5px;
top: 5px;
border-radius: 15px;
width: 30px;
height: 23px;
text-align: center;
padding-top: 7px;
background: var( --color-interactive-background-disabled );
cursor: pointer;
}
span.tLLP-close:hover {
background: var(--color-button-plain-background-hover);
}
span.tLLP-close svg {
pointer-events: none;
}
#triggerLLP-options h2 {
font-size: revert;
background: var( --color-secondary-background-selected );
height: 40px;
min-height: 40px;
padding-left: 2em;
line-height: 1.5em;
}
#triggerLLP-options .content {
padding: 1em;
overflow: auto;
height: 100%;
overscroll-behavior: contain;
}
#triggerLLP-options input {
margin-right: 1em;
}
#triggerLLP-options div {
line-height: 1.5em;
}
#triggerLLP-options .empty {
height: 1em;
}
#triggerLLP-options {
display: flex;
flex-direction: column;
pointer-events: initial;
}
#triggerLLP-options .buttons {
background: var( --color-secondary-background-selected );
padding: 0.5em;
text-align: center;
font-size: small;
}
#triggerLLP-options button,
#triggerLLP-options fieldset,
#triggerLLP-options legend {
margin: revert;
padding: revert;
border: revert;
vertical-align: revert;
}
html.theme-light #triggerLLP-options button {
background: revert;
color: revert;
}
html.theme-light #triggerLLP-options input[type="text"] {
border: revert;
}
#triggerLLP-dialog .cont {
position: fixed;
width: calc( 100vw - 18px );
pointer-events: none;
}
#triggerLLP-dialog.diff input,
#triggerLLP-dialog.diff span.diff {
position: relative;
}
#triggerLLP-dialog.diff input[type="checkbox"]:checked:not([data-checked])::before,
#triggerLLP-dialog.diff input[type="checkbox"][data-checked]:not(:checked)::before,
#triggerLLP-dialog.diff span:first-child.diff::before
{
color: red;
content: "*";
left: -.5em;
position: absolute;
}
#triggerLLP-dialog.diff input[type="checkbox"]:checked:not([data-o_checked])::after,
#triggerLLP-dialog.diff input[type="checkbox"][data-o_checked]:not(:checked)::after,
#triggerLLP-dialog.diff input + span.diff::after
{
color: green;
content: "*";
right: -.5em;
position: absolute;
}
#triggerLLP-dialog.diff input + span.diff::after {
left: -.8em;
right: unset;
}
#triggerLLP-dialog.diff.rst span.diff {
cursor: pointer;
}
#triggerLLP-dialog:not(.rst) span.diff,
#triggerLLP-dialog:not(.rst) input::after,
#triggerLLP-dialog:not(.rst) input::before {
pointer-events: none;
}
#triggerLLP-dialog input:disabled ~ label {
color: var( --color-neutral-content-disabled, var(--color-tone-3) );
}
#triggerLLP-dialog a {
color: var( --newRedditTheme-linkText );
text-decoration: revert;
}
#triggerLLP-dialog a:hover {
color: var( --newRedditTheme-linkTextShaded80 );
}</style>
<div class="cont">
<div id="triggerLLP-options">
<span class="tLLP-close"><svg fill="currentColor" height="16" viewBox="0 0 20 20" width="16">
<path d="m18.442 2.442-.884-.884L10 9.116 2.442 1.558l-.884.884L9.116 10l-7.558 7.558.884.884L10 10.884l7.558 7.558.884-.884L10.884 10l7.558-7.558Z"></path>
</svg></span>
<h2>Fix "New" Reddit - Userscript options</h2>
<diV class="content">
</div>
<div class="buttons"><div><button class="tLLP-save">Save</button> <button class="tLLP-close">Cancel</button> <button class="tLLP-reset">Reset</button> <button class="tLLP-defaults">Defaults</button></div></div>
</div>
</div>
<div style="min-height:101vh;"></div>`;
document.body.appendChild(dlg);
var c=dlg.querySelector('.content');
if (c) fillContent();
function fillContent(opts = options) {
let s='';
for (let v of dlgT) {
let k =v[0];
if (k == '-') s+='<div class="empty"> </div>';
else if (k == '') s+='<div>'+v[1]+'</div>';
else if (k == '{') s+='<fieldset>'+(v[1]?'<legend>'+v[1]+'</legend>':'');
else if (k == '}') s+='</fieldset>';
else {
s+='<div><span></span><input type="'+v[2]+'" name="'+v[0]+'" style="'+(v[3]||'')+'" '+
(typeof opts[k] == 'boolean' ? 'id="'+v[0]+'"'+( opts[k] ? 'checked':'') + (options[k] ? ' data-checked':'') + (defOpts[k] ? ' data-o_checked':'')
:'value="'+opts[k]+'" data-value="'+options[k]+'" data-o_value="'+defOpts[k]+'"')+
(v[4] ? v[4][0]+( v[4][1] ? '="'+v[4][1]+'"' :'') : '')+
'><span></span><label for="'+v[0]+'">'+v[1]+'</label></div>';
}
}
c.innerHTML=s;
dlg.classList.toggle('diff', options.misc_mark);
dlg.classList.toggle('rst', options.misc_rst_o);
dlg.querySelectorAll('input[type="text"]').forEach(txt_diff);
}
dlg.addEventListener('click', hevents);
dlg.addEventListener('change', hevents);
function hevents(ev){
var t=ev.target;
if (t.className == 'tLLP-close') {
dlg.remove();
dlg=undefined;
}
else if (t.className == 'tLLP-save') {
let newOpts=JSON.parse(JSON.stringify(options));
dlg.querySelectorAll('[name]').forEach(function(k){
if (k.name in defOpts) {
if (typeof defOpts[k.name] == 'boolean') newOpts[k.name]=k.checked;
else newOpts[k.name]=k.value;
}
});
saveOpts(newOpts);
getOpts();
dlg.remove();
dlg=undefined;
}
else if (t.className == 'tLLP-reset') {
fillContent();
}
else if (t.className == 'tLLP-defaults') {
fillContent(defOpts);
}
else if ( (t.tagName == 'INPUT') && (t.type == 'text') ) {
txt_diff(t);
}
else if ( (t.tagName == 'INPUT') && (t.name=='misc_mark') ) {
dlg.classList.toggle('diff', t.checked);
}
else if ( (t.tagName == 'INPUT') && (t.name=='misc_rst_o') ) {
dlg.classList.toggle('rst', t.checked);
}
else if ( (t.tagName == 'SPAN') && t.classList.contains('cur') ) {
t.nextElementSibling.value=t.nextElementSibling.dataset.value;
txt_diff(t.nextElementSibling);
}
else if ( (t.tagName == 'SPAN') && t.classList.contains('def') ) {
t.previousElementSibling.value=t.previousElementSibling.dataset.o_value;
txt_diff(t.previousElementSibling);
}
}
function txt_diff(e) {
e.previousElementSibling.classList.toggle('diff', e.value != e.dataset.value);
e.previousElementSibling.classList.toggle('cur', e.value != e.dataset.value);
e.nextElementSibling.classList.toggle('diff', e.value != e.dataset.o_value);
e.nextElementSibling.classList.toggle('def', e.value != e.dataset.o_value);
}
}
// Reddit - fix links
// 0.5.2
var isNew = (location.host == "new.reddit.com");
var onNew = isNew || document.referrer.startsWith('https://new.reddit.com/');
var toNew = onNew && !isNew;
function fixLinks() {
var clicks={}, delay=10000;
Ccl();
setTimeout(Ccl,12000);
if (options.force_new) toNew=true;
if ( /^\/r\/[^/]+\/comments\/$/.test(location.pathname) ) toNew=false;
if (clicks[location.href]) onNew=true;
if (options.mod_fix_modmail && (location.host == "mod.reddit.com") ) onNew=true;
if (toNew) {
if (location.host.startsWith('www.')) {
let L=location.href.replace(/^https:\/\/www\.reddit\./, 'https://new.reddit.');
if (!clicks[L]) {
clicks[L]=Date.now();
Scl();
location.host='new.reddit.com';
}
}
}
if (!onNew) return;
if (document.readyState != 'loading') init();
else {
document.addEventListener('DOMContentLoaded', init);
window.addEventListener('load', init);
}
var done=false;
function init() {
if (done) return;
done=true;
document && document.body && chk();
var obs=new MutationObserver(cb), config = { attributes: false, childList: true, subtree: true};
obs.observe(document.body, config);
}
function chk(r) {
var a=(r||document).getElementsByTagName("a");
if (r && (r.nodeName == 'A')) a=[r, ...a];
for (let i=0,e; e=a[i]; i++) {
if (e._fixed) continue;
if (/^https:\/\/(www\.|mod\.|old\.)?reddit\.com\//.test(e.href)) {
e._fixed=true;
e.classList.add('_fixed');
if (/sticky\?num/.test(e.href)) continue;
if (e.href.startsWith(location.origin+location.pathname+'?')) continue;
e.oriHref=e.href;
let uLnk=e.classList.contains('_3t5uN8xUmg0TOwRCOGQEcU');
if ( (options.lnk_rw_intf && !uLnk) ||
(options.lnk_rw_cmt_www && uLnk) ||
e.classList.contains('_1tpiOc0IxpDU113wUs4zi1') ) e.href=e.href.replace(/^https:\/\/(www\.)?reddit\.com\//,'https://new.reddit.com/');
if (options.lnk_rw_cmt_old && uLnk) e.href=e.href.replace(/^https:\/\/(old\.)?reddit\.com\//,'https://new.reddit.com/');
}
}
}
function cb(mutL) {
for(let mut of mutL) {
if (mut.type == 'childList') {
for (let e,i=0; e=mut.addedNodes[i]; i++) {
if (e.nodeType == 1) chk(e);
}
}
}
}
function Scl() {
if (!Object.keys(clicks).length) localStorage.removeItem("__newL__");
else localStorage.setItem("__newL__", JSON.stringify(clicks) );
}
function Gcl() {
clicks=JSON.parse(localStorage.getItem("__newL__") || '{}');
return clicks;
}
function Ccl() {
Gcl();
var now=Date.now();
for (let k in clicks) {
if ( (clicks[k]+delay) < now ) delete clicks[k];
}
Scl();
}
} // END fixLinks()
fixLinks();
// Reddit - trigger LLP
// 0.5.9b
if (isNew) {
if (document.readyState != 'loading') { document.body.onload=LLP; }
else {
document.addEventListener('DOMContentLoaded', function(){document.body.onload=LLP; });
}
}
function LLP() {
if (location.pathname.endsWith('/submit')) return;
if (location.pathname.endsWith('/settings/')) return;
if (location.pathname.endsWith('/settings')) return;
if (location.pathname.startsWith('/message/')) return;
if (location.search.includes('styling')) return;
var r=document.querySelector('#SHORTCUT_FOCUSABLE_DIV');
// fix no title set in bg win/tab
setBgTitle();
if ( !options.enable_LLP
|| !r
) return;
for (let k in r) {
if (k.startsWith('__reactEventHandlers$')) {
react=k;
break;
}
}
if (!react) return;
// find link without sub-R
var e=document.querySelector('a[href="/submit"]');
if (!e) {
// try to find in drop-down
let c=document.querySelector('header button img + i.icon-caret_down, header button svg + i.icon-caret_down, header button .icon-community + i.icon-caret_down');
if (c) {
c=c.closest('button');
c[react].onMouseDown({target: c});
}
e=document.querySelector('a[href="/submit"]');
}
// use "+" icon
if (!e) e=document.querySelector('header button:not([role="menuitem"]) > .icon-add');
if (e) {
if (options.hide_NP) {
document.body.classList.add('trick');
setTimeout(function(){document.body.classList.remove('trick');}, (options.timed_LLP ? options.LLP_TO || 1000 : 0)*1 + 4000);
}
tit[location.href]=document.title;
e.click();
findField();
}
}
var max=1000;
function findField() {
if (!max) return;
max--;
var b=document.querySelector('textarea[placeholder][maxlength="300"][rows="1"]');
if (!b) { setTimeout(findField, 10); return; }
let tt;
if ( (tt=document.querySelector('html head title')) && !options.no_tit) obsTit.observe(tt, {attributes: false, subtree: false, childList: true });
b.value='e';
b[react].onChange({target:b});
b.value='';
b[react].onChange({target:b});
setTimeout(function(){
history.back();
clickDiscard();
}, options.timed_LLP ? options.LLP_TO || 1000 : 0);
}
var max2=20;
function clickDiscard(){
if (!max2) return;
max2--;
var b=document.querySelector('div[aria-modal="true"] footer button');
if (!b) { setTimeout(clickDiscard, 10); return; }
b.click();
}
function setBgTitle(x) {
if (location.pathname == '/') return;
if (document.visibilityState != 'hidden') return;
try{
let t=document.querySelector('h1')?.textContent;
if (/^\/r\/[^/]+\/wiki\/([^/]+)/.test(location.pathname)) t=null;
if (t && (document.title != t) ) document.title = t;
}catch(e){}
}
var title;
// monitor title to prevent "submit" to appear
function validTit(s) { return !/^(\([0-9]+\) *)?(Submit to |Einreichen bei |Enviar a |Envoyer à |Invia a |Postar no |Enviar para )/.test(s) }
const obsTit=new MutationObserver(function(muts){
if (!title) title=muts[0].addedNodes[0].textContent;
var titleOK, skipT=false;
for (let i=muts.length-1; i >= 0 ; i--) {
titleOK=muts[i].addedNodes[0].textContent;
if (validTit(titleOK)) {
skipT=(i==muts.length-1);
break;
}
else if (validTit(muts[i].removedNodes[0].textContent)) titleOK=muts[i].removedNodes[0].textContent;
}
let t=tit[location.href];
if (!t) {this.disconnect()}
if (!t || !titleOK) return;
if (!skipT) setTimeout(function(){
document.title=titleOK;
},0);
});
var TO;
// Reddit - fix mod log
// 0.2
// if on "new"
if (isNew) {
var hpushState=history.pushState;
history.pushState=function(a,b,u){
if (location.pathname.endsWith('/about/log') && u.includes('?')) {
arguments[2]=u.replace(/\/about\/[^?]*/, '/about/log');
if (!location.search) setTimeout(function(){
history.back();
setTimeout(function(){history.forward()},100);
},0);
}
return hpushState.apply(history, arguments);
}
function viewed(a, c='viewed') {
a.forEach(function(e){
e.classList.add(c);
});
}
// find deepest root
var R;
const obsRoot=new MutationObserver(function(muts){
for (let mut of muts) {
if (mut.addedNodes.length) {
if (R=document.querySelector('#AppRouter-main-content')) {
this.disconnect();
o_pg.observe(R, {attributes: false, subtree: false, childList: true});
fixlocs();
return;
}
}
}
});
function startObs() {obsRoot.observe(document.body, {attributes: false, subtree: true, childList: true});}
if (document.body) startObs();
else {
document.addEventListener('DOMContentLoaded', startObs);
}
function fixlocs(muts){
if (/^\/r\/[^/]+\/about/.test(location.pathname) ) fixMod();
}
const o_pg=new MutationObserver(fixlocs);
var obsmnu;
function fixMod() {
function find(muts){
if (mnu) {
let e=mnu.shadowRoot.querySelector('a[href$="/mod/uBlockOrigin/log"]');
if (e && options.mod_modLog_r) {
obsmnu && obsmnu.disconnect();
var st=document.createElement('style');
mnu.shadowRoot.appendChild(st);
st.textContent=`
.flex.items-center a > :first-child:not(.selected):not(._selected) {
background-color: unset !important;
}
.flex.items-center:has(.selected) ~ .flex.items-center a[href="/r/uBlockOrigin/about/log"] > :first-child,
.flex.items-center:has(~ .flex.items-center .selected) a[href="/r/uBlockOrigin/about/log"] > :first-child {
background: none !important;
}`;
let p=e.closest('div')
let a=p.cloneNode(true);
p.parentNode.insertBefore(a,p);
if (options.mod_modLog_hide) p.style.display='none';
e=a.querySelector('a');
let et=e.querySelector('span.items-center');
if (et) et.innerHTML+=' (old)';
e.href='/r/uBlockOrigin/about/log';
e.onclick=function(ev){
history.pushState({},null, this.href);
e.firstElementChild.classList.add('_selected');
e.firstElementChild.style="background-color: #FF4500;";
history.back();
setTimeout(function(){
history.forward();
setTimeout(function(){ p.parentNode.querySelectorAll('.selected').forEach( (e) => e.classList.remove('selected') ); },1000);
},100);
};
}
}
else {
if (mnu=R.querySelector('mod-nav')) {
obsmnu && obsmnu.disconnect();
obsmnu.observe(mnu.shadowRoot, {attributes: false, subtree: true, childList: true});
return true;
}
}
}
var mnu;
if(obsmnu) obsmnu.disconnect();
obsmnu=new MutationObserver(find);
if (!find(0)) obsmnu.observe(R, {attributes: false, subtree: true, childList: true});
}
} // if (isNew)
})();