import _ from 'lodash' import $ from 'jquery' import he from 'he' import dragula from 'dragula' import textareaAutosizeInit from "./textarea.autosize" import createCursor from './cursor' import Store from './store' import Keymap from './keymap' import getCaretCoordinates from "../editor/src/caret-position"; textareaAutosizeInit($) function textareaCursorInfo(target, dir) { const minLine = 0; let pos = getCaretCoordinates(target, target.selectionEnd, {}) let line = (pos.top - 3) / pos.height; let maxLine = Math.round(target.clientHeight / pos.height) let nextLine = line nextLine += dir return { leaving: !(nextLine >= minLine && nextLine < maxLine), min: minLine, max: maxLine, current: line, next: nextLine }; } function editor(root, inputData, options) { let cursor = createCursor() let store = createStore(inputData); let drake = null; let editorKeymap = new Keymap() editorKeymap.mapKey('ArrowUp', 'backwardLine') editorKeymap.mapKey('ArrowDown', 'forwardLine') editorKeymap.mapKey('S-Delete', 'deleteBlock') editorKeymap.mapKey('C-Enter', 'insertLineAbove') editorKeymap.mapKey('Enter', 'insertLineBelow') editorKeymap.mapKey('Tab', 'indentBlock') editorKeymap.mapKey('S-Tab', 'indentBlock') editorKeymap.mapKey('Escape', 'leaveEditor') editorKeymap.mapKey('C-S-ArrowUp', 'blockMoveBackward') editorKeymap.mapKey('C-S-ArrowDown', 'blockMoveForward') editorKeymap.mapKey('C-.', 'toggleBlock') editorKeymap.mapKey('Backspace', 'deleteCharacterBackward') // keymap.mapKey('C-]', 'zoomIn') // keymap.mapKey('C-[', 'zoomOut') let normalKeymap = new Keymap() normalKeymap.mapKey('k', 'backwardLine') normalKeymap.mapKey('j', 'forwardLine') normalKeymap.mapKey('S-O', 'insertLineAbove') normalKeymap.mapKey('o', 'insertLineBelow') normalKeymap.mapKey('d', 'deleteBlock') normalKeymap.mapKey('i', 'enterEditor') normalKeymap.mapKey('S-I', 'enterEditor') normalKeymap.mapKey('S-A', 'enterEditorAppend') normalKeymap.mapKey('Tab', 'indentBlock') normalKeymap.mapKey('S-Tab', 'indentBlock') normalKeymap.mapKey('C-.', 'toggleBlock') normalKeymap.mapKey('C-;', 'toggleTodo') function createStore(inputData) { let data = [ {indented: 0, text: '', fold: 'open'}, ]; if (inputData.length) { data = inputData } return Store(data); } function save() { return new Promise(function (resolve, reject) { if (store.hasChanged()) { resolve(store.debug().result) store.clearChanged() } }); } function saveTree(from, opt) { opt = _.merge({force: false}, opt) return new Promise(function (resolve, reject) { if (opt.force || store.hasChanged()) { resolve(store.tree(from)) store.clearChanged() } }); } function treeForId(id) { return new Promise(function (resolve, reject) { resolve(store.tree(id)) }) } function copy(element, opt) { let item = $(element).parents('.list-item') let id = item.attr('data-id') if (opt.recursive) { return saveTree(id, {force: true}) } return new Promise(function (resolve, reject) { resolve(store.value(id)); }); } function flat(element) { let item = $(element).parents('.list-item') let id = item.attr('data-id') return new Promise(function (resolve, reject) { resolve(store.flat(id)); }); } function zoomin(element, opt) { let item = $(element).parents('.list-item') let id = item.attr('data-id') return new Promise(function (resolve, reject) { resolve(id); }); } function on(evt, handler) { events[evt].push(handler) } function trigger(event) { let args = [...arguments] args.splice(0, 1) _.each(events[event], function (handler) { handler(...args) }) } function start() { root.focus(); $(root).on('click', '.marker', function (event) { if (event.ctrlKey) { zoomin(this).then(id => { location.href = '/edit/' + id; }) } return true; }) cursor.set(0) renderUpdate() } function getChildren(id) { return store.children(id) } function replaceChildren(id, children) { store.replaceChildren(id, children) } function renderUpdate() { disableDragging(drake) render(root, store); drake = enableDragging(root) } let EDITOR = { normalKeymap, editorKeymap, on, save, saveTree, copy, flat, update, start, zoomin, treeForId, getChildren, replaceChildren, render: renderUpdate, deleteBlock, forwardLine, backwardLine, insertLineAbove, insertLineBelow, indentBlock, leaveEditor, enterEditor, enterEditorAppend, blockMoveBackward, blockMoveForward, deleteCharacterBackward, expandBlock, collapseBlock, toggleBlock, toggleTodo }; root.classList.add('root') root.setAttribute('tabindex', '-1') let defaults = { transform(text, element) { element.html(he.encode(text)) } } options = _.merge(defaults, options) let events = { change: [], 'start-editing': [], 'stop-editing': [], 'rendering': [], 'rendered': [] } let editing = false let currentEditor = null; function newListItem(indented) { return {indented: indented, text: '', fold: 'open', hidden: false} } function newItem(value) { let el = document.createElement('div') el.classList.add('list-item') el.setAttribute('data-id', value.id) el.style.marginLeft = (value.indented * 32) + 'px' let $el = $(el).data('indented', value.indented) let line = document.createElement('div') line.classList.add('line') let content = document.createElement('div') content.classList.add('content') line.prepend(content) options.transform(value.text, $(content), value.id, EDITOR) let marker = document.createElement('span') marker.classList.add('marker') let fold = document.createElement('span') fold.classList.add('fold') fold.innerHTML = '▶' line.prepend(marker) line.prepend(fold) el.prepend(line) return $el; } // TODO: build an actual tree of list items function renderTree(rootElement, rootData) { const el = (tag, children = []) => { let elt = document.createElement(tag) _.each(children, item => { elt.appendChild(item) }) return elt } /** * @param {Item[]} items * @returns {*} */ let buildTree = (items) => { return _.map(items, (item) => { return el("li", [ el("div", [document.createTextNode(item.text)]), el("ul", buildTree(item.children)) ]) }) } let tree = rootData.tree(); $(rootElement).children().remove() let $list = buildTree(tree) let $ul = $('