Skip to content
This repository has been archived by the owner on Jun 17, 2020. It is now read-only.

Commit

Permalink
Merge pull request #39 from yuku-t/editor-interface
Browse files Browse the repository at this point in the history
Fix editor interface
  • Loading branch information
yuku committed Mar 14, 2016
2 parents 3b0f8e6 + da7c141 commit 72bcea0
Show file tree
Hide file tree
Showing 9 changed files with 162 additions and 105 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ This change log adheres to [keepachangelog.com](http://keepachangelog.com).
## [Unreleased]
### Added
- Enable to preload third party editor classes via `Textcomplete.editors`.
- Enable to select dropdown by tab key.

### Changed
- Use methods instead of getter properties to define `Editor` class.
- Emit a custom event on Editor#change and Editor#move event.

### Fixed
- Fix dropdown position when window is scrolled.
Expand Down
39 changes: 19 additions & 20 deletions src/dropdown.js
Original file line number Diff line number Diff line change
Expand Up @@ -191,19 +191,19 @@ class Dropdown extends EventEmitter {
}

/**
* @param {function} callback
* @param {Editor#move} e
* @returns {this}
*/
up(callback) {
return this.moveActiveItem('prev', callback);
up(e) {
return this.shown ? this.moveActiveItem('prev', e) : this;
}

/**
* @param {function} callback
* @param {Editor#move} e
* @returns {this}
*/
down(callback) {
return this.moveActiveItem('next', callback);
down(e) {
return this.shown ? this.moveActiveItem('next', e) : this;
}

/**
Expand Down Expand Up @@ -307,22 +307,21 @@ class Dropdown extends EventEmitter {
/**
* @private
* @param {string} name - "next" or "prev".
* @param {function} callback
* @param {Editor#move} e
* @returns {this}
*/
moveActiveItem(name, callback) {
if (this.shown) {
let activeItem = this.getActiveItem();
let nextActiveItem;
if (activeItem) {
activeItem.deactivate();
nextActiveItem = activeItem[name];
} else {
nextActiveItem = name === 'next' ? this.items[0] : this.items[this.items.length - 1];
}
if (nextActiveItem) {
callback(nextActiveItem.activate());
}
moveActiveItem(name, e) {
let activeItem = this.getActiveItem();
let nextActiveItem;
if (activeItem) {
activeItem.deactivate();
nextActiveItem = activeItem[name];
} else {
nextActiveItem = name === 'next' ? this.items[0] : this.items[this.items.length - 1];
}
if (nextActiveItem) {
nextActiveItem.activate();
e.preventDefault();
}
return this;
}
Expand Down
85 changes: 79 additions & 6 deletions src/editor.js
Original file line number Diff line number Diff line change
@@ -1,25 +1,33 @@
import {createCustomEvent} from './utils';

import {EventEmitter} from 'events';

export const ENTER = 0;
export const UP = 1;
export const DOWN = 2;
export const OTHER = 3;

/**
* @event Editor#move
* @type {object}
* @prop {number} code
* @prop {function} callback
* @type {CustomEvent}
* @prop {function} preventDefault
* @prop {object} detail
* @prop {number} detail.code
*/

/**
* @event Editor#change
* @type {object}
* @prop {string} beforeCursor
* @type {CustomEvent}
* @prop {object} detail
* @prop {string} detail.beforeCursor
*/

/**
* Abstract class representing a editor target.
*
* Editor classes must implement `#applySearchResult`, `#getCursorOffset`,
* `#getBeforeCursor` and `#getAfterCursor` methods.
*
* @abstract
* @extends EventEmitter
*/
Expand All @@ -46,9 +54,74 @@ class Editor extends EventEmitter {
*
* @type {Dropdown~Offset}
*/
get cursorOffset() {
getCursorOffset() {
throw new Error('Not implemented.');
}

/**
* Editor string value from head to cursor.
*
* @private
*/
getBeforeCursor() {
throw new Error('Not implemented.');
}

/**
* Editor string value from cursor to tail.
*
* @private
*/
getAfterCursor() {
throw new Error('Not implemented.');
}

/**
* @private
* @fires Editor#move
* @param {ENTER|UP|DOWN|OTHER} code
* @returns {Editor#move}
*/
emitMoveEvent(code) {
var moveEvent = createCustomEvent('move', {
cancelable: true,
detail: {
code: code,
},
});
this.emit('move', moveEvent);
return moveEvent;
}

/**
* @private
* @fires Editor#change
* @returns {Editor#change}
*/
emitChangeEvent() {
var changeEvent = createCustomEvent('change', {
detail: {
beforeCursor: this.getBeforeCursor(),
},
});
this.emit('change', changeEvent);
return changeEvent;
}

/**
* @private
* @param {KeyboardEvent} e
* @returns {ENTER|UP|DOWN|OTHER}
*/
getCode(e) {
return e.keyCode === 9 ? ENTER // tab
: e.keyCode === 13 ? ENTER // enter
: e.keyCode === 38 ? UP // up
: e.keyCode === 40 ? DOWN // down
: e.keyCode === 78 && e.ctrlKey ? DOWN // ctrl-n
: e.keyCode === 80 && e.ctrlKey ? UP // ctrl-p
: OTHER;
}
}

export default Editor;
51 changes: 13 additions & 38 deletions src/textarea.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import Editor, {ENTER, UP, DOWN} from './editor';
import Editor, {ENTER, OTHER} from './editor';
import {calculateElementOffset} from './utils';

import bindAll from 'lodash.bindall';
Expand Down Expand Up @@ -39,15 +39,15 @@ class Textarea extends Editor {
* @param {SearchResult} searchResult
*/
applySearchResult(searchResult) {
var replace = searchResult.replace(this.beforeCursor, this.afterCursor);
var replace = searchResult.replace(this.getBeforeCursor(), this.getAfterCursor());
if (Array.isArray(replace)) {
this.el.value = replace[0] + replace[1];
this.el.selectionStart = this.el.selectionEnd = replace[0].length;
}
this.el.focus(); // Clicking a dropdown item removes focus from the element.
}

get cursorOffset() {
getCursorOffset() {
var elOffset = calculateElementOffset(this.el);
var elScroll = this.getElScroll();
var cursorPosition = this.getCursorPosition();
Expand All @@ -60,21 +60,13 @@ class Textarea extends Editor {
}
}

/**
* The string from head to current input cursor position.
*
* @private
* @returns {string}
*/
get beforeCursor() {
/** @override */
getBeforeCursor() {
return this.el.value.substring(0, this.el.selectionEnd);
}

/**
* @private
* @returns {string}
*/
get afterCursor() {
/** @override */
getAfterCursor() {
return this.el.value.substring(this.el.selectionEnd);
}

Expand Down Expand Up @@ -114,13 +106,10 @@ class Textarea extends Editor {
*/
onKeydown(e) {
var code = this.getCode(e);
if (code !== null) {
this.emit('move', {
code: code,
callback: function () {
e.preventDefault();
},
});
if (code === OTHER) { return; }
var moveEvent = this.emitMoveEvent(code);
if (moveEvent.defaultPrevented) {
e.preventDefault();
}
}

Expand All @@ -131,7 +120,7 @@ class Textarea extends Editor {
*/
onKeyup(e) {
if (!this.isMoveKeyEvent(e)) {
this.emit('change', { beforeCursor: this.beforeCursor });
this.emitChangeEvent();
}
}

Expand All @@ -142,21 +131,7 @@ class Textarea extends Editor {
*/
isMoveKeyEvent(e) {
var code = this.getCode(e);
return code !== ENTER && code !== null;
}

/**
* @private
* @param {KeyboardEvent} e
* @returns {ENTER|UP|DOWN|null}
*/
getCode(e) {
return e.keyCode === 13 ? ENTER
: e.keyCode === 38 ? UP
: e.keyCode === 40 ? DOWN
: e.keyCode === 78 && e.ctrlKey ? DOWN
: e.keyCode === 80 && e.ctrlKey ? UP
: null;
return code !== ENTER && code !== OTHER;
}

/**
Expand Down
21 changes: 10 additions & 11 deletions src/textcomplete.js
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ class Textcomplete extends EventEmitter {
*/
handleHit({searchResults}) {
if (searchResults.length) {
this.dropdown.render(searchResults, this.editor.cursorOffset);
this.dropdown.render(searchResults, this.editor.getCursorOffset());
} else {
this.dropdown.deactivate();
}
Expand All @@ -137,38 +137,37 @@ class Textcomplete extends EventEmitter {

/**
* @private
* @param {ENTER|UP|DOWN} code
* @param {funcion} callback
* @param {Editor#move} e
* @listens Editor#move
*/
handleMove({code, callback}) {
switch (code) {
handleMove(e) {
switch (e.detail.code) {
case ENTER: {
let activeItem = this.dropdown.getActiveItem();
if (activeItem) {
this.dropdown.select(activeItem);
callback(activeItem);
e.preventDefault();
}
break;
}
case UP: {
this.dropdown.up(callback);
this.dropdown.up(e);
break;
}
case DOWN: {
this.dropdown.down(callback);
this.dropdown.down(e);
break;
}
}
}

/**
* @private
* @param {string} beforeCursor
* @param {Editor#change} e
* @listens Editor#change
*/
handleChange({beforeCursor}) {
this.trigger(beforeCursor);
handleChange(e) {
this.trigger(e.detail.beforeCursor);
}

/**
Expand Down
Loading

0 comments on commit 72bcea0

Please sign in to comment.