Merge pull request #4042 from novaugust/psm2-tabs

Add Tab components
This commit is contained in:
Hannah Wolfe 2014-09-19 17:33:57 +01:00
commit 69ea28e951
14 changed files with 217 additions and 60 deletions

View File

@ -0,0 +1,27 @@
//See gh-tabs-manager.js for use
var TabPane = Ember.Component.extend({
classNameBindings: ['active'],
tabsManager: Ember.computed(function () {
return this.nearestWithProperty('isTabsManager');
}),
tab: Ember.computed('tabsManager.tabs.@each', function () {
var index = this.get('tabsManager.tabPanes').indexOf(this),
tabs = this.get('tabsManager.tabs');
return tabs && tabs.objectAt(index);
}),
active: Ember.computed.alias('tab.active'),
// Register with the tabs manager
registerWithTabs: function () {
this.get('tabsManager').registerTabPane(this);
}.on('didInsertElement'),
unregisterWithTabs: function () {
this.get('tabsManager').unregisterTabPane(this);
}.on('willDestroyElement')
});
export default TabPane;

View File

@ -0,0 +1,30 @@
//See gh-tabs-manager.js for use
var Tab = Ember.Component.extend({
tabsManager: Ember.computed(function () {
return this.nearestWithProperty('isTabsManager');
}),
active: Ember.computed('tabsManager.activeTab', function () {
return this.get('tabsManager.activeTab') === this;
}),
index: Ember.computed('tabsManager.tabs.@each', function () {
return this.get('tabsManager.tabs').indexOf(this);
}),
// Select on click
click: function () {
this.get('tabsManager').select(this);
},
// Registration methods
registerWithTabs: function () {
this.get('tabsManager').registerTab(this);
}.on('didInsertElement'),
unregisterWithTabs: function () {
this.get('tabsManager').unregisterTab(this);
}.on('willDestroyElement')
});
export default Tab;

View File

@ -0,0 +1,80 @@
/**
Heavily inspired by ic-tabs (https://github.com/instructure/ic-tabs)
Three components work together for smooth tabbing.
1. tabs-manager (gh-tabs)
2. tab (gh-tab)
3. tab-pane (gh-tab-pane)
## Usage:
The tabs-manager must wrap all tab and tab-pane components,
but they can be nested at any level.
A tab and its pane are tied together via their order.
So, the second tab within a tab manager will activate
the second pane within that manager.
```hbs
{{#gh-tabs-manager}}
{{#gh-tab}}
First tab
{{/gh-tab}}
{{#gh-tab}}
Second tab
{{/gh-tab}}
....
{{#gh-tab-pane}}
First pane
{{/gh-tab-pane}}
{{#gh-tab-pane}}
Second pane
{{/gh-tab-pane}}
{{/gh-tabs-manager}}
```
## Options:
the tabs-manager will send a "selected" action whenever one of its
tabs is clicked.
```hbs
{{#gh-tabs-manager selected="myAction"}}
....
{{/gh-tabs-manager}}
```
## Styling:
Both tab and tab-pane elements have an "active"
class applied when they are active.
*/
var TabsManager = Ember.Component.extend({
activeTab: null,
tabs: [],
tabPanes: [],
// Called when a gh-tab is clicked.
select: function (tab) {
this.set('activeTab', tab);
this.sendAction('selected');
},
//Used by children to find this tabsManager
isTabsManager: true,
// Register tabs and their panes to allow for
// interaction between components.
registerTab: function (tab) {
this.get('tabs').addObject(tab);
},
unregisterTab: function (tab) {
this.get('tabs').removeObject(tab);
},
registerTabPane: function (tabPane) {
this.get('tabPanes').addObject(tabPane);
},
unregisterTabPane: function (tabPane) {
this.get('tabPanes').removeObject(tabPane);
}
});
export default TabsManager;

View File

@ -3,12 +3,9 @@ var ApplicationController = Ember.Controller.extend({
topNotificationCount: 0,
showGlobalMobileNav: false,
showRightOutlet: false,
actions: {
toggleMenu: function () {
this.toggleProperty('showMenu');
},
topNotificationChange: function (count) {
this.set('topNotificationCount', count);
}

View File

@ -4,18 +4,18 @@ import SlugGenerator from 'ghost/models/slug-generator';
import boundOneWay from 'ghost/utils/bound-one-way';
var PostSettingsMenuController = Ember.ObjectController.extend({
init: function () {
this._super();
},
initializeObserver: function () {
// when creating a new post we want to observe the title
// to generate the post's slug
if (this.get('isNew')) {
this.addObserver('titleScratch', this, 'titleObserver');
//State for if the user is viewing a tab's pane.
needs: 'application',
isViewingSubview: Ember.computed('controllers.application.showRightOutlet', function (key, value) {
// Not viewing a subview if we can't even see the PSM
if (!this.get('controllers.application.showRightOutlet')) {
return false;
}
}.observes('model'),
if (arguments.length > 1) {
return value;
}
return false;
}),
selectedAuthor: null,
initializeSelectedAuthor: function () {
var self = this;
@ -96,6 +96,15 @@ var PostSettingsMenuController = Ember.ObjectController.extend({
self.set('slugPlaceholder', slug);
});
},
// observe titleScratch, keeping the post's slug in sync
// with it until saved for the first time.
addTitleObserver: function () {
if (this.get('isNew')) {
this.addObserver('titleScratch', this, 'titleObserver');
}
}.observes('model'),
titleObserver: function () {
if (this.get('isNew') && !this.get('title')) {
Ember.run.debounce(this, 'generateSlugPlaceholder', 700);
@ -293,6 +302,14 @@ var PostSettingsMenuController = Ember.ObjectController.extend({
self.showErrors(errors);
self.get('model').rollback();
});
},
showSubview: function () {
this.set('isViewingSubview', true);
},
closeSubview: function () {
this.set('isViewingSubview', false);
}
}
});

View File

@ -20,12 +20,6 @@ var EditorRouteBase = Ember.Mixin.create(styleBody, ShortcutsRoute, loadingIndic
//The actual functionality is implemented in utils/codemirror-shortcuts
codeMirrorShortcut: function (options) {
this.get('controller.codemirror').shortcut(options.type);
},
togglePostSettings: function () {
Ember.$('body').toggleClass('right-outlet-expanded');
},
closePostSettings: function () {
Ember.$('body').removeClass('right-outlet-expanded');
}
},

View File

@ -29,6 +29,13 @@ var ApplicationRoute = Ember.Route.extend(SimpleAuth.ApplicationRouteMixin, Shor
this.toggleProperty('controller.showGlobalMobileNav');
},
toggleRightOutlet: function () {
this.toggleProperty('controller.showRightOutlet');
},
closeRightOutlet: function () {
this.set('controller.showRightOutlet', false);
},
closePopups: function () {
this.get('popover').closePopovers();
this.get('notifications').closeAll();

View File

@ -77,6 +77,8 @@ var EditorEditRoute = Ember.Route.extend(SimpleAuth.AuthenticatedRouteMixin, bas
isDeleted = model.get('isDeleted'),
modelIsDirty = model.get('isDirty');
this.send('closeRightOutlet');
// when `isDeleted && isSaving`, model is in-flight, being saved
// to the server. when `isDeleted && !isSaving && !modelIsDirty`,
// the record has already been deleted and the deletion persisted.

View File

@ -34,6 +34,8 @@ var EditorNewRoute = Ember.Route.extend(SimpleAuth.AuthenticatedRouteMixin, base
isDeleted = model.get('isDeleted'),
modelIsDirty = model.get('isDirty');
this.send('closeRightOutlet');
// when `isDeleted && isSaving`, model is in-flight, being saved
// to the server. when `isDeleted && !isSaving && !modelIsDirty`,
// the record has already been deleted and the deletion persisted.

View File

@ -2,7 +2,7 @@
{{render 'post-tags-input'}}
<div class="right">
<button type="button" class='post-settings' {{action "togglePostSettings"}}></button>
<button type="button" class='post-settings' {{action "toggleRightOutlet"}}></button>
{{view "editor-save-button" id="entry-actions"}}
</div>

View File

@ -1,4 +1,3 @@
<div id="container">
<a class="sr-only sr-only-focusable" href="#gh-main">Skip to main content</a>
{{#unless hideNav}}
@ -14,5 +13,3 @@
{{outlet modal}}
{{outlet settings-menu}}
</div>

View File

@ -1,9 +1,10 @@
<div class="editor-cover" {{action "closePostSettings"}}></div>
<div id="entry-controls" {{bind-attr class=":right-outlet isNew:unsaved"}}>
<div class="post-settings-menu outlet-pane outlet-pane-in">
<div class="editor-cover" {{action "closeRightOutlet"}}></div>
{{!----for halfdan:{{#gh-tabs-manager selected="showSubview" id="entry-controls" classNameBindings="isNew:unsaved :right-outlet"}}----}}
<div id="entry-controls" {{bind-attr class="isNew:unsaved :right-outlet"}}>
<div {{bind-attr class="isViewingSubview:outlet-pane-out-left:outlet-pane-in :post-settings-menu :outlet-pane"}}>
<div class="post-settings-header">
<h4>Post Settings</h4>
<button class="close icon-x post-settings-header-action" {{action "closePostSettings"}}><span class="hidden">Close</span></button>
<button class="close icon-x post-settings-header-action" {{action "closeRightOutlet"}}><span class="hidden">Close</span></button>
</div>
<div class="post-settings-content">
{{gh-uploader uploaded="setCoverImage" canceled="clearCoverImage" image=image tagName="section"}}
@ -43,46 +44,46 @@
<p>Static Page</p>
</label>
</div>
<!--
<ul class="nav-list nav-list-block">
<li class="nav-list-item">
<a href="#">
<b>Revision History</b>
<span>12 versions of this post by 3 people.</span>
</a>
</li>
<li class="nav-list-item">
<a href="#">
<b>Advanced Settings</b>
<span>Convert to a page, mark as featured.</span>
</a>
</li>
<li class="nav-list-item">
{{!--
{{#gh-tab tagName="li" classNames="nav-list-item"}}
<a href="#">
<b>Meta Data</b>
<span>Extra content for SEO and social media.</span>
</a>
</li>
<li class="nav-list-item">
{{/gh-tab}}
{{#gh-tab tagName="li" classNames="nav-list-item"}}
<a href="#">
<b>Advanced Settings</b>
<span>Convert to a page, mark as featured.</span>
</a>
{{/gh-tab}}
{{#gh-tab tagName="li" classNames="nav-list-item"}}
<a href="#">
<b>Revision History</b>
<span>12 versions of this post by 3 people.</span>
</a>
{{/gh-tab}}
{{#gh-tab tagName="li" classNames="nav-list-item"}}
<a href="#">
<b>Custom App</b>
<span>Registered an advanced setting panel.</span>
</a>
</li>
{{/gh-tab}}
--}}
</ul>
-->
<button type="button" class="btn btn-red icon-trash delete" {{action "openModal" "delete-post" this}}>Delete This Post</button>
</form>
</div><!-- .post-settings-content -->
</div><!-- .post-settings-menu -->
<!--
<div class="post-settings-menu outlet-pane outlet-pane-out-right">
</div><!-- .post-settings-menu --
<div {{bind-attr class="isViewingSubview:outlet-pane-in:outlet-pane-out-right :post-settings-menu :outlet-pane"}}>
{{!-----{{#gh-tab-pane}}-----}}
<div class="post-settings-header subview">
<button class="back icon-chevron-left post-settings-header-action"><span class="hidden">Back</span></button>
<button {{action "closeSubview"}} class="back icon-chevron-left post-settings-header-action"><span class="hidden">Back</span></button>
<h4>Meta Data</h4>
</div>
<div class="post-settings-content">
<div class="form-group">
<label for="blog-title">Meta Title</label>
@ -105,6 +106,7 @@
</div>
</div>
</div>
</div>
-->
</div>
{{!---{{/gh-tab-pane}}----}}
</div>-->
</div>
{{!---{{/gh-tabs-manager}} ---}}

View File

@ -1,6 +1,8 @@
import mobileQuery from 'ghost/utils/mobile';
var ApplicationView = Ember.View.extend({
elementId: 'container',
blogRoot: Ember.computed.alias('controller.ghostPaths.blogRoot'),
setupGlobalMobileNav: function () {
@ -56,6 +58,10 @@ var ApplicationView = Ember.View.extend({
mobileQuery.removeListener(this.closeGlobalMobileNavOnDesktop);
}.on('willDestroyElement'),
toggleRightOutletBodyClass: function () {
$('body').toggleClass('right-outlet-expanded', this.get('controller.showRightOutlet'));
}.observes('controller.showRightOutlet')
});
export default ApplicationView;

View File

@ -7,11 +7,7 @@ var PostSettingsMenuView = Ember.View.extend({
publishedAtBinding: Ember.Binding.oneWay('controller.publishedAt'),
datePlaceholder: Ember.computed('controller.publishedAt', function () {
return formatDate(moment());
}),
animateOut: function () {
$('body').removeClass('right-outlet-expanded');
}.on('willDestroyElement')
})
});
export default PostSettingsMenuView;