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"
|
"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
|
- **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
|
- **Search in Team** (Ctrl+S) - Puts cursor in search box to search in the current team
|
||||||
- **View**
|
- **View**
|
||||||
|
- **Find..** (Ctrl+F)- Find in page
|
||||||
- **Reload** (Ctrl+R) - Reload page from the server
|
- **Reload** (Ctrl+R) - Reload page from the server
|
||||||
- **Clear Cache and Reload** (Ctrl+Shift+R) - Clear cached content in application and reload page
|
- **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
|
- **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 TabBar from './TabBar.jsx';
|
||||||
import HoveringURL from './HoveringURL.jsx';
|
import HoveringURL from './HoveringURL.jsx';
|
||||||
import PermissionRequestDialog from './PermissionRequestDialog.jsx';
|
import PermissionRequestDialog from './PermissionRequestDialog.jsx';
|
||||||
|
import Finder from './Finder.jsx';
|
||||||
import NewTeamModal from './NewTeamModal.jsx';
|
import NewTeamModal from './NewTeamModal.jsx';
|
||||||
|
|
||||||
const MainPage = createReactClass({
|
const MainPage = createReactClass({
|
||||||
|
@ -45,6 +45,7 @@ const MainPage = createReactClass({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
key,
|
key,
|
||||||
unreadCounts: new Array(this.props.teams.length),
|
unreadCounts: new Array(this.props.teams.length),
|
||||||
|
@ -141,6 +142,10 @@ const MainPage = createReactClass({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
ipcRenderer.on('toggle-find', () => {
|
||||||
|
this.activateFinder(true);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
componentDidUpdate(prevProps, prevState) {
|
componentDidUpdate(prevProps, prevState) {
|
||||||
if (prevState.key !== this.state.key) { // i.e. When tab has been changed
|
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;
|
const newKey = (this.props.teams.length + key) % this.props.teams.length;
|
||||||
this.setState({
|
this.setState({
|
||||||
key: newKey,
|
key: newKey,
|
||||||
|
finderVisible: false,
|
||||||
});
|
});
|
||||||
this.handleOnTeamFocused(newKey);
|
|
||||||
|
|
||||||
var webview = document.getElementById('mattermostView' + newKey);
|
var webview = document.getElementById('mattermostView' + newKey);
|
||||||
ipcRenderer.send('update-title', {
|
ipcRenderer.send('update-title', {
|
||||||
title: webview.getTitle(),
|
title: webview.getTitle(),
|
||||||
});
|
});
|
||||||
|
this.handleOnTeamFocused(newKey);
|
||||||
},
|
},
|
||||||
|
|
||||||
handleUnreadCountChange(index, unreadCount, mentionCount, isUnread, isMentioned) {
|
handleUnreadCountChange(index, unreadCount, mentionCount, isUnread, isMentioned) {
|
||||||
var unreadCounts = this.state.unreadCounts;
|
var unreadCounts = this.state.unreadCounts;
|
||||||
var mentionCounts = this.state.mentionCounts;
|
var mentionCounts = this.state.mentionCounts;
|
||||||
|
@ -245,13 +251,33 @@ const MainPage = createReactClass({
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
focusOnWebView() {
|
focusOnWebView(e) {
|
||||||
|
if (e.target.className !== 'finder-input') {
|
||||||
this.refs[`mattermostView${this.state.key}`].focusOnWebView();
|
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() {
|
render() {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
var tabsRow;
|
var tabsRow;
|
||||||
if (this.props.teams.length > 1) {
|
if (this.props.teams.length > 1) {
|
||||||
tabsRow = (
|
tabsRow = (
|
||||||
|
@ -365,6 +391,14 @@ const MainPage = createReactClass({
|
||||||
<Grid fluid={true}>
|
<Grid fluid={true}>
|
||||||
{ tabsRow }
|
{ tabsRow }
|
||||||
{ viewsRow }
|
{ viewsRow }
|
||||||
|
{ this.state.finderVisible ? (
|
||||||
|
<Finder
|
||||||
|
webviewKey={this.state.key}
|
||||||
|
close={this.closeFinder}
|
||||||
|
focusState={this.state.focusFinder}
|
||||||
|
inputBlur={this.inputBlur}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
</Grid>
|
</Grid>
|
||||||
<TransitionGroup>
|
<TransitionGroup>
|
||||||
{ (this.state.targetURL === '') ?
|
{ (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"] {
|
div[id*="-permissionDialog"] {
|
||||||
max-width: 350px;
|
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("PermissionRequestDialog.css");
|
||||||
@import url("TabBar.css");
|
@import url("TabBar.css");
|
||||||
@import url("TeamListItem.css");
|
@import url("TeamListItem.css");
|
||||||
|
@import url("Finder.css");
|
||||||
|
|
|
@ -95,6 +95,12 @@ function createTemplate(mainWindow, config, isDev) {
|
||||||
template.push({
|
template.push({
|
||||||
label: '&View',
|
label: '&View',
|
||||||
submenu: [{
|
submenu: [{
|
||||||
|
label: 'Find..',
|
||||||
|
accelerator: 'CmdOrCtrl+F',
|
||||||
|
click(item, focusedWindow) {
|
||||||
|
focusedWindow.webContents.send('toggle-find');
|
||||||
|
},
|
||||||
|
}, {
|
||||||
label: 'Reload',
|
label: 'Reload',
|
||||||
accelerator: 'CmdOrCtrl+R',
|
accelerator: 'CmdOrCtrl+R',
|
||||||
click(item, focusedWindow) {
|
click(item, focusedWindow) {
|
||||||
|
|
|
@ -24,7 +24,7 @@ module.exports = merge(base, {
|
||||||
},
|
},
|
||||||
module: {
|
module: {
|
||||||
rules: [{
|
rules: [{
|
||||||
test: /\.jsx$/,
|
test: /\.(js|jsx)?$/,
|
||||||
use: {
|
use: {
|
||||||
loader: 'babel-loader',
|
loader: 'babel-loader',
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in a new issue