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 dragula from 'dragula'
import textareaAutosizeInit from "./textarea.autosize" import textareaAutosizeInit from "./textarea.autosize"
import createCursor from './cursor' import createCursor from './cursor'
import createSelection from './selection'
import Store from './store' import Store from './store'
import Keymap from './keymap'
import getCaretCoordinates from "../editor/src/caret-position"; import getCaretCoordinates from "../editor/src/caret-position";
textareaAutosizeInit($) textareaAutosizeInit($)
@ -28,10 +28,21 @@ function textareaCursorInfo(target, dir) {
function editor(root, inputData, options) { function editor(root, inputData, options) {
let cursor = createCursor() let cursor = createCursor()
let selection = createSelection()
let store = createStore(inputData); let store = createStore(inputData);
let keymap = new Keymap()
let drake = null; 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) { function createStore(inputData) {
let data = [ let data = [
{indented: 0, text: '', fold: 'open'}, {indented: 0, text: '', fold: 'open'},
@ -131,7 +142,19 @@ function editor(root, inputData, options) {
zoomin, zoomin,
treeForId, treeForId,
replaceChildren, replaceChildren,
render: renderUpdate render: renderUpdate,
deleteBlock,
forwardLine,
backwardLine,
insertLineAbove,
insertLineBelow,
indentBlock,
leaveEditor,
blockMoveBackward,
blockMoveForward
}; };
root.classList.add('root') root.classList.add('root')
@ -259,11 +282,7 @@ function editor(root, inputData, options) {
let $li = $(li) let $li = $(li)
.attr('data-id', value.id) .attr('data-id', value.id)
.toggleClass('selected', cursor.atPosition(index)) .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('hidden', value.indented >= hideLevel)
.toggleClass('border', value.indented >= 1)
.css('margin-left', (value.indented * 32) + 'px') .css('margin-left', (value.indented * 32) + 'px')
value.hidden = value.indented >= hideLevel value.hidden = value.indented >= hideLevel
@ -290,11 +309,7 @@ function editor(root, inputData, options) {
let value = rootData.value(storeId) let value = rootData.value(storeId)
let $li = newItem(value) let $li = newItem(value)
.css('margin-left', (value.indented * 32) + 'px') .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('selected', cursor.atPosition(index + $enter.length))
.toggleClass('border', value.indented >= 1)
.toggleClass('hidden', value.indented >= hideLevel); .toggleClass('hidden', value.indented >= hideLevel);
value.hidden = value.indented >= hideLevel value.hidden = value.indented >= hideLevel
@ -359,9 +374,7 @@ function editor(root, inputData, options) {
let position = store.index(id); let position = store.index(id);
cursor.set(position) cursor.set(position)
selection.selectOne(position, store)
_.defer(renderUpdate)
trigger('change') trigger('change')
}) })
return drake; return drake;
@ -369,6 +382,7 @@ function editor(root, inputData, options) {
function stopEditing(rootElement, store, element) { function stopEditing(rootElement, store, element) {
if (!editing) return if (!editing) return
if (element === null) { if (element === null) {
editing = false editing = false
currentEditor = null currentEditor = null
@ -453,120 +467,150 @@ function editor(root, inputData, options) {
})) }))
store.insertAfter(currentID, ...newItems) store.insertAfter(currentID, ...newItems)
disableDragging(drake) trigger('change')
render(root, store);
drake = enableDragging(root) // disableDragging(drake)
// render(root, store);
// drake = enableDragging(root)
return false return false
}); });
$(root).on('keydown', '.input-line', function (event) { $(root).on('keydown', '.input-line', function (event) {
if (event.key === 'Escape') { if (event.key === 'Escape') {
stopEditing(root, store, $(this)) stopEditing(root, store, $(this))
selection.selectOne(cursor.get(), store)
return false return false
} }
return true return true
}); });
$(root).on('keydown', function (event) { function moveCursor(event, dir) {
let target = event.target; let target = event.target;
let dir = 0;
if (event.key === 'ArrowUp') dir = -1
if (event.key === 'ArrowDown') dir = 1
let cursorInfo = null let cursorInfo = null
if (target.nodeName === 'TEXTAREA') cursorInfo = textareaCursorInfo(target, dir); 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 return true
} }
let next = true if (dir < 0) {
let prevSelected = cursor.save() cursor.moveUp(store)
} else if (dir > 0) {
cursor.moveDown(store)
}
if (event.key === 'ArrowUp') { if (editing) {
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') {
stopEditing(root, store, currentEditor); 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') 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); startEditing(root, store, cursor);
return false } else {
} else if (event.key === 'Escape') { trigger('change')
stopEditing(root, store, currentEditor);
return false
} }
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 () { $(root).on('click', '.marker', function () {
stopEditing(root, store, $(this).next('textarea')); stopEditing(root, store, $(this).next('textarea'));
return false; return false;
@ -587,11 +631,8 @@ function editor(root, inputData, options) {
stopEditing(root, store, currentEditor) stopEditing(root, store, currentEditor)
cursor.set(currentIndex) cursor.set(currentIndex)
selection.selectOne(cursor.get(), store)
disableDragging(drake) trigger('change')
render(root, store);
drake = enableDragging(root)
const $input = startEditing(root, store, cursor) const $input = startEditing(root, store, cursor)
$input.trigger('input') $input.trigger('input')
@ -612,9 +653,7 @@ function editor(root, inputData, options) {
return item return item
}) })
disableDragging(drake) trigger('change')
render(root, store);
drake = enableDragging(root)
}); });
function update(id, callback) { function update(id, callback) {
@ -630,6 +669,8 @@ function editor(root, inputData, options) {
} }
} }
EDITOR.on('change', renderUpdate)
return EDITOR; 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) { function moveBefore(from, to) {
let fromIndex = index(from) 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 let n = lastHigherIndented(fromIndex) - fromIndex
if (toIndex >= fromIndex && toIndex < fromIndex + n) return index(from) if (toIndex >= fromIndex && toIndex < fromIndex + n) return index(from)
if (fromIndex < toIndex) { if (fromIndex < toIndex) {