DiasPlus

Userscript that adds tweaks to diaspora*.

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

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

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 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
        });
      }
    }
  }
};