Merge branch 'develop' of https://git.trustie.net/jacknudt/trustieforge into develop
	
		
	
				
					
				
			
						commit
						d948536a10
					
				| @ -0,0 +1,96 @@ | ||||
| <h3> | ||||
|   <%=l(:label_code_work_tests)%> | ||||
| </h3> | ||||
| 
 | ||||
|   | ||||
| 
 | ||||
| 
 | ||||
| <div class="autoscroll"> | ||||
|   <table class="list" style="width: 100%;table-layout: fixed"> | ||||
|     <thead> | ||||
|     <tr> | ||||
|       <th style="width: 50px;"> | ||||
|         作业id | ||||
|       </th> | ||||
|       <th style="width: 60px;"> | ||||
|         平均等待时间 | ||||
|       </th> | ||||
|       <th style="width: 50px;"> | ||||
|         语言 | ||||
|       </th> | ||||
|       <th style="width: 120px;"> | ||||
|         提交测试时间 | ||||
|       </th> | ||||
|       <th style="width: 50px;"> | ||||
|         答题状态 | ||||
|       </th> | ||||
|       <th style="width: 50px;"> | ||||
|         测试集数 | ||||
|       </th> | ||||
|       <th style="width: 50px;"> | ||||
|         最小耗时 | ||||
|       </th> | ||||
|       <th style="width: 50px;"> | ||||
|         最大耗时 | ||||
|       </th> | ||||
|     </tr> | ||||
|     </thead> | ||||
|     <tbody> | ||||
|     <% @code_work_tests.each do |test| %> | ||||
|         <tr class="<%= cycle("odd", "even") %>"> | ||||
|           <td style="text-align: center; " title='<%=test.homeworkid%>'> | ||||
|             <%=link_to(test.homeworkid, student_work_index_path(:homework => test.homeworkid))%> | ||||
|           </td> | ||||
|           <td style="text-align: center;"> | ||||
|                 <% if test.status != -2 && test.results.first['user_wait'] %> | ||||
|                     <% wait_time = 0  %> | ||||
|                     <% test.results.each do |result|  wait_time = wait_time + result['user_wait']  end %> | ||||
|                     <%=(wait_time/test.results.count).to_s+"毫秒" %> | ||||
|                 <% else %> | ||||
|                     <%="未记录"%> | ||||
|                 <% end %> | ||||
|           </td> | ||||
|           <td align="center"> | ||||
|             <%=%W(C C++ Python Java).at(test.language.to_i - 1)%> | ||||
|           </td> | ||||
|           <td align="center"> | ||||
|             <%=Time.parse(test.created_at.to_s).strftime("%Y-%m-%d %H:%M:%S")%> | ||||
|           </td> | ||||
|           <td align="center"> | ||||
|             <% if test.status == 0 %> | ||||
|                 <%= "答题正确" %> | ||||
|             <% elsif test.status == -2 %> | ||||
|                 <%= "编译错误" %> | ||||
|             <% elsif test.status == 2 || test.results.last['status'] == 2 %> | ||||
|                 <%= "超时" %> | ||||
|             <% else %> | ||||
|                 <%= "答题错误" %> | ||||
|             <% end %> | ||||
|           </td> | ||||
|           <td class="center"> | ||||
|             <% if test.status != -2 %> | ||||
|                <%=test.results.count%> | ||||
|             <% end %> | ||||
|           </td> | ||||
|           <td class="center"> | ||||
|             <% if test.status != -2 %> | ||||
|                 <%test.results = test.results.sort_by {|result| result['time_used'] }%> | ||||
|                 <%=test.results.first['time_used'] == 0 ? "1毫秒":test.results.first['time_used'].to_s+"毫秒"%> | ||||
|             <% end %> | ||||
|           </td> | ||||
|           <td class="center"> | ||||
|             <% if test.status != -2 %> | ||||
|                 <%=test.results.last['time_used'] == 0 ? "1毫秒":test.results.last['time_used'].to_s+"毫秒"%> | ||||
|             <% end %> | ||||
|           </td> | ||||
|         </tr> | ||||
|     <% end %> | ||||
|     </tbody> | ||||
|   </table> | ||||
| </div> | ||||
| 
 | ||||
| <div class="pagination"> | ||||
|   <%= pagination_links_full @obj_pages, @obj_count, :per_page_links => false %> | ||||
| </div> | ||||
| 
 | ||||
| <% html_title(l(:label_code_work_tests)) -%> | ||||
| @ -0,0 +1,101 @@ | ||||
| <div class="blue-border-box"> | ||||
|   <div class="box-con"> | ||||
|     <%if @homework.simi_time != nil %> | ||||
|         <h4 id = "compare-tips-1"><%="您上次查重的时间为"+Time.parse(@homework.simi_time.to_s).strftime("%Y-%m-%d %H:%M")%></h4> | ||||
|     <%end%> | ||||
|     <div class="box-con-a"> | ||||
|       <a href="javascript:void(0);" class="Blue-btn fl " onclick = "compare_code_btn(<%=homework.id%>,<%=courseid%>)">重新查重</a> | ||||
|       <a href="javascript:void(0);" class="Blue-btn fl " onclick = "see_last_compare_code(<%=courseid%>,<%=homework.id%>)">查看结果</a> | ||||
|     </div> | ||||
|     <div class="cl"></div> | ||||
|   </div> | ||||
| </div> | ||||
| </div> | ||||
| 
 | ||||
| <script type="text/javascript"> | ||||
|     //请求重新查重 | ||||
|     function compare_code_btn(homeworkid,courseid) | ||||
|     { | ||||
|         hideModal($(".blue-border-box")); | ||||
|         test_repeat(homeworkid,courseid); | ||||
| 
 | ||||
|     } | ||||
|     //查看结果 | ||||
|     function see_last_compare_code(courseid,homeworkid) | ||||
|     { | ||||
|         hideModal($(".blue-border-box")); | ||||
|         var rootpath = getRootPath(); | ||||
|         var code_repeatpath = rootpath+"/courses/"+courseid+"/code_repeat?homework="+homeworkid; | ||||
|         //新打开页面 | ||||
|         window.open(code_repeatpath); | ||||
|     } | ||||
| 
 | ||||
|     var test_repeat = function(homeworkid,courseid){ | ||||
|         $.post( | ||||
|                 '/student_work/code_repeattest', | ||||
|                 {homework: homeworkid}, | ||||
|                 function(data,status){ | ||||
|                     console.log("result = "); | ||||
|                     console.log(data); | ||||
| 
 | ||||
|                     if (data.status == 0) { | ||||
|                         $("#ajax-modal").html('<%= escape_javascript( render :partial => 'courses/compare_code_tips_2',:locals => {:des=>"查重完成是否立即查看结果?",:status=>1, :homework=> homework,:courseid=> courseid})%>'); | ||||
|                         showModal('ajax-modal', '580px'); | ||||
|                         $('#ajax-modal').siblings().remove(); | ||||
|                         $('#ajax-modal').before("<a href='javascript:void(0)' onclick='hideModal();' style='margin-left: 560px;' class='resourceClose'></a>"); | ||||
|                         $('#ajax-modal').parent().css("top","40%").css("left","50%"); | ||||
|                         $('#ajax-modal').parent().addClass("resourceUploadPopup"); | ||||
|                         $('#ajax-modal').css("padding-left","16px").css("padding-bottom","16px"); | ||||
| 
 | ||||
|                         function closeModal(){ | ||||
|                             hideModal($(".blue-border-box")); | ||||
|                         } | ||||
|                     } | ||||
|                     else if (data.status == -1){ | ||||
| //                          confirm("对不起只支持java/c/c++的代码查重!"); | ||||
|                         $("#ajax-modal").html('<%= escape_javascript( render :partial => 'courses/compare_code_tips_2',:locals => {:des=>"对不起目前只支持java/c/c++的代码查重!",:status=>0, :homework=> homework,:courseid=> courseid})%>'); | ||||
|                         showModal('ajax-modal', '580px'); | ||||
|                         $('#ajax-modal').siblings().remove(); | ||||
|                         $('#ajax-modal').before("<a href='javascript:void(0)' onclick='hideModal();' style='margin-left: 560px;' class='resourceClose'></a>"); | ||||
|                         $('#ajax-modal').parent().css("top","40%").css("left","50%"); | ||||
|                         $('#ajax-modal').parent().addClass("resourceUploadPopup"); | ||||
|                         $('#ajax-modal').css("padding-left","16px").css("padding-bottom","16px"); | ||||
| 
 | ||||
|                         function closeModal(){ | ||||
|                             hideModal($(".blue-border-box")); | ||||
|                         } | ||||
| 
 | ||||
|                     } | ||||
|                     else if (data.status == -2){ | ||||
| //                          confirm("对不起该作业的作品过少不能查重!"); | ||||
|                         $("#ajax-modal").html('<%= escape_javascript( render :partial => 'courses/compare_code_tips_2',:locals => {:des=>"对不起该作业的作品过少不能查重!",:status=>0, :homework=> homework,:courseid=> courseid})%>'); | ||||
|                         showModal('ajax-modal', '580px'); | ||||
|                         $('#ajax-modal').siblings().remove(); | ||||
|                         $('#ajax-modal').before("<a href='javascript:void(0)' onclick='hideModal();' style='margin-left: 560px;' class='resourceClose'></a>"); | ||||
|                         $('#ajax-modal').parent().css("top","40%").css("left","50%"); | ||||
|                         $('#ajax-modal').parent().addClass("resourceUploadPopup"); | ||||
|                         $('#ajax-modal').css("padding-left","16px").css("padding-bottom","16px"); | ||||
| 
 | ||||
|                         function closeModal(){ | ||||
|                             hideModal($(".blue-border-box")); | ||||
|                         } | ||||
|                     } | ||||
|                     return; | ||||
|                 } | ||||
|         ).fail(function(xhr, status){ | ||||
| //                    confirm("对不起,服务器繁忙请稍后再试!"); | ||||
|                     $("#ajax-modal").html('<%= escape_javascript( render :partial => 'courses/compare_code_tips_2',:locals => {:des=>"对不起,服务器繁忙请稍后再试!",:status=>0, :homework=> homework,:courseid=> courseid})%>'); | ||||
|                     showModal('ajax-modal', '580px'); | ||||
|                     $('#ajax-modal').siblings().remove(); | ||||
|                     $('#ajax-modal').before("<a href='javascript:void(0)' onclick='hideModal();' style='margin-left: 560px;' class='resourceClose'></a>"); | ||||
|                     $('#ajax-modal').parent().css("top","40%").css("left","50%"); | ||||
|                     $('#ajax-modal').parent().addClass("resourceUploadPopup"); | ||||
|                     $('#ajax-modal').css("padding-left","16px").css("padding-bottom","16px"); | ||||
| 
 | ||||
|                     function closeModal(){ | ||||
|                         hideModal($(".blue-border-box")); | ||||
|                     } | ||||
|                     return; | ||||
|                 }); | ||||
|     }; | ||||
| </script> | ||||
| @ -0,0 +1,22 @@ | ||||
| <div class="blue-border-box"> | ||||
|   <div class="box-con"> | ||||
|     <h4><%=des%></h4> | ||||
|     <div class="box-con-a"> | ||||
|       <a href="javascript:void(0);" class="Blue-btn  " style="width:67px; margin:25px auto 0px auto;" onclick = "yes_btn(<%=status%>,<%=courseid%>,<%=homework.id%>)">确定</a> | ||||
|     </div> | ||||
|     <div class="cl"></div> | ||||
|   </div> | ||||
| </div> | ||||
| </div> | ||||
| 
 | ||||
| <script type="text/javascript"> | ||||
|     function yes_btn(status,courseid,homeworkid) | ||||
|     { | ||||
|         hideModal($(".blue-border-box")); | ||||
|         if(status == 1) { | ||||
|             var rootpath = getRootPath(); | ||||
|             var code_repeatpath = rootpath+"/courses/"+courseid+"/code_repeat?homework="+homeworkid; | ||||
|             window.open(code_repeatpath); | ||||
|         } | ||||
|     } | ||||
| </script> | ||||
| @ -0,0 +1,16 @@ | ||||
| 
 | ||||
| <% content_for :header_tags do %> | ||||
|     <%= javascript_include_tag "/assets/codemirror/codemirror_python_ruby_c"  %> | ||||
|     <%= stylesheet_link_tag "/assets/codemirror/codemirror" %> | ||||
|     <%= stylesheet_link_tag "/assets/codemirror/merge" %> | ||||
|     <%= javascript_include_tag "https://cdnjs.cloudflare.com/ajax/libs/diff_match_patch/20121119/diff_match_patch.js"%> | ||||
|     <%= javascript_include_tag "/assets/codemirror/merge" %> | ||||
| <% end %> | ||||
| 
 | ||||
| <article> | ||||
|   <h3 style="float:left; width:50%; text-align:center;"><%=src_name%></h3> | ||||
|   <h3 style="float:left; width:50%; text-align:center;"><%=dst_name%></h3><div class="cl"></div> | ||||
|   <pre id = "program-src_1" style = "display: none" ><%= src_code if src_code%></pre> | ||||
|   <pre id = "program-src_2" style = "display: none" ><%= dst_code if dst_code%></pre> | ||||
|   <div class = "program-compare-code" id=program-compare-code></div> | ||||
| </article> | ||||
| @ -0,0 +1,79 @@ | ||||
| <div class="conbox"> | ||||
|   <h2 class="conbox-h2">查重结果</h2> | ||||
|   <div class="chabox"> | ||||
|     <ul class="chabox-header"> | ||||
|       <li class="chabox-w-500" style = "width:413px" >全部作品</li> | ||||
|       <li class="chabox-w-500" style = "width:585px" >对比作品</li> | ||||
|       <div class="cl"></div> | ||||
|     </ul> | ||||
|     <ul class="chabox-top"> | ||||
|       <li class="chabox-w-151" >作品名称</li> | ||||
|       <li>姓名</li> | ||||
|       <li>学号 </li> | ||||
|       <li class="chabox-r-line">时间 </li> | ||||
|       <li class="chabox-w-151">作品名称</li> | ||||
|       <li>姓名</li> | ||||
|       <li>学号 </li> | ||||
|       <li class="chabox-r-line">时间 </li> | ||||
|       <li >相似度 </li> | ||||
|       <li >对比 </li> | ||||
|     </ul> | ||||
| 
 | ||||
|     <%if @homework.homework_type == 2 %> | ||||
|         <% @student_works.each do |student_work|%> | ||||
|         <ul class="chabox-con" id = "chabox-con-<%=student_work.id%>" > | ||||
|           <% student_work_name = student_work.name.nil? || student_work.name.empty? ? student_work.user.show_name + '的作品' : student_work.name%> | ||||
| 
 | ||||
|           <li class="chabox-w-151" ><%=student_work_name%></li> | ||||
|           <li><%=student_work.user.show_name%></li> | ||||
|           <li><%= student_work.user.user_extensions.nil? ? "--" : student_work.user.user_extensions.student_id%> </li> | ||||
|           <li class="chabox-r-line"><%= Time.parse(format_time(student_work.created_at)).strftime("%m-%d %H:%M")%></li> | ||||
|           <% if student_work.simi_id > 0 && @works_hash[student_work.simi_id] %> | ||||
|               <% simi_student_work = @works_hash[student_work.simi_id] %> | ||||
|               <% simi_student_work_name = simi_student_work.name.nil? || simi_student_work.name.empty? ? simi_student_work.user.show_name + '的作品' : simi_student_work.name%> | ||||
|               <li class="chabox-w-151"><%=simi_student_work_name%></li> | ||||
|               <li><%=simi_student_work.user.show_name%></li> | ||||
|               <li><%= simi_student_work.user.user_extensions.nil? ? "--" : simi_student_work.user.user_extensions.student_id%></li> | ||||
|               <li class="chabox-r-line"><%= Time.parse(format_time(simi_student_work.created_at)).strftime("%m-%d %H:%M")%></li> | ||||
|               <% if student_work.simi_value >= 90  %> | ||||
|                   <li style = "color:red" ><%=student_work.simi_value%>%</li> | ||||
|               <% else %> | ||||
|                   <li ><%=student_work.simi_value%>%</li> | ||||
|               <% end %> | ||||
| 
 | ||||
|               <!--@works_hash[student_work.id].description --> | ||||
|               <!--<li ><a href="javascript:void(0);"  target="_blank" class="cha-btn" onclick = "show_code_compare()">查看</a>--> | ||||
|               <li > | ||||
|                 <%= link_to("查看", show_comparecode_course_path(:homework_id => @homework.id,:src_id => student_work.id,:dst_id => student_work.simi_id),:class => "cha-btn",:remote => true ) %> | ||||
|               </li> | ||||
|           <%else%> | ||||
|               <li class="chabox-w-151">无</li> | ||||
|               <li>--</li> | ||||
|               <li>--</li> | ||||
|               <li class="chabox-r-line">--</li> | ||||
|               <li >--</li> | ||||
|           <% end %> | ||||
|         </ul> | ||||
|         <%end%> | ||||
|     <%end%> | ||||
|   </div> | ||||
|   <div class="cl"></div> | ||||
| </div> | ||||
| 
 | ||||
| <script type="text/javascript"> | ||||
|     function show_code_compare() { | ||||
| 
 | ||||
| //        $("#ajax-modal").html('<%= escape_javascript( render :partial => 'courses/show_compare_code' ,:locals => {:src_code=> 1,:src_name=> 2,:dst_name=> 3, :dst_code=> 4,:language=> 5,})%>'); | ||||
| //        showModal('ajax-modal', '950px'); | ||||
| //        $('#ajax-modal').siblings().remove(); | ||||
| //        $('#ajax-modal').before("<a href='javascript:void(0)' onclick='closeModal();' style='margin-left: 935px;' class='resourceClose'></a>"); | ||||
| //        $('#ajax-modal').parent().css("top", "20%").css("left", "26.5%").css("position", "absolute"); | ||||
| //        $('#ajax-modal').parent().addClass("resourceUploadPopup"); | ||||
| //        $('#ajax-modal').css("padding-left", "16px").css("padding-bottom", "16px"); | ||||
| // | ||||
| //        function closeModal() { | ||||
| //            hideModal($(".contrast-box")); | ||||
| //        } | ||||
|     } | ||||
| 
 | ||||
| </script> | ||||
| @ -0,0 +1,101 @@ | ||||
| $("#ajax-modal").html('<%= escape_javascript( render :partial => 'courses/show_compare_code' ,:locals => {:src_code=> @src_code,:src_name=> @src_username,:dst_name=> @dst_username, :dst_code=> @dst_code,})%>'); | ||||
| showModal('ajax-modal', '1250px'); | ||||
| $('#ajax-modal').siblings().remove(); | ||||
| $('#ajax-modal').before("<a href='javascript:void(0)' onclick='closeModal();' style='margin-left: 1235px;' class='resourceClose'></a>"); | ||||
| $('#ajax-modal').parent().css("top","20%").css("left","20%").css("position","absolute"); | ||||
| //$('#ajax-modal').parent().addClass("resourceUploadPopup"); | ||||
| $('#ajax-modal').css("padding-left","16px").css("padding-bottom","16px").css("padding-top","10px"); | ||||
| 
 | ||||
| function closeModal(){ | ||||
|     hideModal($(".program-compare-code")); | ||||
| } | ||||
| 
 | ||||
| var program_name = "text/x-csrc"; | ||||
| var language = <%= @homework.language.to_i %>; | ||||
| if (language == 1) { | ||||
|     program_name = 'text/x-csrc'; | ||||
| } else if(language==2){ | ||||
|     program_name = 'text/x-c++src'; | ||||
| }else if(language==3){ | ||||
|     program_name = 'text/x-cython'; | ||||
| } else if(language==4){ | ||||
|     program_name = 'text/x-java'; | ||||
| } | ||||
| // | ||||
| //var editor_1 = CodeMirror(document.getElementById("program-code_1"), { | ||||
| //            mode: {name: program_name, | ||||
| //                version: 2, | ||||
| //                singleLineStringErrors: false}, | ||||
| //            lineNumbers: true, | ||||
| //            indentUnit: 2, | ||||
| //            matchBrackets: true, | ||||
| //            readOnly: true, | ||||
| //            value: $("#program-src_1").text() | ||||
| //        } | ||||
| //); | ||||
| // | ||||
| //var editor_2 = CodeMirror(document.getElementById("program-code_2"), { | ||||
| //            mode: {name: program_name, | ||||
| //                version: 2, | ||||
| //                singleLineStringErrors: false}, | ||||
| //            lineNumbers: true, | ||||
| //            indentUnit: 2, | ||||
| //            matchBrackets: true, | ||||
| //            readOnly: true, | ||||
| //            value: $("#program-src_2").text() | ||||
| //        } | ||||
| //); | ||||
| 
 | ||||
| var value, orig1, orig2, dv, panes = 2, highlight = true, connect = null, collapse = false; | ||||
| function initUI() { | ||||
|     if (value == null) return; | ||||
|     var target = document.getElementById("program-compare-code"); | ||||
|     target.innerHTML = ""; | ||||
|     dv = CodeMirror.MergeView(target, { | ||||
|         value: value, | ||||
|         origLeft: panes == 3 ? orig1 : null, | ||||
|         orig: orig2, | ||||
|         lineNumbers: true, | ||||
|         mode: program_name, | ||||
|         highlightDifferences: highlight, | ||||
|         connect: connect, | ||||
|         collapseIdentical: collapse | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
| function toggleDifferences() { | ||||
|     dv.setShowDifferences(highlight = !highlight); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| value = $("#program-src_1").text(); | ||||
| orig1 = $("#program-src_1").text(); | ||||
| orig2 = $("#program-src_2").text(); | ||||
| initUI(); | ||||
| 
 | ||||
| 
 | ||||
| function mergeViewHeight(mergeView) { | ||||
|     function editorHeight(editor) { | ||||
|         if (!editor) return 0; | ||||
|         return editor.getScrollInfo().height; | ||||
|     } | ||||
|     return Math.max(editorHeight(mergeView.leftOriginal()), | ||||
|             editorHeight(mergeView.editor()), | ||||
|             editorHeight(mergeView.rightOriginal())); | ||||
| } | ||||
| 
 | ||||
| function resize(mergeView) { | ||||
|     var height = mergeViewHeight(mergeView); | ||||
|     for(;;) { | ||||
|         if (mergeView.leftOriginal()) | ||||
|             mergeView.leftOriginal().setSize(null, height); | ||||
|         mergeView.editor().setSize(null, height); | ||||
|         if (mergeView.rightOriginal()) | ||||
|             mergeView.rightOriginal().setSize(null, height); | ||||
| 
 | ||||
|         var newHeight = mergeViewHeight(mergeView); | ||||
|         if (newHeight >= height) break; | ||||
|         else height = newHeight; | ||||
|     } | ||||
|     mergeView.wrap.style.height = height + "px"; | ||||
| } | ||||
| @ -0,0 +1,2 @@ | ||||
| 
 | ||||
| 
 | ||||
| @ -0,0 +1,6 @@ | ||||
| class AddSimiIdToStudentWorks < ActiveRecord::Migration | ||||
|   def change | ||||
|     add_column :student_works, :simi_id, :integer, :default => false | ||||
|     add_column :student_works, :simi_value, :integer, :default => false | ||||
|   end | ||||
| end | ||||
| @ -0,0 +1,5 @@ | ||||
| class AddSimiTimeToHomeworkCommons < ActiveRecord::Migration | ||||
|   def change | ||||
|     add_column :homework_commons, :simi_time, :datetime | ||||
|   end | ||||
| end | ||||
| @ -0,0 +1,113 @@ | ||||
| .CodeMirror-merge { | ||||
|   position: relative; | ||||
|   border: 1px solid #ddd; | ||||
|   white-space: pre; | ||||
| } | ||||
| 
 | ||||
| .CodeMirror-merge, .CodeMirror-merge .CodeMirror { | ||||
|   height: 350px; | ||||
| } | ||||
| 
 | ||||
| .CodeMirror-merge-2pane .CodeMirror-merge-pane { width: 47%; } | ||||
| .CodeMirror-merge-2pane .CodeMirror-merge-gap { width: 6%; } | ||||
| .CodeMirror-merge-3pane .CodeMirror-merge-pane { width: 31%; } | ||||
| .CodeMirror-merge-3pane .CodeMirror-merge-gap { width: 3.5%; } | ||||
| 
 | ||||
| .CodeMirror-merge-pane { | ||||
|   display: inline-block; | ||||
|   white-space: normal; | ||||
|   vertical-align: top; | ||||
| } | ||||
| .CodeMirror-merge-pane-rightmost { | ||||
|   position: absolute; | ||||
|   right: 0px; | ||||
|   z-index: 1; | ||||
| } | ||||
| 
 | ||||
| .CodeMirror-merge-gap { | ||||
|   z-index: 2; | ||||
|   display: inline-block; | ||||
|   height: 100%; | ||||
|   -moz-box-sizing: border-box; | ||||
|   box-sizing: border-box; | ||||
|   overflow: hidden; | ||||
|   border-left: 1px solid #ddd; | ||||
|   border-right: 1px solid #ddd; | ||||
|   position: relative; | ||||
|   background: #f8f8f8; | ||||
| } | ||||
| 
 | ||||
| .CodeMirror-merge-scrolllock-wrap { | ||||
|   position: absolute; | ||||
|   bottom: 0; left: 50%; | ||||
| } | ||||
| .CodeMirror-merge-scrolllock { | ||||
|   position: relative; | ||||
|   left: -50%; | ||||
|   cursor: pointer; | ||||
|   color: #555; | ||||
|   line-height: 1; | ||||
| } | ||||
| 
 | ||||
| .CodeMirror-merge-copybuttons-left, .CodeMirror-merge-copybuttons-right { | ||||
|   position: absolute; | ||||
|   left: 0; top: 0; | ||||
|   right: 0; bottom: 0; | ||||
|   line-height: 1; | ||||
| } | ||||
| 
 | ||||
| .CodeMirror-merge-copy { | ||||
|   position: absolute; | ||||
|   cursor: pointer; | ||||
|   color: #44c; | ||||
|   z-index: 3; | ||||
| } | ||||
| 
 | ||||
| .CodeMirror-merge-copy-reverse { | ||||
|   position: absolute; | ||||
|   cursor: pointer; | ||||
|   color: #44c; | ||||
| } | ||||
| 
 | ||||
| .CodeMirror-merge-copybuttons-left .CodeMirror-merge-copy { left: 2px; } | ||||
| .CodeMirror-merge-copybuttons-right .CodeMirror-merge-copy { right: 2px; } | ||||
| 
 | ||||
| .CodeMirror-merge-r-inserted, .CodeMirror-merge-l-inserted { | ||||
|   background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAACCAYAAACddGYaAAAAGUlEQVQI12MwuCXy3+CWyH8GBgYGJgYkAABZbAQ9ELXurwAAAABJRU5ErkJggg==); | ||||
|   background-position: bottom left; | ||||
|   background-repeat: repeat-x; | ||||
| } | ||||
| 
 | ||||
| .CodeMirror-merge-r-deleted, .CodeMirror-merge-l-deleted { | ||||
|   background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAACCAYAAACddGYaAAAAGUlEQVQI12M4Kyb2/6yY2H8GBgYGJgYkAABURgPz6Ks7wQAAAABJRU5ErkJggg==); | ||||
|   background-position: bottom left; | ||||
|   background-repeat: repeat-x; | ||||
| } | ||||
| 
 | ||||
| .CodeMirror-merge-r-chunk { background: #ffffe0; } | ||||
| .CodeMirror-merge-r-chunk-start { border-top: 1px solid #ee8; } | ||||
| .CodeMirror-merge-r-chunk-end { border-bottom: 1px solid #ee8; } | ||||
| .CodeMirror-merge-r-connect { fill: #ffffe0; stroke: #ee8; stroke-width: 1px; } | ||||
| 
 | ||||
| .CodeMirror-merge-l-chunk { background: #eef; } | ||||
| .CodeMirror-merge-l-chunk-start { border-top: 1px solid #88e; } | ||||
| .CodeMirror-merge-l-chunk-end { border-bottom: 1px solid #88e; } | ||||
| .CodeMirror-merge-l-connect { fill: #eef; stroke: #88e; stroke-width: 1px; } | ||||
| 
 | ||||
| .CodeMirror-merge-l-chunk.CodeMirror-merge-r-chunk { background: #dfd; } | ||||
| .CodeMirror-merge-l-chunk-start.CodeMirror-merge-r-chunk-start { border-top: 1px solid #4e4; } | ||||
| .CodeMirror-merge-l-chunk-end.CodeMirror-merge-r-chunk-end { border-bottom: 1px solid #4e4; } | ||||
| 
 | ||||
| .CodeMirror-merge-collapsed-widget:before { | ||||
|   content: "(...)"; | ||||
| } | ||||
| .CodeMirror-merge-collapsed-widget { | ||||
|   cursor: pointer; | ||||
|   color: #88b; | ||||
|   background: #eef; | ||||
|   border: 1px solid #ddf; | ||||
|   font-size: 90%; | ||||
|   padding: 0 3px; | ||||
|   border-radius: 4px; | ||||
| } | ||||
| .CodeMirror-merge-collapsed-line .CodeMirror-gutter-elt { display: none; } | ||||
| @ -0,0 +1,774 @@ | ||||
| // CodeMirror, copyright (c) by Marijn Haverbeke and others
 | ||||
| // Distributed under an MIT license: http://codemirror.net/LICENSE
 | ||||
| 
 | ||||
| // declare global: diff_match_patch, DIFF_INSERT, DIFF_DELETE, DIFF_EQUAL
 | ||||
| 
 | ||||
| (function(mod) { | ||||
|   if (typeof exports == "object" && typeof module == "object") // CommonJS
 | ||||
|     mod(require("../../lib/codemirror")); // Note non-packaged dependency diff_match_patch
 | ||||
|   else if (typeof define == "function" && define.amd) // AMD
 | ||||
|     define(["../../lib/codemirror", "diff_match_patch"], mod); | ||||
|   else // Plain browser env
 | ||||
|     mod(CodeMirror); | ||||
| })(function(CodeMirror) { | ||||
|   "use strict"; | ||||
|   var Pos = CodeMirror.Pos; | ||||
|   var svgNS = "http://www.w3.org/2000/svg"; | ||||
| 
 | ||||
|   function DiffView(mv, type) { | ||||
|     this.mv = mv; | ||||
|     this.type = type; | ||||
|     this.classes = type == "left" | ||||
|       ? {chunk: "CodeMirror-merge-l-chunk", | ||||
|          start: "CodeMirror-merge-l-chunk-start", | ||||
|          end: "CodeMirror-merge-l-chunk-end", | ||||
|          insert: "CodeMirror-merge-l-inserted", | ||||
|          del: "CodeMirror-merge-l-deleted", | ||||
|          connect: "CodeMirror-merge-l-connect"} | ||||
|       : {chunk: "CodeMirror-merge-r-chunk", | ||||
|          start: "CodeMirror-merge-r-chunk-start", | ||||
|          end: "CodeMirror-merge-r-chunk-end", | ||||
|          insert: "CodeMirror-merge-r-inserted", | ||||
|          del: "CodeMirror-merge-r-deleted", | ||||
|          connect: "CodeMirror-merge-r-connect"}; | ||||
|   } | ||||
| 
 | ||||
|   DiffView.prototype = { | ||||
|     constructor: DiffView, | ||||
|     init: function(pane, orig, options) { | ||||
|       this.edit = this.mv.edit; | ||||
|       (this.edit.state.diffViews || (this.edit.state.diffViews = [])).push(this); | ||||
|       this.orig = CodeMirror(pane, copyObj({value: orig, readOnly: !this.mv.options.allowEditingOriginals}, copyObj(options))); | ||||
|       this.orig.state.diffViews = [this]; | ||||
| 
 | ||||
|       this.diff = getDiff(asString(orig), asString(options.value)); | ||||
|       this.chunks = getChunks(this.diff); | ||||
|       this.diffOutOfDate = this.dealigned = false; | ||||
| 
 | ||||
|       this.showDifferences = options.showDifferences !== false; | ||||
|       this.forceUpdate = registerUpdate(this); | ||||
|       setScrollLock(this, true, false); | ||||
|       registerScroll(this); | ||||
|     }, | ||||
|     setShowDifferences: function(val) { | ||||
|       val = val !== false; | ||||
|       if (val != this.showDifferences) { | ||||
|         this.showDifferences = val; | ||||
|         this.forceUpdate("full"); | ||||
|       } | ||||
|     } | ||||
|   }; | ||||
| 
 | ||||
|   function ensureDiff(dv) { | ||||
|     if (dv.diffOutOfDate) { | ||||
|       dv.diff = getDiff(dv.orig.getValue(), dv.edit.getValue()); | ||||
|       dv.chunks = getChunks(dv.diff); | ||||
|       dv.diffOutOfDate = false; | ||||
|       CodeMirror.signal(dv.edit, "updateDiff", dv.diff); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   var updating = false; | ||||
|   function registerUpdate(dv) { | ||||
|     var edit = {from: 0, to: 0, marked: []}; | ||||
|     var orig = {from: 0, to: 0, marked: []}; | ||||
|     var debounceChange, updatingFast = false; | ||||
|     function update(mode) { | ||||
|       updating = true; | ||||
|       updatingFast = false; | ||||
|       if (mode == "full") { | ||||
|         if (dv.svg) clear(dv.svg); | ||||
|         if (dv.copyButtons) clear(dv.copyButtons); | ||||
|         clearMarks(dv.edit, edit.marked, dv.classes); | ||||
|         clearMarks(dv.orig, orig.marked, dv.classes); | ||||
|         edit.from = edit.to = orig.from = orig.to = 0; | ||||
|       } | ||||
|       ensureDiff(dv); | ||||
|       if (dv.showDifferences) { | ||||
|         updateMarks(dv.edit, dv.diff, edit, DIFF_INSERT, dv.classes); | ||||
|         updateMarks(dv.orig, dv.diff, orig, DIFF_DELETE, dv.classes); | ||||
|       } | ||||
|       makeConnections(dv); | ||||
| 
 | ||||
|       if (dv.mv.options.connect == "align") | ||||
|         alignChunks(dv); | ||||
|       updating = false; | ||||
|     } | ||||
|     function setDealign(fast) { | ||||
|       if (updating) return; | ||||
|       dv.dealigned = true; | ||||
|       set(fast); | ||||
|     } | ||||
|     function set(fast) { | ||||
|       if (updating || updatingFast) return; | ||||
|       clearTimeout(debounceChange); | ||||
|       if (fast === true) updatingFast = true; | ||||
|       debounceChange = setTimeout(update, fast === true ? 20 : 250); | ||||
|     } | ||||
|     function change(_cm, change) { | ||||
|       if (!dv.diffOutOfDate) { | ||||
|         dv.diffOutOfDate = true; | ||||
|         edit.from = edit.to = orig.from = orig.to = 0; | ||||
|       } | ||||
|       // Update faster when a line was added/removed
 | ||||
|       setDealign(change.text.length - 1 != change.to.line - change.from.line); | ||||
|     } | ||||
|     dv.edit.on("change", change); | ||||
|     dv.orig.on("change", change); | ||||
|     dv.edit.on("markerAdded", setDealign); | ||||
|     dv.edit.on("markerCleared", setDealign); | ||||
|     dv.orig.on("markerAdded", setDealign); | ||||
|     dv.orig.on("markerCleared", setDealign); | ||||
|     dv.edit.on("viewportChange", function() { set(false); }); | ||||
|     dv.orig.on("viewportChange", function() { set(false); }); | ||||
|     update(); | ||||
|     return update; | ||||
|   } | ||||
| 
 | ||||
|   function registerScroll(dv) { | ||||
|     dv.edit.on("scroll", function() { | ||||
|       syncScroll(dv, DIFF_INSERT) && makeConnections(dv); | ||||
|     }); | ||||
|     dv.orig.on("scroll", function() { | ||||
|       syncScroll(dv, DIFF_DELETE) && makeConnections(dv); | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   function syncScroll(dv, type) { | ||||
|     // Change handler will do a refresh after a timeout when diff is out of date
 | ||||
|     if (dv.diffOutOfDate) return false; | ||||
|     if (!dv.lockScroll) return true; | ||||
|     var editor, other, now = +new Date; | ||||
|     if (type == DIFF_INSERT) { editor = dv.edit; other = dv.orig; } | ||||
|     else { editor = dv.orig; other = dv.edit; } | ||||
|     // Don't take action if the position of this editor was recently set
 | ||||
|     // (to prevent feedback loops)
 | ||||
|     if (editor.state.scrollSetBy == dv && (editor.state.scrollSetAt || 0) + 50 > now) return false; | ||||
| 
 | ||||
|     var sInfo = editor.getScrollInfo(); | ||||
|     if (dv.mv.options.connect == "align") { | ||||
|       targetPos = sInfo.top; | ||||
|     } else { | ||||
|       var halfScreen = .5 * sInfo.clientHeight, midY = sInfo.top + halfScreen; | ||||
|       var mid = editor.lineAtHeight(midY, "local"); | ||||
|       var around = chunkBoundariesAround(dv.chunks, mid, type == DIFF_INSERT); | ||||
|       var off = getOffsets(editor, type == DIFF_INSERT ? around.edit : around.orig); | ||||
|       var offOther = getOffsets(other, type == DIFF_INSERT ? around.orig : around.edit); | ||||
|       var ratio = (midY - off.top) / (off.bot - off.top); | ||||
|       var targetPos = (offOther.top - halfScreen) + ratio * (offOther.bot - offOther.top); | ||||
| 
 | ||||
|       var botDist, mix; | ||||
|       // Some careful tweaking to make sure no space is left out of view
 | ||||
|       // when scrolling to top or bottom.
 | ||||
|       if (targetPos > sInfo.top && (mix = sInfo.top / halfScreen) < 1) { | ||||
|         targetPos = targetPos * mix + sInfo.top * (1 - mix); | ||||
|       } else if ((botDist = sInfo.height - sInfo.clientHeight - sInfo.top) < halfScreen) { | ||||
|         var otherInfo = other.getScrollInfo(); | ||||
|         var botDistOther = otherInfo.height - otherInfo.clientHeight - targetPos; | ||||
|         if (botDistOther > botDist && (mix = botDist / halfScreen) < 1) | ||||
|           targetPos = targetPos * mix + (otherInfo.height - otherInfo.clientHeight - botDist) * (1 - mix); | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     other.scrollTo(sInfo.left, targetPos); | ||||
|     other.state.scrollSetAt = now; | ||||
|     other.state.scrollSetBy = dv; | ||||
|     return true; | ||||
|   } | ||||
| 
 | ||||
|   function getOffsets(editor, around) { | ||||
|     var bot = around.after; | ||||
|     if (bot == null) bot = editor.lastLine() + 1; | ||||
|     return {top: editor.heightAtLine(around.before || 0, "local"), | ||||
|             bot: editor.heightAtLine(bot, "local")}; | ||||
|   } | ||||
| 
 | ||||
|   function setScrollLock(dv, val, action) { | ||||
|     dv.lockScroll = val; | ||||
|     if (val && action != false) syncScroll(dv, DIFF_INSERT) && makeConnections(dv); | ||||
|     dv.lockButton.innerHTML = val ? "\u21db\u21da" : "\u21db  \u21da"; | ||||
|   } | ||||
| 
 | ||||
|   // Updating the marks for editor content
 | ||||
| 
 | ||||
|   function clearMarks(editor, arr, classes) { | ||||
|     for (var i = 0; i < arr.length; ++i) { | ||||
|       var mark = arr[i]; | ||||
|       if (mark instanceof CodeMirror.TextMarker) { | ||||
|         mark.clear(); | ||||
|       } else if (mark.parent) { | ||||
|         editor.removeLineClass(mark, "background", classes.chunk); | ||||
|         editor.removeLineClass(mark, "background", classes.start); | ||||
|         editor.removeLineClass(mark, "background", classes.end); | ||||
|       } | ||||
|     } | ||||
|     arr.length = 0; | ||||
|   } | ||||
| 
 | ||||
|   // FIXME maybe add a margin around viewport to prevent too many updates
 | ||||
|   function updateMarks(editor, diff, state, type, classes) { | ||||
|     var vp = editor.getViewport(); | ||||
|     editor.operation(function() { | ||||
|       if (state.from == state.to || vp.from - state.to > 20 || state.from - vp.to > 20) { | ||||
|         clearMarks(editor, state.marked, classes); | ||||
|         markChanges(editor, diff, type, state.marked, vp.from, vp.to, classes); | ||||
|         state.from = vp.from; state.to = vp.to; | ||||
|       } else { | ||||
|         if (vp.from < state.from) { | ||||
|           markChanges(editor, diff, type, state.marked, vp.from, state.from, classes); | ||||
|           state.from = vp.from; | ||||
|         } | ||||
|         if (vp.to > state.to) { | ||||
|           markChanges(editor, diff, type, state.marked, state.to, vp.to, classes); | ||||
|           state.to = vp.to; | ||||
|         } | ||||
|       } | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   function markChanges(editor, diff, type, marks, from, to, classes) { | ||||
|     var pos = Pos(0, 0); | ||||
|     var top = Pos(from, 0), bot = editor.clipPos(Pos(to - 1)); | ||||
|     var cls = type == DIFF_DELETE ? classes.del : classes.insert; | ||||
|     function markChunk(start, end) { | ||||
|       var bfrom = Math.max(from, start), bto = Math.min(to, end); | ||||
|       for (var i = bfrom; i < bto; ++i) { | ||||
|         var line = editor.addLineClass(i, "background", classes.chunk); | ||||
|         if (i == start) editor.addLineClass(line, "background", classes.start); | ||||
|         if (i == end - 1) editor.addLineClass(line, "background", classes.end); | ||||
|         marks.push(line); | ||||
|       } | ||||
|       // When the chunk is empty, make sure a horizontal line shows up
 | ||||
|       if (start == end && bfrom == end && bto == end) { | ||||
|         if (bfrom) | ||||
|           marks.push(editor.addLineClass(bfrom - 1, "background", classes.end)); | ||||
|         else | ||||
|           marks.push(editor.addLineClass(bfrom, "background", classes.start)); | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     var chunkStart = 0; | ||||
|     for (var i = 0; i < diff.length; ++i) { | ||||
|       var part = diff[i], tp = part[0], str = part[1]; | ||||
|       if (tp == DIFF_EQUAL) { | ||||
|         var cleanFrom = pos.line + (startOfLineClean(diff, i) ? 0 : 1); | ||||
|         moveOver(pos, str); | ||||
|         var cleanTo = pos.line + (endOfLineClean(diff, i) ? 1 : 0); | ||||
|         if (cleanTo > cleanFrom) { | ||||
|           if (i) markChunk(chunkStart, cleanFrom); | ||||
|           chunkStart = cleanTo; | ||||
|         } | ||||
|       } else { | ||||
|         if (tp == type) { | ||||
|           var end = moveOver(pos, str, true); | ||||
|           var a = posMax(top, pos), b = posMin(bot, end); | ||||
|           if (!posEq(a, b)) | ||||
|             marks.push(editor.markText(a, b, {className: cls})); | ||||
|           pos = end; | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|     if (chunkStart <= pos.line) markChunk(chunkStart, pos.line + 1); | ||||
|   } | ||||
| 
 | ||||
|   // Updating the gap between editor and original
 | ||||
| 
 | ||||
|   function makeConnections(dv) { | ||||
|     if (!dv.showDifferences) return; | ||||
| 
 | ||||
|     if (dv.svg) { | ||||
|       clear(dv.svg); | ||||
|       var w = dv.gap.offsetWidth; | ||||
|       attrs(dv.svg, "width", w, "height", dv.gap.offsetHeight); | ||||
|     } | ||||
|     if (dv.copyButtons) clear(dv.copyButtons); | ||||
| 
 | ||||
|     var vpEdit = dv.edit.getViewport(), vpOrig = dv.orig.getViewport(); | ||||
|     var sTopEdit = dv.edit.getScrollInfo().top, sTopOrig = dv.orig.getScrollInfo().top; | ||||
|     for (var i = 0; i < dv.chunks.length; i++) { | ||||
|       var ch = dv.chunks[i]; | ||||
|       if (ch.editFrom <= vpEdit.to && ch.editTo >= vpEdit.from && | ||||
|           ch.origFrom <= vpOrig.to && ch.origTo >= vpOrig.from) | ||||
|         drawConnectorsForChunk(dv, ch, sTopOrig, sTopEdit, w); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   function getMatchingOrigLine(editLine, chunks) { | ||||
|     var editStart = 0, origStart = 0; | ||||
|     for (var i = 0; i < chunks.length; i++) { | ||||
|       var chunk = chunks[i]; | ||||
|       if (chunk.editTo > editLine && chunk.editFrom <= editLine) return null; | ||||
|       if (chunk.editFrom > editLine) break; | ||||
|       editStart = chunk.editTo; | ||||
|       origStart = chunk.origTo; | ||||
|     } | ||||
|     return origStart + (editLine - editStart); | ||||
|   } | ||||
| 
 | ||||
|   function findAlignedLines(dv, other) { | ||||
|     var linesToAlign = []; | ||||
|     for (var i = 0; i < dv.chunks.length; i++) { | ||||
|       var chunk = dv.chunks[i]; | ||||
|       linesToAlign.push([chunk.origTo, chunk.editTo, other ? getMatchingOrigLine(chunk.editTo, other.chunks) : null]); | ||||
|     } | ||||
|     if (other) { | ||||
|       for (var i = 0; i < other.chunks.length; i++) { | ||||
|         var chunk = other.chunks[i]; | ||||
|         for (var j = 0; j < linesToAlign.length; j++) { | ||||
|           var align = linesToAlign[j]; | ||||
|           if (align[1] == chunk.editTo) { | ||||
|             j = -1; | ||||
|             break; | ||||
|           } else if (align[1] > chunk.editTo) { | ||||
|             break; | ||||
|           } | ||||
|         } | ||||
|         if (j > -1) | ||||
|           linesToAlign.splice(j - 1, 0, [getMatchingOrigLine(chunk.editTo, dv.chunks), chunk.editTo, chunk.origTo]); | ||||
|       } | ||||
|     } | ||||
|     return linesToAlign; | ||||
|   } | ||||
| 
 | ||||
|   function alignChunks(dv, force) { | ||||
|     if (!dv.dealigned && !force) return; | ||||
|     if (!dv.orig.curOp) return dv.orig.operation(function() { | ||||
|       alignChunks(dv, force); | ||||
|     }); | ||||
| 
 | ||||
|     dv.dealigned = false; | ||||
|     var other = dv.mv.left == dv ? dv.mv.right : dv.mv.left; | ||||
|     if (other) { | ||||
|       ensureDiff(other); | ||||
|       other.dealigned = false; | ||||
|     } | ||||
|     var linesToAlign = findAlignedLines(dv, other); | ||||
| 
 | ||||
|     // Clear old aligners
 | ||||
|     var aligners = dv.mv.aligners; | ||||
|     for (var i = 0; i < aligners.length; i++) | ||||
|       aligners[i].clear(); | ||||
|     aligners.length = 0; | ||||
| 
 | ||||
|     var cm = [dv.orig, dv.edit], scroll = []; | ||||
|     if (other) cm.push(other.orig); | ||||
|     for (var i = 0; i < cm.length; i++) | ||||
|       scroll.push(cm[i].getScrollInfo().top); | ||||
| 
 | ||||
|     for (var ln = 0; ln < linesToAlign.length; ln++) | ||||
|       alignLines(cm, linesToAlign[ln], aligners); | ||||
| 
 | ||||
|     for (var i = 0; i < cm.length; i++) | ||||
|       cm[i].scrollTo(null, scroll[i]); | ||||
|   } | ||||
| 
 | ||||
|   function alignLines(cm, lines, aligners) { | ||||
|     var maxOffset = 0, offset = []; | ||||
|     for (var i = 0; i < cm.length; i++) if (lines[i] != null) { | ||||
|       var off = cm[i].heightAtLine(lines[i], "local"); | ||||
|       offset[i] = off; | ||||
|       maxOffset = Math.max(maxOffset, off); | ||||
|     } | ||||
|     for (var i = 0; i < cm.length; i++) if (lines[i] != null) { | ||||
|       var diff = maxOffset - offset[i]; | ||||
|       if (diff > 1) | ||||
|         aligners.push(padAbove(cm[i], lines[i], diff)); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   function padAbove(cm, line, size) { | ||||
|     var above = true; | ||||
|     if (line > cm.lastLine()) { | ||||
|       line--; | ||||
|       above = false; | ||||
|     } | ||||
|     var elt = document.createElement("div"); | ||||
|     elt.className = "CodeMirror-merge-spacer"; | ||||
|     elt.style.height = size + "px"; elt.style.minWidth = "1px"; | ||||
|     return cm.addLineWidget(line, elt, {height: size, above: above}); | ||||
|   } | ||||
| 
 | ||||
|   function drawConnectorsForChunk(dv, chunk, sTopOrig, sTopEdit, w) { | ||||
|     var flip = dv.type == "left"; | ||||
|     var top = dv.orig.heightAtLine(chunk.origFrom, "local") - sTopOrig; | ||||
|     if (dv.svg) { | ||||
|       var topLpx = top; | ||||
|       var topRpx = dv.edit.heightAtLine(chunk.editFrom, "local") - sTopEdit; | ||||
|       if (flip) { var tmp = topLpx; topLpx = topRpx; topRpx = tmp; } | ||||
|       var botLpx = dv.orig.heightAtLine(chunk.origTo, "local") - sTopOrig; | ||||
|       var botRpx = dv.edit.heightAtLine(chunk.editTo, "local") - sTopEdit; | ||||
|       if (flip) { var tmp = botLpx; botLpx = botRpx; botRpx = tmp; } | ||||
|       var curveTop = " C " + w/2 + " " + topRpx + " " + w/2 + " " + topLpx + " " + (w + 2) + " " + topLpx; | ||||
|       var curveBot = " C " + w/2 + " " + botLpx + " " + w/2 + " " + botRpx + " -1 " + botRpx; | ||||
|       attrs(dv.svg.appendChild(document.createElementNS(svgNS, "path")), | ||||
|             "d", "M -1 " + topRpx + curveTop + " L " + (w + 2) + " " + botLpx + curveBot + " z", | ||||
|             "class", dv.classes.connect); | ||||
|     } | ||||
|     if (dv.copyButtons) { | ||||
|       var copy = dv.copyButtons.appendChild(elt("div", dv.type == "left" ? "\u21dd" : "\u21dc", | ||||
|                                                 "CodeMirror-merge-copy")); | ||||
|       var editOriginals = dv.mv.options.allowEditingOriginals; | ||||
|       copy.title = editOriginals ? "Push to left" : "Revert chunk"; | ||||
|       copy.chunk = chunk; | ||||
|       copy.style.top = top + "px"; | ||||
| 
 | ||||
|       if (editOriginals) { | ||||
|         var topReverse = dv.orig.heightAtLine(chunk.editFrom, "local") - sTopEdit; | ||||
|         var copyReverse = dv.copyButtons.appendChild(elt("div", dv.type == "right" ? "\u21dd" : "\u21dc", | ||||
|                                                          "CodeMirror-merge-copy-reverse")); | ||||
|         copyReverse.title = "Push to right"; | ||||
|         copyReverse.chunk = {editFrom: chunk.origFrom, editTo: chunk.origTo, | ||||
|                              origFrom: chunk.editFrom, origTo: chunk.editTo}; | ||||
|         copyReverse.style.top = topReverse + "px"; | ||||
|         dv.type == "right" ? copyReverse.style.left = "2px" : copyReverse.style.right = "2px"; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   function copyChunk(dv, to, from, chunk) { | ||||
|     if (dv.diffOutOfDate) return; | ||||
|     var editStart = chunk.editTo > to.lastLine() ? Pos(chunk.editFrom - 1) : Pos(chunk.editFrom, 0) | ||||
|     var origStart = chunk.origTo > from.lastLine() ? Pos(chunk.origFrom - 1) : Pos(chunk.origFrom, 0) | ||||
|     to.replaceRange(from.getRange(origStart, Pos(chunk.origTo, 0)), editStart, Pos(chunk.editTo, 0)) | ||||
|   } | ||||
| 
 | ||||
|   // Merge view, containing 0, 1, or 2 diff views.
 | ||||
| 
 | ||||
|   var MergeView = CodeMirror.MergeView = function(node, options) { | ||||
|     if (!(this instanceof MergeView)) return new MergeView(node, options); | ||||
| 
 | ||||
|     this.options = options; | ||||
|     var origLeft = options.origLeft, origRight = options.origRight == null ? options.orig : options.origRight; | ||||
| 
 | ||||
|     var hasLeft = origLeft != null, hasRight = origRight != null; | ||||
|     var panes = 1 + (hasLeft ? 1 : 0) + (hasRight ? 1 : 0); | ||||
|     var wrap = [], left = this.left = null, right = this.right = null; | ||||
|     var self = this; | ||||
| 
 | ||||
|     if (hasLeft) { | ||||
|       left = this.left = new DiffView(this, "left"); | ||||
|       var leftPane = elt("div", null, "CodeMirror-merge-pane"); | ||||
|       wrap.push(leftPane); | ||||
|       wrap.push(buildGap(left)); | ||||
|     } | ||||
| 
 | ||||
|     var editPane = elt("div", null, "CodeMirror-merge-pane"); | ||||
|     wrap.push(editPane); | ||||
| 
 | ||||
|     if (hasRight) { | ||||
|       right = this.right = new DiffView(this, "right"); | ||||
|       wrap.push(buildGap(right)); | ||||
|       var rightPane = elt("div", null, "CodeMirror-merge-pane"); | ||||
|       wrap.push(rightPane); | ||||
|     } | ||||
| 
 | ||||
|     (hasRight ? rightPane : editPane).className += " CodeMirror-merge-pane-rightmost"; | ||||
| 
 | ||||
|     wrap.push(elt("div", null, null, "height: 0; clear: both;")); | ||||
| 
 | ||||
|     var wrapElt = this.wrap = node.appendChild(elt("div", wrap, "CodeMirror-merge CodeMirror-merge-" + panes + "pane")); | ||||
|     this.edit = CodeMirror(editPane, copyObj(options)); | ||||
| 
 | ||||
|     if (left) left.init(leftPane, origLeft, options); | ||||
|     if (right) right.init(rightPane, origRight, options); | ||||
| 
 | ||||
|     if (options.collapseIdentical) | ||||
|       this.editor().operation(function() { | ||||
|         collapseIdenticalStretches(self, options.collapseIdentical); | ||||
|       }); | ||||
|     if (options.connect == "align") { | ||||
|       this.aligners = []; | ||||
|       alignChunks(this.left || this.right, true); | ||||
|     } | ||||
| 
 | ||||
|     var onResize = function() { | ||||
|       if (left) makeConnections(left); | ||||
|       if (right) makeConnections(right); | ||||
|     }; | ||||
|     CodeMirror.on(window, "resize", onResize); | ||||
|     var resizeInterval = setInterval(function() { | ||||
|       for (var p = wrapElt.parentNode; p && p != document.body; p = p.parentNode) {} | ||||
|       if (!p) { clearInterval(resizeInterval); CodeMirror.off(window, "resize", onResize); } | ||||
|     }, 5000); | ||||
|   }; | ||||
| 
 | ||||
|   function buildGap(dv) { | ||||
|     var lock = dv.lockButton = elt("div", null, "CodeMirror-merge-scrolllock"); | ||||
|     lock.title = "Toggle locked scrolling"; | ||||
|     var lockWrap = elt("div", [lock], "CodeMirror-merge-scrolllock-wrap"); | ||||
|     CodeMirror.on(lock, "click", function() { setScrollLock(dv, !dv.lockScroll); }); | ||||
|     var gapElts = [lockWrap]; | ||||
|       //去掉复制功能 yuanke20160408
 | ||||
| //    if (dv.mv.options.revertButtons !== false) {
 | ||||
| //      dv.copyButtons = elt("div", null, "CodeMirror-merge-copybuttons-" + dv.type);
 | ||||
| //      CodeMirror.on(dv.copyButtons, "click", function(e) {
 | ||||
| //        var node = e.target || e.srcElement;
 | ||||
| //        if (!node.chunk) return;
 | ||||
| //        if (node.className == "CodeMirror-merge-copy-reverse") {
 | ||||
| //          copyChunk(dv, dv.orig, dv.edit, node.chunk);
 | ||||
| //          return;
 | ||||
| //        }
 | ||||
| //        copyChunk(dv, dv.edit, dv.orig, node.chunk);
 | ||||
| //      });
 | ||||
| //      gapElts.unshift(dv.copyButtons);
 | ||||
| //    }
 | ||||
|     if (dv.mv.options.connect != "align") { | ||||
|       var svg = document.createElementNS && document.createElementNS(svgNS, "svg"); | ||||
|       if (svg && !svg.createSVGRect) svg = null; | ||||
|       dv.svg = svg; | ||||
|       if (svg) gapElts.push(svg); | ||||
|     } | ||||
| 
 | ||||
|     return dv.gap = elt("div", gapElts, "CodeMirror-merge-gap"); | ||||
|   } | ||||
| 
 | ||||
|   MergeView.prototype = { | ||||
|     constuctor: MergeView, | ||||
|     editor: function() { return this.edit; }, | ||||
|     rightOriginal: function() { return this.right && this.right.orig; }, | ||||
|     leftOriginal: function() { return this.left && this.left.orig; }, | ||||
|     setShowDifferences: function(val) { | ||||
|       if (this.right) this.right.setShowDifferences(val); | ||||
|       if (this.left) this.left.setShowDifferences(val); | ||||
|     }, | ||||
|     rightChunks: function() { | ||||
|       if (this.right) { ensureDiff(this.right); return this.right.chunks; } | ||||
|     }, | ||||
|     leftChunks: function() { | ||||
|       if (this.left) { ensureDiff(this.left); return this.left.chunks; } | ||||
|     } | ||||
|   }; | ||||
| 
 | ||||
|   function asString(obj) { | ||||
|     if (typeof obj == "string") return obj; | ||||
|     else return obj.getValue(); | ||||
|   } | ||||
| 
 | ||||
|   // Operations on diffs
 | ||||
| 
 | ||||
|   var dmp = new diff_match_patch(); | ||||
|   function getDiff(a, b) { | ||||
|     var diff = dmp.diff_main(a, b); | ||||
|     dmp.diff_cleanupSemantic(diff); | ||||
|     // The library sometimes leaves in empty parts, which confuse the algorithm
 | ||||
|     for (var i = 0; i < diff.length; ++i) { | ||||
|       var part = diff[i]; | ||||
|       if (!part[1]) { | ||||
|         diff.splice(i--, 1); | ||||
|       } else if (i && diff[i - 1][0] == part[0]) { | ||||
|         diff.splice(i--, 1); | ||||
|         diff[i][1] += part[1]; | ||||
|       } | ||||
|     } | ||||
|     return diff; | ||||
|   } | ||||
| 
 | ||||
|   function getChunks(diff) { | ||||
|     var chunks = []; | ||||
|     var startEdit = 0, startOrig = 0; | ||||
|     var edit = Pos(0, 0), orig = Pos(0, 0); | ||||
|     for (var i = 0; i < diff.length; ++i) { | ||||
|       var part = diff[i], tp = part[0]; | ||||
|       if (tp == DIFF_EQUAL) { | ||||
|         var startOff = startOfLineClean(diff, i) ? 0 : 1; | ||||
|         var cleanFromEdit = edit.line + startOff, cleanFromOrig = orig.line + startOff; | ||||
|         moveOver(edit, part[1], null, orig); | ||||
|         var endOff = endOfLineClean(diff, i) ? 1 : 0; | ||||
|         var cleanToEdit = edit.line + endOff, cleanToOrig = orig.line + endOff; | ||||
|         if (cleanToEdit > cleanFromEdit) { | ||||
|           if (i) chunks.push({origFrom: startOrig, origTo: cleanFromOrig, | ||||
|                               editFrom: startEdit, editTo: cleanFromEdit}); | ||||
|           startEdit = cleanToEdit; startOrig = cleanToOrig; | ||||
|         } | ||||
|       } else { | ||||
|         moveOver(tp == DIFF_INSERT ? edit : orig, part[1]); | ||||
|       } | ||||
|     } | ||||
|     if (startEdit <= edit.line || startOrig <= orig.line) | ||||
|       chunks.push({origFrom: startOrig, origTo: orig.line + 1, | ||||
|                    editFrom: startEdit, editTo: edit.line + 1}); | ||||
|     return chunks; | ||||
|   } | ||||
| 
 | ||||
|   function endOfLineClean(diff, i) { | ||||
|     if (i == diff.length - 1) return true; | ||||
|     var next = diff[i + 1][1]; | ||||
|     if (next.length == 1 || next.charCodeAt(0) != 10) return false; | ||||
|     if (i == diff.length - 2) return true; | ||||
|     next = diff[i + 2][1]; | ||||
|     return next.length > 1 && next.charCodeAt(0) == 10; | ||||
|   } | ||||
| 
 | ||||
|   function startOfLineClean(diff, i) { | ||||
|     if (i == 0) return true; | ||||
|     var last = diff[i - 1][1]; | ||||
|     if (last.charCodeAt(last.length - 1) != 10) return false; | ||||
|     if (i == 1) return true; | ||||
|     last = diff[i - 2][1]; | ||||
|     return last.charCodeAt(last.length - 1) == 10; | ||||
|   } | ||||
| 
 | ||||
|   function chunkBoundariesAround(chunks, n, nInEdit) { | ||||
|     var beforeE, afterE, beforeO, afterO; | ||||
|     for (var i = 0; i < chunks.length; i++) { | ||||
|       var chunk = chunks[i]; | ||||
|       var fromLocal = nInEdit ? chunk.editFrom : chunk.origFrom; | ||||
|       var toLocal = nInEdit ? chunk.editTo : chunk.origTo; | ||||
|       if (afterE == null) { | ||||
|         if (fromLocal > n) { afterE = chunk.editFrom; afterO = chunk.origFrom; } | ||||
|         else if (toLocal > n) { afterE = chunk.editTo; afterO = chunk.origTo; } | ||||
|       } | ||||
|       if (toLocal <= n) { beforeE = chunk.editTo; beforeO = chunk.origTo; } | ||||
|       else if (fromLocal <= n) { beforeE = chunk.editFrom; beforeO = chunk.origFrom; } | ||||
|     } | ||||
|     return {edit: {before: beforeE, after: afterE}, orig: {before: beforeO, after: afterO}}; | ||||
|   } | ||||
| 
 | ||||
|   function collapseSingle(cm, from, to) { | ||||
|     cm.addLineClass(from, "wrap", "CodeMirror-merge-collapsed-line"); | ||||
|     var widget = document.createElement("span"); | ||||
|     widget.className = "CodeMirror-merge-collapsed-widget"; | ||||
|     widget.title = "Identical text collapsed. Click to expand."; | ||||
|     var mark = cm.markText(Pos(from, 0), Pos(to - 1), { | ||||
|       inclusiveLeft: true, | ||||
|       inclusiveRight: true, | ||||
|       replacedWith: widget, | ||||
|       clearOnEnter: true | ||||
|     }); | ||||
|     function clear() { | ||||
|       mark.clear(); | ||||
|       cm.removeLineClass(from, "wrap", "CodeMirror-merge-collapsed-line"); | ||||
|     } | ||||
|     CodeMirror.on(widget, "click", clear); | ||||
|     return {mark: mark, clear: clear}; | ||||
|   } | ||||
| 
 | ||||
|   function collapseStretch(size, editors) { | ||||
|     var marks = []; | ||||
|     function clear() { | ||||
|       for (var i = 0; i < marks.length; i++) marks[i].clear(); | ||||
|     } | ||||
|     for (var i = 0; i < editors.length; i++) { | ||||
|       var editor = editors[i]; | ||||
|       var mark = collapseSingle(editor.cm, editor.line, editor.line + size); | ||||
|       marks.push(mark); | ||||
|       mark.mark.on("clear", clear); | ||||
|     } | ||||
|     return marks[0].mark; | ||||
|   } | ||||
| 
 | ||||
|   function unclearNearChunks(dv, margin, off, clear) { | ||||
|     for (var i = 0; i < dv.chunks.length; i++) { | ||||
|       var chunk = dv.chunks[i]; | ||||
|       for (var l = chunk.editFrom - margin; l < chunk.editTo + margin; l++) { | ||||
|         var pos = l + off; | ||||
|         if (pos >= 0 && pos < clear.length) clear[pos] = false; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   function collapseIdenticalStretches(mv, margin) { | ||||
|     if (typeof margin != "number") margin = 2; | ||||
|     var clear = [], edit = mv.editor(), off = edit.firstLine(); | ||||
|     for (var l = off, e = edit.lastLine(); l <= e; l++) clear.push(true); | ||||
|     if (mv.left) unclearNearChunks(mv.left, margin, off, clear); | ||||
|     if (mv.right) unclearNearChunks(mv.right, margin, off, clear); | ||||
| 
 | ||||
|     for (var i = 0; i < clear.length; i++) { | ||||
|       if (clear[i]) { | ||||
|         var line = i + off; | ||||
|         for (var size = 1; i < clear.length - 1 && clear[i + 1]; i++, size++) {} | ||||
|         if (size > margin) { | ||||
|           var editors = [{line: line, cm: edit}]; | ||||
|           if (mv.left) editors.push({line: getMatchingOrigLine(line, mv.left.chunks), cm: mv.left.orig}); | ||||
|           if (mv.right) editors.push({line: getMatchingOrigLine(line, mv.right.chunks), cm: mv.right.orig}); | ||||
|           var mark = collapseStretch(size, editors); | ||||
|           if (mv.options.onCollapse) mv.options.onCollapse(mv, line, size, mark); | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   // General utilities
 | ||||
| 
 | ||||
|   function elt(tag, content, className, style) { | ||||
|     var e = document.createElement(tag); | ||||
|     if (className) e.className = className; | ||||
|     if (style) e.style.cssText = style; | ||||
|     if (typeof content == "string") e.appendChild(document.createTextNode(content)); | ||||
|     else if (content) for (var i = 0; i < content.length; ++i) e.appendChild(content[i]); | ||||
|     return e; | ||||
|   } | ||||
| 
 | ||||
|   function clear(node) { | ||||
|     for (var count = node.childNodes.length; count > 0; --count) | ||||
|       node.removeChild(node.firstChild); | ||||
|   } | ||||
| 
 | ||||
|   function attrs(elt) { | ||||
|     for (var i = 1; i < arguments.length; i += 2) | ||||
|       elt.setAttribute(arguments[i], arguments[i+1]); | ||||
|   } | ||||
| 
 | ||||
|   function copyObj(obj, target) { | ||||
|     if (!target) target = {}; | ||||
|     for (var prop in obj) if (obj.hasOwnProperty(prop)) target[prop] = obj[prop]; | ||||
|     return target; | ||||
|   } | ||||
| 
 | ||||
|   function moveOver(pos, str, copy, other) { | ||||
|     var out = copy ? Pos(pos.line, pos.ch) : pos, at = 0; | ||||
|     for (;;) { | ||||
|       var nl = str.indexOf("\n", at); | ||||
|       if (nl == -1) break; | ||||
|       ++out.line; | ||||
|       if (other) ++other.line; | ||||
|       at = nl + 1; | ||||
|     } | ||||
|     out.ch = (at ? 0 : out.ch) + (str.length - at); | ||||
|     if (other) other.ch = (at ? 0 : other.ch) + (str.length - at); | ||||
|     return out; | ||||
|   } | ||||
| 
 | ||||
|   function posMin(a, b) { return (a.line - b.line || a.ch - b.ch) < 0 ? a : b; } | ||||
|   function posMax(a, b) { return (a.line - b.line || a.ch - b.ch) > 0 ? a : b; } | ||||
|   function posEq(a, b) { return a.line == b.line && a.ch == b.ch; } | ||||
| 
 | ||||
|   function findPrevDiff(chunks, start, isOrig) { | ||||
|     for (var i = chunks.length - 1; i >= 0; i--) { | ||||
|       var chunk = chunks[i]; | ||||
|       var to = (isOrig ? chunk.origTo : chunk.editTo) - 1; | ||||
|       if (to < start) return to; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   function findNextDiff(chunks, start, isOrig) { | ||||
|     for (var i = 0; i < chunks.length; i++) { | ||||
|       var chunk = chunks[i]; | ||||
|       var from = (isOrig ? chunk.origFrom : chunk.editFrom); | ||||
|       if (from > start) return from; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   function goNearbyDiff(cm, dir) { | ||||
|     var found = null, views = cm.state.diffViews, line = cm.getCursor().line; | ||||
|     if (views) for (var i = 0; i < views.length; i++) { | ||||
|       var dv = views[i], isOrig = cm == dv.orig; | ||||
|       ensureDiff(dv); | ||||
|       var pos = dir < 0 ? findPrevDiff(dv.chunks, line, isOrig) : findNextDiff(dv.chunks, line, isOrig); | ||||
|       if (pos != null && (found == null || (dir < 0 ? pos > found : pos < found))) | ||||
|         found = pos; | ||||
|     } | ||||
|     if (found != null) | ||||
|       cm.setCursor(found, 0); | ||||
|     else | ||||
|       return CodeMirror.Pass; | ||||
|   } | ||||
| 
 | ||||
|   CodeMirror.commands.goNextDiff = function(cm) { | ||||
|     return goNearbyDiff(cm, 1); | ||||
|   }; | ||||
|   CodeMirror.commands.goPrevDiff = function(cm) { | ||||
|     return goNearbyDiff(cm, -1); | ||||
|   }; | ||||
| }); | ||||
					Loading…
					
					
				
		Reference in new issue