Merge pull request #744 from metamaps/feature/markdown-in-topic-cards
markdown in topic card description field
This commit is contained in:
commit
9ac24f7468
13 changed files with 783 additions and 984 deletions
685
app/assets/javascripts/lib/best_in_place.js
Normal file
685
app/assets/javascripts/lib/best_in_place.js
Normal file
|
@ -0,0 +1,685 @@
|
|||
/*
|
||||
* BestInPlace (for jQuery)
|
||||
* version: 3.0.0.alpha (2014)
|
||||
*
|
||||
* By Bernat Farrero based on the work of Jan Varwig.
|
||||
* Examples at http://bernatfarrero.com
|
||||
*
|
||||
* Licensed under the MIT:
|
||||
* http://www.opensource.org/licenses/mit-license.php
|
||||
*
|
||||
* @requires jQuery
|
||||
*
|
||||
* Usage:
|
||||
*
|
||||
* Attention.
|
||||
* The format of the JSON object given to the select inputs is the following:
|
||||
* [["key", "value"],["key", "value"]]
|
||||
* The format of the JSON object given to the checkbox inputs is the following:
|
||||
* ["falseValue", "trueValue"]
|
||||
|
||||
*/
|
||||
//= require jquery.autosize
|
||||
|
||||
function BestInPlaceEditor(e) {
|
||||
'use strict';
|
||||
this.element = e;
|
||||
this.initOptions();
|
||||
this.bindForm();
|
||||
this.initPlaceHolder();
|
||||
jQuery(this.activator).bind('click', {editor: this}, this.clickHandler);
|
||||
}
|
||||
|
||||
BestInPlaceEditor.prototype = {
|
||||
// Public Interface Functions //////////////////////////////////////////////
|
||||
|
||||
activate: function () {
|
||||
'use strict';
|
||||
var to_display;
|
||||
if (this.isPlaceHolder()) {
|
||||
to_display = "";
|
||||
} else if (this.original_content) {
|
||||
to_display = this.original_content;
|
||||
} else {
|
||||
switch (this.formType) {
|
||||
case 'input':
|
||||
case 'textarea':
|
||||
if (this.display_raw) {
|
||||
to_display = this.element.html().replace(/&/gi, '&');
|
||||
}
|
||||
else {
|
||||
var value = this.element.data('bipValue');
|
||||
if (typeof value === 'undefined') {
|
||||
to_display = '';
|
||||
} else if (typeof value === 'string') {
|
||||
to_display = this.element.data('bipValue').replace(/&/gi, '&');
|
||||
} else {
|
||||
to_display = this.element.data('bipValue');
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'select':
|
||||
to_display = this.element.html();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
this.oldValue = this.isPlaceHolder() ? "" : this.element.html();
|
||||
this.display_value = to_display;
|
||||
jQuery(this.activator).unbind("click", this.clickHandler);
|
||||
this.activateForm();
|
||||
this.element.trigger(jQuery.Event("best_in_place:activate"));
|
||||
},
|
||||
|
||||
abort: function () {
|
||||
'use strict';
|
||||
this.activateText(this.oldValue);
|
||||
jQuery(this.activator).bind('click', {editor: this}, this.clickHandler);
|
||||
this.element.trigger(jQuery.Event("best_in_place:abort"));
|
||||
this.element.trigger(jQuery.Event("best_in_place:deactivate"));
|
||||
},
|
||||
|
||||
abortIfConfirm: function () {
|
||||
'use strict';
|
||||
if (!this.useConfirm) {
|
||||
this.abort();
|
||||
return;
|
||||
}
|
||||
|
||||
if (confirm(BestInPlaceEditor.defaults.locales[''].confirmMessage)) {
|
||||
this.abort();
|
||||
}
|
||||
},
|
||||
|
||||
update: function () {
|
||||
'use strict';
|
||||
var editor = this,
|
||||
value = this.getValue();
|
||||
|
||||
// Avoid request if no change is made
|
||||
if (this.formType in {"input": 1, "textarea": 1} && value === this.oldValue) {
|
||||
this.abort();
|
||||
return true;
|
||||
}
|
||||
|
||||
editor.ajax({
|
||||
"type": this.requestMethod(),
|
||||
"dataType": BestInPlaceEditor.defaults.ajaxDataType,
|
||||
"data": editor.requestData(),
|
||||
"success": function (data, status, xhr) {
|
||||
editor.loadSuccessCallback(data, status, xhr);
|
||||
},
|
||||
"error": function (request, error) {
|
||||
editor.loadErrorCallback(request, error);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
switch (this.formType) {
|
||||
case "select":
|
||||
this.previousCollectionValue = value;
|
||||
|
||||
// search for the text for the span
|
||||
$.each(this.values, function(index, arr){ if (String(arr[0]) === String(value)) editor.element.html(arr[1]); });
|
||||
break;
|
||||
|
||||
case "checkbox":
|
||||
$.each(this.values, function(index, arr){ if (String(arr[0]) === String(value)) editor.element.html(arr[1]); });
|
||||
break;
|
||||
|
||||
default:
|
||||
if (value !== "") {
|
||||
if (this.display_raw) {
|
||||
editor.element.html(value);
|
||||
} else {
|
||||
editor.element.text(value);
|
||||
}
|
||||
} else {
|
||||
editor.element.html(this.placeHolder);
|
||||
}
|
||||
}
|
||||
|
||||
editor.element.data('bipValue', value);
|
||||
editor.element.attr('data-bip-value', value);
|
||||
|
||||
editor.element.trigger(jQuery.Event("best_in_place:update"));
|
||||
|
||||
|
||||
},
|
||||
|
||||
activateForm: function () {
|
||||
'use strict';
|
||||
alert(BestInPlaceEditor.defaults.locales[''].uninitializedForm);
|
||||
},
|
||||
|
||||
activateText: function (value) {
|
||||
'use strict';
|
||||
this.element.html(value);
|
||||
if (this.isPlaceHolder()) {
|
||||
this.element.html(this.placeHolder);
|
||||
}
|
||||
},
|
||||
|
||||
// Helper Functions ////////////////////////////////////////////////////////
|
||||
|
||||
initOptions: function () {
|
||||
// Try parent supplied info
|
||||
'use strict';
|
||||
var self = this;
|
||||
self.element.parents().each(function () {
|
||||
var $parent = jQuery(this);
|
||||
self.url = self.url || $parent.data("bipUrl");
|
||||
self.activator = self.activator || $parent.data("bipActivator");
|
||||
self.okButton = self.okButton || $parent.data("bipOkButton");
|
||||
self.okButtonClass = self.okButtonClass || $parent.data("bipOkButtonClass");
|
||||
self.cancelButton = self.cancelButton || $parent.data("bipCancelButton");
|
||||
self.cancelButtonClass = self.cancelButtonClass || $parent.data("bipCancelButtonClass");
|
||||
self.skipBlur = self.skipBlur || $parent.data("bipSkipBlur");
|
||||
});
|
||||
|
||||
// Load own attributes (overrides all others)
|
||||
self.url = self.element.data("bipUrl") || self.url || document.location.pathname;
|
||||
self.collection = self.element.data("bipCollection") || self.collection;
|
||||
self.formType = self.element.data("bipType") || "input";
|
||||
self.objectName = self.element.data("bipObject") || self.objectName;
|
||||
self.attributeName = self.element.data("bipAttribute") || self.attributeName;
|
||||
self.activator = self.element.data("bipActivator") || self.element;
|
||||
self.okButton = self.element.data("bipOkButton") || self.okButton;
|
||||
self.okButtonClass = self.element.data("bipOkButtonClass") || self.okButtonClass || BestInPlaceEditor.defaults.okButtonClass;
|
||||
self.cancelButton = self.element.data("bipCancelButton") || self.cancelButton;
|
||||
self.cancelButtonClass = self.element.data("bipCancelButtonClass") || self.cancelButtonClass || BestInPlaceEditor.defaults.cancelButtonClass;
|
||||
self.skipBlur = self.element.data("bipSkipBlur") || self.skipBlur || BestInPlaceEditor.defaults.skipBlur;
|
||||
self.isNewObject = self.element.data("bipNewObject");
|
||||
self.dataExtraPayload = self.element.data("bipExtraPayload");
|
||||
|
||||
// Fix for default values of 0
|
||||
if (self.element.data("bipPlaceholder") == null) {
|
||||
self.placeHolder = BestInPlaceEditor.defaults.locales[''].placeHolder;
|
||||
} else {
|
||||
self.placeHolder = self.element.data("bipPlaceholder");
|
||||
}
|
||||
|
||||
self.inner_class = self.element.data("bipInnerClass");
|
||||
self.html_attrs = self.element.data("bipHtmlAttrs");
|
||||
self.original_content = self.element.data("bipOriginalContent") || self.original_content;
|
||||
|
||||
// if set the input won't be satinized
|
||||
self.display_raw = self.element.data("bip-raw");
|
||||
|
||||
self.useConfirm = self.element.data("bip-confirm");
|
||||
|
||||
if (self.formType === "select" || self.formType === "checkbox") {
|
||||
self.values = self.collection;
|
||||
self.collectionValue = self.element.data("bipValue") || self.collectionValue;
|
||||
}
|
||||
},
|
||||
|
||||
bindForm: function () {
|
||||
'use strict';
|
||||
this.activateForm = BestInPlaceEditor.forms[this.formType].activateForm;
|
||||
this.getValue = BestInPlaceEditor.forms[this.formType].getValue;
|
||||
},
|
||||
|
||||
|
||||
initPlaceHolder: function () {
|
||||
'use strict';
|
||||
// TODO add placeholder for select and checkbox
|
||||
if (this.element.html() === "") {
|
||||
this.element.addClass('bip-placeholder');
|
||||
this.element.html(this.placeHolder);
|
||||
}
|
||||
},
|
||||
|
||||
isPlaceHolder: function () {
|
||||
'use strict';
|
||||
// TODO: It only work when form is deactivated.
|
||||
// Condition will fail when form is activated
|
||||
return this.element.html() === "" || this.element.html() === this.placeHolder;
|
||||
},
|
||||
|
||||
getValue: function () {
|
||||
'use strict';
|
||||
alert(BestInPlaceEditor.defaults.locales[''].uninitializedForm);
|
||||
},
|
||||
|
||||
// Trim and Strips HTML from text
|
||||
sanitizeValue: function (s) {
|
||||
'use strict';
|
||||
return jQuery.trim(s);
|
||||
},
|
||||
|
||||
requestMethod: function() {
|
||||
'use strict';
|
||||
return this.isNewObject ? 'post' : BestInPlaceEditor.defaults.ajaxMethod;
|
||||
},
|
||||
|
||||
/* Generate the data sent in the POST request */
|
||||
requestData: function () {
|
||||
'use strict';
|
||||
// To prevent xss attacks, a csrf token must be defined as a meta attribute
|
||||
var csrf_token = jQuery('meta[name=csrf-token]').attr('content'),
|
||||
csrf_param = jQuery('meta[name=csrf-param]').attr('content');
|
||||
|
||||
var data = {}
|
||||
data['_method'] = this.requestMethod()
|
||||
|
||||
data[this.objectName] = this.dataExtraPayload || {}
|
||||
|
||||
data[this.objectName][this.attributeName] = this.getValue()
|
||||
|
||||
if (csrf_param !== undefined && csrf_token !== undefined) {
|
||||
data[csrf_param] = csrf_token
|
||||
}
|
||||
return jQuery.param(data);
|
||||
},
|
||||
|
||||
ajax: function (options) {
|
||||
'use strict';
|
||||
options.url = this.url;
|
||||
options.beforeSend = function (xhr) {
|
||||
xhr.setRequestHeader("Accept", "application/json");
|
||||
};
|
||||
return jQuery.ajax(options);
|
||||
},
|
||||
|
||||
// Handlers ////////////////////////////////////////////////////////////////
|
||||
|
||||
loadSuccessCallback: function (data, status, xhr) {
|
||||
'use strict';
|
||||
data = jQuery.trim(data);
|
||||
//Update original content with current text.
|
||||
if (this.display_raw) {
|
||||
this.original_content = this.element.html();
|
||||
} else {
|
||||
this.original_content = this.element.text();
|
||||
}
|
||||
|
||||
if (data && data !== "") {
|
||||
var response = jQuery.parseJSON(data);
|
||||
if (response !== null && response.hasOwnProperty("display_as")) {
|
||||
this.element.data('bip-original-content', this.element.text());
|
||||
this.element.html(response.display_as);
|
||||
}
|
||||
if (this.isNewObject && response && response[this.objectName]) {
|
||||
if (response[this.objectName]["id"]) {
|
||||
this.isNewObject = false
|
||||
this.url += "/" + response[this.objectName]["id"] // in REST a POST /thing url should become PUT /thing/123
|
||||
}
|
||||
}
|
||||
}
|
||||
this.element.toggleClass('bip-placeholder', this.isPlaceHolder());
|
||||
|
||||
this.element.trigger(jQuery.Event("best_in_place:success"), [data, status, xhr]);
|
||||
this.element.trigger(jQuery.Event("ajax:success"), [data, status, xhr]);
|
||||
|
||||
// Binding back after being clicked
|
||||
jQuery(this.activator).bind('click', {editor: this}, this.clickHandler);
|
||||
this.element.trigger(jQuery.Event("best_in_place:deactivate"));
|
||||
|
||||
if (this.collectionValue !== null && this.formType === "select") {
|
||||
this.collectionValue = this.previousCollectionValue;
|
||||
this.previousCollectionValue = null;
|
||||
}
|
||||
},
|
||||
|
||||
loadErrorCallback: function (request, error) {
|
||||
'use strict';
|
||||
this.activateText(this.oldValue);
|
||||
|
||||
this.element.trigger(jQuery.Event("best_in_place:error"), [request, error]);
|
||||
this.element.trigger(jQuery.Event("ajax:error"), request, error);
|
||||
|
||||
// Binding back after being clicked
|
||||
jQuery(this.activator).bind('click', {editor: this}, this.clickHandler);
|
||||
this.element.trigger(jQuery.Event("best_in_place:deactivate"));
|
||||
},
|
||||
|
||||
clickHandler: function (event) {
|
||||
'use strict';
|
||||
event.preventDefault();
|
||||
event.data.editor.activate();
|
||||
},
|
||||
|
||||
setHtmlAttributes: function () {
|
||||
'use strict';
|
||||
var formField = this.element.find(this.formType);
|
||||
|
||||
if (this.html_attrs) {
|
||||
var attrs = this.html_attrs;
|
||||
$.each(attrs, function (key, val) {
|
||||
formField.attr(key, val);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
placeButtons: function (output, field) {
|
||||
'use strict';
|
||||
if (field.okButton) {
|
||||
output.append(
|
||||
jQuery(document.createElement('input'))
|
||||
.attr('type', 'submit')
|
||||
.attr('class', field.okButtonClass)
|
||||
.attr('value', field.okButton)
|
||||
);
|
||||
}
|
||||
if (field.cancelButton) {
|
||||
output.append(
|
||||
jQuery(document.createElement('input'))
|
||||
.attr('type', 'button')
|
||||
.attr('class', field.cancelButtonClass)
|
||||
.attr('value', field.cancelButton)
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// Button cases:
|
||||
// If no buttons, then blur saves, ESC cancels
|
||||
// If just Cancel button, then blur saves, ESC or clicking Cancel cancels (careful of blur event!)
|
||||
// If just OK button, then clicking OK saves (careful of blur event!), ESC or blur cancels
|
||||
// If both buttons, then clicking OK saves, ESC or clicking Cancel or blur cancels
|
||||
BestInPlaceEditor.forms = {
|
||||
"input": {
|
||||
activateForm: function () {
|
||||
'use strict';
|
||||
var output = jQuery(document.createElement('form'))
|
||||
.addClass('form_in_place')
|
||||
.attr('action', 'javascript:void(0);')
|
||||
.attr('style', 'display:inline');
|
||||
var input_elt = jQuery(document.createElement('input'))
|
||||
.attr('type', 'text')
|
||||
.attr('name', this.attributeName)
|
||||
.val(this.display_value);
|
||||
|
||||
// Add class to form input
|
||||
if (this.inner_class) {
|
||||
input_elt.addClass(this.inner_class);
|
||||
}
|
||||
|
||||
output.append(input_elt);
|
||||
this.placeButtons(output, this);
|
||||
|
||||
this.element.html(output);
|
||||
this.setHtmlAttributes();
|
||||
|
||||
this.element.find("input[type='text']")[0].select();
|
||||
this.element.find("form").bind('submit', {editor: this}, BestInPlaceEditor.forms.input.submitHandler);
|
||||
if (this.cancelButton) {
|
||||
this.element.find("input[type='button']").bind('click', {editor: this}, BestInPlaceEditor.forms.input.cancelButtonHandler);
|
||||
}
|
||||
if (!this.okButton) {
|
||||
this.element.find("input[type='text']").bind('blur', {editor: this}, BestInPlaceEditor.forms.input.inputBlurHandler);
|
||||
}
|
||||
this.element.find("input[type='text']").bind('keyup', {editor: this}, BestInPlaceEditor.forms.input.keyupHandler);
|
||||
this.blurTimer = null;
|
||||
this.userClicked = false;
|
||||
},
|
||||
|
||||
getValue: function () {
|
||||
'use strict';
|
||||
return this.sanitizeValue(this.element.find("input").val());
|
||||
},
|
||||
|
||||
// When buttons are present, use a timer on the blur event to give precedence to clicks
|
||||
inputBlurHandler: function (event) {
|
||||
'use strict';
|
||||
if (event.data.editor.okButton) {
|
||||
event.data.editor.blurTimer = setTimeout(function () {
|
||||
if (!event.data.editor.userClicked) {
|
||||
event.data.editor.abort();
|
||||
}
|
||||
}, 500);
|
||||
} else {
|
||||
if (event.data.editor.cancelButton) {
|
||||
event.data.editor.blurTimer = setTimeout(function () {
|
||||
if (!event.data.editor.userClicked) {
|
||||
event.data.editor.update();
|
||||
}
|
||||
}, 500);
|
||||
} else {
|
||||
event.data.editor.update();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
submitHandler: function (event) {
|
||||
'use strict';
|
||||
event.data.editor.userClicked = true;
|
||||
clearTimeout(event.data.editor.blurTimer);
|
||||
event.data.editor.update();
|
||||
},
|
||||
|
||||
cancelButtonHandler: function (event) {
|
||||
'use strict';
|
||||
event.data.editor.userClicked = true;
|
||||
clearTimeout(event.data.editor.blurTimer);
|
||||
event.data.editor.abort();
|
||||
event.stopPropagation(); // Without this, click isn't handled
|
||||
},
|
||||
|
||||
keyupHandler: function (event) {
|
||||
'use strict';
|
||||
if (event.keyCode === 27) {
|
||||
event.data.editor.abort();
|
||||
event.stopImmediatePropagation();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"select": {
|
||||
activateForm: function () {
|
||||
'use strict';
|
||||
var output = jQuery(document.createElement('form'))
|
||||
.attr('action', 'javascript:void(0)')
|
||||
.attr('style', 'display:inline'),
|
||||
selected = '',
|
||||
select_elt = jQuery(document.createElement('select'))
|
||||
.attr('class', this.inner_class !== null ? this.inner_class : ''),
|
||||
currentCollectionValue = this.collectionValue,
|
||||
key, value,
|
||||
a = this.values;
|
||||
|
||||
$.each(a, function(index, arr){
|
||||
key = arr[0];
|
||||
value = arr[1];
|
||||
var option_elt = jQuery(document.createElement('option'))
|
||||
.val(key)
|
||||
.html(value);
|
||||
|
||||
if (currentCollectionValue) {
|
||||
if (String(key) === String(currentCollectionValue)) option_elt.attr('selected', 'selected');
|
||||
}
|
||||
select_elt.append(option_elt);
|
||||
});
|
||||
output.append(select_elt);
|
||||
|
||||
this.element.html(output);
|
||||
this.setHtmlAttributes();
|
||||
this.element.find("select").bind('change', {editor: this}, BestInPlaceEditor.forms.select.blurHandler);
|
||||
this.element.find("select").bind('blur', {editor: this}, BestInPlaceEditor.forms.select.blurHandler);
|
||||
this.element.find("select").bind('keyup', {editor: this}, BestInPlaceEditor.forms.select.keyupHandler);
|
||||
this.element.find("select")[0].focus();
|
||||
|
||||
// automatically click on the select so you
|
||||
// don't have to click twice
|
||||
try {
|
||||
var e = document.createEvent("MouseEvents");
|
||||
e.initMouseEvent("mousedown", true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
|
||||
this.element.find("select")[0].dispatchEvent(e);
|
||||
}
|
||||
catch(e) {
|
||||
// browser doesn't support this, e.g. IE8
|
||||
}
|
||||
},
|
||||
|
||||
getValue: function () {
|
||||
'use strict';
|
||||
return this.sanitizeValue(this.element.find("select").val());
|
||||
},
|
||||
|
||||
blurHandler: function (event) {
|
||||
'use strict';
|
||||
event.data.editor.update();
|
||||
},
|
||||
|
||||
keyupHandler: function (event) {
|
||||
'use strict';
|
||||
if (event.keyCode === 27) {
|
||||
event.data.editor.abort();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"checkbox": {
|
||||
activateForm: function () {
|
||||
'use strict';
|
||||
this.collectionValue = !this.getValue();
|
||||
this.setHtmlAttributes();
|
||||
this.update();
|
||||
},
|
||||
|
||||
getValue: function () {
|
||||
'use strict';
|
||||
return this.collectionValue;
|
||||
}
|
||||
},
|
||||
|
||||
"textarea": {
|
||||
activateForm: function () {
|
||||
'use strict';
|
||||
// grab width and height of text
|
||||
var width = this.element.css('width');
|
||||
var height = this.element.css('height');
|
||||
|
||||
// construct form
|
||||
var output = jQuery(document.createElement('form'))
|
||||
.addClass('form_in_place')
|
||||
.attr('action', 'javascript:void(0);')
|
||||
.attr('style', 'display:inline');
|
||||
var textarea_elt = jQuery(document.createElement('textarea'))
|
||||
.attr('name', this.attributeName)
|
||||
.val(this.sanitizeValue(this.display_value));
|
||||
|
||||
if (this.inner_class !== null) {
|
||||
textarea_elt.addClass(this.inner_class);
|
||||
}
|
||||
|
||||
output.append(textarea_elt);
|
||||
|
||||
this.placeButtons(output, this);
|
||||
|
||||
this.element.html(output);
|
||||
this.setHtmlAttributes();
|
||||
|
||||
// set width and height of textarea
|
||||
jQuery(this.element.find("textarea")[0]).css({'min-width': width, 'min-height': height});
|
||||
jQuery(this.element.find("textarea")[0]).autosize();
|
||||
|
||||
this.element.find("textarea")[0].focus();
|
||||
this.element.find("form").bind('submit', {editor: this}, BestInPlaceEditor.forms.textarea.submitHandler);
|
||||
|
||||
if (this.cancelButton) {
|
||||
this.element.find("input[type='button']").bind('click', {editor: this}, BestInPlaceEditor.forms.textarea.cancelButtonHandler);
|
||||
}
|
||||
|
||||
if (!this.skipBlur) {
|
||||
this.element.find("textarea").bind('blur', {editor: this}, BestInPlaceEditor.forms.textarea.blurHandler);
|
||||
}
|
||||
this.element.find("textarea").bind('keyup', {editor: this}, BestInPlaceEditor.forms.textarea.keyupHandler);
|
||||
this.blurTimer = null;
|
||||
this.userClicked = false;
|
||||
},
|
||||
|
||||
getValue: function () {
|
||||
'use strict';
|
||||
return this.sanitizeValue(this.element.find("textarea").val());
|
||||
},
|
||||
|
||||
// When buttons are present, use a timer on the blur event to give precedence to clicks
|
||||
blurHandler: function (event) {
|
||||
'use strict';
|
||||
if (event.data.editor.okButton) {
|
||||
event.data.editor.blurTimer = setTimeout(function () {
|
||||
if (!event.data.editor.userClicked) {
|
||||
event.data.editor.abortIfConfirm();
|
||||
}
|
||||
}, 500);
|
||||
} else {
|
||||
if (event.data.editor.cancelButton) {
|
||||
event.data.editor.blurTimer = setTimeout(function () {
|
||||
if (!event.data.editor.userClicked) {
|
||||
event.data.editor.update();
|
||||
}
|
||||
}, 500);
|
||||
} else {
|
||||
event.data.editor.update();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
submitHandler: function (event) {
|
||||
'use strict';
|
||||
event.data.editor.userClicked = true;
|
||||
clearTimeout(event.data.editor.blurTimer);
|
||||
event.data.editor.update();
|
||||
},
|
||||
|
||||
cancelButtonHandler: function (event) {
|
||||
'use strict';
|
||||
event.data.editor.userClicked = true;
|
||||
clearTimeout(event.data.editor.blurTimer);
|
||||
event.data.editor.abortIfConfirm();
|
||||
event.stopPropagation(); // Without this, click isn't handled
|
||||
},
|
||||
|
||||
keyupHandler: function (event) {
|
||||
'use strict';
|
||||
if (event.keyCode === 27) {
|
||||
event.data.editor.abortIfConfirm();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
BestInPlaceEditor.defaults = {
|
||||
locales: {},
|
||||
ajaxMethod: "put", //TODO Change to patch when support to 3.2 is dropped
|
||||
ajaxDataType: 'text',
|
||||
okButtonClass: '',
|
||||
cancelButtonClass: '',
|
||||
skipBlur: false
|
||||
};
|
||||
|
||||
// Default locale
|
||||
BestInPlaceEditor.defaults.locales[''] = {
|
||||
confirmMessage: "Are you sure you want to discard your changes?",
|
||||
uninitializedForm: "The form was not properly initialized. getValue is unbound",
|
||||
placeHolder: '-'
|
||||
};
|
||||
|
||||
jQuery.fn.best_in_place = function () {
|
||||
'use strict';
|
||||
function setBestInPlace(element) {
|
||||
if (!element.data('bestInPlaceEditor')) {
|
||||
element.data('bestInPlaceEditor', new BestInPlaceEditor(element));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
jQuery(this.context).delegate(this.selector, 'click', function () {
|
||||
var el = jQuery(this);
|
||||
if (setBestInPlace(el)) {
|
||||
el.click();
|
||||
}
|
||||
});
|
||||
|
||||
this.each(function () {
|
||||
setBestInPlace(jQuery(this));
|
||||
});
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
|
||||
|
|
@ -1,780 +0,0 @@
|
|||
/*
|
||||
BestInPlace (for jQuery)
|
||||
version: 0.1.0 (01/01/2011)
|
||||
@requires jQuery >= v1.4
|
||||
@requires jQuery.purr to display pop-up windows
|
||||
|
||||
By Bernat Farrero based on the work of Jan Varwig.
|
||||
Examples at http://bernatfarrero.com
|
||||
|
||||
Licensed under the MIT:
|
||||
http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
Usage:
|
||||
|
||||
Attention.
|
||||
The format of the JSON object given to the select inputs is the following:
|
||||
[["key", "value"],["key", "value"]]
|
||||
The format of the JSON object given to the checkbox inputs is the following:
|
||||
["falseValue", "trueValue"]
|
||||
*/
|
||||
|
||||
|
||||
function BestInPlaceEditor(e) {
|
||||
this.element = e;
|
||||
this.initOptions();
|
||||
this.bindForm();
|
||||
this.initNil();
|
||||
jQuery(this.activator).bind('click', {editor: this}, this.clickHandler);
|
||||
}
|
||||
|
||||
BestInPlaceEditor.prototype = {
|
||||
// Public Interface Functions //////////////////////////////////////////////
|
||||
|
||||
activate : function() {
|
||||
var to_display = "";
|
||||
if (this.isNil()) {
|
||||
to_display = "";
|
||||
}
|
||||
else if (this.original_content) {
|
||||
to_display = this.original_content;
|
||||
}
|
||||
else {
|
||||
if (this.sanitize) {
|
||||
to_display = this.element.text();
|
||||
} else {
|
||||
to_display = this.element.html();
|
||||
}
|
||||
}
|
||||
|
||||
this.oldValue = this.isNil() ? "" : this.element.html();
|
||||
this.display_value = to_display;
|
||||
jQuery(this.activator).unbind("click", this.clickHandler);
|
||||
this.activateForm();
|
||||
this.element.trigger(jQuery.Event("best_in_place:activate"));
|
||||
},
|
||||
|
||||
abort : function() {
|
||||
this.activateText(this.oldValue);
|
||||
jQuery(this.activator).bind('click', {editor: this}, this.clickHandler);
|
||||
this.element.trigger(jQuery.Event("best_in_place:abort"));
|
||||
this.element.trigger(jQuery.Event("best_in_place:deactivate"));
|
||||
},
|
||||
|
||||
abortIfConfirm : function () {
|
||||
if (!this.useConfirm) {
|
||||
this.abort();
|
||||
return;
|
||||
}
|
||||
|
||||
if (confirm("Are you sure you want to discard your changes?")) {
|
||||
this.abort();
|
||||
}
|
||||
},
|
||||
|
||||
update : function() {
|
||||
var editor = this;
|
||||
if (this.formType in {"input":1, "textarea":1} && this.getValue() == this.oldValue)
|
||||
{ // Avoid request if no change is made
|
||||
this.abort();
|
||||
return true;
|
||||
}
|
||||
editor.ajax({
|
||||
"type" : "post",
|
||||
"dataType" : "text",
|
||||
"data" : editor.requestData(),
|
||||
"success" : function(data){ editor.loadSuccessCallback(data); },
|
||||
"error" : function(request, error){ editor.loadErrorCallback(request, error); }
|
||||
});
|
||||
if (this.formType == "select") {
|
||||
var value = this.getValue();
|
||||
this.previousCollectionValue = value;
|
||||
|
||||
jQuery.each(this.values, function(i, v) {
|
||||
if (value == v[0]) {
|
||||
editor.element.html(v[1]);
|
||||
}
|
||||
}
|
||||
);
|
||||
} else if (this.formType == "checkbox") {
|
||||
editor.element.html(this.getValue() ? this.values[1] : this.values[0]);
|
||||
} else {
|
||||
if (this.getValue() !== "") {
|
||||
editor.element.text(this.getValue());
|
||||
} else {
|
||||
editor.element.html(this.nil);
|
||||
}
|
||||
}
|
||||
editor.element.trigger(jQuery.Event("best_in_place:update"));
|
||||
},
|
||||
|
||||
activateForm : function() {
|
||||
alert("The form was not properly initialized. activateForm is unbound");
|
||||
},
|
||||
|
||||
activateText : function(value){
|
||||
this.element.html(value);
|
||||
if(this.isNil()) this.element.html(this.nil);
|
||||
},
|
||||
|
||||
// Helper Functions ////////////////////////////////////////////////////////
|
||||
|
||||
initOptions : function() {
|
||||
// Try parent supplied info
|
||||
var self = this;
|
||||
self.element.parents().each(function(){
|
||||
$parent = jQuery(this);
|
||||
self.url = self.url || $parent.attr("data-url");
|
||||
self.collection = self.collection || $parent.attr("data-collection");
|
||||
self.formType = self.formType || $parent.attr("data-type");
|
||||
self.objectName = self.objectName || $parent.attr("data-object");
|
||||
self.attributeName = self.attributeName || $parent.attr("data-attribute");
|
||||
self.activator = self.activator || $parent.attr("data-activator");
|
||||
self.okButton = self.okButton || $parent.attr("data-ok-button");
|
||||
self.okButtonClass = self.okButtonClass || $parent.attr("data-ok-button-class");
|
||||
self.cancelButton = self.cancelButton || $parent.attr("data-cancel-button");
|
||||
self.cancelButtonClass = self.cancelButtonClass || $parent.attr("data-cancel-button-class");
|
||||
self.nil = self.nil || $parent.attr("data-nil");
|
||||
self.inner_class = self.inner_class || $parent.attr("data-inner-class");
|
||||
self.html_attrs = self.html_attrs || $parent.attr("data-html-attrs");
|
||||
self.original_content = self.original_content || $parent.attr("data-original-content");
|
||||
self.collectionValue = self.collectionValue || $parent.attr("data-value");
|
||||
});
|
||||
|
||||
// Try Rails-id based if parents did not explicitly supply something
|
||||
self.element.parents().each(function(){
|
||||
var res = this.id.match(/^(\w+)_(\d+)$/i);
|
||||
if (res) {
|
||||
self.objectName = self.objectName || res[1];
|
||||
}
|
||||
});
|
||||
|
||||
// Load own attributes (overrides all others)
|
||||
self.url = self.element.attr("data-url") || self.url || document.location.pathname;
|
||||
self.collection = self.element.attr("data-collection") || self.collection;
|
||||
self.formType = self.element.attr("data-type") || self.formtype || "input";
|
||||
self.objectName = self.element.attr("data-object") || self.objectName;
|
||||
self.attributeName = self.element.attr("data-attribute") || self.attributeName;
|
||||
self.activator = self.element.attr("data-activator") || self.element;
|
||||
self.okButton = self.element.attr("data-ok-button") || self.okButton;
|
||||
self.okButtonClass = self.element.attr("data-ok-button-class") || self.okButtonClass || "";
|
||||
self.cancelButton = self.element.attr("data-cancel-button") || self.cancelButton;
|
||||
self.cancelButtonClass = self.element.attr("data-cancel-button-class") || self.cancelButtonClass || "";
|
||||
self.nil = self.element.attr("data-nil") || self.nil || "—";
|
||||
self.inner_class = self.element.attr("data-inner-class") || self.inner_class || null;
|
||||
self.html_attrs = self.element.attr("data-html-attrs") || self.html_attrs;
|
||||
self.original_content = self.element.attr("data-original-content") || self.original_content;
|
||||
self.collectionValue = self.element.attr("data-value") || self.collectionValue;
|
||||
|
||||
if (!self.element.attr("data-sanitize")) {
|
||||
self.sanitize = true;
|
||||
}
|
||||
else {
|
||||
self.sanitize = (self.element.attr("data-sanitize") == "true");
|
||||
}
|
||||
|
||||
if (!self.element.attr("data-use-confirm")) {
|
||||
self.useConfirm = true;
|
||||
} else {
|
||||
self.useConfirm = (self.element.attr("data-use-confirm") != "false");
|
||||
}
|
||||
|
||||
if ((self.formType == "select" || self.formType == "checkbox") && self.collection !== null)
|
||||
{
|
||||
self.values = jQuery.parseJSON(self.collection);
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
bindForm : function() {
|
||||
this.activateForm = BestInPlaceEditor.forms[this.formType].activateForm;
|
||||
this.getValue = BestInPlaceEditor.forms[this.formType].getValue;
|
||||
},
|
||||
|
||||
initNil: function() {
|
||||
if (this.element.html() === "")
|
||||
{
|
||||
this.element.html(this.nil);
|
||||
}
|
||||
},
|
||||
|
||||
isNil: function() {
|
||||
// TODO: It only work when form is deactivated.
|
||||
// Condition will fail when form is activated
|
||||
return this.element.html() === "" || this.element.html() === this.nil;
|
||||
},
|
||||
|
||||
getValue : function() {
|
||||
alert("The form was not properly initialized. getValue is unbound");
|
||||
},
|
||||
|
||||
// Trim and Strips HTML from text
|
||||
sanitizeValue : function(s) {
|
||||
return jQuery.trim(s);
|
||||
},
|
||||
|
||||
/* Generate the data sent in the POST request */
|
||||
requestData : function() {
|
||||
// To prevent xss attacks, a csrf token must be defined as a meta attribute
|
||||
csrf_token = jQuery('meta[name=csrf-token]').attr('content');
|
||||
csrf_param = jQuery('meta[name=csrf-param]').attr('content');
|
||||
|
||||
var data = "_method=put";
|
||||
data += "&" + this.objectName + '[' + this.attributeName + ']=' + encodeURIComponent(this.getValue());
|
||||
|
||||
if (csrf_param !== undefined && csrf_token !== undefined) {
|
||||
data += "&" + csrf_param + "=" + encodeURIComponent(csrf_token);
|
||||
}
|
||||
return data;
|
||||
},
|
||||
|
||||
ajax : function(options) {
|
||||
options.url = this.url;
|
||||
options.beforeSend = function(xhr){ xhr.setRequestHeader("Accept", "application/json"); };
|
||||
return jQuery.ajax(options);
|
||||
},
|
||||
|
||||
// Handlers ////////////////////////////////////////////////////////////////
|
||||
|
||||
loadSuccessCallback : function(data) {
|
||||
data = jQuery.trim(data);
|
||||
|
||||
if(data && data!=""){
|
||||
var response = jQuery.parseJSON(jQuery.trim(data));
|
||||
if (response !== null && response.hasOwnProperty("display_as")) {
|
||||
this.element.attr("data-original-content", this.element.text());
|
||||
this.original_content = this.element.text();
|
||||
this.element.html(response["display_as"]);
|
||||
}
|
||||
|
||||
this.element.trigger(jQuery.Event("best_in_place:success"), data);
|
||||
this.element.trigger(jQuery.Event("ajax:success"), data);
|
||||
} else {
|
||||
this.element.trigger(jQuery.Event("best_in_place:success"));
|
||||
this.element.trigger(jQuery.Event("ajax:success"));
|
||||
}
|
||||
|
||||
// Binding back after being clicked
|
||||
jQuery(this.activator).bind('click', {editor: this}, this.clickHandler);
|
||||
this.element.trigger(jQuery.Event("best_in_place:deactivate"));
|
||||
|
||||
if (this.collectionValue !== null && this.formType == "select") {
|
||||
this.collectionValue = this.previousCollectionValue;
|
||||
this.previousCollectionValue = null;
|
||||
}
|
||||
},
|
||||
|
||||
loadErrorCallback : function(request, error) {
|
||||
this.activateText(this.oldValue);
|
||||
|
||||
this.element.trigger(jQuery.Event("best_in_place:error"), [request, error]);
|
||||
this.element.trigger(jQuery.Event("ajax:error"), request, error);
|
||||
|
||||
// Binding back after being clicked
|
||||
jQuery(this.activator).bind('click', {editor: this}, this.clickHandler);
|
||||
this.element.trigger(jQuery.Event("best_in_place:deactivate"));
|
||||
},
|
||||
|
||||
clickHandler : function(event) {
|
||||
event.preventDefault();
|
||||
event.data.editor.activate();
|
||||
},
|
||||
|
||||
setHtmlAttributes : function() {
|
||||
var formField = this.element.find(this.formType);
|
||||
|
||||
if(this.html_attrs){
|
||||
var attrs = jQuery.parseJSON(this.html_attrs);
|
||||
for(var key in attrs){
|
||||
formField.attr(key, attrs[key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// Button cases:
|
||||
// If no buttons, then blur saves, ESC cancels
|
||||
// If just Cancel button, then blur saves, ESC or clicking Cancel cancels (careful of blur event!)
|
||||
// If just OK button, then clicking OK saves (careful of blur event!), ESC or blur cancels
|
||||
// If both buttons, then clicking OK saves, ESC or clicking Cancel or blur cancels
|
||||
BestInPlaceEditor.forms = {
|
||||
"input" : {
|
||||
activateForm : function() {
|
||||
var output = jQuery(document.createElement('form'))
|
||||
.addClass('form_in_place')
|
||||
.attr('action', 'javascript:void(0);')
|
||||
.attr('style', 'display:inline');
|
||||
var input_elt = jQuery(document.createElement('input'))
|
||||
.attr('type', 'text')
|
||||
.attr('name', this.attributeName)
|
||||
.val(this.display_value);
|
||||
if(this.inner_class !== null) {
|
||||
input_elt.addClass(this.inner_class);
|
||||
}
|
||||
output.append(input_elt);
|
||||
if(this.okButton) {
|
||||
output.append(
|
||||
jQuery(document.createElement('input'))
|
||||
.attr('type', 'submit')
|
||||
.attr('class', this.okButtonClass)
|
||||
.attr('value', this.okButton)
|
||||
)
|
||||
}
|
||||
if(this.cancelButton) {
|
||||
output.append(
|
||||
jQuery(document.createElement('input'))
|
||||
.attr('type', 'button')
|
||||
.attr('class', this.cancelButtonClass)
|
||||
.attr('value', this.cancelButton)
|
||||
)
|
||||
}
|
||||
|
||||
this.element.html(output);
|
||||
this.setHtmlAttributes();
|
||||
// START METAMAPS CODE
|
||||
//this.element.find("input[type='text']")[0].select();
|
||||
this.element.find("input[type='text']")[0].focus();
|
||||
// END METAMAPS CODE
|
||||
this.element.find("form").bind('submit', {editor: this}, BestInPlaceEditor.forms.input.submitHandler);
|
||||
if (this.cancelButton) {
|
||||
this.element.find("input[type='button']").bind('click', {editor: this}, BestInPlaceEditor.forms.input.cancelButtonHandler);
|
||||
}
|
||||
this.element.find("input[type='text']").bind('blur', {editor: this}, BestInPlaceEditor.forms.input.inputBlurHandler);
|
||||
// START METAMAPS CODE
|
||||
this.element.find("input[type='text']").bind('keydown', {editor: this}, BestInPlaceEditor.forms.input.keydownHandler);
|
||||
// END METAMAPS CODE
|
||||
this.element.find("input[type='text']").bind('keyup', {editor: this}, BestInPlaceEditor.forms.input.keyupHandler);
|
||||
this.blurTimer = null;
|
||||
this.userClicked = false;
|
||||
},
|
||||
|
||||
getValue : function() {
|
||||
return this.sanitizeValue(this.element.find("input").val());
|
||||
},
|
||||
|
||||
// When buttons are present, use a timer on the blur event to give precedence to clicks
|
||||
inputBlurHandler : function(event) {
|
||||
if (event.data.editor.okButton) {
|
||||
event.data.editor.blurTimer = setTimeout(function () {
|
||||
if (!event.data.editor.userClicked) {
|
||||
event.data.editor.abort();
|
||||
}
|
||||
}, 500);
|
||||
} else {
|
||||
if (event.data.editor.cancelButton) {
|
||||
event.data.editor.blurTimer = setTimeout(function () {
|
||||
if (!event.data.editor.userClicked) {
|
||||
event.data.editor.update();
|
||||
}
|
||||
}, 500);
|
||||
} else {
|
||||
event.data.editor.update();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
submitHandler : function(event) {
|
||||
event.data.editor.userClicked = true;
|
||||
clearTimeout(event.data.editor.blurTimer);
|
||||
event.data.editor.update();
|
||||
},
|
||||
|
||||
cancelButtonHandler : function(event) {
|
||||
event.data.editor.userClicked = true;
|
||||
clearTimeout(event.data.editor.blurTimer);
|
||||
event.data.editor.abort();
|
||||
event.stopPropagation(); // Without this, click isn't handled
|
||||
},
|
||||
|
||||
keyupHandler : function(event) {
|
||||
if (event.keyCode == 27) {
|
||||
event.data.editor.abort();
|
||||
}
|
||||
// START METAMAPS CODE
|
||||
else if (event.keyCode == 13 && !event.shiftKey) {
|
||||
event.data.editor.update();
|
||||
}
|
||||
// END METAMAPS CODE
|
||||
}
|
||||
},
|
||||
|
||||
"date" : {
|
||||
activateForm : function() {
|
||||
var that = this,
|
||||
output = jQuery(document.createElement('form'))
|
||||
.addClass('form_in_place')
|
||||
.attr('action', 'javascript:void(0);')
|
||||
.attr('style', 'display:inline'),
|
||||
input_elt = jQuery(document.createElement('input'))
|
||||
.attr('type', 'text')
|
||||
.attr('name', this.attributeName)
|
||||
.attr('value', this.sanitizeValue(this.display_value));
|
||||
if(this.inner_class !== null) {
|
||||
input_elt.addClass(this.inner_class);
|
||||
}
|
||||
output.append(input_elt)
|
||||
|
||||
this.element.html(output);
|
||||
this.setHtmlAttributes();
|
||||
this.element.find('input')[0].select();
|
||||
this.element.find("form").bind('submit', {editor: this}, BestInPlaceEditor.forms.input.submitHandler);
|
||||
this.element.find("input").bind('keyup', {editor: this}, BestInPlaceEditor.forms.input.keyupHandler);
|
||||
|
||||
this.element.find('input')
|
||||
.datepicker({
|
||||
onClose: function() {
|
||||
that.update();
|
||||
}
|
||||
})
|
||||
.datepicker('show');
|
||||
},
|
||||
|
||||
getValue : function() {
|
||||
return this.sanitizeValue(this.element.find("input").val());
|
||||
},
|
||||
|
||||
submitHandler : function(event) {
|
||||
event.data.editor.update();
|
||||
},
|
||||
|
||||
// START METAMAPS CODE
|
||||
keydownHandler : function(event) {
|
||||
if (event.keyCode == 13 && !event.shiftKey) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
return false;
|
||||
}
|
||||
},
|
||||
// END METAMAPS CODE
|
||||
|
||||
keyupHandler : function(event) {
|
||||
if (event.keyCode == 27) {
|
||||
event.data.editor.abort();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"select" : {
|
||||
activateForm : function() {
|
||||
var output = jQuery(document.createElement('form'))
|
||||
.attr('action', 'javascript:void(0)')
|
||||
.attr('style', 'display:inline');
|
||||
selected = '',
|
||||
oldValue = this.oldValue,
|
||||
select_elt = jQuery(document.createElement('select'))
|
||||
.attr('class', this.inned_class !== null ? this.inner_class : '' ),
|
||||
currentCollectionValue = this.collectionValue;
|
||||
|
||||
jQuery.each(this.values, function (index, value) {
|
||||
var option_elt = jQuery(document.createElement('option'))
|
||||
// .attr('value', value[0])
|
||||
.val(value[0])
|
||||
.html(value[1]);
|
||||
if(value[0] == currentCollectionValue) {
|
||||
option_elt.attr('selected', 'selected');
|
||||
}
|
||||
select_elt.append(option_elt);
|
||||
});
|
||||
output.append(select_elt);
|
||||
|
||||
this.element.html(output);
|
||||
this.setHtmlAttributes();
|
||||
this.element.find("select").bind('change', {editor: this}, BestInPlaceEditor.forms.select.blurHandler);
|
||||
this.element.find("select").bind('blur', {editor: this}, BestInPlaceEditor.forms.select.blurHandler);
|
||||
this.element.find("select").bind('keyup', {editor: this}, BestInPlaceEditor.forms.select.keyupHandler);
|
||||
this.element.find("select")[0].focus();
|
||||
},
|
||||
|
||||
getValue : function() {
|
||||
return this.sanitizeValue(this.element.find("select").val());
|
||||
// return this.element.find("select").val();
|
||||
},
|
||||
|
||||
blurHandler : function(event) {
|
||||
event.data.editor.update();
|
||||
},
|
||||
|
||||
keyupHandler : function(event) {
|
||||
if (event.keyCode == 27) event.data.editor.abort();
|
||||
}
|
||||
},
|
||||
|
||||
"checkbox" : {
|
||||
activateForm : function() {
|
||||
this.collectionValue = !this.getValue();
|
||||
this.setHtmlAttributes();
|
||||
this.update();
|
||||
},
|
||||
|
||||
getValue : function() {
|
||||
return this.collectionValue;
|
||||
}
|
||||
},
|
||||
|
||||
"textarea" : {
|
||||
activateForm : function() {
|
||||
// grab width and height of text
|
||||
width = this.element.css('width');
|
||||
height = this.element.css('height');
|
||||
|
||||
// construct form
|
||||
var output = jQuery(document.createElement('form'))
|
||||
.attr('action', 'javascript:void(0)')
|
||||
.attr('style', 'display:inline')
|
||||
.append(jQuery(document.createElement('textarea'))
|
||||
.val(this.sanitizeValue(this.display_value)));
|
||||
if(this.okButton) {
|
||||
output.append(
|
||||
jQuery(document.createElement('input'))
|
||||
.attr('type', 'submit')
|
||||
.attr('value', this.okButton)
|
||||
);
|
||||
}
|
||||
if(this.cancelButton) {
|
||||
output.append(
|
||||
jQuery(document.createElement('input'))
|
||||
.attr('type', 'button')
|
||||
.attr('value', this.cancelButton)
|
||||
)
|
||||
}
|
||||
|
||||
this.element.html(output);
|
||||
this.setHtmlAttributes();
|
||||
|
||||
// set width and height of textarea
|
||||
jQuery(this.element.find("textarea")[0]).css({ 'min-width': width, 'min-height': height });
|
||||
jQuery(this.element.find("textarea")[0]).elastic();
|
||||
|
||||
this.element.find("textarea")[0].focus();
|
||||
this.element.find("form").bind('submit', {editor: this}, BestInPlaceEditor.forms.textarea.submitHandler);
|
||||
if (this.cancelButton) {
|
||||
this.element.find("input[type='button']").bind('click', {editor: this}, BestInPlaceEditor.forms.textarea.cancelButtonHandler);
|
||||
}
|
||||
this.element.find("textarea").bind('blur', {editor: this}, BestInPlaceEditor.forms.textarea.blurHandler);
|
||||
// START METAMAPS CODE
|
||||
this.element.find("textarea").bind('keydown', {editor: this}, BestInPlaceEditor.forms.textarea.keydownHandler);
|
||||
// END METAMAPS CODE
|
||||
this.element.find("textarea").bind('keyup', {editor: this}, BestInPlaceEditor.forms.textarea.keyupHandler);
|
||||
this.blurTimer = null;
|
||||
this.userClicked = false;
|
||||
},
|
||||
|
||||
getValue : function() {
|
||||
return this.sanitizeValue(this.element.find("textarea").val());
|
||||
},
|
||||
|
||||
// When buttons are present, use a timer on the blur event to give precedence to clicks
|
||||
blurHandler : function(event) {
|
||||
if (event.data.editor.okButton) {
|
||||
event.data.editor.blurTimer = setTimeout(function () {
|
||||
if (!event.data.editor.userClicked) {
|
||||
event.data.editor.abortIfConfirm();
|
||||
}
|
||||
}, 500);
|
||||
} else {
|
||||
if (event.data.editor.cancelButton) {
|
||||
event.data.editor.blurTimer = setTimeout(function () {
|
||||
if (!event.data.editor.userClicked) {
|
||||
event.data.editor.update();
|
||||
}
|
||||
}, 500);
|
||||
} else {
|
||||
event.data.editor.update();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
submitHandler : function(event) {
|
||||
event.data.editor.userClicked = true;
|
||||
clearTimeout(event.data.editor.blurTimer);
|
||||
event.data.editor.update();
|
||||
},
|
||||
|
||||
cancelButtonHandler : function(event) {
|
||||
event.data.editor.userClicked = true;
|
||||
clearTimeout(event.data.editor.blurTimer);
|
||||
event.data.editor.abortIfConfirm();
|
||||
event.stopPropagation(); // Without this, click isn't handled
|
||||
},
|
||||
|
||||
// START METAMAPS CODE
|
||||
keydownHandler : function(event) {
|
||||
if (event.keyCode == 13 && !event.shiftKey) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
return false;
|
||||
}
|
||||
},
|
||||
// END METAMAPS CODE
|
||||
|
||||
keyupHandler : function(event) {
|
||||
if (event.keyCode == 27) {
|
||||
event.data.editor.abortIfConfirm();
|
||||
}
|
||||
// START METAMAPS CODE
|
||||
else if (event.keyCode == 13 && !event.shiftKey) {
|
||||
event.data.editor.update();
|
||||
}
|
||||
// END METAMAPS CODE
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
jQuery.fn.best_in_place = function() {
|
||||
|
||||
function setBestInPlace(element) {
|
||||
if (!element.data('bestInPlaceEditor')) {
|
||||
element.data('bestInPlaceEditor', new BestInPlaceEditor(element));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
jQuery(this.context).delegate(this.selector, 'click', function () {
|
||||
var el = jQuery(this);
|
||||
if (setBestInPlace(el))
|
||||
el.click();
|
||||
});
|
||||
|
||||
this.each(function () {
|
||||
setBestInPlace(jQuery(this));
|
||||
});
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @name Elastic
|
||||
* @descripton Elastic is Jquery plugin that grow and shrink your textareas automaticliy
|
||||
* @version 1.6.5
|
||||
* @requires Jquery 1.2.6+
|
||||
*
|
||||
* @author Jan Jarfalk
|
||||
* @author-email jan.jarfalk@unwrongest.com
|
||||
* @author-website http://www.unwrongest.com
|
||||
*
|
||||
* @licens MIT License - http://www.opensource.org/licenses/mit-license.php
|
||||
*/
|
||||
|
||||
(function(jQuery){
|
||||
if (typeof jQuery.fn.elastic !== 'undefined') return;
|
||||
|
||||
jQuery.fn.extend({
|
||||
elastic: function() {
|
||||
// We will create a div clone of the textarea
|
||||
// by copying these attributes from the textarea to the div.
|
||||
var mimics = [
|
||||
'paddingTop',
|
||||
'paddingRight',
|
||||
'paddingBottom',
|
||||
'paddingLeft',
|
||||
'fontSize',
|
||||
'lineHeight',
|
||||
'fontFamily',
|
||||
'width',
|
||||
'fontWeight'];
|
||||
|
||||
return this.each( function() {
|
||||
|
||||
// Elastic only works on textareas
|
||||
if ( this.type != 'textarea' ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var $textarea = jQuery(this),
|
||||
$twin = jQuery('<div />').css({'position': 'absolute','display':'none','word-wrap':'break-word'}),
|
||||
lineHeight = parseInt($textarea.css('line-height'),10) || parseInt($textarea.css('font-size'),'10'),
|
||||
minheight = parseInt($textarea.css('height'),10) || lineHeight*3,
|
||||
maxheight = parseInt($textarea.css('max-height'),10) || Number.MAX_VALUE,
|
||||
goalheight = 0,
|
||||
i = 0;
|
||||
|
||||
// Opera returns max-height of -1 if not set
|
||||
if (maxheight < 0) { maxheight = Number.MAX_VALUE; }
|
||||
|
||||
// Append the twin to the DOM
|
||||
// We are going to meassure the height of this, not the textarea.
|
||||
$twin.appendTo($textarea.parent());
|
||||
|
||||
// Copy the essential styles (mimics) from the textarea to the twin
|
||||
i = mimics.length;
|
||||
while(i--){
|
||||
$twin.css(mimics[i].toString(),$textarea.css(mimics[i].toString()));
|
||||
}
|
||||
|
||||
|
||||
// Sets a given height and overflow state on the textarea
|
||||
function setHeightAndOverflow(height, overflow){
|
||||
curratedHeight = Math.floor(parseInt(height,10));
|
||||
if($textarea.height() != curratedHeight){
|
||||
$textarea.css({'height': curratedHeight + 'px','overflow':overflow});
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// This function will update the height of the textarea if necessary
|
||||
function update() {
|
||||
|
||||
// Get curated content from the textarea.
|
||||
var textareaContent = $textarea.val().replace(/&/g,'&').replace(/ /g, ' ').replace(/<|>/g, '>').replace(/\n/g, '<br />');
|
||||
|
||||
// Compare curated content with curated twin.
|
||||
var twinContent = $twin.html().replace(/<br>/ig,'<br />');
|
||||
|
||||
if(textareaContent+' ' != twinContent){
|
||||
|
||||
// Add an extra white space so new rows are added when you are at the end of a row.
|
||||
$twin.html(textareaContent+' ');
|
||||
|
||||
// Change textarea height if twin plus the height of one line differs more than 3 pixel from textarea height
|
||||
if(Math.abs($twin.height() + lineHeight - $textarea.height()) > 3){
|
||||
|
||||
var goalheight = $twin.height()+lineHeight;
|
||||
if(goalheight >= maxheight) {
|
||||
setHeightAndOverflow(maxheight,'auto');
|
||||
} else if(goalheight <= minheight) {
|
||||
setHeightAndOverflow(minheight,'hidden');
|
||||
} else {
|
||||
setHeightAndOverflow(goalheight,'hidden');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Hide scrollbars
|
||||
$textarea.css({'overflow':'hidden'});
|
||||
|
||||
// Update textarea size on keyup, change, cut and paste
|
||||
$textarea.bind('keyup change cut paste', function(){
|
||||
update();
|
||||
});
|
||||
|
||||
// Compact textarea on blur
|
||||
// Lets animate this....
|
||||
$textarea.bind('blur',function(){
|
||||
if($twin.height() < maxheight){
|
||||
if($twin.height() > minheight) {
|
||||
$textarea.height($twin.height());
|
||||
} else {
|
||||
$textarea.height(minheight);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// And this line is to catch the browser paste event
|
||||
$textarea.on("input paste", function(e){ setTimeout( update, 250); });
|
||||
|
||||
// Run update once when elastic is initialized
|
||||
update();
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
});
|
||||
})(jQuery);
|
|
@ -1,180 +0,0 @@
|
|||
/**
|
||||
* jquery.purr.js
|
||||
* Copyright (c) 2008 Net Perspective (net-perspective.com)
|
||||
* Licensed under the MIT License (http://www.opensource.org/licenses/mit-license.php)
|
||||
*
|
||||
* @author R.A. Ray
|
||||
* @projectDescription jQuery plugin for dynamically displaying unobtrusive messages in the browser. Mimics the behavior of the MacOS program "Growl."
|
||||
* @version 0.1.0
|
||||
*
|
||||
* @requires jquery.js (tested with 1.2.6)
|
||||
*
|
||||
* @param fadeInSpeed int - Duration of fade in animation in miliseconds
|
||||
* default: 500
|
||||
* @param fadeOutSpeed int - Duration of fade out animationin miliseconds
|
||||
default: 500
|
||||
* @param removeTimer int - Timeout, in miliseconds, before notice is removed once it is the top non-sticky notice in the list
|
||||
default: 4000
|
||||
* @param isSticky bool - Whether the notice should fade out on its own or wait to be manually closed
|
||||
default: false
|
||||
* @param usingTransparentPNG bool - Whether or not the notice is using transparent .png images in its styling
|
||||
default: false
|
||||
*/
|
||||
|
||||
( function( $ ) {
|
||||
|
||||
$.purr = function ( notice, options )
|
||||
{
|
||||
// Convert notice to a jQuery object
|
||||
notice = $( notice );
|
||||
|
||||
// Add a class to denote the notice as not sticky
|
||||
if ( !options.isSticky )
|
||||
{
|
||||
notice.addClass( 'not-sticky' );
|
||||
};
|
||||
|
||||
// Get the container element from the page
|
||||
var cont = document.getElementById( 'purr-container' );
|
||||
|
||||
// If the container doesn't yet exist, we need to create it
|
||||
if ( !cont )
|
||||
{
|
||||
cont = '<div id="purr-container"></div>';
|
||||
}
|
||||
|
||||
// Convert cont to a jQuery object
|
||||
cont = $( cont );
|
||||
|
||||
// Add the container to the page
|
||||
$( 'body' ).append( cont );
|
||||
|
||||
notify();
|
||||
|
||||
function notify ()
|
||||
{
|
||||
// Set up the close button
|
||||
var close = document.createElement( 'a' );
|
||||
$( close ).attr(
|
||||
{
|
||||
className: 'close',
|
||||
href: '#close',
|
||||
innerHTML: 'Close'
|
||||
}
|
||||
)
|
||||
.appendTo( notice )
|
||||
.click( function ()
|
||||
{
|
||||
removeNotice();
|
||||
|
||||
return false;
|
||||
}
|
||||
);
|
||||
|
||||
// Add the notice to the page and keep it hidden initially
|
||||
notice.appendTo( cont )
|
||||
.hide();
|
||||
|
||||
if ( jQuery.browser.msie && options.usingTransparentPNG )
|
||||
{
|
||||
// IE7 and earlier can't handle the combination of opacity and transparent pngs, so if we're using transparent pngs in our
|
||||
// notice style, we'll just skip the fading in.
|
||||
notice.show();
|
||||
}
|
||||
else
|
||||
{
|
||||
//Fade in the notice we just added
|
||||
notice.fadeIn( options.fadeInSpeed );
|
||||
}
|
||||
|
||||
// Set up the removal interval for the added notice if that notice is not a sticky
|
||||
if ( !options.isSticky )
|
||||
{
|
||||
var topSpotInt = setInterval( function ()
|
||||
{
|
||||
// Check to see if our notice is the first non-sticky notice in the list
|
||||
if ( notice.prevAll( '.not-sticky' ).length == 0 )
|
||||
{
|
||||
// Stop checking once the condition is met
|
||||
clearInterval( topSpotInt );
|
||||
|
||||
// Call the close action after the timeout set in options
|
||||
setTimeout( function ()
|
||||
{
|
||||
removeNotice();
|
||||
}, options.removeTimer
|
||||
);
|
||||
}
|
||||
}, 200 );
|
||||
}
|
||||
}
|
||||
|
||||
function removeNotice ()
|
||||
{
|
||||
// IE7 and earlier can't handle the combination of opacity and transparent pngs, so if we're using transparent pngs in our
|
||||
// notice style, we'll just skip the fading out.
|
||||
if ( jQuery.browser.msie && options.usingTransparentPNG )
|
||||
{
|
||||
notice.css( { opacity: 0 } )
|
||||
.animate(
|
||||
{
|
||||
height: '0px'
|
||||
},
|
||||
{
|
||||
duration: options.fadeOutSpeed,
|
||||
complete: function ()
|
||||
{
|
||||
notice.remove();
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Fade the object out before reducing its height to produce the sliding effect
|
||||
notice.animate(
|
||||
{
|
||||
opacity: '0'
|
||||
},
|
||||
{
|
||||
duration: options.fadeOutSpeed,
|
||||
complete: function ()
|
||||
{
|
||||
notice.animate(
|
||||
{
|
||||
height: '0px'
|
||||
},
|
||||
{
|
||||
duration: options.fadeOutSpeed,
|
||||
complete: function ()
|
||||
{
|
||||
notice.remove();
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
$.fn.purr = function ( options )
|
||||
{
|
||||
options = options || {};
|
||||
options.fadeInSpeed = options.fadeInSpeed || 500;
|
||||
options.fadeOutSpeed = options.fadeOutSpeed || 500;
|
||||
options.removeTimer = options.removeTimer || 4000;
|
||||
options.isSticky = options.isSticky || false;
|
||||
options.usingTransparentPNG = options.usingTransparentPNG || false;
|
||||
|
||||
this.each( function()
|
||||
{
|
||||
new $.purr( this, options );
|
||||
}
|
||||
);
|
||||
|
||||
return this;
|
||||
};
|
||||
})( jQuery );
|
||||
|
|
@ -143,6 +143,16 @@
|
|||
margin-top:5px;
|
||||
}
|
||||
|
||||
.CardOnGraph .desc ol,
|
||||
.CardOnGraph .desc ul {
|
||||
margin-left: 1em;
|
||||
|
||||
}
|
||||
.CardOnGraph .desc a:hover {
|
||||
text-decoration: underline;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.CardOnGraph .best_in_place_desc {
|
||||
display:block;
|
||||
margin-top:2px;
|
||||
|
|
|
@ -183,11 +183,12 @@
|
|||
<span class="title">
|
||||
<div class="titleWrapper" id="titleActivator">
|
||||
<span class="best_in_place best_in_place_name"
|
||||
data-url="/topics/{{id}}"
|
||||
data-object="topic"
|
||||
data-attribute="name"
|
||||
data-activator="#titleActivator"
|
||||
data-type="textarea">{{name}}</span>
|
||||
data-bip-url="/topics/{{id}}"
|
||||
data-bip-object="topic"
|
||||
data-bip-attribute="name"
|
||||
data-bip-activator="#titleActivator"
|
||||
data-bip-value="{{name}}"
|
||||
data-bip-type="textarea">{{name}}</span>
|
||||
</div>
|
||||
</span>
|
||||
<div class="links">
|
||||
|
@ -220,7 +221,7 @@
|
|||
</div>
|
||||
<div class="scroll">
|
||||
<div class="desc">
|
||||
<span class="best_in_place best_in_place_desc" data-url="/topics/{{id}}" data-object="topic" data-nil="{{desc_nil}}" data-attribute="desc" data-type="textarea">{{desc}}</span>
|
||||
<span class="best_in_place best_in_place_desc" data-bip-url="/topics/{{id}}" data-bip-object="topic" data-bip-nil="{{desc_nil}}" data-bip-attribute="desc" data-bip-type="textarea" data-bip-value="{{desc_markdown}}">{{{desc_html}}}</span>
|
||||
<div class="clearfloat"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
<% if @map %>
|
||||
<div class="mapInfoName" id="mapInfoName">
|
||||
<% if policy(@map).update? %>
|
||||
<span class="best_in_place best_in_place_name" id="best_in_place_map_<%= @map.id %>_name" data-url="/maps/<%= @map.id %>" data-object="map" data-attribute="name" data-type="textarea" data-activator="#mapInfoName"><%= @map.name %></span>
|
||||
<span class="best_in_place best_in_place_name" id="best_in_place_map_<%= @map.id %>_name" data-bip-url="/maps/<%= @map.id %>" data-bip-object="map" data-bip-attribute="name" data-bip-type="textarea" data-bip-activator="#mapInfoName" data-bip-value="<%= @map.name %>"><%= @map.name %></span>
|
||||
<% else %>
|
||||
<%= @map.name %>
|
||||
<% end %>
|
||||
|
@ -67,7 +67,7 @@
|
|||
|
||||
<div class="mapInfoDesc" id="mapInfoDesc">
|
||||
<% if policy(@map).update? %>
|
||||
<span class="best_in_place best_in_place_desc" id="best_in_place_map_<%= @map.id %>_desc" data-url="/maps/<%= @map.id %>" data-object="map" data-attribute="desc" data-nil="Click to add description..." data-type="textarea" data-activator="#mapInfoDesc"><%= @map.desc %></span>
|
||||
<span class="best_in_place best_in_place_desc" id="best_in_place_map_<%= @map.id %>_desc" data-bip-url="/maps/<%= @map.id %>" data-bip-object="map" data-bip-attribute="desc" data-bip-nil="Click to add description..." data-bip-type="textarea" data-bip-activator="#mapInfoDesc" data-bip-value="<%= @map.desc %>"><%= @map.desc %></span>
|
||||
<% else %>
|
||||
<%= @map.desc %>
|
||||
<% end %>
|
||||
|
|
|
@ -23,7 +23,6 @@ const Listeners = {
|
|||
if (e.target.className !== 'chat-input') {
|
||||
JIT.enterKeyHandler()
|
||||
}
|
||||
e.preventDefault()
|
||||
break
|
||||
case 27: // if esc key is pressed
|
||||
JIT.escKeyHandler()
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
/* global Metamaps, $, Hogan, Bloodhound, Countable */
|
||||
|
||||
import outdent from 'outdent'
|
||||
|
||||
import Active from '../Active'
|
||||
import GlobalUI from '../GlobalUI'
|
||||
import Router from '../Router'
|
||||
|
@ -19,8 +21,27 @@ const InfoBox = {
|
|||
changing: false,
|
||||
selectingPermission: false,
|
||||
changePermissionText: "<div class='tooltips'>As the creator, you can change the permission of this map, and the permission of all the topics and synapses you have authority to change will change as well.</div>",
|
||||
nameHTML: '<span class="best_in_place best_in_place_name" id="best_in_place_map_{{id}}_name" data-url="/maps/{{id}}" data-object="map" data-attribute="name" data-type="textarea" data-activator="#mapInfoName">{{name}}</span>',
|
||||
descHTML: '<span class="best_in_place best_in_place_desc" id="best_in_place_map_{{id}}_desc" data-url="/maps/{{id}}" data-object="map" data-attribute="desc" data-nil="Click to add description..." data-type="textarea" data-activator="#mapInfoDesc">{{desc}}</span>',
|
||||
nameHTML: outdent`
|
||||
<span class="best_in_place best_in_place_name"
|
||||
id="best_in_place_map_{{id}}_name"
|
||||
data-bip-url="/maps/{{id}}"
|
||||
data-bip-object="map"
|
||||
data-bip-attribute="name"
|
||||
data-bip-type="textarea"
|
||||
data-bip-activator="#mapInfoName"
|
||||
data-bip-value="{{name}}"
|
||||
>{{name}}</span>`,
|
||||
descHTML: outdent`
|
||||
<span class="best_in_place best_in_place_desc"
|
||||
id="best_in_place_map_{{id}}_desc"
|
||||
data-bip-url="/maps/{{id}}"
|
||||
data-bip-object="map"
|
||||
data-bip-attribute="desc"
|
||||
data-bip-nil="Click to add description..."
|
||||
data-bip-type="textarea"
|
||||
data-bip-activator="#mapInfoDesc"
|
||||
data-bip-value="{{desc}}"
|
||||
>{{desc}}</span>`,
|
||||
init: function () {
|
||||
var self = InfoBox
|
||||
|
||||
|
@ -152,6 +173,13 @@ const InfoBox = {
|
|||
Active.Map.trigger('saved')
|
||||
})
|
||||
|
||||
$('.mapInfoDesc .best_in_place_desc, .mapInfoName .best_in_place_name').unbind('keypress').keypress(function(e) {
|
||||
const ENTER = 13
|
||||
if (e.which === ENTER) {
|
||||
$(this).data('bestInPlaceEditor').update()
|
||||
}
|
||||
})
|
||||
|
||||
$('.yourMap .mapPermission').unbind().click(self.onPermissionClick)
|
||||
// .yourMap in the unbind/bind is just a namespace for the events
|
||||
// not a reference to the class .yourMap on the .mapInfoBox
|
||||
|
|
|
@ -80,11 +80,12 @@ const SynapseCard = {
|
|||
// desc editing form
|
||||
$('#editSynUpperBar').append('<div id="edit_synapse_desc"></div>')
|
||||
$('#edit_synapse_desc').attr('class', 'best_in_place best_in_place_desc')
|
||||
$('#edit_synapse_desc').attr('data-object', 'synapse')
|
||||
$('#edit_synapse_desc').attr('data-attribute', 'desc')
|
||||
$('#edit_synapse_desc').attr('data-type', 'textarea')
|
||||
$('#edit_synapse_desc').attr('data-nil', data_nil)
|
||||
$('#edit_synapse_desc').attr('data-url', '/synapses/' + synapse.id)
|
||||
$('#edit_synapse_desc').attr('data-bip-object', 'synapse')
|
||||
$('#edit_synapse_desc').attr('data-bip-attribute', 'desc')
|
||||
$('#edit_synapse_desc').attr('data-bip-type', 'textarea')
|
||||
$('#edit_synapse_desc').attr('data-bip-nil', data_nil)
|
||||
$('#edit_synapse_desc').attr('data-bip-url', '/synapses/' + synapse.id)
|
||||
$('#edit_synapse_desc').attr('data-bip-value', synapse.get('desc'))
|
||||
$('#edit_synapse_desc').html(synapse.get('desc'))
|
||||
|
||||
// if edge data is blank or just whitespace, populate it with data_nil
|
||||
|
@ -96,6 +97,12 @@ const SynapseCard = {
|
|||
}
|
||||
}
|
||||
|
||||
$('#edit_synapse_desc').keypress(function (e) {
|
||||
const ENTER = 13
|
||||
if (e.which === ENTER) {
|
||||
$(this).data('bestInPlaceEditor').update()
|
||||
}
|
||||
})
|
||||
$('#edit_synapse_desc').bind('ajax:success', function () {
|
||||
var desc = $(this).html()
|
||||
if (desc == data_nil) {
|
||||
|
|
|
@ -265,6 +265,12 @@ const TopicCard = {
|
|||
bipName.bind('best_in_place:deactivate', function () {
|
||||
$('.nameCounter.forTopic').remove()
|
||||
})
|
||||
bipName.keypress(function(e) {
|
||||
const ENTER = 13
|
||||
if (e.which === ENTER) { // enter
|
||||
$(this).data('bestInPlaceEditor').update()
|
||||
}
|
||||
})
|
||||
|
||||
// bind best_in_place ajax callbacks
|
||||
bipName.bind('ajax:success', function () {
|
||||
|
@ -273,12 +279,24 @@ const TopicCard = {
|
|||
topic.trigger('saved')
|
||||
})
|
||||
|
||||
$(showCard).find('.best_in_place_desc').bind('ajax:success', function () {
|
||||
this.innerHTML = this.innerHTML.replace(/\r/g, '')
|
||||
var desc = $(this).html() === $(this).data('nil') ? '' : $(this).html()
|
||||
// this is for all subsequent renders after in-place editing the desc field
|
||||
const bipDesc = $(showCard).find('.best_in_place_desc')
|
||||
bipDesc.bind('ajax:success', function () {
|
||||
var desc = $(this).html() === $(this).data('bip-nil')
|
||||
? ''
|
||||
: $(this).text()
|
||||
topic.set('desc', desc)
|
||||
$(this).data('bip-value', desc)
|
||||
this.innerHTML = Util.mdToHTML(desc)
|
||||
topic.trigger('saved')
|
||||
})
|
||||
bipDesc.keypress(function(e) {
|
||||
// allow typing Enter with Shift+Enter
|
||||
const ENTER = 13
|
||||
if (e.shiftKey === false && e.which === ENTER) {
|
||||
$(this).data('bestInPlaceEditor').update()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
var permissionLiClick = function (event) {
|
||||
|
@ -397,8 +415,6 @@ const TopicCard = {
|
|||
} else {
|
||||
}
|
||||
|
||||
var desc_nil = 'Click to add description...'
|
||||
|
||||
nodeValues.attachmentsHidden = ''
|
||||
if (topic.get('link') && topic.get('link') !== '') {
|
||||
nodeValues.embeds = '<a href="' + topic.get('link') + '" id="embedlyLink" target="_blank" data-card-description="0">'
|
||||
|
@ -454,8 +470,11 @@ const TopicCard = {
|
|||
nodeValues.date = topic.getDate()
|
||||
// the code for this is stored in /views/main/_metacodeOptions.html.erb
|
||||
nodeValues.metacode_select = $('#metacodeOptions').html()
|
||||
nodeValues.desc_nil = desc_nil
|
||||
nodeValues.desc = (topic.get('desc') == '' && authorized) ? desc_nil : topic.get('desc')
|
||||
nodeValues.desc_nil = 'Click to add description...'
|
||||
nodeValues.desc_markdown = (topic.get('desc') === '' && authorized)
|
||||
? nodeValues.desc_nil
|
||||
: topic.get('desc')
|
||||
nodeValues.desc_html = Util.mdToHTML(nodeValues.desc_markdown)
|
||||
return nodeValues
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import { Parser, HtmlRenderer } from 'commonmark'
|
||||
|
||||
import Visualize from './Visualize'
|
||||
|
||||
const Util = {
|
||||
|
@ -119,6 +121,9 @@ const Util = {
|
|||
},
|
||||
checkURLisYoutubeVideo: function (url) {
|
||||
return (url.match(/^https?:\/\/(?:www\.)?youtube.com\/watch\?(?=[^?]*v=\w+)(?:[^\s?]+)?$/) != null)
|
||||
},
|
||||
mdToHTML: text => {
|
||||
return new HtmlRenderer().render(new Parser().parse(text))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -26,8 +26,9 @@
|
|||
"babel-preset-es2015": "6.14.0",
|
||||
"babel-preset-react": "6.11.1",
|
||||
"backbone": "1.0.0",
|
||||
"underscore": "1.4.4",
|
||||
"commonmark": "0.26.0",
|
||||
"csv-parse": "1.1.7",
|
||||
"json-loader": "0.5.4",
|
||||
"lodash": "4.16.1",
|
||||
"node-uuid": "1.4.7",
|
||||
"outdent": "0.2.1",
|
||||
|
@ -35,6 +36,7 @@
|
|||
"react-dom": "15.3.2",
|
||||
"react-dropzone": "3.6.0",
|
||||
"socket.io": "0.9.12",
|
||||
"underscore": "1.4.4",
|
||||
"webpack": "1.13.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
@ -21,6 +21,9 @@ const config = module.exports = {
|
|||
plugins,
|
||||
devtool,
|
||||
module: {
|
||||
preLoaders: [
|
||||
{ test: /\.json$/, loader: 'json' }
|
||||
],
|
||||
loaders: [
|
||||
{
|
||||
test: /\.(js|jsx)?$/,
|
||||
|
|
Loading…
Reference in a new issue