Remove multiple selection and add keymaps
This commit is contained in:
parent
b4a9077c9c
commit
680e600fbe
|
@ -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,120 +467,150 @@ 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
|
||||
} else {
|
||||
cursor.remove(store)
|
||||
}
|
||||
next = false
|
||||
|
||||
trigger('change');
|
||||
} else if (event.key === 'Enter') {
|
||||
stopEditing(root, store, currentEditor);
|
||||
next = false
|
||||
|
||||
if (event.ctrlKey) {
|
||||
let id = store.currentID(cursor.get())
|
||||
let current = store.value(id)
|
||||
let indent = current.indented
|
||||
let item = newListItem(indent)
|
||||
cursor.insertAbove(store, item)
|
||||
} else {
|
||||
let insertion = cursor.save()
|
||||
let currentValue = store.value(store.currentID(cursor.get()));
|
||||
let current = currentValue ? currentValue.indented : 0
|
||||
let next = cursor.get() + 1 < store.length() ? store.value(store.currentID(cursor.get() + 1)).indented : current
|
||||
let indent = next > current ? next : current
|
||||
let item = newListItem(indent)
|
||||
if (currentValue.text.match(/^#\[\[TODO\]\]/)) {
|
||||
item.text = '#[[TODO]] ';
|
||||
}
|
||||
cursor.insertBelow(store, item)
|
||||
}
|
||||
|
||||
selection.selectOne(cursor.get(), store)
|
||||
trigger('change')
|
||||
} else if (event.key === 'Tab') {
|
||||
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);
|
||||
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') {
|
||||
stopEditing(root, store, currentEditor);
|
||||
return false
|
||||
} else {
|
||||
trigger('change')
|
||||
}
|
||||
return next
|
||||
|
||||
cursor.resetLastMove()
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
function insertLine(event, dir) {
|
||||
stopEditing(root, store, currentEditor);
|
||||
|
||||
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 if (dir > 0) {
|
||||
let insertion = cursor.save()
|
||||
let currentValue = store.value(store.currentID(cursor.get()));
|
||||
let current = currentValue ? currentValue.indented : 0
|
||||
let next = cursor.get() + 1 < store.length() ? store.value(store.currentID(cursor.get() + 1)).indented : current
|
||||
let indent = next > current ? next : current
|
||||
let item = newListItem(indent)
|
||||
if (currentValue.text.match(/^#\[\[TODO\]\]/)) {
|
||||
item.text = '#[[TODO]] ';
|
||||
}
|
||||
cursor.insertBelow(store, item)
|
||||
}
|
||||
|
||||
trigger('change')
|
||||
|
||||
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)
|
||||
trigger('change')
|
||||
return false
|
||||
}
|
||||
|
||||
function leaveEditor(event) {
|
||||
stopEditing(root, store, currentEditor);
|
||||
return false
|
||||
}
|
||||
|
||||
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
51
list-editor/keymap.js
Normal 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
|
|
@ -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) {
|
||||
|
|
Loading…
Reference in New Issue
Block a user