Files
codeocean/app/views/request_for_comments/show.html.erb
Maximilian Grundke 74a7deffa2 Change date position
2017-08-17 15:44:51 +02:00

370 lines
12 KiB
Plaintext

<div class="list-group">
<h4 id ="exercise_caption" class="list-group-item-heading" data-exercise-id="<%=@request_for_comment.exercise.id%>" data-comment-exercise-url="<%=create_comment_exercise_request_for_comment_path%>" data-rfc-id = "<%= @request_for_comment.id %>" >
<% if (@request_for_comment.solved?) %>
<span class="fa fa-check" aria-hidden="true"></span>
<% end %>
<%= link_to(@request_for_comment.exercise.title, [:implement, @request_for_comment.exercise]) %>
</h4>
<p class="list-group-item-text">
<%
user = @request_for_comment.user
submission = @request_for_comment.submission
%>
<%= user.displayname %> | <%= @request_for_comment.created_at.localtime %>
</p>
<h5>
<u><%= t('activerecord.attributes.exercise.description') %>:</u> <%= render_markdown(@request_for_comment.exercise.description) %>
</h5>
<h5>
<% if @request_for_comment.question and not @request_for_comment.question == '' %>
<u><%= t('activerecord.attributes.request_for_comments.question')%>:</u> "<%= @request_for_comment.question %>"
<% else %>
<u><%= t('activerecord.attributes.request_for_comments.question')%>:</u> <%= t('request_for_comments.no_question') %>
<% end %>
</h5>
<% if (policy(@request_for_comment).mark_as_solved? and not @request_for_comment.solved?) %>
<button class="btn btn-primary" id="mark-as-solved-button">
<%= t('request_for_comments.mark_as_solved') %>
</button>
<div id="thank-you-container">
<p>
<%= t('request_for_comments.write_a_thank_you_node') %>
</p>
<textarea id="thank-you-note"></textarea>
<button class="btn btn-primary" id="send-thank-you-note">
<%= t('request_for_comments.send_thank_you_note') %>
</button>
<button class="btn btn-default" id="cancel-thank-you-note">
<%= t('request_for_comments.cancel_thank_you_note') %>
</button>
</div>
<% end %>
<% if @current_user.admin? && user.is_a?(ExternalUser) %>
<br>
<br>
<h4>Admin Menu</h4>
<h5>
<ul>
<li><%= link_to "User's current status of this exercise", statistics_external_user_exercise_path(id: @request_for_comment.exercise_id, external_user_id: @request_for_comment.user_id) %></li>
<li><%= link_to "All exercises of this user", statistics_external_user_path(id: @request_for_comment.user_id) %></li> <br>
<li><%= link_to "Implement the exercise yourself", implement_exercise_path(id: @request_for_comment.exercise_id) %> </li>
<li><%= link_to "Show the exercise", exercise_path(id: @request_for_comment.exercise_id) %> </li>
</ul>
</h5>
<% end %>
<h5>
<u><%= t('request_for_comments.howto_title') %></u><br> <%= render_markdown(t('request_for_comments.howto')) %>
</h5>
</div>
<hr>
<div class="hidden sanitizer"></div>
<!--
do not put a carriage return in the line below. it will be present in the presentation of the source code, otherwise.
also, all settings from the rails model needed for the editor configuration in the JavaScript are attached to the editor as data attributes here.
-->
<% submission.files.each do |file| %>
<%= (file.path or "") + "/" + file.name + file.file_type.file_extension %><br>
&nbsp;&nbsp;<i class="fa fa-arrow-down" aria-hidden="true"></i> <%= t('request_for_comments.click_here') %>
<div id='commentitor' class='editor' data-read-only='true' data-file-id='<%=file.id%>' data-mode='<%=file.file_type.editor_mode%>'><%= file.content %>
</div>
<% end %>
<%= render('shared/modal', id: 'comment-modal', title: t('exercises.implement.comment.dialogtitle'), template: 'exercises/_comment_dialogcontent') %>
<script type="text/javascript">
var solvedButton = $('#mark-as-solved-button');
var commentOnExerciseButton = $('#comment-exercise-button');
var addCommentExerciseButton = $('#addCommentExerciseButton');
var thankYouContainer = $('#thank-you-container');
solvedButton.on('click', function(){
$.ajax({
dataType: 'json',
method: 'GET',
url: location + '/mark_as_solved'
}).done(function(response){
if(response.solved){
solvedButton.removeClass('btn-primary');
solvedButton.addClass('btn-success');
solvedButton.html('<%= t('request_for_comments.solved') %>');
solvedButton.off('click');
thankYouContainer.show();
}
});
});
$('#send-thank-you-note').on('click', function () {
var value = $('#thank-you-note').val();
if (value) {
$.ajax({
dataType: 'json',
method: 'POST',
url: location + '/set_thank_you_note',
data: {
note: value
}
}).done(function() {
thankYouContainer.hide();
});
}
});
$('#cancel-thank-you-note').on('click', function () {
thankYouContainer.hide();
});
// set file paths for ace
var ACE_FILES_PATH = '/assets/ace/';
_.each(['modePath', 'themePath', 'workerPath'], function(attribute) {
ace.config.set(attribute, ACE_FILES_PATH);
});
var commentitor = $('.editor');
commentitor.each(function (index, editor) {
var currentEditor = ace.edit(editor);
currentEditor.setReadOnly(true);
// set editor mode (used for syntax highlighting
currentEditor.getSession().setMode($(editor).data('mode'));
currentEditor.getSession().setOption("useWorker", false);
setAnnotations(currentEditor, $(editor).data('file-id'));
currentEditor.on("guttermousedown", handleSidebarClick);
});
function cleanupPopovers() {
// remove all possible popovers
$('.editor > .ace_gutter > .ace_gutter-layer > .ace_gutter-cell').popover('destroy');
}
function preprocess(commentText) {
// sanitize comments to deal with XSS attacks:
commentText = $('div.sanitizer').text(commentText).html();
// display original line breaks:
return commentText.replace(/\n/g, '<br>');
}
function setAnnotations(editor, fileid) {
var session = editor.getSession();
var jqrequest = $.ajax({
dataType: 'json',
method: 'GET',
url: '/comments',
data: {
file_id: fileid
}
});
jqrequest.done(function(response){
// comments need to be sorted to cluster them per line
var comments = response.slice().sort(function (a, b) {
return a.row - b.row;
});
while (comments.length > 0) {
// new cluster of comments
var cluster = [];
var clusterRow = comments[0].row;
// now collect all comments on this line
while (comments.length > 0 && comments[0].row === clusterRow) {
cluster.push(comments.shift());
}
// sort the comments by creation date
cluster = cluster.sort(function (a, b) {
return a.id - b.id;
});
// build the markup for the current line's popover
var popupContent = '';
cluster.forEach(function(comment, index) {
if (index !== 0) {
popupContent += '<div class="popover-divider"></div>'
}
popupContent += '<p class="comment">';
popupContent += '<div class="popover-header">' +
'<div class="popover-username">' + preprocess(comment.username) + '</div>' +
'<div class="popover-date">' + comment.date + '</div>';
if (comment.updated) {
popupContent += '<div class="popover-updated">' +
'<i class="fa fa-pencil" aria-hidden="true"></i>' +
'<%= t('request_for_comments.comment_edited') %>' +
'</div>'
}
popupContent += '</div>';
popupContent += '<div class="popover-comment">' + preprocess(comment.text) + '</div>';
popupContent += '</p>';
});
// attach the popover to the ace sidebar (where the comment icon is displayed)
var icon = $('*[data-file-id="' + fileid + '"]') // the editor for this file
.find('.ace_gutter > .ace_gutter-layer') // the sidebar
.find('div:nth-child(' + (clusterRow + 1) + ')'); // the correct line
icon.popover({
content: popupContent,
html: true, // necessary to style comments. XSS is not possible due to comment pre-processing (sanitizing)
trigger: 'hover',
container: 'body'
});
}
$.each(response, function(index, comment) {
comment.className = 'code-ocean_comment';
// if we have tabs or carriage returns in the comment, just add the name and leave it as it is. otherwise: format!
if(comment.text.includes("\n") || comment.text.includes("\t")){
comment.text = comment.username + ": " + comment.text;
} else {
comment.text = comment.username + ": " + stringDivider(comment.text, 80, "\n");
}
});
session.setAnnotations(response);
})
}
function hasCommentsInRow(editor, row){
return editor.getSession().getAnnotations().some(function(element) {
return element.row === row;
})
}
function getCommentsForRow(editor, row){
return editor.getSession().getAnnotations().filter(function(element) {
return element.row === row;
})
}
function deleteComment(file_id, row, editor) {
cleanupPopovers();
var jqxhr = $.ajax({
type: 'DELETE',
url: "/comments",
data: {
row: row,
file_id: file_id }
});
jqxhr.done(function (response) {
setAnnotations(editor, file_id);
});
jqxhr.fail(ajaxError);
}
function createComment(file_id, row, editor, commenttext){
cleanupPopovers();
var jqxhr = $.ajax({
data: {
comment: {
file_id: file_id,
row: row,
column: 0,
text: commenttext,
request_id: $('h4#exercise_caption').data('rfc-id')
}
},
dataType: 'json',
method: 'POST',
url: "/comments"
});
jqxhr.done(function(response){
setAnnotations(editor, file_id);
});
jqxhr.fail(ajaxError);
}
function createCommentOnExercise(file_id, row, editor, commenttext){
var jqxhr = $.ajax({
data: {
comment: {
file_id: file_id,
row: row,
column: 0,
text: commenttext,
request_id: $('h4#exercise_caption').data('rfc-id')
}
},
dataType: 'json',
method: 'POST',
url: "/comments"
});
jqxhr.done(function(response){
setAnnotations(editor, file_id);
});
jqxhr.fail(ajaxError);
}
function handleSidebarClick(e) {
var target = e.domEvent.target;
var editor = e.editor;
if (target.className.indexOf("ace_gutter-cell") == -1) return;
var row = e.getDocumentPosition().row;
e.stop();
var commentModal = $('#comment-modal');
if (hasCommentsInRow(editor, row)) {
var rowComments = getCommentsForRow(editor, row);
var comments = _.pluck(rowComments, 'text').join('\n');
commentModal.find('#otherComments').show();
commentModal.find('#otherCommentsTextfield').text(comments);
} else {
commentModal.find('#otherComments').hide();
}
commentModal.find('#addCommentButton').off('click');
commentModal.find('#removeAllButton').off('click');
commentModal.find('#addCommentButton').on('click', function(e){
var commenttext = commentModal.find('textarea').val();
var file_id = $(editor.container).data('file-id');
if (commenttext !== "") {
createComment(file_id, row, editor, commenttext);
commentModal.find('textarea').val('') ;
commentModal.modal('hide');
}
});
commentModal.find('#removeAllButton').on('click', function(e){
var file_id = $(editor.container).data('file-id');
deleteComment(file_id, row, editor);
commentModal.modal('hide');
});
commentModal.modal('show');
}
function ajaxError(response) {
var message = ((response || {}).responseJSON || {}).message || '';
$.flash.danger({
text: message.length > 0 ? message : $('#flash').data('message-failure')
});
}
function stringDivider(str, width, spaceReplacer) {
if (str.length>width) {
var p=width;
for (;p>0 && str[p]!=' ';p--) {
}
if (p>0) {
var left = str.substring(0, p);
var right = str.substring(p+1);
return left + spaceReplacer + stringDivider(right, width, spaceReplacer);
}
}
return str;
}
</script>