Drag & Drop Navigation Reordering

Closes #4540

- Implements drag & drop to reorder navigation items
- Adds a `sort` property to navigation items
- Adds a tiny library to enable touch events for drag & drop. It hooks onto jQuery UI.
- Sort nav items before being saved
- Adds `settings-view-navigation` to route for body class
This commit is contained in:
Paul Adam Davis 2015-02-03 16:29:01 +00:00
parent f073e10221
commit 68eb6b67b0
11 changed files with 93 additions and 11 deletions

View File

@ -599,6 +599,7 @@ var _ = require('lodash'),
'bower_components/ember-simple-auth/simple-auth-oauth2.js',
'bower_components/google-caja/html-css-sanitizer-bundle.js',
'bower_components/nanoscroller/bin/javascripts/jquery.nanoscroller.js',
'bower_components/jqueryui-touch-punch/jquery.ui.touch-punch.js',
'core/shared/lib/showdown/extensions/ghostimagepreview.js',
'core/shared/lib/showdown/extensions/ghostgfm.js',
@ -635,6 +636,7 @@ var _ = require('lodash'),
'bower_components/ember-simple-auth/simple-auth-oauth2.js',
'bower_components/google-caja/html-css-sanitizer-bundle.js',
'bower_components/nanoscroller/bin/javascripts/jquery.nanoscroller.js',
'bower_components/jqueryui-touch-punch/jquery.ui.touch-punch.js',
'core/shared/lib/showdown/extensions/ghostimagepreview.js',
'core/shared/lib/showdown/extensions/ghostgfm.js',

View File

@ -16,6 +16,7 @@
"jquery-file-upload": "9.5.6",
"jquery-hammerjs": "1.0.1",
"jquery-ui": "1.10.4",
"jqueryui-touch-punch": "furf/jquery-ui-touch-punch",
"keymaster": "git://github.com/madrobby/keymaster#564ea42e07de40da8113a571f17ceae8802672ff",
"loader.js": "git://github.com/stefanpenner/loader.js#1.0.0",
"moment": "2.8.3",

View File

@ -394,18 +394,33 @@
display: flex;
@media (max-width: 600px) {
position: relative;
margin-bottom: 20px;
}
@media (min-width: 601px) {
margin-bottom: 10px;
}
// &.last-navigation-item {
&:last-child {
padding-left: 27px; // .navigation-item-drag-handle width + horizontal padding
}
}
.navigation-item-drag-handle {
padding-left: 10px;
padding-right: 17px;
padding: 6px 17px 0 10px;
width: 27px;
cursor: move;
@media (max-width: 600px) {
&:before {
position: absolute;
top: 50%;
left: 4px;
margin-top: -9px;
z-index: 20;
}
}
}
.navigation-inputs {
@ -449,6 +464,7 @@
padding-left: 10px;
width: 40px;
position: relative;
z-index: 10;
button {
width: 30px;

View File

@ -187,6 +187,7 @@ select {
width: 100%;
border: 1px solid #E0DFD7;
border-radius: $border-radius;
-webkit-appearance: none;
font-size: 1.4rem;
font-weight: normal;

View File

@ -1,6 +1,9 @@
var NavItemComponent = Ember.Component.extend({
classNames: 'navigation-item',
attributeBindings: ['order:data-order'],
order: Ember.computed.readOnly('navItem.order'),
keyPress: function (event) {
// enter key
if (event.keyCode === 13) {

View File

@ -4,6 +4,7 @@ var NavigationController,
NavItem = Ember.Object.extend({
label: '',
url: '',
order: '',
isComplete: Ember.computed('label', 'url', function () {
return !(Ember.isBlank(this.get('label')) || Ember.isBlank(this.get('url')));
@ -31,6 +32,8 @@ NavigationController = Ember.Controller.extend({
return NavItem.create(item);
});
navItems.sortBy('order');
lastItem = navItems.get('lastObject');
if (!lastItem || lastItem.get('isComplete')) {
navItems.addObject(NavItem.create());
@ -51,13 +54,24 @@ NavigationController = Ember.Controller.extend({
});
}),
updateOrder: function (indexes) {
var navItems = this.get('navigationItems'),
order = 0;
indexes.forEach(function (index) {
navItems[index].set('order', order);
order = order + 1; // Increment order order by one
});
},
actions: {
addItem: function () {
var navItems = this.get('navigationItems'),
lastItem = navItems.get('lastObject');
if (lastItem && lastItem.get('isComplete')) {
navItems.addObject(NavItem.create());
lastItem.set('order', (navItems.length - 1)); // -1 because order is 0-index, length is 1-index
navItems.addObject(NavItem.create()); // Adds new blank navItem
}
},
@ -67,6 +81,14 @@ NavigationController = Ember.Controller.extend({
}
this.get('navigationItems').removeObject(item);
var navItems = this.get('navigationItems'),
order = 0;
navItems.forEach(function (item) {
item.set('order', order);
order = order + 1; // Increment order order by one
});
},
updateUrl: function (url, navItem) {
@ -92,7 +114,8 @@ NavigationController = Ember.Controller.extend({
navSetting = this.get('navigationItems').map(function (item) {
var label,
url;
url,
order;
if (!item || !item.get('isComplete')) {
return;
@ -100,6 +123,7 @@ NavigationController = Ember.Controller.extend({
label = item.get('label').trim();
url = item.get('url').trim();
order = item.get('order');
match = url.match(blogUrlRegex);
@ -118,9 +142,14 @@ NavigationController = Ember.Controller.extend({
return;
}
return {label: label, url: url};
return {label: label, url: url, order: order};
}).compact();
// Sort JSON so nav items are stored in the correct order order
navSetting.sort(function (a, b) {
return a.order - b.order;
});
this.set('model.navigation', JSON.stringify(navSetting));
// trigger change event because even if the final JSON is unchanged

View File

@ -1,10 +1,13 @@
import AuthenticatedRoute from 'ghost/routes/authenticated';
import CurrentUserSettings from 'ghost/mixins/current-user-settings';
import styleBody from 'ghost/mixins/style-body';
var NavigationRoute = AuthenticatedRoute.extend(CurrentUserSettings, {
var NavigationRoute = AuthenticatedRoute.extend(styleBody, CurrentUserSettings, {
titleToken: 'Navigation',
classNames: ['settings-view-navigation'],
beforeModel: function () {
if (!this.get('config.navigationUI')) {
return this.transitionTo('settings.general');

View File

@ -1,6 +1,8 @@
<button type="button" class="navigation-item-drag-handle icon-grab">
<span class="hidden">Reorder</span>
</button>
{{#unless navItem.last}}
<span class="navigation-item-drag-handle icon-grab">
<span class="hidden">Reorder</span>
</span>
{{/unless}}
<div class="navigation-inputs">
<span class="navigation-item-label">
{{gh-trim-focus-input focus=navItem.last placeholder="Label" value=navItem.label}}

View File

@ -7,7 +7,7 @@
</header>
<section class="content settings-navigation">
<form id="settings-navigation" novalidate="novalidate">
<form id="settings-navigation" class="js-settings-navigation" novalidate="novalidate">
{{#each navItem in navigationItems}}
{{gh-navitem navItem=navItem baseUrl=blogUrl addItem="addItem" deleteItem="deleteItem" updateUrl="updateUrl"}}
{{/each}}

View File

@ -1,6 +1,31 @@
import BaseView from 'ghost/views/settings/content-base';
var SettingsNavigationView = BaseView.extend({
didInsertElement: function () {
var controller = this.get('controller'),
navContainer = Ember.$('.js-settings-navigation'),
navElements = '.navigation-item:not(.navigation-item:last-child)';
navContainer.sortable({
handle: '.navigation-item-drag-handle',
items: navElements,
update: function () {
var indexes = [];
navContainer.find(navElements).each(function () {
var order = Ember.$(this).data('order');
indexes.push(order);
});
controller.updateOrder(indexes);
}
});
},
willDestroyElement: function () {
Ember.$('.js-settings-navigation').sortable('destroy');
}
});
export default SettingsNavigationView;

View File

@ -72,7 +72,7 @@
"defaultValue": "{}"
},
"navigation": {
"defaultValue": "[{\"label\":\"Home\", \"url\":\"/\"}]"
"defaultValue": "[{\"label\":\"Home\", \"url\":\"/\", \"order\":0}]"
}
},
"theme": {