Prototyped component based atoms in the editor
refs https://github.com/TryGhost/Team/issues/931 - adds handling for component atoms - add prototype button atom to test atom behaviour - add `Cmd+Shift+B` keyboard shortcut to create a dummy button atom when the `emailCardSegments` feature is enabled
This commit is contained in:
parent
8c23a9b3bf
commit
b9d262ffa6
|
@ -0,0 +1,21 @@
|
|||
<a href="https://ghost.org" class="gh-btn"
|
||||
{{on "mouseover" (fn (mut this.isHovered) true)}}
|
||||
{{on "mouseleave" (fn (mut this.isHovered) false)}}
|
||||
>
|
||||
<span>{{@atom.value}}</span>
|
||||
</a>
|
||||
|
||||
{{#if this.isHovered}}
|
||||
<KgActionBar @class="absolute" @style={{this.toolbarStyle}} @isVisible={{true}} @instantClose={{this.koenigUi.inputHasFocus}}>
|
||||
<li class="ma0 lh-solid">
|
||||
<button
|
||||
type="button"
|
||||
title="Delete button"
|
||||
class="dib dim-lite link h9 w9 nudge-top--1 justify-center"
|
||||
{{on "click" this.deleteButton}}
|
||||
>
|
||||
{{svg-jar "koenig/kg-trash" class="fill-white w4 h4"}}
|
||||
</button>
|
||||
</li>
|
||||
</KgActionBar>
|
||||
{{/if}}
|
|
@ -0,0 +1,12 @@
|
|||
import Component from '@glimmer/component';
|
||||
import {action} from '@ember/object';
|
||||
import {tracked} from '@glimmer/tracking';
|
||||
|
||||
export default class KoenigAtomButtonComponent extends Component {
|
||||
@tracked isHovered = false;
|
||||
|
||||
@action
|
||||
deleteButton() {
|
||||
// noop
|
||||
}
|
||||
}
|
|
@ -93,4 +93,16 @@
|
|||
registerComponent=(action (mut card.component))
|
||||
}}
|
||||
{{/in-element}}
|
||||
{{/each}}
|
||||
|
||||
{{!-- all component atoms wormholed into the editor canvas --}}
|
||||
{{#each this.componentAtoms as |atom|}}
|
||||
{{#in-element atom.destinationElement}}
|
||||
{{component atom.componentName
|
||||
editor=this.editor
|
||||
atom=atom
|
||||
saveAtom=(fn atom.env.save atom.env.value atom.env.payload)
|
||||
registerComponent=(action (mut atom.component))
|
||||
}}
|
||||
{{/in-element}}
|
||||
{{/each}}
|
|
@ -10,7 +10,7 @@ import EmberObject, {computed, get} from '@ember/object';
|
|||
import Key from 'mobiledoc-kit/utils/key';
|
||||
import MobiledocRange from 'mobiledoc-kit/utils/cursor/range';
|
||||
import calculateReadingTime from '../utils/reading-time';
|
||||
import defaultAtoms from '../options/atoms';
|
||||
import defaultAtoms, {ATOM_COMPONENT_MAP} from '../options/atoms';
|
||||
import defaultCards, {CARD_COMPONENT_MAP, CARD_ICON_MAP} from '../options/cards';
|
||||
import formatMarkdown from 'ghost-admin/utils/format-markdown';
|
||||
import registerKeyCommands from '../options/key-commands';
|
||||
|
@ -38,6 +38,8 @@ const UNDO_DEPTH = 100;
|
|||
|
||||
export const ADD_CARD_HOOK = 'addComponent';
|
||||
export const REMOVE_CARD_HOOK = 'removeComponent';
|
||||
export const ADD_ATOM_HOOK = 'addAtomComponent';
|
||||
export const REMOVE_ATOM_HOOK = 'removeAtomComponent';
|
||||
|
||||
// used in test helpers to grab a reference to the underlying mobiledoc editor
|
||||
export const TESTING_EXPANDO_PROPERTY = '__mobiledoc_kit_editor';
|
||||
|
@ -170,7 +172,9 @@ function insertImageCards(files, postEditor) {
|
|||
}
|
||||
|
||||
export default Component.extend({
|
||||
feature: service(),
|
||||
koenigDragDropHandler: service(),
|
||||
koenigUi: service(),
|
||||
|
||||
tagName: 'article',
|
||||
classNames: ['koenig-editor', 'w-100', 'flex-grow', 'relative', 'center', 'mb0', 'mt0'],
|
||||
|
@ -192,6 +196,7 @@ export default Component.extend({
|
|||
activeMarkupTagNames: null,
|
||||
activeSectionTagNames: null,
|
||||
selectedRange: null,
|
||||
componentAtoms: null,
|
||||
componentCards: null,
|
||||
linkRange: null,
|
||||
selectedCard: null,
|
||||
|
@ -256,6 +261,7 @@ export default Component.extend({
|
|||
this.set('mobiledoc', mobiledoc);
|
||||
}
|
||||
|
||||
this.set('componentAtoms', A([]));
|
||||
this.set('componentCards', A([]));
|
||||
this.set('activeMarkupTagNames', {});
|
||||
this.set('activeSectionTagNames', {});
|
||||
|
@ -367,6 +373,46 @@ export default Component.extend({
|
|||
// triggered when a card section is removed from the mobiledoc
|
||||
[REMOVE_CARD_HOOK]: (card) => {
|
||||
this.componentCards.removeObject(card);
|
||||
},
|
||||
[ADD_ATOM_HOOK]: ({env, options, value, payload}) => {
|
||||
const atomName = env.name;
|
||||
const componentName = ATOM_COMPONENT_MAP[atomName];
|
||||
|
||||
const payloadCopy = new TrackedObject(JSON.parse(JSON.stringify(payload || null)));
|
||||
|
||||
const atom = EmberObject.create({
|
||||
atomName,
|
||||
componentName,
|
||||
value,
|
||||
payload: payloadCopy,
|
||||
env,
|
||||
options,
|
||||
editor
|
||||
});
|
||||
|
||||
// the desination element is the container that gets rendered
|
||||
// inside the editor, once rendered we use {{in-element}} to
|
||||
// wormhole in the actual ember component
|
||||
let atomId = guidFor(atom);
|
||||
let destinationElementId = `koenig-editor-atom-${atomId}`;
|
||||
let destinationElement = document.createElement('div');
|
||||
destinationElement.id = destinationElementId;
|
||||
destinationElement.classList.add('dib');
|
||||
|
||||
atom.setProperties({
|
||||
destinationElementId,
|
||||
destinationElement
|
||||
});
|
||||
|
||||
run.schedule('afterRender', () => {
|
||||
this.componentAtoms.pushObject(atom);
|
||||
});
|
||||
|
||||
// render the destination element inside the editor
|
||||
return {atom, element: destinationElement};
|
||||
},
|
||||
[REMOVE_ATOM_HOOK]: (atom) => {
|
||||
this.componentAtoms.removeObject(atom);
|
||||
}
|
||||
};
|
||||
editorOptions.cardOptions = componentHooks;
|
||||
|
|
|
@ -1,6 +1,12 @@
|
|||
// Atoms are effectively read-only inline cards
|
||||
// Full docs: https://github.com/bustle/mobiledoc-kit/blob/master/ATOMS.md
|
||||
|
||||
import createComponentAtom from '../utils/create-component-atom';
|
||||
|
||||
export const ATOM_COMPONENT_MAP = {
|
||||
button: 'koenig-atom-button'
|
||||
};
|
||||
|
||||
export default [
|
||||
// soft-return is triggered by SHIFT+ENTER and allows for line breaks
|
||||
// without creating paragraphs
|
||||
|
@ -10,5 +16,6 @@ export default [
|
|||
render() {
|
||||
return document.createElement('br');
|
||||
}
|
||||
}
|
||||
},
|
||||
createComponentAtom('button')
|
||||
];
|
||||
|
|
|
@ -419,6 +419,31 @@ export const DEFAULT_KEY_COMMANDS = [{
|
|||
|
||||
return false;
|
||||
}
|
||||
}, {
|
||||
str: 'META+SHIFT+B',
|
||||
run(editor, koenig) {
|
||||
if (!koenig.feature.emailCardSegments || !editor.range.headSection.isMarkerable) {
|
||||
return;
|
||||
}
|
||||
|
||||
editor.run((postEditor) => {
|
||||
const texts = [
|
||||
'Hit me!',
|
||||
'Hit me!',
|
||||
'Hit me!',
|
||||
'Hit me slowly',
|
||||
'Hit me quick',
|
||||
'Hit me with your rhythm stick!'
|
||||
];
|
||||
|
||||
const buttonText = texts[koenig.koenigUi.buttonCount % texts.length];
|
||||
koenig.koenigUi.buttonCount += 1;
|
||||
|
||||
const button = postEditor.builder.createAtom('button', buttonText);
|
||||
const endPos = postEditor.insertMarkers(editor.range.head, [button]);
|
||||
postEditor.insertText(endPos, ' ');
|
||||
});
|
||||
}
|
||||
}];
|
||||
|
||||
// key commands that are used in koenig-basic-html-input
|
||||
|
|
|
@ -6,6 +6,8 @@ export default class KoenigUiService extends Service {
|
|||
@tracked inputHasFocus = false;
|
||||
@tracked isDragging = false;
|
||||
|
||||
buttonCount = 0;
|
||||
|
||||
#focusedCaption = null;
|
||||
|
||||
captionGainedFocus(caption) {
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
import {
|
||||
ADD_ATOM_HOOK,
|
||||
REMOVE_ATOM_HOOK
|
||||
} from '../components/koenig-editor';
|
||||
|
||||
const RENDER_TYPE = 'dom';
|
||||
|
||||
function renderFallback(doc) {
|
||||
let element = doc.createElement('span');
|
||||
let text = doc.createTextNode('[placeholder for Ember component atom]');
|
||||
element.appendChild(text);
|
||||
return element;
|
||||
}
|
||||
|
||||
// sets up boilderplate for an Ember component atom
|
||||
export default function createComponentAtom(name, doc = window.document) {
|
||||
return {
|
||||
name,
|
||||
type: RENDER_TYPE,
|
||||
|
||||
// Called when the atom is added to a mobiledoc document.
|
||||
render(atomArgs) {
|
||||
const {env, options} = atomArgs;
|
||||
|
||||
if (!options[ADD_ATOM_HOOK]) {
|
||||
return renderFallback(doc);
|
||||
}
|
||||
|
||||
const {atom, element} = options[ADD_ATOM_HOOK](atomArgs);
|
||||
|
||||
env.onTeardown(() => options[REMOVE_ATOM_HOOK](atom));
|
||||
|
||||
return element;
|
||||
}
|
||||
};
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
export {default} from 'koenig-editor/components/koenig-atom-button';
|
Loading…
Reference in New Issue