From 73d04164e2a6ed23dde418c667336908bcadb2d8 Mon Sep 17 00:00:00 2001 From: redrun Date: Sun, 8 Dec 2024 12:03:17 -0600 Subject: [PATCH 1/5] Author Manager: remove author image URL column This feature is unused, and it makes Author Manager's table too wide. --- application/views/admin/author_manager/index.php | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/application/views/admin/author_manager/index.php b/application/views/admin/author_manager/index.php index 257a5faf..81adb006 100644 --- a/application/views/admin/author_manager/index.php +++ b/application/views/admin/author_manager/index.php @@ -31,7 +31,7 @@ Status Id First name Last name Pseudonyms Url Blurb DOB DOD Confirm - Link to Wiki Image URL + Link to Wiki @@ -96,10 +96,7 @@ ?> - - - - image_url;?> + From 155dfc730b8c0806a974e1462c0960184b895a8c Mon Sep 17 00:00:00 2001 From: redrun Date: Fri, 23 May 2025 22:59:00 -0500 Subject: [PATCH 2/5] Author Manager: load fewer records by default This commit adds some navigation options, rather than loading all author records at once (at least by default). It does NOT add real pagination, which is evidently something that our dataTables library supports[1]. From the user side: 1. The client-side control (and JS code) for toggling between unconfirmed authors and *all* authors has been replaced with a pair of navigation buttons. 'unconfirmed' is the default, and is _faster_. 2. dataTables will now hide its client-side pagination and search controls, if there are 10 or fewer authors in the navigation selection. 3. Authors can be navigated individually by name, using the new, auto-completing search box (based on the one in Section Compiler). [1]: https://datatables.net/manual/server-side ...though, at version 1.9.2, we're officially in legacy-land. --- application/config/routes.php | 3 + .../controllers/admin/Author_manager.php | 21 +++++-- .../views/admin/author_manager/index.php | 18 +++--- public_html/js/admin/author_manager/index.js | 59 +++++++++---------- 4 files changed, 60 insertions(+), 41 deletions(-) diff --git a/application/config/routes.php b/application/config/routes.php index b9f322e7..c1b311b3 100644 --- a/application/config/routes.php +++ b/application/config/routes.php @@ -80,6 +80,9 @@ $route['validator/(:num)'] = 'private/validator/index/$1'; $route['validator/adjust_file_volume/(:num)'] = 'private/validator/adjust_file_volume/$1'; +$route['admin/author_manager/(:num)'] = 'admin/author_manager/index/id/$1'; +$route['admin/author_manager/(all|unconfirmed)'] = 'admin/author_manager/index/$1'; + $route['volunteers'] = 'private/volunteers/index'; $route['volunteers/(:any)'] = 'private/volunteers/index/$1'; diff --git a/application/controllers/admin/Author_manager.php b/application/controllers/admin/Author_manager.php index 7a96973d..8d570ef7 100644 --- a/application/controllers/admin/Author_manager.php +++ b/application/controllers/admin/Author_manager.php @@ -16,17 +16,30 @@ public function __construct() $this->template->add_js('js/libs/jquery.jeditable.js'); } - public function index() + public function index($route = 'unconfirmed', $id = 0) { - ini_set('memory_limit', '-1'); //we need to see about chunking this - $this->data['menu_header'] = $this->load->view('private/common/menu_header', $this->data, TRUE); $this->data['author_blurb_modal'] = $this->load->view('admin/author_manager/author_blurb_modal', $this->data, TRUE); $this->data['author_projects_modal'] = $this->load->view('admin/author_manager/author_projects_modal', $this->data, TRUE); $this->data['author_pseudonyms_modal'] = $this->load->view('admin/author_manager/author_pseudonyms_modal', $this->data, TRUE); $this->data['author_new_modal'] = $this->load->view('admin/author_manager/author_new_modal', $this->data, TRUE); - $this->data['authors'] = $this->author_model->order_by('id', 'asc')->get_many_by(array('linked_to' => '0')); //limit(100)-> + if ($route == 'unconfirmed') + { + // Default: view unconfirmed authors + $this->data['authors'] = $this->author_model->order_by('id', 'asc')->get_many_by(array('linked_to' => '0', 'confirmed' => '0')); + } + elseif ($route == 'id') + { + // Individual: view author by ID + $this->data['authors'] = array($this->author_model->get($id)); + } + elseif ($route == 'all') + { + // Old way: view all non-duplicate authors (very slow!) + ini_set('memory_limit', '-1'); // Still a hack + $this->data['authors'] = $this->author_model->order_by('id', 'asc')->get_many_by(array('linked_to' => '0')); + } $this->insertMethodCSS(); $this->insertMethodJS(); diff --git a/application/views/admin/author_manager/index.php b/application/views/admin/author_manager/index.php index 81adb006..ed4fa291 100644 --- a/application/views/admin/author_manager/index.php +++ b/application/views/admin/author_manager/index.php @@ -8,6 +8,11 @@ white-space: nowrap; overflow: hidden; } +/* Tweak dataTables "Show X entries" control, so that it lines up with other buttons */ +.dataTables_length { + float: none; + display: inline; +} @@ -16,13 +21,12 @@

Author Manager

-
- Show only unconfirmed authors: - - Show all authors: - - - +
+ Load unconfirmed + Load all + + +
diff --git a/public_html/js/admin/author_manager/index.js b/public_html/js/admin/author_manager/index.js index a47501c5..c48b0ceb 100644 --- a/public_html/js/admin/author_manager/index.js +++ b/public_html/js/admin/author_manager/index.js @@ -24,8 +24,10 @@ $(document).ready(function() { bind_edit(); + var table_size = $('#authors_table').find('tr').length; + $.extend( $.fn.dataTable.defaults, { - "bFilter": true, + "bFilter": table_size > 10 + 1, // Header row counts! "bPaginate" : true, "bInfo" : true, "sPaginationType": "full_numbers", @@ -39,29 +41,6 @@ $(document).ready(function() { var oTable = $('#authors_table').dataTable(); oTable.fnSort([[1, 'asc']]); - - - //default -only unconfirmed - // need to make this check which radio button is checked - var checked_radio = $('input[class=status_group]:checked').val(); - apply_filter(checked_radio); - - $('.status_group').live('click', function(){ - apply_filter( $(this).val()); - }) ; - - function apply_filter(filter_value) - { - if (filter_value == '1') - { - oTable.fnFilter( 0, 0 ); - } - else - { - oTable.fnFilter('', 0); - //oTable.fnFilter( '' ); - } - } function bind_edit() { @@ -126,11 +105,6 @@ $(document).ready(function() { var row_id = oTable.fnGetPosition(row); update_author_status(value, row_id); - - //the one from above was static - var sub_checked_radio = $('input[class=status_group]:checked').val(); - apply_filter(sub_checked_radio); - }, }); @@ -336,4 +310,29 @@ $(document).ready(function() { }); -}); \ No newline at end of file +}); + + +// Autocomplete the "Load author by name" search + +function autocomplete_assign_vars(item) +{ + var name_field; + if (item.username == undefined) { + name_field = author_string(item); + } else { + name_field = item.username; + } + + return { + label: name_field, + value: name_field, + source_id: item.id, + source_name: name_field, + } +} + +function autocomplete_assign_elements(search_area, ui) +{ + window.location.href = CI_ROOT + 'admin/author_manager/' + ui.item.source_id +} \ No newline at end of file From 100c32775b9c56e5c65e2bdfeccfc1b47a69e9a3 Mon Sep 17 00:00:00 2001 From: redrun Date: Sat, 24 May 2025 01:27:47 -0500 Subject: [PATCH 3/5] Add 'Edit this page' button on authors in catalog Based on @peterjdann's handy addition to project pages. :+1: --- application/controllers/catalog/Author.php | 27 ++++++++++++++++++++++ application/views/catalog/author.php | 7 ++++-- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/application/controllers/catalog/Author.php b/application/controllers/catalog/Author.php index 1204bb4c..12ba7eed 100644 --- a/application/controllers/catalog/Author.php +++ b/application/controllers/catalog/Author.php @@ -17,6 +17,7 @@ public function index($author_id) $this->load->model('author_model'); $this->data['author'] = $this->author_model->get($author_id); + $this->data['edit_link'] = $this->_get_edit_link($author_id); $this->data['search_category'] = 'author'; @@ -143,4 +144,30 @@ function _get_all_author($author_id, $offset = 0, $limit = 1000000, $search_orde return $projects; } + + function _get_edit_link($author_id) + { + $link = ''; + $auth_checker = new Librivox_auth(); + + if (empty($author_id)) + { + return $link; + } + + $user_id = $auth_checker->get_user_id(); + if ($user_id < 1) { + return $link; + } + + //check permissions + $allowed_groups = array(PERMISSIONS_ADMIN, PERMISSIONS_MCS); + if ($auth_checker->has_permission($allowed_groups, $user_id)) + { + $link = base_url() . 'admin/author_manager/' . $author_id ; + return $link; + } + + return $link; + } } diff --git a/application/views/catalog/author.php b/application/views/catalog/author.php index bb055843..b7bfa031 100644 --- a/application/views/catalog/author.php +++ b/application/views/catalog/author.php @@ -15,13 +15,16 @@

blurb ?>

-

External Links

+

Links

author_url)) { echo '

' . format_author($author, FMT_AUTH_WIKI) . '

'; } - + if (!empty($edit_link)) + { + echo '

Edit this page

'; + } ?>
From e77cecc8b4b76adfbdfa106455d57e8ce6fded4e Mon Sep 17 00:00:00 2001 From: redrun Date: Sat, 24 May 2025 12:27:14 -0500 Subject: [PATCH 4/5] Author Manager: authors and translators by project Navigate Author Manager by project ID, showing all authors and translators assigned to the project (or to its individual sections, if it's a collection/compilation project). For now, this is only linked from the Projects search view. --- application/config/routes.php | 1 + .../controllers/admin/Author_manager.php | 19 +++++++++++++++++++ application/controllers/private/Projects.php | 2 ++ application/models/Project_model.php | 16 ++++++++++++++-- application/views/private/projects/index.php | 7 ++++++- 5 files changed, 42 insertions(+), 3 deletions(-) diff --git a/application/config/routes.php b/application/config/routes.php index c1b311b3..17f50510 100644 --- a/application/config/routes.php +++ b/application/config/routes.php @@ -81,6 +81,7 @@ $route['validator/adjust_file_volume/(:num)'] = 'private/validator/adjust_file_volume/$1'; $route['admin/author_manager/(:num)'] = 'admin/author_manager/index/id/$1'; +$route['admin/author_manager/(project)/(:num)'] = 'admin/author_manager/index/$1/$2'; $route['admin/author_manager/(all|unconfirmed)'] = 'admin/author_manager/index/$1'; $route['volunteers'] = 'private/volunteers/index'; diff --git a/application/controllers/admin/Author_manager.php b/application/controllers/admin/Author_manager.php index 8d570ef7..f54505d0 100644 --- a/application/controllers/admin/Author_manager.php +++ b/application/controllers/admin/Author_manager.php @@ -40,6 +40,25 @@ public function index($route = 'unconfirmed', $id = 0) ini_set('memory_limit', '-1'); // Still a hack $this->data['authors'] = $this->author_model->order_by('id', 'asc')->get_many_by(array('linked_to' => '0')); } + elseif ($route == 'project') + { + // Authors and translators by project ID + $this->data['authors'] = array(); + $results_to_cast = array(); + $this->load->model('project_model'); + + $project_authors = $this->project_model->get_authors_by_project($id, 'author', include_sections: true); + if ($project_authors) $results_to_cast = array_merge($results_to_cast, $project_authors); + + $project_translators = $this->project_model->get_authors_by_project($id, 'translator'); + if ($project_translators) $results_to_cast = array_merge($results_to_cast, $project_translators); + + // Hack: get_authors_by_project returns an array of *arrays*. + // We need to cast them as objects, to match types with the db->get*() results + foreach ($results_to_cast as $person) { + $this->data['authors'][] = (object) $person; + } + } $this->insertMethodCSS(); $this->insertMethodJS(); diff --git a/application/controllers/private/Projects.php b/application/controllers/private/Projects.php index e0f5c8c3..6ea2ade3 100644 --- a/application/controllers/private/Projects.php +++ b/application/controllers/private/Projects.php @@ -30,10 +30,12 @@ public function index($user_projects = false) $this->data['project_statuses'] = $this->config->item('project_statuses'); $this->data['view_validator'] = false; + $this->data['view_author_manager'] = false; $allowed_groups = array(PERMISSIONS_ADMIN, PERMISSIONS_MCS); if ($this->librivox_auth->has_permission($allowed_groups, $this->data['user_id'])) { $this->data['view_validator'] = true; + $this->data['view_author_manager'] = true; } if (!empty($this->data['projects'])) diff --git a/application/models/Project_model.php b/application/models/Project_model.php index 99ea6d56..d6940624 100644 --- a/application/models/Project_model.php +++ b/application/models/Project_model.php @@ -482,7 +482,7 @@ public function search($where) } - public function get_authors_by_project($project_id, $type= 'author') + public function get_authors_by_project($project_id, $type= 'author', $include_sections = false) { $sql = ' SELECT a.* @@ -490,8 +490,20 @@ public function get_authors_by_project($project_id, $type= 'author') JOIN authors a ON (pa.author_id = a.id) WHERE pa.project_id = ? AND pa.type = ? '; + $bind = array($project_id, $type); - $query = $this->db->query($sql, array($project_id, $type)); + if ($include_sections and $this->get($project_id)->is_compilation) { + $sql .= ' + UNION + SELECT a.* + FROM sections s + JOIN authors a ON (s.author_id = a.id) + WHERE s.project_id = ?'; + + $bind[] = $project_id; + } + + $query = $this->db->query($sql, $bind); if ($query->num_rows() > 0) return $query->result_array(); diff --git a/application/views/private/projects/index.php b/application/views/private/projects/index.php index f953fa69..4b8864d4 100644 --- a/application/views/private/projects/index.php +++ b/application/views/private/projects/index.php @@ -43,7 +43,12 @@ title ?> - author ?> + + + + + author ?> + status] ?> link link From 5e3b8149bbbdc56c32df8857fabb34b76c133bc7 Mon Sep 17 00:00:00 2001 From: redrun Date: Sat, 24 May 2025 11:58:36 -0500 Subject: [PATCH 5/5] Author Manager: safer/nicer feedback messages Added some try/catch logic, so the application doesn't hang when the server response is malformed or lost (see: HTML warnings that precede the expected JSON objects, or timeouts in certain AJAX requests). Also, we can use the pretty message pattern that the other pages use, instead of a jarring fallback to `alert()`. --- .../controllers/admin/Author_manager.php | 4 ++-- .../views/admin/author_manager/index.php | 2 ++ public_html/js/admin/author_manager/index.js | 19 ++++++++----------- public_html/js/common/application.js | 18 ++++++++++++++++++ 4 files changed, 30 insertions(+), 13 deletions(-) diff --git a/application/controllers/admin/Author_manager.php b/application/controllers/admin/Author_manager.php index f54505d0..351ca736 100644 --- a/application/controllers/admin/Author_manager.php +++ b/application/controllers/admin/Author_manager.php @@ -174,7 +174,7 @@ public function delete_pseudonym() $message = 'Deleted'; } - $this->ajax_output(array('message' => $message), TRUE, FALSE); + $this->ajax_output(array('message' => $message), (bool)$fields['id'], FALSE); } public function add_new_author() @@ -191,7 +191,7 @@ public function add_new_author() $message = 'Added'; } - $this->ajax_output(array('message' => $message), TRUE, FALSE); + $this->ajax_output(array('message' => $message), (bool)$insert_id, FALSE); } //// ********* TESTING ************///// diff --git a/application/views/admin/author_manager/index.php b/application/views/admin/author_manager/index.php index ed4fa291..87572940 100644 --- a/application/views/admin/author_manager/index.php +++ b/application/views/admin/author_manager/index.php @@ -21,6 +21,8 @@

Author Manager

+ +
Load unconfirmed Load all diff --git a/public_html/js/admin/author_manager/index.js b/public_html/js/admin/author_manager/index.js index c48b0ceb..84abe257 100644 --- a/public_html/js/admin/author_manager/index.js +++ b/public_html/js/admin/author_manager/index.js @@ -122,11 +122,12 @@ $(document).ready(function() { type: 'post', data: $('#add_new_author_form').serialize(), complete: function(r){ - //var response_obj = jQuery.parseJSON(r.responseText); - - alert('You will need to refresh the page in order to see your newly added author. This can be slow, so we let you do it manually when you are ready.') $('#author_new_modal').modal('hide'); - + json_set_message( + 'response_message_authormanager', r.responseText, + 'Author added! Click "Load unconfirmed" or load the author by name to view.
Message from server: ', + 'The author may not have been added. Click "Load unconfirmed" or search by name to verify.
Message from server: ' + ); }, }); }); @@ -248,11 +249,9 @@ $(document).ready(function() { url: CI_ROOT + 'admin/author_manager/update_add_pseudonym', type: 'post', data: {'id' : id, 'author_id' : author_id, 'first_name': first_name, 'last_name': last_name }, - complete: function(r){ - var response_obj = jQuery.parseJSON(r.responseText); - + complete: function(r){ $('#author_pseudonyms_modal').modal('hide'); - alert(response_obj.data.message); + json_set_message('response_message_authormanager', r.responseText); }, }); @@ -268,10 +267,8 @@ $(document).ready(function() { type: 'post', data: {'id' : id }, complete: function(r){ - var response_obj = jQuery.parseJSON(r.responseText); - $('#author_pseudonyms_modal').modal('hide'); - alert(response_obj.data.message); + json_set_message('response_message_authormanager', r.responseText); }, }); diff --git a/public_html/js/common/application.js b/public_html/js/common/application.js index 682cbadb..7346f17d 100644 --- a/public_html/js/common/application.js +++ b/public_html/js/common/application.js @@ -56,6 +56,24 @@ function set_message(element, message, addClass) } +// safe messaging, given a (possibly faulty) JSON response from the server +function json_set_message(element, responseText, intro_success, intro_fail) +{ + var message, success; + try { + var response_obj = jQuery.parseJSON(responseText); + if (response_obj.status == "SUCCESS") { + message = (intro_success ? intro_success : '') + (response_obj.data.message ? response_obj.data.message : ''); + success = true; + } else { + message = (intro_fail ? intro_fail : '') + (response_obj.data.message ? response_obj.data.message : ''); + } + } catch { + message = (intro_fail ? intro_fail : '') + 'No message'; + } + return set_message(element, message, success ? 'alert-success' : 'alert-error') +} + //toggle forms $('.toggle_form_btn').live('click', function(){