Loading app/assets/javascripts/ide/components/file_finder/index.vue 0 → 100644 +245 −0 Original line number Original line Diff line number Diff line <script> import { mapActions, mapGetters, mapState } from 'vuex'; import fuzzaldrinPlus from 'fuzzaldrin-plus'; import VirtualList from 'vue-virtual-scroll-list'; import Item from './item.vue'; import router from '../../ide_router'; import { MAX_FILE_FINDER_RESULTS, FILE_FINDER_ROW_HEIGHT, FILE_FINDER_EMPTY_ROW_HEIGHT, } from '../../constants'; import { UP_KEY_CODE, DOWN_KEY_CODE, ENTER_KEY_CODE, ESC_KEY_CODE, } from '../../../lib/utils/keycodes'; export default { components: { Item, VirtualList, }, data() { return { focusedIndex: 0, searchText: '', mouseOver: false, cancelMouseOver: false, }; }, computed: { ...mapGetters(['allBlobs']), ...mapState(['fileFindVisible', 'loading']), filteredBlobs() { const searchText = this.searchText.trim(); if (searchText === '') { return this.allBlobs.slice(0, MAX_FILE_FINDER_RESULTS); } return fuzzaldrinPlus .filter(this.allBlobs, searchText, { key: 'path', maxResults: MAX_FILE_FINDER_RESULTS, }) .sort((a, b) => b.lastOpenedAt - a.lastOpenedAt); }, filteredBlobsLength() { return this.filteredBlobs.length; }, listShowCount() { return this.filteredBlobsLength ? Math.min(this.filteredBlobsLength, 5) : 1; }, listHeight() { return this.filteredBlobsLength ? FILE_FINDER_ROW_HEIGHT : FILE_FINDER_EMPTY_ROW_HEIGHT; }, showClearInputButton() { return this.searchText.trim() !== ''; }, }, watch: { fileFindVisible() { this.$nextTick(() => { if (!this.fileFindVisible) { this.searchText = ''; } else { this.focusedIndex = 0; if (this.$refs.searchInput) { this.$refs.searchInput.focus(); } } }); }, searchText() { this.focusedIndex = 0; }, focusedIndex() { if (!this.mouseOver) { this.$nextTick(() => { const el = this.$refs.virtualScrollList.$el; const scrollTop = this.focusedIndex * FILE_FINDER_ROW_HEIGHT; const bottom = this.listShowCount * FILE_FINDER_ROW_HEIGHT; if (this.focusedIndex === 0) { // if index is the first index, scroll straight to start el.scrollTop = 0; } else if (this.focusedIndex === this.filteredBlobsLength - 1) { // if index is the last index, scroll to the end el.scrollTop = this.filteredBlobsLength * FILE_FINDER_ROW_HEIGHT; } else if (scrollTop >= bottom + el.scrollTop) { // if element is off the bottom of the scroll list, scroll down one item el.scrollTop = scrollTop - bottom + FILE_FINDER_ROW_HEIGHT; } else if (scrollTop < el.scrollTop) { // if element is off the top of the scroll list, scroll up one item el.scrollTop = scrollTop; } }); } }, }, methods: { ...mapActions(['toggleFileFinder']), clearSearchInput() { this.searchText = ''; this.$nextTick(() => { this.$refs.searchInput.focus(); }); }, onKeydown(e) { switch (e.keyCode) { case UP_KEY_CODE: e.preventDefault(); this.mouseOver = false; this.cancelMouseOver = true; if (this.focusedIndex > 0) { this.focusedIndex -= 1; } else { this.focusedIndex = this.filteredBlobsLength - 1; } break; case DOWN_KEY_CODE: e.preventDefault(); this.mouseOver = false; this.cancelMouseOver = true; if (this.focusedIndex < this.filteredBlobsLength - 1) { this.focusedIndex += 1; } else { this.focusedIndex = 0; } break; default: break; } }, onKeyup(e) { switch (e.keyCode) { case ENTER_KEY_CODE: this.openFile(this.filteredBlobs[this.focusedIndex]); break; case ESC_KEY_CODE: this.toggleFileFinder(false); break; default: break; } }, openFile(file) { this.toggleFileFinder(false); router.push(`/project${file.url}`); }, onMouseOver(index) { if (!this.cancelMouseOver) { this.mouseOver = true; this.focusedIndex = index; } }, onMouseMove(index) { this.cancelMouseOver = false; this.onMouseOver(index); }, }, }; </script> <template> <div class="ide-file-finder-overlay" @mousedown.self="toggleFileFinder(false)" > <div class="dropdown-menu diff-file-changes ide-file-finder show" > <div class="dropdown-input"> <input type="search" class="dropdown-input-field" :placeholder="__('Search files')" autocomplete="off" v-model="searchText" ref="searchInput" @keydown="onKeydown($event)" @keyup="onKeyup($event)" /> <i aria-hidden="true" class="fa fa-search dropdown-input-search" :class="{ hidden: showClearInputButton }" ></i> <i role="button" :aria-label="__('Clear search input')" class="fa fa-times dropdown-input-clear" :class="{ show: showClearInputButton }" @click="clearSearchInput" ></i> </div> <div> <virtual-list :size="listHeight" :remain="listShowCount" wtag="ul" ref="virtualScrollList" > <template v-if="filteredBlobsLength"> <li v-for="(file, index) in filteredBlobs" :key="file.key" > <item class="disable-hover" :file="file" :search-text="searchText" :focused="index === focusedIndex" :index="index" @click="openFile" @mouseover="onMouseOver" @mousemove="onMouseMove" /> </li> </template> <li v-else class="dropdown-menu-empty-item" > <div class="append-right-default prepend-left-default prepend-top-8 append-bottom-8"> <template v-if="loading"> {{ __('Loading...') }} </template> <template v-else> {{ __('No files found.') }} </template> </div> </li> </virtual-list> </div> </div> </div> </template> app/assets/javascripts/ide/components/file_finder/item.vue 0 → 100644 +113 −0 Original line number Original line Diff line number Diff line <script> import fuzzaldrinPlus from 'fuzzaldrin-plus'; import FileIcon from '../../../vue_shared/components/file_icon.vue'; import ChangedFileIcon from '../changed_file_icon.vue'; const MAX_PATH_LENGTH = 60; export default { components: { ChangedFileIcon, FileIcon, }, props: { file: { type: Object, required: true, }, focused: { type: Boolean, required: true, }, searchText: { type: String, required: true, }, index: { type: Number, required: true, }, }, computed: { pathWithEllipsis() { const path = this.file.path; return path.length < MAX_PATH_LENGTH ? path : `...${path.substr(path.length - MAX_PATH_LENGTH)}`; }, nameSearchTextOccurences() { return fuzzaldrinPlus.match(this.file.name, this.searchText); }, pathSearchTextOccurences() { return fuzzaldrinPlus.match(this.pathWithEllipsis, this.searchText); }, }, methods: { clickRow() { this.$emit('click', this.file); }, mouseOverRow() { this.$emit('mouseover', this.index); }, mouseMove() { this.$emit('mousemove', this.index); }, }, }; </script> <template> <button type="button" class="diff-changed-file" :class="{ 'is-focused': focused, }" @click.prevent="clickRow" @mouseover="mouseOverRow" @mousemove="mouseMove" > <file-icon :file-name="file.name" :size="16" css-classes="diff-file-changed-icon append-right-8" /> <span class="diff-changed-file-content append-right-8"> <strong class="diff-changed-file-name" > <span v-for="(char, index) in file.name.split('')" :key="index + char" :class="{ highlighted: nameSearchTextOccurences.indexOf(index) >= 0, }" v-text="char" > </span> </strong> <span class="diff-changed-file-path prepend-top-5" > <span v-for="(char, index) in pathWithEllipsis.split('')" :key="index + char" :class="{ highlighted: pathSearchTextOccurences.indexOf(index) >= 0, }" v-text="char" > </span> </span> </span> <span v-if="file.changed || file.tempFile" class="diff-changed-stats" > <changed-file-icon :file="file" /> </span> </button> </template> app/assets/javascripts/ide/components/ide.vue +75 −39 Original line number Original line Diff line number Diff line <script> <script> import { mapState, mapGetters } from 'vuex'; import { mapActions, mapState, mapGetters } from 'vuex'; import Mousetrap from 'mousetrap'; import ideSidebar from './ide_side_bar.vue'; import ideSidebar from './ide_side_bar.vue'; import ideContextbar from './ide_context_bar.vue'; import ideContextbar from './ide_context_bar.vue'; import repoTabs from './repo_tabs.vue'; import repoTabs from './repo_tabs.vue'; import ideStatusBar from './ide_status_bar.vue'; import ideStatusBar from './ide_status_bar.vue'; import repoEditor from './repo_editor.vue'; import repoEditor from './repo_editor.vue'; import FindFile from './file_finder/index.vue'; const originalStopCallback = Mousetrap.stopCallback; export default { export default { components: { components: { Loading @@ -13,6 +17,7 @@ export default { repoTabs, repoTabs, ideStatusBar, ideStatusBar, repoEditor, repoEditor, FindFile, }, }, props: { props: { emptyStateSvgPath: { emptyStateSvgPath: { Loading @@ -29,7 +34,13 @@ export default { }, }, }, }, computed: { computed: { ...mapState(['changedFiles', 'openFiles', 'viewer', 'currentMergeRequestId']), ...mapState([ 'changedFiles', 'openFiles', 'viewer', 'currentMergeRequestId', 'fileFindVisible', ]), ...mapGetters(['activeFile', 'hasChanges']), ...mapGetters(['activeFile', 'hasChanges']), }, }, mounted() { mounted() { Loading @@ -42,6 +53,28 @@ export default { }); }); return returnValue; return returnValue; }; }; Mousetrap.bind(['t', 'command+p', 'ctrl+p'], e => { if (e.preventDefault) { e.preventDefault(); } this.toggleFileFinder(!this.fileFindVisible); }); Mousetrap.stopCallback = (e, el, combo) => this.mousetrapStopCallback(e, el, combo); }, methods: { ...mapActions(['toggleFileFinder']), mousetrapStopCallback(e, el, combo) { if (combo === 't' && el.classList.contains('dropdown-input-field')) { return true; } else if (combo === 'command+p' || combo === 'ctrl+p') { return false; } return originalStopCallback(e, el, combo); }, }, }, }; }; </script> </script> Loading @@ -50,6 +83,9 @@ export default { <div <div class="ide-view" class="ide-view" > > <find-file v-show="fileFindVisible" /> <ide-sidebar /> <ide-sidebar /> <div <div class="multi-file-edit-pane" class="multi-file-edit-pane" Loading app/assets/javascripts/ide/constants.js +5 −0 Original line number Original line Diff line number Diff line // Fuzzy file finder // Fuzzy file finder export const MAX_FILE_FINDER_RESULTS = 40; export const FILE_FINDER_ROW_HEIGHT = 55; export const FILE_FINDER_EMPTY_ROW_HEIGHT = 33; // Commit message textarea export const MAX_TITLE_LENGTH = 50; export const MAX_TITLE_LENGTH = 50; export const MAX_BODY_LENGTH = 72; export const MAX_BODY_LENGTH = 72; app/assets/javascripts/ide/lib/editor.js +33 −0 Original line number Original line Diff line number Diff line import _ from 'underscore'; import _ from 'underscore'; import store from '../stores'; import DecorationsController from './decorations/controller'; import DecorationsController from './decorations/controller'; import DirtyDiffController from './diff/controller'; import DirtyDiffController from './diff/controller'; import Disposable from './common/disposable'; import Disposable from './common/disposable'; import ModelManager from './common/model_manager'; import ModelManager from './common/model_manager'; import editorOptions, { defaultEditorOptions } from './editor_options'; import editorOptions, { defaultEditorOptions } from './editor_options'; import gitlabTheme from './themes/gl_theme'; import gitlabTheme from './themes/gl_theme'; import keymap from './keymap.json'; export const clearDomElement = el => { export const clearDomElement = el => { if (!el || !el.firstChild) return; if (!el || !el.firstChild) return; Loading Loading @@ -53,6 +55,8 @@ export default class Editor { )), )), ); ); this.addCommands(); window.addEventListener('resize', this.debouncedUpdate, false); window.addEventListener('resize', this.debouncedUpdate, false); } } } } Loading @@ -73,6 +77,8 @@ export default class Editor { })), })), ); ); this.addCommands(); window.addEventListener('resize', this.debouncedUpdate, false); window.addEventListener('resize', this.debouncedUpdate, false); } } } } Loading Loading @@ -189,4 +195,31 @@ export default class Editor { static renderSideBySide(domElement) { static renderSideBySide(domElement) { return domElement.offsetWidth >= 700; return domElement.offsetWidth >= 700; } } addCommands() { const getKeyCode = key => { const monacoKeyMod = key.indexOf('KEY_') === 0; return monacoKeyMod ? this.monaco.KeyCode[key] : this.monaco.KeyMod[key]; }; keymap.forEach(command => { const keybindings = command.bindings.map(binding => { const keys = binding.split('+'); // eslint-disable-next-line no-bitwise return keys.length > 1 ? getKeyCode(keys[0]) | getKeyCode(keys[1]) : getKeyCode(keys[0]); }); this.instance.addAction({ id: command.id, label: command.label, keybindings, run() { store.dispatch(command.action.name, command.action.params); return null; }, }); }); } } } Loading
app/assets/javascripts/ide/components/file_finder/index.vue 0 → 100644 +245 −0 Original line number Original line Diff line number Diff line <script> import { mapActions, mapGetters, mapState } from 'vuex'; import fuzzaldrinPlus from 'fuzzaldrin-plus'; import VirtualList from 'vue-virtual-scroll-list'; import Item from './item.vue'; import router from '../../ide_router'; import { MAX_FILE_FINDER_RESULTS, FILE_FINDER_ROW_HEIGHT, FILE_FINDER_EMPTY_ROW_HEIGHT, } from '../../constants'; import { UP_KEY_CODE, DOWN_KEY_CODE, ENTER_KEY_CODE, ESC_KEY_CODE, } from '../../../lib/utils/keycodes'; export default { components: { Item, VirtualList, }, data() { return { focusedIndex: 0, searchText: '', mouseOver: false, cancelMouseOver: false, }; }, computed: { ...mapGetters(['allBlobs']), ...mapState(['fileFindVisible', 'loading']), filteredBlobs() { const searchText = this.searchText.trim(); if (searchText === '') { return this.allBlobs.slice(0, MAX_FILE_FINDER_RESULTS); } return fuzzaldrinPlus .filter(this.allBlobs, searchText, { key: 'path', maxResults: MAX_FILE_FINDER_RESULTS, }) .sort((a, b) => b.lastOpenedAt - a.lastOpenedAt); }, filteredBlobsLength() { return this.filteredBlobs.length; }, listShowCount() { return this.filteredBlobsLength ? Math.min(this.filteredBlobsLength, 5) : 1; }, listHeight() { return this.filteredBlobsLength ? FILE_FINDER_ROW_HEIGHT : FILE_FINDER_EMPTY_ROW_HEIGHT; }, showClearInputButton() { return this.searchText.trim() !== ''; }, }, watch: { fileFindVisible() { this.$nextTick(() => { if (!this.fileFindVisible) { this.searchText = ''; } else { this.focusedIndex = 0; if (this.$refs.searchInput) { this.$refs.searchInput.focus(); } } }); }, searchText() { this.focusedIndex = 0; }, focusedIndex() { if (!this.mouseOver) { this.$nextTick(() => { const el = this.$refs.virtualScrollList.$el; const scrollTop = this.focusedIndex * FILE_FINDER_ROW_HEIGHT; const bottom = this.listShowCount * FILE_FINDER_ROW_HEIGHT; if (this.focusedIndex === 0) { // if index is the first index, scroll straight to start el.scrollTop = 0; } else if (this.focusedIndex === this.filteredBlobsLength - 1) { // if index is the last index, scroll to the end el.scrollTop = this.filteredBlobsLength * FILE_FINDER_ROW_HEIGHT; } else if (scrollTop >= bottom + el.scrollTop) { // if element is off the bottom of the scroll list, scroll down one item el.scrollTop = scrollTop - bottom + FILE_FINDER_ROW_HEIGHT; } else if (scrollTop < el.scrollTop) { // if element is off the top of the scroll list, scroll up one item el.scrollTop = scrollTop; } }); } }, }, methods: { ...mapActions(['toggleFileFinder']), clearSearchInput() { this.searchText = ''; this.$nextTick(() => { this.$refs.searchInput.focus(); }); }, onKeydown(e) { switch (e.keyCode) { case UP_KEY_CODE: e.preventDefault(); this.mouseOver = false; this.cancelMouseOver = true; if (this.focusedIndex > 0) { this.focusedIndex -= 1; } else { this.focusedIndex = this.filteredBlobsLength - 1; } break; case DOWN_KEY_CODE: e.preventDefault(); this.mouseOver = false; this.cancelMouseOver = true; if (this.focusedIndex < this.filteredBlobsLength - 1) { this.focusedIndex += 1; } else { this.focusedIndex = 0; } break; default: break; } }, onKeyup(e) { switch (e.keyCode) { case ENTER_KEY_CODE: this.openFile(this.filteredBlobs[this.focusedIndex]); break; case ESC_KEY_CODE: this.toggleFileFinder(false); break; default: break; } }, openFile(file) { this.toggleFileFinder(false); router.push(`/project${file.url}`); }, onMouseOver(index) { if (!this.cancelMouseOver) { this.mouseOver = true; this.focusedIndex = index; } }, onMouseMove(index) { this.cancelMouseOver = false; this.onMouseOver(index); }, }, }; </script> <template> <div class="ide-file-finder-overlay" @mousedown.self="toggleFileFinder(false)" > <div class="dropdown-menu diff-file-changes ide-file-finder show" > <div class="dropdown-input"> <input type="search" class="dropdown-input-field" :placeholder="__('Search files')" autocomplete="off" v-model="searchText" ref="searchInput" @keydown="onKeydown($event)" @keyup="onKeyup($event)" /> <i aria-hidden="true" class="fa fa-search dropdown-input-search" :class="{ hidden: showClearInputButton }" ></i> <i role="button" :aria-label="__('Clear search input')" class="fa fa-times dropdown-input-clear" :class="{ show: showClearInputButton }" @click="clearSearchInput" ></i> </div> <div> <virtual-list :size="listHeight" :remain="listShowCount" wtag="ul" ref="virtualScrollList" > <template v-if="filteredBlobsLength"> <li v-for="(file, index) in filteredBlobs" :key="file.key" > <item class="disable-hover" :file="file" :search-text="searchText" :focused="index === focusedIndex" :index="index" @click="openFile" @mouseover="onMouseOver" @mousemove="onMouseMove" /> </li> </template> <li v-else class="dropdown-menu-empty-item" > <div class="append-right-default prepend-left-default prepend-top-8 append-bottom-8"> <template v-if="loading"> {{ __('Loading...') }} </template> <template v-else> {{ __('No files found.') }} </template> </div> </li> </virtual-list> </div> </div> </div> </template>
app/assets/javascripts/ide/components/file_finder/item.vue 0 → 100644 +113 −0 Original line number Original line Diff line number Diff line <script> import fuzzaldrinPlus from 'fuzzaldrin-plus'; import FileIcon from '../../../vue_shared/components/file_icon.vue'; import ChangedFileIcon from '../changed_file_icon.vue'; const MAX_PATH_LENGTH = 60; export default { components: { ChangedFileIcon, FileIcon, }, props: { file: { type: Object, required: true, }, focused: { type: Boolean, required: true, }, searchText: { type: String, required: true, }, index: { type: Number, required: true, }, }, computed: { pathWithEllipsis() { const path = this.file.path; return path.length < MAX_PATH_LENGTH ? path : `...${path.substr(path.length - MAX_PATH_LENGTH)}`; }, nameSearchTextOccurences() { return fuzzaldrinPlus.match(this.file.name, this.searchText); }, pathSearchTextOccurences() { return fuzzaldrinPlus.match(this.pathWithEllipsis, this.searchText); }, }, methods: { clickRow() { this.$emit('click', this.file); }, mouseOverRow() { this.$emit('mouseover', this.index); }, mouseMove() { this.$emit('mousemove', this.index); }, }, }; </script> <template> <button type="button" class="diff-changed-file" :class="{ 'is-focused': focused, }" @click.prevent="clickRow" @mouseover="mouseOverRow" @mousemove="mouseMove" > <file-icon :file-name="file.name" :size="16" css-classes="diff-file-changed-icon append-right-8" /> <span class="diff-changed-file-content append-right-8"> <strong class="diff-changed-file-name" > <span v-for="(char, index) in file.name.split('')" :key="index + char" :class="{ highlighted: nameSearchTextOccurences.indexOf(index) >= 0, }" v-text="char" > </span> </strong> <span class="diff-changed-file-path prepend-top-5" > <span v-for="(char, index) in pathWithEllipsis.split('')" :key="index + char" :class="{ highlighted: pathSearchTextOccurences.indexOf(index) >= 0, }" v-text="char" > </span> </span> </span> <span v-if="file.changed || file.tempFile" class="diff-changed-stats" > <changed-file-icon :file="file" /> </span> </button> </template>
app/assets/javascripts/ide/components/ide.vue +75 −39 Original line number Original line Diff line number Diff line <script> <script> import { mapState, mapGetters } from 'vuex'; import { mapActions, mapState, mapGetters } from 'vuex'; import Mousetrap from 'mousetrap'; import ideSidebar from './ide_side_bar.vue'; import ideSidebar from './ide_side_bar.vue'; import ideContextbar from './ide_context_bar.vue'; import ideContextbar from './ide_context_bar.vue'; import repoTabs from './repo_tabs.vue'; import repoTabs from './repo_tabs.vue'; import ideStatusBar from './ide_status_bar.vue'; import ideStatusBar from './ide_status_bar.vue'; import repoEditor from './repo_editor.vue'; import repoEditor from './repo_editor.vue'; import FindFile from './file_finder/index.vue'; const originalStopCallback = Mousetrap.stopCallback; export default { export default { components: { components: { Loading @@ -13,6 +17,7 @@ export default { repoTabs, repoTabs, ideStatusBar, ideStatusBar, repoEditor, repoEditor, FindFile, }, }, props: { props: { emptyStateSvgPath: { emptyStateSvgPath: { Loading @@ -29,7 +34,13 @@ export default { }, }, }, }, computed: { computed: { ...mapState(['changedFiles', 'openFiles', 'viewer', 'currentMergeRequestId']), ...mapState([ 'changedFiles', 'openFiles', 'viewer', 'currentMergeRequestId', 'fileFindVisible', ]), ...mapGetters(['activeFile', 'hasChanges']), ...mapGetters(['activeFile', 'hasChanges']), }, }, mounted() { mounted() { Loading @@ -42,6 +53,28 @@ export default { }); }); return returnValue; return returnValue; }; }; Mousetrap.bind(['t', 'command+p', 'ctrl+p'], e => { if (e.preventDefault) { e.preventDefault(); } this.toggleFileFinder(!this.fileFindVisible); }); Mousetrap.stopCallback = (e, el, combo) => this.mousetrapStopCallback(e, el, combo); }, methods: { ...mapActions(['toggleFileFinder']), mousetrapStopCallback(e, el, combo) { if (combo === 't' && el.classList.contains('dropdown-input-field')) { return true; } else if (combo === 'command+p' || combo === 'ctrl+p') { return false; } return originalStopCallback(e, el, combo); }, }, }, }; }; </script> </script> Loading @@ -50,6 +83,9 @@ export default { <div <div class="ide-view" class="ide-view" > > <find-file v-show="fileFindVisible" /> <ide-sidebar /> <ide-sidebar /> <div <div class="multi-file-edit-pane" class="multi-file-edit-pane" Loading
app/assets/javascripts/ide/constants.js +5 −0 Original line number Original line Diff line number Diff line // Fuzzy file finder // Fuzzy file finder export const MAX_FILE_FINDER_RESULTS = 40; export const FILE_FINDER_ROW_HEIGHT = 55; export const FILE_FINDER_EMPTY_ROW_HEIGHT = 33; // Commit message textarea export const MAX_TITLE_LENGTH = 50; export const MAX_TITLE_LENGTH = 50; export const MAX_BODY_LENGTH = 72; export const MAX_BODY_LENGTH = 72;
app/assets/javascripts/ide/lib/editor.js +33 −0 Original line number Original line Diff line number Diff line import _ from 'underscore'; import _ from 'underscore'; import store from '../stores'; import DecorationsController from './decorations/controller'; import DecorationsController from './decorations/controller'; import DirtyDiffController from './diff/controller'; import DirtyDiffController from './diff/controller'; import Disposable from './common/disposable'; import Disposable from './common/disposable'; import ModelManager from './common/model_manager'; import ModelManager from './common/model_manager'; import editorOptions, { defaultEditorOptions } from './editor_options'; import editorOptions, { defaultEditorOptions } from './editor_options'; import gitlabTheme from './themes/gl_theme'; import gitlabTheme from './themes/gl_theme'; import keymap from './keymap.json'; export const clearDomElement = el => { export const clearDomElement = el => { if (!el || !el.firstChild) return; if (!el || !el.firstChild) return; Loading Loading @@ -53,6 +55,8 @@ export default class Editor { )), )), ); ); this.addCommands(); window.addEventListener('resize', this.debouncedUpdate, false); window.addEventListener('resize', this.debouncedUpdate, false); } } } } Loading @@ -73,6 +77,8 @@ export default class Editor { })), })), ); ); this.addCommands(); window.addEventListener('resize', this.debouncedUpdate, false); window.addEventListener('resize', this.debouncedUpdate, false); } } } } Loading Loading @@ -189,4 +195,31 @@ export default class Editor { static renderSideBySide(domElement) { static renderSideBySide(domElement) { return domElement.offsetWidth >= 700; return domElement.offsetWidth >= 700; } } addCommands() { const getKeyCode = key => { const monacoKeyMod = key.indexOf('KEY_') === 0; return monacoKeyMod ? this.monaco.KeyCode[key] : this.monaco.KeyMod[key]; }; keymap.forEach(command => { const keybindings = command.bindings.map(binding => { const keys = binding.split('+'); // eslint-disable-next-line no-bitwise return keys.length > 1 ? getKeyCode(keys[0]) | getKeyCode(keys[1]) : getKeyCode(keys[0]); }); this.instance.addAction({ id: command.id, label: command.label, keybindings, run() { store.dispatch(command.action.name, command.action.params); return null; }, }); }); } } }