Added TK support to subtitle

closes https://linear.app/tryghost/issue/MOM-176

- display TK next to subtitle field as per the title field
- include subtitle TK in the pre-publish TK check
This commit is contained in:
Kevin Ansfield 2024-06-03 13:16:57 +01:00
parent 1d4fedf4f2
commit d94a4c581f
5 changed files with 124 additions and 60 deletions

View File

@ -58,23 +58,34 @@
/>
{{#if (feature 'editorSubtitle')}}
<GhTextarea
@class={{concat "gh-editor-subtitle " (if @excerptErrorMessage "red")}}
@placeholder="Add a subtitle..."
@shouldFocus={{false}}
@tabindex="1"
@autoExpand=".gh-koenig-editor"
@value={{readonly this.excerpt}}
@input={{this.onExcerptInput}}
@keyDown={{this.onExcerptKeydown}}
@didCreateTextarea={{this.registerSubtitleElement}}
data-test-textarea="subtitle"
/>
{{#if @excerptErrorMessage}}
<div class="gh-editor-subtitle-error" data-test-error="subtitle">
{{@excerptErrorMessage}}
</div>
{{/if}}
<div class="relative">
<GhTextarea
@class={{concat "gh-editor-subtitle " (if @excerptErrorMessage "red")}}
@placeholder="Add a subtitle..."
@shouldFocus={{false}}
@tabindex="1"
@autoExpand=".gh-koenig-editor"
@value={{readonly this.excerpt}}
@input={{this.onExcerptInput}}
@keyDown={{this.onExcerptKeydown}}
@didCreateTextarea={{this.registerSubtitleElement}}
data-test-textarea="subtitle"
/>
{{#if @excerptErrorMessage}}
<div class="gh-editor-subtitle-error" data-test-error="subtitle">
{{@excerptErrorMessage}}
</div>
{{/if}}
{{#if @excerptHasTk}}
<div
class="tk-indicator tk-indicator-subtitle"
data-testid="tk-indicator-subtitle"
{{on "click" this.focusSubtitle}}
>
TK
</div>
{{/if}}
</div>
<hr class="gh-editor-title-divider {{if @excerptErrorMessage "gh-editor-title-divider-error" ""}}">
{{/if}}
</div>

View File

@ -102,6 +102,45 @@ const messageMap = {
}
};
function textHasTk(text) {
let matchArr = TK_REGEX.exec(text);
if (matchArr === null) {
return false;
}
function isValidMatch(match) {
// negative lookbehind isn't supported before Safari 16.4
// so we capture the preceding char and test it here
if (match[1] && match[1].trim() && WORD_CHAR_REGEX.test(match[1])) {
return false;
}
// we also check any following char in code to avoid an overly
// complex regex when looking for word-chars following the optional
// trailing symbol char
if (match[4] && match[4].trim() && WORD_CHAR_REGEX.test(match[4])) {
return false;
}
return true;
}
// our regex will match invalid TKs because we can't use negative lookbehind
// so we need to loop through the matches discarding any that are invalid
// and moving on to any subsequent matches
while (matchArr !== null && !isValidMatch(matchArr)) {
text = text.slice(matchArr.index + matchArr[0].length - 1);
matchArr = TK_REGEX.exec(text);
}
if (matchArr === null) {
return false;
}
return true;
}
@classic
export default class LexicalEditorController extends Controller {
@controller application;
@ -225,48 +264,23 @@ export default class LexicalEditorController extends Controller {
@computed('post.titleScratch')
get titleHasTk() {
let text = this.post.titleScratch;
let matchArr = TK_REGEX.exec(text);
if (matchArr === null) {
return false;
}
function isValidMatch(match) {
// negative lookbehind isn't supported before Safari 16.4
// so we capture the preceding char and test it here
if (match[1] && match[1].trim() && WORD_CHAR_REGEX.test(match[1])) {
return false;
}
// we also check any following char in code to avoid an overly
// complex regex when looking for word-chars following the optional
// trailing symbol char
if (match[4] && match[4].trim() && WORD_CHAR_REGEX.test(match[4])) {
return false;
}
return true;
}
// our regex will match invalid TKs because we can't use negative lookbehind
// so we need to loop through the matches discarding any that are invalid
// and moving on to any subsequent matches
while (matchArr !== null && !isValidMatch(matchArr)) {
text = text.slice(matchArr.index + matchArr[0].length - 1);
matchArr = TK_REGEX.exec(text);
}
if (matchArr === null) {
return false;
}
return true;
return textHasTk(this.post.titleScratch);
}
@computed('titleHasTk', 'postTkCount', 'featureImageTkCount')
@computed('post.customExcerpt')
get excerptHasTk() {
if (!this.feature.editorSubtitle) {
return false;
}
return textHasTk(this.post.customExcerpt || '');
}
@computed('titleHasTk', 'excerptHasTk', 'postTkCount', 'featureImageTkCount')
get tkCount() {
return (this.titleHasTk ? 1 : 0) + this.postTkCount + this.featureImageTkCount;
const titleTk = this.titleHasTk ? 1 : 0;
const excerptTk = (this.feature.editorSubtitle && this.excerptHasTk) ? 1 : 0;
return titleTk + excerptTk + this.postTkCount + this.featureImageTkCount;
}
@action

View File

@ -839,6 +839,10 @@ body[data-user-is-dragging] .gh-editor-feature-image-dropzone {
cursor: pointer;
}
.gh-editor .tk-indicator-subtitle {
top: -1px;
}
.gh-editor-feature-image-container .tk-indicator {
top: 0;
padding: 0 .4rem;

View File

@ -61,14 +61,15 @@
--}}
<GhKoenigEditorLexical
@title={{readonly this.post.titleScratch}}
@excerpt={{readonly this.post.customExcerpt}}
@setExcerpt={{this.updateExcerpt}}
@excerptErrorMessage={{this.excerptErrorMessage}}
@titleAutofocus={{this.shouldFocusTitle}}
@titlePlaceholder={{concat (capitalize this.post.displayName) " title"}}
@titleAutofocus={{this.shouldFocusTitle}}
@titleHasTk={{this.titleHasTk}}
@onTitleChange={{this.updateTitleScratch}}
@onTitleBlur={{perform this.saveTitleTask}}
@excerpt={{readonly this.post.customExcerpt}}
@excerptHasTk={{this.excerptHasTk}}
@setExcerpt={{this.updateExcerpt}}
@excerptErrorMessage={{this.excerptErrorMessage}}
@body={{readonly this.post.lexicalScratch}}
@bodyPlaceholder={{concat "Begin writing your " this.post.displayName "..."}}
@onBodyChange={{this.updateScratch}}

View File

@ -635,5 +635,39 @@ describe('Acceptance: Editor', function () {
'TK reminder modal'
).to.exist;
});
it('handles TKs in subtitle', async function () {
enableLabsFlag(this.server, 'editorSubtitle');
const post = this.server.create('post', {authors: [author]});
await visit(`/editor/post/${post.id}`);
expect(
find('[data-test-textarea="subtitle"]').value,
'initial subtitle'
).to.equal('');
await fillIn('[data-test-textarea="subtitle"]', 'Test TK subtitle');
expect(
find('[data-test-textarea="subtitle"]').value,
'subtitle after typing'
).to.equal('Test TK subtitle');
// check for TK indicator
expect(
find('[data-testid="tk-indicator-subtitle"]'),
'TK indicator text'
).to.exist;
// click publish to see if confirmation comes up
await click('[data-test-button="publish-flow"]');
expect(
find('[data-test-modal="tk-reminder"]'),
'TK reminder modal'
).to.exist;
});
});
});