kasperpeulen / pressandhold

/*
 *  Project: Long Press
 *  Description: Pops a list of alternate characters when a key is long-pressed
 *  Author: Quentin Thiaucourt, http://toki-woki.net
 *	Licence: MIT License http://opensource.org/licenses/mit-license.php
 */
 
/*
Fork by Kasper Peulen to make the lay-out very similar to the OSX press and hold app.
*/
var strVar = "";
strVar += "<style>";
strVar += "";
strVar += ".long-press-popup {";
strVar += "  -moz-box-shadow: 0px 1px 3px 1px lightgray;";
strVar += "-webkit-box-shadow: 0px 1px 3px 1px lightgray;";
strVar += "box-shadow: 0px 2px 3px 0px lightgrey;";
strVar += "  font-family: Helvetica;";
strVar += "	position: relative;";
strVar += "	top:20px;";
strVar += "	left:-10px;";
strVar += "	padding: 2px;";
strVar += "	margin: 0px;";
strVar += "	background: white;";
strVar += "	font-size: 12pt;";
strVar += "	border: 1px #C2D7FE solid;";
strVar += "	list-style-type: none;";
strVar += "  display: inline-block;";
strVar += "  border-radius: 5px;";
strVar += "}";
strVar += ".long-press-popup li {";
strVar += "position:relative;";
strVar += "    border: 1px white solid;";
strVar += "    border-radius: 4px;";
strVar += "	background: white;";
strVar += "	color: #0A7ED0;";
strVar += "  display: inline-block;";
strVar += "  padding: 0 7px;";
strVar += "}";
strVar += ".long-press-popup li:hover{";
strVar += "  border: 1px #B9D6F0 solid;";
strVar += "	background: #E6F1FE;";
strVar += "	color: #0A7ED0;";
strVar += "}";
strVar += ".long-press-popup .selected {";
strVar += "    border: 1px #B9D6F0 solid;";
strVar += "	background: #E6F1FE;";
strVar += "	color: #0A7ED0;";
strVar += "}";
strVar += "";
strVar += "";
strVar += ".number {";
strVar += "  text-align:center;";
strVar += "  font-size:9pt;";
strVar += "  display: block;";
strVar += "    color: #CCCCCC;";
strVar += "  padding-top:8px ;";
strVar += "}";
strVar += "<\/style>";

$(document).ready(function() {
  var tail = $('<div class="tail" style="position: absolute; z-index:1000;"></div> ');
  $('body').append(tail);

  $('head').append(strVar);
});
var typedChar;

(function($) {

  var types = ['DOMMouseScroll', 'mousewheel'];

  if ($.event.fixHooks) {
    for (var i = types.length; i;) {
      $.event.fixHooks[types[--i]] = $.event.mouseHooks;
    }
  }

  $.event.special.mousewheel = {
    setup: function() {
      if (this.addEventListener) {
        for (var i = types.length; i;) {
          this.addEventListener(types[--i], handler, false);
        }
      } else {
        this.onmousewheel = handler;
      }
    },

    teardown: function() {
      if (this.removeEventListener) {
        for (var i = types.length; i;) {
          this.removeEventListener(types[--i], handler, false);
        }
      } else {
        this.onmousewheel = null;
      }
    }
  };

  $.fn.extend({
    mousewheel: function(fn) {
      return fn ? this.bind("mousewheel", fn) : this.trigger("mousewheel");
    },

    unmousewheel: function(fn) {
      return this.unbind("mousewheel", fn);
    }
  });


  function handler(event) {
    var orgEvent = event || window.event,
      args = [].slice.call(arguments, 1),
      delta = 0,
      returnValue = true,
      deltaX = 0,
      deltaY = 0;
    event = $.event.fix(orgEvent);
    event.type = "mousewheel";

    // Old school scrollwheel delta
    if (orgEvent.wheelDelta) {
      delta = orgEvent.wheelDelta / 120;
    }
    if (orgEvent.detail) {
      delta = -orgEvent.detail / 3;
    }

    // New school multidimensional scroll (touchpads) deltas
    deltaY = delta;

    // Gecko
    if (orgEvent.axis !== undefined && orgEvent.axis === orgEvent.HORIZONTAL_AXIS) {
      deltaY = 0;
      deltaX = -1 * delta;
    }

    // Webkit
    if (orgEvent.wheelDeltaY !== undefined) {
      deltaY = orgEvent.wheelDeltaY / 120;
    }
    if (orgEvent.wheelDeltaX !== undefined) {
      deltaX = -1 * orgEvent.wheelDeltaX / 120;
    }

    // Add event and delta to the front of the arguments
    args.unshift(event, delta, deltaX, deltaY);

    return ($.event.dispatch || $.event.handle).apply(this, args);
  }

})(jQuery);
// Avoid `console` errors in browsers that lack a console.
(function() {
  var method;
  var noop = function noop() {};
  var methods = [
    'assert', 'clear', 'count', 'debug', 'dir', 'dirxml', 'error',
    'exception', 'group', 'groupCollapsed', 'groupEnd', 'info', 'log',
    'markTimeline', 'profile', 'profileEnd', 'table', 'time', 'timeEnd',
    'timeStamp', 'trace', 'warn'
  ];
  var length = methods.length;
  var console = (window.console = window.console || {});

  while (length--) {
    method = methods[length];

    // Only stub undefined methods.
    if (!console[method]) {
      console[method] = noop;
    }
  }
}());

// Place any jQuery/helper plugins in here.

function getCaretPosition(ctrl) {
  var caretPos = 0;
  if (document.selection) {
    // IE Support
    ctrl.focus();
    var sel = document.selection.createRange();
    sel.moveStart('character', -ctrl.value.length);
    caretPos = sel.text.length;
  } else if (ctrl.selectionStart || ctrl.selectionStart == '0') {
    // Firefox support
    caretPos = ctrl.selectionStart;
  }
  return caretPos;
}

function setCaretPosition(ctrl, pos) {
  if (ctrl.setSelectionRange) {
    ctrl.focus();
    ctrl.setSelectionRange(pos, pos);
  } else if (ctrl.createTextRange) {
    var range = ctrl.createTextRange();
    range.collapse(true);
    range.moveEnd('character', pos);
    range.moveStart('character', pos);
    range.select();
  }
}

;
(function($, window, undefined) {

  var pluginName = 'longPress',
    document = window.document,
    defaults = {
      /*
  propertyName: "value"
  */
    };

  var moreChars = {
    // extended latin (and african latin)
    // upper
    'A': '∀{𝔄}{𝒜}',
    'B': 'a{𝔅}ℬ',
    'C': 'ℂ{𝒞}∐',
    'D': '{𝔇}Δ∩',
    'E': '∅ℰ',
    'F': 'ℱ',
    'G': 'Γ{𝒱}{𝔊}âˆ‡âˆ âˆŸâŸ‚âˆ„âˆŠâˆâŠżâŠŸâŠœâŠ',
    'H': 'ℋ',
    'I': 'â„â„‘âˆ©âˆ«âˆŹâˆ­âˆźâˆŻâˆ°âˆ±âˆČ∳⹋⹌',
    'J': '{đ’„}{𝔍}',
    'K': '{𝒩}{𝔎}',
    'L': 'ℒ{𝔏}ÂŹâˆ€âˆƒâˆ„âˆŽâˆ”âˆ§âˆšâŠšâŠ­â‹€â‹',
    'M': 'ℳ{𝔐}',
    'N': 'â„•â„”{đ’©}{𝔑}',
    'O': 'Ω{đ’Ș}⊕⊖⊗⊘⊙⊚⊛⊜⊝',
    'P': 'ΠΩιℙ{đ’«}',
    'Q': 'ℚ∎',
    'R': 'ℝℛ',
    'S': 'Σ⅀{𝒼}',
    'T': '⊀⊄⊹⊣⊧⊚⊩{𝒯}',
    'U': '{𝒰}âˆȘ∩',
    'V': '{đ’±}ÆČ',
    'W': '{đ’Č}',
    'X': 'Ξ{𝒳}',
    'Y': '{𝒮}',
    'Z': 'â„€{đ’”}',

    // lower
    'a': 'α∀∧∠{𝔞}',
    'b': 'ÎČ{𝔟}',
    'c': 'χ{𝔠}↯∼',
    'd': 'ÎŽ{𝔡}∂Âș∏',
    'e': 'ϔΔ∃∄∅',
    'f': 'ϕφ',
    'g': 'Îł{đ”€}',
    'h': 'η{đ”„}†♥♄',
    'i': '∈∉Îč∫∞∋∌∩',
    'j': '{𝔧}',
    'k': 'Îș',
    'l': 'λℓ',
    'm': 'ÎŒ{đ”Ș}',
    'n': 'âżÎœ{đ”«}ÂŹ',
    'o': 'ω{𝔬}√°',
    'p': 'πϕφψ{𝔭}',
    'q': '{𝔼}∎',
    'r': 'ρ{𝔯}',
    's': 'σ{𝔰}√∛∜∔',
    't': 'τΞ{đ”±}∎',
    'u': 'υ{đ”Č}',
    'v': 'ʋ',
    'w': 'ω⚠',
    'x': 'Ο',
    'y': '',
    'z': 'ζ',

    '#': 'â—»âŠžâŠŸâŠ âŠĄâ§‡â§ˆâ§…â§†',
    '.': ' ⋯⋰⋱⋟ ',



    '\'': '̀́̊̇̄̃̂̈',
    '+': '±∓⊕₊âș',
    '-': '⁻‟⏞⊖', //⏜


    '"': '̎̏̋̈̆̂̌̑̐',
    '_': '₋▁⏟', //┬⏝
    '{': '❮⟩⟹âŸȘ⌈⌊⩇⩉',
    '}': '❔⟧⟩⟫⌉⌋⊈⊊',
    '[': '❮⟩⟹âŸȘ⌈⌊⩇⩉',
    ']': '❔⟧⟩⟫⌉⌋⊈⊊',
    '|': '∣∀∄∊',
    '\\': '∖',
    '/': 'âˆ•Ă·ÂŠ',
    '<': '≀â€č⊆⊂←ↀ⇐âŠČ',
    '>': '≄â€ș⊇⊃→↩⇒âŸč⊳',
    '=': '≠≈≅≃≡âŸș⇔≟≝≔≞⇕',
    '1': 'Âč₁',
    '2': 'ÂČ₂',
    '3': '³₃',
    '4': '₄',
    '5': '⁔₅',
    '6': '⁶₆',
    '7': '⁷₇',
    '8': '⁾₈',
    '9': 'âč₉',
    '0': '⁰₀',
    '*': 'Â·âˆ˜âˆ™Ă—âŠ—âš€â‹†â˜Œ',
    '^': '┮'

  };
  var ignoredKeys = [8, 13, 37, 38, 39, 40];

  var selectedCharIndex;
  var lastWhich;
  var timer;
  var activeElement;
  var keyup;
  var count = 0;
  var oldCharlength;
  var tail;
  var popup = $('<ul class=long-press-popup />');
  var onlyonce = false;


  $('html').click(function() {
    hidePopup();
    //$('textarea').focus();
  });
  $(window).mousewheel(onWheel);
  $(window).keyup(onKeyUp);

  function onKeyDown(e) {


    keyup = false;
    count += 1;

    if ($('.long-press-popup').length <= 0) {
      if (e.which == 37 || e.which == 38 || e.which == 39 || e.which == 40 || e.which == 16) {
        return;
      }
      var oldcount = count;
      setTimeout(function() {
        if (!keyup && (oldcount === count)) {


          onTimer();
        }
      }, 250);
    }



    // Arrow key with popup visible
    if ($('.long-press-popup').length > 0) {

      if (e.which == 37) {
        activePreviousLetter();
        e.preventDefault();
      } else if (e.which == 39) {
        activateNextLetter();
        e.preventDefault();
      } else if (e.which == 13) {
        hidePopup();
        e.preventDefault();
      } else if (e.which == 49) {
        selectCharIndex(0);
        e.preventDefault();
        hidePopup();
      } else if (e.which == 50) {
        selectCharIndex(1);
        e.preventDefault();
        hidePopup();
      } else if (e.which == 51) {
        selectCharIndex(2);
        e.preventDefault();
        hidePopup();
      } else if (e.which == 52) {
        selectCharIndex(3);
        e.preventDefault();
        hidePopup();
      } else if (e.which == 53) {
        selectCharIndex(4);
        e.preventDefault();
        hidePopup();
      } else if (e.which == 54) {
        selectCharIndex(5);
        e.preventDefault();
        hidePopup();
      } else if (e.which == 55) {
        selectCharIndex(6);
        e.preventDefault();
        hidePopup();
      } else if (e.which == 56) {
        selectCharIndex(7);
        e.preventDefault();
        hidePopup();
      } else if (e.which == 57) {
        selectCharIndex(8);
        e.preventDefault();
        hidePopup();
      } else if (e.which == lastWhich) {
        e.preventDefault();
        activateNextLetter();
      } else if (e.which != lastWhich) {
        hidePopup();
      }

    }

    activeElement = e.target;



    lastWhich = e.which;
  }

  function onKeyUp(e) {


    keyup = true;


  }

  function onTimer() {

    typedChar = $(activeElement).val().split('')[getCaretPosition(activeElement) - 1];

    if (moreChars[typedChar]) {
      showPopup((moreChars[typedChar]));
    } else {

    }
  }

  function showPopup(chars) {


    popup.empty();
    oldCharlength = 1;
    $('.tail').on('click', function(e) {
      e.preventDefault();
    });
    var letter;
    var count = 0;
    for (var i = 0; i < chars.length; i++) {
      if (chars[i] !== "{") {
        letter = $('<li class=long-press-letter />').html(chars[i] + "<span class=\"number\">" + (count + 1) + "</span>");
        letter.mouseenter(activateLetter);
        letter.mousedown(hidePopup());

        popup.append(letter);
        count += 1;
      } else {
        letter = $('<li class=long-press-letter />').html(chars[i + 1] + chars[i + 2] + "<span class=\"number\">" + (count + 1) + "</span>");
        letter.mouseenter(activateDouble);
        letter.mousedown(hidePopup());

        popup.append(letter);

        count += 1;
        i += 3;
      }
    }
    $('.tail').append(popup);
    var height = $(".long-press-popup").innerHeight();
    $(".long-press-popup li").each(function(index) {
      $(this).css('padding-top', height - $(this).height() - 6);
    });


    $('textarea').each(function(index) {

      if ($(this).is(":focus")) {
        $('.tail').css($(this).textareaHelper('caretPos'));


        var offset1 = $('.tail').offset();
        var offset2 = $(this).offset();

        $(".tail").css('top', offset1.top + offset2.top);

        $(".tail").css('left', offset1.left + offset2.left);
        console.log($(".tail").css('top'), $(this).css('top'))
      }

    });



    /*

*/
    selectedCharIndex = -1;
  }

  function activateLetter(e) {
    selectCharIndex($(e.target).index());
  }

  function activateDouble(e) {
    selectCharIndex($(e.target).index());
  }

  function activateRelativeLetter(i) {
    selectCharIndex(($('.long-press-letter').length + selectedCharIndex + i) % $('.long-press-letter').length);
  }

  function activateNextLetter() {
    activateRelativeLetter(1);
  }

  function activePreviousLetter() {
    activateRelativeLetter(-1);
  }

  function hidePopup() {
    popup.detach();
    keyup = false;


  }

  function onWheel(e, delta, deltaX, deltaY) {
    if ($('.long-press-popup').length == 0) return;
    e.preventDefault();
    delta < 0 ? activateNextLetter() : activePreviousLetter();
  }

  function selectCharIndex(i) {
    $('.long-press-letter.selected').removeClass('selected');
    $('.long-press-letter').eq(i).addClass('selected');
    selectedCharIndex = i;
    updateChar(i);
  }

  function updateChar(i) {
    var caretpostion;
    var endString = $('.long-press-letter.selected').html().indexOf("<");

    var newChar = $('.long-press-letter.selected').html().substring(0, endString);
    var pos = getCaretPosition(activeElement);
    var arVal = $(activeElement).val().split('');

    if (newChar.length == 2 && (oldCharlength == 1 || arVal[pos - 1] == typedChar)) {

      arVal[pos - 1] = newChar[0];
      arVal.splice(pos, 0, newChar[1]);
      caretposition = pos + newChar.length - 1;
    } else if (arVal[pos - 1] == typedChar) {
      arVal[pos - 1] = newChar;
      caretposition = pos + newChar.length - 1;
    } else if (newChar.length == 2 && oldCharlength == 2) {

      arVal[pos - 2] = newChar[0];
      arVal[pos - 1] = newChar[1];
      caretposition = pos + newChar.length - 2;
    } else if (oldCharlength == 2) {

      arVal[pos - 2] = newChar;
      arVal[pos - 1] = "";

      caretposition = pos + newChar.length - 2;
    } else {
      arVal[pos - 1] = newChar;

      caretposition = pos + newChar.length - 1;
    }

    $(activeElement).val(arVal.join(''));
    setCaretPosition(activeElement, caretposition);
    oldCharlength = newChar.length;
  }

  function LongPress(element, options) {

    this.element = element;
    this.options = $.extend({}, defaults, options);

    this._defaults = defaults;
    this._name = pluginName;

    this.init();
  }

  LongPress.prototype = {

    init: function() {
      $(this.element).keydown(onKeyDown);
    }

  };

  $.fn[pluginName] = function(options) {
    return this.each(function() {
      if (!$.data(this, 'plugin_' + pluginName)) {
        $.data(this, 'plugin_' + pluginName, new LongPress(this, options));
      }
    });
  };

}(jQuery, window));

$(function() {
  $('textarea').each(function(index) {
    $(this).longPress();
  });

});

(function($) {
  'use strict';

  var caretClass = 'textarea-helper-caret',
    dataKey = 'textarea-helper'

    // Styles that could influence size of the mirrored element.
    ,
    mirrorStyles = [
      // Box Styles.
      'box-sizing', 'height', 'width', 'padding-bottom', 'padding-left', 'padding-right', 'padding-top'

      // Font stuff.
      , 'font-family', 'font-size', 'font-style', 'font-variant', 'font-weight'

      // Spacing etc.
      , 'word-spacing', 'letter-spacing', 'line-height', 'text-decoration', 'text-indent', 'text-transform'

      // The direction.
      , 'direction'
    ];

  var TextareaHelper = function(elem) {
    if (elem.nodeName.toLowerCase() !== 'textarea') return;
    this.$text = $(elem);
    this.$mirror = $('<div/>').css({
      'position': 'absolute',
      'overflow': 'auto',
      'white-space': 'pre-wrap',
      'word-wrap': 'break-word',
      'top': 0,
      'left': -9999
    }).insertAfter(this.$text);
  };

  (function() {
    this.update = function() {

      // Copy styles.
      var styles = {};
      for (var i = 0, style; style = mirrorStyles[i]; i++) {
        styles[style] = this.$text.css(style);
      }
      this.$mirror.css(styles).empty();

      // Update content and insert caret.
      var caretPos = this.getOriginalCaretPos()
      //caretPos.left += 20;

        ,
        str = this.$text.val(),
        pre = document.createTextNode(str.substring(0, caretPos)),
        post = document.createTextNode(str.substring(caretPos)),
        $car = $('<span/>').addClass(caretClass).css('position', 'absolute').html('&nbsp;');
      this.$mirror.append(pre, $car, post)
        .scrollTop(this.$text.scrollTop());
    };

    this.destroy = function() {
      this.$mirror.remove();
      this.$text.removeData(dataKey);
      return null;
    };

    this.caretPos = function() {
      this.update();
      var $caret = this.$mirror.find('.' + caretClass),
        pos = $caret.position();


      if (this.$text.css('direction') === 'rtl') {
        pos.right = this.$mirror.innerWidth() - pos.left;
        pos.left = 'auto';

      }

      return pos;
    };

    this.height = function() {
      this.update();
      this.$mirror.css('height', '');
      console.log(this.$mirror.height());
      return this.$mirror.height();
    };

    // XBrowser caret position
    // Adapted from http://stackoverflow.com/questions/263743/how-to-get-caret-position-in-textarea
    this.getOriginalCaretPos = function() {
      var text = this.$text[0];
      if (text.selectionStart) {
        return text.selectionStart;
      } else if (document.selection) {
        text.focus();
        var r = document.selection.createRange();
        if (r == null) {
          return 0;
        }
        var re = text.createTextRange(),
          rc = re.duplicate();
        re.moveToBookmark(r.getBookmark());
        rc.setEndPoint('EndToStart', re);
        return rc.text.length;
      }
      return 0;
    };

  }).call(TextareaHelper.prototype);

  $.fn.textareaHelper = function(method) {
    this.each(function() {
      var $this = $(this),
        instance = $this.data(dataKey);
      if (!instance) {
        instance = new TextareaHelper(this);
        $this.data(dataKey, instance);
      }
    });
    if (method) {
      var instance = this.first().data(dataKey);
      return instance[method]();
    } else {
      return this;
    }
  };

})(jQuery);