From 2ddedb6005bd95908af0d3cb799d5f5e449b0d4b Mon Sep 17 00:00:00 2001 From: Kevin Ansfield Date: Wed, 31 Jan 2018 15:49:20 +0100 Subject: [PATCH] Koenig - (+) card/list selection menu refs https://github.com/TryGhost/Ghost/issues/9311 - re-implement the (+) card/list selection menu from the old Koenig alpha with improved positioning and event handling - buttons work for the currently available cards - `
` and `markdown` --- ghost/admin/app/styles/components/koenig.css | 162 +++++++++++++++++ .../addon/components/koenig-plus-menu.js | 168 ++++++++++++++++++ .../addon/components/koenig-toolbar.js | 2 +- .../templates/components/koenig-editor.hbs | 6 + .../templates/components/koenig-plus-menu.hbs | 43 +++++ .../app/components/koenig-plus-menu.js | 1 + .../lib/koenig-editor/public/tools/bold.svg | 1 + .../lib/koenig-editor/public/tools/close.svg | 1 + .../public/tools/file-code-1.svg | 1 + .../public/tools/file-code-edit.svg | 1 + .../public/tools/file-picture-add.svg | 1 + .../koenig-editor/public/tools/font-size.svg | 1 + .../koenig-editor/public/tools/html-five.svg | 1 + .../lib/koenig-editor/public/tools/italic.svg | 1 + .../public/tools/link-broken.svg | 1 + .../lib/koenig-editor/public/tools/link.svg | 1 + .../public/tools/list-bullets.svg | 1 + .../public/tools/list-number.svg | 1 + .../koenig-editor/public/tools/paragraph.svg | 1 + .../koenig-editor/public/tools/pullquote.svg | 1 + .../lib/koenig-editor/public/tools/quote.svg | 1 + .../koenig-editor/public/tools/rich-text.svg | 1 + .../public/tools/strikethrough.svg | 1 + .../koenig-editor/public/tools/subscript.svg | 1 + .../public/tools/superscript.svg | 1 + .../koenig-editor/public/tools/underline.svg | 1 + .../components/koenig-plus-menu-test.js | 24 +++ 27 files changed, 425 insertions(+), 1 deletion(-) create mode 100644 ghost/admin/lib/koenig-editor/addon/components/koenig-plus-menu.js create mode 100644 ghost/admin/lib/koenig-editor/addon/templates/components/koenig-plus-menu.hbs create mode 100644 ghost/admin/lib/koenig-editor/app/components/koenig-plus-menu.js create mode 100644 ghost/admin/lib/koenig-editor/public/tools/bold.svg create mode 100644 ghost/admin/lib/koenig-editor/public/tools/close.svg create mode 100644 ghost/admin/lib/koenig-editor/public/tools/file-code-1.svg create mode 100644 ghost/admin/lib/koenig-editor/public/tools/file-code-edit.svg create mode 100644 ghost/admin/lib/koenig-editor/public/tools/file-picture-add.svg create mode 100644 ghost/admin/lib/koenig-editor/public/tools/font-size.svg create mode 100644 ghost/admin/lib/koenig-editor/public/tools/html-five.svg create mode 100644 ghost/admin/lib/koenig-editor/public/tools/italic.svg create mode 100644 ghost/admin/lib/koenig-editor/public/tools/link-broken.svg create mode 100644 ghost/admin/lib/koenig-editor/public/tools/link.svg create mode 100644 ghost/admin/lib/koenig-editor/public/tools/list-bullets.svg create mode 100644 ghost/admin/lib/koenig-editor/public/tools/list-number.svg create mode 100644 ghost/admin/lib/koenig-editor/public/tools/paragraph.svg create mode 100644 ghost/admin/lib/koenig-editor/public/tools/pullquote.svg create mode 100644 ghost/admin/lib/koenig-editor/public/tools/quote.svg create mode 100644 ghost/admin/lib/koenig-editor/public/tools/rich-text.svg create mode 100644 ghost/admin/lib/koenig-editor/public/tools/strikethrough.svg create mode 100644 ghost/admin/lib/koenig-editor/public/tools/subscript.svg create mode 100644 ghost/admin/lib/koenig-editor/public/tools/superscript.svg create mode 100644 ghost/admin/lib/koenig-editor/public/tools/underline.svg create mode 100644 ghost/admin/tests/integration/components/koenig-plus-menu-test.js diff --git a/ghost/admin/app/styles/components/koenig.css b/ghost/admin/app/styles/components/koenig.css index bb3c687dcb..1cef7ca64b 100644 --- a/ghost/admin/app/styles/components/koenig.css +++ b/ghost/admin/app/styles/components/koenig.css @@ -200,10 +200,172 @@ /* ⨁ menu ------------------------------------------------------------------ */ +.koenig-plus-menu { + position: absolute; +} + +.koenig-plus-menu-button { + display: flex; + justify-content: center; + align-items: center; + position: relative; + width: 30px; + height: 30px; + border: var(--midgrey) 1px solid; + background: #fff; + border-radius: 100%; + margin-left: -40px; +} + +.koenig-plus-menu-button svg { + height: 15px; + width: 15px; +} + +.koenig-plus-menu-button svg path { + stroke: var(--midgrey); + stroke-width: 2px; +} + +@media (max-width: 1024px) { + .koenig-plus-menu-button { + right:10px; + } +} + /* Slash shortcut menu ------------------------------------------------------ */ /* Menu items --------------------------------------------------------------- */ +/* Chrome has a bug with its scrollbars on this element which has been reported here: https://bugs.chromium.org/p/chromium/issues/detail?id=697381 */ +.koenig-cardmenu { + position: absolute; + top: 0; + display: flex; + flex-wrap: wrap; + margin: 0 0 20px 0; + padding: 12px 15px; + width: 350px; + max-height: 460px; + overflow-y: auto; + background-color: #fff; + background-clip: padding-box; + border-radius: 4px; + box-shadow: 0 0 0 1px rgba(99,114,130,0.16), 0 8px 16px rgba(27,39,51,0.08); + text-transform: none; + font-size: 1.4rem; + font-weight: normal; + position: absolute; + z-index: 9999999; /* have to compete with codemirror */ +} + +.koenig-cardmenu-button { + display: flex; + justify-content: center; + align-items: center; + position: absolute; + width: 30px; + height: 30px; + border: var(--midgrey) 1px solid; + background: #fff; + border-radius: 100%; + margin-left: -40px; +} + +.koenig-cardmenu-button svg { + height: 15px; + width: 15px; +} + +.koenig-cardmenu-button svg path { + stroke: var(--midgrey); + stroke-width: 2px; +} + +@media (max-width: 1024px) { + .koenig-cardmenu-button { + right:10px; + } +} + +.koenig-cardmenu-search { + position: relative; + width: 350px; + height: 40px; + margin: -12px -15px; +} + +.koenig-cardmenu-search svg { + position: absolute; + top: 11px; + left: 10px; + z-index: 100; + width: 20px; + height: 19px; +} + +.koenig-cardmenu-search-input { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + height: 40px; + font-size: 1.4rem; + line-height: 40px; + padding: 10px 0 10px 40px; + border: none; + border-radius: 4px 4px 0 0; +} + +.koenig-cardmenu-card { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + width: 80px; + height: 80px; + border-radius: 4px; +} + +.koenig-cardmenu-icon { + display: flex; + align-items: center; +} +.koenig-cardmenu-icon svg { + width: 27px; + height: 27px; + fill: var(--darkgrey); +} + +.koenig-cardmenu-label { + margin: 7px 0 0 0; + font-size: 1.1rem; + color: var(--midgrey); + letter-spacing: 0.2px; + font-weight: 200; +} + +.koenig-cardmenu-card:hover, .koenig-cardmenu-card.selected { + cursor: pointer; + background: color(var(--lightgrey) l(+3%) s(-10%)); +} +.koenig-cardmenu-card:hover .koenig-cardmenu-label, .koenig-cardmenu-card.selected .koenig-cardmenu-label { + color: var(--darkgrey); + font-weight: 300; +} + +.koenig-cardmenu-divider { + top: -12px; + width: 350px; + padding: 5px 0; + margin: 12px -15px; + font-size: 1.2rem; + text-align: center; + background: color(var(--lightgrey) l(+3%) s(-10%)); +} + + /* Cards -------------------------------------------------------------------- */ textarea.koenig-card-markdown { diff --git a/ghost/admin/lib/koenig-editor/addon/components/koenig-plus-menu.js b/ghost/admin/lib/koenig-editor/addon/components/koenig-plus-menu.js new file mode 100644 index 0000000000..fa67ae1ebc --- /dev/null +++ b/ghost/admin/lib/koenig-editor/addon/components/koenig-plus-menu.js @@ -0,0 +1,168 @@ +import Component from '@ember/component'; +import layout from '../templates/components/koenig-plus-menu'; +import {computed} from '@ember/object'; +import {htmlSafe} from '@ember/string'; +import {run} from '@ember/runloop'; + +// clicking on anything in the menu will change the selection because the click +// event propagates, this then closes the menu + +// focusing the search input removes the selection in the editor, again closing +// the menu + +// when the menu is open we want to: +// - close if clicked outside the menu +// - keep the selected range around in case it gets changed + +export default Component.extend({ + layout, + + // public attrs + classNames: 'koenig-plus-menu', + attributeBindings: ['style'], + editor: null, + editorRange: null, + + // internal properties + showButton: false, + showMenu: false, + top: 0, + + style: computed('top', function () { + return htmlSafe(`top: ${this.get('top')}px`); + }), + + didReceiveAttrs() { + this._super(...arguments); + + if (!this.get('showMenu')) { + let editorRange = this.get('editorRange'); + + if (!editorRange) { + this.set('showButton', false); + this._hideMenu(); + return; + } + + let {head: {section}} = editorRange; + + // show the button if the cursor is at the beginning of a blank paragraph + if (editorRange && editorRange.isCollapsed && section && !section.isListItem && (section.isBlank || section.text === '')) { + // find the "top" position by grabbing the current sections + // render node and querying it's bounding rect. Setting "top" + // positions the button+menu container element .koenig-plus-menu + let containerRect = this.element.parentNode.getBoundingClientRect(); + let selectedElement = editorRange.head.section.renderNode.element; + let selectedElementRect = selectedElement.getBoundingClientRect(); + let top = selectedElementRect.top - containerRect.top; + + this.set('top', top); + this.set('showButton', true); + this._hideMenu(); + } else { + this.set('showButton', false); + this._hideMenu(); + } + } + }, + + willDestroyElement() { + this._super(...arguments); + window.removeEventListener('mousedown', this._bodyMousedownHandler); + }, + + actions: { + openMenu() { + this._showMenu(); + }, + + closeMenu() { + this._hideMenu(); + }, + + replaceWithCardSection(cardName) { + let editor = this.get('editor'); + let range = this._editorRange; + let {head: {section}} = range; + + editor.run((postEditor) => { + let {builder} = postEditor; + let card = builder.createCardSection(cardName); + let needsTrailingParagraph = !section.next; + + postEditor.replaceSection(section, card); + + if (needsTrailingParagraph) { + let newSection = postEditor.builder.createMarkupSection('p'); + postEditor.insertSectionAtEnd(newSection); + postEditor.setRange(newSection.tailPosition()); + } + + this._hideMenu(); + }); + }, + + replaceWithListSection(listType) { + let editor = this.get('editor'); + let range = this._editorRange; + let {head: {section}} = range; + + editor.run((postEditor) => { + let {builder} = postEditor; + let item = builder.createListItem(); + let listSection = builder.createListSection(listType, [item]); + + postEditor.replaceSection(section, listSection); + postEditor.setRange(listSection.headPosition()); + this._hideMenu(); + }); + } + }, + + _showMenu() { + this.set('showMenu', true); + + // focus the search immediately so that you can filter immediately + run.schedule('afterRender', this, function () { + this._focusSearch(); + }); + + // watch the window for mousedown events so that we can close the menu + // when we detect a click outside + this._bodyMousedownHandler = run.bind(this, (event) => { + this._handleBodyMousedown(event); + }); + window.addEventListener('mousedown', this._bodyMousedownHandler); + + // store a reference to our range because it will change underneath + // us as editor focus is lost + this._editorRange = this.get('editorRange'); + }, + + _hideMenu() { + if (this.get('showMenu')) { + // reset our cached editorRange + this._editorRange = null; + + // stop watching the body for clicks + window.removeEventListener('mousedown', this._bodyMousedownHandler); + + // hide the menu + this.set('showMenu', false); + } + }, + + _focusSearch() { + let search = this.element.querySelector('input'); + if (search) { + search.focus(); + } + }, + + _handleBodyMousedown(event) { + if (!event.target.closest(`#${this.elementId}`)) { + this._hideMenu(); + } + } + +}); diff --git a/ghost/admin/lib/koenig-editor/addon/components/koenig-toolbar.js b/ghost/admin/lib/koenig-editor/addon/components/koenig-toolbar.js index 3aa29e805c..01e2772797 100644 --- a/ghost/admin/lib/koenig-editor/addon/components/koenig-toolbar.js +++ b/ghost/admin/lib/koenig-editor/addon/components/koenig-toolbar.js @@ -21,7 +21,7 @@ export default Component.extend({ // public attrs classNames: ['koenig-toolbar'], classNameBindings: ['showToolbar:koenig-toolbar--visible'], - selectedRange: null, + editorRange: null, // internal properties showToolbar: false, diff --git a/ghost/admin/lib/koenig-editor/addon/templates/components/koenig-editor.hbs b/ghost/admin/lib/koenig-editor/addon/templates/components/koenig-editor.hbs index be0a6695fe..f8aebb7a01 100644 --- a/ghost/admin/lib/koenig-editor/addon/templates/components/koenig-editor.hbs +++ b/ghost/admin/lib/koenig-editor/addon/templates/components/koenig-editor.hbs @@ -66,6 +66,12 @@ {{/koenig-toolbar}} +{{!-- (+) icon and pop-up menu --}} +{{koenig-plus-menu + editorRange=selectedRange + editor=editor +}} + {{#each componentCards as |card|}} {{!-- TODO: move to the public {{in-element}} API when it's available diff --git a/ghost/admin/lib/koenig-editor/addon/templates/components/koenig-plus-menu.hbs b/ghost/admin/lib/koenig-editor/addon/templates/components/koenig-plus-menu.hbs new file mode 100644 index 0000000000..d5c1ee3125 --- /dev/null +++ b/ghost/admin/lib/koenig-editor/addon/templates/components/koenig-plus-menu.hbs @@ -0,0 +1,43 @@ +{{#if showButton}} + +{{/if}} + +{{#if showMenu}} +
+ +
+ Primary +
+
+
+
Text
+
+
+
+
Markdown
+
+
+
+
Image
+
+
+
+
Embed
+
+
+
+
Divider
+
+
+
+
Bullet list
+
+
+
+
Number list
+
+
+{{/if}} diff --git a/ghost/admin/lib/koenig-editor/app/components/koenig-plus-menu.js b/ghost/admin/lib/koenig-editor/app/components/koenig-plus-menu.js new file mode 100644 index 0000000000..9963a362f5 --- /dev/null +++ b/ghost/admin/lib/koenig-editor/app/components/koenig-plus-menu.js @@ -0,0 +1 @@ +export {default} from 'koenig-editor/components/koenig-plus-menu'; \ No newline at end of file diff --git a/ghost/admin/lib/koenig-editor/public/tools/bold.svg b/ghost/admin/lib/koenig-editor/public/tools/bold.svg new file mode 100644 index 0000000000..c06cd42576 --- /dev/null +++ b/ghost/admin/lib/koenig-editor/public/tools/bold.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ghost/admin/lib/koenig-editor/public/tools/close.svg b/ghost/admin/lib/koenig-editor/public/tools/close.svg new file mode 100644 index 0000000000..0bc2cb1671 --- /dev/null +++ b/ghost/admin/lib/koenig-editor/public/tools/close.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ghost/admin/lib/koenig-editor/public/tools/file-code-1.svg b/ghost/admin/lib/koenig-editor/public/tools/file-code-1.svg new file mode 100644 index 0000000000..52a43c8274 --- /dev/null +++ b/ghost/admin/lib/koenig-editor/public/tools/file-code-1.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ghost/admin/lib/koenig-editor/public/tools/file-code-edit.svg b/ghost/admin/lib/koenig-editor/public/tools/file-code-edit.svg new file mode 100644 index 0000000000..89f377de3d --- /dev/null +++ b/ghost/admin/lib/koenig-editor/public/tools/file-code-edit.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ghost/admin/lib/koenig-editor/public/tools/file-picture-add.svg b/ghost/admin/lib/koenig-editor/public/tools/file-picture-add.svg new file mode 100644 index 0000000000..1ec0abe399 --- /dev/null +++ b/ghost/admin/lib/koenig-editor/public/tools/file-picture-add.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ghost/admin/lib/koenig-editor/public/tools/font-size.svg b/ghost/admin/lib/koenig-editor/public/tools/font-size.svg new file mode 100644 index 0000000000..37376f4feb --- /dev/null +++ b/ghost/admin/lib/koenig-editor/public/tools/font-size.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ghost/admin/lib/koenig-editor/public/tools/html-five.svg b/ghost/admin/lib/koenig-editor/public/tools/html-five.svg new file mode 100644 index 0000000000..139b1259b0 --- /dev/null +++ b/ghost/admin/lib/koenig-editor/public/tools/html-five.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ghost/admin/lib/koenig-editor/public/tools/italic.svg b/ghost/admin/lib/koenig-editor/public/tools/italic.svg new file mode 100644 index 0000000000..e8b00cecfd --- /dev/null +++ b/ghost/admin/lib/koenig-editor/public/tools/italic.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ghost/admin/lib/koenig-editor/public/tools/link-broken.svg b/ghost/admin/lib/koenig-editor/public/tools/link-broken.svg new file mode 100644 index 0000000000..c261f843af --- /dev/null +++ b/ghost/admin/lib/koenig-editor/public/tools/link-broken.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ghost/admin/lib/koenig-editor/public/tools/link.svg b/ghost/admin/lib/koenig-editor/public/tools/link.svg new file mode 100644 index 0000000000..c284c2d953 --- /dev/null +++ b/ghost/admin/lib/koenig-editor/public/tools/link.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ghost/admin/lib/koenig-editor/public/tools/list-bullets.svg b/ghost/admin/lib/koenig-editor/public/tools/list-bullets.svg new file mode 100644 index 0000000000..d2f046345d --- /dev/null +++ b/ghost/admin/lib/koenig-editor/public/tools/list-bullets.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ghost/admin/lib/koenig-editor/public/tools/list-number.svg b/ghost/admin/lib/koenig-editor/public/tools/list-number.svg new file mode 100644 index 0000000000..52e41cfb7c --- /dev/null +++ b/ghost/admin/lib/koenig-editor/public/tools/list-number.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ghost/admin/lib/koenig-editor/public/tools/paragraph.svg b/ghost/admin/lib/koenig-editor/public/tools/paragraph.svg new file mode 100644 index 0000000000..7fb1438ac4 --- /dev/null +++ b/ghost/admin/lib/koenig-editor/public/tools/paragraph.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ghost/admin/lib/koenig-editor/public/tools/pullquote.svg b/ghost/admin/lib/koenig-editor/public/tools/pullquote.svg new file mode 100644 index 0000000000..925f47d763 --- /dev/null +++ b/ghost/admin/lib/koenig-editor/public/tools/pullquote.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ghost/admin/lib/koenig-editor/public/tools/quote.svg b/ghost/admin/lib/koenig-editor/public/tools/quote.svg new file mode 100644 index 0000000000..285a438744 --- /dev/null +++ b/ghost/admin/lib/koenig-editor/public/tools/quote.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ghost/admin/lib/koenig-editor/public/tools/rich-text.svg b/ghost/admin/lib/koenig-editor/public/tools/rich-text.svg new file mode 100644 index 0000000000..bc1ca65ff8 --- /dev/null +++ b/ghost/admin/lib/koenig-editor/public/tools/rich-text.svg @@ -0,0 +1 @@ + diff --git a/ghost/admin/lib/koenig-editor/public/tools/strikethrough.svg b/ghost/admin/lib/koenig-editor/public/tools/strikethrough.svg new file mode 100644 index 0000000000..900f2e06c2 --- /dev/null +++ b/ghost/admin/lib/koenig-editor/public/tools/strikethrough.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ghost/admin/lib/koenig-editor/public/tools/subscript.svg b/ghost/admin/lib/koenig-editor/public/tools/subscript.svg new file mode 100644 index 0000000000..5ce03b56ea --- /dev/null +++ b/ghost/admin/lib/koenig-editor/public/tools/subscript.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ghost/admin/lib/koenig-editor/public/tools/superscript.svg b/ghost/admin/lib/koenig-editor/public/tools/superscript.svg new file mode 100644 index 0000000000..c5bcd99200 --- /dev/null +++ b/ghost/admin/lib/koenig-editor/public/tools/superscript.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ghost/admin/lib/koenig-editor/public/tools/underline.svg b/ghost/admin/lib/koenig-editor/public/tools/underline.svg new file mode 100644 index 0000000000..ebc0b19136 --- /dev/null +++ b/ghost/admin/lib/koenig-editor/public/tools/underline.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ghost/admin/tests/integration/components/koenig-plus-menu-test.js b/ghost/admin/tests/integration/components/koenig-plus-menu-test.js new file mode 100644 index 0000000000..f5153affcf --- /dev/null +++ b/ghost/admin/tests/integration/components/koenig-plus-menu-test.js @@ -0,0 +1,24 @@ +import hbs from 'htmlbars-inline-precompile'; +import {describe, it} from 'mocha'; +import {expect} from 'chai'; +import {setupComponentTest} from 'ember-mocha'; + +describe('Integration: Component: koenig-plus-menu', function () { + setupComponentTest('koenig-plus-menu', { + integration: true + }); + + it('renders', function () { + // Set any properties with this.set('myProperty', 'value'); + // Handle any actions with this.on('myAction', function(val) { ... }); + // Template block usage: + // this.render(hbs` + // {{#koenig-plus-menu}} + // template content + // {{/koenig-plus-menu}} + // `); + + this.render(hbs`{{koenig-plus-menu}}`); + expect(this.$()).to.have.length(1); + }); +});