diff --git a/app/assets/javascripts/lib/best_in_place.js b/app/assets/javascripts/lib/best_in_place.js new file mode 100644 index 00000000..5000103f --- /dev/null +++ b/app/assets/javascripts/lib/best_in_place.js @@ -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; +}; + + + diff --git a/app/assets/javascripts/lib/bip.js b/app/assets/javascripts/lib/bip.js deleted file mode 100644 index 1d575fef..00000000 --- a/app/assets/javascripts/lib/bip.js +++ /dev/null @@ -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('
').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, '
'); - - // Compare curated content with curated twin. - var twinContent = $twin.html().replace(/
/ig,'
'); - - 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); diff --git a/app/assets/javascripts/lib/jquery.purr.js b/app/assets/javascripts/lib/jquery.purr.js deleted file mode 100644 index 1972165b..00000000 --- a/app/assets/javascripts/lib/jquery.purr.js +++ /dev/null @@ -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 = '
'; - } - - // 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 ); - diff --git a/app/assets/stylesheets/base.css.erb b/app/assets/stylesheets/base.css.erb index 5b0fcf84..6cfb6b57 100644 --- a/app/assets/stylesheets/base.css.erb +++ b/app/assets/stylesheets/base.css.erb @@ -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; diff --git a/app/views/layouts/_templates.html.erb b/app/views/layouts/_templates.html.erb index ff41c7dc..921a7d88 100644 --- a/app/views/layouts/_templates.html.erb +++ b/app/views/layouts/_templates.html.erb @@ -183,11 +183,12 @@
{{name}} + 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}}
- {{desc}} + {{{desc_html}}}
diff --git a/app/views/maps/_mapinfobox.html.erb b/app/views/maps/_mapinfobox.html.erb index 5a158d2d..8e6b2dba 100644 --- a/app/views/maps/_mapinfobox.html.erb +++ b/app/views/maps/_mapinfobox.html.erb @@ -16,7 +16,7 @@ <% if @map %>
<% if policy(@map).update? %> - <%= @map.name %> + <%= @map.name %> <% else %> <%= @map.name %> <% end %> @@ -67,7 +67,7 @@
<% if policy(@map).update? %> - <%= @map.desc %> + <%= @map.desc %> <% else %> <%= @map.desc %> <% end %> diff --git a/frontend/src/Metamaps/Listeners.js b/frontend/src/Metamaps/Listeners.js index cf3365f3..db78323d 100644 --- a/frontend/src/Metamaps/Listeners.js +++ b/frontend/src/Metamaps/Listeners.js @@ -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() diff --git a/frontend/src/Metamaps/Map/InfoBox.js b/frontend/src/Metamaps/Map/InfoBox.js index 0d3a5c5f..79fa6c4d 100644 --- a/frontend/src/Metamaps/Map/InfoBox.js +++ b/frontend/src/Metamaps/Map/InfoBox.js @@ -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: "
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.
", - nameHTML: '{{name}}', - descHTML: '{{desc}}', + nameHTML: outdent` + {{name}}`, + descHTML: outdent` + {{desc}}`, 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 diff --git a/frontend/src/Metamaps/SynapseCard.js b/frontend/src/Metamaps/SynapseCard.js index 8203657d..303b98cf 100644 --- a/frontend/src/Metamaps/SynapseCard.js +++ b/frontend/src/Metamaps/SynapseCard.js @@ -80,11 +80,12 @@ const SynapseCard = { // desc editing form $('#editSynUpperBar').append('
') $('#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) { diff --git a/frontend/src/Metamaps/TopicCard.js b/frontend/src/Metamaps/TopicCard.js index 40c51fbd..0956c07b 100644 --- a/frontend/src/Metamaps/TopicCard.js +++ b/frontend/src/Metamaps/TopicCard.js @@ -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 = '' @@ -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 } } diff --git a/frontend/src/Metamaps/Util.js b/frontend/src/Metamaps/Util.js index 9eb715de..f1f8b39c 100644 --- a/frontend/src/Metamaps/Util.js +++ b/frontend/src/Metamaps/Util.js @@ -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)) } } diff --git a/package.json b/package.json index 82cd120d..8b434d2a 100644 --- a/package.json +++ b/package.json @@ -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": { diff --git a/webpack.config.js b/webpack.config.js index 91498abd..644ff002 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -21,6 +21,9 @@ const config = module.exports = { plugins, devtool, module: { + preLoaders: [ + { test: /\.json$/, loader: 'json' } + ], loaders: [ { test: /\.(js|jsx)?$/,