Remove multiple selection and add keymaps

This commit is contained in:
Peter Stuifzand 2020-11-01 18:11:16 +01:00
parent b4a9077c9c
commit 680e600fbe
3 changed files with 203 additions and 111 deletions

View File

@ -4,8 +4,8 @@ import he from 'he'
import dragula from 'dragula'
import textareaAutosizeInit from "./textarea.autosize"
import createCursor from './cursor'
import createSelection from './selection'
import Store from './store'
import Keymap from './keymap'
import getCaretCoordinates from "../editor/src/caret-position";
textareaAutosizeInit($)
@ -28,10 +28,21 @@ function textareaCursorInfo(target, dir) {
function editor(root, inputData, options) {
let cursor = createCursor()
let selection = createSelection()
let store = createStore(inputData);
let keymap = new Keymap()
let drake = null;
keymap.mapKey('ArrowUp', 'backwardLine')
keymap.mapKey('ArrowDown', 'forwardLine')
keymap.mapKey('S-Delete', 'deleteBlock')
keymap.mapKey('C-Enter', 'insertLineAbove')
keymap.mapKey('Enter', 'insertLineBelow')
keymap.mapKey('Tab', 'indentBlock')
keymap.mapKey('S-Tab', 'indentBlock')
keymap.mapKey('Escape', 'leaveEditor')
keymap.mapKey('C-S-ArrowUp', 'blockMoveBackward')
keymap.mapKey('C-S-ArrowDown', 'blockMoveForward')
function createStore(inputData) {
let data = [
{indented: 0, text: '', fold: 'open'},
@ -131,7 +142,19 @@ function editor(root, inputData, options) {
zoomin,
treeForId,
replaceChildren,
render: renderUpdate
render: renderUpdate,
deleteBlock,
forwardLine,
backwardLine,
insertLineAbove,
insertLineBelow,
indentBlock,
leaveEditor,
blockMoveBackward,
blockMoveForward
};
root.classList.add('root')
@ -259,11 +282,7 @@ function editor(root, inputData, options) {
let $li = $(li)
.attr('data-id', value.id)
.toggleClass('selected', cursor.atPosition(index))
.toggleClass('selection-first', selection.isSelectedFirst(index))
.toggleClass('selection-last', selection.isSelectedLast(index))
.toggleClass('selection', selection.isSelected(index))
.toggleClass('hidden', value.indented >= hideLevel)
.toggleClass('border', value.indented >= 1)
.css('margin-left', (value.indented * 32) + 'px')
value.hidden = value.indented >= hideLevel
@ -290,11 +309,7 @@ function editor(root, inputData, options) {
let value = rootData.value(storeId)
let $li = newItem(value)
.css('margin-left', (value.indented * 32) + 'px')
.toggleClass('selection-first', selection.isSelectedFirst(index))
.toggleClass('selection-last', selection.isSelectedLast(index))
.toggleClass('selection', selection.isSelected(index))
.toggleClass('selected', cursor.atPosition(index + $enter.length))
.toggleClass('border', value.indented >= 1)
.toggleClass('hidden', value.indented >= hideLevel);
value.hidden = value.indented >= hideLevel
@ -359,9 +374,7 @@ function editor(root, inputData, options) {
let position = store.index(id);
cursor.set(position)
selection.selectOne(position, store)
_.defer(renderUpdate)
trigger('change')
})
return drake;
@ -369,6 +382,7 @@ function editor(root, inputData, options) {
function stopEditing(rootElement, store, element) {
if (!editing) return
if (element === null) {
editing = false
currentEditor = null
@ -453,75 +467,61 @@ function editor(root, inputData, options) {
}))
store.insertAfter(currentID, ...newItems)
disableDragging(drake)
render(root, store);
drake = enableDragging(root)
trigger('change')
// disableDragging(drake)
// render(root, store);
// drake = enableDragging(root)
return false
});
$(root).on('keydown', '.input-line', function (event) {
if (event.key === 'Escape') {
stopEditing(root, store, $(this))
selection.selectOne(cursor.get(), store)
return false
}
return true
});
$(root).on('keydown', function (event) {
function moveCursor(event, dir) {
let target = event.target;
let dir = 0;
if (event.key === 'ArrowUp') dir = -1
if (event.key === 'ArrowDown') dir = 1
let cursorInfo = null
if (target.nodeName === 'TEXTAREA') cursorInfo = textareaCursorInfo(target, dir);
if (cursorInfo !== null && !cursorInfo.leaving && dir !== 0 && !event.ctrlKey) {
if (cursorInfo !== null && !cursorInfo.leaving && dir !== 0 && !event.ctrlKey) { // FIXME: don't check modifier here
return true
}
let next = true
let prevSelected = cursor.save()
if (dir < 0) {
cursor.moveUp(store)
} else if (dir > 0) {
cursor.moveDown(store)
}
if (event.key === 'ArrowUp') {
cursor.moveUp(store);
if (event.shiftKey) {
selection.include(cursor.get(), store)
} else {
selection.selectNothing(cursor.get())
}
next = false
} else if (event.key === 'ArrowDown') {
cursor.moveDown(store);
if (event.shiftKey) {
selection.include(cursor.get(), store)
} else {
selection.selectNothing(cursor.get())
}
next = false
} else if (event.shiftKey && event.key === 'Delete') {
if (editing) {
stopEditing(root, store, currentEditor);
if (selection.hasSelection()) {
selection.remove(store)
// FIXME: adjust cursor
trigger('change')
startEditing(root, store, cursor);
} else {
cursor.remove(store)
trigger('change')
}
next = false
trigger('change');
} else if (event.key === 'Enter') {
cursor.resetLastMove()
return false
}
function insertLine(event, dir) {
stopEditing(root, store, currentEditor);
next = false
if (event.ctrlKey) {
if (dir < 0) {
let id = store.currentID(cursor.get())
let current = store.value(id)
let indent = current.indented
let item = newListItem(indent)
cursor.insertAbove(store, item)
} else {
} else if (dir > 0) {
let insertion = cursor.save()
let currentValue = store.value(store.currentID(cursor.get()));
let current = currentValue ? currentValue.indented : 0
@ -534,39 +534,83 @@ function editor(root, inputData, options) {
cursor.insertBelow(store, item)
}
selection.selectOne(cursor.get(), store)
trigger('change')
} else if (event.key === 'Tab') {
startEditing(root, store, cursor);
return false
}
function insertLineAbove(event) {
return insertLine(event, -1)
}
function insertLineBelow(event) {
return insertLine(event, 1)
}
function backwardLine(event) {
return moveCursor(event, -1)
}
function forwardLine(event) {
return moveCursor(event, 1)
}
function deleteBlock(event) {
stopEditing(root, store, currentEditor);
cursor.remove(store)
trigger('change')
return false
}
function indentBlock(event) {
store.indent(cursor.get(), store.lastHigherIndented(cursor.get()) - cursor.get(), event.shiftKey ? -1 : 1)
next = false
} else {
return true
}
disableDragging(drake)
render(root, store);
drake = enableDragging(root)
if (cursor.hasMoved(prevSelected)) {
if (!selection.hasSelection() && editing && (event.key === 'ArrowUp' || event.key === 'ArrowDown')) {
stopEditing(root, store, currentEditor);
startEditing(root, store, cursor);
trigger('change')
return false
} else if (selection.hasSelection()) {
stopEditing(root, store, currentEditor);
}
cursor.resetLastMove()
}
if (event.key === 'Enter') {
startEditing(root, store, cursor);
return false
} else if (event.key === 'Escape') {
function leaveEditor(event) {
stopEditing(root, store, currentEditor);
return false
}
return next
function blockMoveBackward(event) {
stopEditing(root, store, currentEditor);
let item = cursor.getCurrent(store);
let before = store.firstSameIndented(cursor.get(), item.indented)
let beforeID = store.currentID(before);
let beforeItem = store.value(beforeID);
store.moveBefore(item.id, beforeID)
// NOTE: should moveBefore adjust the indents of the children
if (item.indented > beforeItem.indented) {
item.indented = beforeItem.indented
}
cursor.set(store.index(item.id))
trigger('change')
return false
}
function blockMoveForward(event) {
stopEditing(root, store, currentEditor);
let item = cursor.getCurrent(store);
let after = store.lastHigherIndented(cursor.get(), item.indented)
after = store.lastHigherIndented(after, item.indented)
store.moveBefore(item.id, store.currentID(after))
cursor.set(store.index(item.id))
trigger('change')
return false
}
$(root).on('keydown', function (event) {
return keymap.handleKey(EDITOR, event)
})
$(root).on('click', '.marker', function () {
stopEditing(root, store, $(this).next('textarea'));
return false;
@ -587,11 +631,8 @@ function editor(root, inputData, options) {
stopEditing(root, store, currentEditor)
cursor.set(currentIndex)
selection.selectOne(cursor.get(), store)
disableDragging(drake)
render(root, store);
drake = enableDragging(root)
trigger('change')
const $input = startEditing(root, store, cursor)
$input.trigger('input')
@ -612,9 +653,7 @@ function editor(root, inputData, options) {
return item
})
disableDragging(drake)
render(root, store);
drake = enableDragging(root)
trigger('change')
});
function update(id, callback) {
@ -630,6 +669,8 @@ function editor(root, inputData, options) {
}
}
EDITOR.on('change', renderUpdate)
return EDITOR;
}

51
list-editor/keymap.js Normal file
View File

@ -0,0 +1,51 @@
function canonicalKey(event) {
let name = [];
if (event.altKey) name.push('M')
if (event.ctrlKey) name.push('C')
if (event.metaKey) name.push('H')
if (event.shiftKey) name.push('S')
name.push(event.key)
return name.join('-')
}
function handleKeyDown(editor, event) {
if (event.repeating) return true;
const key = canonicalKey(event)
console.log('Key down ' + key)
if (this.keys.hasOwnProperty(key)) {
const action = this.keys[key]
if (editor.hasOwnProperty(action)) {
console.log('Calling action ' + action)
return editor[action](event)
} else {
console.warn('Unknown action on editor: ' + action)
}
}
return true
}
function mapKey(key, action) {
if (this.keys.hasOwnProperty(key)) {
console.warn(`Re-defining ${key} to call ${action}`)
} else {
console.log(`Defining ${key} to call ${action}`)
}
this.keys[key] = action
}
function Keymap() {
return {
keys: {},
mapKey,
handleKey: handleKeyDown
}
}
export default Keymap

View File

@ -288,7 +288,7 @@ function Store(inputData) {
*/
function moveBefore(from, to) {
let fromIndex = index(from)
let toIndex = to ? index(to) : idList.length
let toIndex = to && to !== 'at-end' ? index(to) : idList.length
let n = lastHigherIndented(fromIndex) - fromIndex
if (toIndex >= fromIndex && toIndex < fromIndex + n) return index(from)
if (fromIndex < toIndex) {