Dan Brown

Cleaned up tag edit interface

...@@ -60,7 +60,7 @@ class Entity extends Ownable ...@@ -60,7 +60,7 @@ class Entity extends Ownable
60 */ 60 */
61 public function tags() 61 public function tags()
62 { 62 {
63 - return $this->morphMany(Tag::class, 'entity'); 63 + return $this->morphMany(Tag::class, 'entity')->orderBy('order', 'asc');
64 } 64 }
65 65
66 /** 66 /**
......
...@@ -4,10 +4,11 @@ ...@@ -4,10 +4,11 @@
4 "gulp": "^3.9.0" 4 "gulp": "^3.9.0"
5 }, 5 },
6 "dependencies": { 6 "dependencies": {
7 - "angular": "^1.5.0-rc.0", 7 + "angular": "^1.5.5",
8 - "angular-animate": "^1.5.0-rc.0", 8 + "angular-animate": "^1.5.5",
9 - "angular-resource": "^1.5.0-rc.0", 9 + "angular-resource": "^1.5.5",
10 - "angular-sanitize": "^1.5.0-rc.0", 10 + "angular-sanitize": "^1.5.5",
11 + "angular-ui-sortable": "^0.14.0",
11 "babel-runtime": "^5.8.29", 12 "babel-runtime": "^5.8.29",
12 "bootstrap-sass": "^3.0.0", 13 "bootstrap-sass": "^3.0.0",
13 "dropzone": "^4.0.1", 14 "dropzone": "^4.0.1",
......
...@@ -405,6 +405,13 @@ module.exports = function (ngApp, events) { ...@@ -405,6 +405,13 @@ module.exports = function (ngApp, events) {
405 405
406 const pageId = Number($attrs.pageId); 406 const pageId = Number($attrs.pageId);
407 $scope.tags = []; 407 $scope.tags = [];
408 +
409 + $scope.sortOptions = {
410 + handle: '.handle',
411 + items: '> tr',
412 + containment: "parent",
413 + axis: "y"
414 + };
408 415
409 /** 416 /**
410 * Push an empty tag to the end of the scope tags. 417 * Push an empty tag to the end of the scope tags.
...@@ -415,6 +422,7 @@ module.exports = function (ngApp, events) { ...@@ -415,6 +422,7 @@ module.exports = function (ngApp, events) {
415 value: '' 422 value: ''
416 }); 423 });
417 } 424 }
425 + $scope.addEmptyTag = addEmptyTag;
418 426
419 /** 427 /**
420 * Get all tags for the current book and add into scope. 428 * Get all tags for the current book and add into scope.
...@@ -463,6 +471,9 @@ module.exports = function (ngApp, events) { ...@@ -463,6 +471,9 @@ module.exports = function (ngApp, events) {
463 } 471 }
464 }; 472 };
465 473
474 + /**
475 + * Save the tags to the current page.
476 + */
466 $scope.saveTags = function() { 477 $scope.saveTags = function() {
467 setTagOrder(); 478 setTagOrder();
468 let postData = {tags: $scope.tags}; 479 let postData = {tags: $scope.tags};
...@@ -473,6 +484,15 @@ module.exports = function (ngApp, events) { ...@@ -473,6 +484,15 @@ module.exports = function (ngApp, events) {
473 }) 484 })
474 }; 485 };
475 486
487 + /**
488 + * Remove a tag from the current list.
489 + * @param tag
490 + */
491 + $scope.removeTag = function(tag) {
492 + let cIndex = $scope.tags.indexOf(tag);
493 + $scope.tags.splice(cIndex, 1);
494 + };
495 +
476 }]); 496 }]);
477 497
478 }; 498 };
......
...@@ -301,6 +301,42 @@ module.exports = function (ngApp, events) { ...@@ -301,6 +301,42 @@ module.exports = function (ngApp, events) {
301 301
302 } 302 }
303 } 303 }
304 - }]) 304 + }]);
305 +
306 + ngApp.directive('toolbox', [function() {
307 + return {
308 + restrict: 'A',
309 + link: function(scope, elem, attrs) {
310 +
311 + // Get common elements
312 + const $buttons = elem.find('[tab-button]');
313 + const $content = elem.find('[tab-content]');
314 + const $toggle = elem.find('[toolbox-toggle]');
315 +
316 + // Handle toolbox toggle click
317 + $toggle.click((e) => {
318 + elem.toggleClass('open');
319 + });
320 +
321 + // Set an active tab/content by name
322 + function setActive(tabName, openToolbox) {
323 + $buttons.removeClass('active');
324 + $content.hide();
325 + $buttons.filter(`[tab-button="${tabName}"]`).addClass('active');
326 + $content.filter(`[tab-content="${tabName}"]`).show();
327 + if (openToolbox) elem.addClass('open');
328 + }
329 +
330 + // Set the first tab content active on load
331 + setActive($content.first().attr('tab-content'), false);
332 +
333 + // Handle tab button click
334 + $buttons.click(function(e) {
335 + let name = $(this).attr('tab-button');
336 + setActive(name, true);
337 + });
338 + }
339 + }
340 + }]);
305 341
306 }; 342 };
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -5,9 +5,9 @@ var angular = require('angular'); ...@@ -5,9 +5,9 @@ var angular = require('angular');
5 var ngResource = require('angular-resource'); 5 var ngResource = require('angular-resource');
6 var ngAnimate = require('angular-animate'); 6 var ngAnimate = require('angular-animate');
7 var ngSanitize = require('angular-sanitize'); 7 var ngSanitize = require('angular-sanitize');
8 +require('angular-ui-sortable');
8 9
9 -var ngApp = angular.module('bookStack', ['ngResource', 'ngAnimate', 'ngSanitize']); 10 +var ngApp = angular.module('bookStack', ['ngResource', 'ngAnimate', 'ngSanitize', 'ui.sortable']);
10 -
11 11
12 // Global Event System 12 // Global Event System
13 var Events = { 13 var Events = {
......
...@@ -65,6 +65,9 @@ $button-border-radius: 2px; ...@@ -65,6 +65,9 @@ $button-border-radius: 2px;
65 &:focus, &:active { 65 &:focus, &:active {
66 outline: 0; 66 outline: 0;
67 } 67 }
68 + &:hover {
69 + text-decoration: none;
70 + }
68 &.neg { 71 &.neg {
69 color: $negative; 72 color: $negative;
70 } 73 }
......
...@@ -21,6 +21,11 @@ ...@@ -21,6 +21,11 @@
21 21
22 [ng\:cloak], [ng-cloak], .ng-cloak { 22 [ng\:cloak], [ng-cloak], .ng-cloak {
23 display: none !important; 23 display: none !important;
24 + user-select: none;
25 +}
26 +
27 +[ng-click] {
28 + cursor: pointer;
24 } 29 }
25 30
26 // Jquery Sortable Styles 31 // Jquery Sortable Styles
...@@ -206,18 +211,31 @@ $btt-size: 40px; ...@@ -206,18 +211,31 @@ $btt-size: 40px;
206 // Attribute form 211 // Attribute form
207 .floating-toolbox { 212 .floating-toolbox {
208 background-color: #FFF; 213 background-color: #FFF;
209 - border: 1px solid #BBB; 214 + border: 1px solid #DDD;
210 - border-radius: 3px;
211 - position: fixed;
212 right: $-xl*2; 215 right: $-xl*2;
213 - top: 100px;
214 z-index: 99; 216 z-index: 99;
215 - height: 800px; 217 + width: 48px;
216 - width: 480px; 218 + overflow: hidden;
217 - overflow-y: scroll;
218 - display: flex;
219 align-items: stretch; 219 align-items: stretch;
220 flex-direction: row; 220 flex-direction: row;
221 + display: flex;
222 + transition: width ease-in-out 180ms;
223 + margin-top: -1px;
224 + &.open {
225 + width: 480px;
226 + }
227 + [toolbox-toggle] i {
228 + transition: transform ease-in-out 180ms;
229 + }
230 + [toolbox-toggle] {
231 + transition: background-color ease-in-out 180ms;
232 + }
233 + &.open [toolbox-toggle] {
234 + background-color: rgba(255, 0, 0, 0.29);
235 + }
236 + &.open [toolbox-toggle] i {
237 + transform: rotate(180deg);
238 + }
221 > div { 239 > div {
222 flex: 1; 240 flex: 1;
223 position: relative; 241 position: relative;
...@@ -229,27 +247,35 @@ $btt-size: 40px; ...@@ -229,27 +247,35 @@ $btt-size: 40px;
229 flex: 0; 247 flex: 0;
230 } 248 }
231 .tabs i { 249 .tabs i {
250 + color: rgba(0, 0, 0, 0.5);
232 padding: 0; 251 padding: 0;
233 margin: 0; 252 margin: 0;
234 } 253 }
235 - .tabs [tab-button] { 254 + .tabs > span {
236 display: block; 255 display: block;
237 cursor: pointer; 256 cursor: pointer;
238 - color: #666; 257 + padding: $-s $-m;
239 - padding: $-m; 258 + font-size: 13.5px;
259 + line-height: 1.6;
240 border-bottom: 1px solid rgba(255, 255, 255, 0.3); 260 border-bottom: 1px solid rgba(255, 255, 255, 0.3);
241 - &.active { 261 + }
242 - color: #444; 262 + &.open .tabs > span.active {
243 - background-color: rgba(0, 0, 0, 0.1); 263 + color: #444;
244 - } 264 + background-color: rgba(0, 0, 0, 0.1);
265 + }
266 + div[tab-content] {
267 + padding-bottom: 45px;
268 + display: flex;
269 + flex: 1;
245 } 270 }
246 div[tab-content] .padded { 271 div[tab-content] .padded {
247 - padding: 0 $-m; 272 + flex: 1;
273 + padding-top: 0;
248 } 274 }
249 h4 { 275 h4 {
250 font-size: 24px; 276 font-size: 24px;
251 margin: $-m 0 0 0; 277 margin: $-m 0 0 0;
252 - padding: 0 $-m; 278 + padding: 0 $-l $-s $-l;
253 } 279 }
254 .tags input { 280 .tags input {
255 max-width: 100%; 281 max-width: 100%;
...@@ -266,6 +292,7 @@ $btt-size: 40px; ...@@ -266,6 +292,7 @@ $btt-size: 40px;
266 display: block; 292 display: block;
267 width: 100%; 293 width: 100%;
268 padding: $-s; 294 padding: $-s;
295 + height: 45px;
269 border: 0; 296 border: 0;
270 margin: 0; 297 margin: 0;
271 box-shadow: none; 298 box-shadow: none;
...@@ -274,4 +301,19 @@ $btt-size: 40px; ...@@ -274,4 +301,19 @@ $btt-size: 40px;
274 box-shadow: none; 301 box-shadow: none;
275 } 302 }
276 } 303 }
304 + .handle {
305 + user-select: none;
306 + cursor: move;
307 + color: #999;
308 + }
309 + form {
310 + display: flex;
311 + flex: 1;
312 + flex-direction: column;
313 + overflow-y: scroll;
314 + }
315 +}
316 +
317 +[tab-content] {
318 + display: none;
277 } 319 }
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
15 15
16 <!-- Scripts --> 16 <!-- Scripts -->
17 <script src="/libs/jquery/jquery.min.js?version=2.1.4"></script> 17 <script src="/libs/jquery/jquery.min.js?version=2.1.4"></script>
18 + <script src="/libs/jquery/jquery-ui.min.js?version=1.11.4"></script>
18 19
19 @yield('head') 20 @yield('head')
20 21
......
1 -<div class="floating-toolbox"> 1 +
2 +<div toolbox class="floating-toolbox">
2 <div class="tabs primary-background-light"> 3 <div class="tabs primary-background-light">
3 - <span tab-button class="active"><i class="zmdi zmdi-tag"></i></span> 4 + <span toolbox-toggle><i class="zmdi zmdi-caret-left-circle"></i></span>
4 - <span tab-button><i class="zmdi zmdi-wrench"></i></span> 5 + <span tab-button="tags" title="Page Tags" class="active"><i class="zmdi zmdi-tag"></i></span>
5 </div> 6 </div>
6 - <div tab-content ng-controller="PageTagController" page-id="{{ $page->id or 0 }}"> 7 + <div tab-content="tags" ng-controller="PageTagController" page-id="{{ $page->id or 0 }}">
7 <form ng-submit="saveTags()" > 8 <form ng-submit="saveTags()" >
8 <h4>Page Tags</h4> 9 <h4>Page Tags</h4>
9 <div class="padded tags"> 10 <div class="padded tags">
11 + <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>
10 <table class="no-style" style="width: 100%;"> 12 <table class="no-style" style="width: 100%;">
11 - <tr ng-repeat="tag in tags"> 13 + <tbody ui-sortable="sortOptions" ng-model="tags" >
12 - <td><input class="outline" type="text" ng-model="tag.name" ng-change="tagChange(tag)" ng-blur="tagBlur(tag)" placeholder="Tag"></td> 14 + <tr ng-repeat="tag in tags">
13 - <td><input class="outline" type="text" ng-model="tag.value" ng-change="tagChange(tag)" ng-blur="tagBlur(tag)" placeholder="Tag Value (Optional)"></td> 15 + <td width="20" ><i class="handle zmdi zmdi-menu"></i></td>
16 + <td><input class="outline" type="text" ng-model="tag.name" ng-change="tagChange(tag)" ng-blur="tagBlur(tag)" placeholder="Tag"></td>
17 + <td><input class="outline" type="text" ng-model="tag.value" ng-change="tagChange(tag)" ng-blur="tagBlur(tag)" placeholder="Tag Value (Optional)"></td>
18 + <td width="10" class="text-center text-neg" style="padding: 0;" ng-click="removeTag(tag)"><i class="zmdi zmdi-close"></i></td>
19 + </tr>
20 + </tbody>
21 + </table>
22 + <table class="no-style" style="width: 100%;">
23 + <tbody>
24 + <tr class="unsortable">
25 + <td width="34"></td>
26 + <td ng-click="addEmptyTag()">
27 + <button type="button" class="text-button">Add another tag</button>
28 + </td>
29 + <td></td>
14 </tr> 30 </tr>
31 + </tbody>
15 </table> 32 </table>
16 </div> 33 </div>
17 <button class="button pos" type="submit">Save Tags</button> 34 <button class="button pos" type="submit">Save Tags</button>
......
...@@ -15,7 +15,7 @@ ...@@ -15,7 +15,7 @@
15 .nav-tabs a.selected, .nav-tabs .tab-item.selected { 15 .nav-tabs a.selected, .nav-tabs .tab-item.selected {
16 border-bottom-color: {{ Setting::get('app-color') }}; 16 border-bottom-color: {{ Setting::get('app-color') }};
17 } 17 }
18 - p.primary:hover, p .primary:hover, span.primary:hover, .text-primary:hover, a, a:hover, a:focus { 18 + 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 {
19 color: {{ Setting::get('app-color') }}; 19 color: {{ Setting::get('app-color') }};
20 } 20 }
21 </style> 21 </style>
......