diff --git a/.eslintcache b/.eslintcache deleted file mode 100644 index b3807c49..00000000 --- a/.eslintcache +++ /dev/null @@ -1 +0,0 @@ -[{"/Users/jeffwu/projects/vimflowy/src/index.ts":"1","/Users/jeffwu/projects/vimflowy/src/assets/ts/app.tsx":"2","/Users/jeffwu/projects/vimflowy/src/assets/ts/keyEmitter.ts":"3","/Users/jeffwu/projects/vimflowy/src/assets/ts/modes.ts":"4","/Users/jeffwu/projects/vimflowy/src/assets/ts/keyHandler.ts":"5","/Users/jeffwu/projects/vimflowy/src/assets/ts/register.ts":"6","/Users/jeffwu/projects/vimflowy/src/assets/ts/keyMappings.ts":"7","/Users/jeffwu/projects/vimflowy/src/assets/ts/datastore.ts":"8","/Users/jeffwu/projects/vimflowy/src/assets/ts/plugins.ts":"9","/Users/jeffwu/projects/vimflowy/src/assets/ts/path.ts":"10","/Users/jeffwu/projects/vimflowy/src/assets/ts/document.ts":"11","/Users/jeffwu/projects/vimflowy/src/assets/ts/constants.ts":"12","/Users/jeffwu/projects/vimflowy/src/assets/ts/keyDefinitions.ts":"13","/Users/jeffwu/projects/vimflowy/src/assets/ts/session.ts":"14","/Users/jeffwu/projects/vimflowy/src/assets/ts/keyBindings.ts":"15","/Users/jeffwu/projects/vimflowy/src/assets/ts/utils/browser.ts":"16","/Users/jeffwu/projects/vimflowy/src/shared/utils/logger.ts":"17","/Users/jeffwu/projects/vimflowy/src/shared/data_backend.ts":"18","/Users/jeffwu/projects/vimflowy/src/shared/utils/errors.ts":"19","/Users/jeffwu/projects/vimflowy/src/assets/ts/configurations/vim.ts":"20","/Users/jeffwu/projects/vimflowy/src/assets/ts/components/app.tsx":"21","/Users/jeffwu/projects/vimflowy/src/assets/ts/utils/eventEmitter.ts":"22","/Users/jeffwu/projects/vimflowy/src/assets/ts/utils/queue.ts":"23","/Users/jeffwu/projects/vimflowy/src/assets/ts/utils/functional.ts":"24","/Users/jeffwu/projects/vimflowy/src/assets/ts/themes.ts":"25","/Users/jeffwu/projects/vimflowy/src/assets/ts/utils/text.ts":"26","/Users/jeffwu/projects/vimflowy/src/assets/ts/mutations.ts":"27","/Users/jeffwu/projects/vimflowy/src/assets/ts/cursor.ts":"28","/Users/jeffwu/projects/vimflowy/src/plugins/index.ts":"29","/Users/jeffwu/projects/vimflowy/src/assets/ts/definitions/index.ts":"30","/Users/jeffwu/projects/vimflowy/src/assets/ts/data_backend/index.ts":"31","/Users/jeffwu/projects/vimflowy/src/assets/ts/definitions/motions.ts":"32","/Users/jeffwu/projects/vimflowy/src/assets/ts/components/session.tsx":"33","/Users/jeffwu/projects/vimflowy/src/assets/ts/components/hotkeysTable.tsx":"34","/Users/jeffwu/projects/vimflowy/src/assets/ts/components/settings.tsx":"35","/Users/jeffwu/projects/vimflowy/src/assets/ts/components/menu.tsx":"36","/Users/jeffwu/projects/vimflowy/src/assets/ts/definitions/history.ts":"37","/Users/jeffwu/projects/vimflowy/src/assets/ts/definitions/indent.ts":"38","/Users/jeffwu/projects/vimflowy/src/assets/ts/definitions/zoom.ts":"39","/Users/jeffwu/projects/vimflowy/src/assets/ts/definitions/basics.ts":"40","/Users/jeffwu/projects/vimflowy/src/assets/ts/definitions/meta.ts":"41","/Users/jeffwu/projects/vimflowy/src/assets/ts/definitions/menu.tsx":"42","/Users/jeffwu/projects/vimflowy/src/assets/ts/menu.ts":"43","/Users/jeffwu/projects/vimflowy/src/assets/ts/components/breadcrumbs.tsx":"44","/Users/jeffwu/projects/vimflowy/src/assets/ts/components/block.tsx":"45","/Users/jeffwu/projects/vimflowy/src/assets/ts/components/pluginTable.tsx":"46","/Users/jeffwu/projects/vimflowy/src/assets/ts/components/fileInput.tsx":"47","/Users/jeffwu/projects/vimflowy/src/assets/ts/components/spinner.tsx":"48","/Users/jeffwu/projects/vimflowy/src/assets/ts/components/line.tsx":"49","/Users/jeffwu/projects/vimflowy/src/assets/ts/components/settings/backendSettings.tsx":"50","/Users/jeffwu/projects/vimflowy/src/assets/ts/components/settings/behaviorSettings.tsx":"51","/Users/jeffwu/projects/vimflowy/src/plugins/latex/index.tsx":"52","/Users/jeffwu/projects/vimflowy/src/plugins/html/index.tsx":"53","/Users/jeffwu/projects/vimflowy/src/plugins/easy_motion/index.tsx":"54","/Users/jeffwu/projects/vimflowy/src/plugins/text_formatting/index.tsx":"55","/Users/jeffwu/projects/vimflowy/src/plugins/marks/index.tsx":"56","/Users/jeffwu/projects/vimflowy/src/plugins/todo/index.tsx":"57","/Users/jeffwu/projects/vimflowy/src/plugins/time_tracking/index.tsx":"58","/Users/jeffwu/projects/vimflowy/src/plugins/recursive_expand/index.tsx":"59","/Users/jeffwu/projects/vimflowy/src/plugins/daily_notes/index.tsx":"60","/Users/jeffwu/projects/vimflowy/src/assets/ts/utils/token_unfolder.ts":"61"},{"size":27,"mtime":1609189374607,"results":"62","hashOfConfig":"63"},{"size":18224,"mtime":1609189374612,"results":"64","hashOfConfig":"63"},{"size":3296,"mtime":1609189374613,"results":"65","hashOfConfig":"63"},{"size":8216,"mtime":1609189374617,"results":"66","hashOfConfig":"63"},{"size":9472,"mtime":1609189374627,"results":"67","hashOfConfig":"63"},{"size":4133,"mtime":1609189374624,"results":"68","hashOfConfig":"63"},{"size":3421,"mtime":1609189374628,"results":"69","hashOfConfig":"63"},{"size":12061,"mtime":1609189374612,"results":"70","hashOfConfig":"63"},{"size":10858,"mtime":1609189374627,"results":"71","hashOfConfig":"63"},{"size":3296,"mtime":1609189374613,"results":"72","hashOfConfig":"63"},{"size":27160,"mtime":1609189374614,"results":"73","hashOfConfig":"63"},{"size":161,"mtime":1609189374623,"results":"74","hashOfConfig":"63"},{"size":5024,"mtime":1609189374613,"results":"75","hashOfConfig":"63"},{"size":42585,"mtime":1609189374623,"results":"76","hashOfConfig":"63"},{"size":4955,"mtime":1609189374624,"results":"77","hashOfConfig":"63"},{"size":2999,"mtime":1609189374615,"results":"78","hashOfConfig":"63"},{"size":2020,"mtime":1609189374606,"results":"79","hashOfConfig":"63"},{"size":1541,"mtime":1609189374607,"results":"80","hashOfConfig":"63"},{"size":2230,"mtime":1609189374605,"results":"81","hashOfConfig":"63"},{"size":13496,"mtime":1609189374611,"results":"82","hashOfConfig":"63"},{"size":7519,"mtime":1609189374619,"results":"83","hashOfConfig":"63"},{"size":3004,"mtime":1609189374614,"results":"84","hashOfConfig":"63"},{"size":1245,"mtime":1609189374616,"results":"85","hashOfConfig":"63"},{"size":1366,"mtime":1609189374615,"results":"86","hashOfConfig":"63"},{"size":4841,"mtime":1609189374628,"results":"87","hashOfConfig":"63"},{"size":1326,"mtime":1609189374615,"results":"88","hashOfConfig":"63"},{"size":13914,"mtime":1609189374624,"results":"89","hashOfConfig":"63"},{"size":13615,"mtime":1609189374611,"results":"90","hashOfConfig":"63"},{"size":357,"mtime":1609189374603,"results":"91","hashOfConfig":"63"},{"size":129,"mtime":1609189374627,"results":"92","hashOfConfig":"63"},{"size":8494,"mtime":1609189374617,"results":"93","hashOfConfig":"63"},{"size":5415,"mtime":1609189374625,"results":"94","hashOfConfig":"63"},{"size":7161,"mtime":1609189374620,"results":"95","hashOfConfig":"63"},{"size":3544,"mtime":1609189374622,"results":"96","hashOfConfig":"63"},{"size":23168,"mtime":1609189374618,"results":"97","hashOfConfig":"63"},{"size":3792,"mtime":1609189374621,"results":"98","hashOfConfig":"63"},{"size":2440,"mtime":1609189374626,"results":"99","hashOfConfig":"63"},{"size":1886,"mtime":1609189374626,"results":"100","hashOfConfig":"63"},{"size":1717,"mtime":1609189374627,"results":"101","hashOfConfig":"63"},{"size":16683,"mtime":1609189374626,"results":"102","hashOfConfig":"63"},{"size":285,"mtime":1609189374625,"results":"103","hashOfConfig":"63"},{"size":3776,"mtime":1609189374626,"results":"104","hashOfConfig":"63"},{"size":2087,"mtime":1609189374616,"results":"105","hashOfConfig":"63"},{"size":2516,"mtime":1609189374620,"results":"106","hashOfConfig":"63"},{"size":9277,"mtime":1609189374621,"results":"107","hashOfConfig":"63"},{"size":6210,"mtime":1609189374619,"results":"108","hashOfConfig":"63"},{"size":1972,"mtime":1609189374621,"results":"109","hashOfConfig":"63"},{"size":476,"mtime":1609189374622,"results":"110","hashOfConfig":"63"},{"size":7658,"mtime":1609189374622,"results":"111","hashOfConfig":"63"},{"size":12212,"mtime":1609189374618,"results":"112","hashOfConfig":"63"},{"size":2541,"mtime":1609189374619,"results":"113","hashOfConfig":"63"},{"size":1961,"mtime":1609189374599,"results":"114","hashOfConfig":"63"},{"size":1617,"mtime":1609189374601,"results":"115","hashOfConfig":"63"},{"size":4604,"mtime":1609189374604,"results":"116","hashOfConfig":"63"},{"size":3243,"mtime":1609189374600,"results":"117","hashOfConfig":"63"},{"size":19289,"mtime":1609189374598,"results":"118","hashOfConfig":"63"},{"size":3800,"mtime":1609189374602,"results":"119","hashOfConfig":"63"},{"size":10825,"mtime":1609189374599,"results":"120","hashOfConfig":"63"},{"size":1712,"mtime":1609189374601,"results":"121","hashOfConfig":"63"},{"size":15090,"mtime":1609189374597,"results":"122","hashOfConfig":"63"},{"size":5479,"mtime":1609189374614,"results":"123","hashOfConfig":"63"},{"filePath":"124","messages":"125","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"126"},"1xhvcjj",{"filePath":"127","messages":"128","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"126"},{"filePath":"129","messages":"130","errorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"131","usedDeprecatedRules":"126"},{"filePath":"132","messages":"133","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"126"},{"filePath":"134","messages":"135","errorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"136","usedDeprecatedRules":"126"},{"filePath":"137","messages":"138","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"126"},{"filePath":"139","messages":"140","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"126"},{"filePath":"141","messages":"142","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"126"},{"filePath":"143","messages":"144","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"126"},{"filePath":"145","messages":"146","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"126"},{"filePath":"147","messages":"148","errorCount":0,"warningCount":2,"fixableErrorCount":0,"fixableWarningCount":0,"source":"149","usedDeprecatedRules":"126"},{"filePath":"150","messages":"151","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"126"},{"filePath":"152","messages":"153","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"126"},{"filePath":"154","messages":"155","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"126"},{"filePath":"156","messages":"157","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"126"},{"filePath":"158","messages":"159","errorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"160","usedDeprecatedRules":"126"},{"filePath":"161","messages":"162","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"126"},{"filePath":"163","messages":"164","errorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"165","usedDeprecatedRules":"126"},{"filePath":"166","messages":"167","errorCount":0,"warningCount":2,"fixableErrorCount":0,"fixableWarningCount":0,"source":"168","usedDeprecatedRules":"126"},{"filePath":"169","messages":"170","errorCount":0,"warningCount":6,"fixableErrorCount":0,"fixableWarningCount":6,"source":"171","usedDeprecatedRules":"126"},{"filePath":"172","messages":"173","errorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"174","usedDeprecatedRules":"126"},{"filePath":"175","messages":"176","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"126"},{"filePath":"177","messages":"178","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"126"},{"filePath":"179","messages":"180","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"126"},{"filePath":"181","messages":"182","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"126"},{"filePath":"183","messages":"184","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"126"},{"filePath":"185","messages":"186","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"126"},{"filePath":"187","messages":"188","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"126"},{"filePath":"189","messages":"190","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"126"},{"filePath":"191","messages":"192","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"126"},{"filePath":"193","messages":"194","errorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"195","usedDeprecatedRules":"126"},{"filePath":"196","messages":"197","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"126"},{"filePath":"198","messages":"199","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"126"},{"filePath":"200","messages":"201","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"126"},{"filePath":"202","messages":"203","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"126"},{"filePath":"204","messages":"205","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"126"},{"filePath":"206","messages":"207","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"126"},{"filePath":"208","messages":"209","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"126"},{"filePath":"210","messages":"211","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"126"},{"filePath":"212","messages":"213","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"126"},{"filePath":"214","messages":"215","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"126"},{"filePath":"216","messages":"217","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"126"},{"filePath":"218","messages":"219","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"126"},{"filePath":"220","messages":"221","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"126"},{"filePath":"222","messages":"223","errorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"224","usedDeprecatedRules":"126"},{"filePath":"225","messages":"226","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"126"},{"filePath":"227","messages":"228","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"126"},{"filePath":"229","messages":"230","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"126"},{"filePath":"231","messages":"232","errorCount":0,"warningCount":3,"fixableErrorCount":0,"fixableWarningCount":0,"source":"233","usedDeprecatedRules":"126"},{"filePath":"234","messages":"235","errorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"236","usedDeprecatedRules":"126"},{"filePath":"237","messages":"238","errorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"239","usedDeprecatedRules":"126"},{"filePath":"240","messages":"241","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"126"},{"filePath":"242","messages":"243","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"126"},{"filePath":"244","messages":"245","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"126"},{"filePath":"246","messages":"247","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"126"},{"filePath":"248","messages":"249","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"126"},{"filePath":"250","messages":"251","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"126"},{"filePath":"252","messages":"253","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"126"},{"filePath":"254","messages":"255","errorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"256","usedDeprecatedRules":"126"},{"filePath":"257","messages":"258","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"126"},{"filePath":"259","messages":"260","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},"/Users/jeffwu/projects/vimflowy/src/index.ts",[],["261","262"],"/Users/jeffwu/projects/vimflowy/src/assets/ts/app.tsx",[],"/Users/jeffwu/projects/vimflowy/src/assets/ts/keyEmitter.ts",["263"],"import $ from 'jquery';\nimport * as _ from 'lodash';\n\nimport * as browser_utils from './utils/browser';\nimport EventEmitter from './utils/eventEmitter';\nimport logger from '../../shared/utils/logger';\nimport { Key } from './types';\n\n/*\nKeyEmitter is an EventEmitter that emits keys\nA key corresponds to a keypress in the browser, including modifiers/special keys\n\nThe core function is to take browser keypress events, and normalize the key to have a string representation.\n\nFor more info, see its consumer, keyHandler.ts, as well as keyBindings.ts\nNote that one-character keys are treated specially, in that they are insertable in insert mode.\n*/\n\nconst shiftMap: {[key: string]: Key} = {\n '`': '~',\n '1': '!',\n '2': '@',\n '3': '#',\n '4': '$',\n '5': '%',\n '6': '^',\n '7': '&',\n '8': '*',\n '9': '(',\n '0': ')',\n '-': '_',\n '=': '+',\n '[': '{',\n ']': '}',\n ';': ':',\n '\\'': '\"',\n '\\\\': '|',\n '.': '>',\n ',': '<',\n '/': '?',\n};\n\nconst ignoreMap: {[keyCode: number]: string} = {\n 16: 'shift alone',\n 17: 'ctrl alone',\n 18: 'alt alone',\n 91: 'left command alone',\n 93: 'right command alone',\n};\n\nconst keyCodeMap: {[keyCode: number]: Key} = {\n 8: 'backspace',\n 9: 'tab',\n 13: 'enter',\n 27: 'esc',\n 32: 'space',\n\n 33: 'page up',\n 34: 'page down',\n 35: 'end',\n 36: 'home',\n 37: 'left',\n 38: 'up',\n 39: 'right',\n 40: 'down',\n\n 46: 'delete',\n\n 48: '0',\n 49: '1',\n 50: '2',\n 51: '3',\n 52: '4',\n 53: '5',\n 54: '6',\n 55: '7',\n 56: '8',\n 57: '9',\n\n 186: ';',\n 187: '=',\n 188: ',',\n 189: '-',\n 190: '.',\n 191: '/',\n 192: '`',\n\n 219: '[',\n 220: '\\\\',\n 221: ']',\n 222: '\\'',\n};\n\nfor (let j = 1; j <= 26; j++) {\n const keyCode = j + 64;\n const letter = String.fromCharCode(keyCode);\n const lower = letter.toLowerCase();\n keyCodeMap[keyCode] = lower;\n shiftMap[lower] = letter;\n}\n\nif (browser_utils.isFirefox()) {\n keyCodeMap[173] = '-';\n}\n\nexport default class KeyEmitter extends EventEmitter {\n constructor() {\n super();\n }\n\n public listen() {\n // IME event\n $(document).on('compositionend', (e: any) => {\n e.originalEvent.data.split('').forEach((key: string) => {\n this.emit('keydown', key);\n });\n });\n\n return $(document).keydown(e => {\n // IME input keycode is 229\n if (e.keyCode === 229) {\n return false;\n }\n if (e.keyCode in ignoreMap) {\n return true;\n }\n let key;\n if (e.keyCode in keyCodeMap) {\n key = keyCodeMap[e.keyCode];\n } else {\n // this is necessary for typing stuff..\n key = String.fromCharCode(e.keyCode);\n }\n\n if (e.shiftKey) {\n if (key in shiftMap) {\n key = shiftMap[key];\n } else {\n key = `shift+${key}`;\n }\n }\n\n if (e.altKey) {\n key = `alt+${key}`;\n }\n\n if (e.ctrlKey) {\n key = `ctrl+${key}`;\n }\n\n if (e.metaKey) {\n key = `meta+${key}`;\n }\n\n logger.debug('keycode', e.keyCode, 'key', key);\n const results = this.emit('keydown', key);\n // return false to stop propagation, if any handler handled the key\n if (_.some(results)) {\n e.stopPropagation();\n e.preventDefault();\n return false;\n // return browser_utils.cancel(e);\n }\n return true;\n });\n }\n}\n","/Users/jeffwu/projects/vimflowy/src/assets/ts/modes.ts",[],"/Users/jeffwu/projects/vimflowy/src/assets/ts/keyHandler.ts",["264"],"/*\nTakes in keys, and, based on the keybindings (see keyBindings.ts),\nmanipulates the session (see session.ts)\n\nThe KeyHandler class manages the state of what keys have been input, dealing with the logic for\n- handling multi-key sequences, i.e. a key that semantically needs another key (e.g. the GO command, `g` in vim)\n- handling motions and commands that take motions\n- combining together and saving sequences of commands\n (important for the REPEAT command, `.` in vim, for macros, and for number prefixes, e.g. 3j)\n- dropping sequences of commands that are invalid\n- telling the session when to save (i.e. the proper checkpoints for undo and redo)\nIt maintains custom logic for this, for each mode.\n(NOTE: hopefully this logic can be more unified! It is currently quite fragile)\n*/\n\n// TODO/NOTE: for now, macros/repeat sequences still save keys and not commands\n// it's tricky to fix due to\n// the commands not being serializable (e.g. in the case where arguments are motions)\n// or when the command awaits from keyStream\n\nimport EventEmitter from './utils/eventEmitter';\nimport logger from '../../shared/utils/logger';\nimport Queue from './utils/queue';\nimport Session from './session';\nimport KeyBindings, { KeyBindingsTree } from './keyBindings';\nimport { Motion, Action, ActionContext, motionKey, SequenceAction } from './keyDefinitions';\n// import Menu from './menu';\nimport * as Modes from './modes';\n// import * as constants from './constants';\n\nimport { Key } from './types';\n\n// Simple stream class where you can\n// enqueue synchronously, and dequeue asynchronously\n// (waiting for the next enqueue if nothing is available)\nexport class KeyStream extends EventEmitter {\n private queue: Queue;\n\n public lastSequence: Array;\n private curSequence: Array;\n // where up to curSequence to actually keep\n private saveIndex: number;\n\n constructor(keys: Array = []) {\n super();\n this.queue = new Queue(keys);\n\n this.lastSequence = [];\n this.curSequence = [];\n this.saveIndex = 0;\n }\n\n public empty() {\n return this.queue.empty();\n }\n\n public async dequeue(): Promise {\n const val = await this.queue.dequeue();\n this.curSequence.push(val);\n this.emit('dequeue', val);\n return val;\n }\n\n public enqueue(val: Key) {\n this.queue.enqueue(val);\n }\n\n public keep() {\n this.saveIndex = this.curSequence.length;\n }\n\n public drop() {\n this.curSequence = this.curSequence.slice(0, this.saveIndex);\n }\n\n public dropAll() {\n this.curSequence = [];\n }\n\n public save() {\n if (this.curSequence.length) {\n this.lastSequence = this.curSequence;\n this.curSequence = [];\n this.saveIndex = 0;\n }\n }\n}\n\ntype ActionRecord = {\n action: Action,\n motion: Motion | null,\n context: ActionContext,\n};\n\nexport default class KeyHandler extends EventEmitter {\n private session: Session;\n private keyBindings: KeyBindings;\n public keyStream: KeyStream;\n private processQueue: Promise;\n\n constructor(session: Session, keyBindings: KeyBindings) {\n super();\n this.session = session;\n\n this.keyBindings = keyBindings;\n\n this.keyStream = new KeyStream();\n\n this.processQueue = Promise.resolve();\n }\n\n public async playRecording(recording: Array) {\n // the recording shouldn't save, (i.e. no @session.save)\n const oldSave = this.session.save;\n this.session.save = () => null;\n const recordKeyStream = new KeyStream(recording);\n await this._processKeys(recordKeyStream);\n this.session.save = oldSave;\n }\n\n // general handling\n\n public queueKey(key: Key) {\n logger.info('Handling key:', key);\n this.keyStream.enqueue(key);\n this.processKeys(); // FIRE AND FORGET\n }\n\n private async handleRecord(keyStream: KeyStream, record: ActionRecord) {\n const old_mode = this.session.mode;\n const mode_obj = Modes.getMode(old_mode);\n let { action, context, motion } = record;\n context = await mode_obj.transform_context(context);\n logger.info('Action:', action.name);\n if (motion) {\n logger.info('Motion:', motion.name);\n context.motion = await motion.definition.call(motion.definition, context);\n }\n // logger.debug('Context:', context);\n logger.debug(`Context: { repeat: ${context.repeat} }`);\n\n await mode_obj.beforeEvery(action.name, context);\n if (action.metadata.sequence === SequenceAction.DROP_ALL) {\n keyStream.dropAll();\n } else if (action.metadata.sequence === SequenceAction.DROP) {\n keyStream.drop();\n } else {\n keyStream.keep();\n }\n await action.definition.call(action.definition, context);\n\n const new_mode_obj = Modes.getMode(this.session.mode);\n await new_mode_obj.every(action.name, context, old_mode);\n }\n\n private async _processKeys(keyStream: KeyStream) {\n while (!keyStream.empty()) {\n const record = await this.getCommand(keyStream);\n if (record != null) {\n await this.handleRecord(keyStream, record);\n }\n // NOTE: needs to be outside if statement\n // in case of transformed key\n this.session.emit('handledKey');\n this.emit('handledKey');\n }\n }\n\n public queue(next: () => void | Promise) {\n this.processQueue = this.processQueue.then(next);\n return this.processQueue;\n }\n\n public processKeys() {\n this.queue(async () => {\n await this._processKeys(this.keyStream);\n }).catch((err) => {\n // expose any errors\n setTimeout(() => { throw err; });\n }).then(() => {\n this.emit('processedQueue');\n });\n }\n\n public async getCommand(keyStream: KeyStream): Promise {\n const mode = this.session.mode;\n const mode_obj = Modes.getMode(mode);\n\n const bindings = this.keyBindings.bindings[this.session.mode];\n let key: Key | null = await keyStream.dequeue();\n let context: ActionContext = {\n mode,\n session: this.session,\n repeat: 1,\n keyStream,\n keyHandler: this,\n };\n\n [key, context] = await mode_obj.transform_key(key, context);\n if (key === null) {\n // a transform acted (which, for now, we always consider not bad. could change)\n // TODO have transform key return an action?\n return null;\n }\n\n return this.getAction(keyStream, key, bindings, context);\n }\n\n public async getAction(\n keyStream: KeyStream, key: Key,\n bindings: KeyBindingsTree, context: ActionContext\n ): Promise {\n\n let info: KeyBindingsTree | Motion | Action | null = bindings.getKey(key);\n let action: Action;\n let motion: Motion | null = null;\n\n if (info instanceof KeyBindingsTree) {\n if (!info.hasAction) {\n return null;\n }\n key = await keyStream.dequeue();\n return await this.getAction(keyStream, key, info, context);\n }\n if (info instanceof Action) {\n action = info;\n } else if (info instanceof Motion) {\n motion = info;\n // use original bindings' motion function\n const moveInfo = this.keyBindings.bindings[context.mode].getKey(motionKey);\n if (moveInfo == null) {\n return null;\n }\n if (moveInfo instanceof KeyBindingsTree) {\n throw new Error(`${motionKey} should be registered as the final key`);\n }\n if (moveInfo instanceof Motion) {\n throw new Error(`${motionKey} should be registered for an action`);\n }\n action = moveInfo;\n } else { // info is null\n const moveInfo = bindings.getKey(motionKey);\n if (moveInfo == null) { // no handler for motion\n return null;\n }\n if (moveInfo instanceof KeyBindingsTree) {\n throw new Error(`${motionKey} should be registered as the final key`);\n }\n if (moveInfo instanceof Motion) {\n throw new Error(`${motionKey} should be registered for an action`);\n }\n action = moveInfo;\n let motion_repeat;\n [motion_repeat, key] = await this.getRepeat(keyStream, key);\n context.repeat = context.repeat * motion_repeat;\n motion = await this.getMotion(keyStream, key, this.keyBindings.bindings[context.mode]);\n if (motion === null) {\n return null;\n }\n motion = motion;\n }\n\n return {\n motion,\n action,\n context,\n };\n }\n\n // NOTE: this should maybe be normal-mode specific\n // but it would also need to be done for the motions\n // takes keyStream, key, returns repeat number and key\n public async getRepeat(\n keyStream: KeyStream, key: string\n ): Promise<[number, string]> {\n const begins = [1, 2, 3, 4, 5, 6, 7, 8, 9].map((x => x.toString()));\n const continues = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9].map((x => x.toString()));\n\n if (begins.indexOf(key) === -1) {\n return [1, key];\n }\n let numStr = '' + key;\n while (true) {\n key = await keyStream.dequeue();\n if (continues.indexOf(key) === -1) {\n break;\n }\n numStr += key;\n }\n return [parseInt(numStr, 10), key];\n }\n\n // useful when you expect a motion\n private async getMotion(\n keyStream: KeyStream, key: Key, bindings: KeyBindingsTree\n ): Promise {\n const info = bindings.getKey(key);\n if (info == null) {\n return null;\n } else if (info instanceof KeyBindingsTree) {\n if (!info.hasMotion) {\n return null;\n }\n key = await keyStream.dequeue();\n return await this.getMotion(keyStream, key, info);\n } else if (info instanceof Action) {\n return null;\n } else {\n return info;\n }\n }\n}\n","/Users/jeffwu/projects/vimflowy/src/assets/ts/register.ts",[],"/Users/jeffwu/projects/vimflowy/src/assets/ts/keyMappings.ts",[],"/Users/jeffwu/projects/vimflowy/src/assets/ts/datastore.ts",[],"/Users/jeffwu/projects/vimflowy/src/assets/ts/plugins.ts",[],"/Users/jeffwu/projects/vimflowy/src/assets/ts/path.ts",[],"/Users/jeffwu/projects/vimflowy/src/assets/ts/document.ts",["265","266"],"import * as _ from 'lodash';\n// import 'core-js/shim';\n\nimport * as errors from '../../shared/utils/errors';\nimport EventEmitter from './utils/eventEmitter';\nimport * as fn_utils from './utils/functional';\n// import logger from './utils/logger';\nimport { isWhitespace } from './utils/text';\nimport Path from './path';\nimport { DocumentStore } from './datastore';\nimport { InMemory } from '../../shared/data_backend';\nimport {\n Row, Col, Char, Line, SerializedLine, SerializedBlock\n} from './types';\n\ntype RowInfo = {\n readonly line: Line;\n readonly collapsed: boolean;\n // TODO use Immutable.List?\n readonly parentRows: Array;\n readonly childRows: Array;\n readonly pluginData: any;\n};\n\nexport type AttachedChildInfo = {\n parent_row: Row,\n parent_index: number,\n row: Row,\n child_index: number,\n};\n\n/*\n * Immutable representation of what we know about a row,\n * including all descendants (recursively) by reference.\n * Child is null, if we haven't loaded it yet\n */\n// TODO: rename this to cached Tree?\nexport class CachedRowInfo {\n public readonly row: Row;\n public readonly info: RowInfo;\n // TODO: use immutable list?\n public readonly children: Array;\n\n constructor(\n row: Row,\n info: RowInfo,\n children: Array,\n ) {\n this.row = row;\n this.info = info;\n this.children = children;\n }\n\n public clone() {\n return new CachedRowInfo(\n this.row, this.info, this.children\n );\n }\n\n public get parentRows() {\n return this.info.parentRows;\n }\n public get childRows() {\n return this.info.childRows;\n }\n public get line() {\n return this.info.line;\n }\n public get collapsed() {\n return this.info.collapsed;\n }\n public get pluginData() {\n return this.info.pluginData;\n }\n\n public setChildren(children: Array): CachedRowInfo {\n return new CachedRowInfo(this.row, this.info, children);\n }\n private setInfo(info: RowInfo): CachedRowInfo {\n return new CachedRowInfo(this.row, info, this.children);\n }\n public setLine(line: Line): CachedRowInfo {\n const info: RowInfo = Object.assign({}, this.info, { line });\n return this.setInfo(info);\n }\n public setCollapsed(collapsed: boolean): CachedRowInfo {\n const info: RowInfo = Object.assign({}, this.info, { collapsed });\n return this.setInfo(info);\n }\n public setChildRows(childRows: Array): CachedRowInfo {\n const info: RowInfo = Object.assign({}, this.info, { childRows });\n return this.setInfo(info);\n }\n public setParentRows(parentRows: Array): CachedRowInfo {\n const info: RowInfo = Object.assign({}, this.info, { parentRows });\n return this.setInfo(info);\n }\n public setPluginData(pluginData: any): CachedRowInfo {\n const info: RowInfo = Object.assign({}, this.info, { pluginData });\n return this.setInfo(info);\n }\n}\n\nclass DocumentCache {\n private cache: {[row: number]: CachedRowInfo};\n\n constructor() {\n this.cache = {};\n }\n\n public clear() {\n this.cache = {};\n }\n\n public loadRow(row: Row, info: RowInfo) {\n const cached = new CachedRowInfo(row, info, info.childRows.map((childRow) => {\n return this.get(childRow);\n }));\n this.set(row, cached);\n return cached;\n }\n\n public isCached(row: Row) {\n return !!this.get(row);\n }\n\n /*\n * Updates childRows for a given row\n * Call this function if a row changed\n */\n private bubbleUpdate(row: Row) {\n const cachedRow = this.get(row);\n if (!cachedRow) {\n return;\n }\n cachedRow.parentRows.forEach((parentRow) => {\n const parentCachedRow = this.get(parentRow);\n if (!parentCachedRow) {\n return;\n }\n // NOTE: this will cause more bubbled updates\n this.set(parentRow, this.updateChildren(parentCachedRow));\n });\n }\n\n private set(row: Row, cachedRow: CachedRowInfo) {\n this.cache[row] = cachedRow;\n this.bubbleUpdate(row);\n }\n\n private update(row: Row, updateFn: (info: CachedRowInfo) => CachedRowInfo, force?: boolean) {\n const cachedRow = this.get(row);\n if (!cachedRow) {\n if (force) {\n throw new Error(`Updating failed - Row ${row} was not cached`);\n }\n return false;\n }\n this.set(row, updateFn(cachedRow));\n return true;\n }\n\n public get(row: Row) {\n const cachedRow = this.cache[row];\n if (!cachedRow) {\n return null;\n }\n return cachedRow;\n }\n\n private updateChildren(cachedRow: CachedRowInfo) {\n return cachedRow.setChildren(\n cachedRow.childRows.map((childRow) => this.get(childRow))\n );\n }\n\n public setLine(row: Row, line: Line) {\n this.update(row, (cachedRow) => cachedRow.setLine(line), true);\n }\n public setCollapsed(row: Row, collapsed: boolean) {\n this.update(row, (cachedRow) => cachedRow.setCollapsed(collapsed), true);\n }\n public setChildRows(row: Row, childRows: Array) {\n this.update(row, (cachedRow) =>\n this.updateChildren(cachedRow.setChildRows(childRows))\n , true);\n }\n public setParentRows(row: Row, parentRows: Array) {\n this.update(row, (cachedRow) => cachedRow.setParentRows(parentRows), true);\n }\n public setPluginData(row: Row, pluginData: any) {\n this.update(row, (cachedRow) => cachedRow.setPluginData(pluginData), true);\n }\n}\n\n/*\nDocument is a wrapper class around the actual datastore, providing methods to manipulate the document\nthe document itself includes:\n - the text in each line, including text properties like bold/italic\n - the parent/child relationships and collapsed-ness of lines\nalso deals with loading the initial document from the datastore, and serializing the document to a string\n\nCurrently, the separation between the Session and Document classes is not very good. (see session.ts)\n*/\n\ntype SearchOptions = {nresults?: number, case_sensitive?: boolean};\n\nexport default class Document extends EventEmitter {\n public cache: DocumentCache;\n public store: DocumentStore;\n public name: string;\n public root: Path;\n\n constructor(store: DocumentStore, name = '') {\n super();\n this.cache = new DocumentCache();\n this.store = store;\n this.name = name;\n this.root = Path.root();\n return this;\n }\n\n\n public async _newChild(parent: Row, index = -1): Promise {\n const row = await this.store.getNew();\n\n // NOTE: order is important for caching.\n // - first emit async so plugins can prepare for what will happen\n // - then load regular data (attach hooks can use cached plugin data)\n // - lastly update plugin data (plugin data knows attached state)\n\n await this.emitAsync('childAdded', { row, parent });\n\n // necessary only for speed reasons\n this.cache.loadRow(row, {\n line: [], collapsed: false,\n childRows: [], parentRows: [], // parent will get added\n pluginData: {},\n });\n\n const [ info ] = await Promise.all([\n this._attach(row, parent, index),\n // purely to populate the cache\n this.store.setDetachedParent(row, null),\n ]);\n\n await this.updateCachedPluginData(row);\n return info;\n }\n\n public async getInfo(row: Row): Promise {\n errors.assert(row != null, 'Cannot get info for undefined');\n const cached = this.cache.get(row);\n if (cached !== null) {\n return cached;\n }\n\n const [\n line, collapsed, children, parents, pluginData\n ] = await Promise.all, Array, any>([\n this.store.getLine(row),\n this.store.getCollapsed(row),\n this.store.getChildren(row),\n this.store.getParents(row),\n this.applyHookAsync('pluginRowContents', {}, { row }),\n ]);\n const info: RowInfo = {\n line, collapsed,\n parentRows: parents,\n childRows: children,\n pluginData,\n };\n return this.cache.loadRow(row, info);\n }\n\n public async forceLoadTree(row = this.root.row, ignoreCollapsed = false) {\n const cachedRow = await this.getInfo(row);\n\n if (ignoreCollapsed || !cachedRow.collapsed) {\n await Promise.all(\n cachedRow.childRows.map(\n async (childRow) => await this.forceLoadTree(childRow)\n )\n );\n }\n }\n\n // TODO: actually use this\n public async forceLoadPath(path: Path) {\n const ancestry = path.getAncestry();\n await Promise.all(\n ancestry.map(async (row) => {\n await this.getInfo(row);\n })\n );\n }\n\n public async updateCachedPluginData(row: Row) {\n if (this.cache.isCached(row)) {\n const pluginData = await this.applyHookAsync('pluginRowContents', {}, { row });\n this.cache.setPluginData(row, pluginData);\n } else {\n await this.getInfo(row);\n }\n }\n\n public async getLine(row: Row) {\n return (await this.getInfo(row)).line;\n }\n\n public async getText(row: Row) {\n return (await this.getLine(row)).join('');\n }\n\n public async getChar(row: Row, col: Col) {\n return (await this.getLine(row))[col];\n }\n\n public async setLine(row: Row, line: Line) {\n this.cache.setLine(row, line);\n await this.store.setLine(row, line);\n }\n\n // get word at this location\n // if on a whitespace character, return nothing\n public async getWord(row: Row, col: Col) {\n const text = await this.getLine(row);\n\n if (isWhitespace(text[col])) {\n return '';\n }\n\n let start = col;\n let end = col;\n while ((start > 0) && !isWhitespace(text[start - 1])) {\n start -= 1;\n }\n while ((end < text.length - 1) && !isWhitespace(text[end + 1])) {\n end += 1;\n }\n let word = text.slice(start, end + 1).join('');\n // remove leading and trailing punctuation\n word = word.replace(/^[-.,()&$#!\\[\\]{}\"']+/g, '');\n word = word.replace(/[-.,()&$#!\\[\\]{}\"']+$/g, '');\n return word;\n }\n\n public async writeChars(row: Row, col: Col, chars: Array) {\n const line = (await this.getLine(row)).slice();\n line.splice(col, 0, ...chars);\n return await this.setLine(row, line);\n }\n\n public async deleteChars(row: Row, col: Col, num: number): Promise {\n const line = await this.getLine(row);\n const deleted = line.splice(col, num);\n await this.setLine(row, line);\n return deleted;\n }\n\n public async getLength(row: Row) {\n return (await this.getLine(row)).length;\n }\n\n // structure\n\n public async _getChildren(row: Row, min = 0, max = -1): Promise> {\n const info = await this.getInfo(row);\n return fn_utils.getSlice(info.childRows, min, max);\n }\n\n private async _setChildren(row: Row, children: Array) {\n this.cache.setChildRows(row, children);\n return await this.store.setChildren(row, children);\n }\n\n\n private async _getParents(row: Row): Promise> {\n const info = await this.getInfo(row);\n return info.parentRows;\n }\n\n private async _setParents(row: Row, parent_rows: Array) {\n this.cache.setParentRows(row, parent_rows);\n return await this.store.setParents(row, parent_rows);\n }\n\n public async getChildren(parent_path: Path): Promise> {\n return (await this._getChildren(parent_path.row)).map(row => parent_path.child(row));\n }\n\n public async hasChildren(row: Row) {\n return (await this._getChildren(row)).length > 0;\n }\n\n public async hasChild(parent_row: Row, row: Row): Promise {\n return (await this._getChildren(parent_row)).indexOf(row) !== -1;\n }\n\n public async getSiblings(path: Path) {\n if (path.parent == null) { // i.e. (path.isRoot())\n return [path];\n }\n return await this.getChildren(path.parent);\n }\n\n public async nextClone(path: Path): Promise {\n if (path.parent == null) {\n return path;\n }\n const parents = await this._getParents(path.row);\n let i = parents.indexOf(path.parent.row);\n errors.assert(i > -1);\n let new_parent_path;\n while (true) {\n i = (i + 1) % parents.length;\n let new_parent = parents[i];\n new_parent_path = await this.canonicalPath(new_parent);\n // this happens if the parent got detached\n if (new_parent_path !== null) {\n break;\n }\n }\n return new_parent_path.child(path.row);\n }\n\n public async indexInParent(child: Path) {\n const children = await this.getSiblings(child);\n return _.findIndex(children, sib => sib.row === child.row);\n }\n\n public async collapsed(row: Row) {\n return (await this.getInfo(row)).collapsed;\n }\n\n public async setCollapsed(row: Row, collapsed: boolean) {\n this.cache.setCollapsed(row, collapsed);\n await this.store.setCollapsed(row, collapsed);\n }\n\n public async toggleCollapsed(row: Row) {\n this.setCollapsed(row, !await this.collapsed(row));\n }\n\n // last thing visible nested within row\n public async walkToLastVisible(row: Row, pathsofar: Array = []): Promise> {\n if (await this.collapsed(row)) {\n return pathsofar;\n }\n const children = await this._getChildren(row);\n if (children.length === 0) {\n return pathsofar;\n }\n const child = children[children.length - 1];\n return [child].concat(await this.walkToLastVisible(child));\n }\n\n // a node is cloned only if it has multiple parents.\n // note that this may return false even if it appears multiple times in the display (if its ancestor is cloned)\n public async isClone(row: Row) {\n const parents = await this._getParents(row);\n if (parents.length < 2) { // for efficiency reasons\n return false;\n }\n const attachedParents = await fn_utils.asyncFilter(\n parents, async (parent) => await this.isAttached(parent));\n return attachedParents.length > 1;\n }\n\n // Figure out which is the canonical one. Right now this is really 'arbitraryInstance'\n // NOTE: this is not very efficient, in the worst case, but probably doesn't matter\n public async canonicalPath(row: Row): Promise {\n errors.assert(row !== null, 'Empty row passed to canonicalPath');\n if (row === Path.rootRow()) {\n return this.root;\n }\n const parents = await this._getParents(row);\n for (let i = 0; i < parents.length; i++) {\n const parentRow = parents[i];\n const canonicalParent = await this.canonicalPath(parentRow);\n if (canonicalParent !== null) {\n return canonicalParent.child(row);\n }\n }\n return null;\n }\n\n // Return all ancestor rows, topologically sorted (root is *last*).\n // Excludes 'row' itself unless options.inclusive is specified\n // NOTE: includes possibly detached nodes\n public async allAncestors(\n row: Row,\n { inclusive = false }: { inclusive?: boolean } = { }\n ) {\n const visited: {[row: number]: boolean} = {};\n const ancestors: Array = []; // 'visited' with preserved insert order\n if (inclusive) {\n ancestors.push(row);\n }\n const visit = async (n: Row) => { // DFS\n visited[n] = true;\n const parents = await this._getParents(n);\n for (let i = 0; i < parents.length; i++) {\n const parent = parents[i];\n if (!(parent in visited)) {\n ancestors.push(parent);\n await visit(parent);\n }\n }\n return null;\n };\n await visit(row);\n return ancestors;\n }\n\n public async _hasChild(parent_row: Row, row: Row) {\n const children = await this._getChildren(parent_row);\n const ci = _.findIndex(children, sib => sib === row);\n return ci !== -1;\n }\n\n private async _removeChild(parent_row: Row, row: Row) {\n const children = await this._getChildren(parent_row);\n const ci = _.findIndex(children, sib => sib === row);\n errors.assert(ci !== -1);\n children.splice(ci, 1);\n await this._setChildren(parent_row, children);\n\n const parents = await this._getParents(row);\n const pi = _.findIndex(parents, par => par === parent_row);\n parents.splice(pi, 1);\n await this._setParents(row, parents);\n\n const info = {\n parent_row,\n parent_index: pi,\n row,\n child_index: ci,\n };\n this.emit('childRemoved', info);\n return info;\n }\n\n private async _addChild(parent_row: Row, row: Row, index: number): Promise {\n const children = await this._getChildren(parent_row);\n errors.assert(index <= children.length);\n if (index === -1) {\n index = children.length;\n }\n children.splice(index, 0, row);\n await this._setChildren(parent_row, children);\n\n const parents = await this._getParents(row);\n parents.push(parent_row);\n await this._setParents(row, parents);\n const info = {\n parent_row,\n parent_index: parents.length - 1,\n row,\n child_index: index,\n };\n return info;\n }\n\n public async _detach(row: Row, parent_row: Row) {\n const wasLast = !(await this.isClone(row));\n\n await this.emitAsync('beforeDetach', { row, parent_row, last: wasLast });\n const info = await this._removeChild(parent_row, row);\n if (wasLast) {\n await this.store.setDetachedParent(row, parent_row);\n }\n await this.emitAsync('afterDetach', { row, parent_row, last: wasLast });\n return info;\n }\n\n public async _attach(child_row: Row, parent_row: Row, index = -1): Promise {\n const isFirst = (await this._getParents(child_row)).length === 0;\n await this.emitAsync('beforeAttach', { row: child_row, parent_row, first: isFirst});\n const info = await this._addChild(parent_row, child_row, index);\n const old_detached_parent = await this.store.getDetachedParent(child_row);\n if (old_detached_parent !== null) {\n errors.assert(isFirst);\n await this.store.setDetachedParent(child_row, null);\n }\n await this.emitAsync('afterAttach', { row: child_row, parent_row, first: isFirst, old_detached_parent});\n return info;\n }\n\n public async _move(child_row: Row, old_parent_row: Row, new_parent_row: Row, index = -1) {\n await this.emitAsync('beforeMove', {\n row: child_row, old_parent: old_parent_row, new_parent: new_parent_row,\n });\n\n const remove_info = await this._removeChild(old_parent_row, child_row);\n if ((old_parent_row === new_parent_row) && (index > remove_info.child_index)) {\n index = index - 1;\n }\n const add_info = await this._addChild(new_parent_row, child_row, index);\n\n await this.emitAsync('afterMove', {\n row: child_row, old_parent: old_parent_row, new_parent: new_parent_row,\n });\n\n return {\n old: remove_info,\n new: add_info,\n };\n }\n\n // attaches a detached child to a parent\n // the child should not have a parent already\n public async attachChild(parent: Path, child: Path, index = -1): Promise {\n return (await this.attachChildren(parent, [child], index))[0];\n }\n\n public async attachChildren(parent: Path, new_children: Array, index = -1): Promise> {\n await this._attachChildren(parent.row, new_children.map(x => x.row), index);\n // for child in new_children\n // child.setParent parent\n return new_children;\n }\n\n public async _attachChildren(parent: Row, new_children: Array, index = -1) {\n for (let i = 0; i < new_children.length; i++) {\n const child = new_children[i];\n await this._attach(child, parent, index);\n if (index >= 0) {\n index += 1;\n }\n }\n return null;\n }\n\n // given two paths, returns\n // 1. the common ancestor of the paths\n // 2. the array of ancestors between common ancestor and path1\n // 3. the array of ancestors between common ancestor and path2\n public async getCommonAncestor(path1: Path, path2: Path): Promise<[Path, Array, Array]> {\n const ancestors1: Array = path1.getAncestryPaths();\n const ancestors2: Array = path2.getAncestryPaths();\n\n const commonAncestry = _.takeWhile(\n _.zip(ancestors1, ancestors2),\n (pair: any) => (pair[0] && pair[1] && pair[0].is(pair[1]))\n ).map((pair) => pair[0]);\n\n const lastCommon = _.last(commonAncestry);\n if (lastCommon == null) {\n throw new Error(`No common ancestor found between ${path1} and ${path2}`);\n }\n const firstDifference = commonAncestry.length;\n return [lastCommon, ancestors1.slice(firstDifference), ancestors2.slice(firstDifference)];\n }\n\n // returns whether an row is actually reachable from the root node\n // if something is not detached, it will have a parent, but the parent wont mention it as a child\n public async isAttached(row: Row) {\n return (await this.allAncestors(row, {inclusive: true})).indexOf(this.root.row) !== -1;\n }\n\n public async isValidPath(path: Path) {\n let parent_row: Row = 0;\n const ancestry = path.getAncestry();\n for (let i = 0; i < ancestry.length; i++) {\n const row = ancestry[i];\n if (!await this.hasChild(parent_row, row)) {\n return false;\n }\n parent_row = row;\n }\n return true;\n }\n\n public async getSiblingBefore(path: Path) {\n return await this.getSiblingOffset(path, -1);\n }\n\n public async getSiblingAfter(path: Path) {\n return await this.getSiblingOffset(path, 1);\n }\n\n public async getSiblingOffset(path: Path, offset: number) {\n const arr = await this.getSiblingRange(path, offset, offset);\n if (!arr.length) {\n return null;\n }\n return arr[0];\n }\n\n public async getSiblingRange(path: Path, min_offset: number, max_offset: number) {\n const [ index, siblings ] = await Promise.all([\n this.indexInParent(path),\n this.getSiblings(path),\n ]);\n if (index + max_offset < 0) { return []; }\n const arr = fn_utils.getSlice(\n siblings,\n Math.max(index + min_offset, 0),\n index + max_offset,\n );\n return arr;\n }\n\n public async getChildRange(path: Path, min: number, max: number): Promise> {\n return (await this._getChildren(path.row, min, max)).map(function(child_row) {\n return path.child(child_row);\n });\n }\n\n public async newChild(path: Path, index = -1) {\n const { row } = await this._newChild(path.row, index);\n return path.child(row);\n }\n\n private async* traverseSubtree(root: Path): AsyncIterableIterator {\n const visited_rows: {[row: number]: boolean} = {};\n let that = this;\n\n async function* helper(path: Path): AsyncIterableIterator {\n if (path.row in visited_rows) {\n return;\n }\n visited_rows[path.row] = true;\n yield path;\n const children = await that.getChildren(path);\n for (let i = 0; i < children.length; i++) {\n yield* await helper(children[i]);\n }\n }\n yield* await helper(root);\n }\n\n public async search(root: Path, query: string, options: SearchOptions = {}) {\n const { nresults = 10, case_sensitive = false } = options;\n const results: Array<{\n path: Path,\n matches: Array,\n }> = []; // list of (path, index) pairs\n\n if (query.length === 0) {\n return results;\n }\n\n const canonicalize = (x: string) => case_sensitive ? x : x.toLowerCase();\n const query_words =\n query.split(/\\s/g).filter(x => x.length).map(canonicalize);\n\n const paths = this.traverseSubtree(root);\n for await (let path of paths) {\n const text = await this.getText(path.row);\n const line = canonicalize(text);\n const matches: Array = [];\n if (_.every(query_words.map((word) => {\n const index = line.indexOf(word);\n if (index === -1) { return false; }\n for (let j = index; j < index + word.length; j++) {\n matches.push(j);\n }\n return true;\n }))) {\n results.push({ path, matches });\n }\n if (nresults > 0 && results.length === nresults) {\n break;\n }\n }\n return results;\n }\n\n // important: serialized automatically garbage collects\n public async serializeRow(row = this.root.row): Promise {\n const text = await this.getText(row);\n\n const struct: SerializedLine = {\n text,\n };\n if (await this.collapsed(row)) {\n struct.collapsed = true;\n }\n const plugins = await this.applyHookAsync('serializeRow', {}, {row});\n if (Object.keys(plugins).length > 0) {\n struct.plugins = plugins;\n }\n\n return struct;\n }\n\n public async serialize(\n row = this.root.row,\n options: {pretty?: boolean} = {},\n serialized: {[row: number]: SerializedBlock} = {}\n ): Promise {\n if (row in serialized) {\n const clone_struct: any = serialized[row];\n clone_struct.id = row;\n return { clone: row };\n }\n\n const struct: any = await this.serializeRow(row);\n // NOTE: this must be done in order due to cloning\n // const children = await Promise.all((await this._getChildren(row)).map(\n // async (childrow) => await this.serialize(childrow, options, serialized)\n // ));\n const childRows = await this._getChildren(row);\n let children: Array = [];\n for (let i = 0; i < childRows.length; i++) {\n children.push(\n await this.serialize(childRows[i], options, serialized)\n );\n }\n if (children.length) {\n struct.children = children;\n }\n\n serialized[row] = struct;\n\n if (options.pretty) {\n if ((children.length === 0) &&\n (!await this.isClone(row)) &&\n (!struct.plugins)\n ) {\n return struct.text;\n }\n }\n return struct;\n }\n\n public async loadTo(\n // TODO: serialized is a SerializedBlock\n serialized: any, parent_path = this.root, index = -1,\n id_mapping: {[key: number]: Row} = {}, replace_empty = false\n ) {\n if (serialized.clone) {\n // NOTE: this assumes we load in the same order we serialize\n errors.assert(serialized.clone in id_mapping);\n const row = id_mapping[serialized.clone];\n const clone_path = parent_path.child(row);\n await this.attachChild(parent_path, clone_path, index);\n return clone_path;\n }\n\n const children = await this.getChildren(parent_path);\n // if parent_path has only one child and it's empty, delete it\n let path;\n if (replace_empty && children.length === 1 &&\n ((await this.getLine(children[0].row)).length === 0)) {\n path = children[0];\n } else {\n path = await this.newChild(parent_path, index);\n }\n\n if (typeof serialized === 'string') {\n await this.setLine(path.row, serialized.split(''));\n } else {\n if (serialized.id) {\n id_mapping[serialized.id] = path.row;\n }\n const line = serialized.text.split('');\n\n await Promise.all([\n this.setLine(path.row, line),\n this.setCollapsed(path.row, serialized.collapsed),\n ]);\n\n if (serialized.children) {\n for (let i = 0; i < serialized.children.length; i++) {\n const serialized_child = serialized.children[i];\n await this.loadTo(serialized_child, path, -1, id_mapping);\n }\n }\n }\n\n await this.emitAsync('loadRow', path, serialized.plugins || {});\n\n return path;\n }\n\n public async load(serialized_rows: Array) {\n const id_mapping = {};\n for (let i = 0; i < serialized_rows.length; i++) {\n const serialized_row = serialized_rows[i];\n await this.loadTo(serialized_row, this.root, -1, id_mapping, true);\n }\n }\n\n public async loadEmpty() {\n await this.load(['']);\n }\n}\n\nexport class InMemoryDocument extends Document {\n constructor() {\n super(new DocumentStore(new InMemory()));\n }\n}\n","/Users/jeffwu/projects/vimflowy/src/assets/ts/constants.ts",[],"/Users/jeffwu/projects/vimflowy/src/assets/ts/keyDefinitions.ts",[],"/Users/jeffwu/projects/vimflowy/src/assets/ts/session.ts",[],"/Users/jeffwu/projects/vimflowy/src/assets/ts/keyBindings.ts",[],"/Users/jeffwu/projects/vimflowy/src/assets/ts/utils/browser.ts",["267"],"/* Utilities for stuff related to being in the browser */\nimport $ from 'jquery';\nimport { saveAs } from 'file-saver';\n\n// needed for the browser checks\ndeclare var window: any;\n\n// TODO: get jquery typing to work?\nexport function scrollDiv($elem: any, amount: number) {\n // # animate. seems to not actually be great though\n // $elem.stop().animate({\n // scrollTop: $elem[0].scrollTop + amount\n // }, 50)\n return $elem.scrollTop($elem.scrollTop() + amount);\n}\n\n// TODO: get jquery typing to work?\nexport function scrollIntoView(el: Element, $within: any, margin: number = 0) {\n const elemTop = el.getBoundingClientRect().top;\n const elemBottom = el.getBoundingClientRect().bottom;\n\n const top_margin = margin;\n const bottom_margin = margin + ($('#bottom-bar').height() as number);\n\n if (elemTop < top_margin) {\n // scroll up\n return scrollDiv($within, elemTop - top_margin);\n } else if (elemBottom > window.innerHeight - bottom_margin) {\n // scroll down\n return scrollDiv($within, elemBottom - window.innerHeight + bottom_margin);\n }\n}\n\nexport function isScrolledIntoView($elem: any, $container: any) {\n const docViewTop = $container.offset().top;\n const docViewBottom = docViewTop + $container.outerHeight();\n\n const elemTop = $elem.offset().top;\n const elemBottom = elemTop + $elem.height();\n\n return ((elemBottom <= docViewBottom) && (elemTop >= docViewTop));\n}\n\nexport function getParameterByName(name: string) {\n name = name.replace(/[\\[\\]]/g, '\\\\$&');\n const regex = new RegExp('[?&]' + name + '(=([^&#]*)|&|#|$)');\n const results = regex.exec(window.location.href);\n if (!results) { return null; }\n if (!results[2]) { return ''; }\n return decodeURIComponent(results[2].replace(/\\+/g, ' '));\n}\n\nexport function downloadFile(filename: string, content: string, mimetype: string) {\n const blob = new Blob([content], {type: `${mimetype};charset=utf-8`});\n saveAs(blob, filename);\n}\n\n// SEE: http://stackoverflow.com/questions/9847580/how-to-detect-safari-chrome-ie-firefox-and-opera-browser\nexport function isOpera(): boolean {\n return !!window.opera || navigator.userAgent.indexOf(' OPR/') >= 0; // Opera 8.0+\n}\nexport function isSafari(): boolean {\n return Object.prototype.toString.call(window.HTMLElement).indexOf('Constructor') > 0; // Safari 3+\n}\nexport function isChrome(): boolean {\n return !!window.chrome && !isOpera; // Chrome 1+\n}\ndeclare var InstallTrigger: any;\nexport function isFirefox(): boolean {\n return typeof InstallTrigger !== 'undefined'; // Firefox 1.0+\n}\n\nexport function cancel(ev: Event) {\n ev.stopPropagation();\n ev.preventDefault();\n return false;\n}\n\nexport function mimetypeLookup(filename: string): string | undefined {\n const parts = filename.split('.');\n const extension = parts.length > 1 ? parts[parts.length - 1] : '';\n const extensionLookup: {[key: string]: string} = {\n 'json': 'application/json',\n 'txt': 'text/plain',\n '': 'text/plain',\n };\n return extensionLookup[extension.toLowerCase()];\n}\n\n","/Users/jeffwu/projects/vimflowy/src/shared/utils/logger.ts",[],"/Users/jeffwu/projects/vimflowy/src/shared/data_backend.ts",["268"],"import * as errors from './utils/errors';\n\n/*\nDataBackend abstracts the data layer, so that it can be swapped out.\nTo implement a new backend, one simply has to implement a simple key-value store\nwith the two methods get and set.\n\nNote that the backend may want to protect against multiple clients writing/reading.\n*/\n\nexport default class DataBackend {\n public async get(_key: string): Promise {\n throw new errors.NotImplemented();\n }\n\n public async set(_key: string, _value: string): Promise {\n throw new errors.NotImplemented();\n }\n}\n\nexport class SynchronousDataBackend {\n public get(_key: string): string | null {\n throw new errors.NotImplemented();\n }\n\n public set(_key: string, _value: string): void {\n throw new errors.NotImplemented();\n }\n}\n\nexport class SynchronousInMemory extends SynchronousDataBackend {\n private cache: {[key: string]: any} = {};\n constructor() {\n super();\n }\n\n public get(key: string): string | null {\n if (key in this.cache) {\n return this.cache[key];\n }\n return null;\n }\n\n public set(key: string, value: string): void {\n this.cache[key] = value;\n }\n}\n\nexport class InMemory extends DataBackend {\n private sync_backend: SynchronousInMemory;\n constructor() {\n super();\n this.sync_backend = new SynchronousInMemory();\n }\n\n public async get(key: string): Promise {\n return this.sync_backend.get(key);\n }\n\n public async set(key: string, value: string): Promise {\n this.sync_backend.set(key, value);\n }\n}\n\n","/Users/jeffwu/projects/vimflowy/src/shared/utils/errors.ts",["269","270"],"import * as _ from 'lodash';\n\nexport class ExtendableError extends Error {\n constructor(message: string) {\n super(message);\n this.name = this.constructor.name;\n this.stack = (new Error(message)).stack;\n }\n}\n\nexport class NotImplemented extends ExtendableError {\n constructor(m = '') {\n super(m ? `Not implemented: ${m}!` : 'Not implemented!');\n }\n}\n\nexport class UnexpectedValue extends ExtendableError {\n constructor(name: string, value: any) {\n super(`Unexpected value for \\`${name}\\`: ${value}`);\n }\n}\n\nexport class GenericError extends ExtendableError {\n constructor(m: string) { super(m); }\n}\n\n// error class for errors that we can reasonably expect to happen\n// e.g. bad user input, multiple users\n// is special because ignored by error handling in app.tsx\nexport class ExpectedError extends ExtendableError {\n constructor(m: string) { super(m); }\n}\n\n///////////\n// asserts\n///////////\n\nexport class AssertionError extends ExtendableError {\n constructor(m: string) {\n super(`Assertion error: ${m}`);\n }\n}\n\nexport function assert(a: boolean, message = 'assert error') {\n if (!a) {\n throw new AssertionError(`${message}\\nExpected ${a} to be true`);\n }\n}\n\nexport function assert_equals(a: any, b: any, message = 'assert_equals error') {\n if (a !== b) { throw new AssertionError(`${message}\\nExpected ${a} == ${b}`); }\n}\n\nexport function assert_not_equals(a: any, b: any, message = 'assert_not_equals error') {\n if (a === b) { throw new AssertionError(`${message}\\nExpected ${a} != ${b}`); }\n}\n\n// for asserting object equality\nexport function assert_deep_equals(a: any, b: any, message = 'assert_deep_equals error') {\n if (!_.isEqual(a, b)) {\n throw new AssertionError(`${message}\n \\nExpected:\n \\n${JSON.stringify(a, null, 2)}\n \\nBut got:\n \\n${JSON.stringify(b, null, 2)}\n `);\n }\n}\n\nexport function assert_arrays_equal(arr_a: Array, arr_b: Array) {\n const a_minus_b = _.difference(arr_a, arr_b);\n if (a_minus_b.length) { throw new AssertionError(`Arrays not same, first contains: ${a_minus_b}`); }\n const b_minus_a = _.difference(arr_b, arr_a);\n if (b_minus_a.length) { throw new AssertionError(`Arrays not same, second contains: ${b_minus_a}`); }\n}\n","/Users/jeffwu/projects/vimflowy/src/assets/ts/configurations/vim.ts",["271","272","273","274","275","276"],"import * as _ from 'lodash';\n\nimport { SerializedBlock } from '../types';\nimport KeyMappings, { HotkeyMapping } from '../keyMappings';\nimport { motionKey } from '../keyDefinitions';\nimport { SINGLE_LINE_MOTIONS } from '../definitions/motions';\nimport Config from '../config';\n\n// TODO: 'next-sentence': [[')']]\n// TODO: 'prev-sentence': [['(']]\n\nexport const NORMAL_MOTION_MAPPINGS: HotkeyMapping = {\n 'motion-left': [['left'], ['h']],\n 'motion-right': [['right'], ['l']],\n 'motion-up': [['up'], ['k']],\n 'motion-down': [['down'], ['j']],\n 'motion-line-beginning': [['home'], ['0'], ['^']],\n 'motion-line-end': [['end'], ['$']],\n 'motion-word-beginning': [['b']],\n 'motion-word-end': [['e']],\n 'motion-word-next': [['w']],\n 'motion-Word-beginning': [['B']],\n 'motion-Word-end': [['E']],\n 'motion-Word-next': [['W']],\n\n 'motion-visible-beginning': [['g', 'g']],\n 'motion-visible-end': [['G'], ['g', 'G']],\n 'motion-parent': [['g', 'p']],\n 'motion-next-sibling': [['}']],\n 'motion-prev-sibling': [['{']],\n // NOTE: should these work in insert mode also?\n 'motion-find-next-char': [['f']],\n 'motion-find-prev-char': [['F']],\n 'motion-to-next-char': [['t']],\n 'motion-to-prev-char': [['T']],\n};\n\nexport const INSERT_MOTION_MAPPINGS: HotkeyMapping = {\n 'motion-left': [['left']],\n 'motion-right': [['right']],\n 'motion-up': [['up']],\n 'motion-down': [['down']],\n 'motion-line-beginning': [['home'], ['ctrl+a'], ['meta+left']],\n 'motion-line-end': [['end'], ['ctrl+e'], ['meta+right']],\n 'motion-word-beginning': [['alt+b'], ['alt+left']],\n 'motion-word-end': [],\n 'motion-word-next': [['alt+f'], ['alt+right']],\n 'motion-Word-beginning': [],\n 'motion-Word-end': [],\n 'motion-Word-next': [],\n\n 'motion-visible-beginning': [['meta+home']],\n 'motion-visible-end': [['meta+end']],\n 'motion-parent': [['ctrl+g', 'p']],\n 'motion-next-sibling': [['alt+down']],\n 'motion-prev-sibling': [['alt+up']],\n};\n\nexport const NORMAL_MODE_MAPPINGS: HotkeyMapping = Object.assign({\n 'move-cursor-normal': [[motionKey]],\n 'toggle-help': [['?']],\n 'enter-visual-mode': [['v']],\n 'enter-visual-line-mode': [['V']],\n 'enter-insert-before-cursor': [['i']],\n 'enter-insert-after-cursor': [['a']],\n 'enter-insert-line-beginning': [['I']],\n 'enter-insert-line-end': [['A']],\n 'enter-insert-below-line': [['o']],\n 'enter-insert-above-line': [['O']],\n 'visit-link': [['g', 'x']],\n 'fold-toggle': [['z']],\n 'fold-open': [],\n 'fold-close': [],\n 'replace-char': [['r']],\n 'delete-blocks': [['d', 'd']],\n 'delete-motion': [['d', motionKey]],\n 'change-line': [['c', 'c']],\n 'change-to-line-end': [['C']],\n 'change-blocks': [['c', 'r']],\n 'change-motion': [['c', motionKey]],\n 'yank-line': [['y', 'y']],\n 'yank-to-line-end': [['Y']],\n 'yank-blocks': [['y', 'r']],\n 'yank-motion': [['y', motionKey]],\n 'yank-clone': [['y', 'c']],\n 'normal-delete-char': [['x']],\n 'normal-delete-char-before': [['X']],\n 'change-char': [['s']],\n 'delete-to-line-beginning': [],\n 'delete-to-line-end': [['D']],\n 'delete-to-word-beginning': [],\n 'paste-after': [['p']],\n 'paste-before': [['P']],\n 'join-line': [['J']],\n 'split-line': [['K']],\n 'scroll-down': [['page down'], ['ctrl+d']],\n 'scroll-up': [['page up'], ['ctrl+u']],\n 'undo': [['u']],\n 'redo': [['ctrl+r']],\n 'replay-command': [['.']],\n 'record-macro': [['q']],\n 'play-macro': [['@']],\n 'unindent-row': [['<']],\n 'indent-row': [['>']],\n 'unindent-blocks': [['shift+tab'], ['ctrl+h']],\n 'indent-blocks': [['tab'], ['ctrl+l']],\n 'swap-block-down': [['ctrl+j']],\n 'swap-block-up': [['ctrl+k']],\n 'search-local': [['ctrl+/'], ['ctrl+f']],\n 'search-global': [['/']],\n 'export-file': [['ctrl+s']],\n 'zoom-prev-sibling': [['alt+k']],\n 'zoom-next-sibling': [['alt+j']],\n 'zoom-in': [[']'], ['alt+l'], ['ctrl+right']],\n 'zoom-out': [['['], ['alt+h'], ['ctrl+left']],\n 'zoom-cursor': [['enter'], ['ctrl+shift+right']],\n 'zoom-root': [['shift+enter'], ['ctrl+shift+left']],\n 'jump-prev': [['ctrl+o']],\n 'jump-next': [['ctrl+i']],\n 'swap-case': [['~']],\n 'go-next-clone': [['g', 'c']],\n}, NORMAL_MOTION_MAPPINGS);\n\nexport const VISUAL_MODE_MAPPINGS: HotkeyMapping = Object.assign({\n 'move-cursor-visual': [[motionKey]],\n 'toggle-help': [['?']],\n 'exit-mode': [['esc'], ['ctrl+c'], ['ctrl+[']],\n 'swap-visual-cursor': [['o'], ['O']],\n 'visual-delete': [['d'], ['x']],\n 'visual-change': [['c']],\n 'visual-yank': [['y']],\n 'visual-swap-case': [['~']],\n}, _.pick(NORMAL_MOTION_MAPPINGS, SINGLE_LINE_MOTIONS));\n\nexport const VISUAL_LINE_MODE_MAPPINGS: HotkeyMapping = Object.assign({\n 'move-cursor-visual-line': [[motionKey]],\n 'toggle-help': [['?']],\n 'exit-mode': [['esc'], ['ctrl+c'], ['ctrl+[']],\n 'swap-visual-cursor': [['o'], ['O']],\n 'visual-line-delete': [['d'], ['x']],\n 'visual-line-change': [['c']],\n 'visual-line-join': [['J']],\n 'visual-line-yank': [['y']],\n 'visual-line-yank-clone': [['Y']],\n 'visual-line-indent': [['>'], ['tab'], ['ctrl+l']],\n 'visual-line-unindent': [['<'], ['shift+tab'], ['ctrl+h']],\n 'visual-line-swap-case': [['~']],\n}, NORMAL_MOTION_MAPPINGS);\n\nexport const INSERT_MODE_MAPPINGS: HotkeyMapping = Object.assign({\n 'move-cursor-insert': [[motionKey]],\n 'toggle-help': [['ctrl+?']],\n 'exit-mode': [['esc'], ['ctrl+c'], ['ctrl+[']],\n 'fold-toggle': [['ctrl+space']],\n 'fold-open': [['meta+down']],\n 'fold-close': [['meta+up']],\n 'delete-blocks': [['meta+shift+delete']],\n 'delete-char-after': [['delete']],\n 'delete-char-before': [['backspace'], ['shift+backspace']],\n 'delete-to-line-beginning': [['ctrl+u']],\n 'delete-to-line-end': [['ctrl+k']],\n 'delete-to-word-beginning': [['ctrl+w'], ['ctrl+backspace']],\n // NOTE: paste-after doesn't make much sense for insert mode\n 'paste-before': [['ctrl+y']],\n 'split-line': [['enter']],\n 'scroll-down': [['page down']],\n 'scroll-up': [['page up']],\n 'undo': [['ctrl+z']],\n 'redo': [['ctrl+Z']],\n 'unindent-row': [],\n 'indent-row': [],\n 'unindent-blocks': [['shift+tab']],\n 'indent-blocks': [['tab']],\n 'swap-block-down': [],\n 'swap-block-up': [],\n 'zoom-prev-sibling': [['alt+k']],\n 'zoom-next-sibling': [['alt+j']],\n 'zoom-in': [['ctrl+right']],\n 'zoom-out': [['ctrl+left']],\n 'zoom-cursor': [['ctrl+shift+right']],\n 'zoom-root': [['ctrl+shift+left']],\n}, INSERT_MOTION_MAPPINGS);\n\nexport const SEARCH_MODE_MAPPINGS: HotkeyMapping = Object.assign({\n 'move-cursor-search': [[motionKey]],\n 'toggle-help': [['ctrl+?']],\n 'exit-mode': [['esc'], ['ctrl+c'], ['ctrl+[']],\n 'search-delete-char-after': [['delete']],\n 'search-delete-char-before': [['backspace'], ['shift+backspace']],\n 'search-select': [['enter']],\n 'search-up': [['ctrl+k'], ['up'], ['shift+tab']],\n 'search-down': [['ctrl+j'], ['down'], ['tab']],\n}, _.pick(INSERT_MOTION_MAPPINGS, SINGLE_LINE_MOTIONS));\n\nexport const SETTINGS_MODE_MAPPINGS: HotkeyMapping = {\n 'exit-mode': [['esc'], ['ctrl+c'], ['ctrl+[']],\n};\n\nexport const WORKFLOWY_MODE_MAPPINGS: HotkeyMapping = Object.assign({\n 'move-cursor-insert': [[motionKey]],\n 'toggle-help': [['ctrl+?'], ['meta+?']],\n 'fold-toggle': [],\n 'fold-open': [['meta+down']],\n 'fold-close': [['meta+up']],\n 'delete-blocks': [['meta+shift+delete']],\n 'delete-char-after': [['delete']],\n 'delete-char-before': [['backspace'], ['shift+backspace']],\n 'delete-to-line-beginning': [['ctrl+u']],\n 'delete-to-line-end': [['ctrl+k']],\n 'delete-to-word-beginning': [['ctrl+w'], ['ctrl+backspace']],\n // NOTE: paste-after doesn't make much sense for insert mode\n 'paste-before': [['ctrl+y']],\n 'split-line': [['enter']],\n 'scroll-down': [['page down']],\n 'scroll-up': [['page up']],\n 'undo': [['ctrl+z'], ['meta+z']],\n 'redo': [['ctrl+Z'], ['meta+Z'], ['meta+y']],\n 'unindent-row': [],\n 'indent-row': [],\n 'unindent-blocks': [['shift+tab']],\n 'indent-blocks': [['tab']],\n 'swap-block-down': [['meta+shift+up']],\n 'swap-block-up': [['meta+shift+down']],\n 'zoom-prev-sibling': [],\n 'zoom-next-sibling': [],\n 'zoom-in': [],\n 'zoom-out': [['meta+<']],\n 'zoom-cursor': [['meta+>']],\n 'zoom-root': [],\n}, INSERT_MOTION_MAPPINGS);\n\nfunction getDefaultData(): Array {\n return [\n 'Welcome to vimflowy!',\n 'I hope you know to use j and k to move up and down!',\n 'If not, the cheat sheet on the right will be your friend. Once you become an expert, you\\'ll know to use ? to hide it',\n { text: 'Features', children: [\n { text: 'Workflowy features', children: [\n { text: 'Nested bullets', children: [\n 'Bullets with children can be collapsed to avoid clutter',\n { text: 'Use enter to zoom into any bullet. Try on this one', collapsed: true, children: [\n 'This bullet had children, but you can zoom into bullets with no children to start expanding upon them',\n 'Use shift+enter to zoom all the way back out',\n 'Use ] and [ to zoom in and out just one level',\n ] },\n { text: 'Use z to toggle collapsedness', collapsed: true, children: [\n 'You found me :)',\n ] },\n 'Use tab and shift+tab to indent and unindent blocks',\n 'Use < and > to indent and unindent just a single line',\n ] },\n ] },\n { text: 'Vim features', collapsed: true, children: [\n 'Most of vim\\'s movement commands',\n 'Modal editing (note visual mode only works on one line)',\n 'Undo/redo, jump history (ctrl+o to jump to previous location, ctrl+i to jump forward)',\n 'Repeat commands with . and record macros with q',\n 'Let the vimflowy devs know if anything major is missing',\n ] },\n 'Press / to start searching for text',\n { text: 'Marks', plugins: { mark: 'mark' }, collapsed: true, children: [\n { text: 'I am marked!', plugins: { mark: 'im_a_mark' } },\n 'Press m to start marking a line, and enter to finish',\n 'Use \\' to search and jump to marks',\n 'Link to marks with the @ symbol, like this: @im_a_mark. Use gm to follow the link.',\n 'Delete marks by using dm, or just mark with empty string',\n ] },\n { text: 'Cloning', collapsed: true, children: [\n { text: 'I am a clone! Try editing me', id: 1 },\n { text: 'Clones can\\'t be siblings or descendants of each other', children: [\n { clone: 1 },\n ] },\n 'Make new clones with yc, then p',\n ] },\n { text: 'Rich text', collapsed: true, children: [\n { text: 'Text formatting', collapsed: true, children: [\n '**Bold**, *italicized*, and _underlined_ text. ** * _Emphatic_ * **!',\n {\n text: 'Strike through',\n children: [\n '~~Cross thing off todo list~~',\n 'Cross another thing off todo list',\n ],\n },\n ] },\n { text: 'LaTeX', collapsed: true, children: [\n 'Inline equations: $E = mc^2$ and $f(b) - f(a) = \\\\int_a^b f\\'(t) dt$',\n 'Block equations: $$\\\\max_{x \\\\ge 0, Ax \\\\le b} c^T x = \\\\min_{y \\\\ge 0, A^ty \\\\ge c} b^T y$$',\n ] },\n { text: 'HTML', collapsed: true, children: [\n 'Inline arbitrary HTML, such as images: ',\n 'Or tables:
ProsCons
EverythingNothing
',\n 'Or colored text',\n ] },\n ] },\n { text: 'Customizability (see Settings menu)', collapsed: true, children: [\n { text: 'Plugins system', collapsed: true, children: [\n 'If you\\'re interested in writing plugins, see here: ' +\n 'https://github.com/WuTheFWasThat/vimflowy/blob/master/docs/plugins.md',\n ] },\n 'Customizable hotkeys (via downloading/uploading a json file)',\n 'Customizable color theme',\n ] },\n ] },\n { text: 'Data', collapsed: true, children: [\n { text: 'Backing storage', children: [\n 'Vimflowy was designed to be agnostic to the storage backend',\n 'As a user, you are in full control of your data',\n 'By default, all data is entirely local',\n 'There are no backups, and it is never sent over the internet',\n 'However, remote data storage is supported',\n 'For more details, visit https://github.com/WuTheFWasThat/vimflowy/blob/master/docs/storage/README.md',\n 'To manage your data, visit the Settings menu',\n ] },\n { text: 'Importing and exporting data', children: [\n 'Two import and export formats are supported.',\n 'Check out settings for more information.',\n 'You can regularly export your data in JSON format, as a form of backup',\n ] },\n 'To make a new document with separate data, just add the \"doc\" query parameter. ' +\n `For example, ${window.location.origin}/?doc=newdocname#`,\n ] },\n 'Press i to enter insert mode and start adding your own content!',\n 'For more info, visit https://github.com/WuTheFWasThat/vimflowy (visit links under the cursor with gx)',\n ];\n}\n\nconst config: Config = {\n defaultMode: 'NORMAL',\n getDefaultData: getDefaultData,\n // TODO: get the keys from modes.ts\n defaultMappings:\n new KeyMappings({\n [ 'NORMAL' ]: NORMAL_MODE_MAPPINGS,\n [ 'INSERT' ]: INSERT_MODE_MAPPINGS,\n [ 'VISUAL' ]: VISUAL_MODE_MAPPINGS,\n [ 'VISUAL_LINE' ]: VISUAL_LINE_MODE_MAPPINGS,\n [ 'SEARCH' ]: SEARCH_MODE_MAPPINGS,\n [ 'SETTINGS' ]: SETTINGS_MODE_MAPPINGS,\n }),\n};\nexport default config;\n","/Users/jeffwu/projects/vimflowy/src/assets/ts/components/app.tsx",["277"],"import * as React from 'react';\n\nimport * as browser_utils from '../utils/browser';\nimport * as errors from '../../../shared/utils/errors';\nimport * as Modes from '../modes';\nimport { BackendType } from '../data_backend';\nimport { PluginsManager } from '../plugins';\nimport Session from '../session';\nimport Config from '../config';\nimport KeyBindings from '../keyBindings';\nimport { getStyles } from '../themes';\n\nimport SettingsComponent from './settings';\nimport SessionComponent from './session';\nimport MenuComponent from './menu';\nimport HotkeysTableComponent from './hotkeysTable';\n\nexport type TextMessage = { message?: string, text_class?: string };\n\ntype Props = {\n pluginManager: PluginsManager;\n session: Session;\n config: Config;\n message: TextMessage | null;\n saveMessage: TextMessage | null;\n showingKeyBindings: boolean;\n keyBindings: KeyBindings;\n initialBackendType: BackendType;\n error: Error | null;\n};\n\nexport default class AppComponent extends React.Component {\n public render() {\n if (this.props.error !== null) {\n const wasExpected = this.props.error instanceof errors.ExpectedError;\n\n let message;\n if (wasExpected) {\n message = (\n
\n {this.props.error.message}\n
\n );\n } else {\n message = (\n
\n An unexpected error was caught!\n
\n
\n Please help out Vimflowy and report the bug.\n Report the issue {' '}\n \n here\n \n {' '} with:\n
    \n
  • \n a description of what you did\n
  • \n
  • \n a copy of the Javascript console output (ideally, but be careful if data privacy is important)\n
  • \n
  • \n a copy of the following error message\n
  • \n
\n

\n Error:\n

\n
\n              {this.props.error.message}\n              
\n
\n {this.props.error.stack}\n
\n
\n Refresh the page to continue.\n
\n );\n }\n\n return (\n
\n {message}\n
\n );\n }\n const pluginManager = this.props.pluginManager;\n const session = this.props.session;\n const keyBindings = this.props.keyBindings;\n const settingsMode = session.mode === 'SETTINGS';\n const userMessage: TextMessage = this.props.message || {};\n const saveMessage: TextMessage = this.props.saveMessage || {};\n\n return (\n
\n {/* hack for firefox paste */}\n
\n
\n\n
\n \n\n
\n {/* NOTE: maybe always showing session would be nice?\n * Mostly works to never have 'hidden',\n * but would be cool if it mirrored selected search result\n */}\n \n
\n\n \n \n
\n
\n\n
\n\n {\n // TODO: this doesn't actually update everything.. use a better way to do this?\n // See: https://github.com/facebook/react/issues/3038\n // Maybe could do session.document.cache.clear(), but that's inefficient\n this.forceUpdate();\n }}\n onExport={() => {\n const filename = 'vimflowy_hotkeys.json';\n const content = JSON.stringify(keyBindings.mappings.serialize(), null, 2);\n browser_utils.downloadFile(filename, content, 'application/json');\n session.showMessage(`Downloaded hotkeys to ${filename}!`, {text_class: 'success'});\n }}\n />\n
\n\n \n \n );\n }\n}\n","/Users/jeffwu/projects/vimflowy/src/assets/ts/utils/eventEmitter.ts",[],"/Users/jeffwu/projects/vimflowy/src/assets/ts/utils/queue.ts",[],"/Users/jeffwu/projects/vimflowy/src/assets/ts/utils/functional.ts",[],"/Users/jeffwu/projects/vimflowy/src/assets/ts/themes.ts",[],"/Users/jeffwu/projects/vimflowy/src/assets/ts/utils/text.ts",[],"/Users/jeffwu/projects/vimflowy/src/assets/ts/mutations.ts",[],"/Users/jeffwu/projects/vimflowy/src/assets/ts/cursor.ts",[],"/Users/jeffwu/projects/vimflowy/src/plugins/index.ts",[],"/Users/jeffwu/projects/vimflowy/src/assets/ts/definitions/index.ts",[],"/Users/jeffwu/projects/vimflowy/src/assets/ts/data_backend/index.ts",["278"],"import firebase from 'firebase';\n// import \"firebase/auth\";\n\nimport EventEmitter from '../utils/eventEmitter';\nimport DataBackend, { SynchronousDataBackend } from '../../../shared/data_backend';\nimport { ExtendableError } from '../../../shared/utils/errors';\nimport logger from '../../../shared/utils/logger';\n\nexport type BackendType = 'local' | 'firebase' | 'inmemory' | 'socketserver';\n\nexport class MultipleUsersError extends ExtendableError {\n constructor() { super(\n 'This document has been modified (in another tab) since opening it in this tab. Please refresh to continue!'\n ); }\n}\n\n// NOTE: not very elegant, but this won't collide with other keys\n// since prefix always contains either '*save' or 'settings:'.\n// Future backends don't need to use this, as long as they prefix the key passed to them.\n// Backends can prefix internal usage with internalPrefix to avoid namespace collision.\nconst internalPrefix: string = 'internal:';\n\nexport class SynchronousLocalStorageBackend extends SynchronousDataBackend {\n constructor() {\n super();\n }\n\n public get(key: string): string | null {\n const val = localStorage.getItem(key);\n if ((val == null) || (val === 'undefined')) {\n return null;\n }\n return val;\n }\n\n public set(key: string, value: string): void {\n return localStorage.setItem(key, value);\n }\n}\n\nexport class LocalStorageBackend extends DataBackend {\n private lastSave: number;\n private docname: string;\n private sync_backend: SynchronousLocalStorageBackend;\n\n private _lastSaveKey_(): string {\n return `${internalPrefix}${this.docname}:lastSave`;\n }\n\n constructor(docname = '') {\n super();\n this.docname = docname;\n this.lastSave = Date.now();\n this.sync_backend = new SynchronousLocalStorageBackend();\n }\n\n public async get(key: string): Promise {\n return this.sync_backend.get(key);\n }\n\n public async set(key: string, value: string): Promise {\n if (this.getLastSave() > this.lastSave) {\n throw new MultipleUsersError();\n }\n this.lastSave = Date.now();\n this.sync_backend.set(this._lastSaveKey_(), this.lastSave + '');\n this.sync_backend.set(key, value);\n }\n\n // determine last time saved (for multiple tab detection)\n // note that this doesn't cache!\n public getLastSave(): number {\n return JSON.parse(this.sync_backend.get(this._lastSaveKey_()) || '0');\n }\n}\n\nexport class FirebaseBackend extends DataBackend {\n public events: EventEmitter = new EventEmitter();\n\n private fbase: firebase.database.Database;\n private numPendingSaves: number = 0;\n private docname: string;\n\n constructor(docname = '', dbName: string, apiKey: string) {\n super();\n this.docname = docname;\n this.fbase = firebase.initializeApp({\n apiKey: apiKey,\n databaseURL: `https://${dbName}.firebaseio.com`,\n }).database();\n // this.fbase.authWithCustomToken(token, (err, authdata) => {})\n }\n\n public async init(email: string, password: string) {\n this.events.emit('saved');\n\n await this.auth(email, password);\n\n const clientId = Date.now() + '-' + ('' + Math.random()).slice(2);\n const lastClientRef = this.fbase.ref(`${internalPrefix}${this.docname}:lastClient`);\n\n await lastClientRef.set(clientId);\n\n // Number of online users is the number of objects in the presence list.\n lastClientRef.on('value', function(snap) {\n if (snap == null) {\n throw new Error('Failed to get listRef');\n }\n if (snap.val() !== clientId) {\n throw new MultipleUsersError();\n }\n });\n }\n\n public async auth(email: string, password: string) { // : Promise\n try {\n let credential = await firebase.auth().signInWithEmailAndPassword(email, password);\n logger.info('Authenticated against Firebase.');\n return credential;\n } catch (x) {\n logger.error('Authentication against Firebase failed: ' + x.code + ': ' + x.message);\n return;\n }\n }\n\n public get(key: string): Promise {\n logger.debug('Firebase: getting', key);\n return new Promise((resolve: (result: string | null) => void, reject) => {\n this.fbase.ref(key).once(\n 'value',\n (data) => {\n const exists = data.exists();\n if (!exists) {\n return resolve(null);\n }\n return resolve(data.val());\n },\n (err: Error) => {\n return reject(err);\n }\n );\n });\n }\n\n // TODO: make this set proper, and do the pending thing elsewhere\n // same with for socket backend\n public set(key: string, value: string): Promise {\n if (this.numPendingSaves === 0) {\n this.events.emit('unsaved');\n }\n logger.debug('Firebase: setting', key, 'to', value);\n this.numPendingSaves++;\n // TODO: buffer these and batch them?\n this.fbase.ref(key).set(\n value,\n (err) => {\n if (err) { throw err; }\n this.numPendingSaves--;\n if (this.numPendingSaves === 0) {\n this.events.emit('saved');\n }\n }\n );\n return Promise.resolve();\n }\n}\n\nexport class ClientSocketBackend extends DataBackend {\n public events: EventEmitter = new EventEmitter();\n private numPendingSaves: number = 0;\n private callback_table: {[id: string]: (result: any) => void} = {};\n\n // init is like async constructor\n private ws!: WebSocket;\n private clientId: string;\n\n constructor() {\n super();\n this.clientId = Date.now() + '-' + ('' + Math.random()).slice(2);\n }\n\n private async connect(host: string, password: string, docname: string) {\n logger.info('Trying to connect', host);\n this.ws = new WebSocket(`${host}/socket`);\n this.ws.onerror = () => {\n // throw new Error(`Socket connection error: ${err}`);\n logger.info('Socket connection error!');\n };\n this.ws.onclose = () => {\n // throw new Error('Socket connection closed!');\n logger.info('Socket connection closed! Trying to reconnect...');\n setTimeout(() => {\n this.connect(host, password, docname);\n }, 5000);\n };\n\n await new Promise((resolve, reject) => {\n this.ws.onopen = resolve;\n setTimeout(() => {\n reject('Timed out trying to connect!');\n }, 5000);\n });\n logger.info('Connected', host);\n\n this.ws.onmessage = (event) => {\n // tslint:disable-next-line no-console\n const message = JSON.parse(event.data);\n if (message.type === 'callback') {\n const id: string = message.id;\n if (!(id in this.callback_table)) {\n throw new Error(`ID ${id} not found in callback table`);\n }\n const callback = this.callback_table[id];\n delete this.callback_table[id];\n callback(message.result);\n } else if (message.type === 'joined') {\n if (message.docname === docname) {\n if (message.clientId !== this.clientId) {\n throw new MultipleUsersError();\n }\n }\n }\n };\n\n await this.sendMessage({\n type: 'join',\n password: password,\n docname: docname,\n });\n }\n\n public async init(host: string, password: string, docname = '') {\n this.events.emit('saved');\n await this.connect(host, password, docname);\n }\n\n private async sendMessage(message: Object): Promise {\n return new Promise((resolve: (result: string | null) => void, reject) => {\n const id = Date.now() + '-' + ('' + Math.random()).slice(2);\n if (id in this.callback_table) { throw new Error('Duplicate IDs!?'); }\n this.callback_table[id] = (result) => {\n if (result.error) {\n reject(result.error);\n } else {\n resolve(result.value);\n }\n };\n this.ws.send(JSON.stringify({\n ...message,\n id: id,\n clientId: this.clientId\n }));\n });\n }\n\n public async get(key: string): Promise {\n logger.debug('Socket client: getting', key);\n return await this.sendMessage({\n type: 'get',\n key: key,\n });\n }\n\n public set(key: string, value: string): Promise {\n if (this.numPendingSaves === 0) {\n this.events.emit('unsaved');\n }\n logger.debug('Socket client: setting', key, 'to', value);\n this.numPendingSaves++;\n\n this.sendMessage({\n type: 'set',\n key: key,\n value: value,\n }).then(() => {\n this.numPendingSaves--;\n if (this.numPendingSaves === 0) {\n this.events.emit('saved');\n }\n });\n return Promise.resolve();\n }\n}\n","/Users/jeffwu/projects/vimflowy/src/assets/ts/definitions/motions.ts",[],"/Users/jeffwu/projects/vimflowy/src/assets/ts/components/session.tsx",[],"/Users/jeffwu/projects/vimflowy/src/assets/ts/components/hotkeysTable.tsx",[],"/Users/jeffwu/projects/vimflowy/src/assets/ts/components/settings.tsx",[],"/Users/jeffwu/projects/vimflowy/src/assets/ts/components/menu.tsx",[],"/Users/jeffwu/projects/vimflowy/src/assets/ts/definitions/history.ts",[],"/Users/jeffwu/projects/vimflowy/src/assets/ts/definitions/indent.ts",[],"/Users/jeffwu/projects/vimflowy/src/assets/ts/definitions/zoom.ts",[],"/Users/jeffwu/projects/vimflowy/src/assets/ts/definitions/basics.ts",[],"/Users/jeffwu/projects/vimflowy/src/assets/ts/definitions/meta.ts",[],"/Users/jeffwu/projects/vimflowy/src/assets/ts/definitions/menu.tsx",[],"/Users/jeffwu/projects/vimflowy/src/assets/ts/menu.ts",[],"/Users/jeffwu/projects/vimflowy/src/assets/ts/components/breadcrumbs.tsx",[],"/Users/jeffwu/projects/vimflowy/src/assets/ts/components/block.tsx",["279"],"import * as React from 'react';\n\nimport LineComponent, { LineProps } from './line';\nimport Spinner from './spinner';\n\nimport Session from '../session';\nimport { CachedRowInfo } from '../document';\nimport Path from '../path';\nimport { CursorsInfoTree } from '../cursor';\nimport { Col } from '../types';\nimport { PartialUnfolder, Token } from '../utils/token_unfolder';\nimport { getStyles } from '../themes';\n\ntype RowProps = {\n session: Session;\n path: Path;\n cached: CachedRowInfo;\n onCharClick: ((path: Path, column: Col, e: Event) => void) | undefined;\n onClick: ((path: Path) => void) | undefined;\n style: React.CSSProperties;\n cursorsTree: CursorsInfoTree;\n cursorBetween: boolean;\n};\nclass RowComponent extends React.Component {\n private onClick: (() => void) | undefined = undefined;\n private onCharClick: ((column: Col, e: Event) => void) | undefined = undefined;\n\n constructor(props: RowProps) {\n super(props);\n this.init(props);\n }\n\n private init(props: RowProps) {\n if (props.onClick) {\n this.onClick = () => {\n if (!props.onClick) {\n throw new Error('onClick disappeared');\n }\n props.onClick(props.path);\n };\n }\n\n if (props.onCharClick) {\n this.onCharClick = (column: Col, e: Event) => {\n if (!props.onCharClick) {\n throw new Error('onCharClick disappeared');\n }\n props.onCharClick(props.path, column, e);\n };\n }\n }\n\n public componentWillReceiveProps(props: RowProps) {\n this.init(props);\n }\n\n public render() {\n const session = this.props.session;\n const path = this.props.path;\n const lineData = this.props.cached.line;\n const cursorsTree = this.props.cursorsTree;\n\n const cursors: {[col: number]: boolean} = {};\n let has_cursor = false;\n const highlights: {[col: number]: boolean} = {};\n let has_highlight = false;\n\n if (cursorsTree.cursor != null) {\n cursors[cursorsTree.cursor] = true;\n has_cursor = true;\n }\n // TODO: Object.keys alternative that returns Array?\n (Object.keys(cursorsTree.selected)).forEach((col: any) => {\n highlights[col] = true;\n has_highlight = true;\n });\n // TODO: React.ReactNode vs React.ReactElement?\n const results: Array = [];\n\n let lineoptions: LineProps = {\n lineData,\n cursors,\n cursorStyle: getStyles(session.clientStore, ['theme-cursor']),\n highlights,\n highlightStyle: getStyles(session.clientStore, ['theme-bg-highlight']),\n linksStyle: getStyles(session.clientStore, ['theme-link']),\n accentStyle: getStyles(session.clientStore, ['theme-text-accent']),\n cursorBetween: this.props.cursorBetween,\n };\n\n const hooksInfo = {\n path, pluginData: this.props.cached.pluginData,\n has_cursor, has_highlight\n };\n\n lineoptions.lineHook = PartialUnfolder.trivial();\n lineoptions.lineHook = session.applyHook(\n 'renderLineTokenHook', lineoptions.lineHook, hooksInfo\n );\n\n lineoptions.wordHook = PartialUnfolder.trivial();\n lineoptions.wordHook = session.applyHook(\n 'renderWordTokenHook', lineoptions.wordHook, hooksInfo\n );\n\n lineoptions = session.applyHook('renderLineOptions', lineoptions, hooksInfo);\n let lineContents = [\n ,\n ];\n lineContents = session.applyHook('renderLineContents', lineContents, hooksInfo);\n results.push(...lineContents);\n\n const infoChildren = session.applyHook('renderAfterLine', [], hooksInfo);\n\n return (\n
\n {results}\n {infoChildren}\n
\n );\n }\n}\n\ntype BlockProps = {\n session: Session;\n path: Path;\n\n cached: CachedRowInfo | null;\n cursorsTree: CursorsInfoTree;\n cursorBetween: boolean;\n onCharClick: ((path: Path, column: Col, e: Event) => void) | undefined;\n onLineClick: ((path: Path) => void) | undefined;\n onBulletClick: ((path: Path) => void) | undefined;\n topLevel: boolean;\n fetchData: () => void;\n};\nexport default class BlockComponent extends React.Component {\n\n constructor(props: BlockProps) {\n super(props);\n }\n\n public shouldComponentUpdate(nextProps: BlockProps) {\n if (this.props.cursorsTree.hasSelection) {\n return true;\n }\n if (nextProps.cursorsTree.hasSelection) {\n return true;\n }\n if (nextProps.topLevel !== this.props.topLevel) {\n return true;\n }\n if (nextProps.cached !== this.props.cached) {\n return true;\n }\n if (!nextProps.path.is(this.props.path)) {\n // NOTE: this can happen e.g. when you zoom out\n return true;\n }\n if (nextProps.cursorBetween !== this.props.cursorBetween) {\n return true;\n }\n if (nextProps.onCharClick !== this.props.onCharClick) {\n return true;\n }\n if (nextProps.onLineClick !== this.props.onLineClick) {\n return true;\n }\n if (nextProps.onBulletClick !== this.props.onBulletClick) {\n return true;\n }\n // NOTE: it's assumed that session and fetchData never change\n\n return false;\n }\n\n\n public render() {\n const session = this.props.session;\n const parent = this.props.path;\n const cached = this.props.cached;\n const cursorsTree = this.props.cursorsTree;\n\n const pathElements: Array = [];\n\n if (cached === null) {\n this.props.fetchData();\n return ;\n }\n\n if (!parent.isRoot()) {\n const elLine = (\n \n );\n pathElements.push(elLine);\n }\n\n const children = cached.childRows;\n const collapsed = cached.collapsed;\n\n if (this.props.topLevel && !children.length) {\n let message = 'Nothing here yet.';\n if (session.mode === 'NORMAL') {\n // TODO move this\n message += ' Press `o` to start adding content!';\n }\n pathElements.push(\n
\n { message }\n
\n );\n } else if (children.length && ((!collapsed) || this.props.topLevel)) {\n let childrenLoaded = true;\n let childrenDivs = cached.children.map((cachedChild) => {\n if (cachedChild === null) {\n childrenLoaded = false;\n return null;\n }\n\n const row = cachedChild.row;\n const path = parent.child(row);\n\n let cloneIcon: React.ReactNode | null = null;\n\n const parents = cachedChild.parentRows;\n // NOTE: this is not actually correct!\n // should use isClone, which is different since a parent may be detached\n if (parents.length > 1) {\n cloneIcon = (\n \n );\n }\n\n const style: React.CSSProperties = {};\n\n let icon = 'fa-circle';\n\n let onBulletClick: (() => void) | undefined = undefined;\n if (cachedChild.childRows.length) {\n icon = cachedChild.collapsed ? 'fa-plus-circle' : 'fa-minus-circle';\n const onBulletClickProp = this.props.onBulletClick;\n if (onBulletClickProp != null) {\n onBulletClick = () => onBulletClickProp(path);\n style.cursor = 'pointer';\n }\n }\n\n let bullet = (\n \n \n );\n bullet = session.applyHook('renderBullet', bullet, { path });\n\n return (\n
\n {cloneIcon}\n {bullet}\n \n
\n );\n });\n\n if (!childrenLoaded) {\n this.props.fetchData();\n childrenDivs = [];\n }\n\n pathElements.push(\n
\n {childrenDivs}\n
\n );\n }\n\n const style = {};\n if (cursorsTree.visual) {\n Object.assign(style, getStyles(session.clientStore, ['theme-bg-highlight']));\n }\n return (\n
\n {pathElements}\n
\n );\n }\n}\n","/Users/jeffwu/projects/vimflowy/src/assets/ts/components/pluginTable.tsx",[],"/Users/jeffwu/projects/vimflowy/src/assets/ts/components/fileInput.tsx",[],"/Users/jeffwu/projects/vimflowy/src/assets/ts/components/spinner.tsx",[],"/Users/jeffwu/projects/vimflowy/src/assets/ts/components/line.tsx",["280","281","282"],"import * as React from 'react';\nimport * as _ from 'lodash';\n\nimport * as text_utils from '../utils/text';\nimport { Col, Line } from '../types';\nimport {\n EmitFn, Token, Tokenizer, PartialTokenizer,\n RegexTokenizerSplitter, CharInfo, Unfolder, PartialUnfolder\n} from '../utils/token_unfolder';\n\nexport type LineProps = {\n lineData: Line;\n cursors?: {[key: number]: boolean};\n cursorStyle: React.CSSProperties;\n highlights?: {[key: number]: boolean};\n highlightStyle: React.CSSProperties;\n accents?: {[key: number]: boolean};\n accentStyle: React.CSSProperties;\n linksStyle: React.CSSProperties;\n lineHook?: PartialUnfolder;\n wordHook?: PartialUnfolder;\n onCharClick?: ((col: Col, e: Event) => void) | undefined;\n cursorBetween?: boolean;\n};\n\n// NOTE: hacky! we don't include .:/?= since urls contain it\n// should instead make tokenizer for URLs\n// also not including @ for marks\nconst word_boundary_chars = '\\t\\r\\n ,!()\\\"\\'*+\\\\;<>\\\\[\\\\]`{}|';\n\nexport default class LineComponent extends React.Component {\n\n constructor(props: LineProps) {\n super(props);\n }\n\n public render() {\n const cursorBetween: boolean = this.props.cursorBetween || false;\n const lineData = _.cloneDeep(this.props.lineData);\n const cursors = this.props.cursors || {};\n const highlights = this.props.highlights || {};\n const accents = this.props.accents || {};\n\n // ideally this takes up space but is unselectable (uncopyable)\n const cursorChar = ' ';\n\n // add cursor if at end\n if (lineData.length in cursors) {\n lineData.push(cursorChar);\n }\n\n if (lineData.length === 0) {\n return ;\n }\n\n const cursorBetweenDiv = (i: number) => {\n return (\n
\n {' '}\n
\n );\n };\n\n const DefaultTokenizer: Tokenizer = new Unfolder((\n token: Token, emit: EmitFn\n ) => {\n for (let i = 0; i < token.text.length; i++) {\n const char_info = token.info[i];\n const classes = Object.keys(char_info.renderOptions.classes);\n\n const style: React.CSSProperties = char_info.renderOptions.style || {};\n if (char_info.highlight) {\n Object.assign(style, this.props.highlightStyle);\n }\n if (char_info.accent) {\n Object.assign(style, this.props.accentStyle);\n }\n if (char_info.cursor) {\n if (cursorBetween) {\n emit(cursorBetweenDiv(token.index + i));\n } else {\n classes.push('cursor');\n Object.assign(style, this.props.cursorStyle);\n }\n }\n\n const column = token.index + i;\n let href = null;\n let target = null;\n if (char_info.renderOptions.href) {\n href = char_info.renderOptions.href;\n target = '_blank';\n }\n\n let onClick = undefined;\n if (href == null) {\n if (char_info.renderOptions.onClick !== undefined) {\n onClick = char_info.renderOptions.onClick;\n } else if (this.props.onCharClick) {\n onClick = this.props.onCharClick.bind(this, column);\n }\n }\n const divType = char_info.renderOptions.divType || 'span';\n emit(\n React.createElement(\n divType,\n {\n style: style,\n key: `default-${column}`,\n className: classes.join(' '),\n onClick: onClick,\n href: href,\n target: target\n } as React.DOMAttributes,\n token.text[i] as React.ReactNode\n )\n );\n }\n\n });\n\n let lineHook;\n if (this.props.lineHook) {\n lineHook = this.props.lineHook;\n } else {\n lineHook = PartialUnfolder.trivial();\n }\n const LineTokenizer: PartialTokenizer = RegexTokenizerSplitter(\n new RegExp('(\\n)'),\n (token: Token, emit: EmitFn) => {\n if (token.text.length !== 1) {\n throw new Error('Expected matched newline of length 1');\n }\n if (token.info.length !== 1) {\n throw new Error('Expected matched newline with info of length 1');\n }\n const char_info = token.info[0];\n const style: React.CSSProperties = char_info.renderOptions.style || {};\n const classes = Object.keys(char_info.renderOptions.classes);\n if (char_info.highlight) {\n Object.assign(style, this.props.highlightStyle);\n }\n if (char_info.accent) {\n Object.assign(style, this.props.accentStyle);\n }\n if (char_info.cursor) {\n if (cursorBetween) {\n emit(cursorBetweenDiv(token.index));\n } else {\n classes.push('cursor');\n Object.assign(style, this.props.cursorStyle);\n emit(React.createElement(\n 'span',\n {\n style: style,\n key: `cursor-${token.index}`,\n className: classes.join(' '),\n onClick: undefined,\n } as React.DOMAttributes,\n cursorChar as React.ReactNode\n ));\n }\n }\n\n emit(React.createElement(\n 'div',\n {\n key: `newline-${token.index}`,\n className: classes.join(' '),\n onClick: undefined,\n } as React.DOMAttributes,\n '' as React.ReactNode\n ));\n }\n );\n\n let wordHook;\n if (this.props.wordHook) {\n wordHook = this.props.wordHook;\n } else {\n wordHook = PartialUnfolder.trivial();\n }\n wordHook = wordHook.then(new PartialUnfolder((\n token: Token, emit: EmitFn, wrapped: Tokenizer\n ) => {\n if (text_utils.isLink(token.text)) {\n token.info.forEach((char_info) => {\n char_info.renderOptions.divType = 'a';\n char_info.renderOptions.style = char_info.renderOptions.style || {};\n Object.assign(char_info.renderOptions.style, this.props.linksStyle);\n char_info.renderOptions.onClick = null;\n char_info.renderOptions.href = token.text;\n });\n }\n emit(...wrapped.unfold(token));\n }));\n\n const WordTokenizer: PartialTokenizer = RegexTokenizerSplitter(\n new RegExp('([^' + word_boundary_chars + ']+)'),\n wordHook.partial_fn\n );\n\n let tokenizer = lineHook\n .then(LineTokenizer)\n .then(WordTokenizer)\n .finish(DefaultTokenizer);\n\n // NOTE: this doesn't seem to work for the breadcrumbs, e.g. try visual selecting word at end\n\n // - start with a plain text string\n // - allow custom \"sentence\" tokenization first\n // - then tokenize into words\n // - allow more custom \"word\" tokenization\n const info: Array = [];\n for (let i = 0; i < lineData.length; i++) {\n const char_info: CharInfo = {\n highlight: i in highlights,\n cursor: i in cursors,\n accent: i in accents,\n renderOptions: {\n classes: {},\n },\n };\n info.push(char_info);\n }\n let token: Token = {\n index: 0,\n length: lineData.length,\n text: lineData.join(''),\n info: info,\n };\n const results = tokenizer.unfold(token);\n return (\n \n {results}\n \n );\n }\n}\n","/Users/jeffwu/projects/vimflowy/src/assets/ts/components/settings/backendSettings.tsx",["283"],"import * as React from 'react';\n\nimport { BackendType } from '../../data_backend';\nimport { ClientStore } from '../../datastore';\nimport { getStyles } from '../../themes';\n\ntype Props = {\n clientStore: ClientStore;\n initialBackendType: BackendType;\n};\ntype State = {\n backendType: BackendType,\n firebaseId: string,\n firebaseApiKey: string,\n firebaseUserEmail: string,\n firebaseUserPassword: string,\n socketServerHost: string,\n socketServerPassword: string,\n socketServerDocument: string,\n};\nexport default class BackendSettingsComponent extends React.Component {\n constructor(props: Props) {\n super(props);\n\n const clientStore = props.clientStore;\n this.state = {\n backendType: props.initialBackendType,\n firebaseId: clientStore.getDocSetting('firebaseId') || '',\n firebaseApiKey: clientStore.getDocSetting('firebaseApiKey') || '',\n firebaseUserEmail: clientStore.getDocSetting('firebaseUserEmail') || '',\n firebaseUserPassword: clientStore.getDocSetting('firebaseUserPassword') || '',\n socketServerHost: clientStore.getDocSetting('socketServerHost') || '',\n socketServerPassword: clientStore.getDocSetting('socketServerPassword') || '',\n socketServerDocument: clientStore.getDocSetting('socketServerDocument') || '',\n };\n }\n\n private async saveDataSettings() {\n const clientStore = this.props.clientStore;\n const backendType = this.state.backendType;\n clientStore.setDocSetting('dataSource', backendType);\n if (backendType === 'firebase') {\n clientStore.setDocSetting('firebaseId', this.state.firebaseId);\n clientStore.setDocSetting('firebaseApiKey', this.state.firebaseApiKey);\n clientStore.setDocSetting('firebaseUserEmail', this.state.firebaseUserEmail);\n clientStore.setDocSetting('firebaseUserPassword', this.state.firebaseUserPassword);\n } else if (backendType === 'socketserver') {\n clientStore.setDocSetting('socketServerHost', this.state.socketServerHost);\n clientStore.setDocSetting('socketServerPassword', this.state.socketServerPassword);\n clientStore.setDocSetting('socketServerDocument', this.state.socketServerDocument);\n }\n window.location.reload();\n }\n\n private setBackendType(backendType: BackendType) {\n this.setState({ backendType } as State);\n }\n\n private setFirebaseId(firebaseId: string) {\n this.setState({ firebaseId } as State);\n }\n\n private setFirebaseApiKey(firebaseApiKey: string) {\n this.setState({ firebaseApiKey } as State);\n }\n\n private setFirebaseUserEmail(firebaseUserEmail: string) {\n this.setState({ firebaseUserEmail } as State);\n }\n\n private setFirebaseUserPassword(firebaseUserPassword: string) {\n this.setState({ firebaseUserPassword } as State);\n }\n\n private setSocketServerHost(socketServerHost: string) {\n this.setState({ socketServerHost } as State);\n }\n\n private setSocketServerPassword(socketServerPassword: string) {\n this.setState({ socketServerPassword } as State);\n }\n\n private setSocketServerDocument(socketServerDocument: string) {\n this.setState({ socketServerDocument } as State);\n }\n\n public render() {\n const firebaseBaseUrl = `https://console.firebase.google.com/project/${this.state.firebaseId || '${firebaseProjectId}'}`;\n\n const backendTypes: Array<{\n name: string,\n type: 'Remote' | 'Local',\n value: BackendType,\n info: React.ReactElement | string,\n config?: React.ReactElement,\n }> = [\n {\n name: 'HTML5 Local Storage (default)',\n type: 'Local',\n value: 'local',\n info: `\n Stores data on your computer, through standard browser APIs.\n No backups, so you should export regularly.\n Clearing local storage will result in data loss.\n `,\n },\n {\n name: 'In-memory',\n type: 'Local',\n value: 'inmemory',\n info: `\n Not saved at all!\n Data loss as soon as you close the tab.\n Good for testing with complete throw-away data.\n `,\n },\n {\n name: 'Firebase',\n type: 'Remote',\n value: 'firebase',\n info: (\n
\n Stores data in Google's Firebase cloud service.\n Regular backups can be turned on.\n {' '}\n \n Details here\n .\n
\n ),\n config: (\n
\n For details on configuration, \n see here\n .\n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
\n Firebase ID\n \n this.setFirebaseId((ev.target as HTMLInputElement).value)}\n style={{float: 'right'}}\n />\n
\n Firebase API Key\n (found {' '}\n \n here\n \n )\n \n this.setFirebaseApiKey((ev.target as HTMLInputElement).value)}\n style={{float: 'right'}}\n />\n
\n Firebase User Email\n (added {' '}\n \n here\n \n )\n \n this.setFirebaseUserEmail((ev.target as HTMLInputElement).value)}\n style={{float: 'right'}}\n />\n
\n Firebase User Password\n \n this.setFirebaseUserPassword((ev.target as HTMLInputElement).value)}\n style={{float: 'right'}}\n />\n
\n
\n
\n ),\n },\n {\n name: 'Vimflowy server',\n type: 'Remote',\n value: 'socketserver',\n info: `\n Client talks to a server over websockets.\n Server stores data in SQLite.\n `,\n config: (\n
\n For details on configuration, \n see here\n .\n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n
\n Server\n \n this.setSocketServerHost((ev.target as HTMLInputElement).value)}\n style={{float: 'right'}}\n />\n
\n Password\n \n this.setSocketServerPassword((ev.target as HTMLInputElement).value)}\n style={{float: 'right'}}\n />\n
\n Document\n \n this.setSocketServerDocument((ev.target as HTMLInputElement).value)}\n style={{float: 'right'}}\n />\n
\n
\n
\n ),\n },\n ];\n\n return (\n
\n
\n Local data sources:\n
    \n
  • \n Offline access supported\n
  • \n
  • \n Data is never sent over the internet\n
  • \n
  • \n Can only be accessed from this browser\n
  • \n
\n
\n
\n Remote data sources:\n
    \n
  • \n Can be accessed from multiple devices or browsers\n
  • \n
  • \n No offline support\n
  • \n
\n
\n\n \n \n \n \n \n \n \n \n \n \n { (() => {\n const rows: Array> = [];\n backendTypes.forEach((backendTypeInfo) => {\n const selected = this.state.backendType === backendTypeInfo.value;\n rows.push(\n \n \n \n \n \n \n );\n\n if (selected && backendTypeInfo.config) {\n rows.push(\n \n \n \n \n );\n }\n });\n return rows;\n })() }\n \n
\n Storage Type\n \n Local/Remote\n \n Info\n
\n this.setBackendType((ev.target as HTMLInputElement).value as BackendType)}\n />\n \n {backendTypeInfo.name}\n \n {backendTypeInfo.type}\n \n {backendTypeInfo.info}\n
\n {backendTypeInfo.config}\n
\n\n
this.saveDataSettings()} >\n Load Data Settings\n
\n
\n (WARNING: will reload page)\n
\n
\n );\n }\n}\n","/Users/jeffwu/projects/vimflowy/src/assets/ts/components/settings/behaviorSettings.tsx",["284"],"import * as React from 'react';\n\nimport { ClientStore } from '../../datastore';\n\ntype Props = {\n clientStore: ClientStore;\n};\n\ntype State = {\n copyToClipboard: boolean;\n formattedCopy: boolean;\n};\n\nexport default class BehaviorSettingsComponent extends React.Component {\n constructor(props: Props) {\n super(props);\n\n const clientStore = props.clientStore;\n this.state = {\n copyToClipboard: clientStore.getClientSetting('copyToClipboard'),\n formattedCopy: clientStore.getClientSetting('formattedCopy'),\n };\n this.setCopyToClipboard = this.setCopyToClipboard.bind(this);\n this.setFormattedCopy = this.setFormattedCopy.bind(this);\n }\n\n private setCopyToClipboard(copyToClipboard: boolean): boolean {\n this.setState({ copyToClipboard: copyToClipboard });\n this.props.clientStore.setClientSetting('copyToClipboard', copyToClipboard);\n return this.state.copyToClipboard;\n }\n\n private setFormattedCopy(formattedCopy: boolean): boolean {\n this.setState({ formattedCopy: formattedCopy });\n this.props.clientStore.setClientSetting('formattedCopy', formattedCopy);\n return this.state.formattedCopy;\n }\n\n public render() {\n return (\n
\n \n \n
\n );\n }\n}\n\ninterface SettingsCheckboxProps {\n id: string;\n name: string;\n desc: string;\n fn: (val: boolean) => void;\n checked: boolean;\n}\n\nclass SettingsCheckbox extends React.Component {\n constructor(props: SettingsCheckboxProps) {\n super(props);\n }\n\n public render() {\n return (\n
\n {\n this.props.fn(e.target.checked);\n }} />\n \n
\n );\n }\n}\n","/Users/jeffwu/projects/vimflowy/src/plugins/latex/index.tsx",[],"/Users/jeffwu/projects/vimflowy/src/plugins/html/index.tsx",[],"/Users/jeffwu/projects/vimflowy/src/plugins/easy_motion/index.tsx",[],"/Users/jeffwu/projects/vimflowy/src/plugins/text_formatting/index.tsx",[],"/Users/jeffwu/projects/vimflowy/src/plugins/marks/index.tsx",[],"/Users/jeffwu/projects/vimflowy/src/plugins/todo/index.tsx",[],"/Users/jeffwu/projects/vimflowy/src/plugins/time_tracking/index.tsx",[],"/Users/jeffwu/projects/vimflowy/src/plugins/recursive_expand/index.tsx",["285"],"import * as _ from 'lodash';\n\nimport { registerPlugin } from '../../assets/ts/plugins';\nimport Session from '../../assets/ts/session';\nimport logger from '../../shared/utils/logger';\nimport Path from '../../assets/ts/path';\n\nexport const pluginName = 'Recursive-Expand';\n\nregisterPlugin(\n {\n name: pluginName,\n author: 'Nikhil Sonti',\n description: `Lets you recursively expand or collapse a node`,\n },\n function (api) {\n\n async function toggleRecursiveCollapse(session: Session, path: Path, collapse: boolean) {\n logger.debug('Toggle state: ' + collapse + ' row = ' + path.row);\n await session.document.setCollapsed(path.row, collapse);\n\n if (await session.document.hasChildren(path.row)) {\n let children = await session.document.getChildren(path);\n logger.debug('No of children: ' + children.length);\n\n children.forEach(async (child_path) => {\n await toggleRecursiveCollapse(session, child_path, collapse);\n });\n }\n }\n\n api.registerAction(\n 'toggle-expand',\n 'Toggle expand/collapse recursively',\n async function ({ session }) {\n let is_collapsed = await session.document.collapsed(session.cursor.row);\n await toggleRecursiveCollapse(session, session.cursor.path, !is_collapsed);\n await session.document.forceLoadTree(session.cursor.row, false);\n },\n );\n\n api.registerDefaultMappings(\n 'NORMAL',\n {\n 'toggle-expand': [['Z']],\n },\n );\n },\n (api => api.deregisterAll()),\n);\n","/Users/jeffwu/projects/vimflowy/src/plugins/daily_notes/index.tsx",[],"/Users/jeffwu/projects/vimflowy/src/assets/ts/utils/token_unfolder.ts",[],{"ruleId":"286","replacedBy":"287"},{"ruleId":"288","replacedBy":"289"},{"ruleId":"290","severity":1,"message":"291","line":107,"column":3,"nodeType":"292","messageId":"293","endLine":109,"endColumn":4},{"ruleId":"294","severity":1,"message":"295","line":260,"column":16,"nodeType":"296","messageId":"297","endLine":260,"endColumn":22},{"ruleId":"298","severity":1,"message":"299","line":343,"column":37,"nodeType":"300","messageId":"301","endLine":343,"endColumn":38,"suggestions":"302"},{"ruleId":"298","severity":1,"message":"299","line":344,"column":36,"nodeType":"300","messageId":"301","endLine":344,"endColumn":37,"suggestions":"303"},{"ruleId":"298","severity":1,"message":"299","line":45,"column":25,"nodeType":"300","messageId":"301","endLine":45,"endColumn":26,"suggestions":"304"},{"ruleId":"290","severity":1,"message":"291","line":33,"column":3,"nodeType":"292","messageId":"293","endLine":35,"endColumn":4},{"ruleId":"290","severity":1,"message":"291","line":24,"column":3,"nodeType":"292","messageId":"293","endLine":24,"endColumn":39},{"ruleId":"290","severity":1,"message":"291","line":31,"column":3,"nodeType":"292","messageId":"293","endLine":31,"endColumn":39},{"ruleId":"305","severity":1,"message":"306","line":334,"column":7,"nodeType":"307","messageId":"308","endLine":334,"endColumn":41,"fix":"309"},{"ruleId":"305","severity":1,"message":"310","line":335,"column":7,"nodeType":"307","messageId":"308","endLine":335,"endColumn":41,"fix":"311"},{"ruleId":"305","severity":1,"message":"312","line":336,"column":7,"nodeType":"307","messageId":"308","endLine":336,"endColumn":41,"fix":"313"},{"ruleId":"305","severity":1,"message":"314","line":337,"column":7,"nodeType":"307","messageId":"308","endLine":337,"endColumn":51,"fix":"315"},{"ruleId":"305","severity":1,"message":"316","line":338,"column":7,"nodeType":"307","messageId":"308","endLine":338,"endColumn":41,"fix":"317"},{"ruleId":"305","severity":1,"message":"318","line":339,"column":7,"nodeType":"307","messageId":"308","endLine":339,"endColumn":45,"fix":"319"},{"ruleId":"320","severity":1,"message":"321","line":186,"column":13,"nodeType":"322","endLine":195,"endColumn":14},{"ruleId":"290","severity":1,"message":"291","line":24,"column":3,"nodeType":"292","messageId":"293","endLine":26,"endColumn":4},{"ruleId":"290","severity":1,"message":"291","line":145,"column":3,"nodeType":"292","messageId":"293","endLine":147,"endColumn":4},{"ruleId":"298","severity":1,"message":"323","line":29,"column":41,"nodeType":"300","messageId":"301","endLine":29,"endColumn":42,"suggestions":"324"},{"ruleId":"290","severity":1,"message":"291","line":33,"column":3,"nodeType":"292","messageId":"293","endLine":35,"endColumn":4},{"ruleId":"325","severity":1,"message":"326","line":135,"column":18,"nodeType":"300","messageId":"327","endLine":135,"endColumn":24},{"ruleId":"328","severity":1,"message":"329","line":88,"column":101,"nodeType":"300","messageId":"330","endLine":88,"endColumn":123},{"ruleId":"290","severity":1,"message":"291","line":68,"column":3,"nodeType":"292","messageId":"293","endLine":70,"endColumn":4},{"ruleId":"331","severity":1,"message":"332","line":1,"column":13,"nodeType":"296","messageId":"333","endLine":1,"endColumn":14},"no-native-reassign",["334"],"no-negated-in-lhs",["335"],"@typescript-eslint/no-useless-constructor","Useless constructor.","MethodDefinition","noUselessConstructor","no-self-assign","'motion' is assigned to itself.","Identifier","selfAssignment","no-useless-escape","Unnecessary escape character: \\[.","Literal","unnecessaryEscape",["336","337"],["338","339"],["340","341"],"no-useless-computed-key","Unnecessarily computed property ['NORMAL'] found.","Property","unnecessarilyComputedProperty",{"range":"342","text":"343"},"Unnecessarily computed property ['INSERT'] found.",{"range":"344","text":"345"},"Unnecessarily computed property ['VISUAL'] found.",{"range":"346","text":"347"},"Unnecessarily computed property ['VISUAL_LINE'] found.",{"range":"348","text":"349"},"Unnecessarily computed property ['SEARCH'] found.",{"range":"350","text":"351"},"Unnecessarily computed property ['SETTINGS'] found.",{"range":"352","text":"353"},"jsx-a11y/anchor-is-valid","The href attribute is required for an anchor to be keyboard accessible. Provide a valid, navigable address as the href value. If you cannot provide an href, but still need the element to resemble a link, use a button and change it with appropriate styles. Learn more: https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/anchor-is-valid.md","JSXOpeningElement","Unnecessary escape character: \\\".",["354","355"],"no-control-regex","Unexpected control character(s) in regular expression: \\x0a.","unexpected","no-template-curly-in-string","Unexpected template string expression.","unexpectedTemplateExpression","@typescript-eslint/no-unused-vars","'_' is defined but never used.","unusedVar","no-global-assign","no-unsafe-negation",{"messageId":"356","fix":"357","desc":"358"},{"messageId":"359","fix":"360","desc":"361"},{"messageId":"356","fix":"362","desc":"358"},{"messageId":"359","fix":"363","desc":"361"},{"messageId":"356","fix":"364","desc":"358"},{"messageId":"359","fix":"365","desc":"361"},[13202,13214],"'NORMAL'",[13244,13256],"'INSERT'",[13286,13298],"'VISUAL'",[13328,13345],"'VISUAL_LINE'",[13380,13392],"'SEARCH'",[13422,13436],"'SETTINGS'",{"messageId":"356","fix":"366","desc":"358"},{"messageId":"359","fix":"367","desc":"361"},"removeEscape",{"range":"368","text":"369"},"Remove the `\\`. This maintains the current functionality.","escapeBackslash",{"range":"370","text":"371"},"Replace the `\\` with `\\\\` to include the actual backslash character.",{"range":"372","text":"369"},{"range":"373","text":"371"},{"range":"374","text":"369"},{"range":"375","text":"371"},{"range":"376","text":"369"},{"range":"377","text":"371"},[9588,9589],"",[9588,9588],"\\",[9642,9643],[9642,9642],[1475,1476],[1475,1475],[980,981],[980,980]] \ No newline at end of file diff --git a/.gitignore b/.gitignore index c9d12579..54be1bdc 100644 --- a/.gitignore +++ b/.gitignore @@ -12,4 +12,6 @@ npm-debug.log /todo .DS_Store -.vscode/* \ No newline at end of file +.vscode/* + +.eslintcache diff --git a/package-lock.json b/package-lock.json index 029139c2..99d79c6d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2316,177 +2316,11 @@ "loader-utils": "^2.0.0" } }, - "@testing-library/dom": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-7.29.0.tgz", - "integrity": "sha512-0hhuJSmw/zLc6ewR9cVm84TehuTd7tbqBX9pRNSp8znJ9gTmSgesdbiGZtt8R6dL+2rgaPFp9Yjr7IU1HWm49w==", - "requires": { - "@babel/code-frame": "^7.10.4", - "@babel/runtime": "^7.12.5", - "@types/aria-query": "^4.2.0", - "aria-query": "^4.2.2", - "chalk": "^4.1.0", - "dom-accessibility-api": "^0.5.4", - "lz-string": "^1.4.4", - "pretty-format": "^26.6.2" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@testing-library/jest-dom": { - "version": "5.11.6", - "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-5.11.6.tgz", - "integrity": "sha512-cVZyUNRWwUKI0++yepYpYX7uhrP398I+tGz4zOlLVlUYnZS+Svuxv4fwLeCIy7TnBYKXUaOlQr3vopxL8ZfEnA==", - "requires": { - "@babel/runtime": "^7.9.2", - "@types/testing-library__jest-dom": "^5.9.1", - "aria-query": "^4.2.2", - "chalk": "^3.0.0", - "css": "^3.0.0", - "css.escape": "^1.5.1", - "lodash": "^4.17.15", - "redent": "^3.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "css": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/css/-/css-3.0.0.tgz", - "integrity": "sha512-DG9pFfwOrzc+hawpmqX/dHYHJG+Bsdb0klhyi1sDneOgGOXy9wQIC8hzyVp1e4NRYDBdxcylvywPkkXCHAzTyQ==", - "requires": { - "inherits": "^2.0.4", - "source-map": "^0.6.1", - "source-map-resolve": "^0.6.0" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - }, - "source-map-resolve": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.6.0.tgz", - "integrity": "sha512-KXBr9d/fO/bWo97NXsPIAW1bFSBOuCnjbNTBMO7N59hsv5i9yzRDfcYwwt0l04+VqnKC+EwzvJZIP/qkuMgR/w==", - "requires": { - "atob": "^2.1.2", - "decode-uri-component": "^0.2.0" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@testing-library/react": { - "version": "11.2.2", - "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-11.2.2.tgz", - "integrity": "sha512-jaxm0hwUjv+hzC+UFEywic7buDC9JQ1q3cDsrWVSDAPmLotfA6E6kUHlYm/zOeGCac6g48DR36tFHxl7Zb+N5A==", - "requires": { - "@babel/runtime": "^7.12.5", - "@testing-library/dom": "^7.28.1" - } - }, - "@testing-library/user-event": { - "version": "12.6.0", - "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-12.6.0.tgz", - "integrity": "sha512-FNEH/HLmOk5GO70I52tKjs7WvGYckeE/SrnLX/ip7z2IGbffyd5zOUM1tZ10vsTphqm+VbDFI0oaXu0wcfQsAQ==", - "requires": { - "@babel/runtime": "^7.12.5" - } - }, "@types/anymatch": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/@types/anymatch/-/anymatch-1.3.1.tgz", "integrity": "sha512-/+CRPXpBDpo2RK9C68N3b2cOvO0Cf5B9aPijHsoDQTHivnGSObdOF2BRQOYjojWTDy6nQvMjmqRXIxH55VjxxA==" }, - "@types/aria-query": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-4.2.0.tgz", - "integrity": "sha512-iIgQNzCm0v7QMhhe4Jjn9uRh+I6GoPmt03CbEtwx3ao8/EfoQcmgtqH4vQ5Db/lxiIGaWDv6nwvunuh0RyX0+A==" - }, "@types/babel__core": { "version": "7.1.12", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.12.tgz", @@ -2636,6 +2470,12 @@ "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==" }, + "@types/mocha": { + "version": "2.2.48", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-2.2.48.tgz", + "integrity": "sha512-nlK/iyETgafGli8Zh9zJVCTicvU3iajSkRwOh3Hhiva598CMqNJ4NcVCGMTGKpGpTYj/9R8RLzS9NAykSSCqGw==", + "dev": true + }, "@types/node": { "version": "12.19.11", "resolved": "https://registry.npmjs.org/@types/node/-/node-12.19.11.tgz", @@ -2731,14 +2571,6 @@ "resolved": "https://registry.npmjs.org/@types/tapable/-/tapable-1.0.6.tgz", "integrity": "sha512-W+bw9ds02rAQaMvaLYxAbJ6cvguW/iJXNT6lTssS1ps6QdrMKttqEAMEG/b5CR8TZl3/L7/lH0ZV5nNR1LXikA==" }, - "@types/testing-library__jest-dom": { - "version": "5.9.5", - "resolved": "https://registry.npmjs.org/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.9.5.tgz", - "integrity": "sha512-ggn3ws+yRbOHog9GxnXiEZ/35Mow6YtPZpd7Z5mKDeZS/o7zx3yAle0ov/wjhVB5QT4N2Dt+GNoGCdqkBGCajQ==", - "requires": { - "@types/jest": "*" - } - }, "@types/uglify-js": { "version": "3.11.1", "resolved": "https://registry.npmjs.org/@types/uglify-js/-/uglify-js-3.11.1.tgz", @@ -4102,6 +3934,12 @@ "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==" }, + "browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, "browserify-aes": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", @@ -5061,11 +4899,6 @@ "resolved": "https://registry.npmjs.org/css-what/-/css-what-3.4.2.tgz", "integrity": "sha512-ACUm3L0/jiZTqfzRM3Hi9Q8eZqd6IK37mMWPLz9PJxkLWllYeRf+EHUSHYEtFop2Eqytaq1FizFVh7XfBnXCDQ==" }, - "css.escape": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", - "integrity": "sha1-QuJ9T6BK4y+TGktNQZH6nN3ul8s=" - }, "cssdb": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/cssdb/-/cssdb-4.4.0.tgz", @@ -5506,6 +5339,12 @@ } } }, + "diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true + }, "diff-sequences": { "version": "26.6.2", "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-26.6.2.tgz", @@ -5566,11 +5405,6 @@ "esutils": "^2.0.2" } }, - "dom-accessibility-api": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.4.tgz", - "integrity": "sha512-TvrjBckDy2c6v6RLxPv5QXOnU+SmF9nBII5621Ve5fu6Z/BDrENurBEvlC1f44lKEUVqOpK4w9E5Idc5/EgkLQ==" - }, "dom-converter": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz", @@ -7616,6 +7450,12 @@ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==" }, + "growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "dev": true + }, "growly": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz", @@ -7804,6 +7644,15 @@ "minimalistic-crypto-utils": "^1.0.1" } }, + "homedir-polyfill": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", + "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==", + "dev": true, + "requires": { + "parse-passwd": "^1.0.0" + } + }, "hoopy": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz", @@ -8176,6 +8025,12 @@ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==" }, + "ignore-styles": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ignore-styles/-/ignore-styles-5.0.1.tgz", + "integrity": "sha1-tJ7yJ0va/NikiAqWa/440aC/RnE=", + "dev": true + }, "immer": { "version": "7.0.9", "resolved": "https://registry.npmjs.org/immer/-/immer-7.0.9.tgz", @@ -10584,11 +10439,6 @@ "yallist": "^4.0.0" } }, - "lz-string": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.4.4.tgz", - "integrity": "sha1-wNjq82BZ9wV5bh40SBHPTEmNOiY=" - }, "magic-string": { "version": "0.25.7", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz", @@ -10613,6 +10463,12 @@ } } }, + "make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, "makeerror": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.11.tgz", @@ -10893,11 +10749,6 @@ "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==" }, - "min-indent": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", - "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==" - }, "mini-css-extract-plugin": { "version": "0.11.3", "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-0.11.3.tgz", @@ -11047,6 +10898,92 @@ "minimist": "^1.2.5" } }, + "mocha": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz", + "integrity": "sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ==", + "dev": true, + "requires": { + "browser-stdout": "1.3.1", + "commander": "2.15.1", + "debug": "3.1.0", + "diff": "3.5.0", + "escape-string-regexp": "1.0.5", + "glob": "7.1.2", + "growl": "1.10.5", + "he": "1.1.1", + "minimatch": "3.0.4", + "mkdirp": "0.5.1", + "supports-color": "5.4.0" + }, + "dependencies": { + "commander": { + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", + "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", + "dev": true + }, + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "glob": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "he": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", + "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", + "dev": true + }, + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, + "requires": { + "minimist": "0.0.8" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "supports-color": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", + "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, "move-concurrently": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", @@ -11866,6 +11803,12 @@ "lines-and-columns": "^1.1.6" } }, + "parse-passwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", + "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=", + "dev": true + }, "parse5": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", @@ -13737,15 +13680,6 @@ "minimatch": "3.0.4" } }, - "redent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", - "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", - "requires": { - "indent-string": "^4.0.0", - "strip-indent": "^3.0.0" - } - }, "regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", @@ -15522,14 +15456,6 @@ "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==" }, - "strip-indent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", - "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", - "requires": { - "min-indent": "^1.0.0" - } - }, "strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -15980,11 +15906,64 @@ "resolved": "https://registry.npmjs.org/tryer/-/tryer-1.0.1.tgz", "integrity": "sha512-c3zayb8/kWWpycWYg87P71E1S1ZL6b6IJxfb5fvsUgsf0S2MVGaDhDXXjDMpdCpfWXqptc+4mXwmiy1ypXqRAA==" }, + "ts-node": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-3.3.0.tgz", + "integrity": "sha1-wTxqMCTjC+EYDdUwOPwgkonUv2k=", + "dev": true, + "requires": { + "arrify": "^1.0.0", + "chalk": "^2.0.0", + "diff": "^3.1.0", + "make-error": "^1.1.1", + "minimist": "^1.2.0", + "mkdirp": "^0.5.1", + "source-map-support": "^0.4.0", + "tsconfig": "^6.0.0", + "v8flags": "^3.0.0", + "yn": "^2.0.0" + }, + "dependencies": { + "arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", + "dev": true + }, + "source-map-support": { + "version": "0.4.18", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz", + "integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==", + "dev": true, + "requires": { + "source-map": "^0.5.6" + } + } + } + }, "ts-pnp": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/ts-pnp/-/ts-pnp-1.2.0.tgz", "integrity": "sha512-csd+vJOb/gkzvcCHgTGSChYpy5f1/XKNsmvBGO4JXS+z1v2HobugDz4s1IeFXM3wZB44uczs+eazB5Q/ccdhQw==" }, + "tsconfig": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/tsconfig/-/tsconfig-6.0.0.tgz", + "integrity": "sha1-aw6DdgA9evGGT434+J3QBZ/80DI=", + "dev": true, + "requires": { + "strip-bom": "^3.0.0", + "strip-json-comments": "^2.0.0" + }, + "dependencies": { + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true + } + } + }, "tsconfig-paths": { "version": "3.9.0", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.9.0.tgz", @@ -16366,6 +16345,15 @@ } } }, + "v8flags": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-3.2.0.tgz", + "integrity": "sha512-mH8etigqMfiGWdeXpaaqGfs6BndypxusHHcv2qSHyZkGEznCd/qAXCWWRzeowtL54147cktFOC4P5y+kl8d8Jg==", + "dev": true, + "requires": { + "homedir-polyfill": "^1.0.1" + } + }, "validate-npm-package-license": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", @@ -17938,6 +17926,12 @@ } } }, + "yn": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yn/-/yn-2.0.0.tgz", + "integrity": "sha1-5a2ryKz0CPY4X8dklWhMiOavaJo=", + "dev": true + }, "yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index c1a53d51..acd11959 100644 --- a/package.json +++ b/package.json @@ -4,9 +4,6 @@ "private": true, "homepage": "./", "dependencies": { - "@testing-library/jest-dom": "^5.11.6", - "@testing-library/react": "^11.2.2", - "@testing-library/user-event": "^12.6.0", "@types/file-saver": "^2.0.1", "@types/jest": "^26.0.19", "@types/node": "^12.19.11", @@ -28,7 +25,7 @@ "scripts": { "start": "react-scripts start", "build": "react-scripts build", - "test": "react-scripts test", + "test": "TS_NODE_COMPILER_OPTIONS='{\"module\":\"commonjs\"}' mocha --opts test/mocha.opts", "eject": "react-scripts eject" }, "eslintConfig": { @@ -53,7 +50,11 @@ "@types/jquery": "^3.5.5", "@types/katex": "^0.11.0", "@types/lodash": "^4.14.166", + "@types/mocha": "^2.2.48", "@types/react": "^16.14.2", - "@types/react-color": "^3.0.4" + "@types/react-color": "^3.0.4", + "ignore-styles": "^5.0.1", + "mocha": "^5.2.0", + "ts-node": "^3.3.0" } } diff --git a/tsconfig.json b/tsconfig.json index 74e45260..da0181bf 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -12,11 +12,11 @@ "allowJs": false, "strict": true, "experimentalDecorators": true, + "esModuleInterop": true, "noUnusedParameters": true, "noUnusedLocals": false, "noImplicitReturns": true, "skipLibCheck": true, - "esModuleInterop": true, "allowSyntheticDefaultImports": true, "forceConsistentCasingInFileNames": true, "noFallthroughCasesInSwitch": true,