parent
6551420e1a
commit
567b696cec
7 changed files with 172 additions and 166 deletions
@ -0,0 +1,22 @@ |
|||||||
|
import actions from '../actions'; |
||||||
|
|
||||||
|
const setItems = (groups) => { |
||||||
|
return { |
||||||
|
type: actions.COMPLETION_SET_ITEMS, |
||||||
|
groups, |
||||||
|
}; |
||||||
|
}; |
||||||
|
|
||||||
|
const selectNext = () => { |
||||||
|
return { |
||||||
|
type: actions.COMPLETION_SELECT_NEXT |
||||||
|
}; |
||||||
|
}; |
||||||
|
|
||||||
|
const selectPrev = () => { |
||||||
|
return { |
||||||
|
type: actions.COMPLETION_SELECT_PREV |
||||||
|
}; |
||||||
|
}; |
||||||
|
|
||||||
|
export { setItems, selectNext, selectPrev }; |
@ -0,0 +1,55 @@ |
|||||||
|
export default class Completion { |
||||||
|
constructor(wrapper, store) { |
||||||
|
this.wrapper = wrapper; |
||||||
|
this.store = store; |
||||||
|
} |
||||||
|
|
||||||
|
update() { |
||||||
|
let state = this.store.getState(); |
||||||
|
|
||||||
|
this.wrapper.innerHTML = ''; |
||||||
|
|
||||||
|
for (let i = 0; i < state.groups.length; ++i) { |
||||||
|
let group = state.groups[i]; |
||||||
|
let title = this.createCompletionTitle(group.name); |
||||||
|
this.wrapper.append(title); |
||||||
|
|
||||||
|
for (let j = 0; j < group.items.length; ++j) { |
||||||
|
let item = group.items[j]; |
||||||
|
let li = this.createCompletionItem(item.icon, item.caption, item.url); |
||||||
|
this.wrapper.append(li); |
||||||
|
|
||||||
|
if (i === state.groupSelection && j === state.itemSelection) { |
||||||
|
li.classList.add('vimvixen-completion-selected'); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
createCompletionTitle(text) { |
||||||
|
let doc = this.wrapper.ownerDocument; |
||||||
|
let li = doc.createElement('li'); |
||||||
|
li.className = 'vimvixen-console-completion-title'; |
||||||
|
li.textContent = text; |
||||||
|
return li; |
||||||
|
} |
||||||
|
|
||||||
|
createCompletionItem(icon, caption, url) { |
||||||
|
let doc = this.wrapper.ownerDocument; |
||||||
|
|
||||||
|
let captionEle = doc.createElement('span'); |
||||||
|
captionEle.className = 'vimvixen-console-completion-item-caption'; |
||||||
|
captionEle.textContent = caption; |
||||||
|
|
||||||
|
let urlEle = doc.createElement('span'); |
||||||
|
urlEle.className = 'vimvixen-console-completion-item-url'; |
||||||
|
urlEle.textContent = url; |
||||||
|
|
||||||
|
let li = doc.createElement('li'); |
||||||
|
li.style.backgroundImage = 'url(' + icon + ')'; |
||||||
|
li.className = 'vimvixen-console-completion-item'; |
||||||
|
li.append(captionEle); |
||||||
|
li.append(urlEle); |
||||||
|
return li; |
||||||
|
} |
||||||
|
} |
@ -1,27 +0,0 @@ |
|||||||
export default class Completion { |
|
||||||
constructor(completions) { |
|
||||||
if (typeof completions.length !== 'number') { |
|
||||||
throw new TypeError('completions does not have a length in number'); |
|
||||||
} |
|
||||||
this.completions = completions; |
|
||||||
this.index = 0; |
|
||||||
} |
|
||||||
|
|
||||||
prev() { |
|
||||||
let length = this.completions.length; |
|
||||||
if (length === 0) { |
|
||||||
return null; |
|
||||||
} |
|
||||||
this.index = (this.index + length - 1) % length; |
|
||||||
return this.completions[this.index]; |
|
||||||
} |
|
||||||
|
|
||||||
next() { |
|
||||||
if (this.completions.length === 0) { |
|
||||||
return null; |
|
||||||
} |
|
||||||
let item = this.completions[this.index]; |
|
||||||
this.index = (this.index + 1) % this.completions.length; |
|
||||||
return item; |
|
||||||
} |
|
||||||
} |
|
@ -0,0 +1,61 @@ |
|||||||
|
import actions from '../actions'; |
||||||
|
|
||||||
|
const defaultState = { |
||||||
|
groupSelection: -1, |
||||||
|
itemSelection: -1, |
||||||
|
groups: [], |
||||||
|
}; |
||||||
|
|
||||||
|
const nextSelection = (state) => { |
||||||
|
if (state.groupSelection < 0) { |
||||||
|
return [0, 0]; |
||||||
|
} |
||||||
|
|
||||||
|
let group = state.groups[state.groupSelection]; |
||||||
|
if (state.groupSelection + 1 >= state.groups.length && |
||||||
|
state.itemSelection + 1 >= group.items.length) { |
||||||
|
return [-1, -1]; |
||||||
|
} |
||||||
|
if (state.itemSelection + 1 >= group.items.length) { |
||||||
|
return [state.groupSelection + 1, 0]; |
||||||
|
} |
||||||
|
return [state.groupSelection, state.itemSelection + 1]; |
||||||
|
}; |
||||||
|
|
||||||
|
const prevSelection = (state) => { |
||||||
|
if (state.groupSelection < 0) { |
||||||
|
return [0, 0]; |
||||||
|
} |
||||||
|
if (state.groupSelection === 0 && state.itemSelection === 0) { |
||||||
|
return [-1, -1]; |
||||||
|
} else if (state.itemSelection === 0) { |
||||||
|
return [ |
||||||
|
state.groupSelection - 1, |
||||||
|
state.groups[state.groupSelection - 1].items.length - 1 |
||||||
|
]; |
||||||
|
} |
||||||
|
return [state.groupSelection, state.itemSelection - 1]; |
||||||
|
}; |
||||||
|
|
||||||
|
export default function reducer(state = defaultState, action = {}) { |
||||||
|
switch (action.type) { |
||||||
|
case actions.COMPLETION_SET_ITEMS: |
||||||
|
return Object.assign({}, state, { |
||||||
|
groups: action.groups |
||||||
|
}); |
||||||
|
case actions.COMPLETION_SELECT_NEXT: { |
||||||
|
let next = nextSelection(state); |
||||||
|
return Object.assign({}, state, { |
||||||
|
groupSelection: next[0], |
||||||
|
itemSelection: next[1], |
||||||
|
}); |
||||||
|
} |
||||||
|
case actions.COMPLETION_SELECT_PREV: { |
||||||
|
let next = prevSelection(state); |
||||||
|
return Object.assign({}, state, { |
||||||
|
groupSelection: next[0], |
||||||
|
itemSelection: next[1], |
||||||
|
}); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -1,48 +0,0 @@ |
|||||||
import { expect } from "chai"; |
|
||||||
import Completion from '../../src/pages/completion'; |
|
||||||
|
|
||||||
describe('Completion class', () => { |
|
||||||
describe('#constructor', () => { |
|
||||||
it('creates new object by iterable items', () => { |
|
||||||
new Completion([1,2,3,4,5]); |
|
||||||
new Completion([]); |
|
||||||
new Completion('hello'); |
|
||||||
new Completion(''); |
|
||||||
}); |
|
||||||
|
|
||||||
it('creates new object by iterable items', () => { |
|
||||||
expect(() => new Completion({ key: 'value' })).to.throw(TypeError); |
|
||||||
expect(() => new Completion(12345)).to.throw(TypeError); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('#next', () => { |
|
||||||
it('complete next items', () => { |
|
||||||
let completion = new Completion([3, 4, 5]); |
|
||||||
expect(completion.next()).to.equal(3); |
|
||||||
expect(completion.next()).to.equal(4); |
|
||||||
expect(completion.next()).to.equal(5); |
|
||||||
expect(completion.next()).to.equal(3); |
|
||||||
}); |
|
||||||
|
|
||||||
it('returns null when empty completions', () => { |
|
||||||
let completion = new Completion([]); |
|
||||||
expect(completion.next()).to.be.null; |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('#prev', () => { |
|
||||||
it('complete prev items', () => { |
|
||||||
let completion = new Completion([3, 4, 5]); |
|
||||||
expect(completion.prev()).to.equal(5); |
|
||||||
expect(completion.prev()).to.equal(4); |
|
||||||
expect(completion.prev()).to.equal(3); |
|
||||||
expect(completion.prev()).to.equal(5); |
|
||||||
}); |
|
||||||
|
|
||||||
it('returns null when empty completions', () => { |
|
||||||
let completion = new Completion([]); |
|
||||||
expect(completion.prev()).to.be.null; |
|
||||||
}); |
|
||||||
}); |
|
||||||
}); |
|
Reference in new issue