Implement Navigation item input behaviors

Closes #4807
This commit is contained in:
Jason Williams 2015-01-18 00:16:54 +00:00
parent a0e1053f76
commit 245b0bea12
10 changed files with 168 additions and 503 deletions

View File

@ -412,6 +412,12 @@
width: 100%;
}
.navigation-item-url {
.fake-placeholder {
color: lightgrey
}
}
@media (max-width: 600px) {
.navigation-item-label {
margin-bottom: 5px;

View File

@ -0,0 +1,76 @@
function joinUrlParts(url, path) {
if (path[0] !== '/' && url.slice(-1) !== '/') {
path = '/' + path;
} else if (path[0] === '/' && url.slice(-1) === '/') {
path = path.slice(1);
}
return url + path;
}
var NavItemUrlInputComponent = Ember.TextField.extend({
classNameBindings: ['fakePlaceholder'],
isBaseUrl: Ember.computed('baseUrl', 'value', function () {
return this.get('baseUrl') === this.get('value');
}),
fakePlaceholder: Ember.computed('isBaseUrl', 'hasFocus', function () {
return this.get('isBaseUrl') && !this.get('hasFocus');
}),
isRelative: Ember.computed('value', function () {
return !validator.isURL(this.get('value'));
}),
didInsertElement: function () {
var url = this.get('url'),
baseUrl = this.get('baseUrl');
// if we have a relative url, create the absolute url to be displayed in the input
if (this.get('isRelative')) {
url = joinUrlParts(baseUrl, url);
}
this.set('value', url);
this.sendAction('change', this.get('value'));
},
focusIn: function (event) {
this.set('hasFocus', true);
if (this.get('isBaseUrl')) {
// position the cursor at the end of the input
Ember.run.next(function (el) {
var length = el.value.length;
el.setSelectionRange(length, length);
}, event.target);
}
},
keyDown: function (event) {
// delete the "placeholder" value all at once
if (this.get('isBaseUrl') && (event.keyCode === 8 || event.keyCode === 46)) {
this.set('value', '');
event.preventDefault();
}
},
focusOut: function () {
this.set('hasFocus', false);
this.set('value', this.get('value').trim());
var url = this.get('value'),
baseUrl = this.get('baseUrl');
if (this.get('isRelative')) {
this.set('value', joinUrlParts(baseUrl, url));
}
this.sendAction('change', url);
}
});
export default NavItemUrlInputComponent;

View File

@ -0,0 +1,27 @@
var NavItemComponent = Ember.Component.extend({
classNames: 'navigation-item',
keyPress: function (event) {
// enter key
if (event.keyCode === 13) {
event.preventDefault();
this.get('controller').send('addItem');
}
},
actions: {
addItem: function () {
this.sendAction('addItem');
},
deleteItem: function (item) {
this.sendAction('deleteItem', item);
},
updateUrl: function (value) {
this.sendAction('updateUrl', value, this.get('navItem'));
}
}
});
export default NavItemComponent;

View File

@ -5,13 +5,19 @@ NavItem = Ember.Object.extend({
label: '',
url: '',
isBlank: Ember.computed('label', 'url', function () {
return Ember.isBlank(this.get('label')) && Ember.isBlank(this.get('url'));
isComplete: Ember.computed('label', 'url', function () {
return !(Ember.isBlank(this.get('label')) || Ember.isBlank(this.get('url')));
})
});
NavigationController = Ember.Controller.extend({
navigationItems: Ember.computed('model.navigation', function () {
blogUrl: Ember.computed('config.blogUrl', function () {
var url = this.get('config.blogUrl');
return url.slice(-1) !== '/' ? url + '/' : url;
}),
navigationItems: Ember.computed('model.navigation', function () {
var navItems,
lastItem;
@ -26,7 +32,7 @@ NavigationController = Ember.Controller.extend({
});
lastItem = navItems.get('lastObject');
if (!lastItem || !lastItem.get('isBlank')) {
if (!lastItem || lastItem.get('isComplete')) {
navItems.addObject(NavItem.create());
}
@ -50,7 +56,7 @@ NavigationController = Ember.Controller.extend({
var navItems = this.get('navigationItems'),
lastItem = navItems.get('lastObject');
if (lastItem && !lastItem.get('isBlank')) {
if (lastItem && lastItem.get('isComplete')) {
navItems.addObject(NavItem.create());
}
},
@ -63,6 +69,20 @@ NavigationController = Ember.Controller.extend({
this.get('navigationItems').removeObject(item);
},
updateUrl: function (url, navItem) {
if (!navItem) {
return;
}
if (Ember.isBlank(url)) {
navItem.set('url', this.get('blogUrl'));
return;
}
navItem.set('url', url);
},
save: function () {
var self = this,
navSetting,
@ -74,7 +94,7 @@ NavigationController = Ember.Controller.extend({
var label,
url;
if (!item || item.get('isBlank')) {
if (!item || !item.get('isComplete')) {
return;
}
@ -93,6 +113,11 @@ NavigationController = Ember.Controller.extend({
url = '/' + url;
}
// if navItem label is empty and URL is still the default, don't save
if (!label && url === '/') {
return;
}
return {label: label, url: url};
}).compact();

View File

@ -1,199 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Navigation</title>
<link rel="stylesheet" href="../assets/css/ghost.min.css">
<!-- Styles not needed for this. Just to get around various bits that Ember handles for us. -->
<style>
@media (max-width: 900px) {
.settings-nav {
top: 0;
left: -100%;
}
.settings-content {
margin-left: 0;
}
}
</style>
</head>
<body class="ember-application settings settings-view-general" data-apps="false" data-filestorage="true" data-blogurl="http://127.0.0.1:2368">
<div id="container"><a class="sr-only sr-only-focusable" href="#gh-main">Skip to main content</a>
<nav class="global-nav" role="navigation">
<a class="nav-item ghost-logo" href="/" title="/">
<div class="nav-label"><i class="icon-ghost"></i>
<span>Visit blog</span>
</div>
</a>
<a class="nav-item nav-content js-nav-item" href="/ghost/">
<div class="nav-label"><i class="icon-content"></i> Content</div>
</a>
<a class="nav-item nav-new js-nav-item" href="/ghost/editor/">
<div class="nav-label"><i class="icon-add"></i> New Post</div>
</a>
<a class="nav-item nav-settings js-nav-item active" href="/ghost/settings/">
<div class="nav-label"><i class="icon-settings2"></i> Settings</div>
</a>
<div class="nav-item user-menu" data-href="#">
<div class=" nav-label">
<div class="image">
<img src="../../../core/shared/img/user-image.png" alt="Paul Davis's profile picture">
</div>
<div class="name">
Paul Davis <i class="icon-chevron-down"></i>
<small>Profile &amp; Settings</small>
</div>
</div>
</div>
</nav>
<div class="nav-cover js-nav-cover"></div>
<main id="gh-main" class="viewport" role="main" data-notification-count="0" data-bindattr-448="448">
<aside class="notifications top">
</aside>
<aside class="notifications bottom">
</aside>
<div>
<header class="page-header">
<button class="menu-button js-menu-button" data-ember-action="1221">
<span class="sr-only">Menu</span>
</button>
<h2 class="page-title">Settings</h2>
</header>
<div class="page-content">
<nav class="settings-nav js-settings-nav">
<ul>
<li class="settings-nav-general icon-settings"><a href="#">General</a></li>
<li class="settings-nav-users icon-users"><a href="#">Users</a></li>
<li class="settings-nav-about icon-pacman"><a href="#">About</a></li>
<li class="settings-nav-general icon-compass active"><a class="active" href="#">Navigation</a></li>
</ul>
</nav>
<section class="settings-content js-settings-content fade-in">
<header class="settings-view-header">
<a class="btn btn-default btn-back active" href="#">Back</a>
<h2 class="page-title">Navigation</h2>
<section class="page-actions">
<button type="button" class="btn btn-blue">Save</button>
</section>
</header>
<section class="content settings-navigation">
<form id="settings-navigation" novalidate="novalidate">
<div class="navigation-item">
<button type="button" class="navigation-item-drag-handle icon-grab">
<span class="hidden">Reorder</span>
</button>
<div class="navigation-inputs">
<span class="navigation-item-label">
<input type="text" value="Home" placeholder="Label">
</span>
<span class="navigation-item-url">
<input type="text" value="http://my-ghost-blog.com/" placeholder="http://my-ghost-blog.com/">
</span>
</div>
<span class="navigation-item-action">
<button type="button" class="js-navigation-delete icon-trash">
<span class="hidden">Delete</span>
</button>
</span>
</div>
<div class="navigation-item">
<button type="button" class="navigation-item-drag-handle icon-grab">
<span class="hidden">Reorder</span>
</button>
<div class="navigation-inputs">
<span class="navigation-item-label">
<input type="text" value="About" placeholder="Label">
</span>
<span class="navigation-item-url">
<input type="text" value="http://my-ghost-blog.com/about" placeholder="http://my-ghost-blog.com/">
</span>
</div>
<span class="navigation-item-action">
<button type="button" class="js-navigation-delete icon-trash">
<span class="hidden">Delete</span>
</button>
</span>
</div>
<div class="navigation-item">
<button type="button" class="navigation-item-drag-handle icon-grab">
<span class="hidden">Reorder</span>
</button>
<div class="navigation-inputs">
<span class="navigation-item-label">
<input type="text" placeholder="Label">
</span>
<span class="navigation-item-url">
<input type="text" placeholder="http://my-ghost-blog.com/">
</span>
</div>
<span class="navigation-item-action">
<button type="button" class="js-add-navigation-link icon-add">
<span class="hidden">Add</span>
</button>
</span>
</div>
</form>
</section>
</section>
</div>
</div>
</main>
</div>
</body>
</html>
<script src="../../../bower_components/jquery/dist/jquery.js"></script>
<script>
$(function() {
$('.navigation-item').on('click', '.js-add-navigation-link', function(e){
e.preventDefault();
if ($('.navigation-item:last input').val() == '') {
return;
}
var blank_item = $('.navigation-item:last').clone(true);
var delete_button = $('.navigation-item:first .js-navigation-delete').clone(true);
$('#settings-navigation').append(blank_item);
$('.navigation-item:last input').val('');
$('.navigation-item:last').prev('.navigation-item').find('.js-add-navigation-link').replaceWith(delete_button);
});
$('.navigation-item').on('click', '.js-navigation-delete', function(e){
e.preventDefault();
$(this).parents('.navigation-item').remove();
});
});
</script>

View File

@ -1,264 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Tags</title>
<link rel="stylesheet" href="../assets/css/ghost.min.css">
<!-- Styles not needed for this. Just to get around various bits that Ember handles for us. -->
<style>
@media (max-width: 900px) {
.settings-menu {
top: 0;
left: -100%;
}
.settings-content {
margin-left: 0;
}
}
</style>
</head>
<body>
<div id="container">
<a class="sr-only sr-only-focusable" href="#gh-main">Skip to main content</a>
<nav class="global-nav" role="navigation">
<a class="nav-item ghost-logo" href="/" title="/">
<div class="nav-label"><i class="icon-ghost"></i>
<span>Visit blog</span>
</div>
</a>
<a class="nav-item nav-content" href="/ghost/">
<div class="nav-label"><i class="icon-content"></i> Content</div>
</a>
<a class="nav-item nav-new" href="/ghost/editor/">
<div class="nav-label"><i class="icon-add"></i> New Post</div>
</a>
<a class="nav-item nav-settings active" href="/ghost/settings/">
<div class="nav-label"><i class="icon-settings2"></i> Settings</div>
</a>
<div class="nav-item user-menu" data-href="#">
<div class="nav-label">
<div class="image">
<img src="../../../core/shared/img/user-image.png">
</div>
<div class="name">
Paul Davis <i class="icon-chevron-down"></i>
<small>Profile &amp; Settings</small>
</div>
</div>
</div>
</nav>
<main id="gh-main" class="viewport" role="main">
<aside class=" notifications top"></aside>
<aside class="notifications bottom"></aside>
<div>
<header class="page-header">
<a class="menu-button" href="#">
<span class="sr-only">Menu</span>
</a>
<h2 class="page-title">Tags</h2>
</header>
<div class="page-content">
<nav class="settings-nav">
<ul>
<li class="settings-nav-general icon-settings"><a href="#">General</a></li>
<li class="settings-nav-users icon-users"><a href="#">Users</a></li>
<li class="settings-nav-about icon-pacman"><a href="#">About</a></li>
<li class="settings-nav-tags icon-tag active"><a class="active" href="#">Tags</a></li>
</ul>
</nav>
<section class="settings-content fade-in">
<header class="settings-view-header">
<a class="btn btn-default btn-back active" href="/ghost/settings/">Back</a>
<h2 class="page-title">Tags</h2>
<section class="page-actions">
<button type="button" class="js-new-tag btn btn-green">New Tag</button>
<span class="tags-search">
<button href="#" class="btn btn-default">
<i class="icon-search"></i>
<span class="hidden">Search Tags</span>
</button>
<input type="text" class="tags-search-input">
</span>
</section>
</header>
<section class="content settings-tags">
<div class="settings-tag">
<a href="#" class="tag-title">News</a>
<span class="label label-default">/news</span>
<p class="tag-description">The latest news, reviews and information from around the world</p>
<span class="tags-count">20</span>
<div class="settings-tag">
<a href="#" class="tag-title">General</a>
<span class="label label-default">/news/general</span>
<p class="tag-description">My go-to category when Im not really sure what else to file news</p>
<span class="tags-count">7</span>
</div>
</div>
<div class="settings-tag">
<a href="#" class="tag-title">Image</a>
<span class="label label-alt">Private</span>
<p class="tag-description">All posts with the “image” post format</p>
<span class="tags-count">12</span>
</div>
<div class="settings-tag">
<a href="#" class="tag-title">Kittens</a>
<span class="label label-default">/kittens</span>
<p class="tag-description">My sordid past and wrongdoings</p>
<span class="tags-count">9</span>
<div class="settings-tag">
<a href="#" class="tag-title">A Short History of Nearly Everything</a>
<span class="label label-default">/kittens/a-short-history</span>
<span class="tags-count">4</span>
<div class="settings-tag">
<a href="#" class="tag-title">In Parts</a>
<span class="label label-default">/kittens/a-short-history/in-parts</span>
<p class="tag-description">Lorem ipsum kittens innit</p>
<span class="tags-count">2</span>
</div>
</div>
</div>
<div class="settings-tag">
<a href="#" class="tag-title">Video</a>
<span class="label label-default">Private</span>
<p class="tag-description">All posts containing a YouTube video link</p>
<span class="tags-count">6</span>
</div>
<div class="settings-tag">
<a href="#" class="tag-title">The End</a>
<span class="label label-default">/the-end</span>
<p class="tag-description">The final frontier</p>
<span class="tags-count">1</span>
</div>
</section>
<!-- .content.settings-tags -->
</section>
</div>
</div>
</main>
<div class="settings-menu-container">
<div class="settings-menu settings-menu-pane settings-menu-pane-in">
<div class="settings-menu-header">
<h4>Tag Settings</h4>
<button class="close icon-x settings-menu-header-action">
<span class="hidden">Close</span>
</button>
</div>
<div class="settings-menu-content">
<form>
<section class="image-uploader">
<span class="media">
<span class="hidden">Image Upload</span>
</span>
<img class="js-upload-target" style="display: none;" src="">
<div class="description">Add cover image</div>
</section>
<div class="form-group">
<label>Tag Name</label>
<!-- <span class="input-icon icon-link"> -->
<input class="js-tag-name" type="text" />
<!-- </span> -->
</div>
<div class="form-group">
<label>URL</label>
<!-- <span class="input-icon icon-link"> -->
<input class="js-tag-url" type="text" />
<!-- </span> -->
</div>
<div class="form-group">
<label>Description</label>
<!-- <span class="input-icon icon-link"> -->
<textarea class="js-tag-desc"></textarea>
<!-- </span> -->
</div>
<div class="form-group for-select">
<label for="activeTheme">Visibility</label>
<!-- <span class="input-icon icon-user"> -->
<span class="gh-select">
<select>
<option>Public</option>
<option>Private</option>
</select>
</span>
<!-- </span> -->
</div>
<div class="form-group for-select">
<label for="activeTheme">Parent Tag</label>
<!-- <span class="input-icon icon-user"> -->
<span class="gh-select">
<select>
<option>-</option>
<option>News</option>
<option>- News - Local</option>
</select>
</span>
<!-- </span> -->
</div>
<button class="btn btn-red icon-trash">Delete Tag</button>
<button class="btn btn-green tag-save-button">Add Tag</button>
</form>
</div>
</div>
</div>
</div>
<div class="content-cover" data-ember-action="1397"></div>
<script src="../../../bower_components/jquery/dist/jquery.js"></script>
<script>
$(function() {
$('.tags-search .btn').on('click', function(e) {
e.preventDefault();
$('.tags-search').toggleClass('opened');
$(this).toggleClass('active');
});
$(".tag-title, .settings-menu-header-action.close, .content-cover").on("click", function(e) {
e.preventDefault();
$('body').toggleClass('settings-menu-expanded');
$('.settings-menu-header h4').text('Tag Settings');
$('.settings-menu .icon-trash').show();
$('.settings-menu .tag-save-button').hide();
$('.js-tag-name').val('News');
$('.js-tag-url').val('yourblog.com/tag/news');
$('.js-tag-desc').html('The latest news, reviews and information from around the world');
});
$('.js-new-tag').on('click', function(e){
e.preventDefault();
$('body').toggleClass('settings-menu-expanded');
$('.settings-menu-header h4').text('New Tag');
$('.settings-menu .icon-trash').hide();
$('.settings-menu .tag-save-button').show();
$('.js-tag-name').val('');
$('.js-tag-url').val('');
$('.js-tag-desc').html('');
});
});
</script>
</body>
</html>

View File

@ -21,6 +21,10 @@ var NavigationRoute = AuthenticatedRoute.extend(CurrentUserSettings, {
actions: {
save: function () {
// since shortcuts are run on the route, we have to signal to the components
// on the page that we're about to save.
$('.page-actions .btn-blue').focus();
this.get('controller').send('save');
}
}

View File

@ -0,0 +1,22 @@
<button type="button" class="navigation-item-drag-handle icon-grab">
<span class="hidden">Reorder</span>
</button>
<div class="navigation-inputs">
<span class="navigation-item-label">
{{gh-trim-focus-input focus=navItem.last placeholder="Label" value=navItem.label}}
</span>
<span class="navigation-item-url">
{{gh-navitem-url-input baseUrl=baseUrl url=navItem.url change="updateUrl"}}
</span>
</div>
<span class="navigation-item-action">
{{#if navItem.last}}
<button type="button" class="add-navigation-link icon-add" {{action "addItem"}}>
<span class="hidden">Add</span>
</button>
{{else}}
<button type="button" class="navigation-delete icon-trash" {{action "deleteItem" navItem}}>
<span class="hidden">Delete</span>
</button>
{{/if}}
</span>

View File

@ -8,33 +8,8 @@
<section class="content settings-navigation">
<form id="settings-navigation" novalidate="novalidate">
{{#each navItem in navigationItems}}
<div class="navigation-item">
<button type="button" class="navigation-item-drag-handle icon-grab">
<span class="hidden">Reorder</span>
</button>
<div class="navigation-inputs">
<span class="navigation-item-label">
{{gh-trim-focus-input focus=navItem.last placeholder="Label" value=navItem.label}}
</span>
<span class="navigation-item-url">
{{gh-trim-focus-input focus=false placeholder="http://my-ghost-blog.com/" value=navItem.url}}
</span>
</div>
<span class="navigation-item-action">
{{#if navItem.last}}
<button type="button" class="add-navigation-link icon-add" {{action "addItem"}}>
<span class="hidden">Add</span>
</button>
{{else}}
<button type="button" class="navigation-delete icon-trash" {{action "deleteItem" navItem}}>
<span class="hidden">Delete</span>
</button>
{{/if}}
</span>
</div>
{{gh-navitem navItem=navItem baseUrl=blogUrl addItem="addItem" deleteItem="deleteItem" updateUrl="updateUrl"}}
{{/each}}
</form>
</section>
</section>

View File

@ -1,13 +1,6 @@
import BaseView from 'ghost/views/settings/content-base';
var SettingsNavigationView = BaseView.extend({
keyPress: function (event) {
// + character
if (event.keyCode === 43) {
event.preventDefault();
this.get('controller').send('addItem');
}
}
});
export default SettingsNavigationView;