diff options
author | Chaoyi Zha <summermontreal@gmail.com> | 2017-04-30 06:32:22 +0300 |
---|---|---|
committer | Chaoyi Zha <summermontreal@gmail.com> | 2017-04-30 06:32:22 +0300 |
commit | 6a53d0644bb0abc1bcbc821dbd6f29e82d918cfa (patch) | |
tree | 478b52790421159bb6c6da70cc8290689805a524 | |
parent | 21e465e149282004840d0e9fe9c1ad1b0b41974b (diff) |
Implement long link editing WIP; code refactoring
-rw-r--r-- | app/Http/Controllers/AdminPaginationController.php | 230 | ||||
-rw-r--r-- | app/Http/Controllers/AjaxController.php | 28 | ||||
-rw-r--r-- | app/Http/routes.php | 1 | ||||
-rw-r--r-- | public/css/admin.css | 4 | ||||
-rw-r--r-- | public/directives/editLongLinkModal.html | 18 | ||||
-rw-r--r-- | public/js/AdminCtrl.js | 84 | ||||
-rw-r--r-- | public/js/SetupCtrl.js | 2 | ||||
-rw-r--r-- | resources/views/admin.blade.php | 5 | ||||
-rw-r--r-- | resources/views/layouts/base.blade.php | 10 |
9 files changed, 257 insertions, 125 deletions
diff --git a/app/Http/Controllers/AdminPaginationController.php b/app/Http/Controllers/AdminPaginationController.php index 156bea4..78e290d 100644 --- a/app/Http/Controllers/AdminPaginationController.php +++ b/app/Http/Controllers/AdminPaginationController.php @@ -14,75 +14,126 @@ class AdminPaginationController extends Controller { * @return Response */ + /* Cell rendering functions */ + + public function renderLongUrlCell($link) { + return '<a target="_blank" title="' . e($link->long_url) . '" href="'. $link->long_url .'">' . str_limit($link->long_url, 50) . '</a> + <a class="btn btn-primary btn-xs edit-long-link-btn" ng-click="editLongLink(\'' . $link->short_url . '\', \'' . $link->long_url . '\')"><i class="fa fa-edit edit-link-icon"></i></a>'; + } + + public function renderClicksCell($link) { + if (env('SETTING_ADV_ANALYTICS')) { + return $link->clicks . ' <a target="_blank" class="stats-icon" href="/admin/stats/' . e($link->short_url) . '"> + <i class="fa fa-area-chart" aria-hidden="true"></i> + </a>'; + } + else { + return $link->clicks; + } + } + + public function renderDeleteUserCell($user) { + // Add "Delete" action button + $btn_class = ''; + if (session('username') === $user->username) { + $btn_class = 'disabled'; + } + return '<a ng-click="deleteUser($event, \''. $user->id .'\')" class="btn btn-sm btn-danger ' . $btn_class . '"> + Delete + </a>'; + } + + public function renderDeleteLinkCell($link) { + // Add "Delete" action button + return '<a ng-click="deleteLink($event, \'' . e($link->short_url) . '\')" + class="btn btn-sm btn-warning delete-link"> + Delete + </a>'; + } + + public function renderAdminApiActionCell($user) { + // Add "API Info" action button + return '<a class="activate-api-modal btn btn-sm btn-info" + ng-click="openAPIModal($event, \'' . e($user->username) . '\', \'' . $user->api_key . '\', \'' . $user->api_active . '\', \'' . e($user->api_quota) . '\', \'' . $user->id . '\')"> + API info + </a>'; + } + + public function renderToggleUserActiveCell($user) { + // Add user account active state toggle buttons + $btn_class = ''; + if (session('username') === $user->username) { + $btn_class = ' disabled'; + } + + if ($user->active) { + $active_text = 'Active'; + $btn_color_class = ' btn-success'; + } + else { + $active_text = 'Inactive'; + $btn_color_class = ' btn-danger'; + } + + return '<a class="btn btn-sm status-display' . $btn_color_class . $btn_class . '" ng-click="toggleUserActiveStatus($event, ' . $user->id . ')">' . $active_text . '</a>'; + } + + public function renderChangeUserRoleCell($user) { + // Add "change role" select box + // <select> field does not use Angular bindings + // because of an issue affecting fields with duplicate names. + + $select_role = '<select ng-init="changeUserRole.u' . $user->id . ' = \'' . e($user->role) . '\'" + ng-model="changeUserRole.u' . $user->id . '" ng-change="changeUserRole(changeUserRole.u' . $user->id . ', '.$user->id.')" + class="form-control"'; + + if (session('username') === $user->username) { + // Do not allow user to change own role + $select_role .= ' disabled'; + } + $select_role .= '>'; + + foreach (UserHelper::$USER_ROLES as $role_text => $role_val) { + // Iterate over each available role and output option + $select_role .= '<option value="' . e($role_val) . '"'; + + if ($user->role === $role_val) { + $select_role .= ' selected'; + } + + $select_role .= '>' . e($role_text) . '</option>'; + } + + $select_role .= '</select>'; + return $select_role; + } + + public function renderToggleLinkActiveCell($link) { + // Add "Disable/Enable" action buttons + $btn_class = 'btn-danger'; + $btn_text = 'Disable'; + + if ($link->is_disabled) { + $btn_class = 'btn-success'; + $btn_text = 'Enable'; + } + + return '<a ng-click="toggleLink($event, \'' . e($link->short_url) . '\')" class="btn btn-sm ' . $btn_class . '"> + ' . $btn_text . ' + </a>'; + } + + /* DataTables bindings */ + public function paginateAdminUsers(Request $request) { self::ensureAdmin(); $admin_users = User::select(['username', 'email', 'created_at', 'active', 'api_key', 'api_active', 'api_quota', 'role', 'id']); return Datatables::of($admin_users) - ->addColumn('api_action', function ($user) { - // Add "API Info" action button - return '<a class="activate-api-modal btn btn-sm btn-info" - ng-click="openAPIModal($event, \'' . e($user->username) . '\', \'' . $user->api_key . '\', \'' . $user->api_active . '\', \'' . e($user->api_quota) . '\', \'' . $user->id . '\')"> - API info - </a>'; - }) - ->addColumn('toggle_active', function ($user) { - // Add user account active state toggle buttons - $btn_class = ''; - if (session('username') == $user->username) { - $btn_class = ' disabled'; - } - - if ($user->active) { - $active_text = 'Active'; - $btn_color_class = ' btn-success'; - } - else { - $active_text = 'Inactive'; - $btn_color_class = ' btn-danger'; - } - - return '<a class="btn btn-sm status-display' . $btn_color_class . $btn_class . '" ng-click="toggleUserActiveStatus($event, ' . $user->id . ')">' . $active_text . '</a>'; - }) - ->addColumn('change_role', function ($user) { - // Add "change role" select box - // FIXME <select> field does not use Angular bindings - // because of an issue affecting fields with duplicate names. - - $select_role = '<select ng-init="changeUserRole.u' . $user->id . ' = \'' . e($user->role) . '\'" - ng-model="changeUserRole.u' . $user->id . '" ng-change="changeUserRole(changeUserRole.u' . $user->id . ', '.$user->id.')" - class="form-control"'; - - if (session('username') == $user->username) { - // Do not allow user to change own role - $select_role .= ' disabled'; - } - $select_role .= '>'; - - foreach (UserHelper::$USER_ROLES as $role_text => $role_val) { - // Iterate over each available role and output option - $select_role .= '<option value="' . e($role_val) . '"'; - - if ($user->role == $role_val) { - $select_role .= ' selected'; - } - - $select_role .= '>' . e($role_text) . '</option>'; - } - - $select_role .= '</select>'; - return $select_role; - }) - ->addColumn('delete', function ($user) { - // Add "Delete" action button - $btn_class = ''; - if (session('username') == $user->username) { - $btn_class = 'disabled'; - } - return '<a ng-click="deleteUser($event, \''. $user->id .'\')" class="btn btn-sm btn-danger ' . $btn_class . '"> - Delete - </a>'; - }) + ->addColumn('api_action', [$this, 'renderAdminApiActionCell']) + ->addColumn('toggle_active', [$this, 'renderToggleUserActiveCell']) + ->addColumn('change_role', [$this, 'renderChangeUserRoleCell']) + ->addColumn('delete', [$this, 'renderDeleteUserCell']) ->escapeColumns(['username', 'email']) ->make(true); } @@ -92,38 +143,10 @@ class AdminPaginationController extends Controller { $admin_links = Link::select(['short_url', 'long_url', 'clicks', 'created_at', 'creator', 'is_disabled']); return Datatables::of($admin_links) - ->addColumn('disable', function ($link) { - // Add "Disable/Enable" action buttons - $btn_class = 'btn-danger'; - $btn_text = 'Disable'; - - if ($link->is_disabled) { - $btn_class = 'btn-success'; - $btn_text = 'Enable'; - } - - return '<a ng-click="toggleLink($event, \'' . e($link->short_url) . '\')" class="btn btn-sm ' . $btn_class . '"> - ' . $btn_text . ' - </a>'; - }) - ->addColumn('delete', function ($link) { - // Add "Delete" action button - return '<a ng-click="deleteLink($event, \'' . e($link->short_url) . '\')" - class="btn btn-sm btn-warning delete-link"> - Delete - </a>'; - }) - ->editColumn('clicks', function ($link) { - if (env('SETTING_ADV_ANALYTICS')) { - return $link->clicks . ' <a target="_blank" class="stats-icon" href="/admin/stats/' . e($link->short_url) . '"> - <i class="fa fa-area-chart" aria-hidden="true"></i> - </a>'; - } - else { - return $link->clicks; - } - }) - ->editColumn('long_url', '<a target="_blank" title="{{ $long_url }}" href="{{ $long_url }}">{{ str_limit($long_url, 50) }}</a>') + ->addColumn('disable', [$this, 'renderToggleLinkActiveCell']) + ->addColumn('delete', [$this, 'renderDeleteLinkCell']) + ->editColumn('clicks', [$this, 'renderClicksCell']) + ->editColumn('long_url', [$this, 'renderLongUrlCell']) ->escapeColumns(['short_url', 'creator']) ->make(true); } @@ -133,20 +156,11 @@ class AdminPaginationController extends Controller { $username = session('username'); $user_links = Link::where('creator', $username) - ->select(['short_url', 'long_url', 'clicks', 'created_at']); + ->select(['id', 'short_url', 'long_url', 'clicks', 'created_at']); return Datatables::of($user_links) - ->editColumn('clicks', function ($link) { - if (env('SETTING_ADV_ANALYTICS')) { - return $link->clicks . ' <a target="_blank" class="stats-icon" href="/admin/stats/' . e($link->short_url) . '"> - <i class="fa fa-area-chart" aria-hidden="true"></i> - </a>'; - } - else { - return $link->clicks; - } - }) - ->editColumn('long_url', '<a target="_blank" title="{{ $long_url }}" href="{{ $long_url }}">{{ str_limit($long_url, 50) }}</a>') + ->editColumn('clicks', [$this, 'renderClicksCell']) + ->editColumn('long_url', [$this, 'renderLongUrlCell']) // TODO make sure users can't edit other people's links! ->escapeColumns(['short_url']) ->make(true); } diff --git a/app/Http/Controllers/AjaxController.php b/app/Http/Controllers/AjaxController.php index 0373118..b8f5a4e 100644 --- a/app/Http/Controllers/AjaxController.php +++ b/app/Http/Controllers/AjaxController.php @@ -225,4 +225,32 @@ class AjaxController extends Controller { return ($new_status ? "Enable" : "Disable"); } + + public function editLinkLongUrl(Request $request) { + /** + * If user is an admin, allow the user to edit the value of any link's long URL. + * Otherwise, only allow the user to edit their own links. + */ + + $link_ending = $request->input('link_ending'); + $link = LinkHelper::linkExists($link_ending); + + $new_long_url = $request->input('new_long_url'); // TODO check if valid + + $this->validate($request, [ + 'new_long_url' => 'required|url', + ]); + + if (!$link) { + abort(404, 'Link not found.'); + } + + if ($link->creator !== session('username')) { + self::ensureAdmin(); + } + + $link->long_url = $new_long_url; + $link->save(); + return "OK"; + } } diff --git a/app/Http/routes.php b/app/Http/routes.php index 15c4bfd..7982af5 100644 --- a/app/Http/routes.php +++ b/app/Http/routes.php @@ -55,6 +55,7 @@ $app->group(['prefix' => '/api/v2', 'namespace' => 'App\Http\Controllers'], func $app->post('admin/delete_user', ['as' => 'api_delete_user', 'uses' => 'AjaxController@deleteUser']); $app->post('admin/toggle_link', ['as' => 'api_toggle_link', 'uses' => 'AjaxController@toggleLink']); $app->post('admin/delete_link', ['as' => 'api_delete_link', 'uses' => 'AjaxController@deleteLink']); + $app->post('admin/edit_link_long_url', ['as' => 'api_edit_link_long_url', 'uses' => 'AjaxController@editLinkLongUrl']); $app->get('admin/get_admin_users', ['as' => 'api_get_admin_users', 'uses' => 'AdminPaginationController@paginateAdminUsers']); $app->get('admin/get_admin_links', ['as' => 'api_get_admin_links', 'uses' => 'AdminPaginationController@paginateAdminLinks']); diff --git a/public/css/admin.css b/public/css/admin.css index b718f20..2c9b51f 100644 --- a/public/css/admin.css +++ b/public/css/admin.css @@ -54,3 +54,7 @@ input.api-quota { a.new-user-add { margin-left: 0.5em } + +.edit-long-link-btn { + opacity: 0.45; +} diff --git a/public/directives/editLongLinkModal.html b/public/directives/editLongLinkModal.html new file mode 100644 index 0000000..92c7da4 --- /dev/null +++ b/public/directives/editLongLinkModal.html @@ -0,0 +1,18 @@ +<div class="modal fade" id="edit-long-link-{{ linkEnding }}" tabindex="-1" role="dialog"> + <div class="modal-dialog"> + <div class="modal-content"> + <div class="modal-header"> + <button type="button" class="close" data-dismiss="modal" aria-label="Close" ng-click="cleanModals()"> + <span aria-hidden="true">×</span> + </button> + <h4 class="modal-title">Long URL</h4> + </div> + <div class="modal-body"> + <input type="url" value="{{ oldLongLink }}" placeholder="Long URL..." class="form-control" /> + </div> + <div class="modal-footer"> + <button type="button" class="btn btn-default" data-dismiss="modal" ng-click="saveChanges()">Save Changes</button> + </div> + </div> <!-- /.modal-content --> + </div> <!-- /.modal-dialog --> +</div> <!-- /.modal --> diff --git a/public/js/AdminCtrl.js b/public/js/AdminCtrl.js index cd0c606..7c96a74 100644 --- a/public/js/AdminCtrl.js +++ b/public/js/AdminCtrl.js @@ -1,8 +1,37 @@ -polr.controller('AdminCtrl', function($scope, $compile) { +polr.directive('editLongLinkModal', function () { + return { + scope: { + oldLongLink: '=', + linkEnding: '=', + cleanModals: '=' + }, + templateUrl: '/directives/editLongLinkModal.html', + transclude: true, + controller: function ($scope, $element, $timeout) { + // TODO set a listener on close then delete! + + $scope.saveChanges = function () { + // Save long URL changes + apiCall('admin/edit_link_long_url', { + 'link_ending': $scope.linkEnding, + 'new_long_url': $element.find('input').val() + }, function(data) { + toastr.success('The link was updated.', 'Success') + $scope.cleanModals(); + }, function(err) { + toastr.error('The new URL format is not valid.', 'Error'); + }); + }; + }, + }; +}); + +polr.controller('AdminCtrl', function($scope, $compile, $timeout) { $scope.state = { showNewUserWell: false }; $scope.datatables = {}; + $scope.editLongLinkModals = []; $scope.syncHash = function() { var url = document.location.toString(); @@ -11,8 +40,18 @@ polr.controller('AdminCtrl', function($scope, $compile) { } }; + $scope.cleanModals = function() { + $timeout(function () { + $scope.editLongLinkModals.shift(); + console.log('cleaning modals!!'); + console.log($scope.editLongLinkModals); + }, 5000); + + $scope.reloadLinkTables(); + }; + // Initialise Datatables elements - $scope.initTables = function () { + $scope.initTables = function() { var datatables_config = { 'autoWidth': false, 'processing': true, @@ -70,6 +109,20 @@ polr.controller('AdminCtrl', function($scope, $compile) { }, datatables_config)); }; + $scope.reloadLinkTables = function () { + // Reload DataTables for affected tables + // without resetting page + if ('admin_links_table' in $scope.datatables) { + $scope.datatables['admin_links_table'].ajax.reload(null, false); + } + + $scope.datatables['user_links_table'].ajax.reload(null, false); + }; + + $scope.reloadUserTables = function () { + $scope.datatables['admin_users_table'].ajax.reload(null, false); + }; + // Append modals to Angular root $scope.appendModal = function(html, id) { id = esc_selector(id); @@ -86,13 +139,6 @@ polr.controller('AdminCtrl', function($scope, $compile) { }); }; - // Hide table rows - $scope.hideRow = function(el, msg) { - var row = el.parent().parent(); - toastr.success(msg, "Success"); - row.fadeOut('slow'); - }; - /* User Management */ @@ -161,7 +207,8 @@ polr.controller('AdminCtrl', function($scope, $compile) { apiCall('admin/delete_user', { 'user_id': user_id, }, function(new_status) { - $scope.hideRow(el, 'User successfully deleted.'); + toastr.success('User successfully deleted.', 'Success'); + $scope.reloadUserTables(); }); }; @@ -253,7 +300,8 @@ polr.controller('AdminCtrl', function($scope, $compile) { apiCall('admin/delete_link', { 'link_ending': link_ending, }, function(new_status) { - $scope.hideRow(el, 'Link successfully deleted.'); + toastr.success('Link successfully deleted.', 'Success'); + $scope.reloadLinkTables(); }); }; @@ -278,6 +326,20 @@ polr.controller('AdminCtrl', function($scope, $compile) { }); }; + // Edit links' long_url + $scope.editLongLink = function(link_ending, old_long_link) { + $scope.editLongLinkModals.push({ + linkEnding: link_ending, + oldLongLink: old_long_link, + }); + + $timeout(function () { + console.log(link_ending); + $('#edit-long-link-' + link_ending).modal('show'); + // XXX refresh table when done + }); + } + /* Initialisation */ diff --git a/public/js/SetupCtrl.js b/public/js/SetupCtrl.js index c5fced4..61caa50 100644 --- a/public/js/SetupCtrl.js +++ b/public/js/SetupCtrl.js @@ -6,7 +6,7 @@ polr.directive('setupTooltip', function() { replace: true, template: '<button data-content="{{ content }}" type="button" class="btn btn-xs btn-default setup-qmark" data-toggle="popover">?</button>' } -}) +}); polr.controller('SetupCtrl', function($scope) { $scope.init = function () { diff --git a/resources/views/admin.blade.php b/resources/views/admin.blade.php index 1940187..3850724 100644 --- a/resources/views/admin.blade.php +++ b/resources/views/admin.blade.php @@ -123,6 +123,11 @@ @endif </div> </div> + + <div class="angular-modals"> + <edit-long-link-modal ng-repeat="modal in editLongLinkModals" link-ending="modal.linkEnding" + old-long-link="modal.oldLongLink" clean-modals="cleanModals"></edit-long-link-modal> + </div> </div> diff --git a/resources/views/layouts/base.blade.php b/resources/views/layouts/base.blade.php index 85d0ea2..19f910b 100644 --- a/resources/views/layouts/base.blade.php +++ b/resources/views/layouts/base.blade.php @@ -50,13 +50,13 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. </div> </div> - {{-- Load header JavaScript --}} - <script src='/js/constants.js'></script> + {{-- Load JavaScript dependencies --}} + <script src="/js/constants.js"></script> <script src="/js/jquery-1.11.3.min.js"></script> <script src="/js/bootstrap.min.js"></script> - <script src='/js/angular.min.js'></script> - <script src='/js/toastr.min.js'></script> - <script src='/js/base.js'></script> + <script src="/js/angular.min.js"></script> + <script src="/js/toastr.min.js"></script> + <script src="/js/base.js"></script> <script> @if (Session::has('info')) |