diff --git a/src/assets/js/configurations/workflowy.ts b/src/assets/js/configurations/workflowy.ts new file mode 100644 index 00000000..55d53e22 --- /dev/null +++ b/src/assets/js/configurations/workflowy.ts @@ -0,0 +1,183 @@ +import * as _ from 'lodash'; + +import { SerializedBlock } from '../types'; +import KeyMappings, { HotkeyMapping } from '../keyMappings'; +import { motionKey } from '../keyDefinitions'; +import { SINGLE_LINE_MOTIONS } from '../definitions/motions'; +import Config from '../config'; + +export const INSERT_MOTION_MAPPINGS: HotkeyMapping = { + 'motion-left': [['left']], + 'motion-right': [['right']], + 'motion-up': [['up']], + 'motion-down': [['down']], + 'motion-line-beginning': [['home'], ['ctrl+a'], ['meta+left']], + 'motion-line-end': [['end'], ['ctrl+e'], ['meta+right']], + 'motion-word-beginning': [['alt+b'], ['alt+left']], + 'motion-word-end': [], + 'motion-word-next': [['alt+f'], ['alt+right']], + 'motion-Word-beginning': [], + 'motion-Word-end': [], + 'motion-Word-next': [], + + 'motion-visible-beginning': [['meta+home']], + 'motion-visible-end': [['meta+end']], + 'motion-parent': [['ctrl+g', 'p']], + 'motion-next-clone': [['ctrl+g', 'c']], + 'motion-next-sibling': [['alt+down']], + 'motion-prev-sibling': [['alt+up']], +}; + +export const INSERT_MODE_MAPPINGS: HotkeyMapping = Object.assign({ + 'move-cursor-insert': [[motionKey]], + 'toggle-help': [['ctrl+?'], ['meta+?']], + 'fold-toggle': [], + 'fold-open': [['meta+down']], + 'fold-close': [['meta+up']], + 'delete-blocks': [['meta+shift+delete'], ['meta+backspace']], + 'delete-char-after': [['delete']], + 'delete-char-before': [['backspace'], ['shift+backspace']], + 'delete-to-line-beginning': [['ctrl+u']], + 'delete-to-line-end': [['ctrl+k']], + 'delete-to-word-beginning': [['alt+delete']], + // NOTE: paste-after doesn't make much sense for insert mode + 'paste-before': [['ctrl+y']], + 'split-line': [['enter']], + 'scroll-down': [['page down']], + 'scroll-up': [['page up']], + 'undo': [['ctrl+z'], ['meta+z']], + 'redo': [['ctrl+Z'], ['meta+Z'], ['meta+y']], + 'unindent-row': [], + 'indent-row': [], + 'unindent-blocks': [['shift+tab']], + 'indent-blocks': [['tab']], + 'search': [['esc', 'ctrl+f']], + 'swap-block-down': [['meta+shift+up']], + 'swap-block-up': [['meta+shift+down']], + 'toggle-cursor-bold': [['meta+b']], + 'toggle-cursor-italic': [['meta+i']], + 'toggle-cursor-underline': [['meta+u']], + // NOTE: in workflowy, this also crosses out children + 'toggle-cursor-strikethrough': [['meta+enter']], + 'zoom-prev-sibling': [], + 'zoom-next-sibling': [], + 'zoom-in': [], + 'zoom-out': [['meta+<']], + 'zoom-cursor': [['meta+>']], + 'zoom-root': [], +}, INSERT_MOTION_MAPPINGS); + +export const SEARCH_MODE_MAPPINGS: HotkeyMapping = Object.assign({ + 'move-cursor-search': [[motionKey]], + 'toggle-help': [['ctrl+?']], + 'exit-mode': [['esc'], ['ctrl+c']], + 'search-delete-char-after': [['delete']], + 'search-delete-char-before': [['backspace'], ['shift+backspace']], + 'search-select': [['enter']], + 'search-up': [['ctrl+k'], ['up'], ['shift+tab']], + 'search-down': [['ctrl+j'], ['down'], ['tab']], +}, _.pick(INSERT_MOTION_MAPPINGS, SINGLE_LINE_MOTIONS)); + +export const SETTINGS_MODE_MAPPINGS: HotkeyMapping = { + 'exit-mode': [['esc'], ['ctrl+c']], +}; + +// TODO fix this +const defaultData: Array = [ + 'Welcome to vimflowy!', + { text: 'Features', children: [ + { text: 'Workflowy features', children: [ + { text: 'Nested bullets', children: [ + 'Bullets with children can be collapsed', + { text: 'Use enter to zoom into any bullet. Try on this one', collapsed: true, children: [ + 'And shift+enter to zoom all the way back out', + 'Use ] and [ to zoom in and out just one level', + ] }, + { text: 'Use z to toggle collapsedness', collapsed: true, children: [ + 'You found me :)', + ] }, + 'Use tab and shift+tab to indent and unindent blocks', + 'Use < and > to indent and unindent just a single line', + ] }, + { text: 'Text formatting', collapsed: true, children: [ + { + text: 'Bold, italicized, and underlined text. Emphatic!', + properties: { + bold: '.... ........ ', + italic: ' .......... ........ ', + underline: ' .......... ........ ', + }, + }, + { + text: 'Strike through', + children: [ { + text: 'Useful for todo lists', + properties: { + strikethrough: '.....................', + }, + } ], + }, + ] }, + ] }, + 'Press / to start searching for text', + { text: 'Marks', plugins: { mark: 'mark' }, collapsed: true, children: [ + { text: 'I am marked!', plugins: { mark: 'im_a_mark' } }, + 'Press m to start marking a line, and enter to finish', + 'Use \' to search and jump to marks', + 'Link to marks with the @ symbol, like this: @im_a_mark. Use gm to follow the link.', + 'Delete marks by using dm, or just mark with empty string', + ] }, + { text: 'Cloning', collapsed: true, children: [ + { text: 'I am a clone! Try editing me', id: 1 }, + { text: 'Clones can\'t be siblings or descendants of each other', children: [ + { clone: 1 }, + ] }, + 'Make new clones with yc, then p', + ] }, + { text: 'Customizability', collapsed: true, children: [ + { text: 'Plugins system', collapsed: true, children: [ + 'See the settings menu to turn on some plugins!', + 'If you\'re interested in writing plugins, see here: ' + + 'https://github.com/WuTheFWasThat/vimflowy/blob/master/docs/plugins.md', + ] }, + 'Customizable hotkeys (via downloading/uploading a json file)', + 'Different color themes (see Settings)', + ] }, + ] }, + { text: 'Data', collapsed: true, children: [ + { text: 'Backing storage', children: [ + 'Vimflowy was designed to be agnostic to the storage backend', + 'As a user, you are in full control of your data', + 'By default, all data is entirely local', + 'There are no backups, and it is never sent over the internet', + 'However, remote data storage is supported', + 'To manage your data, visit the Settings menu', + ] }, + { text: 'Importing and exporting data', children: [ + 'Two import and export formats are supported.', + 'Check out settings for more information.', + 'You can regularly export your data in JSON format, as a form of backup', + ] }, + ] }, + { text: 'Tips', collapsed: true, children: [ + 'Collapse things often to avoid clutter. Zoom into collapsed bullets', + 'Want to go back to where you were? ctrl+o jumps back in your location history (ctrl+i jumps forward)', + 'Check out the cheat sheet on the right. Once you become an expert, you can hide it', + ] }, + 'Press i to enter insert mode and start adding your own content!', + 'For more info, visit https://github.com/WuTheFWasThat/vimflowy (visit links under the cursor with gx)', +]; + +const config: Config = { + type: 'workflowy', + defaultMode: 'INSERT', + defaultData: defaultData, + // TODO: get the keys from modes.ts + defaultMappings: + new KeyMappings({ + [ 'INSERT' ]: INSERT_MODE_MAPPINGS, + [ 'SEARCH' ]: SEARCH_MODE_MAPPINGS, + [ 'SETTINGS' ]: SETTINGS_MODE_MAPPINGS, + }), +}; +export default config; diff --git a/src/assets/ts/app.tsx b/src/assets/ts/app.tsx index 67f8f138..b1b6364d 100644 --- a/src/assets/ts/app.tsx +++ b/src/assets/ts/app.tsx @@ -36,7 +36,7 @@ import { PluginsManager } from './plugins'; import Path from './path'; import Session from './session'; import { SerializedBlock } from './types'; -import Config from './config'; +import Config, { ConfigType } from './config'; import vimConfig from './configurations/vim'; import keyDefinitions from './keyDefinitions'; @@ -402,7 +402,14 @@ $(document).ready(async () => { dataSource = await settings.getDocSetting('dataSource'); } - const config: Config = vimConfig; + const conf_type: ConfigType = 'vim'; + let config: Config; + if (conf_type === 'vim') { + config = vimConfig; + } else { + // TODO: workflowy config + config = vimConfig; + } if (dataSource === 'firebase') { const [ diff --git a/src/assets/ts/config.ts b/src/assets/ts/config.ts index 46893b98..d38108ed 100644 --- a/src/assets/ts/config.ts +++ b/src/assets/ts/config.ts @@ -5,7 +5,10 @@ import KeyMappings from './keyMappings'; // TODO: starting mode // TODO: starting text (i.e. constants.default_data) +export type ConfigType = 'vim' | 'workflowy'; + type Config = { + type: ConfigType; defaultMode: ModeId; defaultData: Array; // NOTE: can be mutated when there's a mode registered diff --git a/src/assets/ts/configurations/vim.ts b/src/assets/ts/configurations/vim.ts index 583292f6..577608c9 100644 --- a/src/assets/ts/configurations/vim.ts +++ b/src/assets/ts/configurations/vim.ts @@ -154,12 +154,12 @@ export const INSERT_MODE_MAPPINGS: HotkeyMapping = Object.assign({ 'fold-toggle': [['ctrl+space']], 'fold-open': [['meta+down']], 'fold-close': [['meta+up']], - 'delete-blocks': [['meta+shift+delete']], + 'delete-blocks': [['ctrl+delete'], ['ctrl+backspace']], 'delete-char-after': [['delete']], 'delete-char-before': [['backspace'], ['shift+backspace']], 'delete-to-line-beginning': [['ctrl+u']], 'delete-to-line-end': [['ctrl+k']], - 'delete-to-word-beginning': [['ctrl+w']], + 'delete-to-word-beginning': [['ctrl+w'], ['alt+delete']], // NOTE: paste-after doesn't make much sense for insert mode 'paste-before': [['ctrl+y']], 'split-line': [['enter']], @@ -324,6 +324,7 @@ const defaultData: Array = [ ]; const config: Config = { + type: 'vim', defaultMode: 'NORMAL', defaultData: defaultData, // TODO: get the keys from modes.ts diff --git a/test/testcase.ts b/test/testcase.ts index a67d3272..244500ab 100644 --- a/test/testcase.ts +++ b/test/testcase.ts @@ -10,7 +10,7 @@ import Register, { RegisterTypes, SerializedRegister } from '../src/assets/ts/re import '../src/assets/ts/definitions'; import mainDefinitions from '../src/assets/ts/keyDefinitions'; import KeyBindings from '../src/assets/ts/keyBindings'; -import Config from '../src/assets/ts/config'; +import Config, { ConfigType } from '../src/assets/ts/config'; import vimConfig from '../src/assets/ts/configurations/vim'; import KeyHandler from '../src/assets/ts/keyHandler'; import logger, * as Logger from '../src/assets/ts/logger'; @@ -26,10 +26,13 @@ afterEach('empty the queue', () => logger.empty()); // share keybindings across tests, for efficiency // note that the bindings will change when plugins are enabled and disabled // thus, tests are not totally isolated -const keyBindings: KeyBindings = new KeyBindings(mainDefinitions, vimConfig.defaultMappings); +const sharedKeyBindings: {[key: string]: KeyBindings} = { + 'vim': new KeyBindings(mainDefinitions, vimConfig.defaultMappings), +}; type TestCaseOptions = { plugins?: Array, + config?: ConfigType, }; class TestCase { @@ -48,16 +51,18 @@ class TestCase { this.plugins = options.plugins || []; + const bindings: KeyBindings = sharedKeyBindings[options.config || 'vim']; + this.session = new Session(this.document, { viewRoot: Path.root(), }); const config: Config = vimConfig; - this.keyhandler = new KeyHandler(this.session, keyBindings); + this.keyhandler = new KeyHandler(this.session, bindings); this.register = this.session.register; - this.pluginManager = new PluginsManager(this.session, config, keyBindings); + this.pluginManager = new PluginsManager(this.session, config, bindings); this.prom = Promise.resolve(); this.plugins.forEach((pluginName) => {