$(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 execution_environments; var file_types; 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 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); 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()); }); } 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 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); $('body, html').scrollTo('#add-file'); // if we collapse the file forms by default, we need to click on the new element in order to open it. // however, this crashes for more files (if we add several ones by clicking the add button more often), since the elements are probably not correctly added to the files list. //$('#files li:last>div:first>a>div').click(); // 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 ajaxError = function() { $.flash.danger({ text: $('#flash').data('message-failure') }); }; 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 (!$(this).data('toggled')) { $(this).data('toggled', true); $(this).text($(this).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(''); }); }; 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('.panel'); 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 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')) { // ignore tags table since it is in the dom before other tables if ($('table:not(#tags-table)').isPresent()) { enableBatchUpdate(); } else if ($('.edit_exercise, .new_exercise').isPresent()) { execution_environments = $('form').data('execution-environments'); file_types = $('form').data('file-types'); // new MarkdownEditor('#exercise_instructions'); // new MarkdownEditor('#exercise_description') // todo: add an ace editor for each file new PagedownEditor('#exercise_description'); enableInlineFileCreation(); inferFileAttributes(); observeFileRoleChanges(); overrideTextareaTabBehavior(); } else if ($('#files.jstree').isPresent()) { var fileTypeSelect = $('#code_ocean_file_file_type_id'); fileTypeSelect.on("change", function() {updateFileTemplates(fileTypeSelect.val())}); updateFileTemplates(fileTypeSelect.val()); } toggleCodeHeight(); if (window.hljs) { highlightCode(); } } if ($('#editor-edit').isPresent()) { configureEditors(); initializeEditors(); $('.frame').show(); } });