Page Attachments - Improved UI, Now initially complete
Closes #62
Showing
11 changed files
with
212 additions
and
59 deletions
| ... | @@ -3,13 +3,11 @@ | ... | @@ -3,13 +3,11 @@ |
| 3 | namespace BookStack\Http\Controllers; | 3 | namespace BookStack\Http\Controllers; |
| 4 | 4 | ||
| 5 | use BookStack\Ownable; | 5 | use BookStack\Ownable; |
| 6 | -use HttpRequestException; | ||
| 7 | use Illuminate\Foundation\Bus\DispatchesJobs; | 6 | use Illuminate\Foundation\Bus\DispatchesJobs; |
| 8 | use Illuminate\Http\Exception\HttpResponseException; | 7 | use Illuminate\Http\Exception\HttpResponseException; |
| 8 | +use Illuminate\Http\Request; | ||
| 9 | use Illuminate\Routing\Controller as BaseController; | 9 | use Illuminate\Routing\Controller as BaseController; |
| 10 | use Illuminate\Foundation\Validation\ValidatesRequests; | 10 | use Illuminate\Foundation\Validation\ValidatesRequests; |
| 11 | -use Illuminate\Support\Facades\Auth; | ||
| 12 | -use Illuminate\Support\Facades\Session; | ||
| 13 | use BookStack\User; | 11 | use BookStack\User; |
| 14 | 12 | ||
| 15 | abstract class Controller extends BaseController | 13 | abstract class Controller extends BaseController |
| ... | @@ -130,4 +128,22 @@ abstract class Controller extends BaseController | ... | @@ -130,4 +128,22 @@ abstract class Controller extends BaseController |
| 130 | return response()->json(['message' => $messageText], $statusCode); | 128 | return response()->json(['message' => $messageText], $statusCode); |
| 131 | } | 129 | } |
| 132 | 130 | ||
| 131 | + /** | ||
| 132 | + * Create the response for when a request fails validation. | ||
| 133 | + * | ||
| 134 | + * @param \Illuminate\Http\Request $request | ||
| 135 | + * @param array $errors | ||
| 136 | + * @return \Symfony\Component\HttpFoundation\Response | ||
| 137 | + */ | ||
| 138 | + protected function buildFailedValidationResponse(Request $request, array $errors) | ||
| 139 | + { | ||
| 140 | + if ($request->expectsJson()) { | ||
| 141 | + return response()->json(['validation' => $errors], 422); | ||
| 142 | + } | ||
| 143 | + | ||
| 144 | + return redirect()->to($this->getRedirectUrl()) | ||
| 145 | + ->withInput($request->input()) | ||
| 146 | + ->withErrors($errors, $this->errorBag()); | ||
| 147 | + } | ||
| 148 | + | ||
| 133 | } | 149 | } | ... | ... |
| ... | @@ -101,8 +101,8 @@ class FileController extends Controller | ... | @@ -101,8 +101,8 @@ class FileController extends Controller |
| 101 | { | 101 | { |
| 102 | $this->validate($request, [ | 102 | $this->validate($request, [ |
| 103 | 'uploaded_to' => 'required|integer|exists:pages,id', | 103 | 'uploaded_to' => 'required|integer|exists:pages,id', |
| 104 | - 'name' => 'string|max:255', | 104 | + 'name' => 'required|string|min:1|max:255', |
| 105 | - 'link' => 'url' | 105 | + 'link' => 'url|min:1|max:255' |
| 106 | ]); | 106 | ]); |
| 107 | 107 | ||
| 108 | $pageId = $request->get('uploaded_to'); | 108 | $pageId = $request->get('uploaded_to'); |
| ... | @@ -129,8 +129,8 @@ class FileController extends Controller | ... | @@ -129,8 +129,8 @@ class FileController extends Controller |
| 129 | { | 129 | { |
| 130 | $this->validate($request, [ | 130 | $this->validate($request, [ |
| 131 | 'uploaded_to' => 'required|integer|exists:pages,id', | 131 | 'uploaded_to' => 'required|integer|exists:pages,id', |
| 132 | - 'name' => 'string|max:255', | 132 | + 'name' => 'required|string|min:1|max:255', |
| 133 | - 'link' => 'url|max:255' | 133 | + 'link' => 'required|url|min:1|max:255' |
| 134 | ]); | 134 | ]); |
| 135 | 135 | ||
| 136 | $pageId = $request->get('uploaded_to'); | 136 | $pageId = $request->get('uploaded_to'); | ... | ... |
| ... | @@ -538,6 +538,10 @@ module.exports = function (ngApp, events) { | ... | @@ -538,6 +538,10 @@ module.exports = function (ngApp, events) { |
| 538 | $scope.files = []; | 538 | $scope.files = []; |
| 539 | $scope.editFile = false; | 539 | $scope.editFile = false; |
| 540 | $scope.file = getCleanFile(); | 540 | $scope.file = getCleanFile(); |
| 541 | + $scope.errors = { | ||
| 542 | + link: {}, | ||
| 543 | + edit: {} | ||
| 544 | + }; | ||
| 541 | 545 | ||
| 542 | function getCleanFile() { | 546 | function getCleanFile() { |
| 543 | return { | 547 | return { |
| ... | @@ -567,7 +571,7 @@ module.exports = function (ngApp, events) { | ... | @@ -567,7 +571,7 @@ module.exports = function (ngApp, events) { |
| 567 | currentOrder = newOrder; | 571 | currentOrder = newOrder; |
| 568 | $http.put(`/files/sort/page/${pageId}`, {files: $scope.files}).then(resp => { | 572 | $http.put(`/files/sort/page/${pageId}`, {files: $scope.files}).then(resp => { |
| 569 | events.emit('success', resp.data.message); | 573 | events.emit('success', resp.data.message); |
| 570 | - }, checkError); | 574 | + }, checkError('sort')); |
| 571 | } | 575 | } |
| 572 | 576 | ||
| 573 | /** | 577 | /** |
| ... | @@ -587,7 +591,7 @@ module.exports = function (ngApp, events) { | ... | @@ -587,7 +591,7 @@ module.exports = function (ngApp, events) { |
| 587 | $http.get(url).then(resp => { | 591 | $http.get(url).then(resp => { |
| 588 | $scope.files = resp.data; | 592 | $scope.files = resp.data; |
| 589 | currentOrder = resp.data.map(file => {return file.id}).join(':'); | 593 | currentOrder = resp.data.map(file => {return file.id}).join(':'); |
| 590 | - }, checkError); | 594 | + }, checkError('get')); |
| 591 | } | 595 | } |
| 592 | getFiles(); | 596 | getFiles(); |
| 593 | 597 | ||
| ... | @@ -599,7 +603,7 @@ module.exports = function (ngApp, events) { | ... | @@ -599,7 +603,7 @@ module.exports = function (ngApp, events) { |
| 599 | */ | 603 | */ |
| 600 | $scope.uploadSuccess = function (file, data) { | 604 | $scope.uploadSuccess = function (file, data) { |
| 601 | $scope.$apply(() => { | 605 | $scope.$apply(() => { |
| 602 | - $scope.files.unshift(data); | 606 | + $scope.files.push(data); |
| 603 | }); | 607 | }); |
| 604 | events.emit('success', 'File uploaded'); | 608 | events.emit('success', 'File uploaded'); |
| 605 | }; | 609 | }; |
| ... | @@ -612,10 +616,10 @@ module.exports = function (ngApp, events) { | ... | @@ -612,10 +616,10 @@ module.exports = function (ngApp, events) { |
| 612 | $scope.uploadSuccessUpdate = function (file, data) { | 616 | $scope.uploadSuccessUpdate = function (file, data) { |
| 613 | $scope.$apply(() => { | 617 | $scope.$apply(() => { |
| 614 | let search = filesIndexOf(data); | 618 | let search = filesIndexOf(data); |
| 615 | - if (search !== -1) $scope.files[search] = file; | 619 | + if (search !== -1) $scope.files[search] = data; |
| 616 | 620 | ||
| 617 | if ($scope.editFile) { | 621 | if ($scope.editFile) { |
| 618 | - $scope.editFile = data; | 622 | + $scope.editFile = angular.copy(data); |
| 619 | data.link = ''; | 623 | data.link = ''; |
| 620 | } | 624 | } |
| 621 | }); | 625 | }); |
| ... | @@ -627,10 +631,14 @@ module.exports = function (ngApp, events) { | ... | @@ -627,10 +631,14 @@ module.exports = function (ngApp, events) { |
| 627 | * @param file | 631 | * @param file |
| 628 | */ | 632 | */ |
| 629 | $scope.deleteFile = function(file) { | 633 | $scope.deleteFile = function(file) { |
| 634 | + if (!file.deleting) { | ||
| 635 | + file.deleting = true; | ||
| 636 | + return; | ||
| 637 | + } | ||
| 630 | $http.delete(`/files/${file.id}`).then(resp => { | 638 | $http.delete(`/files/${file.id}`).then(resp => { |
| 631 | events.emit('success', resp.data.message); | 639 | events.emit('success', resp.data.message); |
| 632 | $scope.files.splice($scope.files.indexOf(file), 1); | 640 | $scope.files.splice($scope.files.indexOf(file), 1); |
| 633 | - }, checkError); | 641 | + }, checkError('delete')); |
| 634 | }; | 642 | }; |
| 635 | 643 | ||
| 636 | /** | 644 | /** |
| ... | @@ -641,10 +649,10 @@ module.exports = function (ngApp, events) { | ... | @@ -641,10 +649,10 @@ module.exports = function (ngApp, events) { |
| 641 | $scope.attachLinkSubmit = function(file) { | 649 | $scope.attachLinkSubmit = function(file) { |
| 642 | file.uploaded_to = pageId; | 650 | file.uploaded_to = pageId; |
| 643 | $http.post('/files/link', file).then(resp => { | 651 | $http.post('/files/link', file).then(resp => { |
| 644 | - $scope.files.unshift(resp.data); | 652 | + $scope.files.push(resp.data); |
| 645 | events.emit('success', 'Link attached'); | 653 | events.emit('success', 'Link attached'); |
| 646 | $scope.file = getCleanFile(); | 654 | $scope.file = getCleanFile(); |
| 647 | - }, checkError); | 655 | + }, checkError('link')); |
| 648 | }; | 656 | }; |
| 649 | 657 | ||
| 650 | /** | 658 | /** |
| ... | @@ -652,8 +660,9 @@ module.exports = function (ngApp, events) { | ... | @@ -652,8 +660,9 @@ module.exports = function (ngApp, events) { |
| 652 | * @param fileId | 660 | * @param fileId |
| 653 | */ | 661 | */ |
| 654 | $scope.startEdit = function(file) { | 662 | $scope.startEdit = function(file) { |
| 663 | + console.log(file); | ||
| 655 | $scope.editFile = angular.copy(file); | 664 | $scope.editFile = angular.copy(file); |
| 656 | - if (!file.external) $scope.editFile.link = ''; | 665 | + $scope.editFile.link = (file.external) ? file.path : ''; |
| 657 | }; | 666 | }; |
| 658 | 667 | ||
| 659 | /** | 668 | /** |
| ... | @@ -670,17 +679,24 @@ module.exports = function (ngApp, events) { | ... | @@ -670,17 +679,24 @@ module.exports = function (ngApp, events) { |
| 670 | $scope.updateFile = function(file) { | 679 | $scope.updateFile = function(file) { |
| 671 | $http.put(`/files/${file.id}`, file).then(resp => { | 680 | $http.put(`/files/${file.id}`, file).then(resp => { |
| 672 | let search = filesIndexOf(resp.data); | 681 | let search = filesIndexOf(resp.data); |
| 673 | - if (search !== -1) $scope.files[search] = file; | 682 | + if (search !== -1) $scope.files[search] = resp.data; |
| 674 | 683 | ||
| 675 | if ($scope.editFile && !file.external) { | 684 | if ($scope.editFile && !file.external) { |
| 676 | $scope.editFile.link = ''; | 685 | $scope.editFile.link = ''; |
| 677 | } | 686 | } |
| 678 | $scope.editFile = false; | 687 | $scope.editFile = false; |
| 679 | events.emit('success', 'Attachment details updated'); | 688 | events.emit('success', 'Attachment details updated'); |
| 680 | - }); | 689 | + }, checkError('edit')); |
| 681 | }; | 690 | }; |
| 682 | 691 | ||
| 683 | /** | 692 | /** |
| 693 | + * Get the url of a file. | ||
| 694 | + */ | ||
| 695 | + $scope.getFileUrl = function(file) { | ||
| 696 | + return window.baseUrl('/files/' + file.id); | ||
| 697 | + } | ||
| 698 | + | ||
| 699 | + /** | ||
| 684 | * Search the local files via another file object. | 700 | * Search the local files via another file object. |
| 685 | * Used to search via object copies. | 701 | * Used to search via object copies. |
| 686 | * @param file | 702 | * @param file |
| ... | @@ -697,10 +713,17 @@ module.exports = function (ngApp, events) { | ... | @@ -697,10 +713,17 @@ module.exports = function (ngApp, events) { |
| 697 | * Check for an error response in a ajax request. | 713 | * Check for an error response in a ajax request. |
| 698 | * @param response | 714 | * @param response |
| 699 | */ | 715 | */ |
| 700 | - function checkError(response) { | 716 | + function checkError(errorGroupName) { |
| 717 | + $scope.errors[errorGroupName] = {}; | ||
| 718 | + return function(response) { | ||
| 701 | if (typeof response.data !== 'undefined' && typeof response.data.error !== 'undefined') { | 719 | if (typeof response.data !== 'undefined' && typeof response.data.error !== 'undefined') { |
| 702 | events.emit('error', response.data.error); | 720 | events.emit('error', response.data.error); |
| 703 | } | 721 | } |
| 722 | + if (typeof response.data !== 'undefined' && typeof response.data.validation !== 'undefined') { | ||
| 723 | + $scope.errors[errorGroupName] = response.data.validation; | ||
| 724 | + console.log($scope.errors[errorGroupName]) | ||
| 725 | + } | ||
| 726 | + } | ||
| 704 | } | 727 | } |
| 705 | 728 | ||
| 706 | }]); | 729 | }]); | ... | ... |
| ... | @@ -33,6 +33,59 @@ module.exports = function (ngApp, events) { | ... | @@ -33,6 +33,59 @@ module.exports = function (ngApp, events) { |
| 33 | }; | 33 | }; |
| 34 | }); | 34 | }); |
| 35 | 35 | ||
| 36 | + /** | ||
| 37 | + * Common tab controls using simple jQuery functions. | ||
| 38 | + */ | ||
| 39 | + ngApp.directive('tabContainer', function() { | ||
| 40 | + return { | ||
| 41 | + restrict: 'A', | ||
| 42 | + link: function (scope, element, attrs) { | ||
| 43 | + const $content = element.find('[tab-content]'); | ||
| 44 | + const $buttons = element.find('[tab-button]'); | ||
| 45 | + | ||
| 46 | + if (attrs.tabContainer) { | ||
| 47 | + let initial = attrs.tabContainer; | ||
| 48 | + $buttons.filter(`[tab-button="${initial}"]`).addClass('selected'); | ||
| 49 | + $content.hide().filter(`[tab-content="${initial}"]`).show(); | ||
| 50 | + } else { | ||
| 51 | + $content.hide().first().show(); | ||
| 52 | + $buttons.first().addClass('selected'); | ||
| 53 | + } | ||
| 54 | + | ||
| 55 | + $buttons.click(function() { | ||
| 56 | + let clickedTab = $(this); | ||
| 57 | + $buttons.removeClass('selected'); | ||
| 58 | + $content.hide(); | ||
| 59 | + let name = clickedTab.addClass('selected').attr('tab-button'); | ||
| 60 | + $content.filter(`[tab-content="${name}"]`).show(); | ||
| 61 | + }); | ||
| 62 | + } | ||
| 63 | + }; | ||
| 64 | + }); | ||
| 65 | + | ||
| 66 | + /** | ||
| 67 | + * Sub form component to allow inner-form sections to act like thier own forms. | ||
| 68 | + */ | ||
| 69 | + ngApp.directive('subForm', function() { | ||
| 70 | + return { | ||
| 71 | + restrict: 'A', | ||
| 72 | + link: function (scope, element, attrs) { | ||
| 73 | + element.on('keypress', e => { | ||
| 74 | + if (e.keyCode === 13) { | ||
| 75 | + submitEvent(e); | ||
| 76 | + } | ||
| 77 | + }); | ||
| 78 | + | ||
| 79 | + element.find('button[type="submit"]').click(submitEvent); | ||
| 80 | + | ||
| 81 | + function submitEvent(e) { | ||
| 82 | + e.preventDefault() | ||
| 83 | + if (attrs.subForm) scope.$eval(attrs.subForm); | ||
| 84 | + } | ||
| 85 | + } | ||
| 86 | + }; | ||
| 87 | + }); | ||
| 88 | + | ||
| 36 | 89 | ||
| 37 | /** | 90 | /** |
| 38 | * Image Picker | 91 | * Image Picker |
| ... | @@ -489,8 +542,8 @@ module.exports = function (ngApp, events) { | ... | @@ -489,8 +542,8 @@ module.exports = function (ngApp, events) { |
| 489 | link: function (scope, elem, attrs) { | 542 | link: function (scope, elem, attrs) { |
| 490 | 543 | ||
| 491 | // Get common elements | 544 | // Get common elements |
| 492 | - const $buttons = elem.find('[tab-button]'); | 545 | + const $buttons = elem.find('[toolbox-tab-button]'); |
| 493 | - const $content = elem.find('[tab-content]'); | 546 | + const $content = elem.find('[toolbox-tab-content]'); |
| 494 | const $toggle = elem.find('[toolbox-toggle]'); | 547 | const $toggle = elem.find('[toolbox-toggle]'); |
| 495 | 548 | ||
| 496 | // Handle toolbox toggle click | 549 | // Handle toolbox toggle click |
| ... | @@ -502,17 +555,17 @@ module.exports = function (ngApp, events) { | ... | @@ -502,17 +555,17 @@ module.exports = function (ngApp, events) { |
| 502 | function setActive(tabName, openToolbox) { | 555 | function setActive(tabName, openToolbox) { |
| 503 | $buttons.removeClass('active'); | 556 | $buttons.removeClass('active'); |
| 504 | $content.hide(); | 557 | $content.hide(); |
| 505 | - $buttons.filter(`[tab-button="${tabName}"]`).addClass('active'); | 558 | + $buttons.filter(`[toolbox-tab-button="${tabName}"]`).addClass('active'); |
| 506 | - $content.filter(`[tab-content="${tabName}"]`).show(); | 559 | + $content.filter(`[toolbox-tab-content="${tabName}"]`).show(); |
| 507 | if (openToolbox) elem.addClass('open'); | 560 | if (openToolbox) elem.addClass('open'); |
| 508 | } | 561 | } |
| 509 | 562 | ||
| 510 | // Set the first tab content active on load | 563 | // Set the first tab content active on load |
| 511 | - setActive($content.first().attr('tab-content'), false); | 564 | + setActive($content.first().attr('toolbox-tab-content'), false); |
| 512 | 565 | ||
| 513 | // Handle tab button click | 566 | // Handle tab button click |
| 514 | $buttons.click(function (e) { | 567 | $buttons.click(function (e) { |
| 515 | - let name = $(this).attr('tab-button'); | 568 | + let name = $(this).attr('toolbox-tab-button'); |
| 516 | setActive(name, true); | 569 | setActive(name, true); |
| 517 | }); | 570 | }); |
| 518 | } | 571 | } | ... | ... |
| ... | @@ -452,3 +452,17 @@ body.flexbox-support #entity-selector-wrap .popup-body .form-group { | ... | @@ -452,3 +452,17 @@ body.flexbox-support #entity-selector-wrap .popup-body .form-group { |
| 452 | border-right: 6px solid transparent; | 452 | border-right: 6px solid transparent; |
| 453 | border-bottom: 6px solid $negative; | 453 | border-bottom: 6px solid $negative; |
| 454 | } | 454 | } |
| 455 | + | ||
| 456 | + | ||
| 457 | +[tab-container] .nav-tabs { | ||
| 458 | + text-align: left; | ||
| 459 | + border-bottom: 1px solid #DDD; | ||
| 460 | + margin-bottom: $-m; | ||
| 461 | + .tab-item { | ||
| 462 | + padding: $-s; | ||
| 463 | + color: #666; | ||
| 464 | + &.selected { | ||
| 465 | + border-bottom-width: 3px; | ||
| 466 | + } | ||
| 467 | + } | ||
| 468 | +} | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file | ... | ... |
| ... | @@ -150,7 +150,6 @@ | ... | @@ -150,7 +150,6 @@ |
| 150 | background-color: #FFF; | 150 | background-color: #FFF; |
| 151 | border: 1px solid #DDD; | 151 | border: 1px solid #DDD; |
| 152 | right: $-xl*2; | 152 | right: $-xl*2; |
| 153 | - z-index: 99; | ||
| 154 | width: 48px; | 153 | width: 48px; |
| 155 | overflow: hidden; | 154 | overflow: hidden; |
| 156 | align-items: stretch; | 155 | align-items: stretch; |
| ... | @@ -201,7 +200,7 @@ | ... | @@ -201,7 +200,7 @@ |
| 201 | color: #444; | 200 | color: #444; |
| 202 | background-color: rgba(0, 0, 0, 0.1); | 201 | background-color: rgba(0, 0, 0, 0.1); |
| 203 | } | 202 | } |
| 204 | - div[tab-content] { | 203 | + div[toolbox-tab-content] { |
| 205 | padding-bottom: 45px; | 204 | padding-bottom: 45px; |
| 206 | display: flex; | 205 | display: flex; |
| 207 | flex: 1; | 206 | flex: 1; |
| ... | @@ -209,7 +208,7 @@ | ... | @@ -209,7 +208,7 @@ |
| 209 | min-height: 0px; | 208 | min-height: 0px; |
| 210 | overflow-y: scroll; | 209 | overflow-y: scroll; |
| 211 | } | 210 | } |
| 212 | - div[tab-content] .padded { | 211 | + div[toolbox-tab-content] .padded { |
| 213 | flex: 1; | 212 | flex: 1; |
| 214 | padding-top: 0; | 213 | padding-top: 0; |
| 215 | } | 214 | } |
| ... | @@ -241,7 +240,7 @@ | ... | @@ -241,7 +240,7 @@ |
| 241 | } | 240 | } |
| 242 | } | 241 | } |
| 243 | 242 | ||
| 244 | -[tab-content] { | 243 | +[toolbox-tab-content] { |
| 245 | display: none; | 244 | display: none; |
| 246 | } | 245 | } |
| 247 | 246 | ... | ... |
| ... | @@ -52,3 +52,13 @@ table.list-table { | ... | @@ -52,3 +52,13 @@ table.list-table { |
| 52 | padding: $-xs; | 52 | padding: $-xs; |
| 53 | } | 53 | } |
| 54 | } | 54 | } |
| 55 | + | ||
| 56 | +table.file-table { | ||
| 57 | + @extend .no-style; | ||
| 58 | + td { | ||
| 59 | + padding: $-xs; | ||
| 60 | + } | ||
| 61 | + .ui-sortable-helper { | ||
| 62 | + display: table; | ||
| 63 | + } | ||
| 64 | +} | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file | ... | ... |
| ... | @@ -3,13 +3,13 @@ | ... | @@ -3,13 +3,13 @@ |
| 3 | 3 | ||
| 4 | <div class="tabs primary-background-light"> | 4 | <div class="tabs primary-background-light"> |
| 5 | <span toolbox-toggle><i class="zmdi zmdi-caret-left-circle"></i></span> | 5 | <span toolbox-toggle><i class="zmdi zmdi-caret-left-circle"></i></span> |
| 6 | - <span tab-button="tags" title="Page Tags" class="active"><i class="zmdi zmdi-tag"></i></span> | 6 | + <span toolbox-tab-button="tags" title="Page Tags" class="active"><i class="zmdi zmdi-tag"></i></span> |
| 7 | @if(userCan('file-create-all')) | 7 | @if(userCan('file-create-all')) |
| 8 | - <span tab-button="files" title="Attachments"><i class="zmdi zmdi-attachment"></i></span> | 8 | + <span toolbox-tab-button="files" title="Attachments"><i class="zmdi zmdi-attachment"></i></span> |
| 9 | @endif | 9 | @endif |
| 10 | </div> | 10 | </div> |
| 11 | 11 | ||
| 12 | - <div tab-content="tags" ng-controller="PageTagController" page-id="{{ $page->id or 0 }}"> | 12 | + <div toolbox-tab-content="tags" ng-controller="PageTagController" page-id="{{ $page->id or 0 }}"> |
| 13 | <h4>Page Tags</h4> | 13 | <h4>Page Tags</h4> |
| 14 | <div class="padded tags"> | 14 | <div class="padded tags"> |
| 15 | <p class="muted small">Add some tags to better categorise your content. <br> You can assign a value to a tag for more in-depth organisation.</p> | 15 | <p class="muted small">Add some tags to better categorise your content. <br> You can assign a value to a tag for more in-depth organisation.</p> |
| ... | @@ -38,55 +38,93 @@ | ... | @@ -38,55 +38,93 @@ |
| 38 | </div> | 38 | </div> |
| 39 | 39 | ||
| 40 | @if(userCan('file-create-all')) | 40 | @if(userCan('file-create-all')) |
| 41 | - <div tab-content="files" ng-controller="PageAttachmentController" page-id="{{ $page->id or 0 }}"> | 41 | + <div toolbox-tab-content="files" ng-controller="PageAttachmentController" page-id="{{ $page->id or 0 }}"> |
| 42 | - <h4>Attached Files</h4> | 42 | + <h4>Attachments</h4> |
| 43 | <div class="padded files"> | 43 | <div class="padded files"> |
| 44 | 44 | ||
| 45 | <div id="file-list" ng-show="!editFile"> | 45 | <div id="file-list" ng-show="!editFile"> |
| 46 | - <p class="muted small">Upload some files to display on your page. This are visible in the page sidebar.</p> | 46 | + <p class="muted small">Upload some files or attach some link to display on your page. This are visible in the page sidebar.</p> |
| 47 | - <drop-zone upload-url="@{{getUploadUrl()}}" uploaded-to="@{{uploadedTo}}" event-success="uploadSuccess"></drop-zone> | ||
| 48 | - | ||
| 49 | - <hr class="even"> | ||
| 50 | 47 | ||
| 51 | - <div class="form-group"> | 48 | + <div tab-container> |
| 52 | - <label for="attachment-via-link">File Name</label> | 49 | + <div class="nav-tabs"> |
| 53 | - <input type="text" placeholder="File name" ng-model="file.name"> | 50 | + <div tab-button="list" class="tab-item">File List</div> |
| 54 | - </div> | 51 | + <div tab-button="file" class="tab-item">Upload File</div> |
| 55 | - <div class="form-group"> | 52 | + <div tab-button="link" class="tab-item">Attach Link</div> |
| 56 | - <label for="attachment-via-link">Link to file</label> | ||
| 57 | - <input type="text" placeholder="File url" ng-model="file.link"> | ||
| 58 | </div> | 53 | </div> |
| 59 | - <button type="button" ng-click="attachLinkSubmit(file)" class="button pos">Attach</button> | 54 | + <div tab-content="list"> |
| 60 | - | 55 | + <table class="file-table" style="width: 100%;"> |
| 61 | - | ||
| 62 | - <table class="no-style" tag-autosuggestions style="width: 100%;"> | ||
| 63 | <tbody ui-sortable="sortOptions" ng-model="files" > | 56 | <tbody ui-sortable="sortOptions" ng-model="files" > |
| 64 | <tr ng-repeat="file in files track by $index"> | 57 | <tr ng-repeat="file in files track by $index"> |
| 65 | <td width="20" ><i class="handle zmdi zmdi-menu"></i></td> | 58 | <td width="20" ><i class="handle zmdi zmdi-menu"></i></td> |
| 66 | - <td ng-bind="file.name"></td> | 59 | + <td> |
| 60 | + <a ng-href="@{{getFileUrl(file)}}" target="_blank" ng-bind="file.name"></a> | ||
| 61 | + <div ng-if="file.deleting"> | ||
| 62 | + <span class="neg small">Click delete again to confirm you want to delete this attachment.</span> | ||
| 63 | + <br> | ||
| 64 | + <span class="text-primary small" ng-click="file.deleting=false;">Cancel</span> | ||
| 65 | + </div> | ||
| 66 | + </td> | ||
| 67 | + <td width="10" ng-click="startEdit(file)" class="text-center text-primary" style="padding: 0;"><i class="zmdi zmdi-edit"></i></td> | ||
| 68 | + <td width="5"></td> | ||
| 67 | <td width="10" ng-click="deleteFile(file)" class="text-center text-neg" style="padding: 0;"><i class="zmdi zmdi-close"></i></td> | 69 | <td width="10" ng-click="deleteFile(file)" class="text-center text-neg" style="padding: 0;"><i class="zmdi zmdi-close"></i></td> |
| 68 | - <td width="10" ng-click="startEdit(file)" class="text-center text-neg" style="padding: 0;"><i class="zmdi zmdi-edit"></i></td> | ||
| 69 | </tr> | 70 | </tr> |
| 70 | </tbody> | 71 | </tbody> |
| 71 | </table> | 72 | </table> |
| 73 | + <p class="small muted" ng-if="files.length == 0"> | ||
| 74 | + No files have been uploaded. | ||
| 75 | + </p> | ||
| 76 | + </div> | ||
| 77 | + <div tab-content="file"> | ||
| 78 | + <drop-zone upload-url="@{{getUploadUrl()}}" uploaded-to="@{{uploadedTo}}" event-success="uploadSuccess"></drop-zone> | ||
| 79 | + </div> | ||
| 80 | + <div tab-content="link" sub-form="attachLinkSubmit(file)"> | ||
| 81 | + <p class="muted small">You can attach a link if you'd prefer not to upload a file. This can be a link to another page or a link to a file in the cloud.</p> | ||
| 82 | + <div class="form-group"> | ||
| 83 | + <label for="attachment-via-link">Link Name</label> | ||
| 84 | + <input type="text" placeholder="Link name" ng-model="file.name"> | ||
| 85 | + <p class="small neg" ng-repeat="error in errors.link.name" ng-bind="error"></p> | ||
| 86 | + </div> | ||
| 87 | + <div class="form-group"> | ||
| 88 | + <label for="attachment-via-link">Link to file</label> | ||
| 89 | + <input type="text" placeholder="Url of site or file" ng-model="file.link"> | ||
| 90 | + <p class="small neg" ng-repeat="error in errors.link.link" ng-bind="error"></p> | ||
| 91 | + </div> | ||
| 92 | + <button type="submit" class="button pos">Attach</button> | ||
| 93 | + | ||
| 94 | + </div> | ||
| 95 | + </div> | ||
| 96 | + | ||
| 72 | </div> | 97 | </div> |
| 73 | 98 | ||
| 74 | - <div id="file-edit" ng-if="editFile"> | 99 | + <div id="file-edit" ng-if="editFile" sub-form="updateFile(editFile)"> |
| 75 | <h5>Edit File</h5> | 100 | <h5>Edit File</h5> |
| 101 | + | ||
| 76 | <div class="form-group"> | 102 | <div class="form-group"> |
| 77 | <label for="attachment-name-edit">File Name</label> | 103 | <label for="attachment-name-edit">File Name</label> |
| 78 | <input type="text" id="attachment-name-edit" placeholder="File name" ng-model="editFile.name"> | 104 | <input type="text" id="attachment-name-edit" placeholder="File name" ng-model="editFile.name"> |
| 105 | + <p class="small neg" ng-repeat="error in errors.edit.name" ng-bind="error"></p> | ||
| 106 | + </div> | ||
| 107 | + | ||
| 108 | + <div tab-container="@{{ editFile.external ? 'link' : 'file' }}"> | ||
| 109 | + <div class="nav-tabs"> | ||
| 110 | + <div tab-button="file" class="tab-item">Upload File</div> | ||
| 111 | + <div tab-button="link" class="tab-item">Set Link</div> | ||
| 79 | </div> | 112 | </div> |
| 80 | - <hr class="even"> | 113 | + <div tab-content="file"> |
| 81 | <drop-zone upload-url="@{{getUploadUrl(editFile)}}" uploaded-to="@{{uploadedTo}}" placeholder="Drop files or click here to upload and overwrite" event-success="uploadSuccessUpdate"></drop-zone> | 114 | <drop-zone upload-url="@{{getUploadUrl(editFile)}}" uploaded-to="@{{uploadedTo}}" placeholder="Drop files or click here to upload and overwrite" event-success="uploadSuccessUpdate"></drop-zone> |
| 82 | - <hr class="even"> | 115 | + <br> |
| 116 | + </div> | ||
| 117 | + <div tab-content="link"> | ||
| 83 | <div class="form-group"> | 118 | <div class="form-group"> |
| 84 | <label for="attachment-link-edit">Link to file</label> | 119 | <label for="attachment-link-edit">Link to file</label> |
| 85 | - <input type="text" id="attachment-link-edit" placeholder="File url" ng-model="editFile.link"> | 120 | + <input type="text" id="attachment-link-edit" placeholder="Attachment link" ng-model="editFile.link"> |
| 121 | + <p class="small neg" ng-repeat="error in errors.edit.link" ng-bind="error"></p> | ||
| 122 | + </div> | ||
| 123 | + </div> | ||
| 86 | </div> | 124 | </div> |
| 87 | 125 | ||
| 88 | <button type="button" class="button" ng-click="cancelEdit()">Back</button> | 126 | <button type="button" class="button" ng-click="cancelEdit()">Back</button> |
| 89 | - <button type="button" class="button pos" ng-click="updateFile(editFile)">Save</button> | 127 | + <button type="submit" class="button pos">Save</button> |
| 90 | </div> | 128 | </div> |
| 91 | 129 | ||
| 92 | </div> | 130 | </div> | ... | ... |
| ... | @@ -5,7 +5,7 @@ | ... | @@ -5,7 +5,7 @@ |
| 5 | <h6 class="text-muted">Attachments</h6> | 5 | <h6 class="text-muted">Attachments</h6> |
| 6 | @foreach($page->files as $file) | 6 | @foreach($page->files as $file) |
| 7 | <div class="attachment"> | 7 | <div class="attachment"> |
| 8 | - <a href="{{ $file->getUrl() }}" @if($file->external) target="_blank" @endif><i class="zmdi zmdi-file"></i> {{ $file->name }}</a> | 8 | + <a href="{{ $file->getUrl() }}" @if($file->external) target="_blank" @endif><i class="zmdi zmdi-{{ $file->external ? 'open-in-new' : 'file' }}"></i> {{ $file->name }}</a> |
| 9 | </div> | 9 | </div> |
| 10 | @endforeach | 10 | @endforeach |
| 11 | @endif | 11 | @endif | ... | ... |
| ... | @@ -14,7 +14,7 @@ | ... | @@ -14,7 +14,7 @@ |
| 14 | .nav-tabs a.selected, .nav-tabs .tab-item.selected { | 14 | .nav-tabs a.selected, .nav-tabs .tab-item.selected { |
| 15 | border-bottom-color: {{ setting('app-color') }}; | 15 | border-bottom-color: {{ setting('app-color') }}; |
| 16 | } | 16 | } |
| 17 | - p.primary:hover, p .primary:hover, span.primary:hover, .text-primary:hover, a, a:hover, a:focus, .text-button, .text-button:hover, .text-button:focus { | 17 | + .text-primary, p.primary, p .primary, span.primary:hover, .text-primary:hover, a, a:hover, a:focus, .text-button, .text-button:hover, .text-button:focus { |
| 18 | color: {{ setting('app-color') }}; | 18 | color: {{ setting('app-color') }}; |
| 19 | } | 19 | } |
| 20 | </style> | 20 | </style> |
| ... | \ No newline at end of file | ... | \ No newline at end of file | ... | ... |
-
Please register or sign in to post a comment