Koenig - Keep MD card formatting toolbar visible when possible

refs https://github.com/TryGhost/Ghost/issues/9505
- use `{{gh-scroll-trigger}}` components at top and bottom of the markdown card when in edit mode so that styles can be applied to the `.editor-toolbar` element to keep the toolbar fixed at the bottom of the screen whilst scrolling
This commit is contained in:
Kevin Ansfield 2018-04-24 13:10:20 +01:00
parent af03f8f516
commit b21e46aa39
5 changed files with 177 additions and 11 deletions

View File

@ -3,24 +3,42 @@ import InViewportMixin from 'ember-in-viewport';
export default Component.extend(InViewportMixin, {
onEnterViewport() {},
enter() {},
exit() {},
registerElement() {},
didInsertElement() {
let offset = this.get('triggerOffset');
let offset = this.get('triggerOffset') || {};
// if triggerOffset is a number we use it for all dimensions
if (typeof offset === 'number') {
offset = {
top: offset,
bottom: offset,
left: offset,
right: offset
};
}
this.set('viewportSpy', true);
this.set('viewportTolerance', {
top: offset,
bottom: offset,
left: offset,
right: offset
top: offset.top,
bottom: offset.bottom,
left: offset.left,
right: offset.right
});
this._super(...arguments);
this.registerElement(this.element);
},
didEnterViewport() {
return this.onEnterViewport();
return this.enter();
},
didExitViewport() {
return this.exit();
}
});

View File

@ -266,6 +266,8 @@
/* specific cards... */
/* HTML */
.koenig-card-html--editor .CodeMirror {
min-height: 170px;
padding: 0;
@ -283,6 +285,8 @@
margin-right: 0;
}
/* Markdown */
.koenig-editor .gh-markdown-editor {
position: static;
overflow: visible;
@ -295,17 +299,22 @@
right: 0;
border-left: none;
border-right: none;
border-radius: 0 0 .4rem .4rem;
z-index: 9999;
border-radius: 0;
background-color: #fff;
opacity: 1;
text-align: center;
}
/* margin to account for absolutely positioned toolbar */
.koenig-editor .gh-markdown-editor .CodeMirror {
min-height: 130px;
/* margin to account for absolutely positioned toolbar */
margin-bottom: 49px;
}
.koenig-editor .gh-markdown-editor .CodeMirror-scroll {
min-height: 130px;
}
/* mobiledoc-kit base styles ------------------------------------------------
* NOTE: adapted from https://github.com/bustle/mobiledoc-kit/blob/master/src/css/mobiledoc-kit.css

View File

@ -56,7 +56,7 @@
{{/if}}
{{gh-scroll-trigger
onEnterViewport=(action "loadNextPage")
enter=(action "loadNextPage")
triggerOffset=1000}}
</div>
</div>

View File

@ -7,6 +7,8 @@ import {isBlank} from '@ember/utils';
import {run} from '@ember/runloop';
import {set} from '@ember/object';
const MIN_HEIGHT = 130;
export default Component.extend({
layout,
@ -15,6 +17,9 @@ export default Component.extend({
isSelected: false,
isEditing: false,
// internal attrs
bottomOffset: 0,
// closure actions
editCard() {},
saveCard() {},
@ -40,6 +45,18 @@ export default Component.extend({
}
}),
init() {
this._super(...arguments);
// subtract toolbar height from MIN_HEIGHT so the trigger happens at
// the expected position without forcing the min height to be too small
this.set('bottomOffset', -MIN_HEIGHT - 49);
},
willDestroyElement() {
this._super(...arguments);
this._teardownResizeHandler();
},
actions: {
enterEditMode() {
// this action is called before the component is rendered so we
@ -65,10 +82,119 @@ export default Component.extend({
// update the mobiledoc and stay in edit mode
save(payload, false);
},
// fires if top comes into view 0 px from viewport top
// fires if top comes into view MIN_HEIGHTpx above viewport bottom
topEntered() {
this._isTopVisible = true;
run.scheduleOnce('actions', this, this._applyToolbarStyles);
},
// fires if top leaves viewport 0 px from viewport top
// fires if top leaves viewport MIN_HEIGHTpx above viewport bottom
topExited() {
let top = this._topElement.getBoundingClientRect().top;
this._isTopVisible = false;
this._isTopAbove = top < 0;
run.scheduleOnce('actions', this, this._applyToolbarStyles);
},
bottomEntered() {
this._isBottomVisible = true;
run.scheduleOnce('actions', this, this._applyToolbarStyles);
},
bottomExited() {
let top = this._bottomElement.getBoundingClientRect().top;
this._isBottomVisible = false;
this._isBottomBelow = top > window.innerHeight;
run.scheduleOnce('actions', this, this._applyToolbarStyles);
},
registerTop(element) {
this._topElement = element;
},
registerBottom(element) {
this._bottomElement = element;
}
},
_focusTextarea() {
this.element.querySelector('textarea').focus();
},
_applyToolbarStyles() {
let toolbar = this.element.querySelector('.editor-toolbar');
if (!toolbar) {
return;
}
let {left, width} = this._containerDimensions();
let style = '';
let stuckTop = `top: ${MIN_HEIGHT}px; bottom: auto`;
let fixedBottom = `position: fixed; left: ${left + 1}px; width: ${width - 2}px`;
let stuckBottom = '';
if (this._isTopVisible && this._isBottomVisible) {
style = stuckBottom;
}
if (this._isTopVisible && !this._isBottomVisible) {
style = fixedBottom;
}
if (!this._isTopVisible && !this._isTopAbove) {
style = stuckTop;
}
if (!this._isTopVisible && this._isBottomVisible) {
style = stuckBottom;
}
if (!this._isTopVisible && !this._isBottomVisible && this._isTopAbove && this._isBottomBelow) {
style = fixedBottom;
}
// set up resize watchers if in fixed position because we have to
// recalculate left position and width
if (!this._resizeHandler && style === fixedBottom) {
this._setupResizeHandler();
} else if (this._resizeHandler && style !== fixedBottom) {
this._teardownResizeHandler();
}
// account for the mobile nav bar when in fixed position
if (style === fixedBottom) {
let mobileNav = document.querySelector('.gh-mobile-nav-bar');
if (mobileNav.offsetHeight) {
style = `${style}; bottom: ${mobileNav.offsetHeight}px`;
}
}
toolbar.setAttribute('style', style);
},
_containerDimensions() {
return this.element.querySelector('.kg-card-selected').getBoundingClientRect();
},
_setupResizeHandler() {
if (this._resizeHandler) {
return;
}
this._resizeHandler = run.bind(this, this._applyToolbarStyles);
window.addEventListener('resize', this._resizeHandler);
},
_teardownResizeHandler() {
window.removeEventListener('resize', this._resizeHandler);
this._resizeHandler = null;
}
});

View File

@ -11,6 +11,13 @@
}}
{{#if isEditing}}
{{#gh-editor as |editor|}}
{{gh-scroll-trigger
triggerOffset=(hash bottom=bottomOffset)
enter=(action "topEntered")
exit=(action "topExited")
registerElement=(action "registerTop")
}}
{{#gh-markdown-editor
markdown=(readonly payload.markdown)
onChange=(action "updateMarkdown")
@ -26,6 +33,12 @@
{{markdown.editor}}
{{/gh-markdown-editor}}
{{gh-scroll-trigger
enter=(action "bottomEntered")
exit=(action "bottomExited")
registerElement=(action "registerBottom")
}}
{{!-- files are dragged over editor pane --}}
{{#if editor.isDraggedOver}}
<div class="drop-target gh-editor-drop-target">