From 0f657987e7bd631908acc176a96e44981a4e6b2e Mon Sep 17 00:00:00 2001 From: Luke Melia Date: Tue, 20 Aug 2019 23:45:37 +0800 Subject: [PATCH] Add Editor#activeSectionAttributes to support toolbar state for new section attributes in mobiledoc 0.3.2 --- README.md | 2 +- src/js/editor/edit-state.js | 36 +++++++++++++++++++++++++++++--- src/js/editor/editor.js | 4 ++++ tests/unit/editor/editor-test.js | 22 +++++++++++++++++++ 4 files changed, 60 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 6a3b53052..e42cd001f 100644 --- a/README.md +++ b/README.md @@ -222,7 +222,7 @@ The available lifecycle hooks are: * `editor.cursorDidChange()` - When the cursor (or selection) changes as a result of arrow-key movement or clicking in the document. * `editor.onTextInput()` - When the user adds text to the document (see [example](https://github.com/bustlelabs/mobiledoc-kit#responding-to-text-input)) -* `editor.inputModeDidChange()` - The active section(s) or markup(s) at the current cursor position or selection have changed. This hook can be used with `Editor#activeMarkups` and `Editor#activeSections` to implement a custom toolbar. +* `editor.inputModeDidChange()` - The active section(s) or markup(s) at the current cursor position or selection have changed. This hook can be used with `Editor#activeMarkups`, `Editor#activeSections`, and `Editor#activeSectionAttributes` to implement a custom toolbar. * `editor.beforeToggleMarkup(({markup, range, willAdd} => {...})` - Register a callback that will be called before `editor#toggleMarkup` is applied. If any callback returns literal `false`, the toggling of markup will be canceled. diff --git a/src/js/editor/edit-state.js b/src/js/editor/edit-state.js index fd3b44b82..be4d0f9c6 100644 --- a/src/js/editor/edit-state.js +++ b/src/js/editor/edit-state.js @@ -1,4 +1,8 @@ -import { contains, isArrayEqual } from 'mobiledoc-kit/utils/array-utils'; +import { + contains, + isArrayEqual, + objectToSortedKVArray +} from 'mobiledoc-kit/utils/array-utils'; import Range from 'mobiledoc-kit/utils/cursor/range'; /** @@ -14,7 +18,8 @@ class EditState { range: Range.blankRange(), activeMarkups: [], activeSections: [], - activeSectionTagNames: [] + activeSectionTagNames: [], + activeSectionAttributes: {} }; this.prevState = this.state = defaultState; @@ -46,7 +51,8 @@ class EditState { inputModeDidChange() { let { state, prevState } = this; return (!isArrayEqual(state.activeMarkups, prevState.activeMarkups) || - !isArrayEqual(state.activeSectionTagNames, prevState.activeSectionTagNames)); + !isArrayEqual(state.activeSectionTagNames, prevState.activeSectionTagNames) || + !isArrayEqual(objectToSortedKVArray(state.activeSectionAttributes), objectToSortedKVArray(prevState.activeSectionAttributes))); } /** @@ -63,6 +69,14 @@ class EditState { return this.state.activeSections; } + + /** + * @return {Object} + */ + get activeSectionAttributes() { + return this.state.activeSectionAttributes; + } + /** * @return {Markup[]} */ @@ -97,6 +111,7 @@ class EditState { state.activeSectionTagNames = state.activeSections.map(s => { return s.isNested ? s.parent.tagName : s.tagName; }); + state.activeSectionAttributes = this._readSectionAttributes(state.activeSections); return state; } @@ -115,6 +130,21 @@ class EditState { return post.markupsInRange(range); } + _readSectionAttributes(sections) { + return sections.reduce((sectionAttributes, s) => { + let attributes = s.isNested ? s.parent.attributes : s.attributes; + Object.keys(attributes || {}).forEach(attrName => { + let camelizedAttrName = attrName.replace(/^data-md-/, ''); + let attrValue = attributes[attrName]; + sectionAttributes[camelizedAttrName] = sectionAttributes[camelizedAttrName] || []; + if (!contains(sectionAttributes[camelizedAttrName], attrValue)) { + sectionAttributes[camelizedAttrName].push(attrValue); + } + }); + return sectionAttributes; + }, {}); + } + _removeActiveMarkup(markup) { let index = this.state.activeMarkups.indexOf(markup); this.state.activeMarkups.splice(index, 1); diff --git a/src/js/editor/editor.js b/src/js/editor/editor.js index 2bad7054c..755873260 100644 --- a/src/js/editor/editor.js +++ b/src/js/editor/editor.js @@ -491,6 +491,10 @@ class Editor { return activeSections[activeSections.length - 1]; } + get activeSectionAttributes() { + return this._editState.activeSectionAttributes; + } + detectMarkupInRange(range, markupTagName) { let markups = this.post.markupsInRange(range); return detect(markups, markup => { diff --git a/tests/unit/editor/editor-test.js b/tests/unit/editor/editor-test.js index dc0a93559..545f5de61 100644 --- a/tests/unit/editor/editor-test.js +++ b/tests/unit/editor/editor-test.js @@ -310,6 +310,28 @@ test('activeSections is empty when the editor has no cursor', (assert) => { assert.equal(editor.activeSections.length, 0, 'empty activeSections'); }); +test('activeSectionAttributes of a rendered blank mobiledoc is an empty array', (assert) => { + editor = Helpers.mobiledoc.renderInto(editorElement, ({post}) => { + return post(); + }); + + assert.ok(editor.hasRendered, 'editor has rendered'); + assert.deepEqual(editor.activeSectionAttributes, {}, 'empty activeSectionAttributes'); +}); + +test('activeSectionAttributes is updated based on the selection', (assert) => { + editor = Helpers.mobiledoc.renderInto(editorElement, ({post, markupSection, marker}) => { + return post([markupSection('p', [marker('abc')], false, { 'data-md-text-align': 'center' })]); + }, {autofocus: false}); + + assert.ok(!editor.hasCursor(), 'precond - no cursor'); + assert.deepEqual(editor.activeSectionAttributes, {}, 'empty activeSectionAttributes'); + + let head = editor.post.sections.head; + editor.selectRange(Range.create(head, 'abc'.length)); + assert.deepEqual(editor.activeSectionAttributes['text-align'], ['center'], 'active section attributes captured'); +}); + test('editor.cursor.hasCursor() is false before rendering', (assert) => { let mobiledoc = Helpers.mobiledoc.build(({post}) => post()); editor = new Editor({mobiledoc});