colorDate

"Set date color according to date 根据网页上日期的新旧程度, 给日期进行着色, 比如说已经是5年前的一个日期会成为红色, 以便提醒阅览者,注意信息可能过于陈旧。"

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name     colorDate
// @namespace https://github.com/mika-cn/user-scripts
// @description "Set date color according to date 根据网页上日期的新旧程度, 给日期进行着色, 比如说已经是5年前的一个日期会成为红色, 以便提醒阅览者,注意信息可能过于陈旧。"
// @version  1.3.0
// @grant    none
// @include *
// @author   mika
// ==/UserScript==
/**
 *
 * # CHANGE LOG
 * 2018-06-18 [修复] 对于隐藏的文本,不再进行着色
 * 2018-04-17 支持<01.01> <2007.09> 格式
 *            修正标签正则
 * 2018-04-16 支持<01 Feb 2017> 格式
 *            添加 noscript 等特殊标签到黑名单
 *            兼容类似<textarea>$HTML</textarea>这种s13用法
 * 2018-04-14 支持<Feb 01 '17> 和 <2018年> 格式
 * 2018-04-13 修改替换方式,可应对多个正则存在包含关系的情况
 *
 */

(function(){
  'use strict';

  /*
   * 时长阀值与色值
   */
  var day  = 24 * 60 * 60 * 1000;
  var year = 365 * day;
  var colors = [
    {color: "#ff0000", threshold: 5 * year},
    {color: "#ff5f00", threshold: 3 * year},
    {color: "#d700ff", threshold: 1 * year},
    {color: "#8700ff", threshold: 183 * day},
    {color: "#00af00", threshold: 7 * day},
    {color: "#00d700", threshold: 1 * day},
  ];

  var monthPart = (`
    Jan|January|
    Feb|February|
    Mar|March|
    Apr|April|
    May|
    Jun|June|
    Jul|July|
    Aug|Augest|
    Sep|Sept|September|
    Oct|October|
    Nov|November|
    Dec|December
  `).replace(/\n\s*/gm, '');

  /*
   * 判断规则(信息越详细,放在前面)
   */
  var rules = [
    // yyyy-mm-dd
    {key: "01", regExp: /\d{4}-[01]?\d-[0-3]?\d/mg},
    // yyyy/mm/dd
    {key: "01", regExp: /\d{4}\/[01]?\d\/[0-3]?\d/mg},
    // yyyy.mm.dd
    {key: "01", regExp: /\d{4}\.[01]?\d\.[0-3]?\d/mg},
    // month dd, yyyy
    {key: "01", regExp: new RegExp("(?:"+ monthPart +") [0-3]?\\d[,\\s]{1}\\s?\\d{4}", 'igm')},
    // dd month yyyy
    {key: "01", regExp: new RegExp("[0-3]?\\d (?:"+ monthPart +")\\s?\\d{4}", 'igm')},
    // month dd 'yy
    {key: "09", regExp: new RegExp("(?:"+ monthPart +") [0-3]?\\d\\s'\\d{2}", 'igm')},
    // yyyy年mm月dd日
    {key: "01", regExp: /\d{4}\s?年\s?[01]?\d\s?月\s?[0-3]?\d\s?日/mg},
    // yyyy-mm | yyyy/mm | yyyy.mm
    {key: "02", regExp: /\d{4}[-\/\.]{1}[01]{1}\d/mg},
    // yyyy年mm月
    {key: "02", regExp: /\d{4}\s?年\s?[01]?\d\s?月/mg},
    // mm-dd | mm/dd | mm.dd
    {key: "03", regExp: /[01]{1}\d[-\/\.][0-3]{1}\d/mg},
    // mm月dd日
    {key: "03", regExp: /[01]?\d\s?月\s?[0-3]?\d\s?日/mg},
    // N 天前
    {key: "04", regExp: /\d+\s?天前/mg},
    // N days ago
    {key: "04", regExp: /\d+\s?days?\sago/mg},
    // N 月前
    {key: "05", regExp: /\d+\s?月前/mg},
    // N months ago
    {key: "05", regExp: /\d+\s?months?\sago/mg},
    // N 年前
    {key: "07", regExp: /\d+\s?年前/mg},
    // yyyy年
    {key: "10", regExp: /\d{4}\s?年/mg},
    // N years ago
    {key: "07", regExp: /\d+\s?years?\sago/mg},
    // month yyyy
    {key: "08", regExp: new RegExp("(?:"+ monthPart +")['\\s]{1}\\d{4}", 'igm')},
    // month dd
    {key: "06", regExp: new RegExp("(?:"+ monthPart +")['\\s]{1}[0-3]?\\d", 'igm')},
  ];



  // 主函数
  function colorDate(){
    var nodes = getMatchNode();
    nodes.forEach(function(node){
      var newNode = document.createElement("datetext");
      newNode.innerHTML = applyRules(node.nodeValue, rules)
      // replace child node
      node.parentNode.replaceChild(newNode, node);
    });
  }


  /*
   * 根据规则(判断是否是日期),获取满足的节点
   */
  function getMatchNode(){
    var nodes = [];
    var currentNode;
    var nodeIterator = getTextNodeIterator();
    while (currentNode = nodeIterator.nextNode()) {
      var v = currentNode.nodeValue;
      var rule = rules.some(function(rule){
        return !!v.match(rule.regExp);
      });
      if(rule){
        nodes.push(currentNode);
      }
    }
    return nodes;
  }


  /*
   * 获取文本节点迭代器
   */
  function getTextNodeIterator(){
    return document.createNodeIterator(
      document.body,
      NodeFilter.SHOW_TEXT, function(node) {
        var blackList = ['style', 'script', 'noscript', 'noframes', 'canvas', 'template', 'datetext', 'datespan'];
        var parentNode = node.parentNode;
        if(blackList.indexOf(parentNode.nodeName.toLowerCase()) > -1){
          return NodeFilter.FILTER_REJECT;
        }

        /*
         * 隐藏了的文本,不处理
         * - 有的程序员会使用隐藏文本来存储数据(比如存储 html 或者 链接)
         */
        var style = window.getComputedStyle(parentNode);
        if(style.display === "none"){
          return NodeFilter.FILTER_REJECT;
        }
        if(style.visibility === "hidden"){
          return NodeFilter.FILTER_REJECT;
        }
        return NodeFilter.FILTER_ACCEPT;
      }
   );
  }




  /*
   * 根据不同的正则,返回处理函数
   */
  function getHandler(key){
    switch(key){
      case "01" : return handler_01();
      case "02" : return handler_02();
      case "03" : return handler_03();
      case "04" : return handler_04();
      case "05" : return handler_05();
      case "06" : return handler_06();
      case "07" : return handler_07();
      case "08" : return handler_08();
      case "09" : return handler_09();
      case "10" : return handler_10();
      default: return function(match){ return match;};
    }
  }

  function handler_01(){
    return function(match){
      var dateStr = match.replace(/年|月|\./g, '-').replace("日", '');
      return replace(match, dateStr);
    };
  }

  function handler_09(){
    return function(match){
      var parts = match.split("'");
      var year  = parts.pop();
      var curr = new Date().getFullYear();
      var yStr = '' + Math.floor(curr%100 > parseInt(year) ? curr/100 : curr/100 - 1).toString() + year;
      parts.push(yStr);
      return replace(match, parts.join(" "));
    }
  }

  function handler_02(){
    return function(match){
      var dateStr = match.replace(/年|\/|\./g, '-').replace("月", '') + "-01";
      return replace(match, dateStr);
    };
  }

  function handler_03(){
    return function(match){
      var dateStr = (new Date()).getFullYear().toString() + "-" + match.trim().replace(/月|\/|\./g, '-').replace("日", '');
      return replace(match, dateStr);
    };
  }

  function handler_04(){
    return function(match){
      var n = parseInt(match.match(/\d+/)[0]);
      return replace(match, (new Date() - n * day));
    };
  }

  function handler_05(){
    return function(match){
      var n = parseInt(match.match(/\d+/)[0]);
      return replace(match, (new Date() - n * 30 * day));
    };
  }

  function handler_06(){
    return function(match){
      return replace(match, match.replace(/'/, ' ') + " " + (new Date()).getFullYear().toString());
    };
  }


  function handler_07(){
    return function(match){
      var n = parseInt(match.match(/\d+/)[0]);
      return replace(match, (new Date() - n * 365 * day));
    };
  }

  function handler_08(){
    return function(match){
      var v = match.replace(/'/, ' ').split(' ').join(" 01 ");
      return replace(match, v);
    };
  }

  function handler_10(){
    return function(match){
      var year = parseInt(match.match(/\d+/)[0]).toString();
      return replace(match, year + "-01-01");
    }
  }

  /*
   * 处理函数, 根据时长阀值着色
   */
  function replace(match, dateStr){
    try{
      var result = match;
      var diff = Date.now() - new Date(dateStr);
      colors.some(function(item){
        if(diff >= item.threshold){
          result = "<datespan style='color:"+ item.color +";'>" + match +"</datespan>";
          return true;
        }
        return false;
      });
      return result;
    }catch(e){
      return match;
    }
  }

  function applyRules(text, rules){
    var r = matchRules(text, rules);
    return replaceMark(r.markedText, r.values);
  }

  /*
   * 根据rules,进行匹配,标记匹配, 并计算出替换值
   * 用来避免两个正则是包含关系的情况
   */
  function matchRules(text, rules){
    var idxUtil = makeIdxUtil(-1);
    var values = [];
    rules.forEach(function(rule){
      var handler = getHandler(rule.key);
      text = text.replace(rule.regExp, function(match){
        values.push(handler(match));
        var mark = "@[["+ idxUtil.next() +"]]";
        return mark
      });
    });
    return {
      markedText: text,
      values: values
    }
  }

  // 索引工具
  function makeIdxUtil(init){
    return {
      curr: init,
      next: function(){ this.curr++; return this.curr; },
    }
  }


  // 把标记替换为着色后的人日期
  function replaceMark(text, values){
    return text.replace(/@\[\[\d+\]\]/mg, function(mark){
      var idx = parseInt(mark.match(/\d+/)[0]);
      return values[idx]
    });
  }


  /*
   * 创建延迟调用对象
   * 用来防止短时间内被调用多次
   */
  var createDelayCall = function(fn, delay){
    var dc = {};
    dc.action = fn;
    dc.clearTimeout = function(){
      if(dc.timeoutId){
        clearTimeout(dc.timeoutId);
      }
    };
    dc.run = function(){
      dc.clearTimeout();
      dc.timeoutId = setTimeout(function(){
        dc.action();
        dc.clearTimeout();
      }, delay);
    };
    return dc;
  };

  /*
   * 判断变更是否来自脚本
   */
  function isColorDateMotation(mutationRecords){
    return mutationRecords.every(function(record){
      return record.type === "childList" && record.addedNodes.length > 0 && record.addedNodes[0].nodeName.toLowerCase() === "datetext";
    });
  }


  /*
   * 初始化 mutationObserver
   */
  function initMutationObserver(){
    var delayColorDate = createDelayCall(colorDate, 400);
    var observer = new MutationObserver(function(mutationRecords){
      if(isColorDateMotation(mutationRecords)){
        // 本脚本产生的变更,不触发
        // console.log("Ignore motation")
      }else{
        delayColorDate.run();
      }
    });
    observer.observe(document, {
      attributes: false,
      childList: true,
      subtree: true
    });
    //console.log("init mutationObserver");
  }

  // 监听变更,触发着色 (适用于动态网页,如: ajax加载内容后产生变更)
  if(MutationObserver){ initMutationObserver(); }
  // 静态网页加载过程中不会产生变更, 直接调用
  setTimeout(colorDate, 400);
  setTimeout(colorDate, 1000);
  setTimeout(colorDate, 4000);

})();