diff options
| author | George Halkiadakis <gchalkiadakis@sklavenitis.co.gr> | 2023-04-27 03:47:30 +0300 |
|---|---|---|
| committer | George Halkiadakis <gchalkiadakis@sklavenitis.co.gr> | 2023-04-27 03:47:30 +0300 |
| commit | 059e0d95d0c28bc5060e87e146eaf7411f51bf90 (patch) | |
| tree | 7c21ac432f16dde2329a0d5954d70711eceb48e6 /public/assets/js/admin | |
| parent | 26cd8ee99659ef1926c96a049c93645ffc9b169d (diff) | |
| download | gyraf1gov-059e0d95d0c28bc5060e87e146eaf7411f51bf90.tar.gz gyraf1gov-059e0d95d0c28bc5060e87e146eaf7411f51bf90.tar.bz2 gyraf1gov-059e0d95d0c28bc5060e87e146eaf7411f51bf90.zip | |
skeleton commit; based on an anom project
Diffstat (limited to 'public/assets/js/admin')
| -rw-r--r-- | public/assets/js/admin/categories.js | 288 | ||||
| -rw-r--r-- | public/assets/js/admin/edit_lesson.js | 600 | ||||
| -rw-r--r-- | public/assets/js/admin/edit_page.js | 382 | ||||
| -rw-r--r-- | public/assets/js/admin/files.js | 302 | ||||
| -rw-r--r-- | public/assets/js/admin/lessons.js | 364 | ||||
| -rw-r--r-- | public/assets/js/admin/pages.js | 240 | ||||
| -rw-r--r-- | public/assets/js/admin/panel.js | 1 | ||||
| -rw-r--r-- | public/assets/js/admin/privileges.js | 291 |
8 files changed, 2468 insertions, 0 deletions
diff --git a/public/assets/js/admin/categories.js b/public/assets/js/admin/categories.js new file mode 100644 index 0000000..e4d2955 --- /dev/null +++ b/public/assets/js/admin/categories.js @@ -0,0 +1,288 @@ +// Globals +// ----------------------------------------------------------------------------- +var categories = []; +var table; + + + +// Supplamentary functions +// ----------------------------------------------------------------------------- + +// array.indexOf polyfill +// --- -- -- - - - +if (!Array.prototype.indexOf) +{ + Array.prototype.indexOf = function(elt /*, from*/) + { + var len = this.length >>> 0; + + var from = Number(arguments[1]) || 0; + from = (from < 0) + ? Math.ceil(from) + : Math.floor(from); + if (from < 0) + from += len; + + for (; from < len; from++) + { + if (from in this && + this[from] === elt) + return from; + } + return -1; + }; +} + + +// function for sorting an array by 'breadcrumb' attribute +// --- -- -- - - - +function compare_breadcrumb (a,b) { + if ( a.breadcrumb < b.breadcrumb ) { return -1; } + if ( a.breadcrumb > b.breadcrumb ) { return 1; } + return 0; +} + +// return category record by category-id +// --- -- -- - - - +function category_record(id) { + var record = 0; + categories.forEach(el => { if (el.id == id) record = el; }); + + return record; +} + + +// return children of parent +// --- -- -- - - - +function childs_of_parents(list) { + // if 'list' is integer then make it an one-item list + parents = (Number.isInteger(list)) ? [ list ] : list; + + var childs = []; + + if (parents.length == 0) { return []; } + + parents.forEach( pid => { + categories.forEach( el => { if (el.parent == pid) childs.push(el.id)}); + }); + + var grandchilds = childs_of_parents(childs); + + return childs.concat(grandchilds); +} + + +// create actions html (edit and delete buttons) +// --- -- -- - - - +function create_actions_html(id) { + + var del, edit; + + del = ""; + + edit = [ + '<button type="button" class="btn btn-sm btn-warning"', + 'data-bs-toggle="modal" data-bs-target="#manageCategory"', + 'data-id="'+ id +'">', + '<i class="fas fa-pencil-alt"></i>', + '</button>' + ].join('\n'); + + return edit +' '+ del; +} + + + +$(document).ready(function() { + + // When document is ready + // ------------------------------------------------------------------------- + + + /** init selec2 element (used to specify parent-category) + * --- -- -- - - - + */ + $("#parent_id").select2({ + placeholder: 'Γονική Κατηγορία', + dropdownParent: $('#manageCategory'), + width: '100%', + // dropdownParent: "#manageCategory" // ref: https://github.com/select2/select2-bootstrap-theme/issues/41 + }); + + + /** init_table + * --- -- -- - - - + * get categories + * constuct categories array + * render dataTable + */ + function init_table() { + + // get all categories from server (ajax) + $.getJSON( "/admin/api/categories") + .done(function( json ) { + // then ... + + // reset categories + categories.length = 0; + + // construct (new) categories data + Object.keys(json).forEach( key => { + el = json[key]; + categories.push({ + id: parseInt(el.rec.id), + label: el.rec.label, + breadcrumb: el.breadcrumb.replaceAll('\t', ' / '), + parent: parseInt(el.rec.parent), + actions: create_actions_html(el.rec.id) + }); + }); + + // sort by breadcrumb + categories.sort( compare_breadcrumb ); + + // destroy previous datatable table instances + $('#dt-categories').dataTable().fnClearTable(); + $('#dt-categories').dataTable().fnDestroy(); + + // (re-)create categories datatable + table = $('#dt-categories').DataTable({ + language: { url: '/libs/DataTables/localization/Greek.json' }, + data: categories, + ordering: false, + columns: [ + { data: 'breadcrumb' }, + { data: 'actions' } + ] + }); + + }) + .fail(function( jqxhr, textStatus, error ) { + console.log( "Request Failed (" + error +")" ); + }); + + } + init_table(); // init table on first run + + + + /** When modal show-up + * ------------------------------------------------------------------------- + * ... prepare the manage-category form + * ... update select2 with possible parents + */ + $('#manageCategory').on('show.bs.modal', event => { + var button = $(event.relatedTarget); // Button that triggered the modal + var id = parseInt( button.data('id') ); // category_id from data-id + // var modal = $(this); // modal object + + // update modal literature + // --- + $('#manageCategory .modal-title').html( // modal title + (id) ? 'Ενημέρωση Κατηγορίας' : 'Δημιουργία Κατηγορίας' + ); + $('#manageCategory form input[name=id]').val( id ); // category id (hidden) + $('#manageCategory form input[name=label]').val( // category label + (id) ? category_record(id).label : "" + ); + prepare_parents_element(id); // prepare <select> for parents + }) + + /** prepare_parents_element() + * --- + * @param id (int) : id of selected category + * -> Get all posible parents + * -> call set_parent_options() to render the options + */ + function prepare_parents_element(id) { + var parents = []; + + if (id) { // get all possible parents + + // id-category and subcategories are fobiden parents + var forbiden = childs_of_parents(id); + forbiden.push(id); + + // filter catogories adn prepare parents + categories.forEach( el => { + if (!forbiden.includes(el.id)) { + parents.push({ + id: el.id, + breadcrumb: el.breadcrumb, + }); + } + }); + set_parent_options(parents, category_record(id).parent); + + + } else { // new record; all categories are possible parrents + categories.forEach( el => { + parents.push({ + id: el.id, + breadcrumb: el.breadcrumb, + }); + }) + set_parent_options(parents, -1); + } + } + + /** set_parent_options() + * -> create options for parents <select> + * -> choose selected parent + * @param parents (array): array of parents + * @param selected (int): selected parent + */ + function set_parent_options(parents, selected) { + // sort parents by breadcrumb + parents.sort( compare_breadcrumb ); + + // remove any olded options from select2 + $('#parent_id option').each(function() { $(this).remove(); }); + + // first option is the root category + $('#parent_id').append('<option value="0">Root</option>') + + // add all other parent options + parents.forEach( i => { + $('#parent_id').append(`<option value="${i.id}">${i.breadcrumb}</option>`); + }); + + if (selected == -1) { // new category; remove any parent pre-selection + $('#parent_id').val(null).trigger('change'); + + } else { // category exists; choose selected parent + $('#parent_id').val(selected).trigger('change'); + } + } + + + + + // When form is submited + // ------------------------------------------------------------------------- + + $('#manageCategory form').submit( event => { + event.preventDefault(); + + // prepare data to POST + var data = { + id: parseInt($('#manageCategory form input[name=id]').val()), + label: $('#manageCategory form input[name=label]').val(), + parent_id: parseInt($('#parent_id').select2('data')[0].id) + }; + + // select action url (add or update) + var request = '/admin/api/categories/' + ((data.id == 0) ? 'add' : 'update'); + + // send POST request + $.post(request, data) + .done(function( data ) { + + $('#manageCategory').modal('hide'); // when done, close modal + + init_table(); // reload table of categories + }); + + }); + +}); diff --git a/public/assets/js/admin/edit_lesson.js b/public/assets/js/admin/edit_lesson.js new file mode 100644 index 0000000..367c3b7 --- /dev/null +++ b/public/assets/js/admin/edit_lesson.js @@ -0,0 +1,600 @@ +/** NOTE: + * depricated code for tag-system is commented-out and kept in the source-code + * as case-study; the code is operational; + */ + + +// 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 + + +// 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">', + '<h4 class="modal-title" id="myModalLabel">', + title, + '</h4>', + '<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close">', + '<span aria-hidden="true"></span>', + '</button>', + '</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="form-group">', + '<label class="control-label" for="file">Εισαγωγή Αρχείου</label>', + '<input class="form-control" type="file" id="file" name="file" required="">', + '</div>', + + '</div>', + + '<div class="modal-footer">', + '<div class="row" style="width: 50%;">', + '<div class="form-actions">', + '<div class="col col-4">', + '<buton type="button" class="btn btn-default js-modal-close col-12" data-bs-dismiss="modal">', + 'Κλείσιμο', + '</button>', + '</div>', + '<div class="col-8">', + '<button class="btn btn-success col-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" style="width: 50%;">', + '<div class="form-actions">', + '<div class="col-md-4 col-md-offset-4 col-6 col-offset-3">', + '<buton type="button" class="btn btn-default js-modal-close col-12" data-bs-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%' + }); + + // inti category (select2) + // ... + $("#privilege_id").select2({ + placeholder: 'Επίπεδο Πρόσβασης', + width: '100%' + }); + + + /** DEPRICATED: (tag system) + * + * // init tags (select2) + * // ... + * $('#tags').select2({ + * placeholder: 'Ετικέτες (tags/keywords)', + * width: '100%', + * tags: true + * }); + */ + + /** DEPRICATED: + * + * // TINY-MCE EDITOR + * 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/lesson/" + id ) + .done( function (data) { + lesson = data; // keep post + + // update fields' values + $('input[name=id]').val(lesson.id); // id + $('input[name=title]').val(lesson.title); // title + $('textarea[name=intro]').val(lesson.intro); // intro + $('textarea[name=body]').val(lesson.body); // body + $('select[name=status]').val( + (lesson.status == 0) ? '0' : "one" + ).trigger('change'); + // $('input[name=status]').prop( // status + // 'checked', + // ((lesson.status==1)? true : false)) + // .trigger('change'); + + // $('#post-info').html('Δημιουργία: '+post.creation_date+'<br>Τελευταία Ενημέρωση: '+ post.update_date); + + // parse media files; then draw + if (lesson.medias_json != null) { + files = JSON.parse(lesson.medias_json); + } + draw_files_list(); // then draw the files list + + init_form_values(); // init all (other) values + }); + + } else { // else ... just init_form + 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 + + /** DEPRICATED: (tag system) + * + * // 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( $('#course_id'), categories, lesson.course_id ); + + /*** DEPRICATED: (tag system) + * + * // 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( $('#privilege_id'), privileges, lesson.privilege_id ); + + // DEPRICATED: 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 => { + var copy = [ // copy button + '<button type="button" class="btn btn-xs btn-default js-copy"', + ' data-url="' + item.path +'"', + ' data-type="' + item.type + '"', + '>', + '<i class="far fa-clone"></i>', + '</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.label + '</div>', + '<div class="actions">', + 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(); + + /** DEPRICATED: + * + ### TAG SYSTEM + # // 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 ) }); + + // prepare data to POST + // --- + var data = { + id: parseInt($('.edit-post form input[name=id]').val()), + title: $('.edit-post form input[name=title]').val(), + course_id: parseInt($('#course_id').select2('data')[0].id), + intro: $('.edit-post form textarea[name=intro]').val(), + body: $('.edit-post form textarea[name=body]').val(), + status: ( ($('select[name=status]').val() == "0") ? 0 : 1 ), + privilege_id: parseInt($('#privilege_id').select2('data')[0].id), + // tags: old_tags, + // new_tags: new_tags, + media: attachments + }; + + // select action url (add or update) + // --- + var request = '/admin/api/lesson/' + ((data.id == 0) ? 'add' : 'update'); + + // send POST (ajax) request + $.post(request, data) + .done(function( data ) { + // console.log(data); + window.location.href = '/admin/lessons'; // seems ok; bach to articles + }); + + }); + + + // go-back (to lessons) + // --- -- -- - - - + $('#go-back').click( event => { + if (confirm('Θέλετε να ακυρώσετε τις όποιες αλλαγές και να φύγτε από τη σελίδα;')) { + window.location.href = '/admin/lessons'; + } + }); + + + // copy url button + // --- -- -- - - - + $('.admin-container').on('click', 'button.js-copy', function (e) { + var copytext = ''; // default: copy nothing + + if ($(this).data('id') !== undefined) { + copytext = window.location.origin +'/lesson/' + $(this).data('id'); + } + + if ($(this).data('url') !== undefined) { + copytext = window.location.origin + '/serve/file/' + $(this).data('url') + '?type=' + $(this).data('type'); + } + + navigator.clipboard + .writeText(copytext) + .then(() => { // notify the copy event + $(this).addClass('copied'); + setTimeout( () => { $(this).removeClass('copied'); }, 700); + console.log(copytext, ' copied!'); + }) + .catch(() => { + console.log("error on coping text"); + }); + }); + + + + // delete (remove) file button + // --- -- -- - - - + $('.admin-container').on('click', 'button.js-delete', function (e) { + console.log('delete!'); + 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 + }); + + + + /** DEPRICATED: + * + # // show|hide file as referece button + # // --- -- -- - - - + # $('.admin-container').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: '/admin/api/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, + label: response.title, + path: response.path, + type: response.type + }); + draw_files_list(); + + $('#editorModal').modal('hide'); + + } else { + console.log('File not uploaded'); + } + } + }); + }); + + + // TODO: + // disable links while previewing the article + // OR add a target="_blank" + + +}); + diff --git a/public/assets/js/admin/edit_page.js b/public/assets/js/admin/edit_page.js new file mode 100644 index 0000000..ec0e84e --- /dev/null +++ b/public/assets/js/admin/edit_page.js @@ -0,0 +1,382 @@ +// Globals +// ----------------------------------------------------------------------------- + +// NOTE: +// Variable `entity_id` is defined before evaluating this script +// id (int) : the id of edited post (or 0 if new post) + +var page; + +var id = (entity_id == 0) ? false : entity_id; + +var files = []; // array of { id:.., title:.., type:.., path:.. } records + +// TODO: support tags +// var tags = []; // array of integers + + + +// an alternative--id when a page_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">', + '<h4 class="modal-title" id="myModalLabel">', + title, + '</h4>', + '<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close">', + '<span aria-hidden="true"></span>', + '</button>', + '</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="form-group">', + '<label class="control-label" for="file">Εισαγωγή Αρχείου</label>', + '<input class="form-control" type="file" id="file" name="file" required="">', + '</div>', + + '</div>', + + '<div class="modal-footer">', + '<div class="row" style="width: 50%;">', + '<div class="form-actions">', + '<div class="col col-4">', + '<buton type="button" class="btn btn-default js-modal-close col-12" data-bs-dismiss="modal">', + 'Κλείσιμο', + '</button>', + '</div>', + '<div class="col-8">', + '<button class="btn btn-success col-12" type="submit">Καταχώριση</button>', + '</div>', + '</div>', + '</div>', + '</div>', + + '</form>' + ].join('\n'); +} + + +const preview_article = (title, body) => { + return [ + '<div class="modal-body">', + '<div class="post">', + + '<h2>'+ title +'</h2>', + + '<div class="article-body">', + marked.parse(body, { sanitize: true }), + '</div>', + + '</div>', + '</div>', + '<div class="modal-footer">', + '<div class="row" style="width: 50%;">', + '<div class="form-actions">', + '<div class="col-md-4 col-md-offset-4 col-6 col-offset-3">', + '<buton type="button" class="btn btn-default js-modal-close col-12" data-bs-dismiss="modal">', + 'Κλείσιμο', + '</button>', + '</div>', + '</div>', + '</div>', + '</div>' + ].join('\n'); +} + + +$(document).ready(function() { + + // When document is ready + // ------------------------------------------------------------------------- + + /** preload content + * --- -- -- - - - + * get page + * update content (form-elements) + */ + if (id) { // if an `id` is given, load post .. then init_form + + $.getJSON( "/admin/api/page/" + id ) + .done( function (data) { + page = data; // keep post + + // update fields' values + $('input[name=id]').val(page.id); // id + $('input[name=title]').val(page.title); // title + $('textarea[name=body]').val(page.body); // body + $('select[name=status]').val( + (page.status == 0) ? '0' : "one" + ).trigger('change'); + + // $('#post-info').html('Δημιουργία: '+post.creation_date+'<br>Τελευταία Ενημέρωση: '+ post.update_date); + + // parse media files; then draw + if (page.medias_json != null) { + files = JSON.parse(page.medias_json); + } + draw_files_list(); // then draw the files list + + }); + + } + + + /** draw_files_list + * --- -- -- - - - + */ + function draw_files_list() { + var li_list = []; + var ref_icon, ref_class; + + files.forEach( item => { + var copy = [ // copy button + '<button type="button" class="btn btn-xs btn-default js-copy"', + ' data-url="' + item.path +'"', + ' data-type="' + item.type + '"', + '>', + '<i class="far fa-clone"></i>', + '</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.label + '</div>', + '<div class="actions">', + 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-page form').submit( event => { + event.preventDefault(); + + var attachments = []; + // attachments (array) of "`media_id`;`reference`" strings + files.forEach( f => { attachments.push( f.id ) }); + + // prepare data to POST + // --- + var data = { + id: parseInt($('.edit-page form input[name=id]').val()), + title: $('.edit-page form input[name=title]').val(), + body: $('.edit-page form textarea[name=body]').val(), + status: ( ($('select[name=status]').val() == "0") ? 0 : 1 ), + media: attachments + }; + + // select action url (add or update) + // --- + var request = '/admin/api/page/' + ((data.id == 0) ? 'add' : 'update'); + + // send POST (ajax) request + $.post(request, data) + .done(function( data ) { + console.log(data); + window.location.href = '/admin/pages'; // seems ok; bach to articles + }); + + }); + + + // go-back (to pages) + // --- -- -- - - - + $('#go-back').click( event => { + if (confirm('Θέλετε να ακυρώσετε τις όποιες αλλαγές και να φύγτε από τη σελίδα;')) { + window.location.href = '/admin/pages'; + } + }); + + + // copy url button + // --- -- -- - - - + $('.admin-container').on('click', 'button.js-copy', function (e) { + var copytext = ''; // default: copy nothing + + if ($(this).data('id') !== undefined) { + copytext = window.location.origin +'/page/' + $(this).data('id'); + } + + if ($(this).data('url') !== undefined) { + copytext = window.location.origin + '/serve/file/' + $(this).data('url') + '?type=' + $(this).data('type'); + } + + navigator.clipboard + .writeText(copytext) + .then(() => { // notify the copy event + $(this).addClass('copied'); + setTimeout( () => { $(this).removeClass('copied'); }, 700); + console.log(copytext, ' copied!'); + }) + .catch(() => { + console.log("error on coping text"); + }); + }); + + + + // delete (remove) file button + // --- -- -- - - - + $('.admin-container').on('click', 'button.js-delete', function (e) { + console.log('delete!'); + 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 + }); + + + + /** 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-page form input[name=title]').val(), + $('.edit-page 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: '/admin/api/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, + label: response.title, + path: response.path, + type: response.type + }); + draw_files_list(); + + $('#editorModal').modal('hide'); + + } else { + console.log('File not uploaded'); + } + } + }); + }); + + + // TODO: + // disable links while previewing the article + // OR add a target="_blank" + + +}); + diff --git a/public/assets/js/admin/files.js b/public/assets/js/admin/files.js new file mode 100644 index 0000000..e100b30 --- /dev/null +++ b/public/assets/js/admin/files.js @@ -0,0 +1,302 @@ +// Globals +// ----------------------------------------------------------------------------- +var privileges = [ { id: 0, label: 'Δημόσιο' } ]; +var files = []; +var table; + + + +// Supplamentary functions +// ----------------------------------------------------------------------------- + +// array.indexOf polyfill +// --- -- -- - - - +if (!Array.prototype.indexOf) +{ + Array.prototype.indexOf = function(elt /*, from*/) + { + var len = this.length >>> 0; + + var from = Number(arguments[1]) || 0; + from = (from < 0) + ? Math.ceil(from) + : Math.floor(from); + if (from < 0) + from += len; + + for (; from < len; from++) + { + if (from in this && + this[from] === elt) + return from; + } + return -1; + }; +} + + +// 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 file record by file-id +// --- -- -- - - - +function privilege_record(id) { + var record = 0; + files.forEach(el => { if (el.id == id) record = el; }); + return record; +} + + +/** privilege(id) + * label of privilege with + * @param id (int) + */ +function privilege_(id) { + let reply = false; + privileges.forEach( pri => { if (pri.id == id) reply = pri.label; }); + return reply; +} + + +// create actions html (edit and delete buttons) +// --- -- -- - - - +function create_actions_html(id) { + + var del, edit; + + del = ""; + + edit = [ + '<button type="button" class="btn btn-sm btn-warning"', + 'data-bs-toggle="modal" data-bs-target="#manageFiles"', + 'data-id="'+ id +'">', + '<i class="fas fa-pencil-alt"></i>', + '</button>' + ].join('\n'); + + return edit +' '+ del; +} + + + +$(document).ready(function() { + + // When document is ready + // ------------------------------------------------------------------------- + + + /** init selec2 element (used to specify parent-category) + * --- -- -- - - - + */ + $("#privilege_id").select2({ + placeholder: 'Επίπεδο πρόσβασης', + dropdownParent: $('#manageFiles'), + width: '100%', + }); + + + /** init_table + * --- -- -- - - - + * get categories + * constuct categories array + * render dataTable + */ + function init_table() { + + // get all categories from server (ajax) + $.getJSON( "/admin/api/files") + .done(function( json ) { + // then ... + + // reset categories + files.length = 0; + + // construct (new) categories data + Object.keys(json).forEach( key => { + el = json[key]; + files.push({ + id: el.id, + label: el.label, + path: el.path, + type: el.type, + privilege_id: ((el.privilege_id == null) ? 0 : el.privilege_id), + privilege: privilege_( ((el.privilege_id == null) ? 0 : el.privilege_id) ), + actions: create_actions_html(el.id) + }); + }); + + // sort by breadcrumb + // categories.sort( compare_breadcrumb ); + + // destroy previous datatable table instances + $('#dt-files').dataTable().fnClearTable(); + $('#dt-files').dataTable().fnDestroy(); + + // (re-)create categories datatable + table = $('#dt-files').DataTable({ + language: { url: '/libs/DataTables/localization/Greek.json' }, + data: files, + ordering: false, + columns: [ + { data: 'id' }, + { data: 'label' }, + { data: 'path' }, + { data: 'type' }, + { data: 'privilege' }, + { data: 'actions' } + ] + }); + + }) + .fail(function( jqxhr, textStatus, error ) { + console.log( "Request Failed (" + error +")" ); + }); + + } + + + + $.getJSON( "/admin/api/privileges" ) + .done( function( response ) { + + // construct provileges data + // --- + response.forEach( el => { + privileges.push({ + id: el.id, + label: el.label + }); + }); + privileges.sort( compare_label ); // sort categories by breadcrumb + + init_table(); // init table on first run + }); + + + + /** When modal show-up + * ------------------------------------------------------------------------- + * ... prepare the manage-category form + * ... update select2 with possible parents + */ + $('#managePrivileges').on('show.bs.modal', event => { + var button = $(event.relatedTarget); // Button that triggered the modal + var id = parseInt( button.data('id') ); // category_id from data-id + // var modal = $(this); // modal object + + // update modal literature + // --- + $('#managePrivileges .modal-title').html( // modal title + (id) ? 'Ενημέρωση Επιπέδου Πρόσβασης' : 'Δημιουργία Επιπέδου Πρόσβασης' + ); + $('#managePrivileges form input[name=id]').val( id ); // category id (hidden) + $('#managePrivileges form input[name=label]').val( // category label + (id) ? privilege_record(id).label : "" + ); + prepare_included_element(id); // prepare <select> for parents + }) + + /** prepare_included_element() + * --- + * @param id (int) : id of selected privilege + * -> Get all posible included privileges + * -> call set_included_options() to render the options + */ + function prepare_included_element(id) { + var may_included = []; + + if (id) { // get all possible parents + + // TODO: + // specify forbiden privilege IDs for being included + + // filter privileges adn prepare possible included privileges + privileges.forEach( el => { + if (id != el.id) { + may_included.push({ + id: el.id, + label: el.label + }); + } + }); + console.log(may_included); + set_included_options(may_included, privilege_record(id).included_id); + + + } else { // new record; all categories are possible parrents + privileges.forEach( el => { + may_included.push({ + id: el.id, + label: el.label, + }); + }) + set_included_options(may_included, -1); + } + } + + /** set_included_options() + * -> create options for parents <select> + * -> choose selected parent + * @param may_included (array): array of possible included privileges + * @param selected (int): selected included privilege + */ + function set_included_options(may_included, selected) { + // sort parents by breadcrumb + // parents.sort( compare_breadcrumb ); + + // remove any olded options from select2 + $('#included_id option').each(function() { $(this).remove(); }); + + // first option is the root category + $('#included_id').append('<option value="0">— κανένα —</option>') + + // add all other parent options + may_included.forEach( i => { + $('#included_id').append(`<option value="${i.id}">${i.label}</option>`); + }); + + if (selected == -1) { // new category; remove any parent pre-selection + $('#included_id').val(0).trigger('change'); + + } else { // category exists; choose selected parent + $('#included_id').val(selected).trigger('change'); + } + } + + + + + // When form is submited + // ------------------------------------------------------------------------- + + $('#manageCategory form').submit( event => { + event.preventDefault(); + + // prepare data to POST + var data = { + id: parseInt($('#manageCategory form input[name=id]').val()), + label: $('#manageCategory form input[name=label]').val(), + parent_id: parseInt($('#parent_id').select2('data')[0].id) + }; + + // select action url (add or update) + var request = '/admin/api/category/' + ((data.id == 0) ? 'add' : 'update'); + + // send POST request + $.post(request, data) + .done(function( data ) { + + $('#manageCategory').modal('hide'); // when done, close modal + + init_table(); // reload table of categories + }); + + }); + +}); diff --git a/public/assets/js/admin/lessons.js b/public/assets/js/admin/lessons.js new file mode 100644 index 0000000..1a08875 --- /dev/null +++ b/public/assets/js/admin/lessons.js @@ -0,0 +1,364 @@ +// Globals +// ----------------------------------------------------------------------------- +var categories = []; +var privileges = [ { id: 0, label: 'Δημόσιο' } ]; +var lessons = []; +var table; + + + +// Supplamentary functions +// ----------------------------------------------------------------------------- + +// array.indexOf polyfill +// --- -- -- - - - +if (!Array.prototype.indexOf) +{ + Array.prototype.indexOf = function(elt /*, from*/) + { + var len = this.length >>> 0; + + var from = Number(arguments[1]) || 0; + from = (from < 0) + ? Math.ceil(from) + : Math.floor(from); + if (from < 0) + from += len; + + for (; from < len; from++) + { + if (from in this && + this[from] === elt) + return from; + } + return -1; + }; +} + + +// 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; +} + + +// function for sorting an array by 'breadcrumb' attribute +// --- -- -- - - - +function compare_breadcrumb (a,b) { + if ( a.breadcrumb < b.breadcrumb ) { return -1; } + if ( a.breadcrumb > b.breadcrumb ) { return 1; } + return 0; +} + +// return category record by category-id +// --- -- -- - - - +function lesson_record(id) { + var record = 0; + lessons.forEach(el => { if (el.id == id) record = el; }); + + return record; +} + +/** course(id) + * label of course with + * @param id (int) + */ +function course_(id) { + let reply = false; + categories.forEach( cat => { if (cat.id == id) reply = cat.label; }) + return reply; +} + +/** privilege(id) + * label of privilege with + * @param id (int) + */ +function privilege_(id) { + let reply = false; + privileges.forEach( pri => { if (pri.id == id) reply = pri.label; }); + return reply; +} + + +//// return children of parent +//// --- -- -- - - - +//function childs_of_parents(list) { +// // if 'list' is integer then make it an one-item list +// parents = (Number.isInteger(list)) ? [ list ] : list; +// +// var childs = []; +// +// if (parents.length == 0) { return []; } +// +// parents.forEach( pid => { +// categories.forEach( el => { if (el.parent == pid) childs.push(el.id)}); +// }); +// +// var grandchilds = childs_of_parents(childs); +// +// return childs.concat(grandchilds); +//} + + +// create actions html (edit and delete buttons) +// --- -- -- - - - +function create_actions_html(el) { + + var pub, del, edit; + + // preapre [show|hide]-file as reference + if (el.status == 1) { + pub = '<span class="btn btn-sm"><i class="fas fa-eye"></i></span>'; + + } else { + pub = '<span class="btn btn-sm"><i class="fas fa-eye-slash"></i></span>'; + } + + del = ""; + + fast_edit = [ + '<button type="button" class="btn btn-sm btn-light"', + 'data-bs-toggle="modal" data-bs-target="#manageLessons"', + 'data-id="'+ el.id +'">', + '<i class="fa-solid fa-bolt"></i>', + '</button>' + ].join('\n'); + + edit = [ + '<a type="button" class="btn btn-sm btn-warning "', + 'href="/admin/edit_lesson/'+ el.id +'">', + '<i class="fas fa-pencil-alt"></i>', + '</a>' + ].join('\n'); + + return pub +' '+ fast_edit +' '+ edit +' '+ del; +} + + + +$(document).ready(function() { + + // When document is ready + // ------------------------------------------------------------------------- + + + // inti category (select2) + // ... + $("#privilege_id").select2({ + placeholder: 'Επίπεδο Πρόσβασης', + dropdownParent: $('#manageLessons'), + width: '100%' + }); + + /** init selec2 element (used to specify parent-category) + * --- -- -- - - - + */ + $("#course_id").select2({ + placeholder: 'Κεφάλαιο', + dropdownParent: $('#manageLessons'), + width: '100%', + }); + + + /** init_table + * --- -- -- - - - + * get categories + * constuct categories array + * render dataTable + */ + function init_table() { + + // get all categories from server (ajax) + $.getJSON( "/admin/api/lessons") + .done(function( json ) { + // then ... + + // reset categories + lessons.length = 0; + + // construct (new) categories data + Object.keys(json).forEach( key => { + el = json[key]; + lessons.push({ + id: parseInt(el.id), + title: el.title, + course_id: el.course_id, + course: course_(el.course_id), + privilege_id: el.privilege_id, + privilege: privilege_(el.privilege_id), + actions: create_actions_html(el) + }); + }); + + // sort by breadcrumb + // categories.sort( compare_breadcrumb ); + + // destroy previous datatable table instances + $('#dt-lessons').dataTable().fnClearTable(); + $('#dt-lessons').dataTable().fnDestroy(); + + // (re-)create categories datatable + table = $('#dt-lessons').DataTable({ + language: { url: '/libs/DataTables/localization/Greek.json' }, + data: lessons, + ordering: false, + columns: [ + { data: 'id' }, + { data: 'title' }, + { data: 'course' }, + { data: 'privilege' }, + { data: 'actions' } + ] + }); + + }) + .fail(function( jqxhr, textStatus, error ) { + console.log( "Request Failed (" + error +")" ); + }); + + } + + + $.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 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 + + set_parent_options( $('#course_id'), categories ); + set_parent_options( $('#privilege_id'), privileges ); + + init_table(); // init table on first run + }); + + + /** 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'); } + } + + + + /** When modal show-up + * ------------------------------------------------------------------------- + * ... prepare the manage-category form + * ... update select2 with possible parents + */ + $('#manageLesson').on('show.bs.modal', event => { + var button = $(event.relatedTarget); // Button that triggered the modal + var id = parseInt( button.data('id') ); // category_id from data-id + // var modal = $(this); // modal object + + //// update modal literature + //// --- + //$('#manageCategory .modal-title').html( // modal title + // (id) ? 'Ενημέρωση Κατηγορίας' : 'Δημιουργία Κατηγορίας' + //); + //$('#manageCategory form input[name=id]').val( id ); // category id (hidden) + //$('#manageCategory form input[name=label]').val( // category label + // (id) ? category_record(id).label : "" + //); + //prepare_parents_element(id); // prepare <select> for parents + }) + + + /** 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'); } + } + + + + + + // When form is submited + // ------------------------------------------------------------------------- + + $('#manageCategory form').submit( event => { + event.preventDefault(); + + // prepare data to POST + var data = { + id: parseInt($('#manageCategory form input[name=id]').val()), + label: $('#manageCategory form input[name=label]').val(), + parent_id: parseInt($('#parent_id').select2('data')[0].id) + }; + + // select action url (add or update) + var request = '/admin/api/categories/' + ((data.id == 0) ? 'add' : 'update'); + + // send POST request + $.post(request, data) + .done(function( data ) { + + $('#manageCategory').modal('hide'); // when done, close modal + + init_table(); // reload table of categories + }); + + }); + +}); diff --git a/public/assets/js/admin/pages.js b/public/assets/js/admin/pages.js new file mode 100644 index 0000000..4aa02ca --- /dev/null +++ b/public/assets/js/admin/pages.js @@ -0,0 +1,240 @@ +// Globals +// ----------------------------------------------------------------------------- +var categories = []; +var privileges = [ { id: 0, label: 'Δημόσιο' } ]; +var pages = []; +var table; + + + +// Supplamentary functions +// ----------------------------------------------------------------------------- + +// array.indexOf polyfill +// --- -- -- - - - +if (!Array.prototype.indexOf) +{ + Array.prototype.indexOf = function(elt /*, from*/) + { + var len = this.length >>> 0; + + var from = Number(arguments[1]) || 0; + from = (from < 0) + ? Math.ceil(from) + : Math.floor(from); + if (from < 0) + from += len; + + for (; from < len; from++) + { + if (from in this && + this[from] === elt) + return from; + } + return -1; + }; +} + + +// 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; +} + + +// function for sorting an array by 'breadcrumb' attribute +// --- -- -- - - - +function compare_breadcrumb (a,b) { + if ( a.breadcrumb < b.breadcrumb ) { return -1; } + if ( a.breadcrumb > b.breadcrumb ) { return 1; } + return 0; +} + +// return category record by category-id +// --- -- -- - - - +function page_record(id) { + var record = 0; + pages.forEach(el => { if (el.id == id) record = el; }); + + return record; +} + + +// create actions html (edit and delete buttons) +// --- -- -- - - - +function create_actions_html(el) { + + var pub, del, edit; + + // preapre [show|hide]-file as reference + if (el.status == 1) { + pub = '<span class="btn btn-sm"><i class="fas fa-eye"></i></span>'; + + } else { + pub = '<span class="btn btn-sm"><i class="fas fa-eye-slash"></i></span>'; + } + + del = ""; + + fast_edit = [ + '<button type="button" class="btn btn-sm btn-light"', + 'data-bs-toggle="modal" data-bs-target="#managePages"', + 'data-id="'+ el.id +'">', + '<i class="fa-solid fa-bolt"></i>', + '</button>' + ].join('\n'); + + edit = [ + '<a type="button" class="btn btn-sm btn-warning "', + 'href="/admin/edit_page/'+ el.id +'">', + '<i class="fas fa-pencil-alt"></i>', + '</a>' + ].join('\n'); + + return pub +' '+ fast_edit +' '+ edit +' '+ del; +} + + + +$(document).ready(function() { + + // When document is ready + // ------------------------------------------------------------------------- + + + /** init_table + * --- -- -- - - - + * get categories + * constuct categories array + * render dataTable + */ + function init_table() { + + // get all categories from server (ajax) + $.getJSON( "/admin/api/pages") + .done(function( json ) { + // then ... + + // reset categories + pages.length = 0; + + // construct (new) categories data + Object.keys(json).forEach( key => { + el = json[key]; + pages.push({ + id: parseInt(el.id), + title: el.title, + actions: create_actions_html(el) + }); + }); + + // sort by breadcrumb + // categories.sort( compare_breadcrumb ); + + // destroy previous datatable table instances + $('#dt-pages').dataTable().fnClearTable(); + $('#dt-pages').dataTable().fnDestroy(); + + // (re-)create categories datatable + table = $('#dt-pages').DataTable({ + language: { url: '/libs/DataTables/localization/Greek.json' }, + data: pages, + ordering: false, + columns: [ + { data: 'id' }, + { data: 'title' }, + { data: 'actions' } + ] + }); + + }) + .fail(function( jqxhr, textStatus, error ) { + console.log( "Request Failed (" + error +")" ); + }); + + } + init_table(); // init table on first run + + + + /** When modal show-up + * ------------------------------------------------------------------------- + * ... prepare the manage-category form + * ... update select2 with possible parents + */ + $('#managePages').on('show.bs.modal', event => { + var button = $(event.relatedTarget); // Button that triggered the modal + var id = parseInt( button.data('id') ); // category_id from data-id + // var modal = $(this); // modal object + + //// update modal literature + //// --- + //$('#manageCategory .modal-title').html( // modal title + // (id) ? 'Ενημέρωση Κατηγορίας' : 'Δημιουργία Κατηγορίας' + //); + //$('#manageCategory form input[name=id]').val( id ); // category id (hidden) + //$('#manageCategory form input[name=label]').val( // category label + // (id) ? category_record(id).label : "" + //); + //prepare_parents_element(id); // prepare <select> for parents + }) + + + /** 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'); } + } + + + + + + // When form is submited + // ------------------------------------------------------------------------- + + $('#manageCategory form').submit( event => { + event.preventDefault(); + + // prepare data to POST + var data = { + id: parseInt($('#manageCategory form input[name=id]').val()), + label: $('#manageCategory form input[name=label]').val(), + parent_id: parseInt($('#parent_id').select2('data')[0].id) + }; + + // select action url (add or update) + var request = '/admin/api/categories/' + ((data.id == 0) ? 'add' : 'update'); + + // send POST request + $.post(request, data) + .done(function( data ) { + + $('#manageCategory').modal('hide'); // when done, close modal + + init_table(); // reload table of categories + }); + + }); + +}); diff --git a/public/assets/js/admin/panel.js b/public/assets/js/admin/panel.js new file mode 100644 index 0000000..24b465e --- /dev/null +++ b/public/assets/js/admin/panel.js @@ -0,0 +1 @@ +console.log('Admin Panel!');
\ No newline at end of file diff --git a/public/assets/js/admin/privileges.js b/public/assets/js/admin/privileges.js new file mode 100644 index 0000000..e4355c5 --- /dev/null +++ b/public/assets/js/admin/privileges.js @@ -0,0 +1,291 @@ +// Globals +// ----------------------------------------------------------------------------- +var privileges = []; +var table; + + + +// Supplamentary functions +// ----------------------------------------------------------------------------- + +// array.indexOf polyfill +// --- -- -- - - - +if (!Array.prototype.indexOf) +{ + Array.prototype.indexOf = function(elt /*, from*/) + { + var len = this.length >>> 0; + + var from = Number(arguments[1]) || 0; + from = (from < 0) + ? Math.ceil(from) + : Math.floor(from); + if (from < 0) + from += len; + + for (; from < len; from++) + { + if (from in this && + this[from] === elt) + return from; + } + return -1; + }; +} + +/* +// function for sorting an array by 'breadcrumb' attribute +// --- -- -- - - - +function compare_breadcrumb (a,b) { + if ( a.breadcrumb < b.breadcrumb ) { return -1; } + if ( a.breadcrumb > b.breadcrumb ) { return 1; } + return 0; +} +*/ + +// return category record by category-id +// --- -- -- - - - +function privilege_record(id) { + var record = 0; + privileges.forEach(el => { if (el.id == id) record = el; }); + + return record; +} + +/* +// return children of parent +// --- -- -- - - - +function childs_of_parents(list) { + // if 'list' is integer then make it an one-item list + parents = (Number.isInteger(list)) ? [ list ] : list; + + var childs = []; + + if (parents.length == 0) { return []; } + + parents.forEach( pid => { + categories.forEach( el => { if (el.parent == pid) childs.push(el.id)}); + }); + + var grandchilds = childs_of_parents(childs); + + return childs.concat(grandchilds); +} +*/ + +// create actions html (edit and delete buttons) +// --- -- -- - - - +function create_actions_html(id) { + + var del, edit; + + del = ""; + + edit = [ + '<button type="button" class="btn btn-sm btn-warning"', + 'data-bs-toggle="modal" data-bs-target="#managePrivileges"', + 'data-id="'+ id +'">', + '<i class="fas fa-pencil-alt"></i>', + '</button>' + ].join('\n'); + + return edit +' '+ del; +} + + + +$(document).ready(function() { + + // When document is ready + // ------------------------------------------------------------------------- + + + /** init selec2 element (used to specify parent-category) + * --- -- -- - - - + */ + $("#included_id").select2({ + placeholder: 'Περιλαμβάνει τα δικαιώματα της', + dropdownParent: $('#managePrivileges'), + width: '100%', + // dropdownParent: "#manageCategory" // ref: https://github.com/select2/select2-bootstrap-theme/issues/41 + }); + + + /** init_table + * --- -- -- - - - + * get categories + * constuct categories array + * render dataTable + */ + function init_table() { + + // get all categories from server (ajax) + $.getJSON( "/admin/api/privileges") + .done(function( json ) { + // then ... + + // reset categories + privileges.length = 0; + + // construct (new) categories data + Object.keys(json).forEach( key => { + el = json[key]; + privileges.push({ + id: el.id, + label: el.label, + includes: ((el.includes == null) ? [] : el.includes), + included_id: ((el.included_id == null) ? 0 : el.included_id), + actions: create_actions_html(el.id) + }); + }); + + // sort by breadcrumb + // categories.sort( compare_breadcrumb ); + + // destroy previous datatable table instances + $('#dt-privileges').dataTable().fnClearTable(); + $('#dt-privileges').dataTable().fnDestroy(); + + // (re-)create categories datatable + table = $('#dt-privileges').DataTable({ + language: { url: '/libs/DataTables/localization/Greek.json' }, + data: privileges, + ordering: false, + columns: [ + { data: 'id' }, + { data: 'label' }, + { data: 'includes' }, + { data: 'actions' } + ] + }); + + }) + .fail(function( jqxhr, textStatus, error ) { + console.log( "Request Failed (" + error +")" ); + }); + + } + init_table(); // init table on first run + + + + /** When modal show-up + * ------------------------------------------------------------------------- + * ... prepare the manage-category form + * ... update select2 with possible parents + */ + $('#managePrivileges').on('show.bs.modal', event => { + var button = $(event.relatedTarget); // Button that triggered the modal + var id = parseInt( button.data('id') ); // category_id from data-id + // var modal = $(this); // modal object + + // update modal literature + // --- + $('#managePrivileges .modal-title').html( // modal title + (id) ? 'Ενημέρωση Επιπέδου Πρόσβασης' : 'Δημιουργία Επιπέδου Πρόσβασης' + ); + $('#managePrivileges form input[name=id]').val( id ); // category id (hidden) + $('#managePrivileges form input[name=label]').val( // category label + (id) ? privilege_record(id).label : "" + ); + prepare_included_element(id); // prepare <select> for parents + }) + + /** prepare_included_element() + * --- + * @param id (int) : id of selected privilege + * -> Get all posible included privileges + * -> call set_included_options() to render the options + */ + function prepare_included_element(id) { + var may_included = []; + + if (id) { // get all possible parents + + // TODO: + // specify forbiden privilege IDs for being included + + // filter privileges adn prepare possible included privileges + privileges.forEach( el => { + if (id != el.id) { + may_included.push({ + id: el.id, + label: el.label + }); + } + }); + console.log(may_included); + set_included_options(may_included, privilege_record(id).included_id); + + + } else { // new record; all categories are possible parrents + privileges.forEach( el => { + may_included.push({ + id: el.id, + label: el.label, + }); + }) + set_included_options(may_included, -1); + } + } + + /** set_included_options() + * -> create options for parents <select> + * -> choose selected parent + * @param may_included (array): array of possible included privileges + * @param selected (int): selected included privilege + */ + function set_included_options(may_included, selected) { + // sort parents by breadcrumb + // parents.sort( compare_breadcrumb ); + + // remove any olded options from select2 + $('#included_id option').each(function() { $(this).remove(); }); + + // first option is the root category + $('#included_id').append('<option value="0">— κανένα —</option>') + + // add all other parent options + may_included.forEach( i => { + $('#included_id').append(`<option value="${i.id}">${i.label}</option>`); + }); + + if (selected == -1) { // new category; remove any parent pre-selection + $('#included_id').val(0).trigger('change'); + + } else { // category exists; choose selected parent + $('#included_id').val(selected).trigger('change'); + } + } + + + + + // When form is submited + // ------------------------------------------------------------------------- + + $('#manageCategory form').submit( event => { + event.preventDefault(); + + // prepare data to POST + var data = { + id: parseInt($('#manageCategory form input[name=id]').val()), + label: $('#manageCategory form input[name=label]').val(), + parent_id: parseInt($('#parent_id').select2('data')[0].id) + }; + + // select action url (add or update) + var request = '/admin/api/category/' + ((data.id == 0) ? 'add' : 'update'); + + // send POST request + $.post(request, data) + .done(function( data ) { + + $('#manageCategory').modal('hide'); // when done, close modal + + init_table(); // reload table of categories + }); + + }); + +}); |
