mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-11-28 05:37:34 +03:00
Refactor settings routing and mobile interactions
Closes #3254, closes #3138, closes #3245 ### Settings Routing and View refactoring - Refactored `SettingsView` to handle transitions between mobile and desktop layouts - `SettingsRoute` will only transition to `settings.general` if the screen is large enough to show both the menu and the content - Added `SettingsIndexView` to handle showing the settings menu on mobile screens - Added `SettingsContentBaseView` to be inherited by any settings view that is not index. - Updated Settings templates appropriately to work with new views - Removed extraneous `active` class from `settings-content` - Changed settings menu to use `gh-activating-list-item` - Retooled settings tests ### Mobile Utils - Renamed file to `mobile.js`, since it's inside of `utils/` - Added `mobileQuery` MediaQueryList to help detect layout changes - Removed unused `hasTouchScreen`, `device.js` should be used instead. - Removed unused `smallScreen` function - Moved FastClickInit to codemirror-mobile
This commit is contained in:
parent
7cc818d262
commit
14f29f5139
@ -1,8 +1,31 @@
|
||||
import {mobileQuery} from 'ghost/utils/mobile';
|
||||
|
||||
var SettingsIndexRoute = Ember.Route.extend(Ember.SimpleAuth.AuthenticatedRouteMixin, {
|
||||
// redirect to general tab
|
||||
redirect: function () {
|
||||
this.transitionTo('settings.general');
|
||||
activate: function () {
|
||||
this._super();
|
||||
},
|
||||
// redirect to general tab, unless on a mobile phone
|
||||
beforeModel: function () {
|
||||
if (!mobileQuery.matches) {
|
||||
this.transitionTo('settings.general');
|
||||
} else {
|
||||
//fill the empty {{outlet}} in settings.hbs if the user
|
||||
//goes to fullscreen
|
||||
|
||||
//fillOutlet needs special treatment so that it is
|
||||
//properly bound to this when called from a MQ event
|
||||
this.set('fillOutlet', _.bind(function fillOutlet(mq) {
|
||||
if (!mq.matches) {
|
||||
this.transitionTo('settings.general');
|
||||
}
|
||||
}, this));
|
||||
mobileQuery.addListener(this.fillOutlet);
|
||||
}
|
||||
},
|
||||
deactivate: function () {
|
||||
if (this.get('fillOutlet')) {
|
||||
mobileQuery.removeListener(this.fillOutlet);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -4,23 +4,13 @@
|
||||
</header>
|
||||
<nav class="settings-menu">
|
||||
<ul>
|
||||
{{#view "item-view" tagName="li" class="general"}}
|
||||
{{#link-to "settings.general"}}General{{/link-to}}
|
||||
{{/view}}
|
||||
|
||||
{{#view "item-view" tagName="li" class="users"}}
|
||||
{{#link-to "settings.users"}}Users{{/link-to}}
|
||||
{{/view}}
|
||||
|
||||
{{gh-activating-list-item route="settings.general" title="General" classNames="general"}}
|
||||
{{gh-activating-list-item route="settings.users" title="Users" classNames="users"}}
|
||||
{{#if showApps}}
|
||||
{{#view "item-view" tagName="li" class="apps"}}
|
||||
{{#link-to "settings.apps"}}Apps{{/link-to}}
|
||||
{{/view}}
|
||||
{{gh-activating-list-item route="settings.apps" title="Apps" classNames="apps"}}
|
||||
{{/if}}
|
||||
</ul>
|
||||
</nav>
|
||||
</aside>
|
||||
|
||||
<section class="settings-content active">
|
||||
{{outlet}}
|
||||
</section>
|
||||
{{outlet}}
|
||||
|
@ -1,9 +1,9 @@
|
||||
<header class="fade-in">
|
||||
<button class="button-back">Back</button>
|
||||
<header>
|
||||
{{#link-to 'settings' class='button-back button'}}Back{{/link-to}}
|
||||
<h2 class="title">Apps</h2>
|
||||
</header>
|
||||
|
||||
<section class="content settings-apps fade-in">
|
||||
<section class="content settings-apps">
|
||||
<table class="js-apps">
|
||||
<thead>
|
||||
<th>App name</th>
|
||||
|
@ -1,8 +1,8 @@
|
||||
<header class="fade-in">
|
||||
<header>
|
||||
<h2 class="title">General</h2>
|
||||
|
||||
<div class="settings-header-inner">
|
||||
<button class="button-back">Back</button>
|
||||
{{#link-to 'settings' class='button-back button'}}Back{{/link-to}}
|
||||
|
||||
<section class="page-actions">
|
||||
<button class="button-save" {{action "save"}}>Save</button>
|
||||
@ -10,7 +10,7 @@
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<section class="content settings-general fade-in">
|
||||
<section class="content settings-general">
|
||||
<form id="settings-general" novalidate="novalidate">
|
||||
<fieldset>
|
||||
|
||||
|
6
ghost/admin/templates/settings/users.hbs
Normal file
6
ghost/admin/templates/settings/users.hbs
Normal file
@ -0,0 +1,6 @@
|
||||
{{!
|
||||
Yes, this is the template default,
|
||||
but for some reason things break without it in this instance.
|
||||
@TODO Find a better fix?
|
||||
}}
|
||||
{{outlet}}
|
@ -1,13 +1,12 @@
|
||||
<header class="fade-in">
|
||||
<button class="button-back">Back</button>
|
||||
<header>
|
||||
{{#link-to 'settings' class='button-back button'}}Back{{/link-to}}
|
||||
<h2 class="title">Users</h2>
|
||||
<section class="page-actions">
|
||||
<a class="button-add" href="" {{action "openModal" "invite-new-user" this}} >New User</a>
|
||||
</section>
|
||||
</header>
|
||||
|
||||
<section class="content fade-in settings-users">
|
||||
|
||||
<section class="content settings-users">
|
||||
{{#if invitedUsers}}
|
||||
|
||||
<section class="object-list">
|
||||
|
@ -1,11 +1,9 @@
|
||||
<header class="fade-in user-settings-header">
|
||||
<header class="user-settings-header">
|
||||
|
||||
<h2 class="hidden">Your Profile</h2>
|
||||
|
||||
<div class="settings-header-inner">
|
||||
|
||||
<button class="button-back">Back</button>
|
||||
|
||||
{{#link-to 'settings' class='button-back button'}}Back{{/link-to}}
|
||||
<section class="page-actions page-actions-alt">
|
||||
{{#link-to "settings.users" class="button has-icon users-back" tagName="button"}}<i class="icon-chevron-left"></i>Users{{/link-to}}
|
||||
</section>
|
||||
@ -26,7 +24,7 @@
|
||||
|
||||
</header>
|
||||
|
||||
<section class="content settings-user no-padding fade-in">
|
||||
<section class="content settings-user no-padding">
|
||||
|
||||
<header class="user-profile-header">
|
||||
<img id="user-cover" class="cover-image" {{bind-attr src=cover title=coverTitle}} />
|
||||
|
@ -1,5 +1,4 @@
|
||||
/*global CodeMirror, device*/
|
||||
import mobileUtils from 'ghost/utils/mobile-utils';
|
||||
/*global CodeMirror, device, FastClick*/
|
||||
import createTouchEditor from 'ghost/assets/lib/touch-editor';
|
||||
|
||||
var setupMobileCodeMirror,
|
||||
@ -36,7 +35,10 @@ init = function init() {
|
||||
});
|
||||
|
||||
Ember.touchEditor = true;
|
||||
mobileUtils.initFastClick();
|
||||
//initialize FastClick to remove touch delays
|
||||
Ember.run.scheduleOnce('afterRender', null, function () {
|
||||
FastClick.attach(document.body);
|
||||
});
|
||||
TouchEditor = createTouchEditor();
|
||||
setupMobileCodeMirror();
|
||||
}
|
||||
|
@ -1,48 +0,0 @@
|
||||
/*global DocumentTouch,FastClick*/
|
||||
var hasTouchScreen,
|
||||
smallScreen,
|
||||
initFastClick,
|
||||
responsiveAction;
|
||||
|
||||
// Taken from "Responsive design & the Guardian" with thanks to Matt Andrews
|
||||
// Added !window._phantom so that the functional tests run as though this is not a touch screen.
|
||||
// In future we can do something more advanced here for testing both touch and non touch
|
||||
hasTouchScreen = function () {
|
||||
return !window._phantom &&
|
||||
(
|
||||
('ontouchstart' in window) ||
|
||||
(window.DocumentTouch && document instanceof DocumentTouch)
|
||||
);
|
||||
};
|
||||
|
||||
smallScreen = function () {
|
||||
if (window.matchMedia('(max-width: 1000px)').matches) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
initFastClick = function () {
|
||||
Ember.run.scheduleOnce('afterRender', null, function () {
|
||||
FastClick.attach(document.body);
|
||||
});
|
||||
};
|
||||
|
||||
responsiveAction = function responsiveAction(event, mediaCondition, cb) {
|
||||
if (!window.matchMedia(mediaCondition).matches) {
|
||||
return;
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
cb();
|
||||
};
|
||||
|
||||
export { hasTouchScreen, smallScreen, responsiveAction };
|
||||
export default {
|
||||
hasTouchScreen: hasTouchScreen,
|
||||
smallScreen: smallScreen,
|
||||
initFastClick: initFastClick,
|
||||
responsiveAction: responsiveAction
|
||||
};
|
17
ghost/admin/utils/mobile.js
Normal file
17
ghost/admin/utils/mobile.js
Normal file
@ -0,0 +1,17 @@
|
||||
var mobileQuery = matchMedia('(max-width: 800px)'),
|
||||
|
||||
responsiveAction = function responsiveAction(event, mediaCondition, cb) {
|
||||
if (!window.matchMedia(mediaCondition).matches) {
|
||||
return;
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
cb();
|
||||
};
|
||||
|
||||
export { mobileQuery, responsiveAction };
|
||||
export default {
|
||||
mobileQuery: mobileQuery,
|
||||
responsiveAction: responsiveAction
|
||||
};
|
@ -1,4 +1,4 @@
|
||||
import {responsiveAction} from 'ghost/utils/mobile-utils';
|
||||
import {responsiveAction} from 'ghost/utils/mobile';
|
||||
|
||||
var ApplicationView = Ember.View.extend({
|
||||
|
||||
|
@ -1,12 +1,10 @@
|
||||
import mobileUtils from 'ghost/utils/mobile-utils';
|
||||
import {responsiveAction} from 'ghost/utils/mobile';
|
||||
|
||||
var PostsView = Ember.View.extend({
|
||||
classNames: ['content-view-container'],
|
||||
tagName: 'section',
|
||||
|
||||
mobileInteractions: function () {
|
||||
var responsiveAction = mobileUtils.responsiveAction;
|
||||
|
||||
Ember.run.scheduleOnce('afterRender', this, function () {
|
||||
// ### Show content preview when swiping left on content list
|
||||
$('.manage').on('click', '.content-list ol li', function (event) {
|
||||
|
@ -1,39 +1,44 @@
|
||||
import mobileUtils from 'ghost/utils/mobile-utils';
|
||||
import {mobileQuery} from 'ghost/utils/mobile';
|
||||
|
||||
var SettingsView = Ember.View.extend({
|
||||
classNames: ['wrapper'],
|
||||
// used by SettingsContentBaseView and on resize to mobile from desktop
|
||||
showSettingsContent: function () {
|
||||
if (mobileQuery.matches) {
|
||||
$('.settings-sidebar').animate({right: '100%', left: '-110%', 'margin-right': '15px'}, 300);
|
||||
$('.settings-content').animate({right: '0', left: '0', 'margin-left': '0'}, 300);
|
||||
$('.settings-header-inner').css('display', 'block');
|
||||
}
|
||||
},
|
||||
// used by SettingsIndexView
|
||||
showSettingsMenu: function () {
|
||||
if (mobileQuery.matches) {
|
||||
$('.settings-header-inner').css('display', 'none');
|
||||
$('.settings-sidebar').animate({right: '0', left: '0', 'margin-right': '0'}, 300);
|
||||
$('.settings-content').animate({right: '-100%', left: '100%', 'margin-left': '15'}, 300);
|
||||
}
|
||||
},
|
||||
showAll: function () {
|
||||
//Remove any styles applied by jQuery#animate
|
||||
$('.settings-sidebar, .settings-content').removeAttr('style');
|
||||
},
|
||||
|
||||
mobileInteractions: function () {
|
||||
var responsiveAction = mobileUtils.responsiveAction;
|
||||
this.set('changeLayout', _.bind(function changeLayout(mq) {
|
||||
if (mq.matches) {
|
||||
//transitioned to mobile layout, so show content
|
||||
this.showSettingsContent();
|
||||
} else {
|
||||
//went from mobile to desktop
|
||||
this.showAll();
|
||||
}
|
||||
}, this));
|
||||
mobileQuery.addListener(this.changeLayout);
|
||||
}.on('didInsertElement'),
|
||||
|
||||
Ember.run.scheduleOnce('afterRender', this, function () {
|
||||
|
||||
// ### Hide settings page nav items (save, back etc) if the menu is showing
|
||||
responsiveAction(event, '(max-width: 650px)', function () {
|
||||
if ($('.settings-sidebar[style]').length === 0) {
|
||||
$('.settings-header-inner').css('display', 'none');
|
||||
}
|
||||
});
|
||||
|
||||
// ### Show settings options page when swiping left on settings menu link
|
||||
$('.settings').on('click', '.settings-menu li', function (event) {
|
||||
responsiveAction(event, '(max-width: 800px)', function () {
|
||||
$('.settings-sidebar').animate({right: '100%', left: '-110%', 'margin-right': '15px'}, 300);
|
||||
$('.settings-content').animate({right: '0', left: '0', 'margin-left': '0'}, 300);
|
||||
$('.settings-header-inner').css('display', 'block');
|
||||
});
|
||||
});
|
||||
|
||||
// ### Hide settings options page
|
||||
$('.settings').on('click', '.settings-content .button-back', function (event) {
|
||||
responsiveAction(event, '(max-width: 800px)', function () {
|
||||
$('.settings-header-inner').css('display', 'none');
|
||||
$('.settings-sidebar').animate({right: '0', left: '0', 'margin-right': '0'}, 300);
|
||||
$('.settings-content').animate({right: '-100%', left: '100%', 'margin-left': '15'}, 300);
|
||||
});
|
||||
});
|
||||
});
|
||||
}.on('didInsertElement')
|
||||
removeMobileInteractions: function () {
|
||||
mobileQuery.removeListener(this.changeLayout);
|
||||
}.on('willDestroyElement')
|
||||
});
|
||||
|
||||
export default SettingsView;
|
||||
|
5
ghost/admin/views/settings/apps.js
Normal file
5
ghost/admin/views/settings/apps.js
Normal file
@ -0,0 +1,5 @@
|
||||
import BaseView from 'ghost/views/settings/content-base';
|
||||
|
||||
var SettingsAppsView = BaseView.extend();
|
||||
|
||||
export default SettingsAppsView;
|
15
ghost/admin/views/settings/content-base.js
Normal file
15
ghost/admin/views/settings/content-base.js
Normal file
@ -0,0 +1,15 @@
|
||||
/**
|
||||
* All settings views other than the index should inherit from this base class.
|
||||
* It ensures that the correct screen is showing when a mobile user navigates
|
||||
* to a `settings.someRouteThatIsntIndex` route.
|
||||
*/
|
||||
|
||||
var SettingsContentBaseView = Ember.View.extend({
|
||||
tagName: 'section',
|
||||
classNames: ['settings-content', 'fade-in'],
|
||||
showContent: function () {
|
||||
this.get('parentView').showSettingsContent();
|
||||
}.on('didInsertElement')
|
||||
});
|
||||
|
||||
export default SettingsContentBaseView;
|
5
ghost/admin/views/settings/general.js
Normal file
5
ghost/admin/views/settings/general.js
Normal file
@ -0,0 +1,5 @@
|
||||
import BaseView from 'ghost/views/settings/content-base';
|
||||
|
||||
var SettingsGeneralView = BaseView.extend();
|
||||
|
||||
export default SettingsGeneralView;
|
8
ghost/admin/views/settings/index.js
Normal file
8
ghost/admin/views/settings/index.js
Normal file
@ -0,0 +1,8 @@
|
||||
var SettingsIndexView = Ember.View.extend({
|
||||
//Ensure that going to the index brings the menu into view on mobile.
|
||||
showMenu: function () {
|
||||
this.get('parentView').showSettingsMenu();
|
||||
}.on('didInsertElement')
|
||||
});
|
||||
|
||||
export default SettingsIndexView;
|
5
ghost/admin/views/settings/users.js
Normal file
5
ghost/admin/views/settings/users.js
Normal file
@ -0,0 +1,5 @@
|
||||
import BaseView from 'ghost/views/settings/content-base';
|
||||
|
||||
var SettingsUsersView = BaseView.extend();
|
||||
|
||||
export default SettingsUsersView;
|
Loading…
Reference in New Issue
Block a user