"
+ }
+ ]);
+
+ $('#synapse_desc').bind('typeahead:selected', function (event, datum, dataset) {
+ if (datum.id) { // if they clicked on an existing synapse get it
+ Metamaps.Synapse.getSynapseFromAutocomplete(datum.id);
+ }
+ });
+ },
+ beingCreated: false,
+ description: null,
+ topic1id: null,
+ topic2id: null,
+ newSynapseId: null,
+ open: function () {
+ $('#new_synapse').fadeIn('fast', function () {
+ $('#synapse_desc').focus();
+ });
+ Metamaps.Create.newSynapse.beingCreated = true;
+ },
+ hide: function () {
+ $('#new_synapse').fadeOut('fast');
+ $("#synapse_desc").typeahead('setQuery', '');
+ Metamaps.Create.newSynapse.beingCreated = false;
+ Metamaps.Create.newTopic.addSynapse = false;
+ Metamaps.Create.newSynapse.topic1id = 0;
+ Metamaps.Create.newSynapse.topic2id = 0;
+ },
+ getSearchQuery: function () {
+ var self = Metamaps.Create.newSynapse;
+
+ if (Metamaps.Selected.Nodes.length < 2) {
+ return '/search/synapses?topic1id=' + self.topic1id + '&topic2id=' + self.topic2id;
+ } else return '';
+ }
+ }
+}; // end Metamaps.Create
+
+
+////////////////// TOPIC AND SYNAPSE CARDS //////////////////////////
+
+
+/*
+ *
+ * TOPICCARD
+ *
+ */
+Metamaps.TopicCard = {
+ openTopicCard: null, //stores the JIT local ID of the topic with the topic card open
+ init: function () {
+
+ // initialize best_in_place editing
+ $('.authenticated div.permission.canEdit .best_in_place').best_in_place();
+
+ Metamaps.TopicCard.generateShowcardHTML = Hogan.compile($('#topicCardTemplate').html());
+
+ // initialize topic card draggability and resizability
+ $('.showcard').draggable({
+ handle: ".metacodeImage"
+ });
+ $('#showcard').resizable({
+ maxHeight: 500,
+ maxWidth: 500,
+ minHeight: 320,
+ minWidth: 226,
+ resize: function (event, ui) {
+ var p = $('#showcard').find('.scroll');
+ p.height(p.height()).mCustomScrollbar('update');
+ }
+ }).css({
+ display: 'none',
+ top: '300px',
+ left: '100px'
+ });
+ },
+ fadeInShowCard: function (topic) {
+ $('.showcard').fadeIn('fast');
+ Metamaps.TopicCard.openTopicCard = topic.isNew() ? topic.cid : topic.id;
+ },
+ /**
+ * Will open the Topic Card for the node that it's passed
+ * @param {$jit.Graph.Node} node
+ */
+ showCard: function (node) {
+
+ var topic = node.getData('topic');
+
+ //populate the card that's about to show with the right topics data
+ Metamaps.TopicCard.populateShowCard(topic);
+ Metamaps.TopicCard.fadeInShowCard(topic);
+ },
+ hideCard: function () {
+ $('.showcard').fadeOut('fast');
+ Metamaps.TopicCard.openTopicCard = null;
+ },
+ bindShowCardListeners: function (topic) {
+ var self = Metamaps.TopicCard;
+ var showCard = document.getElementById('showcard');
+
+ var selectingMetacode = false;
+ // attach the listener that shows the metacode title when you hover over the image
+ $('.showcard .metacodeImage').mouseenter(function () {
+ $('.showcard .icon').css('z-index', '4');
+ $('.showcard .metacodeTitle').show();
+ });
+ $('.showcard .linkItem.icon').mouseleave(function () {
+ if (!selectingMetacode) {
+ $('.showcard .metacodeTitle').hide();
+ $('.showcard .icon').css('z-index', '1');
+ }
+ });
+
+ $('.showcard .metacodeTitle').click(function () {
+ if (!selectingMetacode) {
+ selectingMetacode = true;
+ $(this).addClass('minimize'); // this line flips the drop down arrow to a pull up arrow
+ $('.metacodeSelect').show();
+ // add the scroll bar to the list of metacode select options if it isn't already there
+ if (!$('.metacodeSelect ul').hasClass('mCustomScrollbar')) {
+ $('.metacodeSelect ul').mCustomScrollbar({
+ mouseWheelPixels: 200,
+ advanced: {
+ updateOnContentResize: true
+ }
+ });
+
+ $('.metacodeSelect li').click(function () {
+ selectingMetacode = false;
+ var metacodeName = $(this).find('.mSelectName').text();
+ var metacode = Metamaps.Metacodes.findWhere({
+ name: metacodeName
+ });
+ $('.CardOnGraph').find('.metacodeTitle').text(metacodeName)
+ .attr('class', 'metacodeTitle mbg' + metacodeName.replace(/\s/g, ''));
+ $('.CardOnGraph').find('.metacodeImage').css('background-image', 'url(' + metacode.get('icon') + ')');
+ topic.save({
+ metacode_id: metacode.id
+ });
+ Metamaps.Visualize.mGraph.plot();
+ $('.metacodeTitle').removeClass('minimize'); // this line flips the pull up arrow to a drop down arrow
+ $('.metacodeSelect').hide();
+ setTimeout(function () {
+ $('.metacodeTitle').hide();
+ $('.showcard .icon').css('z-index', '1');
+ }, 500);
+ });
+ }
+ } else {
+ selectingMetacode = false;
+ $(this).removeClass('minimize'); // this line flips the pull up arrow to a drop down arrow
+ $('.metacodeSelect').hide();
+ }
+ });
+
+
+ // ability to change permission
+ var selectingPermission = false;
+ if (topic.authorizePermissionChange(Metamaps.Active.Mapper)) {
+ $('.showcard .yourTopic .mapPerm').click(function () {
+ if (!selectingPermission) {
+ selectingPermission = true;
+ $(this).addClass('minimize'); // this line flips the drop down arrow to a pull up arrow
+ if ($(this).hasClass('co')) {
+ $(this).append('
');
+ } else if ($(this).hasClass('pu')) {
+ $(this).append('
');
+ } else if ($(this).hasClass('pr')) {
+ $(this).append('
');
+ }
+ $('.permissionSelect li').click(function (event) {
+ selectingPermission = false;
+ var permission = $(this).attr('class');
+ topic.save({
+ permission: permission
+ });
+ $('.showcard .mapPerm').removeClass('co pu pr minimize').addClass(permission.substring(0, 2));
+ $('.permissionSelect').remove();
+ event.stopPropagation();
+ });
+ } else {
+ selectingPermission = false;
+ $(this).removeClass('minimize'); // this line flips the pull up arrow to a drop down arrow
+ $('.permissionSelect').remove();
+ }
+ });
+ }
+
+ // when you're typing a description, resize the scroll box to have space
+ $('.best_in_place_desc textarea').bind('keyup', function () {
+ var s = $('.showcard').find('.scroll');
+ s.height(s.height()).mCustomScrollbar('update');
+ });
+
+ //bind best_in_place ajax callbacks
+ $(showCard).find('.best_in_place_name').bind("ajax:success", function () {
+
+ var s = $('.showcard').find('.scroll');
+ s.height(s.height()).mCustomScrollbar('update');
+
+ var name = $(this).html();
+ topic.set("name", Metamaps.Util.decodeEntities(name));
+ Metamaps.Visualize.mGraph.plot();
+ });
+
+ $(showCard).find('.best_in_place_desc').bind("ajax:success", function () {
+ this.innerHTML = this.innerHTML.replace(/\r/g, '')
+
+ var s = $('.showcard').find('.scroll');
+ s.height(s.height()).mCustomScrollbar('update');
+
+ var desc = $(this).html();
+ topic.set("desc", desc);
+ });
+
+ $(showCard).find('.best_in_place_link').bind("ajax:success", function () {
+ var link = $(this).html();
+ $(showCard).find('.go-link').attr('href', link);
+ topic.set("link", link);
+ });
+ },
+ populateShowCard: function (topic) {
+ var self = Metamaps.TopicCard;
+
+ var showCard = document.getElementById('showcard');
+
+ $(showCard).find('.permission').remove();
+
+ var html = self.generateShowcardHTML.render(self.buildObject(topic));
+
+ if (topic.authorizeToEdit(Metamaps.Active.Mapper)) {
+ var perm = document.createElement('div');
+
+ var string = 'permission canEdit';
+ if (topic.authorizePermissionChange(Metamaps.Active.Mapper)) string += ' yourTopic';
+ perm.className = string;
+ perm.innerHTML = html;
+ showCard.appendChild(perm);
+ } else {
+ var perm = document.createElement('div');
+ perm.className = 'permission cannotEdit';
+ perm.innerHTML = html;
+ showCard.appendChild(perm);
+ }
+
+ Metamaps.TopicCard.bindShowCardListeners(topic);
+ },
+ generateShowcardHTML: null, // will be initialized into a Hogan template within init function
+ //generateShowcardHTML
+ buildObject: function (topic) {
+ var nodeValues = {};
+ var authorized = topic.authorizeToEdit(Metamaps.Active.Mapper);
+
+ //link is rendered differently if user is logged out or in
+ var go_link, a_tag, close_a_tag;
+ if (!authorized) {
+ go_link = '';
+ if (topic.get("link") != "") {
+ a_tag = '';
+ close_a_tag = '';
+ } else {
+ a_tag = '';
+ close_a_tag = '';
+ }
+ } else {
+ go_link = '';
+ a_tag = '';
+ close_a_tag = '';
+ }
+
+ var desc_nil = "Click to add description...";
+ var link_nil = "Click to add link...";
+
+ nodeValues.permission = topic.get("permission");
+ nodeValues.mk_permission = topic.get("permission").substring(0, 2);
+ //nodeValues.map_count = topic.get("inmaps").length;
+ //nodeValues.synapse_count = topic.get("synapseCount");
+ nodeValues.id = topic.isNew() ? topic.cid : topic.id;
+ nodeValues.metacode = topic.getMetacode().get("name");
+ nodeValues.metacode_class = 'mbg' + topic.getMetacode().get("name").replace(/\s/g, '');
+ nodeValues.imgsrc = topic.getMetacode().get("icon");
+ nodeValues.name = topic.get("name");
+ nodeValues.userid = topic.get("user_id");
+ nodeValues.username = topic.getUser().get("name");
+ nodeValues.date = topic.getDate();
+
+ // the code for this is stored in /views/main/_metacodeOptions.html.erb
+ nodeValues.metacode_select = $('#metacodeOptions').html();
+ nodeValues.go_link = go_link;
+ nodeValues.a_tag = a_tag;
+ nodeValues.close_a_tag = close_a_tag;
+ nodeValues.link_nil = link_nil;
+ nodeValues.link = (topic.get("link") == "" && authorized) ? link_nil : topic.get("link");
+ nodeValues.desc_nil = desc_nil;
+ nodeValues.desc = (topic.get("desc") == "" && authorized) ? desc_nil : topic.get("desc");
+ return nodeValues;
+ }
+}; // end Metamaps.TopicCard
+
+
+/*
+ *
+ * SYNAPSECARD
+ *
+ */
+Metamaps.SynapseCard = {
+ openSynapseCard: null,
+ showCard: function (edge, e) {
+ var self = Metamaps.SynapseCard;
+
+ //reset so we don't interfere with other edges, but first, save its x and y
+ var myX = $('#edit_synapse').css('left');
+ var myY = $('#edit_synapse').css('top');
+ $('#edit_synapse').remove();
+
+ //so label is missing while editing
+ Metamaps.Control.deselectEdge(edge);
+
+ var synapse = edge.getData('synapses')[0]; // for now, just get the first synapse
+
+ //create the wrapper around the form elements, including permissions
+ //classes to make best_in_place happy
+ var edit_div = document.createElement('div');
+ edit_div.setAttribute('id', 'edit_synapse');
+ if (synapse.authorizeToEdit(Metamaps.Active.Mapper)) {
+ edit_div.className = 'permission canEdit';
+ edit_div.className += synapse.authorizePermissionChange(Metamaps.Active.Mapper) ? ' yourEdge' : '';
+ } else {
+ edit_div.className = 'permission cannotEdit';
+ }
+ $('.main .wrapper').append(edit_div);
+
+ self.populateShowCard(synapse);
+
+ //drop it in the right spot, activate it
+ $('#edit_synapse').css('position', 'absolute');
+ if (e) {
+ $('#edit_synapse').css('left', e.clientX);
+ $('#edit_synapse').css('top', e.clientY);
+ } else {
+ $('#edit_synapse').css('left', myX);
+ $('#edit_synapse').css('top', myY);
+ }
+ //$('#edit_synapse_name').click(); //required in case name is empty
+ //$('#edit_synapse_name input').focus();
+ $('#edit_synapse').show();
+
+ self.openSynapseCard = synapse.isNew() ? synapse.cid : synapse.id;
+ },
+
+ hideCard: function () {
+ $('#edit_synapse').remove();
+ Metamaps.SynapseCard.openSynapseCard = null;
+ },
+
+ populateShowCard: function (synapse) {
+ var self = Metamaps.SynapseCard;
+
+ self.add_name_form(synapse);
+ self.add_user_info(synapse);
+ self.add_perms_form(synapse);
+ if (synapse.authorizeToEdit(Metamaps.Active.Mapper)) {
+ self.add_direction_form(synapse);
+ }
+ },
+
+ add_name_form: function (synapse) {
+ var data_nil = 'Click to add description.';
+
+ // TODO make it so that this would work even in sandbox mode,
+ // currently with Best_in_place it won't
+
+ //name editing form
+ $('#edit_synapse').append('');
+ $('#edit_synapse_name').attr('class', 'best_in_place best_in_place_desc');
+ $('#edit_synapse_name').attr('data-object', 'synapse');
+ $('#edit_synapse_name').attr('data-attribute', 'desc');
+ $('#edit_synapse_name').attr('data-type', 'textarea');
+ $('#edit_synapse_name').attr('data-nil', data_nil);
+ $('#edit_synapse_name').attr('data-url', '/synapses/' + synapse.id);
+ $('#edit_synapse_name').html(synapse.get("desc"));
+
+ //if edge data is blank or just whitespace, populate it with data_nil
+ if ($('#edit_synapse_name').html().trim() == '') {
+ $('#edit_synapse_name').html(data_nil);
+ }
+
+ $('#edit_synapse_name').bind("ajax:success", function () {
+ var desc = $(this).html();
+ if (desc == data_nil) {
+ synapse.set("desc", '');
+ } else {
+ synapse.set("desc", desc);
+ }
+ Metamaps.Control.selectEdge(synapse.get('edge'));
+ Metamaps.Visualize.mGraph.plot();
+ });
+ },
+
+ add_user_info: function (synapse) {
+ var u = '
';
+ u += '
Created by ' + synapse.getUser().get("name") + '
';
+ $('#edit_synapse').append(u);
+ },
+
+ add_perms_form: function (synapse) {
+ //permissions - if owner, also allow permission editing
+ $('#edit_synapse').append('');
+
+ // ability to change permission
+ var selectingPermission = false;
+ if (synapse.authorizePermissionChange(Metamaps.Active.Mapper)) {
+ $('#edit_synapse.yourEdge .mapPerm').click(function () {
+ if (!selectingPermission) {
+ selectingPermission = true;
+ $(this).addClass('minimize'); // this line flips the drop down arrow to a pull up arrow
+ if ($(this).hasClass('co')) {
+ $(this).append('
');
+ } else if ($(this).hasClass('pu')) {
+ $(this).append('
');
+ } else if ($(this).hasClass('pr')) {
+ $(this).append('
');
+ }
+ $('#edit_synapse .permissionSelect li').click(function (event) {
+ selectingPermission = false;
+ var permission = $(this).attr('class');
+ synapse.save({
+ permission: permission,
+ });
+ $('#edit_synapse .mapPerm').removeClass('co pu pr minimize').addClass(permission.substring(0, 2));
+ $('#edit_synapse .permissionSelect').remove();
+ event.stopPropagation();
+ });
+ } else {
+ selectingPermission = false;
+ $(this).removeClass('minimize'); // this line flips the pull up arrow to a drop down arrow
+ $('#edit_synapse .permissionSelect').remove();
+ }
+ });
+ }
+ }, //add_perms_form
+
+ add_direction_form: function (synapse) {
+ //directionality checkboxes
+ $('#edit_synapse').append('');
+ $('#edit_synapse').append('');
+ $('#edit_synapse').append('');
+ $('#edit_synapse').append('');
+
+ var edge = synapse.get('edge');
+
+ //determine which node is to the left and the right
+ //if directly in a line, top is left
+ if (edge.nodeFrom.pos.x < edge.nodeTo.pos.x ||
+ edge.nodeFrom.pos.x == edge.nodeTo.pos.x &&
+ edge.nodeFrom.pos.y < edge.nodeTo.pos.y) {
+ var left = edge.nodeTo;
+ var right = edge.nodeFrom;
+ } else {
+ var left = edge.nodeFrom;
+ var right = edge.nodeTo;
+ }
+
+ /*
+ * One node is actually on the left onscreen. Call it left, & the other right.
+ * If category is from-to, and that node is first, check the 'right' checkbox.
+ * Else check the 'left' checkbox since the arrow is incoming.
+ */
+
+ var directionCat = synapse.get('category'); //both, none, from-to
+ if (directionCat == 'from-to') {
+ var from_to = synapse.getDirection();
+ if (from_to[0] == left.id) {
+ //check left checkbox
+ $('#edit_synapse_left').prop('checked', true);
+ } else {
+ //check right checkbox
+ $('#edit_synapse_right').prop('checked', true);
+ }
+ } else if (directionCat == 'both') {
+ //check both checkboxes
+ $('#edit_synapse_left').prop('checked', true);
+ $('#edit_synapse_right').prop('checked', true);
+ }
+ $('#edit_synapse_left, #edit_synapse_right').click(function () {
+ var leftChecked = $('#edit_synapse_left').is(':checked');
+ var rightChecked = $('#edit_synapse_right').is(':checked');
+
+ var dir = synapse.getDirection();
+ var dirCat = 'none';
+ if (leftChecked && rightChecked) {
+ dirCat = 'both';
+ } else if (!leftChecked && rightChecked) {
+ dirCat = 'from-to';
+ dir = [right.id, left.id];
+ } else if (leftChecked && !rightChecked) {
+ dirCat = 'from-to';
+ dir = [left.id, right.id];
+ }
+
+ synapse.save({
+ category: dirCat,
+ node1_id: dir[0],
+ node2_id: dir[1]
+ });
+ Metamaps.Visualize.mGraph.plot();
+ });
+ } //add_direction_form
+}; // end Metamaps.SynapseCard
+
+
+////////////////////// END TOPIC AND SYNAPSE CARDS //////////////////////////////////
+
+
+
+
+/*
+ *
+ * VISUALIZE
+ *
+ */
+Metamaps.Visualize = {
+ mGraph: {}, // a reference to the graph object.
+ cameraPosition: null, // stores the camera position when using a 3D visualization
+ type: "ForceDirected", // the type of graph we're building, could be "RGraph", "ForceDirected", or "ForceDirected3D"
+ savedLayout: true, // indicates whether the map has a saved layout or not
+ loadLater: false, // indicates whether there is JSON that should be loaded right in the offset, or whether to wait till the first topic is created
+ target: null, // the selector representing the location to render the graph
+ init: function () {
+ var self = Metamaps.Visualize;
+ // disable awkward dragging of the canvas element that would sometimes happen
+ $('#infovis-canvas').on('dragstart', function (event) {
+ event.preventDefault();
+ });
+
+ // prevent touch events on the canvas from default behaviour
+ $("#infovis-canvas").bind('touchstart', function (event) {
+ event.preventDefault();
+ self.mGraph.events.touched = true;
+ });
+
+ // prevent touch events on the canvas from default behaviour
+ $("#infovis-canvas").bind('touchmove', function (event) {
+ //Metamaps.JIT.touchPanZoomHandler(event);
+ });
+
+ // prevent touch events on the canvas from default behaviour
+ $("#infovis-canvas").bind('touchend touchcancel', function (event) {
+ lastDist = 0;
+ if (!self.mGraph.events.touchMoved && !Metamaps.Touch.touchDragNode) Metamaps.TopicCard.hideCurrentCard();
+ self.mGraph.events.touched = self.mGraph.events.touchMoved = false;
+ Metamaps.Touch.touchDragNode = false;
+ });
+ },
+ render: function (targetID, vizData) {
+ var self = Metamaps.Visualize;
+ self.mGraph = {};
+ self.target = targetID;
+ self.__buildGraph(vizData);
+ },
+ computePositions: function () {
+ var self = Metamaps.Visualize,
+ mapping;
+
+ if (self.type == "RGraph") {
+ self.mGraph.graph.eachNode(function (n) {
+ topic = Metamaps.Topics.get(n.id);
+ topic.set('node', n);
+ topic.updateNode();
+
+ n.eachAdjacency(function (edge) {
+ l = edge.getData('synapseIDs').length;
+ for (i = 0; i < l; i++) {
+ synapse = Metamaps.Synapses.get(edge.getData('synapseIDs')[i]);
+ synapse.set('edge', edge);
+ synapse.updateEdge();
+ }
+ });
+
+ var pos = n.getPos();
+ pos.setc(-200, -200);
+ });
+ self.mGraph.compute('end');
+ } else if (self.type == "ForceDirected" && self.savedLayout) {
+ var i, l, startPos, endPos, topic, synapse;
+
+ self.mGraph.graph.eachNode(function (n) {
+ topic = Metamaps.Topics.get(n.id);
+ topic.set('node', n);
+ topic.updateNode();
+ mapping = topic.getMapping();
+
+ n.eachAdjacency(function (edge) {
+ l = edge.getData('synapseIDs').length;
+ for (i = 0; i < l; i++) {
+ synapse = Metamaps.Synapses.get(edge.getData('synapseIDs')[i]);
+ synapse.set('edge', edge);
+ synapse.updateEdge();
+ }
+ });
+
+ startPos = new $jit.Complex(0, 0);
+ endPos = new $jit.Complex(mapping.get('xloc'), mapping.get('yloc'));
+ n.setPos(startPos, 'start');
+ n.setPos(endPos, 'end');
+ });
+ } else if (self.type == "ForceDirected3D" || !self.savedLayout) {
+ self.mGraph.compute();
+ }
+ },
+ /**
+ * __buildGraph does the heavy lifting of creating the engine that renders the graph with the properties we desire
+ *
+ * @param vizData a json structure containing the data to be rendered.
+ */
+ __buildGraph: function (vizData) {
+ var self = Metamaps.Visualize
+ RGraphSettings = $.extend(true, {}, Metamaps.JIT.ForceDirected.graphSettings);
+
+ if (self.type == "RGraph") {
+ $jit.RGraph.Plot.NodeTypes.implement(Metamaps.JIT.ForceDirected.nodeSettings);
+ $jit.RGraph.Plot.EdgeTypes.implement(Metamaps.JIT.ForceDirected.edgeSettings);
+
+ RGraphSettings.background = Metamaps.JIT.RGraph.background;
+ RGraphSettings.levelDistance = Metamaps.JIT.RGraph.levelDistance;
+
+ self.mGraph = new $jit.RGraph(RGraphSettings);
+ } else if (self.type == "ForceDirected") {
+ $jit.ForceDirected.Plot.NodeTypes.implement(Metamaps.JIT.ForceDirected.nodeSettings);
+ $jit.ForceDirected.Plot.EdgeTypes.implement(Metamaps.JIT.ForceDirected.edgeSettings);
+ self.mGraph = new $jit.ForceDirected(Metamaps.JIT.ForceDirected.graphSettings);
+ } else if (self.type == "ForceDirected3D") {
+ // init ForceDirected3D
+ self.mGraph = new $jit.ForceDirected3D(Metamaps.JIT.ForceDirected3D.graphSettings);
+ self.cameraPosition = self.mGraph.canvas.canvases[0].camera.position;
+ }
+
+ // load JSON data, if it's not empty
+ if (!self.loadLater) {
+ //load JSON data.
+ self.mGraph.loadJSON(vizData);
+ //compute positions and plot.
+ self.computePositions();
+ if (self.type == "RGraph") {
+ self.mGraph.fx.animate(Metamaps.JIT.RGraph.animate);
+ } else if (self.type == "ForceDirected" && self.savedLayout) {
+ Metamaps.Organize.loadSavedLayout();
+ } else if (self.type == "ForceDirected3D" || !self.savedLayout) {
+ self.mGraph.animate(Metamaps.JIT.ForceDirected.animateFDLayout);
+ }
+ }
+ }
+}; // end Metamaps.Visualize
+
+
+/*
+ *
+ * UTIL
+ *
+ */
+Metamaps.Util = {
+ // helper function to determine how many lines are needed
+ // Line Splitter Function
+ // copyright Stephen Chapman, 19th April 2006
+ // you may copy this code but please keep the copyright notice as well
+ splitLine: function (st, n) {
+ var b = '';
+ var s = st;
+ while (s.length > n) {
+ var c = s.substring(0, n);
+ var d = c.lastIndexOf(' ');
+ var e = c.lastIndexOf('\n');
+ if (e != -1) d = e;
+ if (d == -1) d = n;
+ b += c.substring(0, d) + '\n';
+ s = s.substring(d + 1);
+ }
+ return b + s;
+ },
+ decodeEntities: function (desc) {
+ var str, temp = document.createElement('p');
+ temp.innerHTML = desc; //browser handles the topics
+ str = temp.textContent || temp.innerText;
+ temp = null; //delete the element;
+ return str;
+ }, //decodeEntities
+ getDistance: function (p1, p2) {
+ return Math.sqrt(Math.pow((p2.x - p1.x), 2) + Math.pow((p2.y - p1.y), 2));
+ },
+ generateOptionsList: function (data) {
+ var newlist = "";
+ for (var i = 0; i < data.length; i++) {
+ newlist = newlist + '';
+ }
+ return newlist;
+ },
+ checkURLisImage: function (url) {
+ // when the page reloads the following regular expression will be screwed up
+ // please replace it with this one before you save: /*backslashhere*.(jpeg|jpg|gif|png)$/
+ return (url.match(/\.(jpeg|jpg|gif|png)$/) != null);
+ },
+ checkURLisYoutubeVideo: function (url) {
+ return (url.match(/^http:\/\/(?:www\.)?youtube.com\/watch\?(?=[^?]*v=\w+)(?:[^\s?]+)?$/) != null);
+ }
+}; // end Metamaps.Util
+
+/*
+ *
+ * REALTIME
+ *
+ */
+Metamaps.Realtime = {
+ // this is for the heroku staging environment
+ //Metamaps.Realtime.socket = io.connect('http://gentle-savannah-1303.herokuapp.com');
+ // this is for metamaps.cc
+ //Metamaps.Realtime.socket = io.connect('http://metamaps.cc:5001');
+ // this is for localhost development
+ //Metamaps.Realtime.socket = io.connect('http://localhost:5001');
+ socket: null,
+ isOpen: false,
+ timeOut: null,
+ changing: false,
+ mappersOnMap: {},
+ status: true, // stores whether realtime is True/On or False/Off
+ init: function () {
+ var self = Metamaps.Realtime;
+
+ $(".realtimeOnOff").click(self.toggle);
+
+ $(".sidebarCollaborate").hover(self.open, self.close);
+
+ var mapperm = Metamaps.Active.Map && Metamaps.Active.Map.authorizeToEdit(Metamaps.Active.Mapper);
+
+ if (mapperm) {
+ self.socket = io.connect('http://localhost:5001');
+ self.socket.on('connect', function () {
+ console.log('socket connected');
+ self.setupSocket();
+ });
+ }
+ },
+ toggle: function () {
+ var self = Metamaps.Realtime;
+
+ if (!self.status) {
+ self.sendRealtimeOn();
+ $(this).html('ON').removeClass('rtOff').addClass('rtOn');
+ $(".rtMapperSelf").removeClass('littleRtOff').addClass('littleRtOn');
+ } else {
+ self.sendRealtimeOff();
+ $(this).html('OFF').removeClass('rtOn').addClass('rtOff');
+ $(".rtMapperSelf").removeClass('littleRtOn').addClass('littleRtOff');
+ }
+ self.status = !self.status;
+ $(".sidebarCollaborateIcon").toggleClass("blue");
+ },
+ open: function () {
+ var self = Metamaps.Realtime;
+
+ clearTimeout(self.timeOut);
+ if (!self.isOpen && !self.changing) {
+ self.changing = true;
+ $('.sidebarCollaborateBox').fadeIn(200, function () {
+ self.changing = false;
+ self.isOpen = true;
+ });
+ }
+ },
+ close: function () {
+ var self = Metamaps.Realtime;
+
+ self.timeOut = setTimeout(function () {
+ if (!self.changing) {
+ self.changing = true;
+ $('.sidebarCollaborateBox').fadeOut(200, function () {
+ self.changing = false;
+ self.isOpen = false;
+ });
+ }
+ }, 500);
+ },
+ setupSocket: function () {
+ var self = Metamaps.Realtime;
+ var socket = Metamaps.Realtime.socket;
+ var myId = Metamaps.Active.Mapper.id;
+
+ socket.emit('newMapperNotify', {
+ userid: myId,
+ username: Metamaps.Active.Mapper.get("name"),
+ mapid: Metamaps.Active.Map.id
+ });
+
+ // if you're the 'new guy' update your list with who's already online
+ socket.on(myId + '-' + Metamaps.Active.Map.id + '-UpdateMapperList', self.updateMapperList);
+
+ // receive word that there's a new mapper on the map
+ socket.on('maps-' + Metamaps.Active.Map.id + '-newmapper', self.newPeerOnMap);
+
+ // receive word that a mapper left the map
+ socket.on('maps-' + Metamaps.Active.Map.id + '-lostmapper', self.lostPeerOnMap);
+
+ // receive word that there's a mapper turned on realtime
+ socket.on('maps-' + Metamaps.Active.Map.id + '-newrealtime', self.newCollaborator);
+
+ // receive word that there's a mapper turned on realtime
+ socket.on('maps-' + Metamaps.Active.Map.id + '-lostrealtime', self.lostCollaborator);
+
+ socket.on('maps-' + Metamaps.Active.Map.id, self.contentUpdate);
+ },
+ sendRealtimeOn: function () {
+ var self = Metamaps.Realtime;
+ var socket = Metamaps.Realtime.socket;
+
+ // send this new mapper back your details, and the awareness that you're online
+ var update = {
+ username: Metamaps.Active.Mapper.get("name"),
+ userid: Metamaps.Active.Mapper.id,
+ mapid: Metamaps.Active.Map.id
+ };
+ socket.emit('notifyStartRealtime', update);
+ },
+ sendRealtimeOff: function () {
+ var self = Metamaps.Realtime;
+ var socket = Metamaps.Realtime.socket;
+
+ // send this new mapper back your details, and the awareness that you're online
+ var update = {
+ username: Metamaps.Active.Mapper.get("name"),
+ userid: Metamaps.Active.Mapper.id,
+ mapid: Metamaps.Active.Map.id
+ };
+ socket.emit('notifyStopRealtime', update);
+ },
+ updateMapperList: function (data) {
+ var self = Metamaps.Realtime;
+ var socket = Metamaps.Realtime.socket;
+
+ // data.userid
+ // data.username
+ // data.userrealtime
+
+ self.mappersOnMap[data.userid] = {
+ name: data.username,
+ realtime: data.userrealtime
+ };
+
+ var onOff = data.userrealtime ? "On" : "Off";
+ var mapperListItem = '
"
+ }
+ ]);
+
+ $('#synapse_desc').bind('typeahead:selected', function (event, datum, dataset) {
+ if (datum.id) { // if they clicked on an existing synapse get it
+ Metamaps.Synapse.getSynapseFromAutocomplete(datum.id);
+ }
+ });
+ },
+ beingCreated: false,
+ description: null,
+ topic1id: null,
+ topic2id: null,
+ newSynapseId: null,
+ open: function () {
+ $('#new_synapse').fadeIn('fast', function () {
+ $('#synapse_desc').focus();
+ });
+ Metamaps.Create.newSynapse.beingCreated = true;
+ },
+ hide: function () {
+ $('#new_synapse').fadeOut('fast');
+ $("#synapse_desc").typeahead('setQuery', '');
+ Metamaps.Create.newSynapse.beingCreated = false;
+ Metamaps.Create.newTopic.addSynapse = false;
+ Metamaps.Create.newSynapse.topic1id = 0;
+ Metamaps.Create.newSynapse.topic2id = 0;
+ },
+ getSearchQuery: function () {
+ var self = Metamaps.Create.newSynapse;
+
+ if (Metamaps.Selected.Nodes.length < 2) {
+ return '/search/synapses?topic1id=' + self.topic1id + '&topic2id=' + self.topic2id;
+ } else return '';
+ }
+ }
+}; // end Metamaps.Create
+
+
+////////////////// TOPIC AND SYNAPSE CARDS //////////////////////////
+
+
+/*
+ *
+ * TOPICCARD
+ *
+ */
+Metamaps.TopicCard = {
+ openTopicCard: null, //stores the JIT local ID of the topic with the topic card open
+ init: function () {
+
+ // initialize best_in_place editing
+ $('.authenticated div.permission.canEdit .best_in_place').best_in_place();
+
+ Metamaps.TopicCard.generateShowcardHTML = Hogan.compile($('#topicCardTemplate').html());
+
+ // initialize topic card draggability and resizability
+ $('.showcard').draggable({
+ handle: ".metacodeImage"
+ });
+ $('#showcard').resizable({
+ maxHeight: 500,
+ maxWidth: 500,
+ minHeight: 320,
+ minWidth: 226,
+ resize: function (event, ui) {
+ var p = $('#showcard').find('.scroll');
+ p.height(p.height()).mCustomScrollbar('update');
+ }
+ }).css({
+ display: 'none',
+ top: '300px',
+ left: '100px'
+ });
+ },
+ fadeInShowCard: function (topic) {
+ $('.showcard').fadeIn('fast');
+ Metamaps.TopicCard.openTopicCard = topic.isNew() ? topic.cid : topic.id;
+ },
+ /**
+ * Will open the Topic Card for the node that it's passed
+ * @param {$jit.Graph.Node} node
+ */
+ showCard: function (node) {
+
+ var topic = node.getData('topic');
+
+ //populate the card that's about to show with the right topics data
+ Metamaps.TopicCard.populateShowCard(topic);
+ Metamaps.TopicCard.fadeInShowCard(topic);
+ },
+ hideCard: function () {
+ $('.showcard').fadeOut('fast');
+ Metamaps.TopicCard.openTopicCard = null;
+ },
+ bindShowCardListeners: function (topic) {
+ var self = Metamaps.TopicCard;
+ var showCard = document.getElementById('showcard');
+
+ var selectingMetacode = false;
+ // attach the listener that shows the metacode title when you hover over the image
+ $('.showcard .metacodeImage').mouseenter(function () {
+ $('.showcard .icon').css('z-index', '4');
+ $('.showcard .metacodeTitle').show();
+ });
+ $('.showcard .linkItem.icon').mouseleave(function () {
+ if (!selectingMetacode) {
+ $('.showcard .metacodeTitle').hide();
+ $('.showcard .icon').css('z-index', '1');
+ }
+ });
+
+ $('.showcard .metacodeTitle').click(function () {
+ if (!selectingMetacode) {
+ selectingMetacode = true;
+ $(this).addClass('minimize'); // this line flips the drop down arrow to a pull up arrow
+ $('.metacodeSelect').show();
+ // add the scroll bar to the list of metacode select options if it isn't already there
+ if (!$('.metacodeSelect ul').hasClass('mCustomScrollbar')) {
+ $('.metacodeSelect ul').mCustomScrollbar({
+ mouseWheelPixels: 200,
+ advanced: {
+ updateOnContentResize: true
+ }
+ });
+
+ $('.metacodeSelect li').click(function () {
+ selectingMetacode = false;
+ var metacodeName = $(this).find('.mSelectName').text();
+ var metacode = Metamaps.Metacodes.findWhere({
+ name: metacodeName
+ });
+ $('.CardOnGraph').find('.metacodeTitle').text(metacodeName)
+ .attr('class', 'metacodeTitle mbg' + metacodeName.replace(/\s/g, ''));
+ $('.CardOnGraph').find('.metacodeImage').css('background-image', 'url(' + metacode.get('icon') + ')');
+ topic.save({
+ metacode_id: metacode.id
+ });
+ Metamaps.Visualize.mGraph.plot();
+ $('.metacodeTitle').removeClass('minimize'); // this line flips the pull up arrow to a drop down arrow
+ $('.metacodeSelect').hide();
+ setTimeout(function () {
+ $('.metacodeTitle').hide();
+ $('.showcard .icon').css('z-index', '1');
+ }, 500);
+ });
+ }
+ } else {
+ selectingMetacode = false;
+ $(this).removeClass('minimize'); // this line flips the pull up arrow to a drop down arrow
+ $('.metacodeSelect').hide();
+ }
+ });
+
+
+ // ability to change permission
+ var selectingPermission = false;
+ if (topic.authorizePermissionChange(Metamaps.Active.Mapper)) {
+ $('.showcard .yourTopic .mapPerm').click(function () {
+ if (!selectingPermission) {
+ selectingPermission = true;
+ $(this).addClass('minimize'); // this line flips the drop down arrow to a pull up arrow
+ if ($(this).hasClass('co')) {
+ $(this).append('
');
+ } else if ($(this).hasClass('pu')) {
+ $(this).append('
');
+ } else if ($(this).hasClass('pr')) {
+ $(this).append('
');
+ }
+ $('.permissionSelect li').click(function (event) {
+ selectingPermission = false;
+ var permission = $(this).attr('class');
+ topic.save({
+ permission: permission
+ });
+ $('.showcard .mapPerm').removeClass('co pu pr minimize').addClass(permission.substring(0, 2));
+ $('.permissionSelect').remove();
+ event.stopPropagation();
+ });
+ } else {
+ selectingPermission = false;
+ $(this).removeClass('minimize'); // this line flips the pull up arrow to a drop down arrow
+ $('.permissionSelect').remove();
+ }
+ });
+ }
+
+ // when you're typing a description, resize the scroll box to have space
+ $('.best_in_place_desc textarea').bind('keyup', function () {
+ var s = $('.showcard').find('.scroll');
+ s.height(s.height()).mCustomScrollbar('update');
+ });
+
+ //bind best_in_place ajax callbacks
+ $(showCard).find('.best_in_place_name').bind("ajax:success", function () {
+
+ var s = $('.showcard').find('.scroll');
+ s.height(s.height()).mCustomScrollbar('update');
+
+ var name = $(this).html();
+ topic.set("name", Metamaps.Util.decodeEntities(name));
+ Metamaps.Visualize.mGraph.plot();
+ });
+
+ $(showCard).find('.best_in_place_desc').bind("ajax:success", function () {
+ this.innerHTML = this.innerHTML.replace(/\r/g, '')
+
+ var s = $('.showcard').find('.scroll');
+ s.height(s.height()).mCustomScrollbar('update');
+
+ var desc = $(this).html();
+ topic.set("desc", desc);
+ });
+
+ $(showCard).find('.best_in_place_link').bind("ajax:success", function () {
+ var link = $(this).html();
+ $(showCard).find('.go-link').attr('href', link);
+ topic.set("link", link);
+ });
+ },
+ populateShowCard: function (topic) {
+ var self = Metamaps.TopicCard;
+
+ var showCard = document.getElementById('showcard');
+
+ $(showCard).find('.permission').remove();
+
+ var html = self.generateShowcardHTML.render(self.buildObject(topic));
+
+ if (topic.authorizeToEdit(Metamaps.Active.Mapper)) {
+ var perm = document.createElement('div');
+
+ var string = 'permission canEdit';
+ if (topic.authorizePermissionChange(Metamaps.Active.Mapper)) string += ' yourTopic';
+ perm.className = string;
+ perm.innerHTML = html;
+ showCard.appendChild(perm);
+ } else {
+ var perm = document.createElement('div');
+ perm.className = 'permission cannotEdit';
+ perm.innerHTML = html;
+ showCard.appendChild(perm);
+ }
+
+ Metamaps.TopicCard.bindShowCardListeners(topic);
+ },
+ generateShowcardHTML: null, // will be initialized into a Hogan template within init function
+ //generateShowcardHTML
+ buildObject: function (topic) {
+ var nodeValues = {};
+ var authorized = topic.authorizeToEdit(Metamaps.Active.Mapper);
+
+ //link is rendered differently if user is logged out or in
+ var go_link, a_tag, close_a_tag;
+ if (!authorized) {
+ go_link = '';
+ if (topic.get("link") != "") {
+ a_tag = '';
+ close_a_tag = '';
+ } else {
+ a_tag = '';
+ close_a_tag = '';
+ }
+ } else {
+ go_link = '';
+ a_tag = '';
+ close_a_tag = '';
+ }
+
+ var desc_nil = "Click to add description...";
+ var link_nil = "Click to add link...";
+
+ nodeValues.permission = topic.get("permission");
+ nodeValues.mk_permission = topic.get("permission").substring(0, 2);
+ //nodeValues.map_count = topic.get("inmaps").length;
+ //nodeValues.synapse_count = topic.get("synapseCount");
+ nodeValues.id = topic.isNew() ? topic.cid : topic.id;
+ nodeValues.metacode = topic.getMetacode().get("name");
+ nodeValues.metacode_class = 'mbg' + topic.getMetacode().get("name").replace(/\s/g, '');
+ nodeValues.imgsrc = topic.getMetacode().get("icon");
+ nodeValues.name = topic.get("name");
+ nodeValues.userid = topic.get("user_id");
+ nodeValues.username = topic.getUser().get("name");
+ nodeValues.date = topic.getDate();
+
+ // the code for this is stored in /views/main/_metacodeOptions.html.erb
+ nodeValues.metacode_select = $('#metacodeOptions').html();
+ nodeValues.go_link = go_link;
+ nodeValues.a_tag = a_tag;
+ nodeValues.close_a_tag = close_a_tag;
+ nodeValues.link_nil = link_nil;
+ nodeValues.link = (topic.get("link") == "" && authorized) ? link_nil : topic.get("link");
+ nodeValues.desc_nil = desc_nil;
+ nodeValues.desc = (topic.get("desc") == "" && authorized) ? desc_nil : topic.get("desc");
+ return nodeValues;
+ }
+}; // end Metamaps.TopicCard
+
+
+/*
+ *
+ * SYNAPSECARD
+ *
+ */
+Metamaps.SynapseCard = {
+ openSynapseCard: null,
+ showCard: function (edge, e) {
+ var self = Metamaps.SynapseCard;
+
+ //reset so we don't interfere with other edges, but first, save its x and y
+ var myX = $('#edit_synapse').css('left');
+ var myY = $('#edit_synapse').css('top');
+ $('#edit_synapse').remove();
+
+ //so label is missing while editing
+ Metamaps.Control.deselectEdge(edge);
+
+ var synapse = edge.getData('synapses')[0]; // for now, just get the first synapse
+
+ //create the wrapper around the form elements, including permissions
+ //classes to make best_in_place happy
+ var edit_div = document.createElement('div');
+ edit_div.setAttribute('id', 'edit_synapse');
+ if (synapse.authorizeToEdit(Metamaps.Active.Mapper)) {
+ edit_div.className = 'permission canEdit';
+ edit_div.className += synapse.authorizePermissionChange(Metamaps.Active.Mapper) ? ' yourEdge' : '';
+ } else {
+ edit_div.className = 'permission cannotEdit';
+ }
+ $('.main .wrapper').append(edit_div);
+
+ self.populateShowCard(synapse);
+
+ //drop it in the right spot, activate it
+ $('#edit_synapse').css('position', 'absolute');
+ if (e) {
+ $('#edit_synapse').css('left', e.clientX);
+ $('#edit_synapse').css('top', e.clientY);
+ } else {
+ $('#edit_synapse').css('left', myX);
+ $('#edit_synapse').css('top', myY);
+ }
+ //$('#edit_synapse_name').click(); //required in case name is empty
+ //$('#edit_synapse_name input').focus();
+ $('#edit_synapse').show();
+
+ self.openSynapseCard = synapse.isNew() ? synapse.cid : synapse.id;
+ },
+
+ hideCard: function () {
+ $('#edit_synapse').remove();
+ Metamaps.SynapseCard.openSynapseCard = null;
+ },
+
+ populateShowCard: function (synapse) {
+ var self = Metamaps.SynapseCard;
+
+ self.add_name_form(synapse);
+ self.add_user_info(synapse);
+ self.add_perms_form(synapse);
+ if (synapse.authorizeToEdit(Metamaps.Active.Mapper)) {
+ self.add_direction_form(synapse);
+ }
+ },
+
+ add_name_form: function (synapse) {
+ var data_nil = 'Click to add description.';
+
+ // TODO make it so that this would work even in sandbox mode,
+ // currently with Best_in_place it won't
+
+ //name editing form
+ $('#edit_synapse').append('');
+ $('#edit_synapse_name').attr('class', 'best_in_place best_in_place_desc');
+ $('#edit_synapse_name').attr('data-object', 'synapse');
+ $('#edit_synapse_name').attr('data-attribute', 'desc');
+ $('#edit_synapse_name').attr('data-type', 'textarea');
+ $('#edit_synapse_name').attr('data-nil', data_nil);
+ $('#edit_synapse_name').attr('data-url', '/synapses/' + synapse.id);
+ $('#edit_synapse_name').html(synapse.get("desc"));
+
+ //if edge data is blank or just whitespace, populate it with data_nil
+ if ($('#edit_synapse_name').html().trim() == '') {
+ $('#edit_synapse_name').html(data_nil);
+ }
+
+ $('#edit_synapse_name').bind("ajax:success", function () {
+ var desc = $(this).html();
+ if (desc == data_nil) {
+ synapse.set("desc", '');
+ } else {
+ synapse.set("desc", desc);
+ }
+ Metamaps.Control.selectEdge(synapse.get('edge'));
+ Metamaps.Visualize.mGraph.plot();
+ });
+ },
+
+ add_user_info: function (synapse) {
+ var u = '
';
+ u += '
Created by ' + synapse.getUser().get("name") + '
';
+ $('#edit_synapse').append(u);
+ },
+
+ add_perms_form: function (synapse) {
+ //permissions - if owner, also allow permission editing
+ $('#edit_synapse').append('');
+
+ // ability to change permission
+ var selectingPermission = false;
+ if (synapse.authorizePermissionChange(Metamaps.Active.Mapper)) {
+ $('#edit_synapse.yourEdge .mapPerm').click(function () {
+ if (!selectingPermission) {
+ selectingPermission = true;
+ $(this).addClass('minimize'); // this line flips the drop down arrow to a pull up arrow
+ if ($(this).hasClass('co')) {
+ $(this).append('
');
+ } else if ($(this).hasClass('pu')) {
+ $(this).append('
');
+ } else if ($(this).hasClass('pr')) {
+ $(this).append('
');
+ }
+ $('#edit_synapse .permissionSelect li').click(function (event) {
+ selectingPermission = false;
+ var permission = $(this).attr('class');
+ synapse.save({
+ permission: permission,
+ });
+ $('#edit_synapse .mapPerm').removeClass('co pu pr minimize').addClass(permission.substring(0, 2));
+ $('#edit_synapse .permissionSelect').remove();
+ event.stopPropagation();
+ });
+ } else {
+ selectingPermission = false;
+ $(this).removeClass('minimize'); // this line flips the pull up arrow to a drop down arrow
+ $('#edit_synapse .permissionSelect').remove();
+ }
+ });
+ }
+ }, //add_perms_form
+
+ add_direction_form: function (synapse) {
+ //directionality checkboxes
+ $('#edit_synapse').append('');
+ $('#edit_synapse').append('');
+ $('#edit_synapse').append('');
+ $('#edit_synapse').append('');
+
+ var edge = synapse.get('edge');
+
+ //determine which node is to the left and the right
+ //if directly in a line, top is left
+ if (edge.nodeFrom.pos.x < edge.nodeTo.pos.x ||
+ edge.nodeFrom.pos.x == edge.nodeTo.pos.x &&
+ edge.nodeFrom.pos.y < edge.nodeTo.pos.y) {
+ var left = edge.nodeTo;
+ var right = edge.nodeFrom;
+ } else {
+ var left = edge.nodeFrom;
+ var right = edge.nodeTo;
+ }
+
+ /*
+ * One node is actually on the left onscreen. Call it left, & the other right.
+ * If category is from-to, and that node is first, check the 'right' checkbox.
+ * Else check the 'left' checkbox since the arrow is incoming.
+ */
+
+ var directionCat = synapse.get('category'); //both, none, from-to
+ if (directionCat == 'from-to') {
+ var from_to = synapse.getDirection();
+ if (from_to[0] == left.id) {
+ //check left checkbox
+ $('#edit_synapse_left').prop('checked', true);
+ } else {
+ //check right checkbox
+ $('#edit_synapse_right').prop('checked', true);
+ }
+ } else if (directionCat == 'both') {
+ //check both checkboxes
+ $('#edit_synapse_left').prop('checked', true);
+ $('#edit_synapse_right').prop('checked', true);
+ }
+ $('#edit_synapse_left, #edit_synapse_right').click(function () {
+ var leftChecked = $('#edit_synapse_left').is(':checked');
+ var rightChecked = $('#edit_synapse_right').is(':checked');
+
+ var dir = synapse.getDirection();
+ var dirCat = 'none';
+ if (leftChecked && rightChecked) {
+ dirCat = 'both';
+ } else if (!leftChecked && rightChecked) {
+ dirCat = 'from-to';
+ dir = [right.id, left.id];
+ } else if (leftChecked && !rightChecked) {
+ dirCat = 'from-to';
+ dir = [left.id, right.id];
+ }
+
+ synapse.save({
+ category: dirCat,
+ node1_id: dir[0],
+ node2_id: dir[1]
+ });
+ Metamaps.Visualize.mGraph.plot();
+ });
+ } //add_direction_form
+}; // end Metamaps.SynapseCard
+
+
+////////////////////// END TOPIC AND SYNAPSE CARDS //////////////////////////////////
+
+
+
+
+/*
+ *
+ * VISUALIZE
+ *
+ */
+Metamaps.Visualize = {
+ mGraph: {}, // a reference to the graph object.
+ cameraPosition: null, // stores the camera position when using a 3D visualization
+ type: "ForceDirected", // the type of graph we're building, could be "RGraph", "ForceDirected", or "ForceDirected3D"
+ savedLayout: true, // indicates whether the map has a saved layout or not
+ loadLater: false, // indicates whether there is JSON that should be loaded right in the offset, or whether to wait till the first topic is created
+ target: null, // the selector representing the location to render the graph
+ init: function () {
+ var self = Metamaps.Visualize;
+ // disable awkward dragging of the canvas element that would sometimes happen
+ $('#infovis-canvas').on('dragstart', function (event) {
+ event.preventDefault();
+ });
+
+ // prevent touch events on the canvas from default behaviour
+ $("#infovis-canvas").bind('touchstart', function (event) {
+ event.preventDefault();
+ self.mGraph.events.touched = true;
+ });
+
+ // prevent touch events on the canvas from default behaviour
+ $("#infovis-canvas").bind('touchmove', function (event) {
+ //Metamaps.JIT.touchPanZoomHandler(event);
+ });
+
+ // prevent touch events on the canvas from default behaviour
+ $("#infovis-canvas").bind('touchend touchcancel', function (event) {
+ lastDist = 0;
+ if (!self.mGraph.events.touchMoved && !Metamaps.Touch.touchDragNode) Metamaps.TopicCard.hideCurrentCard();
+ self.mGraph.events.touched = self.mGraph.events.touchMoved = false;
+ Metamaps.Touch.touchDragNode = false;
+ });
+ },
+ render: function (targetID, vizData) {
+ var self = Metamaps.Visualize;
+ self.mGraph = {};
+ self.target = targetID;
+ self.__buildGraph(vizData);
+ },
+ computePositions: function () {
+ var self = Metamaps.Visualize,
+ mapping;
+
+ if (self.type == "RGraph") {
+ self.mGraph.graph.eachNode(function (n) {
+ topic = Metamaps.Topics.get(n.id);
+ topic.set('node', n);
+ topic.updateNode();
+
+ n.eachAdjacency(function (edge) {
+ l = edge.getData('synapseIDs').length;
+ for (i = 0; i < l; i++) {
+ synapse = Metamaps.Synapses.get(edge.getData('synapseIDs')[i]);
+ synapse.set('edge', edge);
+ synapse.updateEdge();
+ }
+ });
+
+ var pos = n.getPos();
+ pos.setc(-200, -200);
+ });
+ self.mGraph.compute('end');
+ } else if (self.type == "ForceDirected" && self.savedLayout) {
+ var i, l, startPos, endPos, topic, synapse;
+
+ self.mGraph.graph.eachNode(function (n) {
+ topic = Metamaps.Topics.get(n.id);
+ topic.set('node', n);
+ topic.updateNode();
+ mapping = topic.getMapping();
+
+ n.eachAdjacency(function (edge) {
+ l = edge.getData('synapseIDs').length;
+ for (i = 0; i < l; i++) {
+ synapse = Metamaps.Synapses.get(edge.getData('synapseIDs')[i]);
+ synapse.set('edge', edge);
+ synapse.updateEdge();
+ }
+ });
+
+ startPos = new $jit.Complex(0, 0);
+ endPos = new $jit.Complex(mapping.get('xloc'), mapping.get('yloc'));
+ n.setPos(startPos, 'start');
+ n.setPos(endPos, 'end');
+ });
+ } else if (self.type == "ForceDirected3D" || !self.savedLayout) {
+ self.mGraph.compute();
+ }
+ },
+ /**
+ * __buildGraph does the heavy lifting of creating the engine that renders the graph with the properties we desire
+ *
+ * @param vizData a json structure containing the data to be rendered.
+ */
+ __buildGraph: function (vizData) {
+ var self = Metamaps.Visualize
+ RGraphSettings = $.extend(true, {}, Metamaps.JIT.ForceDirected.graphSettings);
+
+ if (self.type == "RGraph") {
+ $jit.RGraph.Plot.NodeTypes.implement(Metamaps.JIT.ForceDirected.nodeSettings);
+ $jit.RGraph.Plot.EdgeTypes.implement(Metamaps.JIT.ForceDirected.edgeSettings);
+
+ RGraphSettings.background = Metamaps.JIT.RGraph.background;
+ RGraphSettings.levelDistance = Metamaps.JIT.RGraph.levelDistance;
+
+ self.mGraph = new $jit.RGraph(RGraphSettings);
+ } else if (self.type == "ForceDirected") {
+ $jit.ForceDirected.Plot.NodeTypes.implement(Metamaps.JIT.ForceDirected.nodeSettings);
+ $jit.ForceDirected.Plot.EdgeTypes.implement(Metamaps.JIT.ForceDirected.edgeSettings);
+ self.mGraph = new $jit.ForceDirected(Metamaps.JIT.ForceDirected.graphSettings);
+ } else if (self.type == "ForceDirected3D") {
+ // init ForceDirected3D
+ self.mGraph = new $jit.ForceDirected3D(Metamaps.JIT.ForceDirected3D.graphSettings);
+ self.cameraPosition = self.mGraph.canvas.canvases[0].camera.position;
+ }
+
+ // load JSON data, if it's not empty
+ if (!self.loadLater) {
+ //load JSON data.
+ self.mGraph.loadJSON(vizData);
+ //compute positions and plot.
+ self.computePositions();
+ if (self.type == "RGraph") {
+ self.mGraph.fx.animate(Metamaps.JIT.RGraph.animate);
+ } else if (self.type == "ForceDirected" && self.savedLayout) {
+ Metamaps.Organize.loadSavedLayout();
+ } else if (self.type == "ForceDirected3D" || !self.savedLayout) {
+ self.mGraph.animate(Metamaps.JIT.ForceDirected.animateFDLayout);
+ }
+ }
+ }
+}; // end Metamaps.Visualize
+
+
+/*
+ *
+ * UTIL
+ *
+ */
+Metamaps.Util = {
+ // helper function to determine how many lines are needed
+ // Line Splitter Function
+ // copyright Stephen Chapman, 19th April 2006
+ // you may copy this code but please keep the copyright notice as well
+ splitLine: function (st, n) {
+ var b = '';
+ var s = st;
+ while (s.length > n) {
+ var c = s.substring(0, n);
+ var d = c.lastIndexOf(' ');
+ var e = c.lastIndexOf('\n');
+ if (e != -1) d = e;
+ if (d == -1) d = n;
+ b += c.substring(0, d) + '\n';
+ s = s.substring(d + 1);
+ }
+ return b + s;
+ },
+ decodeEntities: function (desc) {
+ var str, temp = document.createElement('p');
+ temp.innerHTML = desc; //browser handles the topics
+ str = temp.textContent || temp.innerText;
+ temp = null; //delete the element;
+ return str;
+ }, //decodeEntities
+ getDistance: function (p1, p2) {
+ return Math.sqrt(Math.pow((p2.x - p1.x), 2) + Math.pow((p2.y - p1.y), 2));
+ },
+ generateOptionsList: function (data) {
+ var newlist = "";
+ for (var i = 0; i < data.length; i++) {
+ newlist = newlist + '';
+ }
+ return newlist;
+ },
+ checkURLisImage: function (url) {
+ // when the page reloads the following regular expression will be screwed up
+ // please replace it with this one before you save: /*backslashhere*.(jpeg|jpg|gif|png)$/
+ return (url.match(/\.(jpeg|jpg|gif|png)$/) != null);
+ },
+ checkURLisYoutubeVideo: function (url) {
+ return (url.match(/^http:\/\/(?:www\.)?youtube.com\/watch\?(?=[^?]*v=\w+)(?:[^\s?]+)?$/) != null);
+ }
+}; // end Metamaps.Util
+
+/*
+ *
+ * REALTIME
+ *
+ */
+Metamaps.Realtime = {
+ // this is for the heroku staging environment
+ //Metamaps.Realtime.socket = io.connect('http://gentle-savannah-1303.herokuapp.com');
+ // this is for metamaps.cc
+ //Metamaps.Realtime.socket = io.connect('http://metamaps.cc:5001');
+ // this is for localhost development
+ //Metamaps.Realtime.socket = io.connect('http://localhost:5001');
+ socket: null,
+ isOpen: false,
+ timeOut: null,
+ changing: false,
+ mappersOnMap: {},
+ status: true, // stores whether realtime is True/On or False/Off
+ init: function () {
+ var self = Metamaps.Realtime;
+
+ $(".realtimeOnOff").click(self.toggle);
+
+ $(".sidebarCollaborate").hover(self.open, self.close);
+
+ var mapperm = Metamaps.Active.Map && Metamaps.Active.Map.authorizeToEdit(Metamaps.Active.Mapper);
+
+ if (mapperm) {
+ self.socket = io.connect('http://localhost:5001');
+ self.socket.on('connect', function () {
+ console.log('socket connected');
+ self.setupSocket();
+ });
+ }
+ },
+ toggle: function () {
+ var self = Metamaps.Realtime;
+
+ if (!self.status) {
+ self.sendRealtimeOn();
+ $(this).html('ON').removeClass('rtOff').addClass('rtOn');
+ $(".rtMapperSelf").removeClass('littleRtOff').addClass('littleRtOn');
+ } else {
+ self.sendRealtimeOff();
+ $(this).html('OFF').removeClass('rtOn').addClass('rtOff');
+ $(".rtMapperSelf").removeClass('littleRtOn').addClass('littleRtOff');
+ }
+ self.status = !self.status;
+ $(".sidebarCollaborateIcon").toggleClass("blue");
+ },
+ open: function () {
+ var self = Metamaps.Realtime;
+
+ clearTimeout(self.timeOut);
+ if (!self.isOpen && !self.changing) {
+ self.changing = true;
+ $('.sidebarCollaborateBox').fadeIn(200, function () {
+ self.changing = false;
+ self.isOpen = true;
+ });
+ }
+ },
+ close: function () {
+ var self = Metamaps.Realtime;
+
+ self.timeOut = setTimeout(function () {
+ if (!self.changing) {
+ self.changing = true;
+ $('.sidebarCollaborateBox').fadeOut(200, function () {
+ self.changing = false;
+ self.isOpen = false;
+ });
+ }
+ }, 500);
+ },
+ setupSocket: function () {
+ var self = Metamaps.Realtime;
+ var socket = Metamaps.Realtime.socket;
+ var myId = Metamaps.Active.Mapper.id;
+
+ socket.emit('newMapperNotify', {
+ userid: myId,
+ username: Metamaps.Active.Mapper.get("name"),
+ mapid: Metamaps.Active.Map.id
+ });
+
+ // if you're the 'new guy' update your list with who's already online
+ socket.on(myId + '-' + Metamaps.Active.Map.id + '-UpdateMapperList', self.updateMapperList);
+
+ // receive word that there's a new mapper on the map
+ socket.on('maps-' + Metamaps.Active.Map.id + '-newmapper', self.newPeerOnMap);
+
+ // receive word that a mapper left the map
+ socket.on('maps-' + Metamaps.Active.Map.id + '-lostmapper', self.lostPeerOnMap);
+
+ // receive word that there's a mapper turned on realtime
+ socket.on('maps-' + Metamaps.Active.Map.id + '-newrealtime', self.newCollaborator);
+
+ // receive word that there's a mapper turned on realtime
+ socket.on('maps-' + Metamaps.Active.Map.id + '-lostrealtime', self.lostCollaborator);
+
+ socket.on('maps-' + Metamaps.Active.Map.id, self.contentUpdate);
+ },
+ sendRealtimeOn: function () {
+ var self = Metamaps.Realtime;
+ var socket = Metamaps.Realtime.socket;
+
+ // send this new mapper back your details, and the awareness that you're online
+ var update = {
+ username: Metamaps.Active.Mapper.get("name"),
+ userid: Metamaps.Active.Mapper.id,
+ mapid: Metamaps.Active.Map.id
+ };
+ socket.emit('notifyStartRealtime', update);
+ },
+ sendRealtimeOff: function () {
+ var self = Metamaps.Realtime;
+ var socket = Metamaps.Realtime.socket;
+
+ // send this new mapper back your details, and the awareness that you're online
+ var update = {
+ username: Metamaps.Active.Mapper.get("name"),
+ userid: Metamaps.Active.Mapper.id,
+ mapid: Metamaps.Active.Map.id
+ };
+ socket.emit('notifyStopRealtime', update);
+ },
+ updateMapperList: function (data) {
+ var self = Metamaps.Realtime;
+ var socket = Metamaps.Realtime.socket;
+
+ // data.userid
+ // data.username
+ // data.userrealtime
+
+ self.mappersOnMap[data.userid] = {
+ name: data.username,
+ realtime: data.userrealtime
+ };
+
+ var onOff = data.userrealtime ? "On" : "Off";
+ var mapperListItem = '
+
+
+<% if authenticated? %>
+
+<% # add these if you have edit permissions on the map %>
+<% if @map.permission == "commons" || @map.user == user %>
+<% # for creating and pulling in topics and synapses %>
+<%= render :partial => 'newtopic' %>
+<%= render :partial => 'newsynapse' %>
+<% end %>
+
+<% # for populating the change metacode list on the topic card %>
+<%= render :partial => 'shared/metacodeoptions' %>
+<% end %>
+
+
+
diff --git a/app/views/shared/_filterBox.html.erb b/app/views/shared/_filterBox.html.erb
new file mode 100644
index 00000000..f74d3c4a
--- /dev/null
+++ b/app/views/shared/_filterBox.html.erb
@@ -0,0 +1,44 @@
+<%#
+ # @file
+ # this code generates the list of icons in the filter by metacode box in the upper right menu area
+ #%>
+
+<%
+ @mappers = []
+ @synapses = []
+ @metacodes = []
+ @metacodelist = ''
+ @mapperlist = ''
+ @synapselist = ''
+ @map.topics.each_with_index do |topic, index|
+ if @metacodes.index(topic.metacode_id) == nil
+ @metacodes.push(topic.metacode_id)
+ @metacodelist += '
' + topic.metacode.name.downcase + '
'
+ end
+ end
+
+ @map.synapses.each_with_index do |synapses, index|
+ if @synapses.index(synapses.synapse_id) == nil
+ @synapses.push(synapses.synapse_id)
+ @synapselist += '
' + synapse.name.downcase + '
'
+ end
+
+ end
+%>
+
+
Filter By Metacode
allnone
+
+
+
+ <%= @metacodelist.html_safe %>
+
+
+
+
+
+ <%= @synapselist.html_safe %>
+
+
+
+
+
diff --git a/app/views/shared/_filterBox.html.erb~ b/app/views/shared/_filterBox.html.erb~
new file mode 100644
index 00000000..e909651a
--- /dev/null
+++ b/app/views/shared/_filterBox.html.erb~
@@ -0,0 +1,42 @@
+<%#
+ # @file
+ # this code generates the list of icons in the filter by metacode box in the upper right menu area
+ #%>
+
+<%
+ @mappers = []
+ @synapses = []
+ @metacodes = []
+ @metacodelist = ''
+ @mapperlist = ''
+ @synapselist = ''
+ @map.topics.each_with_index do |topic, index|
+ if @metacodes.index(topic.metacode_id) == nil
+ @metacodes.push(topic.metacode_id)
+ @metacodelist += '
' + topic.metacode.name.downcase + '
'
+ end
+ end
+
+ @map.synapses.each_with_index do |synapses, index|
+ if @synapses.index(synapses.synapse_id) == nil
+ @synapses.push(synapses.synapse_id)
+ @synapselist += '
' + synapse.name.downcase + '
'
+ end
+
+ end
+%>
+
+
Filter By Metacode
allnone
+
+
+
+ <%= @metacodelist.html_safe %>
+
+
+
+
+
+ <%= @synapselist.html_safe %>
+
+
+
diff --git a/db/schema.rb b/db/schema.rb
index c3fce2b2..ec86b421 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -1,129 +1,129 @@
-# encoding: UTF-8
-# This file is auto-generated from the current state of the database. Instead
-# of editing this file, please use the migrations feature of Active Record to
-# incrementally modify your database, and then regenerate this schema definition.
-#
-# Note that this schema.rb definition is the authoritative source for your
-# database schema. If you need to create the application database on another
-# system, you should be using db:schema:load, not running all the migrations
-# from scratch. The latter is a flawed and unsustainable approach (the more migrations
-# you'll amass, the slower it'll run and the greater likelihood for issues).
-#
-# It's strongly recommended to check this file into your version control system.
-
-ActiveRecord::Schema.define(:version => 20140707161810) do
-
- create_table "in_metacode_sets", :force => true do |t|
- t.integer "metacode_id"
- t.integer "metacode_set_id"
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
- end
-
- add_index "in_metacode_sets", ["metacode_id"], :name => "index_in_metacode_sets_on_metacode_id"
- add_index "in_metacode_sets", ["metacode_set_id"], :name => "index_in_metacode_sets_on_metacode_set_id"
-
- create_table "mappings", :force => true do |t|
- t.text "category"
- t.integer "xloc"
- t.integer "yloc"
- t.integer "topic_id"
- t.integer "synapse_id"
- t.integer "map_id"
- t.integer "user_id"
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
- end
-
- create_table "maps", :force => true do |t|
- t.text "name"
- t.boolean "arranged"
- t.text "desc"
- t.text "permission"
- t.integer "user_id"
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
- t.boolean "featured"
- end
-
- create_table "metacode_sets", :force => true do |t|
- t.string "name"
- t.text "desc"
- t.integer "user_id"
- t.boolean "mapperContributed"
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
- end
-
- add_index "metacode_sets", ["user_id"], :name => "index_metacode_sets_on_user_id"
-
- create_table "metacodes", :force => true do |t|
- t.text "name"
- t.string "icon"
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
- end
-
- create_table "synapses", :force => true do |t|
- t.text "desc"
- t.text "category"
- t.text "weight"
- t.text "permission"
- t.integer "node1_id"
- t.integer "node2_id"
- t.integer "user_id"
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
- end
-
- create_table "topics", :force => true do |t|
- t.text "name"
- t.text "desc"
- t.text "link"
- t.text "permission"
- t.integer "user_id"
- t.integer "metacode_id"
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
- t.string "image_file_name"
- t.string "image_content_type"
- t.integer "image_file_size"
- t.datetime "image_updated_at"
- t.string "audio_file_name"
- t.string "audio_content_type"
- t.integer "audio_file_size"
- t.datetime "audio_updated_at"
- end
-
- create_table "users", :force => true do |t|
- t.string "name"
- t.string "email"
- t.text "settings"
- t.string "code", :limit => 8
- t.string "joinedwithcode", :limit => 8
- t.string "crypted_password"
- t.string "password_salt"
- t.string "persistence_token"
- t.string "perishable_token"
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
- t.string "encrypted_password", :limit => 128, :default => ""
- t.string "remember_token"
- t.datetime "remember_created_at"
- t.string "reset_password_token"
- t.datetime "last_sign_in_at"
- t.string "last_sign_in_ip"
- t.integer "sign_in_count", :default => 0
- t.datetime "current_sign_in_at"
- t.string "current_sign_in_ip"
- t.datetime "reset_password_sent_at"
- t.boolean "admin"
- t.string "image_file_name"
- t.string "image_content_type"
- t.integer "image_file_size"
- t.datetime "image_updated_at"
- end
-
- add_index "users", ["reset_password_token"], :name => "index_users_on_reset_password_token", :unique => true
-
-end
+# encoding: UTF-8
+# This file is auto-generated from the current state of the database. Instead
+# of editing this file, please use the migrations feature of Active Record to
+# incrementally modify your database, and then regenerate this schema definition.
+#
+# Note that this schema.rb definition is the authoritative source for your
+# database schema. If you need to create the application database on another
+# system, you should be using db:schema:load, not running all the migrations
+# from scratch. The latter is a flawed and unsustainable approach (the more migrations
+# you'll amass, the slower it'll run and the greater likelihood for issues).
+#
+# It's strongly recommended to check this file into your version control system.
+
+ActiveRecord::Schema.define(:version => 20140707161810) do
+
+ create_table "in_metacode_sets", :force => true do |t|
+ t.integer "metacode_id"
+ t.integer "metacode_set_id"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ end
+
+ add_index "in_metacode_sets", ["metacode_id"], :name => "index_in_metacode_sets_on_metacode_id"
+ add_index "in_metacode_sets", ["metacode_set_id"], :name => "index_in_metacode_sets_on_metacode_set_id"
+
+ create_table "mappings", :force => true do |t|
+ t.text "category"
+ t.integer "xloc"
+ t.integer "yloc"
+ t.integer "topic_id"
+ t.integer "synapse_id"
+ t.integer "map_id"
+ t.integer "user_id"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ end
+
+ create_table "maps", :force => true do |t|
+ t.text "name"
+ t.boolean "arranged"
+ t.text "desc"
+ t.text "permission"
+ t.integer "user_id"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ t.boolean "featured"
+ end
+
+ create_table "metacode_sets", :force => true do |t|
+ t.string "name"
+ t.text "desc"
+ t.integer "user_id"
+ t.boolean "mapperContributed"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ end
+
+ add_index "metacode_sets", ["user_id"], :name => "index_metacode_sets_on_user_id"
+
+ create_table "metacodes", :force => true do |t|
+ t.text "name"
+ t.string "icon"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ end
+
+ create_table "synapses", :force => true do |t|
+ t.text "desc"
+ t.text "category"
+ t.text "weight"
+ t.text "permission"
+ t.integer "node1_id"
+ t.integer "node2_id"
+ t.integer "user_id"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ end
+
+ create_table "topics", :force => true do |t|
+ t.text "name"
+ t.text "desc"
+ t.text "link"
+ t.text "permission"
+ t.integer "user_id"
+ t.integer "metacode_id"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ t.string "image_file_name"
+ t.string "image_content_type"
+ t.integer "image_file_size"
+ t.datetime "image_updated_at"
+ t.string "audio_file_name"
+ t.string "audio_content_type"
+ t.integer "audio_file_size"
+ t.datetime "audio_updated_at"
+ end
+
+ create_table "users", :force => true do |t|
+ t.string "name"
+ t.string "email"
+ t.text "settings"
+ t.string "code", :limit => 8
+ t.string "joinedwithcode", :limit => 8
+ t.string "crypted_password"
+ t.string "password_salt"
+ t.string "persistence_token"
+ t.string "perishable_token"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ t.string "encrypted_password", :limit => 128, :default => ""
+ t.string "remember_token"
+ t.datetime "remember_created_at"
+ t.string "reset_password_token"
+ t.datetime "last_sign_in_at"
+ t.string "last_sign_in_ip"
+ t.integer "sign_in_count", :default => 0
+ t.datetime "current_sign_in_at"
+ t.string "current_sign_in_ip"
+ t.datetime "reset_password_sent_at"
+ t.boolean "admin"
+ t.string "image_file_name"
+ t.string "image_content_type"
+ t.integer "image_file_size"
+ t.datetime "image_updated_at"
+ end
+
+ add_index "users", ["reset_password_token"], :name => "index_users_on_reset_password_token", :unique => true
+
+end