Merge pull request #836 from mattermost/MM-10322

MM-10322 Add CTRL+F shortcut to work as browser search
This commit is contained in:
Yuya Ochiai 2018-08-01 23:52:23 +09:00 committed by GitHub
commit 8c40f5df09
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 299 additions and 8 deletions

View file

@ -8,5 +8,5 @@
}],
"react"
],
"plugins": ["transform-object-rest-spread"]
"plugins": ["transform-object-rest-spread", "transform-class-properties"]
}

View file

@ -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

View 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,
};

View file

@ -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 === '') ?

View 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;
}

View file

@ -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;
}

View file

@ -6,3 +6,4 @@
@import url("PermissionRequestDialog.css");
@import url("TabBar.css");
@import url("TeamListItem.css");
@import url("Finder.css");

View file

@ -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) {

View file

@ -24,7 +24,7 @@ module.exports = merge(base, {
},
module: {
rules: [{
test: /\.jsx$/,
test: /\.(js|jsx)?$/,
use: {
loader: 'babel-loader',
},