DiasPlus

Userscript that adds tweaks to diaspora*.

当前为 2016-11-22 提交的版本,查看 最新版本

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name        DiasPlus
// @namespace   diasplus
// @description Userscript that adds tweaks to diaspora*.
// @include     *
// @version     2.0.0
// @copyright   2016 Armando Lüscher
// @author      Armando Lüscher
// @oujs:author noplanman
// @grant       GM_addStyle
// @grant       GM_getValue
// @grant       GM_setValue
// @grant       window
// @require     https://code.jquery.com/jquery-3.1.1.slim.min.js
// @homepageURL https://github.com/noplanman/DiasPlus
// @supportURL  https://github.com/noplanman/DiasPlus/issues
// ==/UserScript==

// Make sure we're on a diaspora* pod.
if (typeof unsafeWindow.Diaspora === 'undefined') {
  throw 'Not a diaspora* pod, move along...';
}

var DiasPlus = {};
DiasPlus.gon = unsafeWindow.gon;
DiasPlus.secure = true;
DiasPlus.domain = '';

/**
 * Get the pod URL (protocol + domain).
 *
 * @return {string} The pod URL.
 */
DiasPlus.getPodURL = function () {
  return 'http' + (DiasPlus.secure ? 's' : '') + '://' + DiasPlus.domain;
};


/**
 * Get the pod info from the GM settings.
 */
DiasPlus.loadPodInfo = function () {
  DiasPlus.setPodInfo(GM_getValue('dplus-pod-url', ''));
};

/**
 * Set the pod info and save it to the DiasPlus object and the GM settings.
 *
 * @param {string} podURL Pod URL to save.
 *
 * @return {boolean}
 */
DiasPlus.setPodInfo = function (podURL) {
  var info = podURL.split('://');
  if (info.length === 2) {
    DiasPlus.secure = info[0] === 'https';
    DiasPlus.domain = info[1];
    GM_setValue('dplus-pod-url', DiasPlus.getPodURL());

    return true
  }

  return false;
};

/**
 * Add the settings button to the top right navbar.
 */
DiasPlus.addSettingsButton = function () {
  // Add settings button.
  $('<li class="dplus-settings-button" title="DiasPlus Settings"><a><i class="entypo-cog"></i>D+</a></li>')
    .click(function () {
      var p = prompt('Modify your pod URL (eg. https://diasp.eu)', DiasPlus.getPodURL());
      DiasPlus.setPodInfo(p) && DiasPlus.addOompButton();
    })
    .appendTo('ul.navbar-right:first');
};

/**
 * Add the "Open on my pod" button to the top right navbar.
 */
DiasPlus.addOompButton = function () {
  // Remove the existing button if it already exists.
  $('.dplus-oomp-button').remove();

  // If we are not logged into this pod, it must be a foreign one.
  if (!('user' in DiasPlus.gon) && location.hostname !== DiasPlus.domain) {
    var $button = $('<li class="dplus-oomp-button" title="Open on my pod"><a target="_self"><i class="entypo-export"></i></a></li>')

    // Is this the first time we're setting the pod URL?
    if ('' === DiasPlus.domain) {
      $button.click(function () {
        var p = prompt('Your pod has not been defined yet!\n\nEnter your pod domain (eg. https://diasp.eu)', DiasPlus.getPodURL());
        DiasPlus.setPodInfo(p) && DiasPlus.addOompButton();
      });
    } else {
      var url = DiasPlus.getPodURL();

      if ('post' in DiasPlus.gon) {
        url += '/posts/' + DiasPlus.gon.post.guid;
      } else {
        url += location.pathname;
      }

      $('a', $button).attr('href', url);
    }

    $button.appendTo('ul.navbar-right');
  }
};

/**
 * Add the "Liked" and "Commented" links to the stream selection menu.
 */
DiasPlus.addExtraMenuLinks = function () {
  var $streamSelection = $('#stream_selection');
  $('li:nth-child(2)', $streamSelection).after(
    '<li><a class="hoverable" href="/liked">Liked</a></li>' +
    '<li><a class="hoverable" href="/commented">Commented</a></li>'
  );

  // Highlight the background of the active nav item's page.
  $streamSelection.find('li').each(function () {
    var navHref = $('a', this).attr('href');
    if (navHref === location.href.substring(location.href.length - navHref.length)) {
      $(this).addClass('selected');
    }
  });
};

/**
 * Add button that reverses the order of conversation messages.
 */
DiasPlus.addMessageSortingButton = function () {
  if ($('body').hasClass('page-conversations')) {
    var revMessages = function () {
      $('<a class="dplus-reverse-messages" title="Reverse message order"><i class="entypo-switch"></i></a>')
        .click(function () {
          $('#conversation-show .stream').html($('#conversation-show .stream-element').get().reverse());
        })
        .prependTo('.control-icons');
    };
    revMessages();
    DiasPlus.Observer.add('#conversation-show', revMessages);
  }
};

// Time when the mouse button was pressed, or false if not pressed.
var md = false;

/**
 * Initialise the "long click tags" feature.
 */
DiasPlus.initLongClickTags = function () {
  // MouseDown and MouseUp actions for the post entry field.
  $('#status_message_fake_text')
    .mousedown(function () {
      md = Date.now();
      DiasPlus.makeTag($(this));
    })
    .mouseup(function () {
      md = false;
    });
};

/**
 * Check if the passed character is not a space or new line character.
 *
 * @param {string} c The character to check.
 *
 * @return {boolean} True if not a space or new line, else False.
 */
DiasPlus.isValidChar = function (c) {
  return undefined !== c && !/\s/.test(c);
};

/**
 * Convert the currently selected word of the passed text area to a tag.
 *
 * @param {jQuery} $textArea The text area to be handled.
 */
DiasPlus.makeTag = function ($textArea) {
  try {
    // Mouse has been released early.
    if (!md) {
      return;
    }

    // Mouse button down long enough? Loop with timeouts until yes.
    if (md + 500 > Date.now()) {
      setTimeout(function () {
        DiasPlus.makeTag($textArea);
      }, 50);
    } else if ($textArea instanceof jQuery && $textArea.is('textarea')) {
      // Make sure we have been passed a text area.
      var textAreaText = $textArea.val();
      var cPos1 = $textArea[0].selectionStart;
      var cPos2 = $textArea[0].selectionEnd;

      // Only if there is no selection.
      if (cPos1 === cPos2) {

        // Search for the word end backwards.
        while (--cPos1 >= 0 && DiasPlus.isValidChar(textAreaText[cPos1]));
        cPos1++;

        // Let's handle the tag.
        if (DiasPlus.isValidChar(textAreaText[cPos1])) {
          if (textAreaText[cPos1] === '#') {
            // Looks like we're removing the tag.
            if (DiasPlus.isValidChar(textAreaText[cPos1 + 1]) && textAreaText[cPos1 + 1] !== '#') {
              $textArea.val(textAreaText.substring(0, cPos1) + textAreaText.substring(cPos1 + 1));
              // If we're removing the tag from the left, compensate for the # character.
              (textAreaText[cPos2] === '#') || cPos2--;
            }
          } else {
            // Looks like we're adding the tag.
            $textArea.val(textAreaText.substring(0, cPos1) + '#' + textAreaText.substring(cPos1));
            cPos2++;
          }
          // Set new caret positions.
          $textArea[0].selectionStart = $textArea[0].selectionEnd = cPos2;
        }
      }
      md = false;
    }
  } catch (e) {
    DiasPlus.doLog('Error while making tag.', 'e', false, e);
    md = false;
  }
};

/**
 * Make a log entry.
 *
 * @param {string}  logMessage Message to write to the log console.
 * @param {string}  logLevel   Level to log ([l]og,[i]nfo,[w]arning,[e]rror).
 * @param {boolean} alsoAlert  Also echo the message in an alert box.
 * @param {Error}   e          If an exception is passed too, add that info.
 */
DiasPlus.doLog = function (logMessage, logLevel, alsoAlert, e) {
  // Default to "log" if nothing is provided.
  logLevel = logLevel || 'l';

  // Add exception details if available.
  if (e instanceof Error) {
    logMessage += ' (' + e.name + ': ' + e.message + ')';
  }

  logLevel === 'l' && console.log(logMessage);
  logLevel === 'i' && console.info(logMessage);
  logLevel === 'w' && console.warn(logMessage);
  logLevel === 'e' && console.error(logMessage);

  alsoAlert && alert(logMessage);
};

/**
 * Start the party.
 */
DiasPlus.init = function () {
  // Add the global CSS rules.
  GM_addStyle(
    '.dplus-settings-button, .dplus-oomp-button { cursor: pointer; }' +
    '.dplus-oomp-button { background: #0c0; }' +
    '.dplus-oomp-button a { color: #fff !important; }' +
    '.page-conversations .control-icons a { cursor: pointer; display: inline-block !important; }' +
    '.page-conversations .control-icons .dplus-reverse-messages i { font-size: 20px; }'
  );

  // Load the pod infos from the GM settings.
  DiasPlus.loadPodInfo();

  // Load all the features.
  DiasPlus.addSettingsButton();
  DiasPlus.initLongClickTags();
  DiasPlus.addExtraMenuLinks();
  DiasPlus.addOompButton();
  DiasPlus.addMessageSortingButton();
};

// source: https://muffinresearch.co.uk/does-settimeout-solve-the-domcontentloaded-problem/
if (/(?!.*?compatible|.*?webkit)^mozilla|opera/i.test(navigator.userAgent)) { // Feeling dirty yet?
  document.addEventListener('DOMContentLoaded', DiasPlus.init, false);
} else {
  window.setTimeout(DiasPlus.init, 0);
}

/**
 * The MutationObserver to detect page changes.
 */
DiasPlus.Observer = {
  // The mutation observer objects.
  observers: [],

  /**
   * Add an observer to observe for DOM changes.
   *
   * @param {string}   queryToObserve Query string of elements to observe.
   * @param {function} cb             Callback function for the observer.
   */
  add: function (queryToObserve, cb) {
    // Check if we can use the MutationObserver.
    if ('MutationObserver' in window) {
      var toObserve = document.querySelector(queryToObserve);
      if (toObserve) {
        var mo = new MutationObserver(cb);
        DiasPlus.Observer.observers.push(mo);

        // Observe child changes.
        mo.observe(toObserve, {
          childList: true
        });
      }
    }
  }
};