diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js
index 89cdb16..4a58cd9 100644
--- a/app/assets/javascripts/application.js
+++ b/app/assets/javascripts/application.js
@@ -14,4 +14,5 @@
//= require app
//= require editor
//= require ago
-//= require highlight
\ No newline at end of file
+//= require highlight
+//= require autocomplete
\ No newline at end of file
diff --git a/app/assets/javascripts/autocomplete.js b/app/assets/javascripts/autocomplete.js
new file mode 100644
index 0000000..40df641
--- /dev/null
+++ b/app/assets/javascripts/autocomplete.js
@@ -0,0 +1,603 @@
+/**
+ * Plugin Name: Autocomplete for Textarea
+ * Author: Amir Harel
+ * Copyright: amir harel (harel.amir1@gmail.com)
+ * Twitter: @amir_harel
+ * Version 1.4
+ * Published at : http://www.amirharel.com/2011/03/07/implementing-autocomplete-jquery-plugin-for-textarea/
+ */
+
+(function($){
+ /**
+ * @param obj
+ * @attr wordCount {Number} the number of words the user want to for matching it with the dictionary
+ * @attr mode {String} set "outter" for using an autocomplete that is being displayed in the outter layout of the textarea, as opposed to inner display
+ * @attr on {Object} containing the followings:
+ * @attr query {Function} will be called to query if there is any match for the user input
+ */
+ $.fn.autocomplete = function(obj){
+ this.each(function(index,element){
+ if( element.nodeName == 'TEXTAREA' ){
+ makeAutoComplete(element,obj);
+ }
+ });
+ }
+
+ //var browser = {isChrome: $.browser.webkit };
+
+ function getTextAreaSelectionEnd(ta) {
+ var textArea = ta;//document.getElementById('textarea1');
+ if (document.selection) { //IE
+ var bm = document.selection.createRange().getBookmark();
+ var sel = textArea.createTextRange();
+ sel.moveToBookmark(bm);
+ var sleft = textArea.createTextRange();
+ sleft.collapse(true);
+ sleft.setEndPoint("EndToStart", sel);
+ return sleft.text.length + sel.text.length;
+ }
+ return textArea.selectionEnd; //ff & chrome
+ }
+
+ function getDefaultCharArray(){
+ return {
+ '`':0,
+ '~':0,
+ '1':0,
+ '!':0,
+ '2':0,
+ '@':0,
+ '3':0,
+ '#':0,
+ '4':0,
+ '$':0,
+ '5':0,
+ '%':0,
+ '6':0,
+ '^':0,
+ '7':0,
+ '&':0,
+ '8':0,
+ '*':0,
+ '9':0,
+ '(':0,
+ '0':0,
+ ')':0,
+ '-':0,
+ '_':0,
+ '=':0,
+ '+':0,
+ 'q':0,
+ 'Q':0,
+ 'w':0,
+ 'W':0,
+ 'e':0,
+ 'E':0,
+ 'r':0,
+ 'R':0,
+ 't':0,
+ 'T':0,
+ 'y':0,
+ 'Y':0,
+ 'u':0,
+ 'U':0,
+ 'i':0,
+ 'I':0,
+ 'o':0,
+ 'O':0,
+ 'p':0,
+ 'P':0,
+ '[':0,
+ '{':0,
+ ']':0,
+ '}':0,
+ 'a':0,
+ 'A':0,
+ 's':0,
+ 'S':0,
+ 'd':0,
+ 'D':0,
+ 'f':0,
+ 'F':0,
+ 'g':0,
+ 'G':0,
+ 'h':0,
+ 'H':0,
+ 'j':0,
+ 'J':0,
+ 'k':0,
+ 'K':0,
+ 'l':0,
+ 'L':0,
+ ';':0,
+ ':':0,
+ '\'':0,
+ '"':0,
+ '\\':0,
+ '|':0,
+ 'z':0,
+ 'Z':0,
+ 'x':0,
+ 'X':0,
+ 'c':0,
+ 'C':0,
+ 'v':0,
+ 'V':0,
+ 'b':0,
+ 'B':0,
+ 'n':0,
+ 'N':0,
+ 'm':0,
+ 'M':0,
+ ',':0,
+ '<':0,
+ '.':0,
+ '>':0,
+ '/':0,
+ '?':0,
+ ' ':0
+ };
+ }
+
+
+ function setCharSize(data){
+ for( var ch in data.chars ){
+ if( ch == ' ' ) $(data.clone).html(" ");
+ else $(data.clone).html(""+ch+"");
+ var testWidth = $("#test-width_"+data.id).width();
+ data.chars[ch] = testWidth;
+ }
+ }
+
+ var _data = {};
+ var _count = 0;
+ function makeAutoComplete(ta,obj){
+ _count++;
+ _data[_count] = {
+ id:"auto_"+_count,
+ ta:ta,
+ wordCount:obj.wordCount,
+ on:obj.on,
+ clone:null,
+ lineHeight:0,
+ list:null,
+ charInLines:{},
+ mode:obj.mode,
+ chars:getDefaultCharArray()};
+
+ var clone = createClone(_count);
+ _data[_count].clone = clone;
+ setCharSize(_data[_count]);
+ //_data[_count].lineHeight = $(ta).css("font-size");
+ _data[_count].list = createList(_data[_count]);
+ registerEvents(_data[_count]);
+ }
+
+ function createList(data){
+ var ul = document.createElement("ul");
+ $(ul).addClass("auto-list");
+ document.body.appendChild(ul);
+ return ul;
+ }
+
+ function createClone(id){
+ var data = _data[id];
+ var div = document.createElement("div");
+ var offset = $(data.ta).offset();
+ offset.top = offset.top - parseInt($(data.ta).css("margin-top"));
+ offset.left = offset.left - parseInt($(data.ta).css("margin-left"));
+ //console.log("createClone: offset.top=",offset.top," offset.left=",offset.left);
+ $(div).css({
+ position:"absolute",
+ top: offset.top,
+ left: offset.left,
+ "border-collapse" : $(data.ta).css("border-collapse"),
+ "border-bottom-style" : $(data.ta).css("border-bottom-style"),
+ "border-bottom-width" : $(data.ta).css("border-bottom-width"),
+ "border-left-style" : $(data.ta).css("border-left-style"),
+ "border-left-width" : $(data.ta).css("border-left-width"),
+ "border-right-style" : $(data.ta).css("border-right-style"),
+ "border-right-width" : $(data.ta).css("border-right-width"),
+ "border-spacing" : $(data.ta).css("border-spacing"),
+ "border-top-style" : $(data.ta).css("border-top-style"),
+ "border-top-width" : $(data.ta).css("border-top-width"),
+ "direction" : $(data.ta).css("direction"),
+ "font-size-adjust" : $(data.ta).css("font-size-adjust"),
+ "font-size" : $(data.ta).css("font-size"),
+ "font-stretch" : $(data.ta).css("font-stretch"),
+ "font-style" : $(data.ta).css("font-style"),
+ "font-family" : $(data.ta).css("font-family"),
+ "font-variant" : $(data.ta).css("font-variant"),
+ "font-weight" : $(data.ta).css("font-weight"),
+ "width" : $(data.ta).css("width"),
+ "height" : $(data.ta).css("height"),
+ "letter-spacing" : $(data.ta).css("letter-spacing"),
+ "margin-bottom" : $(data.ta).css("margin-bottom"),
+ "margin-top" : $(data.ta).css("margin-top"),
+ "margin-right" : $(data.ta).css("margin-right"),
+ "margin-left" : $(data.ta).css("margin-left"),
+ "padding-bottom" : $(data.ta).css("padding-bottom"),
+ "padding-top" : $(data.ta).css("padding-top"),
+ "padding-right" : $(data.ta).css("padding-right"),
+ "padding-left" : $(data.ta).css("padding-left"),
+ "overflow-x" : "hidden",
+ "line-height" : $(data.ta).css("line-height"),
+ "overflow-y" : "hidden",
+ "z-index" : -10
+ });
+
+ //console.log("createClone: ta width=",$(data.ta).css("width")," ta clientWidth=",data.ta.clientWidth, "scrollWidth=",data.ta.scrollWidth," offsetWidth=",data.ta.offsetWidth," jquery.width=",$(data.ta).width());
+ //i don't know why by chrome adds some pixels to the clientWidth...
+ data.chromeWidthFix = (data.ta.clientWidth - $(data.ta).width());
+ data.lineHeight = $(data.ta).css("line-height");
+ if( isNaN(parseInt(data.lineHeight)) ) data.lineHeight = parseInt($(data.ta).css("font-size"))+2;
+
+ document.body.appendChild(div);
+ return div;
+ }
+
+ /*function breakLongLines(lines,miror){
+ var ret = [];
+ for( var i=0; i"+line+"");
+ var span = miror.children("span");
+ if( span.width()+10 < miror.width()) return [line];
+ while( span.width() >= miror.width()-10 ){
+
+ count++;
+ var words = line.split(" ");
+ var left = words.slice(0,words.length-count);
+ var right = words.slice(words.length-count,words.length);
+ miror.html(""+left.join(" ")+"");
+ var span = miror.children("span");
+
+ ret = [left.join(" "),right.join(" ")];
+ }
+ var arr = breakLine(ret[1],miror);
+ var ret2= [ret[0]];
+ for( var i=0; i= 0 && text.charAt(pos) != '\n'){
+ ret.unshift(text.charAt(pos));
+ pos--;
+ if( text.charAt(pos) == ' ' || pos < 0 ){
+ wordsFound++;
+ }
+ }
+ return ret.join("");
+ }
+
+
+ function showList(data,list,text){
+ if( !data.listVisible ){
+ data.listVisible = true;
+ var pos = getCursorPosition(data);
+ $(data.list).css({
+ left: pos.left+"px",
+ top: pos.top+"px",
+ display: "block"
+ });
+
+ }
+
+ var html = "";
+ var regEx = new RegExp("("+text.slice(1)+")");
+ var taWidth = $(data.ta).width()-5;
+ var width = data.mode == "outter" ? "style='width:"+taWidth+"px;'" : "";
+ for( var i=0; i< list.length; i++ ){
+
+ //var a = list[i].replace(regEx,"$1");
+
+
+ html += ""+list[i].replace(regEx,"$1")+"";
+ }
+ $(data.list).html(html);
+ }
+
+ function breakLines(text,data){
+ var lines = [];
+
+ var width = $(data.clone).width();
+
+ var line1 = "";
+ var line1Width = 0;
+ var line2Width = 0;
+ var line2 = "";
+ var chSize = data.chars;
+
+
+ var len = text.length;
+ for( var i=0; i"+lines[i]+"");
+ }
+ miror.append(""+lines[lines.length-1]+"");
+
+ miror.append(""+restText.replace(/\n/g,"
")+" ");
+
+ miror.get(0).scrollTop = ta.scrollTop;
+
+ var span = miror.children("#"+data.id);
+ var offset = span.offset();
+
+ return {top:offset.top+span.height(),left:offset.left+span.width()};
+
+ }
+
+ function getOuterPosition(data){
+ var offset = $(data.ta).offset();
+ return {top:offset.top+$(data.ta).height()+8,left:offset.left};
+ }
+
+ function hideList(data){
+ if( data.listVisible ){
+ $(data.list).css("display","none");
+ data.listVisible = false;
+ }
+ }
+
+ function setSelected(dir,data){
+ var selected = $(data.list).find("[data-selected=true]");
+ if( selected.length != 1 ){
+ if( dir > 0 ) $(data.list).find("li:first-child").attr("data-selected","true");
+ else $(data.list).find("li:last-child").attr("data-selected","true");
+ return;
+ }
+ selected.attr("data-selected","false");
+ if( dir > 0 ){
+ selected.next().attr("data-selected","true");
+ }
+ else{
+ selected.prev().attr("data-selected","true");
+ }
+
+ }
+
+ function getCurrentSelected(data){
+ var selected = $(data.list).find("[data-selected=true]");
+ if( selected.length == 1) return selected.get(0);
+ return null;
+ }
+
+ function onUserSelected(li,data){
+ var seletedText = $(li).attr("data-value");
+
+
+ var selectionEnd = getTextAreaSelectionEnd(data.ta);//.selectionEnd;
+ var text = data.ta.value;
+ text = text.substr(0,selectionEnd);
+ //if( text.charAt(text.length-1) == ' ' || text.charAt(text.length-1) == '\n' ) return "";
+ //var ret = [];
+ var wordsFound = 0;
+ var pos = text.length-1;
+
+ while( wordsFound < data.wordCount && pos >= 0 && text.charAt(pos) != '\n'){
+ //ret.unshift(text.charAt(pos));
+ pos--;
+ if( text.charAt(pos) == ' ' || pos < 0 ){
+ wordsFound++;
+ }
+ }
+ var a = data.ta.value.substr(0,pos+1);
+ var c = data.ta.value.substr(selectionEnd,data.ta.value.length);
+ var scrollTop = data.ta.scrollTop;
+ data.ta.value = a+seletedText+c;
+ data.ta.scrollTop = scrollTop;
+ data.ta.selectionEnd = pos+1+seletedText.length;
+ hideList(data);
+ $(data.ta).focus();
+ }
+
+ function registerEvents(data){
+ $(data.list).delegate("li","click",function(e){
+ var li = this;
+ onUserSelected(li,data);
+ e.stopPropagation();
+ e.preventDefault();
+ return false;
+ });
+
+
+
+ $(data.ta).blur(function(e){
+ setTimeout(function(){
+ hideList(data);
+ },400);
+
+ });
+
+ $(data.ta).click(function(e){
+ hideList(data);
+ });
+
+ $(data.ta).keydown(function(e){
+ //console.log("keydown keycode="+e.keyCode);
+ if( data.listVisible ){
+ switch(e.keyCode){
+ case 9:
+ case 13:
+ case 40:
+ case 38:
+ e.stopImmediatePropagation();
+ e.preventDefault();
+ return false;
+ case 27: //esc
+ hideList(data);
+ }
+
+ }
+ });
+
+ $(data.ta).keyup(function(e){
+ if( data.listVisible ){
+ //console.log("keCode=",e.keyCode);
+ if( e.keyCode == 40 ){//down key
+ setSelected(+1,data);
+ e.stopImmediatePropagation();
+ e.preventDefault();
+ return false;
+ }
+ if( e.keyCode == 38 ){//up key
+ setSelected(-1,data);
+ e.stopImmediatePropagation();
+ e.preventDefault();
+ return false;
+ }
+ if( e.keyCode == 13 || e.keyCode == 9 ){//enter key or Tab key
+ var li = getCurrentSelected(data);
+ if( li ){
+
+ e.stopImmediatePropagation();
+ e.preventDefault();
+ hideList(data);
+ onUserSelected(li,data);
+ return false;
+ }
+ hideList(data);
+ }
+ if( e.keyCode == 27 ){
+ e.stopImmediatePropagation();
+ e.preventDefault();
+ return false;
+ }
+ }
+ switch( e.keyCode ){
+ case 27:
+ return true;
+ }
+
+ var text = getWords(data);
+ //console.log("getWords return ",text);
+ if( text != "" ){
+ data.on.query(text,function(list){
+ //console.log("got list = ",list);
+ if( list.length ){
+ showList(data,list,text);
+ }
+ else{
+ hideList(data);
+ }
+
+
+ });
+ }
+ else{
+ hideList(data);
+ }
+ });
+
+
+
+ $(data.ta).scroll(function(e){
+ var ta = e.target;
+ var miror = $(data.clone);
+ miror.get(0).scrollTop = ta.scrollTop;
+ });
+ }
+})(jQuery);
\ No newline at end of file
diff --git a/app/assets/javascripts/editor.js b/app/assets/javascripts/editor.js
index f82b7f7..4e91eda 100644
--- a/app/assets/javascripts/editor.js
+++ b/app/assets/javascripts/editor.js
@@ -52,4 +52,37 @@ $(function() {
});
}
+ var query_history = {};
+ $('.md_editor .editor_field').autocomplete({
+ wordCount: 1,
+ mode: "inner",
+ on: {
+ query: function(text, callback) {
+ console.log(query_history)
+ if (text.length > 2 && text[0] == "@") {
+ text = text.slice(1)
+ if (query_history[text]) {
+ callback(query_history[text]);
+ } else {
+ $.ajax("/users/suggestions", {
+ type: 'post',
+ data: {name: text},
+ dataType: 'json',
+ headers: {
+ 'X-CSRF-Token': $('meta[name="csrf-token"]').attr('content')
+ },
+ success: function(data) {
+ query_history[text] = data;
+ callback(data);
+ },
+ error: function(xhr, status, err) {
+ callback([]);
+ }
+ });
+ }
+ }
+ }
+ }
+ });
+
});
\ No newline at end of file
diff --git a/app/assets/stylesheets/style.css.scss b/app/assets/stylesheets/style.css.scss
index 851eb9c..e7f2fbc 100644
--- a/app/assets/stylesheets/style.css.scss
+++ b/app/assets/stylesheets/style.css.scss
@@ -467,15 +467,64 @@ blockquote p {
left: 1em;
z-index: 10;
}
+
.editor_field {
padding-top: 4em;
min-height: 5em;
+ margin: 0;
}
.preview {
display: none;
padding: 4em 1em 1em;
}
+
+ .suggestions {
+ background: #ddd;
+ padding: 0.5em;
+
+ .name {
+ display: inline-block;
+ font-style: italic;
+ padding: 1px 2px;
+ margin-right: 1em;
+ border: 1px solid #6cf;
+ border-radius: 4px;
+ font-weight: bold;
+ }
+ }
+ }
+}
+
+ul.auto-list {
+ display: none;
+ position: absolute;
+ top: 0;
+ left: 0;
+ border: 1px solid #000;
+ background-color: #363636;
+ padding: 0;
+ margin: 0;
+ list-style: none;
+ z-index: 999;
+ color: #fff;
+
+ li {
+ cursor: pointer;
+ padding: 2px;
+
+ &:hover, &[data-selected=true] {
+ background-color: #f66;
+ }
+
+ mark {
+ font-weight: bold;
+ margin: 0;
+ padding: 0;
+ background: inherit;
+ text-decoration: underline;
+ color: inherit;
+ }
}
}
@@ -484,7 +533,7 @@ blockquote p {
}
.markdown-help {
- margin: 4px 0 -4px;
+ margin: 4px 0 0;
background: #ddd;
padding: 0.5em 1em;
border-bottom: 1px solid;
diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb
index 7cd3c49..061ddc6 100644
--- a/app/controllers/users_controller.rb
+++ b/app/controllers/users_controller.rb
@@ -3,7 +3,7 @@ class UsersController < ApplicationController
require 'open-uri'
include MailerHelper
- before_filter :set_user, except: [:index, :new, :create, :lost_password, :reset_password]
+ before_filter :set_user, except: [:index, :new, :create, :lost_password, :reset_password, :suggestions]
def index
if params[:role]
@@ -286,6 +286,17 @@ class UsersController < ApplicationController
end
end
+ def suggestions
+ query = params[:name]
+ if current_user && query.present? && query =~ /\A[a-zA-Z0-9_]{1,16}\Z/
+ @users = User.where("ign LIKE ?", "#{query}%").order(:ign).limit(7)
+ @users = @users.to_a.map{|u| u.ign}
+ render json: @users
+ else
+ render json: []
+ end
+ end
+
private
def validate_token(uuid, email, token)
diff --git a/app/views/application/_mdhelp.html.erb b/app/views/application/_mdhelp.html.erb
index cb21e63..6979bfd 100644
--- a/app/views/application/_mdhelp.html.erb
+++ b/app/views/application/_mdhelp.html.erb
@@ -7,6 +7,6 @@
==mark== |
[link](https://example.com)
- <%= link_to "more...", "/info/1", target: "_blank", class: "right", tabindex: -1 %>
+ <%= link_to "formatting help", "/info/1", target: "_blank", class: "right", tabindex: -1 %>
\ No newline at end of file
diff --git a/config/routes.rb b/config/routes.rb
index 3868199..4128402 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -24,6 +24,7 @@ Redstoner::Application.routes.draw do
collection do
get 'lost_password'
post 'reset_password'
+ post 'suggestions'
end
end