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', },