mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-12-26 12:21:36 +03:00
Added first version of feature image with caption inside editor (#1999)
refs https://github.com/TryGhost/Team/issues/771 - updated `<GhImageUploaderWithPreview>` to take an `@includeMetadata` argument that shows a basic html supporting caption field underneath (ready for toggle between caption/alt but not fully implemented) - added feature image alt/caption properties to post model - updated UI behind "featureImageMeta" labs flag - added the feature image uploader to the top of `<GhKoenigEditor>` for display above the editor title - removed feature image uploader from post settings menu - added labs flag checkbox
This commit is contained in:
parent
85c6655959
commit
ceb16b5e9f
@ -1,18 +1,33 @@
|
|||||||
{{#if this.image}}
|
{{#if @image}}
|
||||||
<div class="gh-image-uploader -with-image">
|
<div class="gh-image-uploader -with-image">
|
||||||
<div><img src={{this.image}}></div>
|
<div><img src={{@image}}></div>
|
||||||
<a class="image-cancel" title="Delete" {{action "remove"}}>
|
<a class="image-cancel" title="Delete" {{on "click" @remove}}>
|
||||||
{{svg-jar "trash"}}
|
{{svg-jar "trash"}}
|
||||||
<span class="hidden">Delete</span>
|
<span class="hidden">Delete</span>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
{{else}}
|
{{else}}
|
||||||
<GhImageUploader
|
<GhImageUploader
|
||||||
@text={{this.text}}
|
@text={{@text}}
|
||||||
@altText={{this.altText}}
|
@altText={{@altText}}
|
||||||
@allowUnsplash={{this.allowUnsplash}}
|
@allowUnsplash={{@allowUnsplash}}
|
||||||
@update={{action "update"}}
|
@update={{@update}}
|
||||||
@uploadStarted={{action "uploadStarted"}}
|
@uploadStarted={{optional @uploadStarted}}
|
||||||
@uploadFinished={{action "uploadFinished"}}
|
@uploadFinished={{optional @uploadFinished}}
|
||||||
/>
|
/>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
|
{{#if @includeMetadata}}
|
||||||
|
{{#if this.isEditingAlt}}
|
||||||
|
{{else}}
|
||||||
|
<KoenigBasicHtmlInput
|
||||||
|
@html={{@caption}}
|
||||||
|
@placeholder={{if this.captionInputFocused "" @captionPlaceholder}}
|
||||||
|
@class="miw-100 tc bn form-text bg-transparent pr8 pl8"
|
||||||
|
@name="caption"
|
||||||
|
@onChange={{@updateCaption}}
|
||||||
|
@onFocus={{fn (mut this.captionInputFocused) true}}
|
||||||
|
@onBlur={{fn (mut this.captionInputFocused) false}}
|
||||||
|
/>
|
||||||
|
{{/if}}
|
||||||
|
{{/if}}
|
@ -1,36 +1,7 @@
|
|||||||
import Component from '@ember/component';
|
import Component from '@glimmer/component';
|
||||||
|
import {tracked} from '@glimmer/tracking';
|
||||||
|
|
||||||
export default Component.extend({
|
export default class GhImageUploaderWithPreviewComponent extends Component {
|
||||||
|
@tracked isEditingAlt = false;
|
||||||
allowUnsplash: false,
|
@tracked captionInputFocused = false;
|
||||||
|
}
|
||||||
actions: {
|
|
||||||
update() {
|
|
||||||
let action = this.update;
|
|
||||||
if (action) {
|
|
||||||
action(...arguments);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
uploadStarted() {
|
|
||||||
let action = this.uploadStarted;
|
|
||||||
if (action) {
|
|
||||||
action(...arguments);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
uploadFinished() {
|
|
||||||
let action = this.uploadFinished;
|
|
||||||
if (action) {
|
|
||||||
action(...arguments);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
remove() {
|
|
||||||
let action = this.remove;
|
|
||||||
if (action) {
|
|
||||||
action();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
@ -5,6 +5,24 @@
|
|||||||
{{on "mousedown" this.trackMousedown}}
|
{{on "mousedown" this.trackMousedown}}
|
||||||
{{on "mouseup" this.focusEditor}}
|
{{on "mouseup" this.focusEditor}}
|
||||||
>
|
>
|
||||||
|
{{#if (feature "featureImageMeta")}}
|
||||||
|
<div class="gh-editor-feature-image">
|
||||||
|
<GhImageUploaderWithPreview
|
||||||
|
@image={{@featureImage}}
|
||||||
|
@text="Upload feature image"
|
||||||
|
@allowUnsplash={{true}}
|
||||||
|
@includeMetadata={{true}}
|
||||||
|
@update={{@setFeatureImage}}
|
||||||
|
@remove={{fn @setFeatureImage null}}
|
||||||
|
@alt={{@featureImageAlt}}
|
||||||
|
@caption={{@featureImageCaption}}
|
||||||
|
@updateAlt={{@setFeatureImageAlt}}
|
||||||
|
@updateCaption={{@setFeatureImageCaption}}
|
||||||
|
@captionPlaceholder="Add a caption to the feature image"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
<GhTextarea
|
<GhTextarea
|
||||||
@class="gh-editor-title"
|
@class="gh-editor-title"
|
||||||
@placeholder={{@titlePlaceholder}}
|
@placeholder={{@titlePlaceholder}}
|
||||||
|
@ -8,13 +8,15 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="settings-menu-content">
|
<div class="settings-menu-content">
|
||||||
<GhImageUploaderWithPreview
|
{{#unless (feature "featureImageMeta")}}
|
||||||
@image={{this.post.featureImage}}
|
<GhImageUploaderWithPreview
|
||||||
@text={{concat "Upload " this.post.displayName " image"}}
|
@image={{this.post.featureImage}}
|
||||||
@allowUnsplash={{true}}
|
@text={{concat "Upload " this.post.displayName " image"}}
|
||||||
@update={{action "setCoverImage"}}
|
@allowUnsplash={{true}}
|
||||||
@remove={{action "clearCoverImage"}}
|
@update={{action "setCoverImage"}}
|
||||||
/>
|
@remove={{action "clearCoverImage"}}
|
||||||
|
/>
|
||||||
|
{{/unless}}
|
||||||
<form>
|
<form>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="url">{{capitalize this.post.displayName}} URL</label>
|
<label for="url">{{capitalize this.post.displayName}} URL</label>
|
||||||
|
@ -305,6 +305,22 @@ export default Controller.extend({
|
|||||||
|
|
||||||
updateWordCount(counts) {
|
updateWordCount(counts) {
|
||||||
this.set('wordCount', counts);
|
this.set('wordCount', counts);
|
||||||
|
},
|
||||||
|
|
||||||
|
setFeatureImage(url) {
|
||||||
|
this.post.set('featureImage', url);
|
||||||
|
},
|
||||||
|
|
||||||
|
clearFeatureImage() {
|
||||||
|
this.post.set('featureImage', null);
|
||||||
|
},
|
||||||
|
|
||||||
|
setFeatureImageAlt(text) {
|
||||||
|
this.post.set('featureImageAlt', text);
|
||||||
|
},
|
||||||
|
|
||||||
|
setFeatureImageCaption(html) {
|
||||||
|
this.post.set('featureImageCaption', html);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -79,7 +79,6 @@ export default Model.extend(Comparable, ValidationEngine, {
|
|||||||
excerpt: attr('string'),
|
excerpt: attr('string'),
|
||||||
customExcerpt: attr('string'),
|
customExcerpt: attr('string'),
|
||||||
featured: attr('boolean', {defaultValue: false}),
|
featured: attr('boolean', {defaultValue: false}),
|
||||||
featureImage: attr('string'),
|
|
||||||
canonicalUrl: attr('string'),
|
canonicalUrl: attr('string'),
|
||||||
codeinjectionFoot: attr('string', {defaultValue: ''}),
|
codeinjectionFoot: attr('string', {defaultValue: ''}),
|
||||||
codeinjectionHead: attr('string', {defaultValue: ''}),
|
codeinjectionHead: attr('string', {defaultValue: ''}),
|
||||||
@ -107,6 +106,10 @@ export default Model.extend(Comparable, ValidationEngine, {
|
|||||||
uuid: attr('string'),
|
uuid: attr('string'),
|
||||||
emailRecipientFilter: attr('members-segment-string', {defaultValue: null}),
|
emailRecipientFilter: attr('members-segment-string', {defaultValue: null}),
|
||||||
|
|
||||||
|
featureImage: attr('string'),
|
||||||
|
featureImageAlt: attr('string'),
|
||||||
|
featureImageCaption: attr('string'),
|
||||||
|
|
||||||
authors: hasMany('user', {embedded: 'always', async: false}),
|
authors: hasMany('user', {embedded: 'always', async: false}),
|
||||||
createdBy: belongsTo('user', {async: true}),
|
createdBy: belongsTo('user', {async: true}),
|
||||||
email: belongsTo('email', {async: false}),
|
email: belongsTo('email', {async: false}),
|
||||||
|
@ -55,6 +55,7 @@ export default Service.extend({
|
|||||||
launchComplete: feature('launchComplete', {user: true}),
|
launchComplete: feature('launchComplete', {user: true}),
|
||||||
matchHelper: feature('matchHelper'),
|
matchHelper: feature('matchHelper'),
|
||||||
multipleProducts: feature('multipleProducts', {developer: true}),
|
multipleProducts: feature('multipleProducts', {developer: true}),
|
||||||
|
featureImageMeta: feature('featureImageMeta', {developer: true}),
|
||||||
|
|
||||||
_user: null,
|
_user: null,
|
||||||
|
|
||||||
|
@ -380,6 +380,16 @@
|
|||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.gh-editor-feature-image {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 740px;
|
||||||
|
min-height: auto;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
margin-bottom: 1.6rem;
|
||||||
|
}
|
||||||
|
|
||||||
.gh-editor-title {
|
.gh-editor-title {
|
||||||
display: block;
|
display: block;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
@ -84,6 +84,12 @@
|
|||||||
@snippets={{this.snippets}}
|
@snippets={{this.snippets}}
|
||||||
@saveSnippet={{if this.canManageSnippets this.saveSnippet}}
|
@saveSnippet={{if this.canManageSnippets this.saveSnippet}}
|
||||||
@deleteSnippet={{if this.canManageSnippets this.toggleDeleteSnippetModal}}
|
@deleteSnippet={{if this.canManageSnippets this.toggleDeleteSnippetModal}}
|
||||||
|
@featureImage={{this.post.featureImage}}
|
||||||
|
@featureImageAlt={{this.post.featureImageAlt}}
|
||||||
|
@featureImageCaption={{this.post.featureImageCaption}}
|
||||||
|
@setFeatureImage={{action "setFeatureImage"}}
|
||||||
|
@setFeatureImageAlt={{action "setFeatureImageAlt"}}
|
||||||
|
@setFeatureImageCaption={{action "setFeatureImageCaption"}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div class="gh-editor-wordcount-container {{if editor.headerClass "small"}}">
|
<div class="gh-editor-wordcount-container {{if editor.headerClass "small"}}">
|
||||||
|
@ -267,6 +267,19 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="gh-expandable-block">
|
||||||
|
<div class="gh-expandable-header">
|
||||||
|
<div>
|
||||||
|
<h4 class="gh-expandable-title">Feature image redesign with caption support</h4>
|
||||||
|
<p class="gh-expandable-description">
|
||||||
|
Moves feature image uploader to top of editor screen and adds alt/caption input.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="for-switch">
|
||||||
|
<GhFeatureFlag @flag="featureImageMeta" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
@ -19,7 +19,8 @@ export default BaseValidator.create({
|
|||||||
'twitterDescription',
|
'twitterDescription',
|
||||||
'publishedAtBlogTime',
|
'publishedAtBlogTime',
|
||||||
'publishedAtBlogDate',
|
'publishedAtBlogDate',
|
||||||
'emailSubject'
|
'emailSubject',
|
||||||
|
'featureImageAlt'
|
||||||
],
|
],
|
||||||
|
|
||||||
title(model) {
|
title(model) {
|
||||||
@ -187,5 +188,12 @@ export default BaseValidator.create({
|
|||||||
this.invalidate();
|
this.invalidate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
featureImageAlt(model) {
|
||||||
|
if (!validator.isLength(model.featureImageAlt || '', 0, 125)) {
|
||||||
|
model.errors.add('featureImageAlt', 'Feature image alt text cannot be longer than 125 characters.');
|
||||||
|
this.invalidate();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user