diff --git a/.babelrc b/.babelrc
index 56f23e0b..bec63fb1 100644
--- a/.babelrc
+++ b/.babelrc
@@ -8,5 +8,5 @@
}],
"react"
],
- "plugins": ["transform-object-rest-spread"]
+ "plugins": ["transform-object-rest-spread", "transform-class-properties"]
}
diff --git a/docs/setup.md b/docs/setup.md
index ec802cd5..463d1df7 100644
--- a/docs/setup.md
+++ b/docs/setup.md
@@ -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
diff --git a/src/browser/components/Finder.jsx b/src/browser/components/Finder.jsx
new file mode 100644
index 00000000..5f13cdcf
--- /dev/null
+++ b/src/browser/components/Finder.jsx
@@ -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 (
+
+
+
+ {
+ this.searchInput = input;
+ }}
+ />
+ {this.state.matches}
+
+
+
+
+
+
+ );
+ }
+}
+
+Finder.propTypes = {
+ close: PropTypes.func,
+ webviewKey: PropTypes.number,
+ focusState: PropTypes.bool,
+ inputBlur: PropTypes.func,
+};
diff --git a/src/browser/components/MainPage.jsx b/src/browser/components/MainPage.jsx
index 4ea84f4d..f80695c0 100644
--- a/src/browser/components/MainPage.jsx
+++ b/src/browser/components/MainPage.jsx
@@ -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({
{ tabsRow }
{ viewsRow }
+ { this.state.finderVisible ? (
+
+ ) : null}
{ (this.state.targetURL === '') ?
diff --git a/src/browser/css/components/Finder.css b/src/browser/css/components/Finder.css
new file mode 100644
index 00000000..8ab08d8e
--- /dev/null
+++ b/src/browser/css/components/Finder.css
@@ -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;
+}
diff --git a/src/browser/css/components/MainPage.css b/src/browser/css/components/MainPage.css
index e5097959..e3518a11 100644
--- a/src/browser/css/components/MainPage.css
+++ b/src/browser/css/components/MainPage.css
@@ -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;
+}
diff --git a/src/browser/css/components/index.css b/src/browser/css/components/index.css
index 65e16e05..357b259d 100644
--- a/src/browser/css/components/index.css
+++ b/src/browser/css/components/index.css
@@ -6,3 +6,4 @@
@import url("PermissionRequestDialog.css");
@import url("TabBar.css");
@import url("TeamListItem.css");
+@import url("Finder.css");
diff --git a/src/main/menus/app.js b/src/main/menus/app.js
index 6c819bf7..eec6037e 100644
--- a/src/main/menus/app.js
+++ b/src/main/menus/app.js
@@ -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) {
diff --git a/webpack.config.renderer.js b/webpack.config.renderer.js
index 973840d0..28d60528 100644
--- a/webpack.config.renderer.js
+++ b/webpack.config.renderer.js
@@ -24,7 +24,7 @@ module.exports = merge(base, {
},
module: {
rules: [{
- test: /\.jsx$/,
+ test: /\.(js|jsx)?$/,
use: {
loader: 'babel-loader',
},