🐛 Fixed existing snippets not being available in beta editor

closes https://github.com/TryGhost/Team/issues/3387

- adds syncing of mobiledoc->lexical formats for snippets when opening the beta editor
- this is a one-way sync
  - new snippets or changes made to snippets inside the beta will not be available in the old editor
  - creating or changing snippets in the old editor will sync to (and potentially overwrite snippets) in the beta editor
- fixed incorrect saving of doubly-escaped JSON when creating snippets in the beta editor
This commit is contained in:
Kevin Ansfield 2023-06-06 12:56:33 +01:00
parent 7170b2489e
commit ed24899fa5
No known key found for this signature in database
4 changed files with 61 additions and 10 deletions

View File

@ -15,6 +15,10 @@ export default class UpdateSnippetModal extends Component {
}
if (lexical) {
snippet.lexical = lexical;
if (!snippet.mobiledoc) {
snippet.mobiledoc = {};
}
}
yield snippet.save();

View File

@ -173,8 +173,8 @@ export default class EditorController extends Controller {
get snippets() {
return this._snippets
.reject(snippet => snippet.get('isNew'))
.sort((a, b) => a.name.localeCompare(b.name))
.filterBy('lexical', null);
.reject(snippet => JSON.stringify(snippet.mobiledoc) === '{}')
.sort((a, b) => a.name.localeCompare(b.name));
}
@computed('session.user.{isAdmin,isEditor}')

View File

@ -22,6 +22,7 @@ import {isBlank} from '@ember/utils';
import {isArray as isEmberArray} from '@ember/array';
import {isHostLimitError, isServerUnreachableError, isVersionMismatchError} from 'ghost-admin/services/ajax';
import {isInvalidError} from 'ember-ajax/errors';
import {mobiledocToLexical} from '@tryghost/kg-converters';
import {inject as service} from '@ember/service';
const DEFAULT_TITLE = '(Untitled)';
@ -171,14 +172,15 @@ export default class LexicalEditorController extends Controller {
return this.store.peekAll('snippet');
}
@computed('_snippets.@each.{name,isNew}')
@computed('_snippets.@each.{name,isNew,mobiledoc,lexical}')
get snippets() {
const snippets = this._snippets
.reject(snippet => snippet.get('isNew'))
.sort((a, b) => a.name.localeCompare(b.name))
.filter(item => item.lexical !== null);
return snippets.map((item) => {
item.value = item.lexical;
item.value = JSON.stringify(item.lexical);
return item;
});
@ -360,9 +362,10 @@ export default class LexicalEditorController extends Controller {
}
@action
saveSnippet(snippet) {
const snippetData = {name: snippet.name, lexical: snippet.value, mobiledoc: '{}'};
let snippetRecord = this.store.createRecord('snippet', snippetData);
saveNewSnippet(snippet) {
const snippetData = {name: snippet.name, lexical: JSON.parse(snippet.value), mobiledoc: {}};
const snippetRecord = this.store.createRecord('snippet', snippetData);
return snippetRecord.save().then(() => {
this.notifications.closeAlerts('snippet.save');
this.notifications.showNotification(
@ -388,9 +391,9 @@ export default class LexicalEditorController extends Controller {
const existingSnippet = this.snippets.find(snippet => snippet.name.toLowerCase() === snippetNameLC);
if (existingSnippet) {
await this.confirmUpdateSnippet(existingSnippet, {lexical: data.value, mobiledoc: '{}'});
await this.confirmUpdateSnippet(existingSnippet, {lexical: data.value});
} else {
await this.saveSnippet(data);
await this.saveNewSnippet(data);
}
}
@ -771,10 +774,46 @@ export default class LexicalEditorController extends Controller {
}
}
// load supplementel data such as the members count in the background
// load supplemental data such as snippets and members count in the background
@restartableTask
*backgroundLoaderTask() {
yield this.store.query('snippet', {limit: 'all'});
this.syncMobiledocSnippets();
}
@action
syncMobiledocSnippets() {
const snippets = this.store.peekAll('snippet');
snippets.forEach((snippet) => {
if (!snippet.lexical || snippet.lexical.syncedAt && moment.utc(snippet.lexical.syncedAt).isBefore(snippet.updatedAtUTC)) {
const serializedLexical = mobiledocToLexical(JSON.stringify(snippet.mobiledoc));
// we get a full Lexical doc from the converter but Lexical only
// stores an array of nodes in it's copy/paste dataset that we use for snippets
const lexical = JSON.parse(serializedLexical);
let nodes = lexical.root.children;
// for a single-paragraph text selection Lexical only stores the
// text children in the nodes array
if (nodes.length === 1 && nodes[0].type === 'paragraph') {
nodes = nodes[0].children;
}
const lexicalData = {
namespace: 'KoenigEditor',
nodes
};
// set syncedAt so we can check if mobiledoc has been updated in next sync
lexicalData.syncedAt = moment.utc().toISOString();
snippet.lexical = lexicalData;
// kick off a background save, we already have .lexical updated which is what we need
snippet.save();
}
});
}
/* Public methods --------------------------------------------------------*/

View File

@ -0,0 +1,8 @@
import ApplicationSerializer from 'ghost-admin/serializers/application';
export default class PostSerializer extends ApplicationSerializer {
attrs = {
createdAtUTC: {key: 'created_at'},
updatedAtUTC: {key: 'updated_at'}
};
}