mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-11-30 01:42:29 +03:00
7c3d75ade7
fixes https://github.com/TryGhost/Team/issues/2193 - When pasting a title with a newline, we now trim the string and clear newslines before pasting. - When sending the slug to the backend to generate a unique slug, we now sluggify it in the frontend before adding it to the URL to prevent issues with unsupported characters (causing possible routing problems in Pro).
169 lines
5.1 KiB
JavaScript
169 lines
5.1 KiB
JavaScript
import Component from '@glimmer/component';
|
|
import {action} from '@ember/object';
|
|
import {tracked} from '@glimmer/tracking';
|
|
|
|
export default class GhKoenigEditorComponent extends Component {
|
|
containerElement = null;
|
|
titleElement = null;
|
|
koenigEditor = null;
|
|
mousedownY = 0;
|
|
|
|
@tracked titleIsHovered = false;
|
|
@tracked titleIsFocused = false;
|
|
|
|
get title() {
|
|
return this.args.title === '(Untitled)' ? '' : this.args.title;
|
|
}
|
|
|
|
@action
|
|
registerElement(element) {
|
|
this.containerElement = element;
|
|
}
|
|
|
|
@action
|
|
trackMousedown(event) {
|
|
// triggered when a mousedown is registered on .gh-koenig-editor-pane
|
|
this.mousedownY = event.clientY;
|
|
}
|
|
|
|
// Title actions -----------------------------------------------------------
|
|
|
|
@action
|
|
registerTitleElement(element) {
|
|
this.titleElement = element;
|
|
|
|
// this is needed because focus event handler won't be fired if input has focus when rendering
|
|
if (this.titleElement === document.activeElement) {
|
|
this.titleIsFocused = true;
|
|
}
|
|
}
|
|
|
|
@action
|
|
updateTitle(event) {
|
|
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();
|
|
}
|
|
|
|
@action
|
|
onTitleKeydown(event) {
|
|
let value = event.target.value;
|
|
let selectionStart = event.target.selectionStart;
|
|
|
|
// enter will always focus the editor
|
|
// down arrow will only focus the editor when the cursor is at the
|
|
// end of the input to preserve the default OS behaviour
|
|
if (
|
|
event.key === 'Enter' ||
|
|
event.key === 'Tab' ||
|
|
((event.key === 'ArrowDown' || event.key === 'ArrowRight') && (!value || selectionStart === value.length))
|
|
) {
|
|
event.preventDefault();
|
|
|
|
// on Enter we also want to create a blank para if necessary
|
|
if (event.key === 'Enter') {
|
|
this._addParaAtTop();
|
|
}
|
|
|
|
this.koenigEditor.focus();
|
|
}
|
|
}
|
|
|
|
// Body actions ------------------------------------------------------------
|
|
|
|
@action
|
|
onEditorCreated(koenig) {
|
|
this._setupEditor(koenig);
|
|
this.args.onEditorCreated?.(koenig);
|
|
}
|
|
|
|
@action
|
|
focusEditor(event) {
|
|
if (event.target.classList.contains('gh-koenig-editor-pane')) {
|
|
let editorCanvas = this.koenigEditor.element;
|
|
let {bottom} = editorCanvas.getBoundingClientRect();
|
|
|
|
// if a mousedown and subsequent mouseup occurs below the editor
|
|
// canvas, focus the editor and put the cursor at the end of the
|
|
// document
|
|
if (this.mousedownY > bottom && event.clientY > bottom) {
|
|
let {post} = this.koenigEditor;
|
|
let range = post.toRange();
|
|
let {tailSection} = range;
|
|
|
|
event.preventDefault();
|
|
this.koenigEditor.focus();
|
|
|
|
// we should always have a visible cursor when focusing
|
|
// at the bottom so create an empty paragraph if last
|
|
// section is a card
|
|
if (tailSection.isCardSection) {
|
|
this.koenigEditor.run((postEditor) => {
|
|
let newSection = postEditor.builder.createMarkupSection('p');
|
|
postEditor.insertSectionAtEnd(newSection);
|
|
tailSection = newSection;
|
|
});
|
|
}
|
|
|
|
this.koenigEditor.selectRange(tailSection.tailPosition());
|
|
|
|
// ensure we're scrolled to the bottom
|
|
this.containerElement.scrollTop = this.containerElement.scrollHeight;
|
|
}
|
|
}
|
|
}
|
|
|
|
_setupEditor(koenig) {
|
|
let component = this;
|
|
|
|
this.koenigEditor = koenig.editor;
|
|
|
|
// focus the title when pressing SHIFT+TAB
|
|
this.koenigEditor.registerKeyCommand({
|
|
str: 'SHIFT+TAB',
|
|
run() {
|
|
component.focusTitle();
|
|
return true;
|
|
}
|
|
});
|
|
}
|
|
|
|
_addParaAtTop() {
|
|
if (!this.koenigEditor) {
|
|
return;
|
|
}
|
|
|
|
let editor = this.koenigEditor;
|
|
let section = editor.post.toRange().head.section;
|
|
|
|
// create a blank paragraph at the top of the editor unless it's already
|
|
// a blank paragraph
|
|
if (section.isListItem || !section.isBlank || section.text !== '') {
|
|
editor.run((postEditor) => {
|
|
let {builder} = postEditor;
|
|
let newPara = builder.createMarkupSection('p');
|
|
let sections = section.isListItem ? section.parent.parent.sections : section.parent.sections;
|
|
|
|
postEditor.insertSectionBefore(sections, newPara, section);
|
|
});
|
|
}
|
|
}
|
|
}
|