Merge branch 'jquery-mentions-input'
This commit is contained in:
commit
45f0031763
15 changed files with 750 additions and 623 deletions
|
|
@ -5,6 +5,8 @@
|
||||||
|
|
||||||
- content_for :head do
|
- content_for :head do
|
||||||
= include_javascripts :people
|
= include_javascripts :people
|
||||||
|
:javascript
|
||||||
|
Mentions.options.prefillMention = #{@person.to_json};
|
||||||
|
|
||||||
- content_for :page_title do
|
- content_for :page_title do
|
||||||
= @person.name
|
= @person.name
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,13 @@
|
||||||
-# Copyright (c) 2010-2011, Diaspora Inc. This file is
|
-# Copyright (c) 2010-2011, Diaspora Inc. This file is
|
||||||
-# licensed under the Affero General Public License version 3 or later. See
|
-# licensed under the Affero General Public License version 3 or later. See
|
||||||
-# the COPYRIGHT file.
|
-# the COPYRIGHT file.
|
||||||
|
|
||||||
|
|
||||||
= javascript_include_tag "publisher.js"
|
= javascript_include_tag "publisher.js"
|
||||||
|
|
||||||
:javascript
|
:javascript
|
||||||
$(document).ready(function()
|
$(function() {
|
||||||
{
|
$("#publisher").bind('ajax:success', function(){ location.reload(); });
|
||||||
var person = {name: '#{@person.name}', handle: '#{@person.diaspora_handle}' };
|
Publisher.bookmarklet = true;
|
||||||
Publisher.autocompletion.onSelect($("#status_message_fake_text"),person,'#{@person.name}');
|
});
|
||||||
$("#publisher #status_message_fake_text").val(function(index, value){ return value + " " });
|
|
||||||
$("#publisher").bind('ajax:success', function(){location.reload();});
|
|
||||||
Publisher.bookmarklet = true;
|
|
||||||
});
|
|
||||||
|
|
||||||
#new_status_message_pane
|
#new_status_message_pane
|
||||||
.span-15.last
|
.span-15.last
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,10 @@ javascripts:
|
||||||
- public/javascripts/vendor/jquery.expander.js
|
- public/javascripts/vendor/jquery.expander.js
|
||||||
- public/javascripts/vendor/timeago.js
|
- public/javascripts/vendor/timeago.js
|
||||||
- public/javascripts/vendor/facebox.js
|
- public/javascripts/vendor/facebox.js
|
||||||
|
- public/javascripts/vendor/underscore.js
|
||||||
|
- public/javascripts/vendor/jquery.events.input.js
|
||||||
|
- public/javascripts/vendor/jquery.elastic.js
|
||||||
|
- public/javascripts/vendor/jquery.mentionsInput.js
|
||||||
- public/javascripts/jquery.infinitescroll-custom.js
|
- public/javascripts/jquery.infinitescroll-custom.js
|
||||||
- public/javascripts/jquery.autocomplete-custom.js
|
- public/javascripts/jquery.autocomplete-custom.js
|
||||||
- public/javascripts/jquery.infieldlabel-custom.js
|
- public/javascripts/jquery.infieldlabel-custom.js
|
||||||
|
|
@ -47,6 +51,7 @@ javascripts:
|
||||||
- public/javascripts/contact-edit.js
|
- public/javascripts/contact-edit.js
|
||||||
- public/javascripts/contact-list.js
|
- public/javascripts/contact-list.js
|
||||||
- public/javascripts/aspect-sorting.js
|
- public/javascripts/aspect-sorting.js
|
||||||
|
- public/javascripts/mentions.js
|
||||||
|
|
||||||
- public/javascripts/vendor/bootstrap/bootstrap-twipsy.js
|
- public/javascripts/vendor/bootstrap/bootstrap-twipsy.js
|
||||||
- public/javascripts/vendor/bootstrap/bootstrap-popover.js
|
- public/javascripts/vendor/bootstrap/bootstrap-popover.js
|
||||||
|
|
@ -71,6 +76,10 @@ javascripts:
|
||||||
- public/javascripts/aspect-edit-pane.js
|
- public/javascripts/aspect-edit-pane.js
|
||||||
- public/javascripts/fileuploader-custom.js
|
- public/javascripts/fileuploader-custom.js
|
||||||
people:
|
people:
|
||||||
|
- public/javascripts/aspect-edit-pane.js
|
||||||
|
- public/javascripts/fileuploader-custom.js
|
||||||
|
people:
|
||||||
|
- public/javascripts/vendor/jquery.autoSuggest.custom.js
|
||||||
- public/javascripts/vendor/jquery.autoSuggest.custom.js
|
- public/javascripts/vendor/jquery.autoSuggest.custom.js
|
||||||
- public/javascripts/aspect-edit-pane.js
|
- public/javascripts/aspect-edit-pane.js
|
||||||
photos:
|
photos:
|
||||||
|
|
@ -87,6 +96,7 @@ stylesheets:
|
||||||
- public/stylesheets/ui.css
|
- public/stylesheets/ui.css
|
||||||
- public/stylesheets/lightbox.css
|
- public/stylesheets/lightbox.css
|
||||||
- public/stylesheets/autocomplete.css
|
- public/stylesheets/autocomplete.css
|
||||||
|
- public/stylesheets/mentions.css
|
||||||
- public/stylesheets/tags.css
|
- public/stylesheets/tags.css
|
||||||
- public/stylesheets/hovercard.css
|
- public/stylesheets/hovercard.css
|
||||||
- public/stylesheets/vendor/facebox.css
|
- public/stylesheets/vendor/facebox.css
|
||||||
|
|
|
||||||
|
|
@ -33,27 +33,10 @@ Then /^the publisher should be expanded$/ do
|
||||||
end
|
end
|
||||||
|
|
||||||
When /^I append "([^"]*)" to the publisher$/ do |stuff|
|
When /^I append "([^"]*)" to the publisher$/ do |stuff|
|
||||||
# Wait for the publisher to appear and all the elements to lay out
|
previous_value = page.find("#status_message_fake_text").value
|
||||||
wait_until { evaluate_script("$('#status_message_fake_text').focus().length == 1") }
|
fill_in "status_message_fake_text", :with => previous_value + " " + stuff
|
||||||
|
|
||||||
# Write to the placeholder field and trigger a keyup to start the copy
|
|
||||||
page.execute_script <<-JS
|
|
||||||
$('#status_message_fake_text').val($('#status_message_fake_text').val() + '#{stuff}');
|
|
||||||
$('#status_message_fake_text').keyup();
|
|
||||||
JS
|
|
||||||
|
|
||||||
# Wait until the text appears in the placeholder
|
|
||||||
wait_until do
|
wait_until do
|
||||||
evaluate_script("$('#status_message_fake_text').val().match(/#{stuff}/) != null")
|
page.find("#status_message_text").value.match(/#{stuff}/)
|
||||||
end
|
|
||||||
|
|
||||||
# WAIT FOR IT!...
|
|
||||||
|
|
||||||
# Wait until the text copy is finished
|
|
||||||
wait_until do
|
|
||||||
evaluate_script <<-JS
|
|
||||||
$('#status_message_text').val() && ($('#status_message_text').val().match(/#{stuff}/) != null)
|
|
||||||
JS
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -54,8 +54,7 @@ app.views.Publisher = Backbone.View.extend({
|
||||||
// close publishing area (CSS)
|
// close publishing area (CSS)
|
||||||
this.close();
|
this.close();
|
||||||
|
|
||||||
// clear mentions (TO BE REMOVED!!)
|
Publisher.clear()
|
||||||
Publisher.autocompletion.mentionList.clear()
|
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
},
|
},
|
||||||
|
|
|
||||||
26
public/javascripts/mentions.js
Normal file
26
public/javascripts/mentions.js
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
var Mentions = {
|
||||||
|
initialize: function(mentionsInput) {
|
||||||
|
mentionsInput.mentionsInput(Mentions.options);
|
||||||
|
Mentions.fetchContacts();
|
||||||
|
},
|
||||||
|
|
||||||
|
fetchContacts : function(){
|
||||||
|
$.getJSON($(".selected_contacts_link").attr("href"), function(data) {
|
||||||
|
Mentions.contacts = data;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
options: {
|
||||||
|
elastic: false,
|
||||||
|
|
||||||
|
onDataRequest: function(mode, query, callback) {
|
||||||
|
var filteredResults = _.filter(Mentions.contacts, function(item) { return item.name.toLowerCase().indexOf(query.toLowerCase()) > -1 });
|
||||||
|
|
||||||
|
callback.call(this, filteredResults);
|
||||||
|
},
|
||||||
|
|
||||||
|
templates: {
|
||||||
|
mentionItemSyntax: _.template("@{<%= mention.name %> ; <%= mention.handle %>}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -5,257 +5,22 @@
|
||||||
|
|
||||||
//TODO: make this a widget
|
//TODO: make this a widget
|
||||||
var Publisher = {
|
var Publisher = {
|
||||||
|
|
||||||
bookmarklet : false,
|
bookmarklet : false,
|
||||||
|
|
||||||
cachedForm : false,
|
|
||||||
form: function(){
|
form: function(){
|
||||||
if(!Publisher.cachedForm){
|
return Publisher.cachedForm = Publisher.cachedForm || $('#publisher');
|
||||||
Publisher.cachedForm = $('#publisher');
|
|
||||||
}
|
|
||||||
return Publisher.cachedForm;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
cachedInput : false,
|
|
||||||
input: function(){
|
input: function(){
|
||||||
if(!Publisher.cachedInput){
|
return Publisher.cachedInput = Publisher.cachedInput || Publisher.form().find('#status_message_fake_text');
|
||||||
Publisher.cachedInput = Publisher.form().find('#status_message_fake_text');
|
|
||||||
}
|
|
||||||
return Publisher.cachedInput;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
cachedHiddenInput : false,
|
|
||||||
hiddenInput: function(){
|
hiddenInput: function(){
|
||||||
if(!Publisher.cachedHiddenInput){
|
return Publisher.cachedHiddenInput= Publisher.cachedHiddenInput || Publisher.form().find('#status_message_text');
|
||||||
Publisher.cachedHiddenInput = Publisher.form().find('#status_message_text');
|
|
||||||
}
|
|
||||||
return Publisher.cachedHiddenInput;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
cachedSubmit : false,
|
|
||||||
submit: function(){
|
submit: function(){
|
||||||
if(!Publisher.cachedSubmit){
|
return Publisher.cachedSubmit = Publisher.cachedSubmit || Publisher.form().find('#status_message_submit');
|
||||||
Publisher.cachedSubmit = Publisher.form().find('#status_message_submit');
|
|
||||||
}
|
|
||||||
return Publisher.cachedSubmit;
|
|
||||||
},
|
|
||||||
|
|
||||||
autocompletion: {
|
|
||||||
options : function(){return {
|
|
||||||
minChars : 1,
|
|
||||||
max : 5,
|
|
||||||
onSelect : Publisher.autocompletion.onSelect,
|
|
||||||
searchTermFromValue: Publisher.autocompletion.searchTermFromValue,
|
|
||||||
scroll : false,
|
|
||||||
formatItem: function(row, i, max) {
|
|
||||||
return "<img src='"+ row.avatar +"' class='avatar'/>" + row.name;
|
|
||||||
},
|
|
||||||
formatMatch: function(row, i, max) {
|
|
||||||
return row.name;
|
|
||||||
},
|
|
||||||
formatResult: function(row) {
|
|
||||||
return row.name;
|
|
||||||
},
|
|
||||||
disableRightAndLeft : true
|
|
||||||
};},
|
|
||||||
hiddenMentionFromPerson : function(personData){
|
|
||||||
return "@{" + personData.name + "; " + personData.handle + "}";
|
|
||||||
},
|
|
||||||
|
|
||||||
onSelect : function(visibleInput, data, formatted) {
|
|
||||||
var visibleCursorIndex = visibleInput[0].selectionStart;
|
|
||||||
var visibleLoc = Publisher.autocompletion.addMentionToInput(visibleInput, visibleCursorIndex, formatted);
|
|
||||||
$.Autocompleter.Selection(visibleInput[0], visibleLoc[1], visibleLoc[1]);
|
|
||||||
|
|
||||||
var mentionString = Publisher.autocompletion.hiddenMentionFromPerson(data);
|
|
||||||
var mention = { visibleStart: visibleLoc[0],
|
|
||||||
visibleEnd : visibleLoc[1],
|
|
||||||
mentionString : mentionString
|
|
||||||
};
|
|
||||||
Publisher.autocompletion.mentionList.push(mention);
|
|
||||||
Publisher.oldInputContent = visibleInput.val();
|
|
||||||
Publisher.hiddenInput().val(Publisher.autocompletion.mentionList.generateHiddenInput(visibleInput.val()));
|
|
||||||
},
|
|
||||||
|
|
||||||
mentionList : {
|
|
||||||
mentions : [],
|
|
||||||
sortedMentions : function(){
|
|
||||||
return this.mentions.sort(function(m1, m2){
|
|
||||||
if(m1.visibleStart > m2.visibleStart){
|
|
||||||
return -1;
|
|
||||||
} else if(m1.visibleStart < m2.visibleStart){
|
|
||||||
return 1;
|
|
||||||
} else {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
push : function(mention){
|
|
||||||
this.mentions.push(mention);
|
|
||||||
},
|
|
||||||
generateHiddenInput : function(visibleString){
|
|
||||||
var resultString = visibleString;
|
|
||||||
for(var i in this.sortedMentions()){
|
|
||||||
var mention = this.mentions[i];
|
|
||||||
var start = resultString.slice(0, mention.visibleStart);
|
|
||||||
var insertion = mention.mentionString;
|
|
||||||
var end = resultString.slice(mention.visibleEnd);
|
|
||||||
|
|
||||||
resultString = start + insertion + end;
|
|
||||||
}
|
|
||||||
return resultString;
|
|
||||||
},
|
|
||||||
|
|
||||||
insertionAt : function(insertionStartIndex, selectionEnd, keyCode){
|
|
||||||
if(insertionStartIndex != selectionEnd){
|
|
||||||
this.selectionDeleted(insertionStartIndex, selectionEnd);
|
|
||||||
}
|
|
||||||
this.updateMentionLocations(insertionStartIndex, 1);
|
|
||||||
this.destroyMentionAt(insertionStartIndex);
|
|
||||||
},
|
|
||||||
deletionAt : function(selectionStart, selectionEnd, keyCode){
|
|
||||||
if(selectionStart != selectionEnd){
|
|
||||||
this.selectionDeleted(selectionStart, selectionEnd);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var effectiveCursorIndex;
|
|
||||||
if(keyCode == KEYCODES.DEL){
|
|
||||||
effectiveCursorIndex = selectionStart;
|
|
||||||
}else{
|
|
||||||
effectiveCursorIndex = selectionStart - 1;
|
|
||||||
}
|
|
||||||
this.updateMentionLocations(effectiveCursorIndex, -1);
|
|
||||||
this.destroyMentionAt(effectiveCursorIndex);
|
|
||||||
},
|
|
||||||
selectionDeleted : function(selectionStart, selectionEnd){
|
|
||||||
Publisher.autocompletion.mentionList.destroyMentionsWithin(selectionStart, selectionEnd);
|
|
||||||
Publisher.autocompletion.mentionList.updateMentionLocations(selectionStart, selectionStart - selectionEnd);
|
|
||||||
},
|
|
||||||
destroyMentionsWithin : function(start, end){
|
|
||||||
for (var i = this.mentions.length - 1; i >= 0; i--){
|
|
||||||
var mention = this.mentions[i];
|
|
||||||
if(start < mention.visibleEnd && end >= mention.visibleStart){
|
|
||||||
this.mentions.splice(i, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
clear: function(){
|
|
||||||
this.mentions = [];
|
|
||||||
},
|
|
||||||
destroyMentionAt : function(effectiveCursorIndex){
|
|
||||||
|
|
||||||
var mentionIndex = this.mentionAt(effectiveCursorIndex);
|
|
||||||
var mention = this.mentions[mentionIndex];
|
|
||||||
if(mention){
|
|
||||||
this.mentions.splice(mentionIndex, 1);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
updateMentionLocations : function(effectiveCursorIndex, offset){
|
|
||||||
var changedMentions = this.mentionsAfter(effectiveCursorIndex);
|
|
||||||
for(var i in changedMentions){
|
|
||||||
var mention = changedMentions[i];
|
|
||||||
mention.visibleStart += offset;
|
|
||||||
mention.visibleEnd += offset;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mentionAt : function(visibleCursorIndex){
|
|
||||||
for(var i in this.mentions){
|
|
||||||
var mention = this.mentions[i];
|
|
||||||
if(visibleCursorIndex > mention.visibleStart && visibleCursorIndex < mention.visibleEnd){
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
mentionsAfter : function(visibleCursorIndex){
|
|
||||||
var resultMentions = [];
|
|
||||||
for(var i in this.mentions){
|
|
||||||
var mention = this.mentions[i];
|
|
||||||
if(visibleCursorIndex <= mention.visibleStart){
|
|
||||||
resultMentions.push(mention);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return resultMentions;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
repopulateHiddenInput: function(){
|
|
||||||
var newHiddenVal = Publisher.autocompletion.mentionList.generateHiddenInput(Publisher.input().val());
|
|
||||||
if(newHiddenVal != Publisher.hiddenInput().val()){
|
|
||||||
Publisher.hiddenInput().val(newHiddenVal);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
keyUpHandler : function(event){
|
|
||||||
Publisher.autocompletion.repopulateHiddenInput();
|
|
||||||
Publisher.determineSubmitAvailability();
|
|
||||||
},
|
|
||||||
|
|
||||||
keyDownHandler : function(event){
|
|
||||||
var input = Publisher.input();
|
|
||||||
var selectionStart = input[0].selectionStart;
|
|
||||||
var selectionEnd = input[0].selectionEnd;
|
|
||||||
var isDeletion = (event.keyCode == KEYCODES.DEL && selectionStart < input.val().length) || (event.keyCode == KEYCODES.BACKSPACE && (selectionStart > 0 || selectionStart != selectionEnd));
|
|
||||||
var isInsertion = (KEYCODES.isInsertion(event.keyCode) && event.keyCode != KEYCODES.RETURN );
|
|
||||||
|
|
||||||
if(isDeletion){
|
|
||||||
Publisher.autocompletion.mentionList.deletionAt(selectionStart, selectionEnd, event.keyCode);
|
|
||||||
}else if(isInsertion){
|
|
||||||
Publisher.autocompletion.mentionList.insertionAt(selectionStart, selectionEnd, event.keyCode);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
addMentionToInput: function(input, cursorIndex, formatted){
|
|
||||||
var inputContent = input.val();
|
|
||||||
|
|
||||||
var stringLoc = Publisher.autocompletion.findStringToReplace(inputContent, cursorIndex);
|
|
||||||
|
|
||||||
var stringStart = inputContent.slice(0, stringLoc[0]);
|
|
||||||
var stringEnd = inputContent.slice(stringLoc[1]);
|
|
||||||
|
|
||||||
input.val(stringStart + formatted + stringEnd);
|
|
||||||
var offset = formatted.length - (stringLoc[1] - stringLoc[0]);
|
|
||||||
Publisher.autocompletion.mentionList.updateMentionLocations(stringStart.length, offset);
|
|
||||||
return [stringStart.length, stringStart.length + formatted.length];
|
|
||||||
},
|
|
||||||
|
|
||||||
findStringToReplace: function(value, cursorIndex){
|
|
||||||
var atLocation = value.lastIndexOf('@', cursorIndex);
|
|
||||||
if(atLocation == -1){return [0,0];}
|
|
||||||
var nextAt = cursorIndex;
|
|
||||||
|
|
||||||
if(nextAt == -1){nextAt = value.length;}
|
|
||||||
return [atLocation, nextAt];
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
searchTermFromValue: function(value, cursorIndex) {
|
|
||||||
var stringLoc = Publisher.autocompletion.findStringToReplace(value, cursorIndex);
|
|
||||||
if(stringLoc[0] <= 2){
|
|
||||||
stringLoc[0] = 0;
|
|
||||||
}else{
|
|
||||||
stringLoc[0] -= 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
var relevantString = value.slice(stringLoc[0], stringLoc[1]).replace(/\s+$/,"");
|
|
||||||
|
|
||||||
var matches = relevantString.match(/(^|\s)@(.+)/);
|
|
||||||
if(matches){
|
|
||||||
return matches[2];
|
|
||||||
}else{
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
},
|
|
||||||
initialize: function(){
|
|
||||||
$.getJSON($("#publisher .selected_contacts_link").attr("href"), undefined ,
|
|
||||||
function(data){
|
|
||||||
Publisher.input().autocomplete(data,
|
|
||||||
Publisher.autocompletion.options());
|
|
||||||
Publisher.input().result(Publisher.autocompletion.selectItemCallback);
|
|
||||||
Publisher.oldInputContent = Publisher.input().val();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
determineSubmitAvailability: function(){
|
determineSubmitAvailability: function(){
|
||||||
|
|
@ -271,7 +36,11 @@ var Publisher = {
|
||||||
},
|
},
|
||||||
|
|
||||||
clear: function(){
|
clear: function(){
|
||||||
this.autocompletion.mentionList.clear();
|
$("#photodropzone").find('li').remove();
|
||||||
|
Publisher.input()
|
||||||
|
.removeClass("with_attachments")
|
||||||
|
.css('paddingBottom', '')
|
||||||
|
.mentionsInput("reset");
|
||||||
},
|
},
|
||||||
|
|
||||||
bindServiceIcons: function(){
|
bindServiceIcons: function(){
|
||||||
|
|
@ -380,59 +149,12 @@ var Publisher = {
|
||||||
Publisher.toggleAspectIds(li);
|
Publisher.toggleAspectIds(li);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
beforeSubmit: function(){
|
|
||||||
if($("#publisher .content_creation form #aspect_ids_").length == 0){
|
|
||||||
alert(Diaspora.I18n.t('publisher.at_least_one_aspect'));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onSubmit: function(data, json, xhr){
|
|
||||||
$("#photodropzone").find('li').remove();
|
|
||||||
$("#publisher textarea").removeClass("with_attachments").css('paddingBottom', '');
|
|
||||||
},
|
|
||||||
onFailure: function(data, json, xhr){
|
|
||||||
json = $.parseJSON(json.responseText);
|
|
||||||
if(json.errors.length !== 0){
|
|
||||||
Diaspora.Alert.show(json.errors);
|
|
||||||
}else{
|
|
||||||
Diaspora.Alert.show(Diaspora.I18n.t('failed_to_post_message'));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onSuccess: function(data, json, xhr){
|
|
||||||
if (Publisher.bookmarklet == false) {
|
|
||||||
var isPostVisible = Diaspora.page.aspectNavigation.selectedAspects().length == 0;
|
|
||||||
var postedTo = Publisher.selectedAspectIds();
|
|
||||||
|
|
||||||
|
keyUp : function(){
|
||||||
if(Publisher.isPublicPost() || Publisher.isToAllAspects()){
|
Publisher.determineSubmitAvailability()
|
||||||
isPostVisible = true;
|
Publisher.input().mentionsInput("val", function(value) {
|
||||||
|
Publisher.hiddenInput().val(value);
|
||||||
} else {
|
});
|
||||||
$.each(Diaspora.page.aspectNavigation.selectedAspects(), function(index, value) {
|
|
||||||
if (postedTo.indexOf(parseInt(value)) > -1)
|
|
||||||
isPostVisible = true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if(isPostVisible) {
|
|
||||||
Diaspora.page.stream.addPost($("#" + json.post_id));
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
Diaspora.widgets.flashMessages.render({
|
|
||||||
success: true,
|
|
||||||
message: Diaspora.I18n.t('successfully_posted_message_to_an_aspects_that_is_not_visible')
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//Stream.setUpImageLinks();
|
|
||||||
Stream.setUpAudioLinks();
|
|
||||||
},
|
|
||||||
bindAjax: function(){
|
|
||||||
//Publisher.form().bind('submit', Publisher.beforeSubmit);
|
|
||||||
Publisher.form().bind('ajax:loading', Publisher.onSubmit);
|
|
||||||
Publisher.form().bind('ajax:failure', Publisher.onFailure);
|
|
||||||
Publisher.form().bind('ajax:success', Publisher.onSuccess);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
triggerGettingStarted: function(){
|
triggerGettingStarted: function(){
|
||||||
|
|
@ -473,16 +195,22 @@ var Publisher = {
|
||||||
Publisher.bindServiceIcons();
|
Publisher.bindServiceIcons();
|
||||||
Publisher.bindAspectToggles();
|
Publisher.bindAspectToggles();
|
||||||
|
|
||||||
Publisher.autocompletion.initialize();
|
/* close text area */
|
||||||
|
Publisher.form().delegate("#hide_publisher", "click", function(){
|
||||||
|
$.each(Publisher.form().find("textarea"), function(idx, element){
|
||||||
|
$(element).val("");
|
||||||
|
});
|
||||||
|
Publisher.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
Mentions.initialize(Publisher.input());
|
||||||
|
|
||||||
if(Publisher.hiddenInput().val() === "") {
|
if(Publisher.hiddenInput().val() === "") {
|
||||||
Publisher.hiddenInput().val(Publisher.input().val());
|
Publisher.hiddenInput().val(Publisher.input().val());
|
||||||
}
|
}
|
||||||
|
|
||||||
Publisher.input().autoResize({'extraSpace' : 10});
|
Publisher.input().autoResize({'extraSpace' : 10});
|
||||||
Publisher.input().keydown(Publisher.autocompletion.keyDownHandler);
|
Publisher.input().keyup(Publisher.keyUp)
|
||||||
Publisher.input().keyup(Publisher.autocompletion.keyUpHandler);
|
|
||||||
Publisher.input().mouseup(Publisher.autocompletion.keyUpHandler);
|
|
||||||
//Publisher.bindAjax();
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
132
public/javascripts/vendor/jquery.events.input.js
generated
vendored
Normal file
132
public/javascripts/vendor/jquery.events.input.js
generated
vendored
Normal file
|
|
@ -0,0 +1,132 @@
|
||||||
|
/*
|
||||||
|
jQuery `input` special event v1.0
|
||||||
|
|
||||||
|
http://whattheheadsaid.com/projects/input-special-event
|
||||||
|
|
||||||
|
(c) 2010-2011 Andy Earnshaw
|
||||||
|
MIT license
|
||||||
|
www.opensource.org/licenses/mit-license.php
|
||||||
|
|
||||||
|
Modified by Kenneth Auchenberg
|
||||||
|
* Disabled usage of onPropertyChange event in IE, since its a bit delayed, if you type really fast.
|
||||||
|
*/
|
||||||
|
|
||||||
|
(function($) {
|
||||||
|
// Handler for propertychange events only
|
||||||
|
function propHandler() {
|
||||||
|
var $this = $(this);
|
||||||
|
if (window.event.propertyName == "value" && !$this.data("triggering.inputEvent")) {
|
||||||
|
$this.data("triggering.inputEvent", true).trigger("input");
|
||||||
|
window.setTimeout(function () {
|
||||||
|
$this.data("triggering.inputEvent", false);
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$.event.special.input = {
|
||||||
|
setup: function(data, namespaces) {
|
||||||
|
var timer,
|
||||||
|
// Get a reference to the element
|
||||||
|
elem = this,
|
||||||
|
// Store the current state of the element
|
||||||
|
state = elem.value,
|
||||||
|
// Create a dummy element that we can use for testing event support
|
||||||
|
tester = document.createElement(this.tagName),
|
||||||
|
// Check for native oninput
|
||||||
|
oninput = "oninput" in tester || checkEvent(tester),
|
||||||
|
// Check for onpropertychange
|
||||||
|
onprop = "onpropertychange" in tester,
|
||||||
|
// Generate a random namespace for event bindings
|
||||||
|
ns = "inputEventNS" + ~~(Math.random() * 10000000),
|
||||||
|
// Last resort event names
|
||||||
|
evts = ["focus", "blur", "paste", "cut", "keydown", "drop", ""].join("." + ns + " ");
|
||||||
|
|
||||||
|
function checkState() {
|
||||||
|
var $this = $(elem);
|
||||||
|
if (elem.value != state && !$this.data("triggering.inputEvent")) {
|
||||||
|
state = elem.value;
|
||||||
|
|
||||||
|
$this.data("triggering.inputEvent", true).trigger("input");
|
||||||
|
window.setTimeout(function () {
|
||||||
|
$this.data("triggering.inputEvent", false);
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up a function to handle the different events that may fire
|
||||||
|
function handler(e) {
|
||||||
|
// When focusing, set a timer that polls for changes to the value
|
||||||
|
if (e.type == "focus") {
|
||||||
|
checkState();
|
||||||
|
clearInterval(timer);
|
||||||
|
timer = window.setInterval(checkState, 250);
|
||||||
|
} else if (e.type == "blur") {
|
||||||
|
// When blurring, cancel the aforeset timer
|
||||||
|
window.clearInterval(timer);
|
||||||
|
} else {
|
||||||
|
// For all other events, queue a timer to check state ASAP
|
||||||
|
window.setTimeout(checkState, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bind to native event if available
|
||||||
|
if (oninput) {
|
||||||
|
return false;
|
||||||
|
// } else if (onprop) {
|
||||||
|
// // Else fall back to propertychange if available
|
||||||
|
// $(this).find("input, textarea").andSelf().filter("input, textarea").bind("propertychange." + ns, propHandler);
|
||||||
|
} else {
|
||||||
|
// Else clutch at straws!
|
||||||
|
$(this).find("input, textarea").andSelf().filter("input, textarea").bind(evts, handler);
|
||||||
|
}
|
||||||
|
$(this).data("inputEventHandlerNS", ns);
|
||||||
|
},
|
||||||
|
teardown: function () {
|
||||||
|
var elem = $(this);
|
||||||
|
elem.find("input, textarea").unbind(elem.data("inputEventHandlerNS"));
|
||||||
|
elem.data("inputEventHandlerNS", "");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Setup our jQuery shorthand method
|
||||||
|
$.fn.input = function (handler) {
|
||||||
|
return handler ? this.bind("input", handler) : this.trigger("input");
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
The following function tests the element for oninput support in Firefox. Many thanks to
|
||||||
|
http://blog.danielfriesen.name/2010/02/16/html5-browser-maze-oninput-support/
|
||||||
|
*/
|
||||||
|
function checkEvent(el) {
|
||||||
|
// First check, for if Firefox fixes its issue with el.oninput = function
|
||||||
|
el.setAttribute("oninput", "return");
|
||||||
|
if (typeof el.oninput == "function") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// Second check, because Firefox doesn't map oninput attribute to oninput property
|
||||||
|
try {
|
||||||
|
|
||||||
|
// "* Note * : Disabled focus and dispatch of keypress event due to conflict with DOMready, which resulted in scrolling down to the bottom of the page, possibly because layout wasn't finished rendering.
|
||||||
|
var e = document.createEvent("KeyboardEvent"),
|
||||||
|
ok = false,
|
||||||
|
tester = function(e) {
|
||||||
|
ok = true;
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
};
|
||||||
|
|
||||||
|
// e.initKeyEvent("keypress", true, true, window, false, false, false, false, 0, "e".charCodeAt(0));
|
||||||
|
|
||||||
|
document.body.appendChild(el);
|
||||||
|
el.addEventListener("input", tester, false);
|
||||||
|
// el.focus();
|
||||||
|
// el.dispatchEvent(e);
|
||||||
|
el.removeEventListener("input", tester, false);
|
||||||
|
document.body.removeChild(el);
|
||||||
|
return ok;
|
||||||
|
|
||||||
|
} catch(error) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})(jQuery);
|
||||||
387
public/javascripts/vendor/jquery.mentionsInput.js
generated
vendored
Normal file
387
public/javascripts/vendor/jquery.mentionsInput.js
generated
vendored
Normal file
|
|
@ -0,0 +1,387 @@
|
||||||
|
/*
|
||||||
|
* Mentions Input
|
||||||
|
* Version 1.0
|
||||||
|
* Written by: Kenneth Auchenberg (Podio)
|
||||||
|
*
|
||||||
|
* Using underscore.js
|
||||||
|
*
|
||||||
|
* License: MIT License - http://www.opensource.org/licenses/mit-license.php
|
||||||
|
*/
|
||||||
|
|
||||||
|
(function ($, _, undefined) {
|
||||||
|
|
||||||
|
// Settings
|
||||||
|
var KEY = { BACKSPACE : 8, TAB : 9, RETURN : 13, ESC : 27, LEFT : 37, UP : 38, RIGHT : 39, DOWN : 40, COMMA : 188, SPACE : 32, HOME : 36, END : 35 }; // Keys "enum"
|
||||||
|
var defaultSettings = {
|
||||||
|
triggerChar : '@',
|
||||||
|
onDataRequest : $.noop,
|
||||||
|
minChars : 2,
|
||||||
|
showAvatars : true,
|
||||||
|
elastic : true,
|
||||||
|
classes : {
|
||||||
|
autoCompleteItemActive : "active"
|
||||||
|
},
|
||||||
|
templates : {
|
||||||
|
wrapper : _.template('<div class="mentions-input-box"></div>'),
|
||||||
|
autocompleteList : _.template('<div class="mentions-autocomplete-list"></div>'),
|
||||||
|
autocompleteListItem : _.template('<li data-ref-id="<%= id %>" data-ref-type="<%= type %>" data-display="<%= display %>"><%= content %></li>'),
|
||||||
|
autocompleteListItemAvatar : _.template('<img src="<%= avatar %>" />'),
|
||||||
|
autocompleteListItemIcon : _.template('<div class="icon <%= icon %>"></div>'),
|
||||||
|
mentionsOverlay : _.template('<div class="mentions"><div></div></div>'),
|
||||||
|
mentionItemSyntax : _.template('@[<%= name %>](<%= type %>:<%= id %>)'),
|
||||||
|
mentionItemHighlight : _.template('<strong><span><%= name %></span></strong>')
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var utils = {
|
||||||
|
htmlEncode : function (str) {
|
||||||
|
return _.escape(str);
|
||||||
|
},
|
||||||
|
highlightTerm : function (value, term) {
|
||||||
|
if (!term && !term.length) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
return value.replace(new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + term + ")(?![^<>]*>)(?![^&;]+;)", "gi"), "<b>$1</b>");
|
||||||
|
},
|
||||||
|
setCaratPosition : function (domNode, caretPos) {
|
||||||
|
if (domNode.createTextRange) {
|
||||||
|
var range = domNode.createTextRange();
|
||||||
|
range.move('character', caretPos);
|
||||||
|
range.select();
|
||||||
|
} else {
|
||||||
|
if (domNode.selectionStart) {
|
||||||
|
domNode.focus();
|
||||||
|
domNode.setSelectionRange(caretPos, caretPos);
|
||||||
|
} else {
|
||||||
|
domNode.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var MentionsInput = function (input) {
|
||||||
|
var settings;
|
||||||
|
var elmInputBox, elmInputWrapper, elmAutocompleteList, elmWrapperBox, elmMentionsOverlay, elmActiveAutoCompleteItem;
|
||||||
|
var mentionsCollection = [];
|
||||||
|
var inputBuffer = [];
|
||||||
|
var currentDataQuery = '';
|
||||||
|
|
||||||
|
function initTextarea() {
|
||||||
|
elmInputBox = $(input);
|
||||||
|
|
||||||
|
if (elmInputBox.attr('data-mentions-input') == 'true') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
elmInputWrapper = elmInputBox.parent();
|
||||||
|
elmWrapperBox = $(settings.templates.wrapper());
|
||||||
|
elmInputBox.wrapAll(elmWrapperBox);
|
||||||
|
elmWrapperBox = elmInputWrapper.find('> div');
|
||||||
|
|
||||||
|
elmInputBox.attr('data-mentions-input', 'true');
|
||||||
|
elmInputBox.bind('keydown', onInputBoxKeyDown);
|
||||||
|
elmInputBox.bind('keypress', onInputBoxKeyPress);
|
||||||
|
elmInputBox.bind('input', onInputBoxInput);
|
||||||
|
elmInputBox.bind('click', onInputBoxClick);
|
||||||
|
|
||||||
|
if (settings.elastic) {
|
||||||
|
elmInputBox.elastic();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function initAutocomplete() {
|
||||||
|
elmAutocompleteList = $(settings.templates.autocompleteList());
|
||||||
|
elmAutocompleteList.appendTo(elmWrapperBox);
|
||||||
|
elmAutocompleteList.delegate('li', 'click', onAutoCompleteItemClick);
|
||||||
|
}
|
||||||
|
|
||||||
|
function initMentionsOverlay() {
|
||||||
|
elmMentionsOverlay = $(settings.templates.mentionsOverlay());
|
||||||
|
elmMentionsOverlay.prependTo(elmWrapperBox);
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateNames() {
|
||||||
|
var syntaxMessage = getInputBoxValue();
|
||||||
|
|
||||||
|
_.each(mentionsCollection, function (mention) {
|
||||||
|
var textSyntax = settings.templates.mentionItemSyntax({ name : mention.name, type : 'contact', id : mention.id, mention: mention });
|
||||||
|
|
||||||
|
syntaxMessage = syntaxMessage.replace(mention.name, textSyntax);
|
||||||
|
});
|
||||||
|
|
||||||
|
var mentionText = utils.htmlEncode(syntaxMessage);
|
||||||
|
|
||||||
|
_.each(mentionsCollection, function (mention) {
|
||||||
|
var textSyntax = settings.templates.mentionItemSyntax({ name : utils.htmlEncode(mention.name), type : 'contact', id : mention.id, mention : mention });
|
||||||
|
var textHighlight = settings.templates.mentionItemHighlight({ name : utils.htmlEncode(mention.name), mention : mention });
|
||||||
|
|
||||||
|
mentionText = mentionText.replace(textSyntax, textHighlight);
|
||||||
|
});
|
||||||
|
|
||||||
|
mentionText = mentionText.replace(/\n/g, '<br />');
|
||||||
|
mentionText = mentionText.replace(/ {2}/g, ' ');
|
||||||
|
|
||||||
|
elmInputBox.data('messageText', syntaxMessage);
|
||||||
|
elmMentionsOverlay.find('div').html(mentionText);
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetBuffer() {
|
||||||
|
inputBuffer = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateMentionsCollection() {
|
||||||
|
var inputText = getInputBoxValue();
|
||||||
|
|
||||||
|
mentionsCollection = _.reject(mentionsCollection, function (mention, index) {
|
||||||
|
return !mention.name || inputText.indexOf(mention.name) == -1;
|
||||||
|
});
|
||||||
|
mentionsCollection = _.compact(mentionsCollection);
|
||||||
|
}
|
||||||
|
|
||||||
|
function addMention(mention) {
|
||||||
|
var currentMessage = getInputBoxValue();
|
||||||
|
|
||||||
|
// Using a regex to figure out positions
|
||||||
|
var regex = new RegExp("\\" + settings.triggerChar + currentDataQuery, "gi");
|
||||||
|
regex.exec(currentMessage);
|
||||||
|
|
||||||
|
var startCaretPosition = regex.lastIndex - currentDataQuery.length - 1;
|
||||||
|
var currentCaretPosition = regex.lastIndex;
|
||||||
|
|
||||||
|
var start = currentMessage.substr(0, startCaretPosition);
|
||||||
|
var end = currentMessage.substr(currentCaretPosition, currentMessage.length);
|
||||||
|
var startEndIndex = (start + mention.name).length;
|
||||||
|
|
||||||
|
var updatedMessageText = start + mention.name + end;
|
||||||
|
|
||||||
|
mentionsCollection.push(mention);
|
||||||
|
|
||||||
|
// Cleaning before inserting the value, otherwise auto-complete would be triggered with "old" inputbuffer
|
||||||
|
resetBuffer();
|
||||||
|
currentDataQuery = '';
|
||||||
|
hideAutoComplete();
|
||||||
|
|
||||||
|
// Mentions & syntax message
|
||||||
|
elmInputBox.val(updatedMessageText);
|
||||||
|
updateNames();
|
||||||
|
|
||||||
|
// Set correct focus and selection
|
||||||
|
elmInputBox.focus();
|
||||||
|
utils.setCaratPosition(elmInputBox[0], startEndIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getInputBoxValue() {
|
||||||
|
return $.trim(elmInputBox.val());
|
||||||
|
}
|
||||||
|
|
||||||
|
function onAutoCompleteItemClick(e) {
|
||||||
|
var mention = $(this).data("mention");
|
||||||
|
|
||||||
|
addMention(mention);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function onInputBoxClick(e) {
|
||||||
|
resetBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
function onInputBoxInput(e) {
|
||||||
|
updateNames();
|
||||||
|
updateMentionsCollection();
|
||||||
|
hideAutoComplete();
|
||||||
|
|
||||||
|
var triggerCharIndex = _.lastIndexOf(inputBuffer, settings.triggerChar);
|
||||||
|
if (triggerCharIndex > -1) {
|
||||||
|
currentDataQuery = inputBuffer.slice(triggerCharIndex + 1).join('');
|
||||||
|
|
||||||
|
_.defer(_.bind(doSearch, this, currentDataQuery));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onInputBoxKeyPress(e) {
|
||||||
|
var typedValue = String.fromCharCode(e.which || e.keyCode);
|
||||||
|
inputBuffer.push(typedValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onInputBoxKeyDown(e) {
|
||||||
|
|
||||||
|
// This also matches HOME/END on OSX which is CMD+LEFT, CMD+RIGHT
|
||||||
|
if (e.keyCode == KEY.LEFT || e.keyCode == KEY.RIGHT || e.keyCode == KEY.HOME || e.keyCode == KEY.END) {
|
||||||
|
// Defer execution to ensure carat pos has changed after HOME/END keys
|
||||||
|
_.defer(resetBuffer);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.keyCode == KEY.BACKSPACE) {
|
||||||
|
inputBuffer = inputBuffer.slice(0, -1 + inputBuffer.length); // Can't use splice, not available in IE
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!elmAutocompleteList.is(':visible')) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (e.keyCode) {
|
||||||
|
case KEY.UP:
|
||||||
|
case KEY.DOWN:
|
||||||
|
var elmCurrentAutoCompleteItem = null;
|
||||||
|
if (e.keyCode == KEY.DOWN) {
|
||||||
|
if (elmActiveAutoCompleteItem && elmActiveAutoCompleteItem.length) {
|
||||||
|
elmCurrentAutoCompleteItem = elmActiveAutoCompleteItem.next();
|
||||||
|
} else {
|
||||||
|
elmCurrentAutoCompleteItem = elmAutocompleteList.find('li').first();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
elmCurrentAutoCompleteItem = $(elmActiveAutoCompleteItem).prev();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (elmCurrentAutoCompleteItem.length) {
|
||||||
|
selectAutoCompleteItem(elmCurrentAutoCompleteItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
|
||||||
|
case KEY.RETURN:
|
||||||
|
case KEY.TAB:
|
||||||
|
if (elmActiveAutoCompleteItem && elmActiveAutoCompleteItem.length) {
|
||||||
|
elmActiveAutoCompleteItem.click();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function hideAutoComplete() {
|
||||||
|
elmActiveAutoCompleteItem = null;
|
||||||
|
elmAutocompleteList.empty().hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectAutoCompleteItem(elmItem) {
|
||||||
|
elmItem.addClass(settings.classes.autoCompleteItemActive);
|
||||||
|
elmItem.siblings().removeClass(settings.classes.autoCompleteItemActive);
|
||||||
|
|
||||||
|
elmActiveAutoCompleteItem = elmItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
function populateDropdown(query, results) {
|
||||||
|
elmAutocompleteList.show();
|
||||||
|
|
||||||
|
// Filter items that has already been mentioned
|
||||||
|
var mentionedNames = _.pluck(mentionsCollection, 'name');
|
||||||
|
results = _.reject(results, function (item) {
|
||||||
|
return _.include(mentionedNames, item.name);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!results.length) {
|
||||||
|
hideAutoComplete();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
elmAutocompleteList.empty();
|
||||||
|
var elmDropDownList = $("<ul>").appendTo(elmAutocompleteList).hide();
|
||||||
|
|
||||||
|
_.each(results, function (item, index) {
|
||||||
|
var elmListItem = $(settings.templates.autocompleteListItem({
|
||||||
|
'id' : utils.htmlEncode(item.id),
|
||||||
|
'display' : utils.htmlEncode(item.name),
|
||||||
|
'type' : utils.htmlEncode(item.type),
|
||||||
|
'content' : utils.highlightTerm(utils.htmlEncode((item.name)), query)
|
||||||
|
})).data('mention', item);
|
||||||
|
|
||||||
|
if (index === 0) {
|
||||||
|
selectAutoCompleteItem(elmListItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (settings.showAvatars) {
|
||||||
|
var elmIcon;
|
||||||
|
|
||||||
|
if (item.avatar) {
|
||||||
|
elmIcon = $(settings.templates.autocompleteListItemAvatar({ avatar : item.avatar }));
|
||||||
|
} else {
|
||||||
|
elmIcon = $(settings.templates.autocompleteListItemIcon({ icon : item.icon }));
|
||||||
|
}
|
||||||
|
elmIcon.prependTo(elmListItem);
|
||||||
|
}
|
||||||
|
elmListItem = elmListItem.appendTo(elmDropDownList);
|
||||||
|
});
|
||||||
|
|
||||||
|
elmAutocompleteList.show();
|
||||||
|
elmDropDownList.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
function doSearch(query) {
|
||||||
|
if (query && query.length && query.length >= settings.minChars) {
|
||||||
|
settings.onDataRequest.call(this, 'search', query, function (responseData) {
|
||||||
|
populateDropdown(query, responseData);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Public methods
|
||||||
|
return {
|
||||||
|
init : function (options) {
|
||||||
|
settings = options;
|
||||||
|
|
||||||
|
initTextarea();
|
||||||
|
initAutocomplete();
|
||||||
|
initMentionsOverlay();
|
||||||
|
|
||||||
|
if(options.prefillMention) {
|
||||||
|
addMention(options.prefillMention);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
val : function (callback) {
|
||||||
|
if (!_.isFunction(callback)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var value = mentionsCollection.length ? elmInputBox.data('messageText') : getInputBoxValue();
|
||||||
|
callback.call(this, value);
|
||||||
|
},
|
||||||
|
|
||||||
|
reset : function () {
|
||||||
|
elmInputBox.val('');
|
||||||
|
mentionsCollection = [];
|
||||||
|
updateNames();
|
||||||
|
},
|
||||||
|
|
||||||
|
getMentions : function (callback) {
|
||||||
|
if (!_.isFunction(callback)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
callback.call(this, mentionsCollection);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
$.fn.mentionsInput = function (method, settings) {
|
||||||
|
|
||||||
|
if (typeof method === 'object' || !method) {
|
||||||
|
settings = $.extend(true, {}, defaultSettings, method);
|
||||||
|
}
|
||||||
|
|
||||||
|
var outerArguments = arguments;
|
||||||
|
|
||||||
|
return this.each(function () {
|
||||||
|
var instance = $.data(this, 'mentionsInput') || $.data(this, 'mentionsInput', new MentionsInput(this));
|
||||||
|
|
||||||
|
if (_.isFunction(instance[method])) {
|
||||||
|
return instance[method].apply(this, Array.prototype.slice.call(outerArguments, 1));
|
||||||
|
|
||||||
|
} else if (typeof method === 'object' || !method) {
|
||||||
|
return instance.init.call(this, settings);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
$.error('Method ' + method + ' does not exist');
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
})(jQuery, _);
|
||||||
92
public/javascripts/vendor/underscore.js
vendored
92
public/javascripts/vendor/underscore.js
vendored
|
|
@ -1,5 +1,5 @@
|
||||||
// Underscore.js 1.2.2
|
// Underscore.js 1.2.4
|
||||||
// (c) 2011 Jeremy Ashkenas, DocumentCloud Inc.
|
// (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc.
|
||||||
// Underscore is freely distributable under the MIT license.
|
// Underscore is freely distributable under the MIT license.
|
||||||
// Portions of Underscore are inspired or borrowed from Prototype,
|
// Portions of Underscore are inspired or borrowed from Prototype,
|
||||||
// Oliver Steele's Functional, and John Resig's Micro-Templating.
|
// Oliver Steele's Functional, and John Resig's Micro-Templating.
|
||||||
|
|
@ -67,7 +67,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
// Current version.
|
// Current version.
|
||||||
_.VERSION = '1.2.2';
|
_.VERSION = '1.2.4';
|
||||||
|
|
||||||
// Collection Functions
|
// Collection Functions
|
||||||
// --------------------
|
// --------------------
|
||||||
|
|
@ -101,13 +101,14 @@
|
||||||
each(obj, function(value, index, list) {
|
each(obj, function(value, index, list) {
|
||||||
results[results.length] = iterator.call(context, value, index, list);
|
results[results.length] = iterator.call(context, value, index, list);
|
||||||
});
|
});
|
||||||
|
if (obj.length === +obj.length) results.length = obj.length;
|
||||||
return results;
|
return results;
|
||||||
};
|
};
|
||||||
|
|
||||||
// **Reduce** builds up a single result from a list of values, aka `inject`,
|
// **Reduce** builds up a single result from a list of values, aka `inject`,
|
||||||
// or `foldl`. Delegates to **ECMAScript 5**'s native `reduce` if available.
|
// or `foldl`. Delegates to **ECMAScript 5**'s native `reduce` if available.
|
||||||
_.reduce = _.foldl = _.inject = function(obj, iterator, memo, context) {
|
_.reduce = _.foldl = _.inject = function(obj, iterator, memo, context) {
|
||||||
var initial = memo !== void 0;
|
var initial = arguments.length > 2;
|
||||||
if (obj == null) obj = [];
|
if (obj == null) obj = [];
|
||||||
if (nativeReduce && obj.reduce === nativeReduce) {
|
if (nativeReduce && obj.reduce === nativeReduce) {
|
||||||
if (context) iterator = _.bind(iterator, context);
|
if (context) iterator = _.bind(iterator, context);
|
||||||
|
|
@ -121,20 +122,22 @@
|
||||||
memo = iterator.call(context, memo, value, index, list);
|
memo = iterator.call(context, memo, value, index, list);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (!initial) throw new TypeError("Reduce of empty array with no initial value");
|
if (!initial) throw new TypeError('Reduce of empty array with no initial value');
|
||||||
return memo;
|
return memo;
|
||||||
};
|
};
|
||||||
|
|
||||||
// The right-associative version of reduce, also known as `foldr`.
|
// The right-associative version of reduce, also known as `foldr`.
|
||||||
// Delegates to **ECMAScript 5**'s native `reduceRight` if available.
|
// Delegates to **ECMAScript 5**'s native `reduceRight` if available.
|
||||||
_.reduceRight = _.foldr = function(obj, iterator, memo, context) {
|
_.reduceRight = _.foldr = function(obj, iterator, memo, context) {
|
||||||
|
var initial = arguments.length > 2;
|
||||||
if (obj == null) obj = [];
|
if (obj == null) obj = [];
|
||||||
if (nativeReduceRight && obj.reduceRight === nativeReduceRight) {
|
if (nativeReduceRight && obj.reduceRight === nativeReduceRight) {
|
||||||
if (context) iterator = _.bind(iterator, context);
|
if (context) iterator = _.bind(iterator, context);
|
||||||
return memo !== void 0 ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator);
|
return initial ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator);
|
||||||
}
|
}
|
||||||
var reversed = (_.isArray(obj) ? obj.slice() : _.toArray(obj)).reverse();
|
var reversed = _.toArray(obj).reverse();
|
||||||
return _.reduce(reversed, iterator, memo, context);
|
if (context && !initial) iterator = _.bind(iterator, context);
|
||||||
|
return initial ? _.reduce(reversed, iterator, memo, context) : _.reduce(reversed, iterator);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Return the first value which passes a truth test. Aliased as `detect`.
|
// Return the first value which passes a truth test. Aliased as `detect`.
|
||||||
|
|
@ -189,7 +192,7 @@
|
||||||
// Delegates to **ECMAScript 5**'s native `some` if available.
|
// Delegates to **ECMAScript 5**'s native `some` if available.
|
||||||
// Aliased as `any`.
|
// Aliased as `any`.
|
||||||
var any = _.some = _.any = function(obj, iterator, context) {
|
var any = _.some = _.any = function(obj, iterator, context) {
|
||||||
iterator = iterator || _.identity;
|
iterator || (iterator = _.identity);
|
||||||
var result = false;
|
var result = false;
|
||||||
if (obj == null) return result;
|
if (obj == null) return result;
|
||||||
if (nativeSome && obj.some === nativeSome) return obj.some(iterator, context);
|
if (nativeSome && obj.some === nativeSome) return obj.some(iterator, context);
|
||||||
|
|
@ -215,7 +218,7 @@
|
||||||
_.invoke = function(obj, method) {
|
_.invoke = function(obj, method) {
|
||||||
var args = slice.call(arguments, 2);
|
var args = slice.call(arguments, 2);
|
||||||
return _.map(obj, function(value) {
|
return _.map(obj, function(value) {
|
||||||
return (method.call ? method || value : value[method]).apply(value, args);
|
return (_.isFunction(method) ? method || value : value[method]).apply(value, args);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -402,10 +405,11 @@
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// Take the difference between one array and another.
|
// Take the difference between one array and a number of other arrays.
|
||||||
// Only the elements present in just the first array will remain.
|
// Only the elements present in just the first array will remain.
|
||||||
_.difference = function(array, other) {
|
_.difference = function(array) {
|
||||||
return _.filter(array, function(value){ return !_.include(other, value); });
|
var rest = _.flatten(slice.call(arguments, 1));
|
||||||
|
return _.filter(array, function(value){ return !_.include(rest, value); });
|
||||||
};
|
};
|
||||||
|
|
||||||
// Zip together multiple lists into a single array -- elements that share
|
// Zip together multiple lists into a single array -- elements that share
|
||||||
|
|
@ -432,7 +436,7 @@
|
||||||
return array[i] === item ? i : -1;
|
return array[i] === item ? i : -1;
|
||||||
}
|
}
|
||||||
if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item);
|
if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item);
|
||||||
for (i = 0, l = array.length; i < l; i++) if (array[i] === item) return i;
|
for (i = 0, l = array.length; i < l; i++) if (i in array && array[i] === item) return i;
|
||||||
return -1;
|
return -1;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -441,7 +445,7 @@
|
||||||
if (array == null) return -1;
|
if (array == null) return -1;
|
||||||
if (nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) return array.lastIndexOf(item);
|
if (nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) return array.lastIndexOf(item);
|
||||||
var i = array.length;
|
var i = array.length;
|
||||||
while (i--) if (array[i] === item) return i;
|
while (i--) if (i in array && array[i] === item) return i;
|
||||||
return -1;
|
return -1;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -579,7 +583,7 @@
|
||||||
// conditionally execute the original function.
|
// conditionally execute the original function.
|
||||||
_.wrap = function(func, wrapper) {
|
_.wrap = function(func, wrapper) {
|
||||||
return function() {
|
return function() {
|
||||||
var args = [func].concat(slice.call(arguments));
|
var args = [func].concat(slice.call(arguments, 0));
|
||||||
return wrapper.apply(this, args);
|
return wrapper.apply(this, args);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
@ -587,9 +591,9 @@
|
||||||
// Returns a function that is the composition of a list of functions, each
|
// Returns a function that is the composition of a list of functions, each
|
||||||
// consuming the return value of the function that follows.
|
// consuming the return value of the function that follows.
|
||||||
_.compose = function() {
|
_.compose = function() {
|
||||||
var funcs = slice.call(arguments);
|
var funcs = arguments;
|
||||||
return function() {
|
return function() {
|
||||||
var args = slice.call(arguments);
|
var args = arguments;
|
||||||
for (var i = funcs.length - 1; i >= 0; i--) {
|
for (var i = funcs.length - 1; i >= 0; i--) {
|
||||||
args = [funcs[i].apply(this, args)];
|
args = [funcs[i].apply(this, args)];
|
||||||
}
|
}
|
||||||
|
|
@ -677,8 +681,8 @@
|
||||||
if (a._chain) a = a._wrapped;
|
if (a._chain) a = a._wrapped;
|
||||||
if (b._chain) b = b._wrapped;
|
if (b._chain) b = b._wrapped;
|
||||||
// Invoke a custom `isEqual` method if one is provided.
|
// Invoke a custom `isEqual` method if one is provided.
|
||||||
if (_.isFunction(a.isEqual)) return a.isEqual(b);
|
if (a.isEqual && _.isFunction(a.isEqual)) return a.isEqual(b);
|
||||||
if (_.isFunction(b.isEqual)) return b.isEqual(a);
|
if (b.isEqual && _.isFunction(b.isEqual)) return b.isEqual(a);
|
||||||
// Compare `[[Class]]` names.
|
// Compare `[[Class]]` names.
|
||||||
var className = toString.call(a);
|
var className = toString.call(a);
|
||||||
if (className != toString.call(b)) return false;
|
if (className != toString.call(b)) return false;
|
||||||
|
|
@ -687,13 +691,11 @@
|
||||||
case '[object String]':
|
case '[object String]':
|
||||||
// Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is
|
// Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is
|
||||||
// equivalent to `new String("5")`.
|
// equivalent to `new String("5")`.
|
||||||
return String(a) == String(b);
|
return a == String(b);
|
||||||
case '[object Number]':
|
case '[object Number]':
|
||||||
a = +a;
|
|
||||||
b = +b;
|
|
||||||
// `NaN`s are equivalent, but non-reflexive. An `egal` comparison is performed for
|
// `NaN`s are equivalent, but non-reflexive. An `egal` comparison is performed for
|
||||||
// other numeric values.
|
// other numeric values.
|
||||||
return a != a ? b != b : (a == 0 ? 1 / a == 1 / b : a == b);
|
return a != +a ? b != +b : (a == 0 ? 1 / a == 1 / b : a == +b);
|
||||||
case '[object Date]':
|
case '[object Date]':
|
||||||
case '[object Boolean]':
|
case '[object Boolean]':
|
||||||
// Coerce dates and booleans to numeric primitive values. Dates are compared by their
|
// Coerce dates and booleans to numeric primitive values. Dates are compared by their
|
||||||
|
|
@ -733,7 +735,7 @@
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Objects with different constructors are not equivalent.
|
// Objects with different constructors are not equivalent.
|
||||||
if ("constructor" in a != "constructor" in b || a.constructor != b.constructor) return false;
|
if ('constructor' in a != 'constructor' in b || a.constructor != b.constructor) return false;
|
||||||
// Deep compare objects.
|
// Deep compare objects.
|
||||||
for (var key in a) {
|
for (var key in a) {
|
||||||
if (hasOwnProperty.call(a, key)) {
|
if (hasOwnProperty.call(a, key)) {
|
||||||
|
|
@ -786,11 +788,10 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
// Is a given variable an arguments object?
|
// Is a given variable an arguments object?
|
||||||
if (toString.call(arguments) == '[object Arguments]') {
|
_.isArguments = function(obj) {
|
||||||
_.isArguments = function(obj) {
|
return toString.call(obj) == '[object Arguments]';
|
||||||
return toString.call(obj) == '[object Arguments]';
|
};
|
||||||
};
|
if (!_.isArguments(arguments)) {
|
||||||
} else {
|
|
||||||
_.isArguments = function(obj) {
|
_.isArguments = function(obj) {
|
||||||
return !!(obj && hasOwnProperty.call(obj, 'callee'));
|
return !!(obj && hasOwnProperty.call(obj, 'callee'));
|
||||||
};
|
};
|
||||||
|
|
@ -891,6 +892,11 @@
|
||||||
escape : /<%-([\s\S]+?)%>/g
|
escape : /<%-([\s\S]+?)%>/g
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// When customizing `templateSettings`, if you don't want to define an
|
||||||
|
// interpolation, evaluation or escaping regex, we need one that is
|
||||||
|
// guaranteed not to match.
|
||||||
|
var noMatch = /.^/;
|
||||||
|
|
||||||
// JavaScript micro-templating, similar to John Resig's implementation.
|
// JavaScript micro-templating, similar to John Resig's implementation.
|
||||||
// Underscore templating handles arbitrary delimiters, preserves whitespace,
|
// Underscore templating handles arbitrary delimiters, preserves whitespace,
|
||||||
// and correctly escapes quotes within interpolated code.
|
// and correctly escapes quotes within interpolated code.
|
||||||
|
|
@ -900,22 +906,31 @@
|
||||||
'with(obj||{}){__p.push(\'' +
|
'with(obj||{}){__p.push(\'' +
|
||||||
str.replace(/\\/g, '\\\\')
|
str.replace(/\\/g, '\\\\')
|
||||||
.replace(/'/g, "\\'")
|
.replace(/'/g, "\\'")
|
||||||
.replace(c.escape, function(match, code) {
|
.replace(c.escape || noMatch, function(match, code) {
|
||||||
return "',_.escape(" + code.replace(/\\'/g, "'") + "),'";
|
return "',_.escape(" + code.replace(/\\'/g, "'") + "),'";
|
||||||
})
|
})
|
||||||
.replace(c.interpolate, function(match, code) {
|
.replace(c.interpolate || noMatch, function(match, code) {
|
||||||
return "'," + code.replace(/\\'/g, "'") + ",'";
|
return "'," + code.replace(/\\'/g, "'") + ",'";
|
||||||
})
|
})
|
||||||
.replace(c.evaluate || null, function(match, code) {
|
.replace(c.evaluate || noMatch, function(match, code) {
|
||||||
return "');" + code.replace(/\\'/g, "'")
|
return "');" + code.replace(/\\'/g, "'")
|
||||||
.replace(/[\r\n\t]/g, ' ') + ";__p.push('";
|
.replace(/[\r\n\t]/g, ' ')
|
||||||
|
.replace(/\\\\/g, '\\') + ";__p.push('";
|
||||||
})
|
})
|
||||||
.replace(/\r/g, '\\r')
|
.replace(/\r/g, '\\r')
|
||||||
.replace(/\n/g, '\\n')
|
.replace(/\n/g, '\\n')
|
||||||
.replace(/\t/g, '\\t')
|
.replace(/\t/g, '\\t')
|
||||||
+ "');}return __p.join('');";
|
+ "');}return __p.join('');";
|
||||||
var func = new Function('obj', '_', tmpl);
|
var func = new Function('obj', '_', tmpl);
|
||||||
return data ? func(data, _) : function(data) { return func(data, _) };
|
if (data) return func(data, _);
|
||||||
|
return function(data) {
|
||||||
|
return func.call(this, data, _);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add a "chain" function, which will delegate to the wrapper.
|
||||||
|
_.chain = function(obj) {
|
||||||
|
return _(obj).chain();
|
||||||
};
|
};
|
||||||
|
|
||||||
// The OOP Wrapper
|
// The OOP Wrapper
|
||||||
|
|
@ -950,8 +965,11 @@
|
||||||
each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) {
|
each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) {
|
||||||
var method = ArrayProto[name];
|
var method = ArrayProto[name];
|
||||||
wrapper.prototype[name] = function() {
|
wrapper.prototype[name] = function() {
|
||||||
method.apply(this._wrapped, arguments);
|
var wrapped = this._wrapped;
|
||||||
return result(this._wrapped, this._chain);
|
method.apply(wrapped, arguments);
|
||||||
|
var length = wrapped.length;
|
||||||
|
if ((name == 'shift' || name == 'splice') && length === 0) delete wrapped[0];
|
||||||
|
return result(wrapped, this._chain);
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -935,7 +935,7 @@ label:not(.bootstrapped)
|
||||||
:display none !important
|
:display none !important
|
||||||
|
|
||||||
textarea
|
textarea
|
||||||
:height 18px !important
|
:height 24px !important
|
||||||
|
|
||||||
.counter
|
.counter
|
||||||
:display none
|
:display none
|
||||||
|
|
|
||||||
97
public/stylesheets/sass/mentions.scss
Normal file
97
public/stylesheets/sass/mentions.scss
Normal file
|
|
@ -0,0 +1,97 @@
|
||||||
|
|
||||||
|
@import 'mixins';
|
||||||
|
|
||||||
|
.mentions-input-box {
|
||||||
|
background: #fff;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
display: block;
|
||||||
|
background: transparent;
|
||||||
|
border: 1px solid #dcdcdc;
|
||||||
|
border-radius: 3px;
|
||||||
|
outline: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
resize: none;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
|
||||||
|
-webkit-box-sizing: border-box;
|
||||||
|
-moz-box-sizing: border-box;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mentions-autocomplete-list {
|
||||||
|
background: white;
|
||||||
|
display: none;
|
||||||
|
left: 0;
|
||||||
|
margin-left: -1px;
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
z-index: 10000;
|
||||||
|
|
||||||
|
|
||||||
|
ul {
|
||||||
|
border: 1px solid #999;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
@include border-radius(0px, 0px, 5px, 5px);
|
||||||
|
|
||||||
|
li {
|
||||||
|
background: white;
|
||||||
|
border-bottom: 1px solid #ccc;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 15px;
|
||||||
|
height: 26px;
|
||||||
|
line-height: 26px;
|
||||||
|
list-style: none;
|
||||||
|
margin: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
padding: 5px;
|
||||||
|
text-decoration: underline;
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
|
&:hover, &.active { background: #eee; }
|
||||||
|
&:last-child { @include border-radius(0px, 0px, 5px, 5px); }
|
||||||
|
|
||||||
|
img, div.icon {
|
||||||
|
float: left;
|
||||||
|
height: 25px;
|
||||||
|
margin-right: 5px;
|
||||||
|
width: 25px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mentions {
|
||||||
|
bottom: 0;
|
||||||
|
color: white;
|
||||||
|
font-size: 14px;
|
||||||
|
font-family: Arial, Helvetica, sans-serif;
|
||||||
|
left: 4px;
|
||||||
|
line-height: normal;
|
||||||
|
overflow: hidden;
|
||||||
|
padding: 6px 0px 3px;
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
top: -2px;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-wrap: break-word;
|
||||||
|
|
||||||
|
> div {
|
||||||
|
color: white;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
strong {
|
||||||
|
background: #d8dfea;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#publisher .mentions-autocomplete-list ul { width: 483px; }
|
||||||
4
public/stylesheets/vendor/jquery.mentionsInput.css
generated
vendored
Normal file
4
public/stylesheets/vendor/jquery.mentionsInput.css
generated
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
|
||||||
|
#publisher .mentions-input-box .mentions-autocomplete-list {
|
||||||
|
width: 483px;
|
||||||
|
}
|
||||||
|
|
@ -174,259 +174,4 @@ describe("Publisher", function() {
|
||||||
expect(Publisher.input().length).toBe(1);
|
expect(Publisher.input().length).toBe(1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("autocompletion", function(){
|
|
||||||
describe("searchTermFromValue", function(){
|
|
||||||
beforeEach(function(){
|
|
||||||
this.func = Publisher.autocompletion.searchTermFromValue;
|
|
||||||
});
|
|
||||||
|
|
||||||
it("returns nothing if the cursor is before the @", function(){
|
|
||||||
expect(this.func('not @dan grip', 2)).toBe('');
|
|
||||||
});
|
|
||||||
|
|
||||||
it("returns everything up to the cursor if the cursor is a word after that @", function(){
|
|
||||||
expect(this.func('not @dan grip', 13)).toBe('dan grip');
|
|
||||||
});
|
|
||||||
|
|
||||||
it("returns up to the cursor if the cursor is after that @", function(){
|
|
||||||
expect(this.func('not @dan grip', 7)).toBe('da');
|
|
||||||
});
|
|
||||||
|
|
||||||
it("returns everything after an @ at the start of the line", function(){
|
|
||||||
expect(this.func('@dan grip', 9)).toBe('dan grip');
|
|
||||||
});
|
|
||||||
it("returns nothing if there is no @", function(){
|
|
||||||
expect(this.func('dan', 3)).toBe('');
|
|
||||||
});
|
|
||||||
it("returns nothing for just an @", function(){
|
|
||||||
expect(this.func('@', 1)).toBe('');
|
|
||||||
});
|
|
||||||
it("returns nothing if there are letters preceding the @", function(){
|
|
||||||
expect(this.func('ioj@asdo', 8)).toBe('');
|
|
||||||
});
|
|
||||||
it("returns everything up to the cursor if there are 2 @s and the cursor is between them", function(){
|
|
||||||
expect(this.func('@asdpo aoisdj @asodk', 8)).toBe('asdpo');
|
|
||||||
});
|
|
||||||
it("returns everything from the 2nd @ up to the cursor if there are 2 @s and the cursor after them", function(){
|
|
||||||
expect(this.func('@asod asdo @asd asok', 15)).toBe('asd');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("mentionList", function(){
|
|
||||||
beforeEach(function(){
|
|
||||||
spec.loadFixture('aspects_index');
|
|
||||||
|
|
||||||
this.list = Publisher.autocompletion.mentionList;
|
|
||||||
this.visibleInput = Publisher.input();
|
|
||||||
this.hiddenInput = Publisher.hiddenInput();
|
|
||||||
this.mention = { visibleStart : 0,
|
|
||||||
visibleEnd : 5,
|
|
||||||
mentionString : "@{Danny; dan@pod.org}"
|
|
||||||
};
|
|
||||||
|
|
||||||
this.list.mentions = [];
|
|
||||||
this.list.push(this.mention);
|
|
||||||
this.visibleVal = "Danny loves testing javascript";
|
|
||||||
this.visibleInput.val(this.visibleVal);
|
|
||||||
this.hiddenVal = "@{Danny; dan@pod.org} loves testing javascript";
|
|
||||||
this.hiddenInput.val(this.hiddenVal);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("selectionDeleted", function(){
|
|
||||||
beforeEach(function(){
|
|
||||||
this.func = this.list.selectionDeleted;
|
|
||||||
this.visibleVal = "Danny Daniel David Darren";
|
|
||||||
this.visibleInput.val(this.visibleVal);
|
|
||||||
this.list.mentions = [];
|
|
||||||
this.danny = {
|
|
||||||
visibleStart : 0,
|
|
||||||
visibleEnd : 5,
|
|
||||||
mentionString : "@{Danny; danny@pod.org}"
|
|
||||||
};
|
|
||||||
this.daniel = {
|
|
||||||
visibleStart : 6,
|
|
||||||
visibleEnd : 12,
|
|
||||||
mentionString : "@{Daniel; daniel@pod.org}"
|
|
||||||
};
|
|
||||||
this.david = {
|
|
||||||
visibleStart : 13,
|
|
||||||
visibleEnd : 18,
|
|
||||||
mentionString : "@{David; david@pod.org}"
|
|
||||||
};
|
|
||||||
this.darren = {
|
|
||||||
visibleStart : 19,
|
|
||||||
visibleEnd : 25,
|
|
||||||
mentionString : "@{Darren; darren@pod.org}"
|
|
||||||
};
|
|
||||||
|
|
||||||
_.each([this.danny, this.daniel, this.david, this.darren], function(person){
|
|
||||||
this.list.push(person);
|
|
||||||
}, this);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("destroys mentions within the selection", function(){
|
|
||||||
this.func(4,11);
|
|
||||||
expect(this.list.sortedMentions()).toEqual([this.darren, this.david])
|
|
||||||
});
|
|
||||||
|
|
||||||
it("moves remaining mentions back", function(){
|
|
||||||
this.func(7,14);
|
|
||||||
var length = 11 - 4;
|
|
||||||
|
|
||||||
expect(this.danny.visibleStart).toBe(0);
|
|
||||||
expect(this.darren.visibleStart).toBe(19-length);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("generateHiddenInput", function(){
|
|
||||||
it("replaces mentions in a string", function(){
|
|
||||||
expect(this.list.generateHiddenInput(this.visibleVal)).toBe(this.hiddenVal);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("push", function(){
|
|
||||||
it("adds mention to mentions array", function(){
|
|
||||||
expect(this.list.mentions.length).toBe(1);
|
|
||||||
expect(this.list.mentions[0]).toBe(this.mention)
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("mentionAt", function(){
|
|
||||||
it("returns the location of the mention at that location in the mentions array", function(){
|
|
||||||
expect(this.list.mentions[this.list.mentionAt(3)]).toBe(this.mention);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("returns null if there is no mention", function(){
|
|
||||||
expect(this.list.mentionAt(8)).toBeFalsy();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("insertionAt", function(){
|
|
||||||
it("does nothing if there is no visible mention at that index", function(){
|
|
||||||
this.list.insertionAt(8);
|
|
||||||
expect(this.visibleInput.val()).toBe(this.visibleVal);
|
|
||||||
expect(this.hiddenInput.val()).toBe(this.hiddenVal);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("deletes the mention from the hidden field if there is a mention", function(){
|
|
||||||
this.list.insertionAt(3);
|
|
||||||
expect(this.visibleInput.val()).toBe(this.visibleVal);
|
|
||||||
expect(this.list.generateHiddenInput(this.visibleInput.val())).toBe(this.visibleVal);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("deletes the mention from the list", function(){
|
|
||||||
this.list.insertionAt(3);
|
|
||||||
expect(this.list.mentionAt(3)).toBeFalsy();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("calls updateMentionLocations", function(){
|
|
||||||
mentionTwo = { visibleStart : 8,
|
|
||||||
visibleEnd : 15,
|
|
||||||
mentionString : "@{SomeoneElse; other@pod.org}"
|
|
||||||
};
|
|
||||||
this.list.push(mentionTwo);
|
|
||||||
|
|
||||||
spyOn(this.list, 'updateMentionLocations');
|
|
||||||
this.list.insertionAt(3,4, 60);
|
|
||||||
expect(this.list.updateMentionLocations).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("updateMentionLocations", function(){
|
|
||||||
it("updates the offsets of the remaining mentions in the list", function(){
|
|
||||||
mentionTwo = { visibleStart : 8,
|
|
||||||
visibleEnd : 15,
|
|
||||||
mentionString : "@{SomeoneElse; other@pod.org}"
|
|
||||||
};
|
|
||||||
this.list.push(mentionTwo);
|
|
||||||
this.list.updateMentionLocations(7, 1);
|
|
||||||
expect(mentionTwo.visibleStart).toBe(9);
|
|
||||||
expect(mentionTwo.visibleEnd).toBe(16);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("keyUpHandler", function(){
|
|
||||||
beforeEach(function(){
|
|
||||||
spec.loadFixture('aspects_index');
|
|
||||||
Publisher.initialize();
|
|
||||||
this.input = Publisher.input();
|
|
||||||
this.submit = Publisher.submit();
|
|
||||||
Publisher.open();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("keep the share button disabled when adding only whitespaces", function(){
|
|
||||||
expect(this.submit.attr('disabled')).toBeTruthy();
|
|
||||||
this.input.val(' ');
|
|
||||||
this.input.keyup();
|
|
||||||
expect(this.submit.attr('disabled')).toBeTruthy();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("enable the share button when adding non-whitespace characters", function(){
|
|
||||||
expect(this.submit.attr('disabled')).toBeTruthy();
|
|
||||||
this.input.val('some text');
|
|
||||||
this.input.keyup();
|
|
||||||
expect(this.submit.attr('disabled')).toBeFalsy();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should toggle share button disable/enable when playing with input", function(){
|
|
||||||
expect(this.submit.attr('disabled')).toBeTruthy();
|
|
||||||
this.input.val(' ');
|
|
||||||
this.input.keyup();
|
|
||||||
expect(this.submit.attr('disabled')).toBeTruthy();
|
|
||||||
this.input.val('text');
|
|
||||||
this.input.keyup();
|
|
||||||
this.expect(this.submit.attr('disabled')).toBeFalsy();
|
|
||||||
this.input.val('');
|
|
||||||
this.input.keyup();
|
|
||||||
expect(this.submit.attr('disabled')).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("addMentionToInput", function(){
|
|
||||||
beforeEach(function(){
|
|
||||||
spec.loadFixture('aspects_index');
|
|
||||||
this.func = Publisher.autocompletion.addMentionToInput;
|
|
||||||
this.input = Publisher.input();
|
|
||||||
this.replaceWith = "Replace with this.";
|
|
||||||
Publisher.autocompletion.mentionList.mentions = [];
|
|
||||||
});
|
|
||||||
|
|
||||||
it("replaces everything up to the cursor if the cursor is a word after that @", function(){
|
|
||||||
this.input.val('not @dan grip');
|
|
||||||
var cursorIndex = 13;
|
|
||||||
this.func(this.input, cursorIndex, this.replaceWith);
|
|
||||||
expect(this.input.val()).toBe('not ' + this.replaceWith);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("replaces everything between @ and the cursor if the cursor is after that @", function(){
|
|
||||||
this.input.val('not @dan grip');
|
|
||||||
var cursorIndex = 7;
|
|
||||||
this.func(this.input, cursorIndex, this.replaceWith);
|
|
||||||
expect(this.input.val()).toBe('not ' + this.replaceWith + 'n grip');
|
|
||||||
});
|
|
||||||
|
|
||||||
it("replaces everything up to the cursor from @ at the start of the line", function(){
|
|
||||||
this.input.val('@dan grip');
|
|
||||||
var cursorIndex = 9;
|
|
||||||
this.func(this.input, cursorIndex, this.replaceWith);
|
|
||||||
expect(this.input.val()).toBe(this.replaceWith);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("replaces everything between the first @ and the cursor if there are 2 @s and the cursor is between them", function(){
|
|
||||||
this.input.val('@asdpo aoisdj @asodk');
|
|
||||||
var cursorIndex = 8;
|
|
||||||
this.func(this.input, cursorIndex, this.replaceWith);
|
|
||||||
expect(this.input.val()).toBe(this.replaceWith + 'aoisdj @asodk');
|
|
||||||
});
|
|
||||||
|
|
||||||
it("replaces everything after the 2nd @ if there are 2 @s and the cursor after them", function(){
|
|
||||||
this.input.val('@asod asdo @asd asok');
|
|
||||||
var cursorIndex = 15;
|
|
||||||
this.func(this.input, cursorIndex, this.replaceWith);
|
|
||||||
expect(this.input.val()).toBe('@asod asdo ' + this.replaceWith + ' asok');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@
|
||||||
# - dist/**/*.js
|
# - dist/**/*.js
|
||||||
#
|
#
|
||||||
src_files:
|
src_files:
|
||||||
|
- public/javascripts/vendor/underscore.js
|
||||||
- public/javascripts/vendor/jquery-1.7.1.min.js
|
- public/javascripts/vendor/jquery-1.7.1.min.js
|
||||||
- public/javascripts/vendor/jquery-ui-1.8.9.custom.min.js
|
- public/javascripts/vendor/jquery-ui-1.8.9.custom.min.js
|
||||||
- public/javascripts/vendor/bootstrap/bootstrap-popover.js
|
- public/javascripts/vendor/bootstrap/bootstrap-popover.js
|
||||||
|
|
@ -20,11 +21,11 @@ src_files:
|
||||||
- public/javascripts/vendor/jquery.autoresize.js
|
- public/javascripts/vendor/jquery.autoresize.js
|
||||||
- public/javascripts/vendor/jquery.expander.js
|
- public/javascripts/vendor/jquery.expander.js
|
||||||
- public/javascripts/vendor/jquery.charcount.js
|
- public/javascripts/vendor/jquery.charcount.js
|
||||||
|
- public/javascripts/vendor/jquery.mentionsInput.js
|
||||||
- public/javascripts/vendor/timeago.js
|
- public/javascripts/vendor/timeago.js
|
||||||
- public/javascripts/vendor/facebox.js
|
- public/javascripts/vendor/facebox.js
|
||||||
- public/javascripts/vendor/markdown/*
|
- public/javascripts/vendor/markdown/*
|
||||||
- public/javascripts/jquery.infieldlabel-custom.js
|
- public/javascripts/jquery.infieldlabel-custom.js
|
||||||
- public/javascripts/vendor/underscore.js
|
|
||||||
- public/javascripts/vendor/backbone.js
|
- public/javascripts/vendor/backbone.js
|
||||||
- public/javascripts/vendor/handlebars-1.0.0.beta.6.js
|
- public/javascripts/vendor/handlebars-1.0.0.beta.6.js
|
||||||
- public/javascripts/fileuploader-custom.js
|
- public/javascripts/fileuploader-custom.js
|
||||||
|
|
@ -50,6 +51,7 @@ src_files:
|
||||||
- public/javascripts/mobile.js
|
- public/javascripts/mobile.js
|
||||||
- public/javascripts/contact-list.js
|
- public/javascripts/contact-list.js
|
||||||
- public/javascripts/view.js
|
- public/javascripts/view.js
|
||||||
|
- public/javascripts/mentions.js
|
||||||
- public/javascripts/publisher.js
|
- public/javascripts/publisher.js
|
||||||
- public/javascripts/stream.js
|
- public/javascripts/stream.js
|
||||||
- public/javascripts/validation.js
|
- public/javascripts/validation.js
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue