diff options
| author | Geo Halkiadakis <gchalkiadakis@sklavenitis.co.gr> | 2023-04-20 18:49:01 +0300 |
|---|---|---|
| committer | Geo Halkiadakis <gchalkiadakis@sklavenitis.co.gr> | 2023-04-20 18:49:01 +0300 |
| commit | a647a6180e6f2b0a3c3aaddc40bc2dcf5f396429 (patch) | |
| tree | c1134705450e571fb2305fd6e9197512cb37a8af /public/assets | |
| parent | d9bce23822117d03b1630f002d3ee0491ac83d4b (diff) | |
| download | classroom-a647a6180e6f2b0a3c3aaddc40bc2dcf5f396429.tar.gz classroom-a647a6180e6f2b0a3c3aaddc40bc2dcf5f396429.tar.bz2 classroom-a647a6180e6f2b0a3c3aaddc40bc2dcf5f396429.zip | |
new lesson initialization
Diffstat (limited to 'public/assets')
| -rw-r--r-- | public/assets/css/overides.css | 3 | ||||
| -rw-r--r-- | public/assets/js/admin/edit_lesson.js | 631 |
2 files changed, 633 insertions, 1 deletions
diff --git a/public/assets/css/overides.css b/public/assets/css/overides.css index b217f65..c2ca07d 100644 --- a/public/assets/css/overides.css +++ b/public/assets/css/overides.css @@ -80,7 +80,8 @@ ul.select2-results__options li { font-size: 14px; padding: 4px 8px; } .admin-container select[data-chosen='one'] { font-weight: 700; - color: #000; + color: #0d6efd; + border: 1px solid #0d6efd; } /* hack select2 caret styling diff --git a/public/assets/js/admin/edit_lesson.js b/public/assets/js/admin/edit_lesson.js index e69de29..a9355ae 100644 --- a/public/assets/js/admin/edit_lesson.js +++ b/public/assets/js/admin/edit_lesson.js @@ -0,0 +1,631 @@ +// Globals +// ----------------------------------------------------------------------------- + +// NOTE: +// Variable `entity_id` is defined before evaluating this script +// id (int) : the id of edited post (or 0 if new post) + +var lesson; + +var id = (entity_id == 0) ? false : entity_id; + +var categories = [ + // Nope! root can not be a post's category ... { id: 0 , label: 'Ρίζα (root)' } +]; + +var privileges = [ + { id: 0, label: '_Δημόσιο_'} +]; + +var files = []; // array of { id:.., title:.., type:.., path:.. } records + +// TODO: support tags +// var tags = []; // array of integers + + +const files_root = 'https://cdn.sklavenitis.co.gr/'; // files root firectory + +var pageUrl = new URL(window.location.href); + +// an alternative--id when a lesson_id not exists (needed for file-upload) +// ... consist of a 6-digit date string + random number up to 999 +const yymmdd = new Date().toISOString().slice(2, 10).replaceAll('-',''); +var rnd999 = Math.floor(Math.random() * 1000); +var altID = (id == 0) ? ( yymmdd +'-'+ rnd999.toString() ) : id; + + + + +// Supplamentary functions +// ----------------------------------------------------------------------------- + +// function for sorting an array by 'label' attribute +// --- -- -- - - - +function compare_label (a,b) { + if ( a.label < b.label ) { return -1; } + if ( a.label > b.label ) { return 1; } + return 0; +} + +// return course record by course-id +// --- -- -- - - - +function course_record(id) { + var record = 0; + categories.forEach(el => { if (el.id == id) record = el; }); + + return record; +} + + + + +// Modal HTML Creators +// ----------------------------------------------------------------------------- + +const modal_header = (title) => { + return [ + '<div class="modal-header">', + '<button type="button" class="close" data-dismiss="modal" aria-label="Close">', + '<span aria-hidden="true">×</span>', + '</button>', + '<h4 class="modal-title" id="myModalLabel">', + title, + '</h4>', + '</div>' + ].join('\n'); +} + + +const upload_file_form = () => { + return [ + '<form method="POST" id="file_upload_form" name="upload_file_form">', + + '<div class="modal-body">', + + '<input type="hidden" name="folder" value="'+ altID +'">', + + '<div class="form-group">', + '<label for="title">Τίτλος/Περιγραφή για το Αρχείο</label>', + '<input type="text" class="form-control " value="" name="title" id="title" placeholder="Τίτλος για το Αρχείο" required="">', + '</div>', + + '<div class="row">', + + '<div class="form-group col-md-7">', + '<label class="control-label" for="file">Εισαγωγή Αρχείου</label>', + '<input class="form-control" type="file" id="file" name="file" required="">', + '</div>', + + '<div class="form-group col-md-5">', + '<label class="control-label" for="category_id">Είδος</label>', + '<select class="form-control" id="reference" name="reference" required="">', + '<option"></option>', + '<option value="1">Παραπομπή (εμφανίζεται)</option>', + '<option value="0">Βοηθητικό αρχείο (κρυφό)</option>', + '</select>', + '</div>', + + '</div>', + + '</div>', + + '<div class="modal-footer">', + '<div class="row">', + '<div class="form-actions">', + '<div class="col-lg-3 col-lg-offset-6 col-md-4 col-md-offset-4 col-xs-6">', + '<buton type="button" class="btn btn-default js-modal-close col-xs-12" data-dismiss="modal">', + 'Κλείσιμο', + '</button>', + '</div>', + '<div class="col-lg-3 col-md-4 col-xs-6">', + '<button class="btn btn-success col-xs-12" type="submit">Καταχώριση</button>', + '</div>', + '</div>', + '</div>', + '</div>', + + '</form>' + ].join('\n'); +} + + +const preview_article = (title, intro, body) => { + return [ + '<div class="modal-body">', + '<div class="post">', + + '<h2>'+ title +'</h2>', + + ((intro == '') ? '' : ('<div class="intro">'+ intro +'</div>')), + + '<div class="article-body">', + marked.parse(body, { sanitize: true }), + '</div>', + + '</div>', + '</div>', + '<div class="modal-footer">', + '<div class="row">', + '<div class="form-actions">', + '<div class="col-md-4 col-md-offset-4 col-xs-6 col-xs-offset-3">', + '<buton type="button" class="btn btn-default js-modal-close col-xs-12" data-dismiss="modal">', + 'Κλείσιμο', + '</button>', + '</div>', + '</div>', + '</div>', + '</div>' + ].join('\n'); +} + + +$(document).ready(function() { + + // When document is ready + // ------------------------------------------------------------------------- + + + // inti category (select2) + // ... + $("#course_id").select2({ + placeholder: 'Γονική Κατηγορία', + width: '100%' + }); + + + // // init tags (select2) + // // ... + // $('#tags').select2({ + // placeholder: 'Ετικέτες (tags/keywords)', + // width: '100%', + // tags: true + // }); + + + // init editor (tiny MCE) + // tinymce.init({ + // selector: 'form textarea[name=body]', + // language: 'el', + // menubar: true + // }); + // --- -- -- - - - + + + /** preload content + * --- -- -- - - - + * get post + * get categories + * get tags + * update content (form-elements) + */ + if (id) { // if an `id` is given, load post .. then init_form + + $.getJSON( "/admin/api/", { action: "get_post", id: id } ) + .done( function (data) { + post = data; // keep post + + // update fields' values + $('input[name=id]').val(post.id); // id + $('input[name=title]').val(post.title); // title + $('textarea[name=intro]').val(post.intro); // intro + $('textarea[name=body]').val(post.body); // body + $('input[name=status]').prop( // status + 'checked', + ((post.status==1)? true : false)) + .trigger('change'); + + $('#post-info').html('Δημιουργία: '+post.creation_date+'<br>Τελευταία Ενημέρωση: '+ post.update_date); + + $('#delete').prop( "disabled", false ); // enable delete button + + // if intro exist, show intro (optinal) field + if ((post.intro != null) && (post.intro != '')) { + $('.optional label[for=intro]').addClass('show'); // unhide section + } + + // parse media files; then draw + if (post.medias_json != null) { + files = JSON.parse(post.medias_json); + $('.optional label[for=media]').addClass('show'); // unhide section + } + draw_files_list(); // then draw the files list + + init_form_values(); // init all (other) values + }); + + } else { // else ... just init_form + $('#delete').prop( "disabled", true ); // disable delete button for new post attempt + init_form_values(); + } + + + + /** + * load categories and tags; + * if not a new post (id!=0) update form elemnts with post's values + */ + function init_form_values() { + + $.when( // when loaded posts and categories + + $.getJSON( "/admin/api/categories" ), + + $.getJSON( "/admin/api/privileges" ) + + ).done( function( categories_response, privileges_response ) { + + // construct categories data + // --- + Object.keys(categories_response[0]).forEach( key => { + cat = categories_response[0][key]; + categories.push({ + id: cat.rec.id, + label: cat.breadcrumb.replaceAll('\t', ' / ') + }); + }); + categories.sort( compare_label ); // sort categories by breadcrumb + + // // construct tags data + // // --- + // Object.keys(tags_response[0]).forEach( key => { + // tag = tags_response[0][key]; + // tags.push({ + // id: tag.id, + // label: tag.name + // }); + // }); + + // construct provileges data + // --- + Object.keys(privileges_response[0]).forEach( key => { + priv = privileges_response[0][key]; + privileges.push({ + id: priv.id, + label: priv.label + }); + }); + privileges.sort( compare_label ); // sort categories by breadcrumb + + // pass categories and tags options onto select2 elements + // --- -- -- - - - + if (id == 0) { + set_parent_options( $('#course_id'), categories ); + set_parent_options( $('#privilege_id'), privileges ); + + } else { + // setup category_id options; select post's chosen category-id + set_parent_options( $('#category_id'), categories, post.category_id ); + + // parse selected tags + // --- + var selected_tag_ids = []; + if (post.tags_json != null) { + var selected_tags = JSON.parse(post.tags_json); + selected_tags.forEach( itag => { selected_tag_ids.push(itag.id); }) + } + // setup tags options; select post's chosen tags + set_parent_options( $('#tags'), tags, selected_tag_ids ); + + // ALSO: if tags selectd, show tags field + if (selected_tag_ids.length != 0) { $('.optional label[for=tags]').addClass('show'); } + } + + }); + + } + + + /** set_parent_options() + * + * -> create options for parents <select> + * -> choose selected parent + * + * NOTE: + * the targer <select> should be a select2 control + * + * @param parents (array): array of parents + * @param selected (int): selected parent + */ + function set_parent_options(selector, list, selected_value = false) { + + list.forEach( li => { // attach options into <select> control + selector.append(`<option value="${li.id}">${li.label}</option>`); + }); + + if (selected_value !== false) { // mark selected option(s) + selector.val(selected_value).trigger('change'); + } + else { selector.val(null).trigger('change'); } + } + + + /** draw_files_list + * --- -- -- - - - + */ + function draw_files_list() { + var li_list = []; + var ref_icon, ref_class; + + files.forEach( item => { + // preapre [show|hide]-file as reference + if (item.reference == 1) { + ref_icon = '<i class="fas fa-eye"></i>'; + ref_class = 'btn-default'; + + } else { + ref_icon = '<i class="fas fa-eye-slash"></i>'; + ref_class = 'btn-disabled'; + } + + var copy = [ // copy button + '<button type="button" class="btn btn-xs btn-default js-copy"', + 'data-url="' + item.path + '">', + '<i class="far fa-clone"></i>', + '</button>' + ].join(''); + var ref = [ // show|hide (is_reference) + '<button type="button" class="btn btn-xs '+ ref_class +' js-reference"', + 'data-id="' + item.id + '">', + ref_icon, + '</button>' + ].join(''); + var del = [ // delete button + '<button type="button" class="btn btn-xs btn-danger js-delete"', + 'data-id="' + item.id + '">', + '<i class="fas fa-trash-alt"></i>', + '</button>' + ].join(''); + var option = [ // li html + '<li>', + '<div class="text">' + item.title + '</div>', + '<div class="actions">', + ref, ' ', copy, ' ', del, + '</div>', + '</li>' + ].join(''); + li_list.push(option); // push to li_list + }); + $('#files_list').html( li_list.join('\n') ); + + } + + + + // When form's action-buttons are clicked + // ------------------------------------------------------------------------- + + + // form submit + // --- -- -- - - - + $('.edit-post form').submit( event => { + event.preventDefault(); + + // rearrange tags to old and new ones + // --- + var old_tags = []; + var new_tags = []; + var tags = $('#tags').select2('data'); + tags.forEach( t => { + if (!parseInt(t.id)) { + new_tags.push(t.text); + } else { old_tags.push(t.id); } + }) + + var attachments = []; + // attachments (array) of "`media_id`;`reference`" strings + files.forEach( f => { attachments.push( f.id +';'+ f.reference ) }); + + // prepare data to POST + // --- + var data = { + id: parseInt($('.edit-post form input[name=id]').val()), + title: $('.edit-post form input[name=title]').val(), + category_id: parseInt($('#category_id').select2('data')[0].id), + intro: $('.edit-post form textarea[name=intro]').val(), + body: $('.edit-post form textarea[name=body]').val(), + status: (($('input[name=status]').is(":checked")) ? 1 : 0), + tags: old_tags, + new_tags: new_tags, + media: attachments + }; + + // select action url (add or update) + // --- + var request = '/wiki/ajax?action=' + ((data.id == 0) ? 'add_post' : 'update_post'); + + // send POST (ajax) request + $.post(request, data) + .done(function( data ) { + window.location.href = '/wiki/admin?action=posts'; // seems ok; bach to articles + }); + + }); + + + // go-back (to posts) + // --- -- -- - - - + $('#go-back').click( event => { + window.location.href = '/wiki/admin?action=posts'; + }); + + + // delete + // --- -- -- - - - + $('#delete').click( event => { + // ask before deletion + swal({ + title: 'Είστε βέβαιος;', + html: "Ζητήσατε να διαγραφεί το άρθρο.<br>Παρακαλώ επιβεβαιώστε την διαγραφή.<br>ΠΡΟΣΟΧΗ: Δεν θα έχετε την δυνατότητα αναίρεσης!", + type: 'warning', + showCancelButton: true, + confirmButtonColor: '#3085d6', + cancelButtonColor: '#d33', + cancelButtonText: 'Ακύρωση', + confirmButtonText: 'Ναι, να διαγραφεί', + showLoaderOnConfirm: true, + preConfirm: function() { + return new Promise(function(resolve) { + $.getJSON( "/wiki/ajax", { action: "delete_post", id: id } ) + .done( function (data) { + // return to posts-administration + window.location.href = '/wiki/admin?action=posts'; + }); + }); + }, + allowOutsideClick: false + }).catch(swal.noop); + + }); + + + + // copy url button + // --- -- -- - - - + $('.wiki').on('click', 'button.js-copy', function (e) { + var copytext = ''; // default: copy nothing + + if ($(this).data('id') !== undefined) { + copytext = pageUrl.origin +'/wiki/post?id=' + $(this).data('id'); + } + + if ($(this).data('url') !== undefined) { + copytext = files_root + $(this).data('url'); + } + + navigator.clipboard + .writeText(copytext) + .then(() => { // notify the copy event + $(this).addClass('copied'); + setTimeout( () => { $(this).removeClass('copied'); }, 700); + }) + .catch(() => { + console.log("error on coping text"); + }); + }); + + + + // delete (remove) file button + // --- -- -- - - - + $('.wiki').on('click', 'button.js-delete', function (e) { + var media_id = $(this).data('id'); + + // remove from files array the one with id == media_id + // --- + var temp = []; + files.forEach( it => { if (it.id != media_id) temp.push(it); }); + + files = temp; // re-define files + draw_files_list(); // then re-draw files list + }); + + + + + + // show|hide file as referece button + // --- -- -- - - - + $('.wiki').on('click', 'button.js-reference', function (e) { + var id = $(this).data('id'); + + // toggle reference attribute of item with `id` = id + // --- + var temp = []; + files.forEach( it => { + if (it.id == id) { + it.reference = (it.reference==0) ? 1 : 0; + } + temp.push(it) + }); + + files = temp; // re-define files + draw_files_list(); // then re-draw files list + }); + + + + + + /** When modal show-up + * ------------------------------------------------------------------------- + * ... prepare the manage-category form + * ... update select2 with possible parents + */ + $('#editorModal').on('show.bs.modal', event => { + let button = $(event.relatedTarget); // Button that triggered the modal + let action = button.data('action'); // action + // var modal = $(this); // modal object + + // update modal literature + + switch (action) { + case 'upload_file': + $("#editorModal .modal-content").html( + modal_header('Εισαγωγή Αρχείου') + upload_file_form() + ); + break; + + case 'preview': + let preview = preview_article( + $('.edit-post form input[name=title]').val(), + $('.edit-post form textarea[name=intro]').val(), + $('.edit-post form textarea[name=body]').val() + ); + $("#editorModal .modal-content").html( modal_header('Προεπισκόπηση') + preview); + break; + + default: + // do nothing + } + + }); + + + + /** submit upload file event + * ------------------------------------------------------------------------- + */ + $('#editorModal').on('submit', 'form[name=upload_file_form]', function (event) { + event.preventDefault(); + + // create the FormData object + // --- -- -- - - - + var fd = new FormData(document.getElementById('file_upload_form')); + + // post the form (via ajax) + $.ajax({ + url: '/wiki/ajax?action=file_upload', + type: 'POST', + data: fd, + contentType: false, + processData: false, + dataType: 'json', + success: function(response) { // on success + + if (response.success) { + files.push({ // ... update files array + id: response.id, + title: response.title, + path: response.path, + type: response.type, + reference: $('#reference').val() + }); + draw_files_list(); + + $('#editorModal').modal('hide'); + + } else { + console('File not uploaded'); + } + } + }); + }); + + + // TODO: + // disable links while previewing the article + // OR add a target="_blank" + + +}); + |
