tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

ReverseSearchInput.js (7927B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 "use strict";
      6 
      7 // React & Redux
      8 const {
      9  Component,
     10 } = require("resource://devtools/client/shared/vendor/react.mjs");
     11 const dom = require("resource://devtools/client/shared/vendor/react-dom-factories.js");
     12 const {
     13  connect,
     14 } = require("resource://devtools/client/shared/vendor/react-redux.js");
     15 
     16 const {
     17  getReverseSearchTotalResults,
     18  getReverseSearchResultPosition,
     19  getReverseSearchResult,
     20 } = require("resource://devtools/client/webconsole/selectors/history.js");
     21 
     22 loader.lazyRequireGetter(
     23  this,
     24  "PropTypes",
     25  "resource://devtools/client/shared/vendor/react-prop-types.js"
     26 );
     27 loader.lazyRequireGetter(
     28  this,
     29  "actions",
     30  "resource://devtools/client/webconsole/actions/index.js"
     31 );
     32 loader.lazyRequireGetter(
     33  this,
     34  "l10n",
     35  "resource://devtools/client/webconsole/utils/messages.js",
     36  true
     37 );
     38 loader.lazyRequireGetter(
     39  this,
     40  "PluralForm",
     41  "resource://devtools/shared/plural-form.js",
     42  true
     43 );
     44 loader.lazyRequireGetter(
     45  this,
     46  "KeyCodes",
     47  "resource://devtools/client/shared/keycodes.js",
     48  true
     49 );
     50 
     51 const isMacOS = Services.appinfo.OS === "Darwin";
     52 
     53 class ReverseSearchInput extends Component {
     54  static get propTypes() {
     55    return {
     56      dispatch: PropTypes.func.isRequired,
     57      setInputValue: PropTypes.func.isRequired,
     58      focusInput: PropTypes.func.isRequired,
     59      reverseSearchResult: PropTypes.string,
     60      reverseSearchTotalResults: PropTypes.number,
     61      reverseSearchResultPosition: PropTypes.number,
     62      visible: PropTypes.bool,
     63      initialValue: PropTypes.string,
     64    };
     65  }
     66 
     67  constructor(props) {
     68    super(props);
     69 
     70    this.onInputKeyDown = this.onInputKeyDown.bind(this);
     71  }
     72 
     73  componentDidUpdate(prevProps) {
     74    const { setInputValue, focusInput } = this.props;
     75    if (
     76      prevProps.reverseSearchResult !== this.props.reverseSearchResult &&
     77      this.props.visible &&
     78      this.props.reverseSearchTotalResults > 0
     79    ) {
     80      setInputValue(this.props.reverseSearchResult);
     81    }
     82 
     83    if (prevProps.visible === true && this.props.visible === false) {
     84      focusInput();
     85    }
     86 
     87    if (
     88      prevProps.visible === false &&
     89      this.props.visible === true &&
     90      this.props.initialValue
     91    ) {
     92      this.inputNode.value = this.props.initialValue;
     93    }
     94  }
     95 
     96  onEnterKeyboardShortcut(event) {
     97    const { dispatch } = this.props;
     98    event.stopPropagation();
     99    dispatch(actions.reverseSearchInputToggle());
    100    dispatch(actions.evaluateExpression(undefined, "reverse-search"));
    101  }
    102 
    103  onEscapeKeyboardShortcut(event) {
    104    const { dispatch } = this.props;
    105    event.stopPropagation();
    106    dispatch(actions.reverseSearchInputToggle());
    107  }
    108 
    109  onBackwardNavigationKeyBoardShortcut(event, canNavigate) {
    110    const { dispatch } = this.props;
    111    event.stopPropagation();
    112    event.preventDefault();
    113    if (canNavigate) {
    114      dispatch(actions.showReverseSearchBack({ access: "keyboard" }));
    115    }
    116  }
    117 
    118  onForwardNavigationKeyBoardShortcut(event, canNavigate) {
    119    const { dispatch } = this.props;
    120    event.stopPropagation();
    121    event.preventDefault();
    122    if (canNavigate) {
    123      dispatch(actions.showReverseSearchNext({ access: "keyboard" }));
    124    }
    125  }
    126 
    127  onInputKeyDown(event) {
    128    const { keyCode, key, ctrlKey, shiftKey } = event;
    129    const { reverseSearchTotalResults } = this.props;
    130 
    131    // On Enter, we trigger an execute.
    132    if (keyCode === KeyCodes.DOM_VK_RETURN) {
    133      return this.onEnterKeyboardShortcut(event);
    134    }
    135 
    136    const lowerCaseKey = key.toLowerCase();
    137 
    138    // On Escape (and Ctrl + c on OSX), we close the reverse search input.
    139    if (
    140      keyCode === KeyCodes.DOM_VK_ESCAPE ||
    141      (isMacOS && ctrlKey && lowerCaseKey === "c")
    142    ) {
    143      return this.onEscapeKeyboardShortcut(event);
    144    }
    145 
    146    const canNavigate =
    147      Number.isInteger(reverseSearchTotalResults) &&
    148      reverseSearchTotalResults > 1;
    149 
    150    if (
    151      (!isMacOS && key === "F9" && !shiftKey) ||
    152      (isMacOS && ctrlKey && lowerCaseKey === "r")
    153    ) {
    154      return this.onBackwardNavigationKeyBoardShortcut(event, canNavigate);
    155    }
    156 
    157    if (
    158      (!isMacOS && key === "F9" && shiftKey) ||
    159      (isMacOS && ctrlKey && lowerCaseKey === "s")
    160    ) {
    161      return this.onForwardNavigationKeyBoardShortcut(event, canNavigate);
    162    }
    163 
    164    return null;
    165  }
    166 
    167  renderSearchInformation() {
    168    const { reverseSearchTotalResults, reverseSearchResultPosition } =
    169      this.props;
    170 
    171    if (!Number.isInteger(reverseSearchTotalResults)) {
    172      return null;
    173    }
    174 
    175    let text;
    176    if (reverseSearchTotalResults === 0) {
    177      text = l10n.getStr("webconsole.reverseSearch.noResult");
    178    } else {
    179      const resultsString = l10n.getStr("webconsole.reverseSearch.results");
    180      text = PluralForm.get(reverseSearchTotalResults, resultsString)
    181        .replace("#1", reverseSearchResultPosition)
    182        .replace("#2", reverseSearchTotalResults);
    183    }
    184 
    185    return dom.div({ className: "reverse-search-info" }, text);
    186  }
    187 
    188  renderNavigationButtons() {
    189    const { dispatch, reverseSearchTotalResults } = this.props;
    190 
    191    if (
    192      !Number.isInteger(reverseSearchTotalResults) ||
    193      reverseSearchTotalResults <= 1
    194    ) {
    195      return null;
    196    }
    197 
    198    return [
    199      dom.button({
    200        key: "search-result-button-prev",
    201        className: "devtools-button search-result-button-prev",
    202        title: l10n.getFormatStr(
    203          "webconsole.reverseSearch.result.previousButton.tooltip",
    204          [isMacOS ? "Ctrl + R" : "F9"]
    205        ),
    206        onClick: () => {
    207          dispatch(actions.showReverseSearchBack({ access: "click" }));
    208          this.inputNode.focus();
    209        },
    210      }),
    211      dom.button({
    212        key: "search-result-button-next",
    213        className: "devtools-button search-result-button-next",
    214        title: l10n.getFormatStr(
    215          "webconsole.reverseSearch.result.nextButton.tooltip",
    216          [isMacOS ? "Ctrl + S" : "Shift + F9"]
    217        ),
    218        onClick: () => {
    219          dispatch(actions.showReverseSearchNext({ access: "click" }));
    220          this.inputNode.focus();
    221        },
    222      }),
    223    ];
    224  }
    225 
    226  render() {
    227    const { dispatch, visible, reverseSearchTotalResults } = this.props;
    228 
    229    if (!visible) {
    230      return null;
    231    }
    232 
    233    const classNames = ["reverse-search"];
    234 
    235    if (reverseSearchTotalResults === 0) {
    236      classNames.push("no-result");
    237    }
    238 
    239    return dom.div(
    240      { className: classNames.join(" ") },
    241      dom.input({
    242        ref: node => {
    243          this.inputNode = node;
    244        },
    245        autoFocus: true,
    246        placeholder: l10n.getStr("webconsole.reverseSearch.input.placeHolder"),
    247        className: "reverse-search-input devtools-monospace",
    248        onKeyDown: this.onInputKeyDown,
    249        onInput: ({ target }) =>
    250          dispatch(actions.reverseSearchInputChange(target.value)),
    251      }),
    252      dom.div(
    253        {
    254          className: "reverse-search-actions",
    255        },
    256        this.renderSearchInformation(),
    257        this.renderNavigationButtons(),
    258        dom.button({
    259          className: "devtools-button reverse-search-close-button",
    260          title: l10n.getFormatStr(
    261            "webconsole.reverseSearch.closeButton.tooltip",
    262            ["Esc" + (isMacOS ? " | Ctrl + C" : "")]
    263          ),
    264          onClick: () => {
    265            dispatch(actions.reverseSearchInputToggle());
    266          },
    267        })
    268      )
    269    );
    270  }
    271 }
    272 
    273 const mapStateToProps = state => ({
    274  visible: state.ui.reverseSearchInputVisible,
    275  reverseSearchTotalResults: getReverseSearchTotalResults(state),
    276  reverseSearchResultPosition: getReverseSearchResultPosition(state),
    277  reverseSearchResult: getReverseSearchResult(state),
    278 });
    279 
    280 const mapDispatchToProps = dispatch => ({ dispatch });
    281 
    282 module.exports = connect(
    283  mapStateToProps,
    284  mapDispatchToProps
    285 )(ReverseSearchInput);