Merge pull request #836 from mattermost/MM-10322
MM-10322 Add CTRL+F shortcut to work as browser search
This commit is contained in:
commit
8c40f5df09
2
.babelrc
2
.babelrc
|
@ -8,5 +8,5 @@
|
|||
}],
|
||||
"react"
|
||||
],
|
||||
"plugins": ["transform-object-rest-spread"]
|
||||
"plugins": ["transform-object-rest-spread", "transform-class-properties"]
|
||||
}
|
||||
|
|
|
@ -154,6 +154,7 @@ Below lists menu options (shortcut keys are listed in brackets, `Ctrl` becomes `
|
|||
- **Select All** (Ctrl+A) - Select all text in input box
|
||||
- **Search in Team** (Ctrl+S) - Puts cursor in search box to search in the current team
|
||||
- **View**
|
||||
- **Find..** (Ctrl+F)- Find in page
|
||||
- **Reload** (Ctrl+R) - Reload page from the server
|
||||
- **Clear Cache and Reload** (Ctrl+Shift+R) - Clear cached content in application and reload page
|
||||
- **Toggle Full Screen** (F11) - Toggle application from window to full screen and back
|
||||
|
|
173
src/browser/components/Finder.jsx
Normal file
173
src/browser/components/Finder.jsx
Normal file
|
@ -0,0 +1,173 @@
|
|||
// Copyright (c) 2015-2016 Yuya Ochiai
|
||||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
export default class Finder extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.webview = document.getElementById('mattermostView' + this.props.webviewKey);
|
||||
this.state = {
|
||||
foundInPage: false,
|
||||
searchTxt: '',
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.webview.addEventListener('found-in-page', this.foundInPage);
|
||||
this.searchInput.focus();
|
||||
|
||||
// synthetic events are not working all that reliably for touch bar with esc keys
|
||||
this.searchInput.addEventListener('keyup', this.handleKeyEvent);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.webview.stopFindInPage('clearSelection');
|
||||
this.webview.removeEventListener('found-in-page', this.foundInPage);
|
||||
this.searchInput.removeEventListener('keyup', this.handleKeyEvent);
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (this.props.focusState && (this.props.focusState !== prevProps.focusState)) {
|
||||
this.searchInput.focus();
|
||||
}
|
||||
}
|
||||
|
||||
findNext = () => {
|
||||
this.webview.findInPage(this.state.searchTxt);
|
||||
};
|
||||
|
||||
find = (keyword) => {
|
||||
this.webview.stopFindInPage('clearSelection');
|
||||
if (keyword) {
|
||||
this.webview.findInPage(keyword);
|
||||
} else {
|
||||
this.setState({
|
||||
matches: '0/0',
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
findPrev = () => {
|
||||
this.webview.findInPage(this.state.searchTxt, {forward: false});
|
||||
}
|
||||
|
||||
searchTxt = (event) => {
|
||||
this.setState({searchTxt: event.target.value});
|
||||
this.find(event.target.value);
|
||||
}
|
||||
|
||||
handleKeyEvent = (event) => {
|
||||
if (event.code === 'Escape') {
|
||||
this.props.close();
|
||||
} else if (event.code === 'Enter') {
|
||||
this.findNext();
|
||||
}
|
||||
}
|
||||
|
||||
foundInPage = (event) => {
|
||||
const {matches, activeMatchOrdinal} = event.result;
|
||||
this.setState({
|
||||
foundInPage: true,
|
||||
matches: `${activeMatchOrdinal}/${matches}`,
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div id='finder'>
|
||||
<div className='finder'>
|
||||
<div className='finder-input-wrapper'>
|
||||
<input
|
||||
className='finder-input'
|
||||
placeholder=''
|
||||
value={this.state.searchTxt}
|
||||
onChange={this.searchTxt}
|
||||
onBlur={this.props.inputBlur}
|
||||
ref={(input) => {
|
||||
this.searchInput = input;
|
||||
}}
|
||||
/>
|
||||
<span className={this.state.foundInPage ? 'finder-progress' : 'finder-progress finder-progress__disabled'}>{this.state.matches}</span>
|
||||
</div>
|
||||
<button
|
||||
className='finder-prev'
|
||||
onClick={this.findPrev}
|
||||
>
|
||||
<svg
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
width='24'
|
||||
height='24'
|
||||
viewBox='0 0 24 24'
|
||||
className='icon'
|
||||
fill='none'
|
||||
stroke='currentColor'
|
||||
strokeWidth='2'
|
||||
strokeLinecap='round'
|
||||
strokeLinejoin='round'
|
||||
>
|
||||
<polyline points='18 15 12 9 6 15'/>
|
||||
</svg>
|
||||
</button>
|
||||
<button
|
||||
className='finder-next'
|
||||
onClick={this.findNext}
|
||||
>
|
||||
<svg
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
width='24'
|
||||
height='24'
|
||||
viewBox='0 0 24 24'
|
||||
fill='none'
|
||||
className='icon arrow-up'
|
||||
stroke='currentColor'
|
||||
strokeWidth='2'
|
||||
strokeLinecap='round'
|
||||
strokeLinejoin='round'
|
||||
>
|
||||
<polyline points='6 9 12 15 18 9'/>
|
||||
</svg>
|
||||
</button>
|
||||
<button
|
||||
className='finder-close'
|
||||
onClick={this.props.close}
|
||||
>
|
||||
<svg
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
width='24'
|
||||
height='24'
|
||||
viewBox='0 0 24 24'
|
||||
fill='none'
|
||||
className='icon'
|
||||
stroke='currentColor'
|
||||
strokeWidth='2'
|
||||
strokeLinecap='round'
|
||||
strokeLinejoin='round'
|
||||
>
|
||||
<line
|
||||
x1='18'
|
||||
y1='6'
|
||||
x2='6'
|
||||
y2='18'
|
||||
/>
|
||||
<line
|
||||
x1='6'
|
||||
y1='6'
|
||||
x2='18'
|
||||
y2='18'
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Finder.propTypes = {
|
||||
close: PropTypes.func,
|
||||
webviewKey: PropTypes.number,
|
||||
focusState: PropTypes.bool,
|
||||
inputBlur: PropTypes.func,
|
||||
};
|
|
@ -18,7 +18,7 @@ import MattermostView from './MattermostView.jsx';
|
|||
import TabBar from './TabBar.jsx';
|
||||
import HoveringURL from './HoveringURL.jsx';
|
||||
import PermissionRequestDialog from './PermissionRequestDialog.jsx';
|
||||
|
||||
import Finder from './Finder.jsx';
|
||||
import NewTeamModal from './NewTeamModal.jsx';
|
||||
|
||||
const MainPage = createReactClass({
|
||||
|
@ -45,6 +45,7 @@ const MainPage = createReactClass({
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
key,
|
||||
unreadCounts: new Array(this.props.teams.length),
|
||||
|
@ -141,6 +142,10 @@ const MainPage = createReactClass({
|
|||
}
|
||||
}
|
||||
});
|
||||
|
||||
ipcRenderer.on('toggle-find', () => {
|
||||
this.activateFinder(true);
|
||||
});
|
||||
},
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
if (prevState.key !== this.state.key) { // i.e. When tab has been changed
|
||||
|
@ -151,14 +156,15 @@ const MainPage = createReactClass({
|
|||
const newKey = (this.props.teams.length + key) % this.props.teams.length;
|
||||
this.setState({
|
||||
key: newKey,
|
||||
finderVisible: false,
|
||||
});
|
||||
this.handleOnTeamFocused(newKey);
|
||||
|
||||
var webview = document.getElementById('mattermostView' + newKey);
|
||||
ipcRenderer.send('update-title', {
|
||||
title: webview.getTitle(),
|
||||
});
|
||||
this.handleOnTeamFocused(newKey);
|
||||
},
|
||||
|
||||
handleUnreadCountChange(index, unreadCount, mentionCount, isUnread, isMentioned) {
|
||||
var unreadCounts = this.state.unreadCounts;
|
||||
var mentionCounts = this.state.mentionCounts;
|
||||
|
@ -245,13 +251,33 @@ const MainPage = createReactClass({
|
|||
});
|
||||
},
|
||||
|
||||
focusOnWebView() {
|
||||
this.refs[`mattermostView${this.state.key}`].focusOnWebView();
|
||||
focusOnWebView(e) {
|
||||
if (e.target.className !== 'finder-input') {
|
||||
this.refs[`mattermostView${this.state.key}`].focusOnWebView();
|
||||
}
|
||||
},
|
||||
|
||||
activateFinder() {
|
||||
this.setState({
|
||||
finderVisible: true,
|
||||
focusFinder: true,
|
||||
});
|
||||
},
|
||||
|
||||
closeFinder() {
|
||||
this.setState({
|
||||
finderVisible: false,
|
||||
});
|
||||
},
|
||||
|
||||
inputBlur() {
|
||||
this.setState({
|
||||
focusFinder: false,
|
||||
});
|
||||
},
|
||||
|
||||
render() {
|
||||
var self = this;
|
||||
|
||||
var tabsRow;
|
||||
if (this.props.teams.length > 1) {
|
||||
tabsRow = (
|
||||
|
@ -365,6 +391,14 @@ const MainPage = createReactClass({
|
|||
<Grid fluid={true}>
|
||||
{ tabsRow }
|
||||
{ viewsRow }
|
||||
{ this.state.finderVisible ? (
|
||||
<Finder
|
||||
webviewKey={this.state.key}
|
||||
close={this.closeFinder}
|
||||
focusState={this.state.focusFinder}
|
||||
inputBlur={this.inputBlur}
|
||||
/>
|
||||
) : null}
|
||||
</Grid>
|
||||
<TransitionGroup>
|
||||
{ (this.state.targetURL === '') ?
|
||||
|
|
62
src/browser/css/components/Finder.css
Normal file
62
src/browser/css/components/Finder.css
Normal file
|
@ -0,0 +1,62 @@
|
|||
.finder-input-wrapper {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
vertical-align: bottom;
|
||||
}
|
||||
|
||||
.finder button {
|
||||
border: none;
|
||||
background: #d2d2d2;
|
||||
outline: none;
|
||||
cursor: pointer;
|
||||
font-size: 18px;
|
||||
height: 26px;
|
||||
}
|
||||
|
||||
.finder button:hover {
|
||||
background: #f0f0f0;
|
||||
}
|
||||
|
||||
.finder-input {
|
||||
border: 1px solid #d2d2d2;
|
||||
border-radius: 3px;
|
||||
width: 200px;
|
||||
outline: none;
|
||||
line-height: 24px;
|
||||
font-size: 14px;
|
||||
padding: 0px 35px 0px 5px;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
.finder-input:focus {
|
||||
border-color: #35b5f4;
|
||||
box-shadow: 0 0 1px #35b5f4;
|
||||
}
|
||||
|
||||
.finder-progress__disabled {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.finder-progress {
|
||||
position: absolute;
|
||||
font-size: 12px;
|
||||
right: 8px;
|
||||
top: 6px;
|
||||
color: #7b7b7b;
|
||||
}
|
||||
|
||||
.icon {
|
||||
height: 18px;
|
||||
width: 18px;
|
||||
}
|
||||
|
||||
.finder .finder-close {
|
||||
background: transparent;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.finder-next {
|
||||
border-top-right-radius: 3px;
|
||||
border-bottom-right-radius: 3px;
|
||||
margin-right: 2px;
|
||||
}
|
|
@ -11,3 +11,17 @@
|
|||
div[id*="-permissionDialog"] {
|
||||
max-width: 350px;
|
||||
}
|
||||
|
||||
.finder {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 20px;
|
||||
padding: 4px;
|
||||
background: #eee;
|
||||
border: 1px solid #d7d7d7;
|
||||
border-top: none;
|
||||
border-right: none;
|
||||
border-bottom-left-radius: 5px;
|
||||
border-bottom-right-radius: 5px;
|
||||
font-size: 0px;
|
||||
}
|
||||
|
|
|
@ -6,3 +6,4 @@
|
|||
@import url("PermissionRequestDialog.css");
|
||||
@import url("TabBar.css");
|
||||
@import url("TeamListItem.css");
|
||||
@import url("Finder.css");
|
||||
|
|
|
@ -95,6 +95,12 @@ function createTemplate(mainWindow, config, isDev) {
|
|||
template.push({
|
||||
label: '&View',
|
||||
submenu: [{
|
||||
label: 'Find..',
|
||||
accelerator: 'CmdOrCtrl+F',
|
||||
click(item, focusedWindow) {
|
||||
focusedWindow.webContents.send('toggle-find');
|
||||
},
|
||||
}, {
|
||||
label: 'Reload',
|
||||
accelerator: 'CmdOrCtrl+R',
|
||||
click(item, focusedWindow) {
|
||||
|
|
|
@ -24,7 +24,7 @@ module.exports = merge(base, {
|
|||
},
|
||||
module: {
|
||||
rules: [{
|
||||
test: /\.jsx$/,
|
||||
test: /\.(js|jsx)?$/,
|
||||
use: {
|
||||
loader: 'babel-loader',
|
||||
},
|
||||
|
|
Loading…
Reference in a new issue