Merge pull request #20 from chanakyabhardwajj/main

For multiword scenarios, ignore activation keys when a match is in progress.
This commit is contained in:
Keith Cirkel 2020-11-30 16:05:27 +01:00 коммит произвёл GitHub
Родитель 982cbf811a 400783d57b
Коммит 33a0e74d79
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
6 изменённых файлов: 94 добавлений и 10 удалений

Просмотреть файл

@ -44,6 +44,7 @@ With a script tag:
- `key`: The matched key; for example: `:`.
- `text`: The matched text; for example: `cat`, for `:cat`.
- If the `key` is specified in the `multiword` attribute then the matched text can contain multiple words; for example `cat and dog` for `:cat and dog`.
- `provide`: A function to be called when you have the menu results. Takes a `Promise` with `{matched: boolean, fragment: HTMLElement}` where `matched` tells the element whether a suggestion is available, and `fragment` is the menu content to be displayed on the page.
```js

Просмотреть файл

@ -19,7 +19,7 @@
</text-expander>
<h2>Multiword text-expander element</h2>
<text-expander keys="#" multiword>
<text-expander keys="#" multiword="#">
<textarea autofocus rows="10" cols="40"></textarea>
</text-expander>
@ -34,7 +34,9 @@
for (const issue of [
'#1 Implement a text-expander element',
'#2 Implement multi word option',
'#3 Fix tpoy'
'#3 Fix tpoy',
'#4 Implement #12',
'#5 Implement #123 and #456',
]) {
if (issue.toLowerCase().includes(text.toLowerCase())) {
const item = document.createElement('li')

Просмотреть файл

@ -6,6 +6,7 @@ type Query = {
type QueryOptions = {
lookBackIndex: number
multiWord: boolean
lastMatchPosition: number | null
}
const boundary = /\s|\(|\[/
@ -15,19 +16,30 @@ export default function query(
text: string,
key: string,
cursor: number,
{multiWord, lookBackIndex}: QueryOptions = {multiWord: false, lookBackIndex: 0}
{multiWord, lookBackIndex, lastMatchPosition}: QueryOptions = {
multiWord: false,
lookBackIndex: 0,
lastMatchPosition: null
}
): Query | void {
// Activation key not found in front of the cursor.
const keyIndex = text.lastIndexOf(key, cursor - 1)
let keyIndex = text.lastIndexOf(key, cursor - 1)
if (keyIndex === -1) return
// Stop matching at the lookBackIndex
if (keyIndex < lookBackIndex) return
if (multiWord) {
// Space immediately after activation key
if (lastMatchPosition != null) {
// If the current activation key is the same as last match
// i.e. consecutive activation keys, then return.
if (lastMatchPosition === keyIndex) return
keyIndex = lastMatchPosition - 1
}
// Space immediately after activation key followed by the cursor
const charAfterKey = text[keyIndex + 1]
if (charAfterKey === ' ') return
if (charAfterKey === ' ' && cursor >= keyIndex + 2) return
// New line the cursor and previous activation key.
const newLineIndex = text.lastIndexOf('\n', cursor - 1)

Просмотреть файл

@ -135,6 +135,7 @@ class TextExpander {
this.input.selectionStart = cursor
this.input.selectionEnd = cursor
this.lookBackIndex = cursor
this.match = null
}
private onBlur() {
@ -179,10 +180,14 @@ class TextExpander {
const cursor = this.input.selectionEnd || 0
const text = this.input.value
if (cursor <= this.lookBackIndex) {
this.lookBackIndex = 0
this.lookBackIndex = cursor - 1
}
for (const {key, multiWord} of this.expander.keys) {
const found = query(text, key, cursor, {multiWord, lookBackIndex: this.lookBackIndex})
const found = query(text, key, cursor, {
multiWord,
lookBackIndex: this.lookBackIndex,
lastMatchPosition: this.match ? this.match.position : null
})
if (found) {
return {text: found.text, key, position: found.position}
}
@ -208,6 +213,7 @@ class TextExpander {
private onKeydown(event: KeyboardEvent) {
if (event.key === 'Escape') {
this.match = null
if (this.deactivate()) {
this.lookBackIndex = this.input.selectionEnd || this.lookBackIndex
event.stopImmediatePropagation()

Просмотреть файл

@ -124,6 +124,39 @@ describe('text-expander multi word parsing', function() {
})
})
describe('text-expander multi word parsing with multiple activation keys', function() {
it('does not match consecutive activation keys', function() {
let found = query('::', ':', 2, {multiWord: true})
assert(found == null)
found = query('::', ':', 3, {multiWord: true})
assert(found == null)
found = query('hi :: there', ':', 5, {multiWord: true})
assert(found == null)
found = query('hi ::: there', ':', 6, {multiWord: true})
assert(found == null)
found = query('hi ::', ':', 5, {multiWord: true})
assert(found == null)
found = query('hi :::', ':', 6, {multiWord: true})
assert(found == null)
})
it('uses lastMatchPosition to match', function() {
let found = query('hi :cat :bye', ':', 12, {multiWord: true, lastMatchPosition: 4})
assert.deepEqual(found, {text: 'cat :bye', position: 4})
found = query('hi :cat :bye :::', ':', 16, {multiWord: true, lastMatchPosition: 4})
assert.deepEqual(found, {text: 'cat :bye :::', position: 4})
found = query(':hi :cat :bye :::', ':', 17, {multiWord: true, lastMatchPosition: 1})
assert.deepEqual(found, {text: 'hi :cat :bye :::', position: 1})
})
})
describe('text-expander limits the lookBack after commit', function() {
it('does not match if lookBackIndex is bigger than activation key index', function() {
const found = query('hi :cat bye', ':', 11, {multiWord: true, lookBackIndex: 7})

Просмотреть файл

@ -128,6 +128,36 @@ describe('text-expander element', function() {
assert.equal('#', key)
assert.equal('some text @match word', text)
})
it('dispatches change event for the first activation key even if it is typed again', async function() {
const expander = document.querySelector('text-expander')
const input = expander.querySelector('textarea')
let result = once(expander, 'text-expander-change')
triggerInput(input, '#step 1')
let event = await result
let {key, text} = event.detail
assert.equal('#', key)
assert.equal('step 1', text)
await waitForAnimationFrame()
result = once(expander, 'text-expander-change')
triggerInput(input, ' #step 2', true) //<-- At this point the text inside the input field is "#step 1 #step 2"
event = await result
;({key, text} = event.detail)
assert.equal('#', key)
assert.equal('step 1 #step 2', text)
await waitForAnimationFrame()
result = once(expander, 'text-expander-change')
triggerInput(input, ' #step 3', true) //<-- At this point the text inside the input field is "#step 1 #step 2 #step 3"
event = await result
;({key, text} = event.detail)
assert.equal('#', key)
assert.equal('step 1 #step 2 #step 3', text)
})
})
})
@ -137,8 +167,8 @@ function once(element, eventName) {
})
}
function triggerInput(input, value) {
input.value = value
function triggerInput(input, value, onlyAppend = false) {
input.value = onlyAppend ? input.value + value : value
return input.dispatchEvent(new InputEvent('input'))
}