diff --git a/app/assets/javascripts/exercises.js.erb b/app/assets/javascripts/exercises.js.erb
index e24f0b7e..000637e6 100644
--- a/app/assets/javascripts/exercises.js.erb
+++ b/app/assets/javascripts/exercises.js.erb
@@ -1,397 +1,399 @@
-$(document).on('turbolinks:load', function() {
- // ruby part adds the relative_url_root, if it is set.
- var ACE_FILES_PATH = '<%= (defined? Rails.application.config.relative_url_root) && Rails.application.config.relative_url_root != nil && Rails.application.config.relative_url_root != "" ? Rails.application.config.relative_url_root : "" %>' + '/assets/ace/';
- var THEME = 'ace/theme/textmate';
+$(document).on('turbolinks:load', function () {
+ // ruby part adds the relative_url_root, if it is set.
+ var ACE_FILES_PATH = '<%= (defined? Rails.application.config.relative_url_root) && Rails.application.config.relative_url_root != nil && Rails.application.config.relative_url_root != "" ? Rails.application.config.relative_url_root : "" %>' + '/assets/ace/';
+ var THEME = 'ace/theme/textmate';
- var TAB_KEY_CODE = 9;
+ var TAB_KEY_CODE = 9;
- var execution_environments;
- var file_types;
+ var execution_environments;
+ var file_types;
+ var configureEditors = function () {
+ _.each(['modePath', 'themePath', 'workerPath'], function (attribute) {
+ ace.config.set(attribute, ACE_FILES_PATH);
+ });
+ };
- var configureEditors = function() {
- _.each(['modePath', 'themePath', 'workerPath'], function(attribute) {
- ace.config.set(attribute, ACE_FILES_PATH);
- });
- };
+ var initializeEditor = function (index, element) {
+ var editor = ace.edit(element);
- var initializeEditor = function(index, element) {
- var editor = ace.edit(element);
+ var document = editor.getSession().getDocument();
+ // insert pre-existing code into editor. we have to use insertLines, otherwise the deltas are not properly added
+ var file_id = $(element).data('file-id');
+ var content = $('.editor-content[data-file-id=' + file_id + ']');
- var document = editor.getSession().getDocument();
- // insert pre-existing code into editor. we have to use insertLines, otherwise the deltas are not properly added
- var file_id = $(element).data('file-id');
- var content = $('.editor-content[data-file-id=' + file_id + ']');
+ document.insertLines(0, content.text().split(/\n/));
+ // remove last (empty) that is there by default line
+ document.removeLines(document.getLength() - 1, document.getLength() - 1);
+ editor.setReadOnly($(element).data('read-only') !== undefined);
+ editor.setShowPrintMargin(false);
+ editor.setTheme(THEME);
- document.insertLines(0, content.text().split(/\n/));
- // remove last (empty) that is there by default line
- document.removeLines(document.getLength() - 1, document.getLength() - 1);
- editor.setReadOnly($(element).data('read-only') !== undefined);
- editor.setShowPrintMargin(false);
- editor.setTheme(THEME);
+ var textarea = $('textarea[id="exercise_files_attributes_' + index + '_content"]');
+ var content = textarea.val();
- var textarea = $('textarea[id="exercise_files_attributes_'+index+'_content"]');
- var content = textarea.val();
+ if (content != undefined) {
+ editor.getSession().setValue(content);
+ editor.getSession().on('change', function () {
+ textarea.val(editor.getSession().getValue());
+ });
+ }
- if (content != undefined)
- {
- editor.getSession().setValue(content);
- editor.getSession().on('change', function(){
- textarea.val(editor.getSession().getValue());
- });
+ editor.commands.bindKey("ctrl+alt+0", null);
+ var session = editor.getSession();
+ session.setMode($(element).data('mode'));
+ session.setTabSize($(element).data('indent-size'));
+ session.setUseSoftTabs(true);
+ session.setUseWrapMode(true);
}
- editor.commands.bindKey("ctrl+alt+0", null);
- var session = editor.getSession();
- session.setMode($(element).data('mode'));
- session.setTabSize($(element).data('indent-size'));
- session.setUseSoftTabs(true);
- session.setUseWrapMode(true);
- }
+ var initializeEditors = function () {
+ // initialize ace editors for all code textareas in the dom except the last one. The last one is the dummy area for new files, which is cloned when needed.
+ // this one must NOT! be initialized.
+ $('.editor:not(:last)').each(initializeEditor)
+ };
- var initializeEditors = function() {
- // initialize ace editors for all code textareas in the dom except the last one. The last one is the dummy area for new files, which is cloned when needed.
- // this one must NOT! be initialized.
- $('.editor:not(:last)').each(initializeEditor)
- };
-
- var addFileForm = function(event) {
- event.preventDefault();
- var element = $('#dummies').children().first().clone();
-
- // the timestamp is used here, since it is most probably unique. This is strange, but was originally designed that way.
- var latestTextAreaIndex = new Date().getTime();
- var html = $('
').append(element).html().replace(/index/g, latestTextAreaIndex);
- $('#files').append(html);
- $('#files li:last select[name*="file_type_id"]').val(getSelectedExecutionEnvironment().file_type_id);
- $('#files li:last select').chosen(window.CodeOcean.CHOSEN_OPTIONS);
- $('#files li:last>div:last').removeClass('in').addClass('show')
- $('body, html').scrollTo('#add-file');
-
- // initialize the ace editor for the new textarea.
- // pass the correct index and the last ace editor under the node files. this is the last one, since we just added it.
- initializeEditor(latestTextAreaIndex, $('#files .editor').last()[0]);
- };
-
- var removeFileForm = function(fileUrl) {
- // validate fileUrl
- var matches = fileUrl.match(/files\/(\d+)/);
- if (matches) {
- // select the file form based on the delete button
- var fileForm = $('*[data-file-url="' + fileUrl + '"]').parent().parent().parent();
- fileForm.remove();
-
- // now remove the hidden input representing the file
- var fileId = matches[1];
- var input = $('input[type="hidden"][value="' + fileId + '"]')
- input.remove()
- }
- }
-
- var deleteFile = function(event) {
- event.preventDefault();
- var fileUrl = $(event.target).data('file-url');
-
- if (confirm('<%= I18n.t('shared.confirm_destroy') %>')) {
- var jqxhr = $.ajax({
- // normal file path (without json) would destroy the context object (the exercise) as well, due to redirection
- // to the context after the :destroy action.
- contentType: 'Application/json',
- url: fileUrl + '.json',
- method: 'DELETE'
- });
- jqxhr.done(function () {
- removeFileForm(fileUrl)
- });
- jqxhr.fail(ajaxError);
- }
- }
-
- var ajaxError = function(error) {
- $.flash.danger({
- text: $('#flash').data('message-failure')
- });
- Sentry.captureException(JSON.stringify(error));
- };
-
- var buildCheckboxes = function() {
- $('tbody tr').each(function(index, element) {
- var td = $('td.public', element);
- var checkbox = $('', {
- checked: td.data('value'),
- type: 'checkbox'
- });
- td.on('click', function(event) {
+ var addFileForm = function (event) {
event.preventDefault();
- checkbox.prop('checked', !checkbox.prop('checked'));
- });
- td.html(checkbox);
- });
- };
+ var element = $('#dummies').children().first().clone();
- var discardFile = function(event) {
- event.preventDefault();
- $(this).parents('li').remove();
- };
+ // the timestamp is used here, since it is most probably unique. This is strange, but was originally designed that way.
+ var latestTextAreaIndex = new Date().getTime();
+ var html = $('
').append(element).html().replace(/index/g, latestTextAreaIndex);
+ $('#files').append(html);
+ $('#files li:last select[name*="file_type_id"]').val(getSelectedExecutionEnvironment().file_type_id);
+ $('#files li:last select').chosen(window.CodeOcean.CHOSEN_OPTIONS);
+ $('#files li:last>div:last').removeClass('in').addClass('show')
+ $('body, html').scrollTo('#add-file');
- var enableBatchUpdate = function() {
- $('thead .batch a').on('click', function(event) {
- event.preventDefault();
- if (!$(event.target).data('toggled')) {
- $(event.target).data('toggled', true);
- $(event.target).text($(event.target).data('text'));
- buildCheckboxes();
- } else {
- performBatchUpdate();
- }
- });
- };
+ // initialize the ace editor for the new textarea.
+ // pass the correct index and the last ace editor under the node files. this is the last one, since we just added it.
+ initializeEditor(latestTextAreaIndex, $('#files .editor').last()[0]);
+ };
- var enableInlineFileCreation = function() {
- $('#add-file').on('click', addFileForm);
- $('#files').on('click', 'li .discard-file', discardFile);
- $('form.edit_exercise, form.new_exercise').on('submit', function() {
- $('#dummies').html('');
- });
- $('.delete-file').on('click', deleteFile);
- };
+ var removeFileForm = function (fileUrl) {
+ // validate fileUrl
+ var matches = fileUrl.match(/files\/(\d+)/);
+ if (matches) {
+ // select the file form based on the delete button
+ var fileForm = $('*[data-file-url="' + fileUrl + '"]').parent().parent().parent();
+ fileForm.remove();
- var findFileTypeByFileExtension = function(file_extension) {
- return _.find(file_types, function(file_type) {
- return file_type.file_extension === file_extension;
- }) || {};
- };
-
- var getSelectedExecutionEnvironment = function() {
- return _.find(execution_environments, function(execution_environment) {
- return execution_environment.id === parseInt($('#exercise_execution_environment_id').val());
- }) || {};
- };
-
- var highlightCode = function() {
- $('pre code').each(function(index, element) {
- hljs.highlightBlock(element);
- });
- };
-
- var inferFileAttributes = function() {
- $(document).on('change', 'input[type="file"]', function() {
- var filename = $(this).val().split(/\\|\//g).pop();
- var file_extension = '.' + filename.split('.')[1];
- var file_type = findFileTypeByFileExtension(file_extension);
- var name = filename.split('.')[0];
- var parent = $(this).parents('li');
- parent.find('input[name*="name"]').val(name);
- parent.find('select[name*="file_type_id"]').val(file_type.id).trigger('chosen:updated');
- });
- };
-
- var insertTabAtCursor = function(textarea) {
- var selection_start = textarea.get(0).selectionStart;
- var selection_end = textarea.get(0).selectionEnd;
- textarea.val(textarea.val().substring(0, selection_start) + "\t" + textarea.val().substring(selection_end));
- textarea.get(0).selectionStart = selection_start + 1;
- textarea.get(0).selectionEnd = selection_start + 1;
- };
-
- var observeFileRoleChanges = function() {
- $(document).on('change', 'select[name$="[role]"]', function() {
- var is_test_file = $(this).val() === 'teacher_defined_test';
- var parent = $(this).parents('.card');
- var fields = parent.find('.test-related-fields');
- if (is_test_file) {
- fields.slideDown();
- } else {
- fields.slideUp();
- parent.find('[name$="[feedback_message]"]').val('');
- parent.find('[name$="[weight]"]').val(1);
- }
- });
- };
-
- var old_execution_environment = $('#exercise_execution_environment_id').val();
- var observeExecutionEnvironment = function() {
- $('#exercise_execution_environment_id').on('change', function(){
- new_execution_environment = $('#exercise_execution_environment_id').val();
-
- if(new_execution_environment == '' && !$('#exercise_unpublished').prop('checked')){
- if(confirm('<%= I18n.t('exercises.form.unpublish_warning') %>')){
- $('#exercise_unpublished').prop('checked', true);
- } else {
- return $('#exercise_execution_environment_id').val(old_execution_environment).trigger("chosen:updated");
+ // now remove the hidden input representing the file
+ var fileId = matches[1];
+ var input = $('input[type="hidden"][value="' + fileId + '"]')
+ input.remove()
}
- }
- old_execution_environment = new_execution_environment;
- });
- };
+ }
- var observeUnpublishedState = function() {
- $('#exercise_unpublished').on('change', function(){
- if($('#exercise_unpublished').prop('checked')){
- if(!confirm('<%= I18n.t('exercises.form.unpublish_warning') %>')){
- $('#exercise_unpublished').prop('checked', false);
- }
- } else if($('#exercise_execution_environment_id').val() == '') {
- alert('<%= I18n.t('exercises.form.no_execution_environment_selected') %>');
- $('#exercise_unpublished').prop('checked', true);
- }
- })
- };
-
- var observeExportButtons = function(){
- $('.export-start').on('click', function(e){
- e.preventDefault();
- $('#export-modal').modal({
- height: 250
- });
- $('#export-modal').modal('show');
- exportExerciseStart($(this).data().exerciseId);
- });
- $('body').on('click', '.export-retry-button', function(){
- exportExerciseStart($(this).data().exerciseId);
- });
- $('body').on('click', '.export-action', function(){
- exportExerciseConfirm($(this).data().exerciseId);
- });
- }
-
- var exportExerciseStart = function(exerciseID) {
- var $exerciseDiv = $('#export-exercise');
- var $messageDiv = $exerciseDiv.children('.export-message');
- var $actionsDiv = $exerciseDiv.children('.export-exercise-actions');
-
- $messageDiv.removeClass('export-failure');
-
- $messageDiv.html('<%= I18n.t('exercises.export_codeharbor.checking_codeharbor') %>');
- $actionsDiv.html('');
-
- return $.ajax({
- type: 'POST',
- url: '/exercises/' + exerciseID + '/export_external_check',
- dataType: 'json',
- success: function(response) {
- $messageDiv.html(response.message);
- return $actionsDiv.html(response.actions);
- },
- error: function(a, b, c) {
- return alert('error:' + c);
- }
- });
- };
-
- var exportExerciseConfirm = function(exerciseID) {
- var $exerciseDiv = $('#export-exercise');
- var $messageDiv = $exerciseDiv.children('.export-message');
- var $actionsDiv = $exerciseDiv.children('.export-exercise-actions');
-
- return $.ajax({
- type: 'POST',
- url: '/exercises/' + exerciseID + '/export_external_confirm',
- dataType: 'json',
- success: function(response) {
- $messageDiv.html(response.message)
- $actionsDiv.html(response.actions);
-
- if(response.status == 'success') {
- $messageDiv.addClass('export-success');
- setTimeout((function() {
- $('#export-modal').modal('hide');
- $messageDiv.html('').removeClass('export-success');
- }), 3000);
- } else {
- $messageDiv.addClass('export-failure');
- }
- },
- error: function(a, b, c) {
- return alert('error:' + c);
- }
- });
- };
-
- var overrideTextareaTabBehavior = function() {
- $('.form-group textarea[name$="[content]"]').on('keydown', function(event) {
- if (event.which === TAB_KEY_CODE) {
+ var deleteFile = function (event) {
event.preventDefault();
- insertTabAtCursor($(this));
- }
- });
- };
+ var fileUrl = $(event.target).data('file-url');
- var performBatchUpdate = function() {
- var jqxhr = $.ajax({
- data: {
- exercises: _.map($('tbody tr'), function(element) {
- return {
- id: $(element).data('id'),
- public: $('.public input', element).prop('checked')
- };
+ if (confirm('<%= I18n.t('shared.confirm_destroy') %>')) {
+ var jqxhr = $.ajax({
+ // normal file path (without json) would destroy the context object (the exercise) as well, due to redirection
+ // to the context after the :destroy action.
+ contentType: 'Application/json',
+ url: fileUrl + '.json',
+ method: 'DELETE'
+ });
+ jqxhr.done(function () {
+ removeFileForm(fileUrl)
+ });
+ jqxhr.fail(ajaxError);
+ }
+ }
+
+ var ajaxError = function (error) {
+ $.flash.danger({
+ text: $('#flash').data('message-failure')
+ });
+ Sentry.captureException(JSON.stringify(error));
+ };
+
+ var buildCheckboxes = function () {
+ $('tbody tr').each(function (index, element) {
+ var td = $('td.public', element);
+ var checkbox = $('', {
+ checked: td.data('value'),
+ type: 'checkbox'
+ });
+ td.on('click', function (event) {
+ event.preventDefault();
+ checkbox.prop('checked', !checkbox.prop('checked'));
+ });
+ td.html(checkbox);
+ });
+ };
+
+ var discardFile = function (event) {
+ event.preventDefault();
+ $(this).parents('li').remove();
+ };
+
+ var enableBatchUpdate = function () {
+ $('thead .batch a').on('click', function (event) {
+ event.preventDefault();
+ if (!$(event.target).data('toggled')) {
+ $(event.target).data('toggled', true);
+ $(event.target).text($(event.target).data('text'));
+ buildCheckboxes();
+ } else {
+ performBatchUpdate();
+ }
+ });
+ };
+
+ var enableInlineFileCreation = function () {
+ $('#add-file').on('click', addFileForm);
+ $('#files').on('click', 'li .discard-file', discardFile);
+ $('form.edit_exercise, form.new_exercise').on('submit', function () {
+ $('#dummies').html('');
+ });
+ $('.delete-file').on('click', deleteFile);
+ };
+
+ var findFileTypeByFileExtension = function (file_extension) {
+ return _.find(file_types, function (file_type) {
+ return file_type.file_extension === file_extension;
+ }) || {};
+ };
+
+ var getSelectedExecutionEnvironment = function () {
+ return _.find(execution_environments, function (execution_environment) {
+ return execution_environment.id === parseInt($('#exercise_execution_environment_id').val());
+ }) || {};
+ };
+
+ var highlightCode = function () {
+ $('pre code').each(function (index, element) {
+ hljs.highlightBlock(element);
+ });
+ };
+
+ var inferFileAttributes = function () {
+ $(document).on('change', 'input[type="file"]', function () {
+ var filename = $(this).val().split(/\\|\//g).pop();
+ var file_extension = '.' + filename.split('.')[1];
+ var file_type = findFileTypeByFileExtension(file_extension);
+ var name = filename.split('.')[0];
+ var parent = $(this).parents('li');
+ parent.find('input[name*="name"]').val(name);
+ parent.find('select[name*="file_type_id"]').val(file_type.id).trigger('chosen:updated');
+ });
+ };
+
+ var insertTabAtCursor = function (textarea) {
+ var selection_start = textarea.get(0).selectionStart;
+ var selection_end = textarea.get(0).selectionEnd;
+ textarea.val(textarea.val().substring(0, selection_start) + "\t" + textarea.val().substring(selection_end));
+ textarea.get(0).selectionStart = selection_start + 1;
+ textarea.get(0).selectionEnd = selection_start + 1;
+ };
+
+ var observeFileRoleChanges = function () {
+ $(document).on('change', 'select[name$="[role]"]', function () {
+ var is_test_file = $(this).val() === 'teacher_defined_test';
+ var parent = $(this).parents('.card');
+ var fields = parent.find('.test-related-fields');
+ if (is_test_file) {
+ fields.slideDown();
+ } else {
+ fields.slideUp();
+ parent.find('[name$="[feedback_message]"]').val('');
+ parent.find('[name$="[weight]"]').val(1);
+ }
+ });
+ };
+
+ var old_execution_environment = $('#exercise_execution_environment_id').val();
+ var observeExecutionEnvironment = function () {
+ $('#exercise_execution_environment_id').on('change', function () {
+ new_execution_environment = $('#exercise_execution_environment_id').val();
+
+ if (new_execution_environment == '' && !$('#exercise_unpublished').prop('checked')) {
+ if (confirm('<%= I18n.t('exercises.form.unpublish_warning') %>')) {
+ $('#exercise_unpublished').prop('checked', true);
+ } else {
+ return $('#exercise_execution_environment_id').val(old_execution_environment).trigger("chosen:updated");
+ }
+ }
+ old_execution_environment = new_execution_environment;
+ });
+ };
+
+ var observeUnpublishedState = function () {
+ $('#exercise_unpublished').on('change', function () {
+ if ($('#exercise_unpublished').prop('checked')) {
+ if (!confirm('<%= I18n.t('exercises.form.unpublish_warning') %>')) {
+ $('#exercise_unpublished').prop('checked', false);
+ }
+ } else if ($('#exercise_execution_environment_id').val() == '') {
+ alert('<%= I18n.t('exercises.form.no_execution_environment_selected') %>');
+ $('#exercise_unpublished').prop('checked', true);
+ }
})
- },
- dataType: 'json',
- method: 'PUT'
- });
- jqxhr.done(window.CodeOcean.refresh);
- jqxhr.fail(ajaxError);
- };
+ };
- var toggleCodeHeight = function() {
- $('code').on('click', function() {
- $(this).css({
- 'max-height': 'initial'
- });
- });
- };
-
- var updateFileTemplates = function(fileType) {
- var rel_url_root = '<%= (defined? Rails.application.config.relative_url_root) && Rails.application.config.relative_url_root != nil && Rails.application.config.relative_url_root != "" ? Rails.application.config.relative_url_root : "" %>';
- var jqxhr = $.ajax({
- url: rel_url_root + '/file_templates/by_file_type/' + fileType + '.json',
- dataType: 'json'
- });
- jqxhr.done(function(response) {
- var noTemplateLabel = $('#noTemplateLabel').data('text');
- var options = "";
- for (var i = 0; i < response.length; i++) {
- options += ""
- }
- $("#code_ocean_file_file_template_id").find('option').remove().end().append($(options));
- });
- jqxhr.fail(ajaxError);
- }
-
- if ($.isController('exercises') || $.isController('submissions')) {
- // ignore tags table since it is in the dom before other tables
- if ($('table:not(#tags-table)').isPresent()) {
- enableBatchUpdate();
- observeExportButtons();
- } else if ($('.edit_exercise, .new_exercise').isPresent()) {
- execution_environments = $('form').data('execution-environments');
- file_types = $('form').data('file-types');
-
- enableInlineFileCreation();
- inferFileAttributes();
- observeFileRoleChanges();
- observeExecutionEnvironment();
- observeUnpublishedState();
- overrideTextareaTabBehavior();
- } else if ($('#files.jstree').isPresent()) {
- var fileTypeSelect = $('#code_ocean_file_file_type_id');
- fileTypeSelect.on("change", function() {updateFileTemplates(fileTypeSelect.val())});
- updateFileTemplates(fileTypeSelect.val());
+ var observeExportButtons = function () {
+ $('.export-start').on('click', function (e) {
+ e.preventDefault();
+ $('#export-modal').modal({
+ height: 250
+ });
+ $('#export-modal').modal('show');
+ exportExerciseStart($(this).data().exerciseId);
+ });
+ $('body').on('click', '.export-retry-button', function () {
+ exportExerciseStart($(this).data().exerciseId);
+ });
+ $('body').on('click', '.export-action', function () {
+ exportExerciseConfirm($(this).data().exerciseId);
+ });
}
- toggleCodeHeight();
- if (window.hljs) {
- highlightCode();
+
+ var exportExerciseStart = function (exerciseID) {
+ var $exerciseDiv = $('#export-exercise');
+ var $messageDiv = $exerciseDiv.children('.export-message');
+ var $actionsDiv = $exerciseDiv.children('.export-exercise-actions');
+
+ $messageDiv.removeClass('export-failure');
+
+ $messageDiv.html('<%= I18n.t('exercises.export_codeharbor.checking_codeharbor') %>');
+ $actionsDiv.html('');
+
+ return $.ajax({
+ type: 'POST',
+ url: '/exercises/' + exerciseID + '/export_external_check',
+ dataType: 'json',
+ success: function (response) {
+ $messageDiv.html(response.message);
+ return $actionsDiv.html(response.actions);
+ },
+ error: function (a, b, c) {
+ return alert('error:' + c);
+ }
+ });
+ };
+
+ var exportExerciseConfirm = function (exerciseID) {
+ var $exerciseDiv = $('#export-exercise');
+ var $messageDiv = $exerciseDiv.children('.export-message');
+ var $actionsDiv = $exerciseDiv.children('.export-exercise-actions');
+
+ return $.ajax({
+ type: 'POST',
+ url: '/exercises/' + exerciseID + '/export_external_confirm',
+ dataType: 'json',
+ success: function (response) {
+ $messageDiv.html(response.message)
+ $actionsDiv.html(response.actions);
+
+ if (response.status == 'success') {
+ $messageDiv.addClass('export-success');
+ setTimeout((function () {
+ $('#export-modal').modal('hide');
+ $messageDiv.html('').removeClass('export-success');
+ }), 3000);
+ } else {
+ $messageDiv.addClass('export-failure');
+ }
+ },
+ error: function (a, b, c) {
+ return alert('error:' + c);
+ }
+ });
+ };
+
+ var overrideTextareaTabBehavior = function () {
+ $('.form-group textarea[name$="[content]"]').on('keydown', function (event) {
+ if (event.which === TAB_KEY_CODE) {
+ event.preventDefault();
+ insertTabAtCursor($(this));
+ }
+ });
+ };
+
+ var performBatchUpdate = function () {
+ var jqxhr = $.ajax({
+ data: {
+ exercises: _.map($('tbody tr'), function (element) {
+ return {
+ id: $(element).data('id'),
+ public: $('.public input', element).prop('checked')
+ };
+ })
+ },
+ dataType: 'json',
+ method: 'PUT'
+ });
+ jqxhr.done(window.CodeOcean.refresh);
+ jqxhr.fail(ajaxError);
+ };
+
+ var toggleCodeHeight = function () {
+ $('code').on('click', function () {
+ $(this).css({
+ 'max-height': 'initial'
+ });
+ });
+ };
+
+ var updateFileTemplates = function (fileType) {
+ var rel_url_root = '<%= (defined? Rails.application.config.relative_url_root) && Rails.application.config.relative_url_root != nil && Rails.application.config.relative_url_root != "" ? Rails.application.config.relative_url_root : "" %>';
+ var jqxhr = $.ajax({
+ url: rel_url_root + '/file_templates/by_file_type/' + fileType + '.json',
+ dataType: 'json'
+ });
+ jqxhr.done(function (response) {
+ var noTemplateLabel = $('#noTemplateLabel').data('text');
+ var options = "";
+ for (var i = 0; i < response.length; i++) {
+ options += ""
+ }
+ $("#code_ocean_file_file_template_id").find('option').remove().end().append($(options));
+ });
+ jqxhr.fail(ajaxError);
+ }
+
+ if ($.isController('exercises') || $.isController('submissions')) {
+ // ignore tags table since it is in the dom before other tables
+ if ($('table:not(#tags-table)').isPresent()) {
+ enableBatchUpdate();
+ observeExportButtons();
+ } else if ($('.edit_exercise, .new_exercise').isPresent()) {
+ execution_environments = $('form').data('execution-environments');
+ file_types = $('form').data('file-types');
+
+ enableInlineFileCreation();
+ inferFileAttributes();
+ observeFileRoleChanges();
+ observeExecutionEnvironment();
+ observeUnpublishedState();
+ overrideTextareaTabBehavior();
+ } else if ($('#files.jstree').isPresent()) {
+ var fileTypeSelect = $('#code_ocean_file_file_type_id');
+ fileTypeSelect.on("change", function () {
+ updateFileTemplates(fileTypeSelect.val())
+ });
+ updateFileTemplates(fileTypeSelect.val());
+ } else if ($('.export-start').isPresent()) {
+ observeExportButtons();
+ }
+ toggleCodeHeight();
+ if (window.hljs) {
+ highlightCode();
+ }
}
- }
- if ($('#editor-edit').isPresent()) {
- configureEditors();
- initializeEditors();
- $('.frame').show();
- }
+ if ($('#editor-edit').isPresent()) {
+ configureEditors();
+ initializeEditors();
+ $('.frame').show();
+ }
});
diff --git a/app/views/exercises/show.html.slim b/app/views/exercises/show.html.slim
index c046a2d5..90513d77 100644
--- a/app/views/exercises/show.html.slim
+++ b/app/views/exercises/show.html.slim
@@ -7,7 +7,17 @@
h1
= @exercise
- = render('shared/edit_button', object: @exercise)
+ .btn-group.float-right
+ = render('shared/edit_button', object: @exercise)
+ button.btn.btn-secondary.float-right.dropdown-toggle data-toggle='dropdown' type='button'
+ ul.dropdown-menu.dropdown-menu-right role='menu'
+ li = link_to(t('exercises.index.implement'), implement_exercise_path(@exercise), 'data-turbolinks' => "false", class: 'dropdown-item text-dark') if policy(@exercise).implement?
+ li = link_to(t('shared.statistics'), statistics_exercise_path(@exercise), 'data-turbolinks' => "false", class: 'dropdown-item text-dark') if policy(@exercise).statistics?
+ li = link_to(t('activerecord.models.user_exercise_feedback.other'), feedback_exercise_path(@exercise), class: 'dropdown-item text-dark') if policy(@exercise).feedback?
+ li = link_to(t('activerecord.models.request_for_comment.other'), requests_for_comments_exercise_path(@exercise), class: 'dropdown-item text-dark') if policy(@exercise).requests_for_comments?
+ li = link_to(t('shared.destroy'), @exercise, data: {confirm: t('shared.confirm_destroy')}, method: :delete, class: 'dropdown-item text-dark') if policy(@exercise).destroy?
+ li = link_to(t('exercises.index.clone'), clone_exercise_path(@exercise), data: {confirm: t('shared.confirm_destroy')}, method: :post, class: 'dropdown-item text-dark') if policy(@exercise).clone?
+ li = link_to(t('exercises.export_codeharbor.label'), '', class: 'dropdown-item export-start text-dark', data: {'exercise-id' => @exercise.id}) if policy(@exercise).export_external_confirm?
= row(label: 'exercise.title', value: @exercise.title)
= row(label: 'exercise.user', value: link_to_if(policy(@exercise.author).show?, @exercise.author, @exercise.author))
@@ -42,3 +52,5 @@ ul.list-unstyled#files
- if policy(file).destroy?
.clearfix = link_to(t('shared.destroy'), file, class:'btn btn-warning btn-sm float-right', data: {confirm: t('shared.confirm_destroy')}, method: :delete)
= render('shared/file', file: file)
+
+= render('shared/modal', id: 'export-modal', title: t('exercises.export_codeharbor.dialogtitle'), template: 'exercises/_export_dialogcontent')
\ No newline at end of file