diff --git a/ghost/admin/app/components/gh-koenig-editor-lexical.hbs b/ghost/admin/app/components/gh-koenig-editor-lexical.hbs index 3ed24c5326..2919f96159 100644 --- a/ghost/admin/app/components/gh-koenig-editor-lexical.hbs +++ b/ghost/admin/app/components/gh-koenig-editor-lexical.hbs @@ -32,6 +32,7 @@ {{on "blur" (fn (mut this.titleIsFocused) false)}} {{on "mouseover" (fn (mut this.titleIsHovered) true)}} {{on "mouseleave" (fn (mut this.titleIsHovered) false)}} + {{on "paste" this.cleanPastedTitle}} data-test-editor-title-input={{true}} /> @@ -61,4 +62,4 @@ @postType={{@postType}} /> --}} - \ No newline at end of file + diff --git a/ghost/admin/app/components/gh-koenig-editor-lexical.js b/ghost/admin/app/components/gh-koenig-editor-lexical.js index 5736be4f3e..879f1d1092 100644 --- a/ghost/admin/app/components/gh-koenig-editor-lexical.js +++ b/ghost/admin/app/components/gh-koenig-editor-lexical.js @@ -56,6 +56,20 @@ export default class GhKoenigEditorReactComponent extends Component { this.args.onTitleChange?.(event.target.value); } + @action + cleanPastedTitle(event) { + const pastedText = (event.clipboardData || window.clipboardData).getData('text'); + + if (!pastedText) { + return; + } + + event.preventDefault(); + + const cleanValue = pastedText.replace(/(\n|\r)+/g, ' ').trim(); + document.execCommand('insertText', false, cleanValue); + } + @action focusTitle() { this.titleElement.focus(); diff --git a/ghost/admin/app/components/gh-koenig-editor.hbs b/ghost/admin/app/components/gh-koenig-editor.hbs index 32c4bf0a0d..a1a9f142ea 100644 --- a/ghost/admin/app/components/gh-koenig-editor.hbs +++ b/ghost/admin/app/components/gh-koenig-editor.hbs @@ -32,6 +32,7 @@ {{on "blur" (fn (mut this.titleIsFocused) false)}} {{on "mouseover" (fn (mut this.titleIsHovered) true)}} {{on "mouseleave" (fn (mut this.titleIsHovered) false)}} + {{on "paste" this.cleanPastedTitle}} data-test-editor-title-input={{true}} /> @@ -56,4 +57,4 @@ @postType={{@postType}} /> - \ No newline at end of file + diff --git a/ghost/admin/app/components/gh-koenig-editor.js b/ghost/admin/app/components/gh-koenig-editor.js index e21a18427a..93f0ee6fee 100644 --- a/ghost/admin/app/components/gh-koenig-editor.js +++ b/ghost/admin/app/components/gh-koenig-editor.js @@ -43,6 +43,20 @@ export default class GhKoenigEditorComponent extends Component { this.args.onTitleChange?.(event.target.value); } + @action + cleanPastedTitle(event) { + const pastedText = (event.clipboardData || window.clipboardData).getData('text'); + + if (!pastedText) { + return; + } + + event.preventDefault(); + + const cleanValue = pastedText.replace(/(\n|\r)+/g, ' ').trim(); + document.execCommand('insertText', false, cleanValue); + } + @action focusTitle() { this.titleElement.focus(); diff --git a/ghost/admin/app/services/slug-generator.js b/ghost/admin/app/services/slug-generator.js index f6ab19ad3b..397340e075 100644 --- a/ghost/admin/app/services/slug-generator.js +++ b/ghost/admin/app/services/slug-generator.js @@ -1,6 +1,7 @@ import RSVP from 'rsvp'; import Service, {inject as service} from '@ember/service'; import classic from 'ember-classic-decorator'; +import {slugify} from '@tryghost/string'; const {resolve} = RSVP; @@ -16,7 +17,8 @@ export default class SlugGeneratorService extends Service { return resolve(''); } - url = this.get('ghostPaths.url').api('slugs', slugType, encodeURIComponent(textToSlugify)); + // We already do a partial slugify at the client side to prevent issues with Pro returning a 404 page because of invalid (encoded) characters (a newline, %0A, for example) + url = this.get('ghostPaths.url').api('slugs', slugType, encodeURIComponent(slugify(textToSlugify))); return this.ajax.request(url).then((response) => { let [firstSlug] = response.slugs; diff --git a/ghost/admin/tests/integration/services/slug-generator-test.js b/ghost/admin/tests/integration/services/slug-generator-test.js index 29bbc8c869..2ac4961e34 100644 --- a/ghost/admin/tests/integration/services/slug-generator-test.js +++ b/ghost/admin/tests/integration/services/slug-generator-test.js @@ -42,7 +42,7 @@ describe('Integration: Service: slug-generator', function () { it('calls correct endpoint and returns correct data', function (done) { let rawSlug = 'a test post'; - stubSlugEndpoint(server, 'post', rawSlug); + stubSlugEndpoint(server, 'post', 'a-test-post'); let service = this.owner.lookup('service:slug-generator');