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

Added TK support to feature image caption editor (#19285)

closes https://github.com/TryGhost/Product/issues/4227

- added `@onTKCountChange` to `<KoenigLexicalEditorInput>`
  - when present the `isTKEnabled` flag will be turned on and the `<TKCountPlugin>` registered
- added `@registerAPI` support to `<KoenigLexicalEditorInput>` so we can focus the caption editor when its TK indicator is clicked
- added manual display of TK indicator for the caption input
  - default editor indicator positioning doesn't work for this input because its container is not full editor width
  - hid it by adding `overflow: hidden` to the inner caption container
  - added custom indicator button shown when we have a non-zero count
This commit is contained in:
Kevin Ansfield 2023-12-07 15:46:23 +00:00 committed by GitHub
parent e5c6b0a23f
commit 309aaf98aa
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 57 additions and 5 deletions

View file

@ -50,7 +50,7 @@
{{svg-jar "koenig/kg-trash"}} {{svg-jar "koenig/kg-trash"}}
</button> </button>
</div> </div>
<div class="flex justify-between align-center"> <div class="relative flex justify-between align-center">
{{#if this.isEditingAlt}} {{#if this.isEditingAlt}}
<input <input
type="text" type="text"
@ -70,6 +70,8 @@
@placeholderText={{if this.captionInputFocused "" "Add a caption to the feature image"}} @placeholderText={{if this.captionInputFocused "" "Add a caption to the feature image"}}
@onFocus={{fn (mut this.captionInputFocused) true}} @onFocus={{fn (mut this.captionInputFocused) true}}
@onBlur={{this.handleCaptionBlur}} @onBlur={{this.handleCaptionBlur}}
@onTKCountChange={{this.onTKCountChange}}
@registerAPI={{this.registerEditorAPI}}
/> />
{{/if}} {{/if}}
<button <button
@ -78,6 +80,12 @@
> >
Alt Alt
</button> </button>
{{#if (and this.tkCount (not this.isEditingAlt))}}
<div class="tk-indicator" data-testid="feature-image-tk-indicator" role="button" {{on "click" this.focusCaptionEditor}}>
TK
</div>
{{/if}}
</div> </div>
{{else}} {{else}}
{{!-- no-image state --}} {{!-- no-image state --}}

View file

@ -18,6 +18,7 @@ export default class GhEditorFeatureImageComponent extends Component {
@tracked captionInputFocused = false; @tracked captionInputFocused = false;
@tracked showUnsplashSelector = false; @tracked showUnsplashSelector = false;
@tracked canDrop = false; @tracked canDrop = false;
@tracked tkCount = 0;
get caption() { get caption() {
const content = this.args.caption; const content = this.args.caption;
@ -34,6 +35,18 @@ export default class GhEditorFeatureImageComponent extends Component {
this.args.updateCaption(cleanedHtml); this.args.updateCaption(cleanedHtml);
} }
@action
registerEditorAPI(API) {
this.editorAPI = API;
}
@action
focusCaptionEditor() {
if (this.editorAPI) {
this.editorAPI.focusEditor({position: 'bottom'});
}
}
@action @action
handleCaptionBlur() { handleCaptionBlur() {
this.captionInputFocused = false; this.captionInputFocused = false;
@ -116,4 +129,12 @@ export default class GhEditorFeatureImageComponent extends Component {
this.canDrop = false; this.canDrop = false;
setFiles([imageFile]); setFiles([imageFile]);
} }
@action
onTKCountChange(count) {
if (this.args.onTKCountChange) {
this.tkCount = count;
this.args.onTKCountChange(count);
}
}
} }

View file

@ -19,6 +19,7 @@
@handleCaptionBlur={{@handleFeatureImageCaptionBlur}} @handleCaptionBlur={{@handleFeatureImageCaptionBlur}}
@forceButtonDisplay={{or (not @title) (eq @title "(Untitled)") this.titleIsHovered this.titleIsFocused}} @forceButtonDisplay={{or (not @title) (eq @title "(Untitled)") this.titleIsHovered this.titleIsFocused}}
@isHidden={{or (not @cardOptions.post.showTitleAndFeatureImage) false}} @isHidden={{or (not @cardOptions.post.showTitleAndFeatureImage) false}}
@onTKCountChange={{@updateFeatureImageTkCount}}
/> />
<div class="gh-editor-title-container page-improvements"> <div class="gh-editor-title-container page-improvements">

View file

@ -57,6 +57,11 @@ const EmojiPickerPlugin = ({editorResource, ...props}) => {
return <_EmojiPickerPlugin {...props} />; return <_EmojiPickerPlugin {...props} />;
}; };
const TKCountPlugin = ({editorResource, ...props}) => {
const {TKCountPlugin: _TKCountPlugin} = editorResource.read();
return <_TKCountPlugin {...props} />;
};
export default class KoenigLexicalEditorInput extends Component { export default class KoenigLexicalEditorInput extends Component {
@service ajax; @service ajax;
@service feature; @service feature;
@ -100,6 +105,7 @@ export default class KoenigLexicalEditorInput extends Component {
editorResource={this.editorResource} editorResource={this.editorResource}
initialEditorState={this.args.lexical} initialEditorState={this.args.lexical}
onError={this.onError} onError={this.onError}
isTKEnabled={this.args.onTKCountChange ? true : false}
> >
<KoenigComposableEditor <KoenigComposableEditor
editorResource={this.editorResource} editorResource={this.editorResource}
@ -112,9 +118,11 @@ export default class KoenigLexicalEditorInput extends Component {
className={`koenig-lexical-editor-input ${this.feature.nightShift ? 'dark' : ''}`} className={`koenig-lexical-editor-input ${this.feature.nightShift ? 'dark' : ''}`}
placeholderText={props.placeholderText} placeholderText={props.placeholderText}
placeholderClassName="koenig-lexical-editor-input-placeholder" placeholderClassName="koenig-lexical-editor-input-placeholder"
registerAPI={this.args.registerAPI}
> >
<HtmlOutputPlugin editorResource={this.editorResource} html={props.html} setHtml={props.onChangeHtml} /> <HtmlOutputPlugin editorResource={this.editorResource} html={props.html} setHtml={props.onChangeHtml} />
{this.emojiPicker ? <EmojiPickerPlugin editorResource={this.editorResource} /> : null} {this.emojiPicker ? <EmojiPickerPlugin editorResource={this.editorResource} /> : null}
{this.args.onTKCountChange ? <TKCountPlugin editorResource={this.editorResource} onChange={this.args.onTKCountChange} /> : null}
</KoenigComposableEditor> </KoenigComposableEditor>
</KoenigComposer> </KoenigComposer>
</Suspense> </Suspense>

View file

@ -131,6 +131,7 @@ export default class LexicalEditorController extends Controller {
// koenig related properties // koenig related properties
wordCount = 0; wordCount = 0;
postTkCount = 0; postTkCount = 0;
featureImageTkCount = 0;
/* private properties ----------------------------------------------------*/ /* private properties ----------------------------------------------------*/
@ -262,9 +263,9 @@ export default class LexicalEditorController extends Controller {
return true; return true;
} }
@computed('titleHasTk', 'postTkCount') @computed('titleHasTk', 'postTkCount', 'featureImageTkCount')
get tkCount() { get tkCount() {
return (this.titleHasTk ? 1 : 0) + this.postTkCount; return (this.titleHasTk ? 1 : 0) + this.postTkCount + this.featureImageTkCount;
} }
@action @action
@ -368,6 +369,11 @@ export default class LexicalEditorController extends Controller {
this.set('postTkCount', count); this.set('postTkCount', count);
} }
@action
updateFeatureImageTkCount(count) {
this.set('featureImageTkCount', count);
}
@action @action
setFeatureImage(url) { setFeatureImage(url) {
this.post.set('featureImage', url); this.post.set('featureImage', url);
@ -1135,6 +1141,7 @@ export default class LexicalEditorController extends Controller {
this.set('showSettingsMenu', false); this.set('showSettingsMenu', false);
this.set('wordCount', 0); this.set('wordCount', 0);
this.set('postTkCount', 0); this.set('postTkCount', 0);
this.set('featureImageTkCount', 0);
// remove the onbeforeunload handler as it's only relevant whilst on // remove the onbeforeunload handler as it's only relevant whilst on
// the editor route // the editor route

View file

@ -635,6 +635,7 @@ body[data-user-is-dragging] .gh-editor-feature-image-dropzone {
background-color: transparent !important; background-color: transparent !important;
transition: border-color .15s linear; transition: border-color .15s linear;
-webkit-appearance: none; -webkit-appearance: none;
overflow: hidden; /* Hides any indicators such as TK */
} }
.gh-editor-feature-image-alttext::placeholder, .gh-editor-feature-image-alttext::placeholder,
@ -683,7 +684,7 @@ body[data-user-is-dragging] .gh-editor-feature-image-dropzone {
opacity: .5; opacity: .5;
} }
.gh-editor-title-container .tk-indicator { .gh-editor .tk-indicator {
position: absolute; position: absolute;
top: 15px; top: 15px;
right: -5.6rem; right: -5.6rem;
@ -691,7 +692,12 @@ body[data-user-is-dragging] .gh-editor-feature-image-dropzone {
color: #95A1AD; color: #95A1AD;
font-size: 1.2rem; font-size: 1.2rem;
font-weight: 500; font-weight: 500;
cursor: default; cursor: pointer;
}
.gh-editor-feature-image-container .tk-indicator {
top: 0;
padding: 0 .4rem;
} }
.gh-editor-back-button { .gh-editor-back-button {

View file

@ -79,6 +79,7 @@
@onEditorCreated={{this.setKoenigEditor}} @onEditorCreated={{this.setKoenigEditor}}
@updateWordCount={{this.updateWordCount}} @updateWordCount={{this.updateWordCount}}
@updatePostTkCount={{this.updatePostTkCount}} @updatePostTkCount={{this.updatePostTkCount}}
@updateFeatureImageTkCount={{if (feature "tkReminders") this.updateFeatureImageTkCount}}
@featureImage={{this.post.featureImage}} @featureImage={{this.post.featureImage}}
@featureImageAlt={{this.post.featureImageAlt}} @featureImageAlt={{this.post.featureImageAlt}}
@featureImageCaption={{this.post.featureImageCaption}} @featureImageCaption={{this.post.featureImageCaption}}