mirror of
https://github.com/overleaf/overleaf.git
synced 2025-04-26 16:12:08 +00:00
Merge pull request #3441 from overleaf/3803-hotkeys-modal
Migrate hotkeys modal to React GitOrigin-RevId: 78399d3d62771cd296bdc2f4f8b1083263d31551
This commit is contained in:
parent
fb7dffaa88
commit
771a39f267
11 changed files with 328 additions and 164 deletions
|
@ -110,8 +110,6 @@ block content
|
|||
else
|
||||
include ./editor/chat
|
||||
|
||||
include ./editor/hotkeys
|
||||
|
||||
script(type="text/ng-template", id="genericMessageModalTemplate")
|
||||
.modal-header
|
||||
button.close(
|
||||
|
|
|
@ -1,113 +0,0 @@
|
|||
script(type="text/ng-template", id="hotkeysModalTemplate")
|
||||
.modal-header
|
||||
button.close(
|
||||
type="button"
|
||||
data-dismiss="modal"
|
||||
ng-click="cancel()"
|
||||
aria-label="Close"
|
||||
)
|
||||
span(aria-hidden="true") ×
|
||||
h3 #{translate("hotkeys")}
|
||||
.modal-body.modal-hotkeys
|
||||
h3 #{translate("common")}
|
||||
.row
|
||||
.col-xs-4
|
||||
.hotkey
|
||||
span.combination {{ctrl}} + F
|
||||
span.description Find (and replace)
|
||||
.hotkey
|
||||
span.combination {{ctrl}} + Enter
|
||||
span.description Compile
|
||||
.col-xs-4
|
||||
.hotkey
|
||||
span.combination {{ctrl}} + Z
|
||||
span.description Undo
|
||||
.col-xs-4
|
||||
.hotkey
|
||||
span.combination {{ctrl}} + Y
|
||||
span.description Redo
|
||||
h3 #{translate("navigation")}
|
||||
.row
|
||||
.col-xs-4
|
||||
.hotkey
|
||||
span.combination {{ctrl}} + Home
|
||||
span.description Beginning of document
|
||||
.col-xs-4
|
||||
.hotkey
|
||||
span.combination {{ctrl}} + End
|
||||
span.description End of document
|
||||
.col-xs-4
|
||||
.hotkey
|
||||
span.combination {{ctrl}} + L
|
||||
span.description Go To Line
|
||||
h3 #{translate("editing")}
|
||||
.row
|
||||
.col-xs-4
|
||||
.hotkey
|
||||
span.combination {{ctrl}} + /
|
||||
span.description Toggle Comment
|
||||
.hotkey
|
||||
span.combination {{ctrl}} + D
|
||||
span.description Delete Current Line
|
||||
.hotkey
|
||||
span.combination {{ctrl}} + A
|
||||
span.description Select All
|
||||
.col-xs-4
|
||||
.hotkey
|
||||
span.combination Ctrl + U
|
||||
span.description To Uppercase
|
||||
.hotkey
|
||||
span.combination Ctrl + Shift + U
|
||||
span.description To Lowercase
|
||||
.hotkey
|
||||
span.combination Tab
|
||||
span.description Indent Selection
|
||||
.col-xs-4
|
||||
.hotkey
|
||||
span.combination {{ctrl}} + B
|
||||
span.description Bold text
|
||||
.hotkey
|
||||
span.combination {{ctrl}} + I
|
||||
span.description Italic Text
|
||||
|
||||
h3 #{translate("autocomplete")}
|
||||
.row
|
||||
.col-xs-4
|
||||
.hotkey
|
||||
span.combination Ctrl + Space
|
||||
span.description Autocomplete Menu
|
||||
.col-xs-4
|
||||
.hotkey
|
||||
span.combination Tab / Up / Down
|
||||
span.description Select Candidate
|
||||
.col-xs-4
|
||||
.hotkey
|
||||
span.combination Enter
|
||||
span.description Insert Candidate
|
||||
|
||||
h3 !{translate("autocomplete_references")}
|
||||
.row
|
||||
.col-xs-4
|
||||
.hotkey
|
||||
span.combination Ctrl + Space
|
||||
span.description Search References
|
||||
|
||||
h3(ng-if="trackChangesVisible") #{translate("review")}
|
||||
.row(ng-if="trackChangesVisible")
|
||||
.col-xs-4
|
||||
.hotkey
|
||||
span.combination {{ctrl}} + J
|
||||
span.description Toggle review panel
|
||||
.col-xs-4
|
||||
.hotkey
|
||||
span.combination {{ctrl}} + Shift + A
|
||||
span.description Toggle track changes
|
||||
.col-xs-4
|
||||
.hotkey
|
||||
span.combination {{ctrl}} + Shift + C
|
||||
span.description Add a comment
|
||||
|
||||
.modal-footer
|
||||
button.btn.btn-default(
|
||||
ng-click="cancel()"
|
||||
) #{translate("ok")}
|
|
@ -201,10 +201,16 @@ aside#left-menu.full-size(
|
|||
|
||||
h4 #{translate("help")}
|
||||
ul.list-unstyled.nav
|
||||
li(ng-controller="HotkeysController")
|
||||
li(ng-controller="HotkeysModalController")
|
||||
a(ng-click="openHotkeysModal()")
|
||||
i.fa.fa-keyboard-o.fa-fw
|
||||
| #{translate("show_hotkeys")}
|
||||
|
||||
hotkeys-modal(
|
||||
handle-hide="handleHide"
|
||||
show="show"
|
||||
track-changes-visible="trackChangesVisible"
|
||||
)
|
||||
if showSupport
|
||||
li
|
||||
a(href='/learn', target="_blank")
|
||||
|
|
|
@ -2,10 +2,13 @@
|
|||
"auto_compile",
|
||||
"autocompile_disabled_reason",
|
||||
"autocompile_disabled",
|
||||
"autocomplete",
|
||||
"autocomplete_references",
|
||||
"clear_cached_files",
|
||||
"clsi_maintenance",
|
||||
"clsi_unavailable",
|
||||
"collapse",
|
||||
"common",
|
||||
"compile_error_description",
|
||||
"compile_error_entry_description",
|
||||
"compile_mode",
|
||||
|
@ -16,6 +19,7 @@
|
|||
"download_file",
|
||||
"download_pdf",
|
||||
"duplicate_paths_found",
|
||||
"editing",
|
||||
"expand",
|
||||
"fast",
|
||||
"file_outline",
|
||||
|
@ -27,6 +31,7 @@
|
|||
"full_screen",
|
||||
"go_to_error_location",
|
||||
"hide_outline",
|
||||
"hotkeys",
|
||||
"ignore_validation_errors",
|
||||
"latex_error",
|
||||
"learn_how_to_make_documents_compile_quickly",
|
||||
|
@ -34,6 +39,7 @@
|
|||
"log_entry_description",
|
||||
"log_hint_extra_info",
|
||||
"main_file_not_found",
|
||||
"navigation",
|
||||
"n_errors_plural",
|
||||
"n_errors",
|
||||
"n_warnings_plural",
|
||||
|
@ -58,6 +64,7 @@
|
|||
"raw_logs",
|
||||
"recompile_from_scratch",
|
||||
"recompile",
|
||||
"review",
|
||||
"run_syntax_check_now",
|
||||
"send_first_message",
|
||||
"server_error",
|
||||
|
|
|
@ -0,0 +1,177 @@
|
|||
import React from 'react'
|
||||
import { Button, Modal, Row, Col } from 'react-bootstrap'
|
||||
import PropTypes from 'prop-types'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
|
||||
function HotkeysModalContent({
|
||||
handleHide,
|
||||
isMac = false,
|
||||
trackChangesVisible = false
|
||||
}) {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const ctrl = isMac ? 'Cmd' : 'Ctrl'
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal.Header closeButton>
|
||||
<Modal.Title>{t('hotkeys')}</Modal.Title>
|
||||
</Modal.Header>
|
||||
|
||||
<Modal.Body className="modal-hotkeys">
|
||||
<h3>{t('common')}</h3>
|
||||
|
||||
<Row>
|
||||
<Col xs={4}>
|
||||
<Hotkey
|
||||
combination={`${ctrl} + F`}
|
||||
description="Find (and replace)"
|
||||
/>
|
||||
<Hotkey combination={`${ctrl} + Enter`} description="Compile" />
|
||||
</Col>
|
||||
<Col xs={4}>
|
||||
<Hotkey combination={`${ctrl} + Z`} description="Undo" />
|
||||
</Col>
|
||||
<Col xs={4}>
|
||||
<Hotkey combination={`${ctrl} + Y`} description="Redo" />
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<h3>{t('navigation')}</h3>
|
||||
|
||||
<Row>
|
||||
<Col xs={4}>
|
||||
<Hotkey
|
||||
combination={`${ctrl} + Home`}
|
||||
description="Beginning of document"
|
||||
/>
|
||||
</Col>
|
||||
<Col xs={4}>
|
||||
<Hotkey
|
||||
combination={`${ctrl} + End`}
|
||||
description="End of document"
|
||||
/>
|
||||
</Col>
|
||||
<Col xs={4}>
|
||||
<Hotkey combination={`${ctrl} + L`} description="Go To Line" />
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<h3>{t('editing')}</h3>
|
||||
|
||||
<Row>
|
||||
<Col xs={4}>
|
||||
<Hotkey combination={`${ctrl} + /`} description="Toggle Comment" />
|
||||
<Hotkey
|
||||
combination={`${ctrl} + D`}
|
||||
description="Delete Current Line"
|
||||
/>
|
||||
<Hotkey combination={`${ctrl} + A`} description="Select All" />
|
||||
</Col>
|
||||
|
||||
<Col xs={4}>
|
||||
<Hotkey combination={`${ctrl} + U`} description="To Uppercase" />
|
||||
<Hotkey
|
||||
combination={`${ctrl} + Shift + U`}
|
||||
description="To Lowercase"
|
||||
/>
|
||||
<Hotkey combination={`Tab`} description="Indent Selection" />
|
||||
</Col>
|
||||
|
||||
<Col xs={4}>
|
||||
<Hotkey combination={`${ctrl} + B`} description="Bold text" />
|
||||
<Hotkey combination={`${ctrl} + I`} description="Italic Text" />
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<h3>{t('autocomplete')}</h3>
|
||||
|
||||
<Row>
|
||||
<Col xs={4}>
|
||||
<Hotkey
|
||||
combination={`${ctrl} + Space`}
|
||||
description="Autocomplete Menu"
|
||||
/>
|
||||
</Col>
|
||||
<Col xs={4}>
|
||||
<Hotkey
|
||||
combination="Tab / Up / Down"
|
||||
description="Select Candidate"
|
||||
/>
|
||||
</Col>
|
||||
<Col xs={4}>
|
||||
<Hotkey combination="Enter" description="Insert Candidate" />
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<h3>
|
||||
<Trans
|
||||
i18nKey="autocomplete_references"
|
||||
components={{ code: <code /> }}
|
||||
/>
|
||||
</h3>
|
||||
|
||||
<Row>
|
||||
<Col xs={4}>
|
||||
<Hotkey
|
||||
combination={`${ctrl} + Space `}
|
||||
description="Search References"
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
{trackChangesVisible && (
|
||||
<>
|
||||
<h3>{t('review')}</h3>
|
||||
|
||||
<Row>
|
||||
<Col xs={4}>
|
||||
<Hotkey
|
||||
combination={`${ctrl} + J`}
|
||||
description="Toggle review panel"
|
||||
/>
|
||||
</Col>
|
||||
<Col xs={4}>
|
||||
<Hotkey
|
||||
combination={`${ctrl} + Shift + A`}
|
||||
description="Toggle track changes"
|
||||
/>
|
||||
</Col>
|
||||
<Col xs={4}>
|
||||
<Hotkey
|
||||
combination={`${ctrl} + Shift + C`}
|
||||
description="Add a comment"
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
</>
|
||||
)}
|
||||
</Modal.Body>
|
||||
|
||||
<Modal.Footer>
|
||||
<Button onClick={handleHide}>{t('ok')}</Button>
|
||||
</Modal.Footer>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
HotkeysModalContent.propTypes = {
|
||||
isMac: PropTypes.bool,
|
||||
handleHide: PropTypes.func.isRequired,
|
||||
trackChangesVisible: PropTypes.bool
|
||||
}
|
||||
|
||||
function Hotkey({ combination, description }) {
|
||||
return (
|
||||
<div className="hotkey" data-test-selector="hotkey">
|
||||
<span className="combination">{combination}</span>
|
||||
<span className="description">{description}</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Hotkey.propTypes = {
|
||||
combination: PropTypes.string.isRequired,
|
||||
description: PropTypes.string.isRequired
|
||||
}
|
||||
|
||||
export default HotkeysModalContent
|
|
@ -0,0 +1,26 @@
|
|||
import React from 'react'
|
||||
import { Modal } from 'react-bootstrap'
|
||||
import PropTypes from 'prop-types'
|
||||
import HotkeysModalContent from './hotkeys-modal-content'
|
||||
|
||||
function HotkeysModal({ handleHide, show, trackChangesVisible = false }) {
|
||||
const isMac = /Mac/i.test(navigator.platform)
|
||||
|
||||
return (
|
||||
<Modal bsSize="large" onHide={handleHide} show={show}>
|
||||
<HotkeysModalContent
|
||||
handleHide={handleHide}
|
||||
isMac={isMac}
|
||||
trackChangesVisible={trackChangesVisible}
|
||||
/>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
HotkeysModal.propTypes = {
|
||||
handleHide: PropTypes.func.isRequired,
|
||||
show: PropTypes.bool.isRequired,
|
||||
trackChangesVisible: PropTypes.bool
|
||||
}
|
||||
|
||||
export default HotkeysModal
|
|
@ -0,0 +1,25 @@
|
|||
import App from '../../../base'
|
||||
import { react2angular } from 'react2angular'
|
||||
|
||||
import HotkeysModal from '../components/hotkeys-modal'
|
||||
|
||||
App.component('hotkeysModal', react2angular(HotkeysModal))
|
||||
|
||||
export default App.controller('HotkeysModalController', function($scope) {
|
||||
$scope.show = false
|
||||
|
||||
$scope.handleHide = () => {
|
||||
$scope.$applyAsync(() => {
|
||||
$scope.show = false
|
||||
})
|
||||
}
|
||||
|
||||
$scope.openHotkeysModal = () => {
|
||||
$scope.trackChangesVisible =
|
||||
$scope.project && $scope.project.features.trackChangesVisible
|
||||
|
||||
$scope.$applyAsync(() => {
|
||||
$scope.show = true
|
||||
})
|
||||
}
|
||||
})
|
|
@ -1,47 +0,0 @@
|
|||
/* eslint-disable
|
||||
camelcase,
|
||||
max-len,
|
||||
no-return-assign,
|
||||
*/
|
||||
// TODO: This file was created by bulk-decaffeinate.
|
||||
// Fix any style issues and re-enable lint.
|
||||
/*
|
||||
* decaffeinate suggestions:
|
||||
* DS102: Remove unnecessary code created because of implicit returns
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
import App from '../../../base'
|
||||
import 'ace/ace'
|
||||
App.controller(
|
||||
'HotkeysController',
|
||||
($scope, $modal, eventTracking) =>
|
||||
($scope.openHotkeysModal = function() {
|
||||
eventTracking.sendMB('ide-open-hotkeys-modal')
|
||||
|
||||
return $modal.open({
|
||||
templateUrl: 'hotkeysModalTemplate',
|
||||
controller: 'HotkeysModalController',
|
||||
size: 'lg',
|
||||
resolve: {
|
||||
trackChangesVisible() {
|
||||
return $scope.project.features.trackChangesVisible
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
)
|
||||
|
||||
export default App.controller('HotkeysModalController', function(
|
||||
$scope,
|
||||
$modalInstance,
|
||||
trackChangesVisible
|
||||
) {
|
||||
$scope.trackChangesVisible = trackChangesVisible
|
||||
if (ace.require('ace/lib/useragent').isMac) {
|
||||
$scope.ctrl = 'Cmd'
|
||||
} else {
|
||||
$scope.ctrl = 'Ctrl'
|
||||
}
|
||||
|
||||
return ($scope.cancel = () => $modalInstance.dismiss())
|
||||
})
|
|
@ -3,4 +3,4 @@
|
|||
// TODO: This file was created by bulk-decaffeinate.
|
||||
// Fix any style issues and re-enable lint.
|
||||
import './BackspaceHighjack'
|
||||
import './controllers/HotkeysController'
|
||||
import '../../features/hotkeys-modal/controllers/hotkeys-modal-controller'
|
||||
|
|
24
services/web/frontend/stories/hotkeys-modal.stories.js
Normal file
24
services/web/frontend/stories/hotkeys-modal.stories.js
Normal file
|
@ -0,0 +1,24 @@
|
|||
import React from 'react'
|
||||
|
||||
import HotkeysModalContent from '../js/features/hotkeys-modal/components/hotkeys-modal-content'
|
||||
|
||||
// NOTE: HotkeysModalContent is wrapped in modal classes, without modal behaviours
|
||||
export const Basic = args => (
|
||||
<div className="modal-lg modal-dialog">
|
||||
<div className="modal-content">
|
||||
<HotkeysModalContent {...args} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
export default {
|
||||
title: 'Hotkeys Modal',
|
||||
component: HotkeysModalContent,
|
||||
args: {
|
||||
isMac: true,
|
||||
trackChangesVisible: true
|
||||
},
|
||||
argTypes: {
|
||||
handleHide: { action: 'handleHide' }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
import React from 'react'
|
||||
import { render, screen } from '@testing-library/react'
|
||||
import HotkeysModalContent from '../../../../../frontend/js/features/hotkeys-modal/components/hotkeys-modal-content'
|
||||
import { expect } from 'chai'
|
||||
|
||||
const handleHide = () => {
|
||||
// closed
|
||||
}
|
||||
|
||||
describe('<HotkeysModalContent />', function() {
|
||||
it('renders the translated modal title', function() {
|
||||
const { container } = render(
|
||||
<HotkeysModalContent handleHide={handleHide} />
|
||||
)
|
||||
|
||||
expect(container.querySelector('.modal-title').textContent).to.equal(
|
||||
'Hotkeys'
|
||||
)
|
||||
})
|
||||
|
||||
it('renders translated heading with embedded code', function() {
|
||||
const { container } = render(
|
||||
<HotkeysModalContent handleHide={handleHide} />
|
||||
)
|
||||
|
||||
const results = container.querySelectorAll('h3 code')
|
||||
expect(results).to.have.length(1)
|
||||
})
|
||||
|
||||
it('renders the hotkey descriptions', function() {
|
||||
const { container } = render(
|
||||
<HotkeysModalContent handleHide={handleHide} />
|
||||
)
|
||||
|
||||
const hotkeys = container.querySelectorAll('[data-test-selector="hotkey"]')
|
||||
expect(hotkeys).to.have.length(19)
|
||||
})
|
||||
|
||||
it('renders extra hotkey descriptions when Track Changes is enabled', function() {
|
||||
const { container } = render(
|
||||
<HotkeysModalContent handleHide={handleHide} trackChangesVisible />
|
||||
)
|
||||
|
||||
const hotkeys = container.querySelectorAll('[data-test-selector="hotkey"]')
|
||||
expect(hotkeys).to.have.length(22)
|
||||
})
|
||||
|
||||
it('uses Ctrl for non-macOS', function() {
|
||||
render(<HotkeysModalContent handleHide={handleHide} />)
|
||||
|
||||
screen.getAllByText(/Ctrl/)
|
||||
expect(screen.queryByText(/Cmd/)).to.not.exist
|
||||
})
|
||||
|
||||
it('uses Cmd for macOS', function() {
|
||||
render(<HotkeysModalContent handleHide={handleHide} isMac />)
|
||||
|
||||
screen.getAllByText(/Cmd/)
|
||||
expect(screen.queryByText(/Ctrl/)).to.not.exist
|
||||
})
|
||||
})
|
Loading…
Add table
Reference in a new issue