我是靠谱客的博主 甜甜泥猴桃,最近开发中收集的这篇文章主要介绍gi克隆github文件_如何构建GitHub文件搜索功能的克隆,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

gi克隆github文件

In this article, we will build a project that mimics the lesser known but awesome file search functionality provided by GitHub.

在本文中,我们将构建一个项目,该项目模仿GitHub提供的鲜为人知但功能强大的文件搜索功能。

To see how it works, go to any GitHub repository and press the letter t which will land you in search view. Then you can simultaneously search and scroll through the list as shown in the below gif:

要查看其工作原理,请转到任何GitHub存储库,然后按字母t ,这将使您进入搜索视图。 然后,您可以同时搜索和滚动列表,如以下gif所示:

By building this app, you will learn the following:

通过构建此应用程序,您将学到以下内容:

  • How to create a UI similar to a GitHub repository

    如何创建类似于GitHub存储库的UI
  • How to work with keyboard events in React

    如何在React中处理键盘事件
  • How to work with navigation using the arrow keys on your keyboard

    如何使用键盘上的箭头键进行导航
  • How to highlight matching text while searching

    如何在搜索时突出显示匹配的文本
  • How to add icons in React

    如何在React中添加图标
  • How to render HTML content in a JSX expression

    如何在JSX表达式中呈现HTML内容

And much more.

以及更多。

You can see the live demo of the application here.

您可以在此处查看该应用程序的实时演示。

让我们开始吧 (Let’s get started)

Create a new project using create-react-app:

使用create-react-app创建一个新项目:

create-react-app github-file-search-react

Once the project is created, delete all files from the src folder and create index.js, App.js and styles.scss files inside the src folder. Also create components and utils folders inside the src folder.

一旦项目被创建,从删除所有文件src文件夹并创建index.jsApp.jsstyles.scss内部文件src文件夹。 还要在src文件夹内创建componentsutils文件夹。

Install the necessary dependencies:

安装必要的依赖项:

yarn add moment@2.27.0 node-sass@4.14.1 prop-types@15.7.2 react-icons@3.10.0

Open styles.scss and add the contents from here inside it.

打开styles.scss然后从此处添加内容。

Create a new file Header.js inside the components folder with the following content:

components文件夹内创建一个新文件Header.js ,内容如下:

import React from 'react';

const Header = () => <h1 className="header">GitHub File Search</h1>;

export default Header;

Create a new file api.js inside the utils folder and add the content from here inside it.

utils文件夹中创建一个新文件api.js ,然后从此处添加内容。

In this file, we have created static data to be displayed on the UI to keep the application simple and easy to understand.

在此文件中,我们创建了要在UI上显示的静态数据,以使应用程序简单易懂。

Create a new file ListItem.js inside the components folder with the following content:

components文件夹中创建一个具有以下内容的新文件ListItem.js

import React from 'react';
import moment from 'moment';
import { AiFillFolder, AiOutlineFile } from 'react-icons/ai';

const ListItem = ({ type, name, comment, modified_time }) => {
  return (
    <React.Fragment>
      <div className="list-item">
        <div className="file">
          <span className="file-icon">
            {type === 'folder' ? (
              <AiFillFolder color="#79b8ff" size="20" />
            ) : (
              <AiOutlineFile size="18" />
            )}
          </span>
          <span className="label">{name}</span>
        </div>
        <div className="comment">{comment}</div>
        <div className="time" title={modified_time}>
          {moment(modified_time).fromNow()}
        </div>
      </div>
    </React.Fragment>
  );
};

export default ListItem;

In this file, we are taking the data of each file we want to display and we're displaying the folder/file icon, the name of the file, the comment(s), and the last time the file was modified.

在此文件中,我们将获取要显示的每个文件的数据,并显示文件夹/文件图标,文件名,注释和文件的最后修改时间。

For displaying the icons, we'll use the react-icons npm library. It has a really nice website that lets you easily search and use the icons you need. Check it out here.

为了显示图标,我们将使用react-icons npm库。 它有一个非常不错的网站,可让您轻松搜索和使用所需的图标。 在这里查看 。

The icons component accepts the color and size props to customize the icon which we have used in the above code.

图标组件接受colorsize道具以自定义我们在以上代码中使用的图标。

Create a new file called FilesList.js inside the components folder with the following content:

components文件夹中创建一个名为FilesList.js的新文件,其内容如下:

import React from 'react';
import ListItem from './ListItem';

const FilesList = ({ files }) => {
  return (
    <div className="list">
      {files.length > 0 ? (
        files.map((file, index) => {
          return <ListItem key={file.id} {...file} />;
        })
      ) : (
        <div>
          <h3 className="no-result">No matching files found</h3>
        </div>
      )}
    </div>
  );
};

export default FilesList;

In this file, we read the static data from the api.js file and then display each element of the files array by using the array map method.

在此文件中,我们从api.js文件中读取静态数据,然后使用数组映射方法显示文件数组的每个元素。

Now open the src/App.js file and add the following code inside it:

现在打开src/App.js文件,并在其中添加以下代码:

import React from 'react';
import Header from './components/Header';
import FilesList from './components/FilesList';
import files from './utils/api';

export default class App extends React.Component {
  state = {
    filesList: files
  };

  render() {
    const { counter, filesList } = this.state;

    return (
      <div className="container">
        <Header />
        <FilesList files={filesList} />
      </div>
    );
  }
}

In this file, we have added a state to store the static files data which we can modify whenever we need to do so. Then we have passed it to the FilesList component to display on the UI.

在此文件中,我们添加了一个状态来存储静态文件数据,可以在需要时对其进行修改。 然后,我们将其传递给FilesList组件以在UI上显示。

Now, open the index.js file and add the following code inside it:

现在,打开index.js文件,并在其中添加以下代码:

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import './styles.scss';

ReactDOM.render(<App />, document.getElementById('root'));

Now, start your application by running the yarn start command from the terminal or command prompt and you will see the following initial screen:

现在,通过在终端或命令提示符处运行yarn start命令来启动应用程序,您将看到以下初始屏幕:

You can find the code up to this point in this branch.

您可以在此分支中找到到目前为止的代码。

添加基本​​搜索功能
(Add basic search functionality
)

Now, let's add the functionality which changes the UI and allows us to search through files when we press the letter t on our keyboard.

现在,让我们添加功能,该功能可以更改UI,并允许我们在按键盘上的字母t时搜索文件。

Inside the utils folder create a new file called keyCodes.js with the following content:

utils文件夹中,创建一个名为keyCodes.js的新文件,其内容如下:

export const ESCAPE_CODE = 27;
export const HOTKEY_CODE = 84; // key code of letter t
export const UP_ARROW_CODE = 38;
export const DOWN_ARROW_CODE = 40;

Create a new file called SearchView.js inside the components folder with the following content:

components文件夹内创建一个名为SearchView.js的新文件,其内容如下:

import React, { useState, useEffect, useRef } from 'react';

const SearchView = ({ onSearch }) => {
  const [input, setInput] = useState('');
  const inputRef = useRef();

  useEffect(() => {
    inputRef.current.focus();
  }, []);

  const onInputChange = (event) => {
    const input = event.target.value;
    setInput(input);
    onSearch(input);
  };

  return (
    <div className="search-box">
      My Repository <span className="slash">/</span>
      <input
        type="text"
        name="input"
        value={input}
        ref={inputRef}
        autoComplete="off"
        onChange={onInputChange}
      />
    </div>
  );
};

export default SearchView;

We are using React Hooks here for our state and lifecycle methods. If you are new to React Hooks check out this article for an introduction.

我们在这里将React Hooks用于状态和生命周期方法。 如果您不熟悉React Hooks,请查看本文的介绍。

In this file, we have first declared a state to store the user typed input. Then we added a ref using the useRef Hook so we can focus on the input field when the component is mounted.

在此文件中,我们首先声明了一个状态来存储用户键入的输入。 然后,我们使用useRef Hook添加了一个ref ,以便在安装组件时可以专注于输入字段。

const inputRef = useRef();

useEffect(() => {
  inputRef.current.focus();
}, []);

...

<input
    type="text"
    name="input"
    value={input}
    ref={inputRef}
    autoComplete="off"
    onChange={onInputChange}
  />

In this code, by passing the Empty array [] as the second argument to the useEffect hook, the code inside the useEffect hook will only be executed once when the component is mounted. This acts as the componentDidMount lifecycle method in class components.

在该代码中,通过使空数组[]作为第二个参数到useEffect钩,内部的代码useEffect钩将仅当部件被安装执行一次。 这充当类组件中的componentDidMount生命周期方法。

Then we assigned the ref to the input field as ref={inputRef}. On change of the input field inside the onInputChange handler, we are calling the onSearch method passed as a prop to the component from the App.js file.

然后,将ref分配给输入字段ref={inputRef} 。 在onInputChange处理函数中的输入字段发生更改时,我们将调用作为道具传递的onSearch方法从App.js文件传递到组件。

Now, open App.js and replace its contents with following code:

现在,打开App.js并将其内容替换为以下代码:

import React from 'react';
import Header from './components/Header';
import FilesList from './components/FilesList';
import SearchView from './components/SearchView';
import { ESCAPE_CODE, HOTKEY_CODE } from './utils/keyCodes';
import files from './utils/api';

export default class App extends React.Component {
  state = {
    isSearchView: false,
    filesList: files
  };

  componentDidMount() {
    window.addEventListener('keydown', this.handleEvent);
  }

  componentWillUnmount() {
    window.removeEventListener('keydown', this.handleEvent);
  }

  handleEvent = (event) => {
    const keyCode = event.keyCode || event.which;

    switch (keyCode) {
      case HOTKEY_CODE:
        this.setState((prevState) => ({
          isSearchView: true,
          filesList: prevState.filesList.filter((file) => file.type === 'file')
        }));
        break;
      case ESCAPE_CODE:
        this.setState({ isSearchView: false, filesList: files });
        break;
      default:
        break;
    }
  };

  handleSearch = (searchTerm) => {
    let list;
    if (searchTerm) {
      list = files.filter(
        (file) =>
          file.name.toLowerCase().indexOf(searchTerm.toLowerCase()) > -1 &&
          file.type === 'file'
      );
    } else {
      list = files.filter((file) => file.type === 'file');
    }

    this.setState({
      filesList: list
    });
  };

  render() {
    const { isSearchView, filesList } = this.state;

    return (
      <div className="container">
        <Header />
        {isSearchView ? (
          <div className="search-view">
            <SearchView onSearch={this.handleSearch} />
            <FilesList files={filesList} isSearchView={isSearchView} />
          </div>
        ) : (
          <FilesList files={filesList} />
        )}
      </div>
    );
  }
}

Now, restart the app by running the yarn start command again and check its functionality.

现在,通过再次运行yarn start命令重新启动应用程序并检查其功能。

As you can see, initially all folders and files are displayed. Then when we press the letter t on the keyboard, the view changes to allow us to search through the displayed files.

如您所见,最初显示所有文件夹和文件。 然后,当我们按键盘上的字母t时,视图将更改,以允许我们搜索显示的文件。

Now, let's understand the code from the App.js file.

现在,让我们了解App.js文件中的代码。

In this file, we have first declared isSearchView as a state variable. Then inside the componentDidMount and componentWillUnmount lifecycle methods we are adding and removing the keydown event handler, respectively.

在此文件中,我们首先将isSearchView声明为状态变量。 然后在componentDidMountcomponentWillUnmount生命周期方法内部,我们分别添加和删除keydown事件处理程序。

Then inside the handleEvent function, we are checking which key is pressed by the user.

然后在handleEvent函数内部,我们正在检查用户按下了哪个键。

  • If the user presses the t key, then we set the isSearchView state to true and update the filesList state array to include only files and exclude the folders.

    如果用户按下t键,则将isSearchView状态设置为true并更新filesList状态数组以仅包括文件而排除文件夹。

  • If use presses the escape key then we set the isSearchView state to false and update the filesList state array to include all the files and folders.

    如果use按下转义键,则将isSearchView状态设置为false并更新filesList状态数组以包括所有文件和文件夹。

The reason we declare HOTKEY_CODE and ESCAPE_CODE in separate files (keyCodes.js instead of directly using the keycode like 84) is that later, if we want to change the hotkey from t to s, then we just need to change the keycode in that file. It will reflect the change in all files where it's used without needing to change it in every file.

我们在单独的文件中声明HOTKEY_CODEESCAPE_CODE的原因( keyCodes.js而不是像84那样直接使用键码)是因为稍后,如果我们要将热键从t更改为s ,那么我们只需要在该文件中更改键码即可。 。 它会反映使用该文件的所有文件中的更改,而无需在每个文件中都进行更改。

Now, let's understand the handleSearch function. In this function, we check if the user has entered something in the input search box and then filter out the matching file name(s) which include that search term. Then we update the state with the filtered result(s).

现在,让我们了解handleSearch函数。 在此功能中,我们检查用户是否在输入搜索框中输入了某些内容,然后过滤出包含该搜索词的匹配文件名。 然后,我们使用过滤后的结果更新状态。

Then inside the render method, based on the isSearchView value, we display either the file list view or search view to the user.

然后,在render方法中,基于isSearchView值,我们向用户显示文件列表视图或搜索视图。

You can find code up to this point in this branch.

您可以在此分支中找到到目前为止的代码。

添加功能以在文件之间导航
(Add functionality to navigate between files
)

Now, lets add the functionality to display an arrow in front of the currently selected file while navigating the list of files.

现在,让我们添加功能,在导航文件列表时在当前选定文件的前面显示箭头。

Create a new file called InfoMessage.js inside the components folder with the following content:

components文件夹中创建一个名为InfoMessage.js的新文件,其内容如下:

import React from 'react';

const InfoMessage = () => {
  return (
    <div className="info-message">
      You've activated the <em>file finder</em>. Start typing to filter the file
      list. Use <span className="navigation">↑</span> and{' '}
      <span className="navigation">↓</span> to navigate,{' '}
      <span className="navigation">esc</span> to exit.
    </div>
  );
};

export default InfoMessage;

Now, open the App.js file and import the InfoMessage component to use it:

现在,打开App.js文件并导入InfoMessage组件以使用它:

import InfoMessage from './components/InfoMessage';

Add a new state variable called counter with the initial value of 0. This is to keep track of the index of the arrow.

添加一个名为counter的新状态变量,其初始值为0 。 这是为了跟踪箭头的索引。

Inside the handleEvent handler, get the filesList and counter values from state:

handleEvent处理程序内,从状态获取filesListcounter值:

const { filesList, counter } = this.state;

Add two new switch cases:

添加两个新的开关盒:

case UP_ARROW_CODE:
  if (counter > 0) {
    this.setState({ counter: counter - 1 });
  }
  break;
case DOWN_ARROW_CODE:
  if (counter < filesList.length - 1) {
    this.setState({ counter: counter + 1 });
  }
  break;

Here, we decrement the counter state value when we press the up arrow on the keyboard and increment when we press the down arrow.

在这里,当我们按下键盘上的向上箭头时,递减counter状态值,而当我们按下向下箭头时,则递增计数。

Also import the up and down array constants at the top of the file:

还要在文件顶部导入up和down数组常量:

import {
  ESCAPE_CODE,
  HOTKEY_CODE,
  UP_ARROW_CODE,
  DOWN_ARROW_CODE
} from './utils/keyCodes';

Inside the handleSearch function, reset the counter state to 0 at the end of the function so the arrow will always display for the first file from the list while filtering the files list.

handleSearch函数内部,在函数末尾将counter状态重置为0 ,这样,在过滤文件列表时,箭头将始终显示列表中第一个文件。

this.setState({
  filesList: list,
  counter: 0
});

Change the render method to display the InfoMessage component and pass counter and isSearchView as props to the FilesList component:

更改render方法以显示InfoMessage组件,并将counterisSearchView作为props传递给FilesList组件:

render() {
  const { isSearchView, counter, filesList } = this.state;

  return (
    <div className="container">
      <Header />
      {isSearchView ? (
        <div className="search-view">
          <SearchView onSearch={this.handleSearch} />
          <InfoMessage />
          <FilesList
            files={filesList}
            isSearchView={isSearchView}
            counter={counter}
          />
        </div>
      ) : (
        <FilesList files={filesList} />
      )}
    </div>
  );
}

Now, open the FilesList.js file and accept the isSearchView and counter props and pass them to the ListItem component.

现在,打开FilesList.js文件并接受isSearchViewcounter道具并将它们传递给ListItem组件。

Your FilesList.js file will look like this now:

您的FilesList.js文件现在看起来像这样:

import React from 'react';
import ListItem from './ListItem';

const FilesList = ({ files, isSearchView, counter }) => {
  return (
    <div className="list">
      {files.length > 0 ? (
        files.map((file, index) => {
          return (
            <ListItem
              key={file.id}
              {...file}
              index={index}
              isSearchView={isSearchView}
              counter={counter}
            />
          );
        })
      ) : (
        <div>
          <h3 className="no-result">No matching files found</h3>
        </div>
      )}
    </div>
  );
};

export default FilesList;

Now, open ListItem.js file and replace its contents with the following content:

现在,打开ListItem.js文件,并将其内容替换为以下内容:

import React from 'react';
import moment from 'moment';
import { AiFillFolder, AiOutlineFile, AiOutlineRight } from 'react-icons/ai';

const ListItem = ({
  index,
  type,
  name,
  comment,
  modified_time,
  isSearchView,
  counter
}) => {
  const isSelected = counter === index;

  return (
    <React.Fragment>
      <div className={`list-item ${isSelected ? 'active' : ''}`}>
        <div className="file">
          {isSearchView && (
            <span
              className={`arrow-icon ${isSelected ? 'visible' : 'invisible'}`}
            >
              <AiOutlineRight color="#0366d6" />
            </span>
          )}
          <span className="file-icon">
            {type === 'folder' ? (
              <AiFillFolder color="#79b8ff" size="20" />
            ) : (
              <AiOutlineFile size="18" />
            )}
          </span>
          <span className="label">{name}</span>
        </div>
        {!isSearchView && (
          <React.Fragment>
            <div className="comment">{comment}</div>
            <div className="time" title={modified_time}>
              {moment(modified_time).fromNow()}
            </div>
          </React.Fragment>
        )}
      </div>
    </React.Fragment>
  );
};

export default ListItem;

In this file, we first accept the isSearchView and counter prop. Then we check if the index of the currently displayed file from the list matches with the counter value.

在此文件中,我们首先接受isSearchViewcounter isSearchView 。 然后,我们检查列表中当前显示文件的索引是否与counter值匹配。

Based on that, we display the arrow in front only for that file. Then when we use the down or up arrow to navigate through the list, we increment or decrement the counter value respectively in the App.js file.

基于此,我们仅在该文件的前面显示箭头。 然后,当我们使用向下或向上箭头浏览列表时,我们分别在App.js文件中增加或减少计数器值。

Based on the isSearchView value we display or hide the comment and time column in the search view on the UI.

基于isSearchView值,我们在UI的搜索视图中显示或隐藏注释和时间列。

Now, restart the app by running the yarn start command again and check its functionality:

现在,通过再次运行yarn start命令重新启动应用程序并检查其功能:

You can find the code up to this point in this branch.

您可以在此分支中找到到目前为止的代码。

添加功能以突出显示匹配的文本
(Add functionality to highlight matching text
)

Now, let's add the functionality to highlight the matching text from the filename when we filter the file.

现在,让我们添加功能,以在过滤文件时突出显示文件名中的匹配文本。

Open App.js and change the handleSearch function to the following code:

打开App.js并将handleSearch函数更改为以下代码:

handleSearch = (searchTerm) => {
  let list;
  if (searchTerm) {
    const pattern = new RegExp(searchTerm, 'gi');
    list = files
      .filter(
        (file) =>
          file.name.toLowerCase().indexOf(searchTerm.toLowerCase()) > -1 &&
          file.type === 'file'
      )
      .map((file) => {
        return {
          ...file,
          name: file.name.replace(pattern, (match) => {
            return `<mark>${match}</mark>`;
          })
        };
      });
  } else {
    list = files.filter((file) => file.type === 'file');
  }

  this.setState({
    filesList: list,
    counter: 0
  });
};

In this code, first we use the RegExp constructor to create a dynamic regular expression for global and case insensentive search:

在这段代码中,首先我们使用RegExp构造函数为全局和不区分大小写的搜索创建动态正则表达式:

const pattern = new RegExp(searchTerm, 'gi');

Then we filter out the files which match that search criteria:

然后,我们筛选出符合搜索条件的文件:

files.filter(
  (file) =>
    file.name.toLowerCase().indexOf(searchTerm.toLowerCase()) > -1 &&
    file.type === 'file'
);

Then we call the array map method on the result we got from above filter functionality.

然后,根据从上述过滤器功能获得的结果调用数组映射方法。

In the map method, we use the string replace method. The replace method accepts two parameters:

在map方法中,我们使用字符串replace方法。 replace方法接受两个参数:

  • pattern to search for

    搜索模式
  • function to execute for each matched pattern

    为每个匹配的模式执行的函数

We use the replace method to find all the matches for the pattern and replace it with the string <mark>${match}</mark>. Here match will contain the matched text from the file name.

我们使用replace方法查找该pattern所有匹配项,并将其替换为字符串<mark>${match}</mark> 。 这里的match将包含文件名中的匹配文本。

If you check the JSON structure from the utils/api.js file, the structure of each file looks like this:

如果您从utils/api.js文件中检查JSON结构,则每个文件的结构如下所示:

{
  id: 12,
  type: 'file',
  name: 'Search.js',
  comment: 'changes using react context',
  modified_time: '2020-06-30T07:55:33Z'
}

As we want to replace the text from the name field only, we spread out the file object properties and only change the name, keeping other values as they are.

因为我们只想替换名称字段中的文本,所以我们扩展了文件对象属性,仅更改名称,并保持其他值不变。

{
  ...file,
  name: file.name.replace(pattern, (match) => {
    return `<mark>${match}</mark>`;
  })
}

Now, restart the app by running the yarn start command again and check its functionality.

现在,通过再次运行yarn start命令重新启动应用程序并检查其功能。

You will see that the HTML is displayed as it is on the UI when you search:

搜索时,您将看到HTML在UI上按原样显示:

This is because we are displaying the file name in the ListItem.js file in the following way:

这是因为我们通过以下方式在ListItem.js文件中显示文件名:

<span className="label">{name}</span>

And to prevent Cross-site scripting (XSS) attacks, React escapes all the content displayed using the JSX Expression (which is in curly brackets).

为了防止Cross-site scripting (XSS)攻击,React会使用JSX表达式(位于大括号中)对所有显示的内容进行转义。

So if we want to actually display the correct HTML, we need to use a special prop known as dangerouslySetInnerHTML. It passes the __html name with the HTML to display as the value like this:

因此,如果我们想实际显示正确HTML,我们需要使用一种称为特殊道具dangerouslySetInnerHTML 。 它通过HTML传递__html名称,以显示为如下所示的值:

<span className="label" dangerouslySetInnerHTML={{ __html: name }}></span>

Now, restart the app by running the yarn start command again and check its functionality:

现在,通过再次运行yarn start命令重新启动应用程序并检查其功能:

As you can see, the search term is correctly getting highlighted in the name of the file.

如您所见,搜索词在文件名中正确突出显示。

而已! (That's it!)

You can find the code up to this point in this branch.

您可以在此分支中找到到目前为止的代码。

Complete GitHub Source Code: here Live Demo: here

完整的GitHub源代码: 此处在线演示: 此处

Check out my other React, Node.js, and Javascript articles at Medium, dev.to and subscribe to get weekly updates directly in your inbox here.

在Medium ( dev.to)上查看我的其他React,Node.js和Javascript文章,并订阅直接在此处的收件箱中获取每周更新

翻译自: https://www.freecodecamp.org/news/build-a-clone-of-githubs-file-search-functionality/

gi克隆github文件

最后

以上就是甜甜泥猴桃为你收集整理的gi克隆github文件_如何构建GitHub文件搜索功能的克隆的全部内容,希望文章能够帮你解决gi克隆github文件_如何构建GitHub文件搜索功能的克隆所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(48)

评论列表共有 0 条评论

立即
投稿
返回
顶部