Reddit - fix "new"

Fix "new" reddit behaviour: long-lived page, internal links, moderator page

当前为 2024-05-02 提交的版本,查看 最新版本

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Reddit - fix "new"
// @namespace    https://github.com/Procyon-b
// @version      0.2.2
// @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> &ndash; <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> &nbsp; &nbsp; <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">&nbsp;</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)


})();