mirror of
https://github.com/TryGhost/Ghost.git
synced 2023-12-13 21:00:40 +01:00
Deleted react-editor experiment
no issue - cleanup now that the experiments have finished
This commit is contained in:
parent
a440076a12
commit
3ec8b4e9b3
14 changed files with 0 additions and 1775 deletions
|
@ -1,67 +0,0 @@
|
|||
<div class="gh-koenig-editor relative w-100 vh-100 overflow-x-hidden overflow-y-auto z-0" {{did-insert this.registerElement}} ...attributes>
|
||||
{{!-- full height content pane --}}
|
||||
{{!-- template-lint-disable no-down-event-binding no-invalid-interactive no-passed-in-event-handlers --}}
|
||||
<div
|
||||
class="gh-koenig-editor-pane flex flex-column mih-100"
|
||||
{{on "mousedown" this.trackMousedown}}
|
||||
{{on "mouseup" this.focusEditor}}
|
||||
>
|
||||
<GhEditorFeatureImage
|
||||
@image={{@featureImage}}
|
||||
@updateImage={{@setFeatureImage}}
|
||||
@clearImage={{@clearFeatureImage}}
|
||||
@alt={{@featureImageAlt}}
|
||||
@updateAlt={{@setFeatureImageAlt}}
|
||||
@caption={{@featureImageCaption}}
|
||||
@updateCaption={{@setFeatureImageCaption}}
|
||||
@forceButtonDisplay={{or (not @title) (eq @title "(Untitled)") this.titleIsHovered this.titleIsFocused}}
|
||||
/>
|
||||
|
||||
<GhTextarea
|
||||
@class="gh-editor-title"
|
||||
@placeholder={{@titlePlaceholder}}
|
||||
@shouldFocus={{or @titleAutofocus false}}
|
||||
@tabindex="1"
|
||||
@autoExpand=".gh-koenig-editor"
|
||||
@value={{readonly this.title}}
|
||||
@input={{this.updateTitle}}
|
||||
@focus-out={{optional @onTitleBlur}}
|
||||
@keyDown={{this.onTitleKeydown}}
|
||||
@didCreateTextarea={{this.registerTitleElement}}
|
||||
{{on "focus" (fn (mut this.titleIsFocused) true)}}
|
||||
{{on "blur" (fn (mut this.titleIsFocused) false)}}
|
||||
{{on "mouseover" (fn (mut this.titleIsHovered) true)}}
|
||||
{{on "mouseleave" (fn (mut this.titleIsHovered) false)}}
|
||||
data-test-editor-title-input={{true}}
|
||||
/>
|
||||
|
||||
<KoenigReactEditor
|
||||
@mobiledoc={{@body}}
|
||||
@didCreateEditor={{this.onEditorCreated}}
|
||||
@onChange={{@onBodyChange}}
|
||||
@uploadUrl={{this.uploadUrl}}
|
||||
@accentColor={{this.accentColor}}
|
||||
/>
|
||||
|
||||
{{!-- <KoenigEditor
|
||||
@mobiledoc={{@body}}
|
||||
@placeholder={{@bodyPlaceholder}}
|
||||
@spellcheck={{true}}
|
||||
@onChange={{@onBodyChange}}
|
||||
@didCreateEditor={{this.onEditorCreated}}
|
||||
@cursorDidExitAtTop={{this.focusTitle}}
|
||||
@headerOffset={{@headerOffset}}
|
||||
@dropTargetSelector=".gh-koenig-editor-pane"
|
||||
@scrollContainerSelector={{@scrollContainerSelector}}
|
||||
@scrollOffsetTopSelector={{@scrollOffsetTopSelector}}
|
||||
@scrollOffsetBottomSelector={{@scrollOffsetBottomSelector}}
|
||||
@wordCountDidChange={{@onWordCountChange}}
|
||||
@snippets={{@snippets}}
|
||||
@saveSnippet={{@saveSnippet}}
|
||||
@updateSnippet={{@updateSnippet}}
|
||||
@deleteSnippet={{@deleteSnippet}}
|
||||
@cardOptions={{@cardOptions}}
|
||||
@postType={{@postType}}
|
||||
/> --}}
|
||||
</div>
|
||||
</div>
|
|
@ -1,167 +0,0 @@
|
|||
import Component from '@glimmer/component';
|
||||
import ghostPaths from 'ghost-admin/utils/ghost-paths';
|
||||
import {action} from '@ember/object';
|
||||
import {inject as service} from '@ember/service';
|
||||
import {tracked} from '@glimmer/tracking';
|
||||
|
||||
export default class GhKoenigEditorReactComponent extends Component {
|
||||
@service settings;
|
||||
|
||||
containerElement = null;
|
||||
titleElement = null;
|
||||
koenigEditor = null;
|
||||
mousedownY = 0;
|
||||
uploadUrl = `${ghostPaths().apiRoot}/images/upload/`;
|
||||
|
||||
@tracked titleIsHovered = false;
|
||||
@tracked titleIsFocused = false;
|
||||
|
||||
get title() {
|
||||
return this.args.title === '(Untitled)' ? '' : this.args.title;
|
||||
}
|
||||
|
||||
get accentColor() {
|
||||
const color = this.settings.get('accentColor');
|
||||
if (color && color[0] === '#') {
|
||||
return color.slice(1);
|
||||
}
|
||||
return color;
|
||||
}
|
||||
|
||||
@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
|
||||
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;
|
||||
|
||||
// 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);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
<div {{react-render this.ReactComponent}}></div>
|
|
@ -1,98 +0,0 @@
|
|||
import Component from '@glimmer/component';
|
||||
import React, {Suspense} from 'react';
|
||||
|
||||
class ErrorHandler extends React.Component {
|
||||
state = {
|
||||
hasError: false
|
||||
};
|
||||
|
||||
static getDerivedStateFromError() {
|
||||
return {hasError: true};
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.hasError) {
|
||||
return (
|
||||
<p className="koenig-react-editor-error">Loading has failed. Try refreshing the browser!</p>
|
||||
);
|
||||
}
|
||||
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
|
||||
const fetchKoenig = function () {
|
||||
let status = 'pending';
|
||||
let response;
|
||||
|
||||
const fetchPackage = async () => {
|
||||
if (window.koenigEditor) {
|
||||
return window.koenigEditor.default;
|
||||
}
|
||||
|
||||
// the manual specification of the protocol in the import template string is
|
||||
// required to work around ember-auto-import complaining about an unknown dynamic import
|
||||
// during the build step
|
||||
const GhostAdmin = window.Ember.Namespace.NAMESPACES.find(ns => ns.name === 'ghost-admin');
|
||||
const url = new URL(GhostAdmin.__container__.lookup('service:config').get('editor.url'));
|
||||
|
||||
if (url.protocol === 'http:') {
|
||||
await import(`http://${url.host}${url.pathname}`);
|
||||
} else {
|
||||
await import(`https://${url.host}${url.pathname}`);
|
||||
}
|
||||
|
||||
return window.koenigEditor.default;
|
||||
};
|
||||
|
||||
const suspender = fetchPackage().then(
|
||||
(res) => {
|
||||
status = 'success';
|
||||
response = res;
|
||||
},
|
||||
(err) => {
|
||||
status = 'error';
|
||||
response = err;
|
||||
}
|
||||
);
|
||||
|
||||
const read = () => {
|
||||
switch (status) {
|
||||
case 'pending':
|
||||
throw suspender;
|
||||
case 'error':
|
||||
throw response;
|
||||
default:
|
||||
return response;
|
||||
}
|
||||
};
|
||||
|
||||
return {read};
|
||||
};
|
||||
|
||||
const editorResource = fetchKoenig();
|
||||
|
||||
const Koenig = (props) => {
|
||||
const _Koenig = editorResource.read();
|
||||
return <_Koenig {...props} />;
|
||||
};
|
||||
|
||||
export default class KoenigReactEditor extends Component {
|
||||
ReactComponent = () => {
|
||||
return (
|
||||
<div className={['koenig-react-editor', this.args.className].filter(Boolean).join(' ')}>
|
||||
<ErrorHandler>
|
||||
<Suspense fallback={<p className="koenig-react-editor-loading">Loading editor...</p>}>
|
||||
<Koenig
|
||||
mobiledoc={this.args.mobiledoc}
|
||||
didCreateEditor={this.args.didCreateEditor}
|
||||
onChange={this.args.onChange}
|
||||
uploadUrl={this.args.uploadUrl}
|
||||
accentColor={this.args.accentColor}
|
||||
/>
|
||||
</Suspense>
|
||||
</ErrorHandler>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
}
|
1097
ghost/admin/app/controllers/react-editor.js
vendored
1097
ghost/admin/app/controllers/react-editor.js
vendored
File diff suppressed because it is too large
Load diff
|
@ -1,5 +0,0 @@
|
|||
import Controller from '@ember/controller';
|
||||
import {inject as service} from '@ember/service';
|
||||
export default class ReactEditLoadingController extends Controller {
|
||||
@service ui;
|
||||
}
|
|
@ -36,11 +36,6 @@ Router.map(function () {
|
|||
this.route('edit', {path: ':type/:post_id'});
|
||||
});
|
||||
|
||||
this.route('react-editor', function () {
|
||||
this.route('new', {path: ':type'});
|
||||
this.route('edit', {path: ':type/:post_id'});
|
||||
});
|
||||
|
||||
this.route('lexical-editor', function () {
|
||||
this.route('new', {path: ':type'});
|
||||
this.route('edit', {path: ':type/:post_id'});
|
||||
|
|
74
ghost/admin/app/routes/react-editor.js
vendored
74
ghost/admin/app/routes/react-editor.js
vendored
|
@ -1,74 +0,0 @@
|
|||
import $ from 'jquery';
|
||||
import AuthenticatedRoute from 'ghost-admin/routes/authenticated';
|
||||
import {run} from '@ember/runloop';
|
||||
import {inject as service} from '@ember/service';
|
||||
|
||||
export default AuthenticatedRoute.extend({
|
||||
config: service(),
|
||||
feature: service(),
|
||||
notifications: service(),
|
||||
router: service(),
|
||||
ui: service(),
|
||||
|
||||
classNames: ['editor'],
|
||||
|
||||
beforeModel() {
|
||||
if (!this.config.get('editor.url')) {
|
||||
return this.router.transitionTo('posts');
|
||||
}
|
||||
},
|
||||
|
||||
activate() {
|
||||
this._super(...arguments);
|
||||
this.ui.set('isFullScreen', true);
|
||||
},
|
||||
|
||||
deactivate() {
|
||||
this._super(...arguments);
|
||||
this.ui.set('isFullScreen', false);
|
||||
},
|
||||
|
||||
actions: {
|
||||
save() {
|
||||
this._blurAndScheduleAction(function () {
|
||||
this.controller.send('save');
|
||||
});
|
||||
},
|
||||
|
||||
authorizationFailed() {
|
||||
this.controller.send('toggleReAuthenticateModal');
|
||||
},
|
||||
|
||||
willTransition(transition) {
|
||||
// exit early if an upgrade is required because our extended route
|
||||
// class will abort the transition and show an error
|
||||
if (this.get('upgradeStatus.isRequired')) {
|
||||
return this._super(...arguments);
|
||||
}
|
||||
|
||||
this.controller.willTransition(transition);
|
||||
}
|
||||
},
|
||||
|
||||
buildRouteInfoMetadata() {
|
||||
return {
|
||||
titleToken: () => {
|
||||
return this.get('controller.post.title') || 'Editor';
|
||||
},
|
||||
bodyClasses: ['gh-body-fullscreen'],
|
||||
mainClasses: ['gh-main-white']
|
||||
};
|
||||
},
|
||||
|
||||
_blurAndScheduleAction(func) {
|
||||
let selectedElement = $(document.activeElement);
|
||||
|
||||
// TODO: we should trigger a blur for textareas as well as text inputs
|
||||
if (selectedElement.is('input[type="text"]')) {
|
||||
selectedElement.trigger('focusout');
|
||||
}
|
||||
|
||||
// wait for actions triggered by the focusout to finish before saving
|
||||
run.scheduleOnce('actions', this, func);
|
||||
}
|
||||
});
|
67
ghost/admin/app/routes/react-editor/edit.js
vendored
67
ghost/admin/app/routes/react-editor/edit.js
vendored
|
@ -1,67 +0,0 @@
|
|||
import AuthenticatedRoute from 'ghost-admin/routes/authenticated';
|
||||
import {pluralize} from 'ember-inflector';
|
||||
|
||||
export default class EditRoute extends AuthenticatedRoute {
|
||||
beforeModel(transition) {
|
||||
super.beforeModel(...arguments);
|
||||
|
||||
// if the transition is not new->edit, reset the post on the controller
|
||||
// so that the editor view is cleared before showing the loading state
|
||||
if (transition.urlMethod !== 'replace') {
|
||||
let editor = this.controllerFor('react-editor');
|
||||
editor.set('post', null);
|
||||
editor.reset();
|
||||
}
|
||||
}
|
||||
|
||||
model(params, transition) {
|
||||
// eslint-disable-next-line camelcase
|
||||
let {type: modelName, post_id} = params;
|
||||
|
||||
if (!['post', 'page'].includes(modelName)) {
|
||||
let path = transition.intent.url.replace(/^\//, '');
|
||||
return this.replaceWith('error404', {path, status: 404});
|
||||
}
|
||||
|
||||
let query = {
|
||||
// eslint-disable-next-line camelcase
|
||||
id: post_id
|
||||
};
|
||||
|
||||
return this.store.query(modelName, query)
|
||||
.then(records => records.get('firstObject'));
|
||||
}
|
||||
|
||||
// the API will return a post even if the logged in user doesn't have
|
||||
// permission to edit it (all posts are public) so we need to do our
|
||||
// own permissions check and redirect if necessary
|
||||
afterModel(post) {
|
||||
super.afterModel(...arguments);
|
||||
|
||||
const user = this.session.user;
|
||||
const returnRoute = pluralize(post.constructor.modelName);
|
||||
|
||||
if (user.isAuthorOrContributor && !post.isAuthoredByUser(user)) {
|
||||
return this.replaceWith(returnRoute);
|
||||
}
|
||||
|
||||
// If the post is not a draft and user is contributor, redirect to index
|
||||
if (user.isContributor && !post.isDraft) {
|
||||
return this.replaceWith(returnRoute);
|
||||
}
|
||||
}
|
||||
|
||||
serialize(model) {
|
||||
return {
|
||||
type: model.constructor.modelName,
|
||||
post_id: model.id
|
||||
};
|
||||
}
|
||||
|
||||
// there's no specific controller for this route, instead all editor
|
||||
// handling is done on the editor route/controller
|
||||
setupController(controller, post) {
|
||||
let editor = this.controllerFor('react-editor');
|
||||
editor.setPost(post);
|
||||
}
|
||||
}
|
8
ghost/admin/app/routes/react-editor/index.js
vendored
8
ghost/admin/app/routes/react-editor/index.js
vendored
|
@ -1,8 +0,0 @@
|
|||
import AuthenticatedRoute from 'ghost-admin/routes/authenticated';
|
||||
|
||||
export default class IndexRoute extends AuthenticatedRoute {
|
||||
beforeModel() {
|
||||
super.beforeModel(...arguments);
|
||||
this.replaceWith('react-editor.new', 'post');
|
||||
}
|
||||
}
|
27
ghost/admin/app/routes/react-editor/new.js
vendored
27
ghost/admin/app/routes/react-editor/new.js
vendored
|
@ -1,27 +0,0 @@
|
|||
import AuthenticatedRoute from 'ghost-admin/routes/authenticated';
|
||||
|
||||
export default class NewRoute extends AuthenticatedRoute {
|
||||
model(params, transition) {
|
||||
let {type: modelName} = params;
|
||||
|
||||
if (!['post','page'].includes(modelName)) {
|
||||
let path = transition.intent.url.replace(/^\//, '');
|
||||
return this.replaceWith('error404', {path, status: 404});
|
||||
}
|
||||
|
||||
return this.store.createRecord(modelName, {authors: [this.session.user]});
|
||||
}
|
||||
|
||||
// there's no specific controller for this route, instead all editor
|
||||
// handling is done on the editor route/controler
|
||||
setupController(controller, newPost) {
|
||||
let editor = this.controllerFor('react-editor');
|
||||
editor.setPost(newPost);
|
||||
}
|
||||
|
||||
buildRouteInfoMetadata() {
|
||||
return {
|
||||
mainClasses: ['editor-new']
|
||||
};
|
||||
}
|
||||
}
|
|
@ -957,11 +957,6 @@
|
|||
max-width: 740px;
|
||||
}
|
||||
|
||||
.koenig-react-editor {
|
||||
max-width: 740px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
/* ⨁ menu
|
||||
/* --------------------------------------------------------------- */
|
||||
.koenig-plus-menu-button {
|
||||
|
|
|
@ -1,151 +0,0 @@
|
|||
{{#if this.post}}
|
||||
<div class="flex flex-row">
|
||||
<GhEditor
|
||||
@tagName="section"
|
||||
@class="gh-editor gh-view relative"
|
||||
as |editor|
|
||||
>
|
||||
<header class="gh-editor-header br2 pe-none">
|
||||
<Editor::PublishManagement
|
||||
@post={{this.post}}
|
||||
@hasUnsavedChanges={{this.hasDirtyAttributes}}
|
||||
@beforePublish={{perform this.beforeSaveTask}}
|
||||
@afterPublish={{this.afterSave}}
|
||||
@saveTask={{this.saveTask}}
|
||||
as |publishManagement|
|
||||
>
|
||||
<div class="flex items-center pe-auto h-100">
|
||||
{{#if this.ui.isFullScreen}}
|
||||
<LinkTo @route={{pluralize this.post.displayName }} class="gh-btn-editor gh-editor-back-button" data-test-link={{pluralize this.post.displayName}}>
|
||||
<span>
|
||||
{{svg-jar "arrow-left"}}
|
||||
{{capitalize (pluralize this.post.displayName)}}
|
||||
</span>
|
||||
</LinkTo>
|
||||
{{/if}}
|
||||
<div class="gh-editor-post-status">
|
||||
<span>
|
||||
<GhEditorPostStatus
|
||||
@post={{this.post}}
|
||||
@hasDirtyAttributes={{this.hasDirtyAttributes}}
|
||||
@isSaving={{or this.autosaveTask.isRunning this.saveTasks.isRunning}}
|
||||
@openUpdateFlow={{publishManagement.openUpdateFlow}}
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section class="flex items-center pe-auto h-100">
|
||||
{{#unless this.post.isNew}}
|
||||
<Editor::PublishButtons @publishManagement={{publishManagement}} />
|
||||
|
||||
{{#unless this.showSettingsMenu}}
|
||||
<div class="settings-menu-toggle-spacer"></div>
|
||||
{{/unless}}
|
||||
{{/unless}}
|
||||
</section>
|
||||
</Editor::PublishManagement>
|
||||
</header>
|
||||
|
||||
{{!--
|
||||
gh-koenig-editor acts as a wrapper around the title input and
|
||||
koenig editor canvas to support Ghost-specific editor behaviour
|
||||
--}}
|
||||
<GhKoenigEditorReact
|
||||
@title={{readonly this.post.titleScratch}}
|
||||
@titleAutofocus={{this.shouldFocusTitle}}
|
||||
@titlePlaceholder={{concat (capitalize this.post.displayName) " title"}}
|
||||
@onTitleChange={{this.updateTitleScratch}}
|
||||
@onTitleBlur={{perform this.saveTitleTask}}
|
||||
@body={{readonly this.post.scratch}}
|
||||
@bodyPlaceholder={{concat "Begin writing your " this.post.displayName "..."}}
|
||||
@onBodyChange={{this.updateScratch}}
|
||||
@headerOffset={{editor.headerHeight}}
|
||||
@scrollContainerSelector=".gh-koenig-editor"
|
||||
@scrollOffsetBottomSelector=".gh-mobile-nav-bar"
|
||||
@onEditorCreated={{this.setKoenigEditor}}
|
||||
@onWordCountChange={{this.updateWordCount}}
|
||||
@snippets={{this.snippets}}
|
||||
@saveSnippet={{if this.canManageSnippets this.saveSnippet}}
|
||||
@updateSnippet={{if this.canManageSnippets this.toggleUpdateSnippetModal}}
|
||||
@deleteSnippet={{if this.canManageSnippets this.toggleDeleteSnippetModal}}
|
||||
@featureImage={{this.post.featureImage}}
|
||||
@featureImageAlt={{this.post.featureImageAlt}}
|
||||
@featureImageCaption={{this.post.featureImageCaption}}
|
||||
@setFeatureImage={{this.setFeatureImage}}
|
||||
@setFeatureImageAlt={{this.setFeatureImageAlt}}
|
||||
@setFeatureImageCaption={{this.setFeatureImageCaption}}
|
||||
@clearFeatureImage={{this.clearFeatureImage}}
|
||||
@cardOptions={{hash
|
||||
post=this.post
|
||||
}}
|
||||
@postType={{this.post.displayName}}
|
||||
/>
|
||||
|
||||
<div class="gh-editor-wordcount-container">
|
||||
<div class="gh-editor-wordcount">
|
||||
{{gh-pluralize this.wordCount.wordCount "word"}}
|
||||
</div>
|
||||
<a href="https://ghost.org/help/using-the-editor/" class="flex" target="_blank" rel="noopener noreferrer">{{svg-jar "help"}}</a>
|
||||
</div>
|
||||
|
||||
</GhEditor>
|
||||
|
||||
{{#if this.showSettingsMenu}}
|
||||
<GhPostSettingsMenu
|
||||
@post={{this.post}}
|
||||
@deletePost={{this.openDeletePostModal}}
|
||||
@updateSlugTask={{this.updateSlugTask}}
|
||||
@savePostTask={{this.savePostTask}}
|
||||
/>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
<button type="button" class="settings-menu-toggle gh-btn gh-btn-editor gh-btn-icon icon-only gh-btn-action-icon" title="Settings" {{on "click" this.toggleSettingsMenu}} data-test-psm-trigger>
|
||||
{{#if this.showSettingsMenu}}
|
||||
<span class="settings-menu-open">{{svg-jar "sidemenu-open"}}</span>
|
||||
{{else}}
|
||||
<span>{{svg-jar "sidemenu"}}</span>
|
||||
{{/if}}
|
||||
</button>
|
||||
|
||||
{{#if this.showReAuthenticateModal}}
|
||||
<GhFullscreenModal @modal="re-authenticate"
|
||||
@close={{this.toggleReAuthenticateModal}}
|
||||
@modifier="action wide" />
|
||||
{{/if}}
|
||||
|
||||
{{#if this.showUpgradeModal}}
|
||||
<GhFullscreenModal
|
||||
@modal="upgrade-host-limit"
|
||||
@model={{hash
|
||||
message=this.hostLimitError.context
|
||||
details=this.hostLimitError.details
|
||||
}}
|
||||
@close={{this.closeUpgradeModal}}
|
||||
@modifier="action wide"
|
||||
/>
|
||||
{{/if}}
|
||||
|
||||
{{#if this.snippetToUpdate}}
|
||||
<GhFullscreenModal
|
||||
@modal="update-snippet"
|
||||
@model={{this.snippetToUpdate}}
|
||||
@confirm={{this.updateSnippet}}
|
||||
@close={{this.toggleUpdateSnippetModal}}
|
||||
@modifier="action wide"
|
||||
/>
|
||||
{{/if}}
|
||||
|
||||
{{#if this.snippetToDelete}}
|
||||
<GhFullscreenModal
|
||||
@modal="delete-snippet"
|
||||
@model={{this.snippetToDelete}}
|
||||
@confirm={{this.deleteSnippet}}
|
||||
@close={{this.toggleDeleteSnippetModal}}
|
||||
@modifier="action wide"
|
||||
/>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
|
||||
{{outlet}}
|
|
@ -233,9 +233,6 @@ module.exports = function (defaults) {
|
|||
process: 'process/browser'
|
||||
})
|
||||
]
|
||||
},
|
||||
alias: {
|
||||
'react-mobiledoc-editor': 'react-mobiledoc-editor/dist/main.js'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue