add server-side reading merge logic for issue #9; uniform handling of buttons in...
Tara L Andrews [Thu, 4 Jul 2013 22:08:24 +0000 (00:08 +0200)]
lib/stemmaweb/Controller/Relation.pm
root/js/relationship.js

index 46fb11f..bfe9d87 100644 (file)
@@ -388,6 +388,72 @@ sub reading :Chained('text') :PathPart :Args(1) {
 
 }
 
+=head2 merge
+
+ POST relation/$textid/merge { data }
+Merges the requested readings, combining the witnesses of both readings into
+the target reading. All non-conflicting source relationships are inherited by
+the target relationship.
+
+=cut
+
+sub merge :Chained('text') :PathPart :Args(0) {
+       my( $self, $c ) = @_;
+       my $tradition = delete $c->stash->{'tradition'};
+       my $collation = $tradition->collation;
+       my $m = $c->model('Directory');
+       if( $c->request->method eq 'POST' ) {
+               if( $c->stash->{'permission'} ne 'full' ) {
+                       $c->response->status( '403' );
+                       $c->stash->{'result'} = { 
+                               'error' => 'You do not have permission to modify this tradition.' };
+                       $c->detach('View::JSON');
+                       return;
+               }
+               my $errmsg;
+               my $response;
+               
+               my $main = $c->request->param('target_id');
+               my $second = $c->request->param('source_id');
+               # Find the common successor of these, so that we can detect other
+               # potentially identical readings.
+               my $csucc = $collation->common_successor( $main, $second );
+
+               # Try the merge if these are parallel readings.
+               if( $csucc->id eq $main || $csucc->id eq $second ) {
+                       $errmsg = "Cannot merge readings in the same path";
+               } else {
+                       try {
+                               $collation->merge_readings( $main, $second );
+                       } catch( Text::Tradition::Error $e ) {
+                               $c->response->status( '403' );
+                               $errmsg = $e->message;
+                       } catch {
+                               # Something else went wrong, probably a Moose error
+                               $c->response->status( '403' );
+                               $errmsg = 'Something went wrong with the request';      
+                       }
+               }
+               
+               # Look for readings that are now identical.
+               if( $errmsg ) {
+                       $response = { status => 'error', error => $errmsg };
+               } else {
+                       $response = { status => 'ok' };
+                       my @identical = $collation->identical_readings(
+                               start => $main, end => $csucc->id );
+                       if( @identical ) {
+                               $response->{'checkalign'} = [ 
+                                       map { [ $_->[0]->id, $_->[1]->id ] } @identical ];
+                       }
+                       $m->save( $collation );
+               }
+               $c->stash->{'result'} = $response;
+               $c->forward('View::JSON');                      
+       }
+}
+
 =head2 duplicate
 
  POST relation/$textid/duplicate { data }
@@ -425,7 +491,6 @@ sub duplicate :Chained('text') :PathPart :Args(0) {
                foreach my $rid ( $c->request->param('readings[]') ) {
                        my $numwits = 0;
                        my $rdg = $collation->reading( $rid );
-                       $DB::single = 1;
                        foreach my $rwit ( $rdg->witnesses( $rid ) ) {
                                $numwits++ if exists $wits{$rwit};
                        }
index 39d8f50..89ca5e8 100644 (file)
@@ -837,6 +837,21 @@ function Marquee() {
      
 }
 
+function readings_equivalent( source, target ) {
+       var sourcetext = readingdata[source].text;
+       var targettext = readingdata[target].text;
+       if( sourcetext === targettext ) {
+               return true;
+       }
+       // Lowercase and strip punctuation from both and compare again
+       var stlc = sourcetext.toLowerCase().replace(/[^\w\s]|_/g, "");
+       var ttlc = targettext.toLowerCase().replace(/[^\w\s]|_/g, "");
+       if( stlc === ttlc ) {
+               return true;
+       }       
+       return false;
+}
+
 
 $(document).ready(function () {
     
@@ -844,7 +859,8 @@ $(document).ready(function () {
   relation_manager = new relation_factory();
   
   $('#update_workspace_button').data('locked', false);
-                
+   
+  // Set up the mouse events on the SVG enlargement             
   $('#enlargement').mousedown(function (event) {
     $(this)
         .data('down', true)
@@ -909,6 +925,11 @@ $(document).ready(function () {
   });
   
   
+  // Set up the relationship creation dialog. This also functions as the reading
+  // merge dialog where appropriate.
+  var relation_buttonset = {
+  };
+                         
   if( editable ) {
        $( "#dialog-form" ).dialog({
        autoOpen: false,
@@ -916,7 +937,16 @@ $(document).ready(function () {
        width: 290,
        modal: true,
        buttons: {
-         "Ok": function( evt ) {
+         "Merge readings": function( evt ) {
+                 $(evt.target).button("disable");
+                 $('#status').empty();
+                 form_values = $('#collapse_node_form').serialize();
+                 ncpath = getTextURL( 'merge' );
+                 var jqjson = $.post( ncpath, form_values, function(data) {
+                         alert( "Did a node merge" );
+                 });
+         },
+         OK: function( evt ) {
                $(evt.target).button("disable");
                $('#status').empty();
                form_values = $('#collapse_node_form').serialize();
@@ -961,7 +991,15 @@ $(document).ready(function () {
                });
        },
        open: function() {
-               relation_manager.create_temporary( $('#source_node_id').val(), $('#target_node_id').val() );
+               relation_manager.create_temporary( 
+                       $('#source_node_id').val(), $('#target_node_id').val() );
+               var buttonset = $(this).parent().find( '.ui-dialog-buttonset' )
+               if( readings_equivalent( $('#source_node_id').val(), 
+                               $('#target_node_id').val() ) ) {
+                       buttonset.find( "button:contains('Merge readings')" ).show();
+               } else {
+                       buttonset.find( "button:contains('Merge readings')" ).hide();
+               }
                $(".ui-widget-overlay").css("background", "none");
                $("#dialog_overlay").show();
                $("#dialog_overlay").height( $("#enlargement_container").height() );
@@ -994,23 +1032,23 @@ $(document).ready(function () {
        } );
   }
 
-  var deletion_buttonset = {
-        cancel: function() { $( this ).dialog( "close" ); },
-        global: function () { delete_relation( true ); },
-        delete: function() { delete_relation( false ); }
-  };   
-  
+  // Set up the relationship info display and deletion dialog.  
   $( "#delete-form" ).dialog({
     autoOpen: false,
     height: 135,
     width: 250,
     modal: false,
+    buttons: {
+        OK: function() { $( this ).dialog( "close" ); },
+        "Delete all": function () { delete_relation( true ); },
+        Delete: function() { delete_relation( false ); }
+    },
     create: function(event, ui) {
        // TODO What is this logic doing?
        // This scales the buttons in the dialog and makes it look proper
        // Not sure how essential it is, does anything break if it's not here?
         var buttonset = $(this).parent().find( '.ui-dialog-buttonset' ).css( 'width', '100%' );
-        buttonset.find( "button:contains('Cancel')" ).css( 'float', 'right' );
+        buttonset.find( "button:contains('OK')" ).css( 'float', 'right' );
        // A: This makes sure that the pop up delete relation dialogue for a hovered over
        // relation auto closes if the user doesn't engage (mouseover) with it.
         var dialog_aria = $("div[aria-labelledby='ui-dialog-title-delete-form']");  
@@ -1022,22 +1060,21 @@ $(document).ready(function () {
         })
     },
     open: function() {
+       // Show the appropriate buttons...
+               var buttonset = $(this).parent().find( '.ui-dialog-buttonset' )
+               // If the user can't edit, show only the OK button
        if( !editable ) {
-               $( this ).dialog( "option", "buttons", 
-                       [{ text: "OK", click: deletion_buttonset['cancel'] }] );
+               buttonset.find( "button:contains('Delete')" ).hide();
+       // If the relationship scope is local, show only OK and Delete
        } else if( $('#delete_relation_scope').text() === 'local' ) {
                $( this ).dialog( "option", "width", 160 );
-               $( this ).dialog( "option", "buttons",
-                       [{ text: "Delete", click: deletion_buttonset['delete'] },
-                        { text: "Cancel", click: deletion_buttonset['cancel'] }] );
+               buttonset.find( "button:contains('Delete')" ).show();
+               buttonset.find( "button:contains('Delete all')" ).hide();
+       // Otherwise, show all three
        } else {
                $( this ).dialog( "option", "width", 200 );
-               $( this ).dialog( "option", "buttons",
-                       [{ text: "Delete", click: deletion_buttonset['delete'] },
-                        { text: "Delete all", click: deletion_buttonset['global'] },
-                        { text: "Cancel", click: deletion_buttonset['cancel'] }] );
+               buttonset.find( "button:contains('Delete')" ).show();
                }       
-                       
         mouseWait = setTimeout( function() { $("#delete-form").dialog( "close" ) }, 2000 );
     },
     close: function() {}
@@ -1055,10 +1092,10 @@ $(document).ready(function () {
         var form_values = $('#detach_collated_form').serialize();
         ncpath = getTextURL( 'duplicate' );
         var jqjson = $.post( ncpath, form_values, function(data) {
-        detach_node( data );
-        $(evt.target).button("enable");
-        $( this ).dialog( "close" );
-      });
+               detach_node( data );
+               $(evt.target).button("enable");
+               $( "#multipleselect-form" ).dialog( "close" );
+       });
      }
     },
     create: function(event, ui) {