summaryrefslogtreecommitdiff
path: root/public/assets/js/admin
diff options
context:
space:
mode:
authorGeorge Halkiadakis <gchalkiadakis@sklavenitis.co.gr>2023-04-27 03:47:30 +0300
committerGeorge Halkiadakis <gchalkiadakis@sklavenitis.co.gr>2023-04-27 03:47:30 +0300
commit059e0d95d0c28bc5060e87e146eaf7411f51bf90 (patch)
tree7c21ac432f16dde2329a0d5954d70711eceb48e6 /public/assets/js/admin
parent26cd8ee99659ef1926c96a049c93645ffc9b169d (diff)
downloadgyraf1gov-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.js288
-rw-r--r--public/assets/js/admin/edit_lesson.js600
-rw-r--r--public/assets/js/admin/edit_page.js382
-rw-r--r--public/assets/js/admin/files.js302
-rw-r--r--public/assets/js/admin/lessons.js364
-rw-r--r--public/assets/js/admin/pages.js240
-rw-r--r--public/assets/js/admin/panel.js1
-rw-r--r--public/assets/js/admin/privileges.js291
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
+ });
+
+ });
+
+});