0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-02-03 23:00:14 -05:00

Mobile markdown/preview tabs

refs #5652
- on mobile, add Markdown/Preview links to the editor footer that switch between the respective views
- temporary resolution to the inability to upload on mobile without access to the preview
This commit is contained in:
Kevin Ansfield 2015-09-01 12:22:10 +01:00
commit e8a28734bd
8 changed files with 111 additions and 39 deletions

View file

@ -9,7 +9,9 @@ export default Ember.Component.extend({
resizeService: Ember.inject.service(), resizeService: Ember.inject.service(),
calculatePreviewIsHidden: function () { calculatePreviewIsHidden: function () {
this.set('previewIsHidden', !this.$('.content-preview').is(':visible')); if (this.$('.content-preview').length) {
this.set('previewIsHidden', !this.$('.content-preview').is(':visible'));
}
}, },
didInsertElement: function () { didInsertElement: function () {

View file

@ -54,5 +54,15 @@ export default Ember.Component.extend({
previewPosition = scrollInfo.top * ratio; previewPosition = scrollInfo.top * ratio;
return previewPosition; return previewPosition;
}) }),
activeTab: 'markdown',
markdownActive: Ember.computed.equal('activeTab', 'markdown'),
previewActive: Ember.computed.equal('activeTab', 'preview'),
actions: {
selectTab: function (tab) {
this.set('activeTab', tab);
}
}
}); });

View file

@ -330,7 +330,7 @@ export default Ember.Controller.extend(SettingsMenuMixin, {
// If errors, notify and exit. // If errors, notify and exit.
if (errMessage) { if (errMessage) {
this.showErrors(errMessage); this.get('model.errors').add('post-setting-date', errMessage);
return; return;
} }
@ -356,47 +356,45 @@ export default Ember.Controller.extend(SettingsMenuMixin, {
}, },
setMetaTitle: function (metaTitle) { setMetaTitle: function (metaTitle) {
var self = this, var property = 'meta_title',
currentTitle = this.get('model.meta_title') || ''; model = this.get('model'),
currentTitle = model.get(property) || '';
// Only update if the title has changed // Only update if the title has changed
if (currentTitle === metaTitle) { if (currentTitle === metaTitle) {
return; return;
} }
this.set('model.meta_title', metaTitle); model.set(property, metaTitle);
// If this is a new post. Don't save the model. Defer the save // If this is a new post. Don't save the model. Defer the save
// to the user pressing the save button // to the user pressing the save button
if (this.get('model.isNew')) { if (model.get('isNew')) {
return; return;
} }
this.get('model').save().catch(function (errors) { model.save();
self.showErrors(errors);
});
}, },
setMetaDescription: function (metaDescription) { setMetaDescription: function (metaDescription) {
var self = this, var property = 'meta_description',
currentDescription = this.get('model.meta_description') || ''; model = this.get('model'),
currentDescription = model.get(property) || '';
// Only update if the description has changed // Only update if the description has changed
if (currentDescription === metaDescription) { if (currentDescription === metaDescription) {
return; return;
} }
this.set('model.meta_description', metaDescription); model.set(property, metaDescription);
// If this is a new post. Don't save the model. Defer the save // If this is a new post. Don't save the model. Defer the save
// to the user pressing the save button // to the user pressing the save button
if (this.get('model.isNew')) { if (model.get('isNew')) {
return; return;
} }
this.get('model').save().catch(function (errors) { model.save();
self.showErrors(errors);
});
}, },
setCoverImage: function (image) { setCoverImage: function (image) {

View file

@ -224,10 +224,25 @@ export default Ember.Mixin.create({
notifications.showNotification(message.htmlSafe(), {delayed: delay}); notifications.showNotification(message.htmlSafe(), {delayed: delay});
}, },
showErrorNotification: function (prevStatus, status, errors, delay) { showErrorAlert: function (prevStatus, status, errors, delay) {
var message = this.messageMap.errors.post[prevStatus][status], var message = this.messageMap.errors.post[prevStatus][status],
error = (errors && errors[0] && errors[0].message) || 'Unknown Error', notifications = this.get('notifications'),
notifications = this.get('notifications'); error;
function isString(str) {
/*global toString*/
return toString.call(str) === '[object String]';
}
if (errors && isString(errors)) {
error = errors;
} else if (errors && errors[0] && isString(errors[0])) {
error = errors[0];
} else if (errors && errors[0] && errors[0].message && isString(errors[0].message)) {
error = errors[0].message;
} else {
error = 'Unknown Error';
}
message += '<br />' + error; message += '<br />' + error;
@ -306,7 +321,8 @@ export default Ember.Mixin.create({
}); });
}).catch(function (errors) { }).catch(function (errors) {
if (!options.silent) { if (!options.silent) {
self.showErrorNotification(prevStatus, self.get('model.status'), errors); errors = errors || self.get('model.errors.messages');
self.showErrorAlert(prevStatus, self.get('model.status'), errors);
} }
self.set('model.status', prevStatus); self.set('model.status', prevStatus);

View file

@ -73,15 +73,45 @@
line-height: 1em; line-height: 1em;
} }
.editor .floatingheader a { .editor .floatingheader a {
padding: 5px 15px;
color: var(--midgrey); color: var(--midgrey);
} }
.editor .floatingheader a.active {
font-weight: bold;
}
.editor .floatingheader a:first-of-type {
padding-left: 0;
}
.editor .floatingheader a:last-of-type {
padding-right: 0;
}
.editor .floatingheader span a:not(:first-of-type) {
border-left: 1px solid #dfe1e3;
}
.editor .floatingheader .mobile-tabs {
display: none;
}
/* Switch to 1 col editor on small screens */ /* Switch to 1 col editor on small screens */
@media (max-width: 1000px) { @media (max-width: 1000px) {
.editor .entry-markdown { .editor .entry-markdown,
width: 100%;
}
.editor .entry-preview { .editor .entry-preview {
width: 100%;
border-left: none;
}
/* We can't use display:none here as we want to keep widths/heights
* so that scrolling is kept in sync */
.editor .entry-markdown:not(.active),
.editor .entry-preview:not(.active) {
visibility: hidden;
position: absolute;
z-index: -1;
height: 100%;
}
.editor .floatingheader .mobile-tabs {
display: inline;
}
.editor .floatingheader .desktop-tabs {
display: none; display: none;
} }
} }

View file

@ -1,7 +1,7 @@
{{#gh-editor editorScrollInfo=editorScrollInfo as |ghEditor|}} {{#gh-editor editorScrollInfo=editorScrollInfo as |ghEditor|}}
<header class="view-header"> <header class="view-header">
{{#gh-view-title classNames="gh-editor-title" openMobileMenu="openMobileMenu"}} {{#gh-view-title classNames="gh-editor-title" openMobileMenu="openMobileMenu"}}
{{gh-trim-focus-input type="text" id="entry-title" class="gh-input" placeholder="Your Post Title" value=model.titleScratch {{gh-trim-focus-input type="text" id="entry-title"placeholder="Your Post Title" value=model.titleScratch
tabindex="1" focus=shouldFocusTitle}} tabindex="1" focus=shouldFocusTitle}}
{{/gh-view-title}} {{/gh-view-title}}
<section class="view-actions"> <section class="view-actions">
@ -22,9 +22,13 @@
</header> </header>
<section class="view-container view-editor"> <section class="view-container view-editor">
<section class="entry-markdown js-entry-markdown"> <section class="entry-markdown js-entry-markdown {{if ghEditor.markdownActive 'active'}}">
<header class="floatingheader"> <header class="floatingheader">
<span>Markdown</span> <span class="desktop-tabs">Markdown</span>
<span class="mobile-tabs">
<a href="#" {{action 'selectTab' 'markdown' target=ghEditor}} class="{{if ghEditor.markdownActive 'active'}}">Markdown</a>
<a href="#" {{action 'selectTab' 'preview' target=ghEditor}} class="{{if ghEditor.previewActive 'active'}}">Preview</a>
</span>
<a class="markdown-help" href="" {{action "openModal" "markdown"}}><i class="icon-markdown"></i></a> <a class="markdown-help" href="" {{action "openModal" "markdown"}}><i class="icon-markdown"></i></a>
</header> </header>
<section id="entry-markdown-content" class="entry-markdown-content"> <section id="entry-markdown-content" class="entry-markdown-content">
@ -32,10 +36,14 @@
</section> </section>
</section> </section>
<section class="entry-preview js-entry-preview"> <section class="entry-preview js-entry-preview {{if ghEditor.previewActive 'active'}}">
<header class="floatingheader"> <header class="floatingheader">
<span>Preview</span> <span class="desktop-tabs">Preview</span>
<span class="entry-word-count js-entry-word-count">{{gh-count-words model.scratch}}</span> <span class="mobile-tabs">
<a href="#" {{action 'selectTab' 'markdown' target=ghEditor}} class="{{if ghEditor.markdownActive 'active'}}">Markdown</a>
<a href="#" {{action 'selectTab' 'preview' target=ghEditor}} class="{{if ghEditor.previewActive 'active'}}">Preview</a>
</span>
<span class="entry-word-count">{{gh-count-words model.scratch}}</span>
</header> </header>
<section class="entry-preview-content js-entry-preview-content"> <section class="entry-preview-content js-entry-preview-content">
{{gh-ed-preview classNames="rendered-markdown js-rendered-markdown" {{gh-ed-preview classNames="rendered-markdown js-rendered-markdown"

View file

@ -21,17 +21,18 @@
{{/if}} {{/if}}
<span class="input-icon icon-link"> <span class="input-icon icon-link">
{{gh-input class="gh-input post-setting-slug" id="url" value=slugValue name="post-setting-slug" focus-out="updateSlug" selectOnClick="true" stopEnterKeyDownPropagation="true"}} {{gh-input class="post-setting-slug" id="url" value=slugValue name="post-setting-slug" focus-out="updateSlug" selectOnClick="true" stopEnterKeyDownPropagation="true"}}
</span> </span>
{{gh-url-preview slug=slugValue tagName="p" classNames="description"}} {{gh-url-preview slug=slugValue tagName="p" classNames="description"}}
</div> </div>
<div class="form-group"> {{#gh-form-group errors=model.errors property="post-setting-date"}}
<label for="post-setting-date">Publish Date</label> <label for="post-setting-date">Publish Date</label>
<span class="input-icon icon-calendar"> <span class="input-icon icon-calendar">
{{gh-input class="gh-input post-setting-date" id="post-setting-date" value=publishedAtValue name="post-setting-date" focus-out="setPublishedAt" stopEnterKeyDownPropagation="true"}} {{gh-input class="post-setting-date" id="post-setting-date" value=publishedAtValue name="post-setting-date" focus-out="setPublishedAt" stopEnterKeyDownPropagation="true"}}
</span> </span>
</div> {{gh-error-message errors=model.errors property="post-setting-date"}}
{{/gh-form-group}}
<div class="form-group"> <div class="form-group">
<label for="tag-input">Tags</label> <label for="tag-input">Tags</label>
@ -104,17 +105,19 @@
<div class="settings-menu-content"> <div class="settings-menu-content">
<form> <form>
<div class="form-group"> {{#gh-form-group errors=model.errors property="meta_title"}}
<label for="meta-title">Meta Title</label> <label for="meta-title">Meta Title</label>
{{gh-input class="gh-input post-setting-meta-title" id="meta-title" value=metaTitleScratch name="post-setting-meta-title" focus-out="setMetaTitle" stopEnterKeyDownPropagation="true"}} {{gh-input class="post-setting-meta-title" id="meta-title" value=metaTitleScratch name="post-setting-meta-title" focus-out="setMetaTitle" stopEnterKeyDownPropagation="true"}}
<p>Recommended: <b>70</b> characters. Youve used {{gh-count-down-characters metaTitleScratch 70}}</p> <p>Recommended: <b>70</b> characters. Youve used {{gh-count-down-characters metaTitleScratch 70}}</p>
</div> {{gh-error-message errors=model.errors property="meta_title"}}
{{/gh-form-group}}
<div class="form-group"> {{#gh-form-group errors=model.errors property="meta_description"}}
<label for="meta-description">Meta Description</label> <label for="meta-description">Meta Description</label>
{{gh-textarea class="gh-input post-setting-meta-description" id="meta-description" value=metaDescriptionScratch name="post-setting-meta-description" focus-out="setMetaDescription" stopEnterKeyDownPropagation="true"}} {{gh-textarea class="gh-input post-setting-meta-description" id="meta-description" value=metaDescriptionScratch name="post-setting-meta-description" focus-out="setMetaDescription" stopEnterKeyDownPropagation="true"}}
<p>Recommended: <b>156</b> characters. Youve used {{gh-count-down-characters metaDescriptionScratch 156}}</p> <p>Recommended: <b>156</b> characters. Youve used {{gh-count-down-characters metaDescriptionScratch 156}}</p>
</div> {{gh-error-message errors=model.errors property="meta_description"}}
{{/gh-form-group}}
<div class="form-group"> <div class="form-group">
<label>Search Engine Result Preview</label> <label>Search Engine Result Preview</label>

View file

@ -10,6 +10,11 @@ var PostValidator = BaseValidator.create({
model.get('errors').add('title', 'You must specify a title for the post.'); model.get('errors').add('title', 'You must specify a title for the post.');
this.invalidate(); this.invalidate();
} }
if (!validator.isLength(title, 0, 150)) {
model.get('errors').add('title', 'Title cannot be longer than 150 characters.');
this.invalidate();
}
}, },
metaTitle: function (model) { metaTitle: function (model) {