Wired up lexical editor saving
no issue - fixed API returning "Invalid mobiledoc structure" errors when `mobiledoc:null` is sent in the payload alongside `lexical: '{...}'` - updated Admin's `posts` and `pages` adapters to always add `?formats=mobiledoc,lexical` because the API doesn't return `lexical` by default - added `lexical` attribute to Admin's Post model - updated `lexical-editor` controller and related components to work with `lexical` always being a JSON string rather than a parsed object - updated `<KoenigLexicalEditor>` to pass through the lexical state string as initial state and wired up the `onChange` prop
This commit is contained in:
parent
2d9dd4639d
commit
a7c4991af5
@ -7,6 +7,10 @@ export default class Page extends ApplicationAdapter {
|
||||
}
|
||||
|
||||
buildQuery(store, modelName, options) {
|
||||
if (!options.formats) {
|
||||
options.formats = 'mobiledoc,lexical';
|
||||
}
|
||||
|
||||
return options;
|
||||
}
|
||||
}
|
||||
|
@ -25,6 +25,10 @@ export default class Post extends ApplicationAdapter {
|
||||
}
|
||||
|
||||
buildQuery(store, modelName, options) {
|
||||
if (!options.formats) {
|
||||
options.formats = 'mobiledoc,lexical';
|
||||
}
|
||||
|
||||
return options;
|
||||
}
|
||||
}
|
||||
|
@ -35,7 +35,10 @@
|
||||
data-test-editor-title-input={{true}}
|
||||
/>
|
||||
|
||||
<KoenigLexicalEditor />
|
||||
<KoenigLexicalEditor
|
||||
@lexical={{@body}}
|
||||
@onChange={{@onBodyChange}}
|
||||
/>
|
||||
|
||||
{{!-- <KoenigEditor
|
||||
@mobiledoc={{@body}}
|
||||
|
@ -91,8 +91,8 @@ export default class KoenigLexicalEditor extends Component {
|
||||
<div className={['koenig-react-editor', this.args.className].filter(Boolean).join(' ')}>
|
||||
<ErrorHandler>
|
||||
<Suspense fallback={<p className="koenig-react-editor-loading">Loading editor...</p>}>
|
||||
<KoenigComposer>
|
||||
<KoenigEditor />
|
||||
<KoenigComposer initialEditorState={this.args.lexical}>
|
||||
<KoenigEditor onChange={this.args.onChange} />
|
||||
</KoenigComposer>
|
||||
</Suspense>
|
||||
</ErrorHandler>
|
||||
|
@ -27,7 +27,7 @@ const TIMEDSAVE_TIMEOUT = 60000;
|
||||
|
||||
// this array will hold properties we need to watch for this.hasDirtyAttributes
|
||||
let watchedProps = [
|
||||
'post.scratch',
|
||||
'post.lexicalScratch',
|
||||
'post.titleScratch',
|
||||
'post.hasDirtyAttributes',
|
||||
'post.tags.[]',
|
||||
@ -190,8 +190,8 @@ export default class LexicalEditorController extends Controller {
|
||||
}
|
||||
|
||||
@action
|
||||
updateScratch(mobiledoc) {
|
||||
this.set('post.scratch', mobiledoc);
|
||||
updateScratch(lexical) {
|
||||
this.set('post.lexicalScratch', JSON.stringify(lexical));
|
||||
|
||||
// save 3 seconds after last edit
|
||||
this._autosaveTask.perform();
|
||||
@ -544,10 +544,10 @@ export default class LexicalEditorController extends Controller {
|
||||
|
||||
// Set the properties that are indirected
|
||||
|
||||
// Set mobiledoc equal to what's in the editor but create a copy so that
|
||||
// Set lexical equal to what's in the editor but create a copy so that
|
||||
// nested objects/arrays don't keep references which can mean that both
|
||||
// scratch and mobiledoc get updated simultaneously
|
||||
this.set('post.mobiledoc', JSON.parse(JSON.stringify(this.post.scratch || null)));
|
||||
// scratch and lexical get updated simultaneously
|
||||
this.set('post.lexical', this.post.lexicalScratch || null);
|
||||
|
||||
// Set a default title
|
||||
if (!this.get('post.titleScratch').trim()) {
|
||||
@ -692,17 +692,17 @@ export default class LexicalEditorController extends Controller {
|
||||
post.updateTags();
|
||||
this._previousTagNames = this._tagNames;
|
||||
|
||||
// update the scratch property if it's `null` and we get a blank mobiledoc
|
||||
// update the scratch property if it's `null` and we get a blank lexical
|
||||
// back from the API - prevents "unsaved changes" modal on new+blank posts
|
||||
if (!post.scratch) {
|
||||
post.set('scratch', JSON.parse(JSON.stringify(post.get('mobiledoc'))));
|
||||
if (!post.lexicalScratch) {
|
||||
post.set('lexicalScratch', post.get('lexical'));
|
||||
}
|
||||
|
||||
// if the two "scratch" properties (title and content) match the post,
|
||||
// then it's ok to set hasDirtyAttributes to false
|
||||
// TODO: why is this necessary?
|
||||
let titlesMatch = post.get('titleScratch') === post.get('title');
|
||||
let bodiesMatch = JSON.stringify(post.get('scratch')) === JSON.stringify(post.get('mobiledoc'));
|
||||
let bodiesMatch = post.get('lexicalScratch') === post.get('lexical');
|
||||
|
||||
if (titlesMatch && bodiesMatch) {
|
||||
this.set('hasDirtyAttributes', false);
|
||||
@ -789,7 +789,7 @@ export default class LexicalEditorController extends Controller {
|
||||
// edit of the post
|
||||
// TODO: can these be `boundOneWay` on the model as per the other attrs?
|
||||
post.set('titleScratch', post.get('title'));
|
||||
post.set('scratch', post.get('mobiledoc'));
|
||||
post.set('lexicalScratch', post.get('lexical'));
|
||||
|
||||
this._previousTagNames = this._tagNames;
|
||||
|
||||
@ -980,15 +980,12 @@ export default class LexicalEditorController extends Controller {
|
||||
}
|
||||
|
||||
// scratch isn't an attr so needs a manual dirty check
|
||||
let mobiledoc = post.get('mobiledoc');
|
||||
let scratch = post.get('scratch');
|
||||
let lexical = post.get('lexical');
|
||||
let scratch = post.get('lexicalScratch');
|
||||
// additional guard in case we are trying to compare null with undefined
|
||||
if (scratch || mobiledoc) {
|
||||
let mobiledocJSON = JSON.stringify(mobiledoc);
|
||||
let scratchJSON = JSON.stringify(scratch);
|
||||
|
||||
if (scratchJSON !== mobiledocJSON) {
|
||||
this._leaveModalReason = {reason: 'mobiledoc is different', context: {current: mobiledocJSON, scratch: scratchJSON}};
|
||||
if (scratch || lexical) {
|
||||
if (scratch !== lexical) {
|
||||
this._leaveModalReason = {reason: 'lexical is different', context: {current: lexical, scratch}};
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -94,7 +94,7 @@ export default Model.extend(Comparable, ValidationEngine, {
|
||||
metaDescription: attr('string'),
|
||||
metaTitle: attr('string'),
|
||||
mobiledoc: attr('json-string'),
|
||||
lexical: attr('json-string'),
|
||||
lexical: attr(),
|
||||
plaintext: attr('string'),
|
||||
publishedAtUTC: attr('moment-utc'),
|
||||
slug: attr('string'),
|
||||
@ -122,6 +122,7 @@ export default Model.extend(Comparable, ValidationEngine, {
|
||||
primaryTag: reads('tags.firstObject'),
|
||||
|
||||
scratch: null,
|
||||
lexicalScratch: null,
|
||||
titleScratch: null,
|
||||
|
||||
// HACK: used for validation so that date/time can be validated based on
|
||||
|
@ -57,7 +57,7 @@
|
||||
@titlePlaceholder={{concat (capitalize this.post.displayName) " title"}}
|
||||
@onTitleChange={{this.updateTitleScratch}}
|
||||
@onTitleBlur={{perform this.saveTitleTask}}
|
||||
@body={{readonly this.post.scratch}}
|
||||
@body={{readonly this.post.lexicalScratch}}
|
||||
@bodyPlaceholder={{concat "Begin writing your " this.post.displayName "..."}}
|
||||
@onBodyChange={{this.updateScratch}}
|
||||
@headerOffset={{editor.headerHeight}}
|
||||
|
@ -610,7 +610,7 @@ Post = ghostBookshelf.Model.extend({
|
||||
// CASE: ?force_rerender=true passed via Admin API
|
||||
// CASE: html is null, but mobiledoc exists (only important for migrations & importing)
|
||||
if (
|
||||
this.hasChanged('mobiledoc')
|
||||
(this.hasChanged('mobiledoc') && !this.get('lexical'))
|
||||
|| options.force_rerender
|
||||
|| (!this.get('html') && (options.migrating || options.importing))
|
||||
) {
|
||||
|
@ -67,7 +67,8 @@ describe('Posts API', function () {
|
||||
[0, [], 0, 'Testing post creation with mobiledoc']
|
||||
]]
|
||||
]
|
||||
})
|
||||
}),
|
||||
lexical: null
|
||||
};
|
||||
|
||||
await agent
|
||||
@ -86,6 +87,7 @@ describe('Posts API', function () {
|
||||
it('Can create a post with lexical', async function () {
|
||||
const post = {
|
||||
title: 'Lexical test',
|
||||
mobiledoc: null,
|
||||
lexical: JSON.stringify({
|
||||
editorState: {
|
||||
root: {
|
||||
|
Loading…
Reference in New Issue
Block a user