Greasy Fork is available in English.

ao3 savior

Tumblr savior clone for AO3 works pages.

Pada tanggal 24 April 2016. Lihat %(latest_version_link).

// ==UserScript==
// @name    ao3 savior
// @description Tumblr savior clone for AO3 works pages.
// @namespace   ao3
// @include   http*://archiveofourown.org/*
// @grant     none
// @version   1.6
// ==/UserScript==


/**** CONFIG ********************/
window.ao3SaviorConfig = {
  showReasons: true,
  // set to false if you don't want to see why works were hidden

  showPlaceholders: true,
  // set to false if you don't want to see the "This work is hidden!" boxes (could result in empty works lists. Check out the "saved filters" script)

  authorBlacklist: ['Hated Author 1', 'smeyer'],
  // excludes works with an author that exactly matches at least one term

  titleBlacklist: ['The Catcher in the Rye', 'Sylvester And The Magic Pebble'],
  // excludes works with a title that exactly matches at least one term

  tagBlacklist: ['dobby', 'jar jar binks', '*mimes'],
  // excludes works with a tag that matches at least one term. Use * for wildcard
  
  tagWhitelist: [],
  // if a work contains any of these tags, it will not be blocked, even if it matches one of the blacklists

  summaryBlacklist: ['horse-sized ducks', 'duck-sized horses', "bee's knees"]
  // excludes works with summaries that contain at least one term
};
/********************************/

(function($) {
  var _works = $('li.blurb');
  if (!_works[0]) return;

  var _toggleClass = 'ao3-savior-hide-toggle';
  var _cfg = getConfig();

  _works.each(function() {
    var reason = shouldBlockWork($(this));
    if (reason) {
      blockWork($(this), reason)
    }
  });
  
  function getConfig() {
    var config = window.ao3SaviorConfig || {};
    
    config.showReasons = (config.showReasons !== undefined) ? config.showReasons : true;
    config.showPlaceholders = (config.showPlaceholders !== undefined) ? config.showPlaceholders : true;
    config.authorBlacklist = config.authorBlacklist || [];
    config.titleBlacklist = config.titleBlacklist || [];
    config.tagBlacklist = config.tagBlacklist || [];
    config.tagWhitelist = config.tagWhitelist || [];
    config.summaryBlacklist = config.summaryBlacklist || [];
    
    return config;
  }
  
  function blockWork(work, reason) {
    var cut = $('<div>').addClass('cut').html(work.html()),
      reason = '(' +reason +'.)',
      thisFold = makeFold(),
      reasonContainer = thisFold.find('.reason');

    if (!ao3SaviorConfig.showPlaceholders) {
      work.hide();
      return;
    }

    if (ao3SaviorConfig.showReasons) {
      reasonContainer.html(reason);
    } else {
      reasonContainer.remove();
    }

    work.empty().append(thisFold, cut.hide());
    work.find('a.action').click(function() {
      var fold = $(this).closest('.fold'),
        cut = fold.next('.cut');

      cut.add(fold.children('.'+_toggleClass)).toggle();
      $(this).text(cut.is(':visible') ? 'Hide' : 'Unhide');
    })
    
    function makeFold() {
      return $('<p>').addClass('fold').append(
        $('<span>').addClass(_toggleClass).text('This work is hidden! '),
        $('<span>').addClass(_toggleClass).html('This work <em>was</em> hidden. ').hide(),
        $('<span>').addClass('reason'),
        $('<span>').addClass('actions').append(
          $('<a>').addClass('action').css({
              'position': 'absolute',
              'right': 8,
              'top': 10
            }).text('Unhide')
          )
      );
    }
  }
  
  function shouldBlockWork(work) {
    var authors = work.find('a[rel=author]'),
      title = work.find('.header .heading a:first-child'),
      tags = work.find('a.tag'),
      summary = work.find('blockquote.summary').text(),
      reason = 'Reason: ';

    for (var i = 0, tag; tag = $(tags[i]).text(); i++) {
      for (var j = 0, wlTag; wlTag = _cfg.tagWhitelist[j]; j++) {
        if (tag == wlTag) {
          return '';
        }
      }
      
      for (var j = 0, blTag; blTag = _cfg.tagBlacklist[j]; j++) {
        if (termsMatch(tag, blTag)) {
          return reason +'tags include <strong>' +blTag +'</strong>';
        }
      }
    }

    for (var i = 0, blAuthor; blAuthor = _cfg.authorBlacklist[i]; i++) {
      var done;
      if (blAuthor == 'Anonymous' && !authors[0]) {
        done = true;
      } else {
        for (var j = 0, author; author = $(authors[j]).text(); j++) {
          if (author == blAuthor) {
            done = true;
            break;
          }
        }
      }

      if (done) {
        return reason +'author is <strong>' +blAuthor +'</strong>';
      }
    }
    
    var trimmedTitleText = title.text().replace(/^\s+|\s$/, '');
    for (var i = 0, blTitle; blTitle = _cfg.titleBlacklist[i]; i++) {
      if (trimmedTitleText == blTitle) {
        return reason +'title is <strong>' +blTitle +'</strong>';          
      }
    }

    for (var i = 0, blSummaryTerm; blSummaryTerm = _cfg.summaryBlacklist[i]; i++) {
      if (summary.indexOf(blSummaryTerm) != -1) {
        return reason +'summary includes <strong>' +blSummaryTerm +'</strong>';
      }
    }

    return '';
    
    function termsMatch(testTerm, listTerm) {
      testTerm = testTerm.toLowerCase();
      listTerm = listTerm.toLowerCase();
      if (testTerm == listTerm) { return true; }

      if (listTerm.indexOf('*') == -1) return false;
      
      var parts = listTerm.split('*'),
        prevPartIndex = 0,
        firstPart,
        lastPart;

      for (var i = 0, part, len = parts.length; i < len; i++) {
        part = parts[i];
        partIndex = testTerm.indexOf(part);
        if (part && partIndex < prevPartIndex) {
          return false;
        }
        prevPartIndex = partIndex + part.length;
      }
      
      firstPart = parts[0];
      lastPart = parts[parts.length-1];

      return !(
        firstPart && testTerm.indexOf(firstPart) != 0 ||
        lastPart && testTerm.indexOf(lastPart)+lastPart.length != testTerm.length
      );
    }
  }

})(window.jQuery);