diff --git a/core/client/components/gh-tab-pane.js b/core/client/components/gh-tab-pane.js new file mode 100644 index 0000000000..85b1a68099 --- /dev/null +++ b/core/client/components/gh-tab-pane.js @@ -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; diff --git a/core/client/components/gh-tab.js b/core/client/components/gh-tab.js new file mode 100644 index 0000000000..c60641db7e --- /dev/null +++ b/core/client/components/gh-tab.js @@ -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; diff --git a/core/client/components/gh-tabs-manager.js b/core/client/components/gh-tabs-manager.js new file mode 100644 index 0000000000..cbee603b7a --- /dev/null +++ b/core/client/components/gh-tabs-manager.js @@ -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; diff --git a/core/client/controllers/application.js b/core/client/controllers/application.js index 4bf4e2ef88..fa1c6471bd 100644 --- a/core/client/controllers/application.js +++ b/core/client/controllers/application.js @@ -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); } diff --git a/core/client/controllers/post-settings-menu.js b/core/client/controllers/post-settings-menu.js index e11193945b..50403a4909 100644 --- a/core/client/controllers/post-settings-menu.js +++ b/core/client/controllers/post-settings-menu.js @@ -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); } } }); diff --git a/core/client/mixins/editor-route-base.js b/core/client/mixins/editor-route-base.js index 73a928ba58..9f7560434d 100644 --- a/core/client/mixins/editor-route-base.js +++ b/core/client/mixins/editor-route-base.js @@ -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'); } }, diff --git a/core/client/routes/application.js b/core/client/routes/application.js index b8497502b5..12b2440b80 100644 --- a/core/client/routes/application.js +++ b/core/client/routes/application.js @@ -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(); diff --git a/core/client/routes/editor/edit.js b/core/client/routes/editor/edit.js index ec8f9c5b0d..b3e9e8d0f5 100644 --- a/core/client/routes/editor/edit.js +++ b/core/client/routes/editor/edit.js @@ -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. diff --git a/core/client/routes/editor/new.js b/core/client/routes/editor/new.js index 7b3bfeff55..63f5ebb45a 100644 --- a/core/client/routes/editor/new.js +++ b/core/client/routes/editor/new.js @@ -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. diff --git a/core/client/templates/-publish-bar.hbs b/core/client/templates/-publish-bar.hbs index f1eaf9bad9..48b17279ec 100644 --- a/core/client/templates/-publish-bar.hbs +++ b/core/client/templates/-publish-bar.hbs @@ -2,7 +2,7 @@ {{render 'post-tags-input'}}
- + {{view "editor-save-button" id="entry-actions"}}
diff --git a/core/client/templates/application.hbs b/core/client/templates/application.hbs index 8bdddb3911..05fee0818f 100644 --- a/core/client/templates/application.hbs +++ b/core/client/templates/application.hbs @@ -1,4 +1,3 @@ -
Skip to main content {{#unless hideNav}} @@ -14,5 +13,3 @@ {{outlet modal}} {{outlet settings-menu}} - -
\ No newline at end of file diff --git a/core/client/templates/post-settings-menu.hbs b/core/client/templates/post-settings-menu.hbs index e64956dd2e..870e79fb83 100644 --- a/core/client/templates/post-settings-menu.hbs +++ b/core/client/templates/post-settings-menu.hbs @@ -1,9 +1,10 @@ -
-
-
+
+{{!----for halfdan:{{#gh-tabs-manager selected="showSubview" id="entry-controls" classNameBindings="isNew:unsaved :right-outlet"}}----}} +
+

Post Settings

- +
{{gh-uploader uploaded="setCoverImage" canceled="clearCoverImage" image=image tagName="section"}} @@ -43,46 +44,46 @@

Static Page

-
-
- -
\ No newline at end of file + {{!---{{/gh-tab-pane}}----}} +
--> + +{{!---{{/gh-tabs-manager}} ---}} diff --git a/core/client/views/application.js b/core/client/views/application.js index 2180ff2389..d60b2d8c23 100644 --- a/core/client/views/application.js +++ b/core/client/views/application.js @@ -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; diff --git a/core/client/views/post-settings-menu.js b/core/client/views/post-settings-menu.js index 80381ba2ee..189808dea5 100644 --- a/core/client/views/post-settings-menu.js +++ b/core/client/views/post-settings-menu.js @@ -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;