Repository: RiyaNegi/react-comments-section
Branch: main
Commit: 7809d1cbacb3
Files: 51
Total size: 82.2 KB
Directory structure:
gitextract_e9r3kx80/
├── .editorconfig
├── .eslintignore
├── .eslintrc
├── .gitignore
├── .prettierrc
├── .travis.yml
├── README.md
├── example/
│ ├── README.md
│ ├── package.json
│ ├── public/
│ │ ├── index.html
│ │ └── manifest.json
│ ├── src/
│ │ ├── App.test.tsx
│ │ ├── App.tsx
│ │ ├── components/
│ │ │ ├── AdvancedComponent.tsx
│ │ │ ├── ClassComponent.tsx
│ │ │ ├── CustomComponent.tsx
│ │ │ ├── DefaultComponent.tsx
│ │ │ └── LogInComponent.tsx
│ │ ├── index.css
│ │ ├── index.tsx
│ │ ├── react-app-env.d.ts
│ │ └── setupTests.ts
│ └── tsconfig.json
├── package.json
├── packageold.json
├── src/
│ ├── .eslintrc
│ ├── Index.scss
│ ├── components/
│ │ ├── CommentSectionComponent/
│ │ │ ├── CommentSection.css
│ │ │ ├── Index.tsx
│ │ │ └── NoComments.tsx
│ │ ├── CommentStructure.tsx/
│ │ │ ├── CommentStructure.scss
│ │ │ ├── DeleteModal.tsx
│ │ │ ├── Index.tsx
│ │ │ └── ModalStyles.tsx
│ │ ├── InputField/
│ │ │ ├── AdvancedInput.tsx
│ │ │ ├── EmojiInput.tsx
│ │ │ ├── Index.tsx
│ │ │ ├── InputField.scss
│ │ │ ├── InputFieldStyles.tsx
│ │ │ └── RegularInput.tsx
│ │ ├── LoginSection/
│ │ │ ├── LoginSection.scss
│ │ │ └── LoginSection.tsx
│ │ ├── ModalStyles.tsx
│ │ └── data.json
│ ├── context/
│ │ └── Provider.tsx
│ ├── index.test.tsx
│ ├── index.tsx
│ ├── react-app-env.d.ts
│ └── typings.d.ts
├── tsconfig.json
└── tsconfig.test.json
================================================
FILE CONTENTS
================================================
================================================
FILE: .editorconfig
================================================
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
================================================
FILE: .eslintignore
================================================
build/
dist/
node_modules/
.snapshots/
*.min.js
================================================
FILE: .eslintrc
================================================
{
"parser": "babel-eslint",
"extends": [
"standard",
"standard-react",
"plugin:prettier/recommended",
"prettier/standard",
"prettier/react"
],
"env": {
"node": true
},
"parserOptions": {
"ecmaVersion": 2020,
"ecmaFeatures": {
"legacyDecorators": true,
"jsx": true
}
},
"settings": {
"react": {
"version": "16"
}
},
"rules": {
"space-before-function-paren": 0,
"react/prop-types": 0,
"react/jsx-handler-names": 0,
"react/jsx-fragments": 0,
"react/no-unused-prop-types": 0,
"import/export": 0
}
}
================================================
FILE: .gitignore
================================================
# See https://help.github.com/ignore-files/ for more about ignoring files.
# dependencies
node_modules
# builds
build
dist
.rpt2_cache
# misc
.DS_Store
.env
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*
================================================
FILE: .prettierrc
================================================
{
"singleQuote": true,
"jsxSingleQuote": true,
"semi": false,
"tabWidth": 2,
"bracketSpacing": true,
"jsxBracketSameLine": false,
"arrowParens": "always",
"trailingComma": "none"
}
================================================
FILE: .travis.yml
================================================
language: node_js
node_js:
- 12
- 10
================================================
FILE: README.md
================================================
# react-comments-section
## Install
Install the latest version!
```bash
npm i react-comments-section
```
## Detailed Documentation : https://riyanegi.github.io/react-comments-documentation/
`react-comments-section` is a simple but multi-functional react comment section component that helps you create comments section similar to youtube or instagram for your React App.
`react-comments-section` is very useful for react beginners who want a comment section in their project but want to skip it's complexity. This library will give a fully functional comment section with the following features:
- User can reply to comments
- User can edit his/her comments
- User can delete his/her comments
live demo of the library -> https://riyanegi.github.io/react-comments-section/
## Default Example

## Advanced Input (rich text editor)

## Usage
### Hooks Implementation (Typescript)
Following is a basic example to start testing the library in your project. This library works on a user basis
system and here are a few important points to remember:
- currentUser[required]. For no user details pass the prop as currentUser={null}
- A new user can be redirected using the login/signup links in the logIn[required] prop.
- The currentData[optional] prop returns an object of current data available after any action such as comment submission, reply, edit or delete.
- The onSubmitAction returns an object of data with the required information to make an API call after a comment is submitted.
For more details check out the props list in our detailed documentation.
This is how the basic default component would look.
```jsx
import React from 'react'
import { CommentSection} from 'react-comments-section'
import 'react-comments-section/dist/index.css'
const DefaultComponent = () => {
const data =[
{
userId: '02b',
comId: '017',
fullName: 'Lily',
userProfile: 'https://www.linkedin.com/in/riya-negi-8879631a9/',
text: 'I think you have a point🤔',
avatarUrl: 'https://ui-avatars.com/api/name=Lily&background=random',
timestamp: '2024-09-28T12:34:56Z'
replies: [],
}
]
return <CommentSection
currentUser={{
currentUserId: '01a',
currentUserImg:
'https://ui-avatars.com/api/name=Riya&background=random',
currentUserProfile:
'https://www.linkedin.com/in/riya-negi-8879631a9/',
currentUserFullName: 'Riya Negi'
}}
logIn={{
onLogin: () => alert('Call login function '),
signUpLink: 'http://localhost:3001/'
}}
commentData={data}
placeholder="Write your comment..."
onSubmitAction={(data: {
userId: string
comId: string
avatarUrl: string
userProfile?: string
fullName: string
text: string
replies: any
commentId: string
}) => console.log('check submit, ', data)}
currentData={(data: any) => {
console.log('current data', data)
}}
/>
}
export default DefaultComponent
```
### Class Implementation
```jsx
import React, { PureComponent } from 'react'
import { CommentSection } from 'react-comments-section'
import 'react-comments-section/dist/index.css'
class ClassComponent extends PureComponent {
state = {
data: [
{
userId: '01a',
comId: '012',
fullName: 'Riya Negi',
avatarUrl: 'https://ui-avatars.com/api/name=Riya&background=random',
userProfile: 'https://www.linkedin.com/in/riya-negi-8879631a9/',
text: 'Hey, Loved your blog! ',
timestamp: '2024-09-28T12:34:56Z'
replies: []
},
{
userId: '02b',
comId: '017',
fullName: 'Lily',
userProfile: 'https://www.linkedin.com/in/riya-negi-8879631a9/',
text: 'I have a doubt about the 4th point🤔',
avatarUrl: 'https://ui-avatars.com/api/name=Lily&background=random',
timestamp: '2024-09-28T12:34:56Z'
replies: []
}
]
}
onSubmitAction = (data: any) => {
console.log('this comment was posted!', data)
}
customNoComment = () => <div className='no-com'>No comments wohoooo!</div>
render() {
return (
<CommentSection
currentUser={{
currentUserId: '01a',
currentUserImg:
'https://ui-avatars.com/api/name=Riya&background=random',
currentUserProfile: 'https://www.linkedin.com/in/riya-negi-8879631a9/',
currentUserFullName: 'Riya Negi'
}}
commentData={this.state.data}
onSubmitAction={(data: any) => this.onSubmitAction(data)}
customNoComment={() => this.customNoComment()}
logIn={{
onLogin: () => alert('Call login function '),
signUpLink: 'http://localhost:3001/'
}}
placeholder="Write your comment..."
/>)
}
}
export default ClassComponent
```
## License
MIT © [RiyaNegi](https://github.com/RiyaNegi)
================================================
FILE: example/README.md
================================================
This example was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
It is linked to the rc package in the parent directory for development purposes.
You can run `npm install` and then `npm start` to test your package.
================================================
FILE: example/package.json
================================================
{
"name": "rc-example",
"homepage": ".",
"version": "1.0.0",
"private": true,
"scripts": {
"start": "node ../node_modules/react-scripts/bin/react-scripts.js start",
"build": "node ../node_modules/react-scripts/bin/react-scripts.js build",
"test": "node ../node_modules/react-scripts/bin/react-scripts.js test",
"eject": "node ../node_modules/react-scripts/bin/react-scripts.js eject"
},
"dependencies": {
"@testing-library/jest-dom": "file:../node_modules/@testing-library/jest-dom",
"@testing-library/react": "file:../node_modules/@testing-library/react",
"@testing-library/user-event": "file:../node_modules/@testing-library/user-event",
"@types/jest": "file:../node_modules/@types/jest",
"@types/node": "file:../node_modules/@types/node",
"@types/react": "file:../node_modules/@types/react",
"@types/react-dom": "file:../node_modules/@types/react-dom",
"react": "file:../node_modules/react",
"react-comments-section": "file:..",
"react-dom": "file:../node_modules/react-dom",
"react-scripts": "file:../node_modules/react-scripts",
"typescript": "file:../node_modules/typescript"
},
"devDependencies": {
"@babel/plugin-syntax-object-rest-spread": "^7.8.3"
},
"eslintConfig": {
"extends": "react-app"
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}
================================================
FILE: example/public/index.html
================================================
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, shrink-to-fit=no"
/>
<meta name="theme-color" content="#000000" />
<!--
manifest.json provides metadata used when your web app is added to the
homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>react-comments-section</title>
</head>
<body>
<noscript> You need to enable JavaScript to run this app. </noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>
================================================
FILE: example/public/manifest.json
================================================
{
"short_name": "react-comments-section",
"name": "react-comments-section",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}
================================================
FILE: example/src/App.test.tsx
================================================
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'
it('renders without crashing', () => {
const div = document.createElement('div')
ReactDOM.render(<App />, div)
ReactDOM.unmountComponentAtNode(div)
})
================================================
FILE: example/src/App.tsx
================================================
import React from 'react'
import AdvancedComponent from './components/AdvancedComponent'
import ClassComponent from './components/ClassComponent'
import CustomComponent from './components/CustomComponent'
import DefaultComponent from './components/DefaultComponent'
import LogInComponent from './components/LogInComponent'
const App = () => {
return (
<div className='example-div'>
<div className='head-title'>Demo Examples</div>
<hr style={{ borderTop: '1px solid', width: '100%' }} />
<div className='example-row'>
<DefaultComponent />
<ClassComponent />
</div>
<div className='example-row'>
<CustomComponent />
<LogInComponent />
</div>
<div className='example-row'>
<AdvancedComponent />
</div>
</div>
)
}
export default App
================================================
FILE: example/src/components/AdvancedComponent.tsx
================================================
import React from 'react'
import { CommentSection } from 'react-comments-section'
import 'react-comments-section/dist/index.css'
import { useState } from 'react'
const AdvancedComponent = () => {
let date = new Date()
const [data] = useState([
{
userId: '01a',
comId: '012',
fullName: 'Riya Negi',
avatarUrl: 'https://ui-avatars.com/api/name=Riya&background=random',
userProfile: 'https://www.linkedin.com/in/riya-negi-8879631a9/',
text: `<p>Hey <strong>loved</strong> your blog! Can you show me some other ways to <del><em>fix</em></del> solve this?🤔<br>Here's my <a href="https://www.linkedin.com/in/riya-negi-8879631a9/" target="_blank">Linkedin Profile</a> to reach out.</p>`,
timestamp: `${new Date(
date.getTime() - 5 * 60 * 60 * 1000
).toISOString()}`,
replies: [
{
userId: '02a',
comId: '013',
userProfile: 'https://www.linkedin.com/in/riya-negi-8879631a9/',
fullName: 'Adam Scott',
avatarUrl: 'https://ui-avatars.com/api/name=Adam&background=random',
text: `<p>Yeah sure try adding this line to your code. You need to pass <span style="color: rgb(147,101,184);">event</span><span style="color: rgb(26,188,156);"> </span><span style="color: rgb(0,0,0);">as a param. </span></p>
<pre>event.preventDefault()</pre>
<p>Best of luck with your project! <br></p>
<img src="https://c.tenor.com/4cR1jMpsrEgAAAAC/snoopy-cheerleader.gif" alt="undefined" style="height: auto;width: auto"/>
<p></p>`,
timestamp: `${new Date(
date.getTime() - 30 * 60 * 1000
).toISOString()}`
},
{
userId: '01a',
comId: '014',
userProfile: 'https://www.linkedin.com/in/riya-negi-8879631a9/',
fullName: 'Riya Negi',
avatarUrl: 'https://ui-avatars.com/api/name=Riya&background=random',
text: '<p><strong>OMG!</strong> it worked! <span style="color: rgb(209,72,65);">DO NOT stop this blog series!!!!</span> 💃</p>',
timestamp: `${new Date()}`
}
]
},
{
userId: '02b',
comId: '017',
fullName: 'Lily',
userProfile: 'https://www.linkedin.com/in/riya-negi-8879631a9/',
text: `<blockquote><strong>DRY </strong>- is the right of passage to good coding</blockquote>
<p>True story brother!! <em>Amen to that! </em>For anyone wondering DRY is </p>
<ol>
<li>Don't</li>
<li>Repeat</li>
<li>Yoursef</li>
</ol>`,
avatarUrl: 'https://ui-avatars.com/api/name=Lily&background=random',
timestamp: `${new Date(
date.getTime() - 3 * 60 * 60 * 1000
).toISOString()}`,
replies: []
}
])
return (
<div style={{ width: '100%' }}>
<a
style={{ color: 'black', cursor: 'pointer' }}
target='_blank'
rel='noopener noreferrer'
href='https://github.com/RiyaNegi/react-comments-section/blob/main/example/src/components/AdvancedComponent.tsx'
>
<span className='title'>Advanced Input Component</span>
</a>
<CommentSection
currentUser={{
currentUserId: '01a',
currentUserImg:
'https://ui-avatars.com/api/name=Riya&background=random',
currentUserProfile:
'https://www.linkedin.com/in/riya-negi-8879631a9/',
currentUserFullName: 'Riya Negi'
}}
hrStyle={{ border: '0.5px solid #ff0072' }}
commentData={data}
currentData={(data: any) => {
console.log('current data', data)
}}
logIn={{
onLogin: () => alert('Call login function '),
signUpLink: 'http://localhost:3001/'
}}
customImg='https://imagesvc.meredithcorp.io/v3/mm/image?url=https%3A%2F%2Fstatic.onecms.io%2Fwp-content%2Fuploads%2Fsites%2F13%2F2015%2F04%2F05%2Ffeatured.jpg&q=60'
inputStyle={{ border: '1px solid rgb(208 208 208)' }}
formStyle={{ backgroundColor: 'white' }}
submitBtnStyle={{
border: '1px solid black',
backgroundColor: 'black',
padding: '7px 15px'
}}
cancelBtnStyle={{
border: '1px solid gray',
backgroundColor: 'gray',
color: 'white',
padding: '7px 15px'
}}
advancedInput={true}
replyInputStyle={{ borderBottom: '1px solid black', color: 'black' }}
/>
</div>
)
}
export default AdvancedComponent
================================================
FILE: example/src/components/ClassComponent.tsx
================================================
import React, { PureComponent } from 'react'
import { CommentSection } from 'react-comments-section'
import 'react-comments-section/dist/index.css'
class ClassComponent extends PureComponent {
state = {
data: [
{
userId: '01a',
comId: '012',
fullName: 'Riya Negi',
avatarUrl: 'https://ui-avatars.com/api/name=Riya&background=random',
userProfile: 'https://www.linkedin.com/in/riya-negi-8879631a9/',
text: 'Hey, Loved your blog! ',
timestamp: `${new Date()}`,
replies: [
{
userId: '02a',
comId: '013',
userProfile: 'https://www.linkedin.com/in/riya-negi-8879631a9/',
fullName: 'Adam Scott',
avatarUrl: 'https://ui-avatars.com/api/name=Adam&background=random',
text: 'Thanks! It took me 1 month to finish this project but I am glad it helped out someone!🥰',
timestamp: `${new Date()}`
},
{
userId: '01a',
comId: '014',
userProfile: 'https://www.linkedin.com/in/riya-negi-8879631a9/',
fullName: 'Riya Negi',
avatarUrl: 'https://ui-avatars.com/api/name=Riya&background=random',
text: 'thanks!😊',
timestamp: `${new Date()}`
}
]
},
{
userId: '02b',
comId: '017',
fullName: 'Lily',
userProfile: 'https://www.linkedin.com/in/riya-negi-8879631a9/',
text: 'I have a doubt about the 4th point🤔',
avatarUrl: 'https://ui-avatars.com/api/name=Lily&background=random',
timestamp: `${new Date()}`,
replies: []
}
]
}
onSubmitAction = (data: any) => {
console.log('this comment was posted!,data', data)
}
render() {
return (
<div style={{ width: '100%' }}>
<a
style={{ color: 'black', cursor: 'pointer' }}
target='_blank'
rel='noopener noreferrer'
href='https://github.com/RiyaNegi/react-comments-section/blob/main/example/src/components/ClassComponent.tsx'
>
<span className='title'>Class Component</span>
</a>
<CommentSection
currentUser={{
currentUserId: '01a',
currentUserImg:
'https://ui-avatars.com/api/name=Riya&background=random',
currentUserProfile:
'https://www.linkedin.com/in/riya-negi-8879631a9/',
currentUserFullName: 'Riya Negi'
}}
placeHolder='Write your comment...'
commentData={this.state.data}
onSubmitAction={(data: any) => this.onSubmitAction(data)}
logIn={{
onLogin: () => alert('Call login function '),
signUpLink: 'http://localhost:3001/'
}}
/>
</div>
)
}
}
export default ClassComponent
================================================
FILE: example/src/components/CustomComponent.tsx
================================================
import React from 'react'
import { CommentSection } from 'react-comments-section'
import 'react-comments-section/dist/index.css'
import { useState } from 'react'
const CustomComponent = () => {
const [data] = useState([
{
userId: '01a',
comId: '012',
fullName: 'Riya Negi',
avatarUrl: 'https://ui-avatars.com/api/name=Riya&background=random',
userProfile: 'https://www.linkedin.com/in/riya-negi-8879631a9/',
text: 'Hey, Loved your blog! ',
replies: [
{
userId: '02a',
comId: '013',
userProfile: 'https://www.linkedin.com/in/riya-negi-8879631a9/',
fullName: 'Adam Scott',
avatarUrl: 'https://ui-avatars.com/api/name=Adam&background=random',
text: 'Thanks! It took me 1 month to finish this project but I am glad it helped out someone!🥰'
},
{
userId: '01a',
comId: '014',
userProfile: 'https://www.linkedin.com/in/riya-negi-8879631a9/',
fullName: 'Riya Negi',
avatarUrl: 'https://ui-avatars.com/api/name=Riya&background=random',
text: 'thanks!😊'
}
]
},
{
userId: '02b',
comId: '017',
fullName: 'Lily',
userProfile: 'https://www.linkedin.com/in/riya-negi-8879631a9/',
text: 'I have a doubt about the 4th point🤔',
avatarUrl: 'https://ui-avatars.com/api/name=Lily&background=random',
replies: []
}
])
const customNoComment = () => (
<div className='no-com'>No comments wohoooo!</div>
)
return (
<div style={{ width: '100%' }}>
<a
style={{ color: 'black', cursor: 'pointer' }}
target='_blank'
rel='noopener noreferrer'
href='https://github.com/RiyaNegi/react-comments-section/blob/main/example/src/components/CustomComponent.tsx'
>
<span className='title'>Custom Component</span>
</a>
<CommentSection
currentUser={{
currentUserId: '01a',
currentUserImg:
'https://ui-avatars.com/api/name=Riya&background=random',
currentUserProfile:
'https://www.linkedin.com/in/riya-negi-8879631a9/',
currentUserFullName: 'Riya Negi'
}}
showTimestamp={false}
hrStyle={{ border: '0.5px solid #ff0072' }}
titleStyle={{ color: '#f2f2f2' }}
commentsCount={8}
commentData={data}
currentData={(data: any) => {
console.log('current data', data)
}}
logIn={{
onLogin: () => alert('Call login function '),
signUpLink: 'http://localhost:3001/'
}}
onSubmitAction={(data: {
userId: string
comId: string
avatarUrl: string
userProfile?: string
fullName: string
text: string
replies: any
}) => console.log('check submit, ', data)}
onDeleteAction={(data: any) => console.log('comment was deleted', data)}
onReplyAction={(data: {
userId: string
parentOfRepliedCommentId: string
repliedToCommentId: string
avatarUrl: string
userProfile?: string
fullName: string
text: string
}) => console.log('check reply, ', data)}
onEditAction={(data: any) => console.log('check edit', data)}
customNoComment={() => customNoComment()}
imgStyle={{ borderRadius: '0%' }}
customImg='https://imagesvc.meredithcorp.io/v3/mm/image?url=https%3A%2F%2Fstatic.onecms.io%2Fwp-content%2Fuploads%2Fsites%2F13%2F2015%2F04%2F05%2Ffeatured.jpg&q=60'
inputStyle={{ border: '1px solid rgb(208 208 208)' }}
formStyle={{ backgroundColor: 'white' }}
submitBtnStyle={{ border: '1px solid black', backgroundColor: 'black' }}
cancelBtnStyle={{
border: '1px solid gray',
backgroundColor: 'gray',
color: 'white'
}}
removeEmoji={true}
overlayStyle={{ backgroundColor: '#0f0d29', color: 'white' }}
replyInputStyle={{ borderBottom: '1px solid black', color: 'black' }}
/>
</div>
)
}
export default CustomComponent
================================================
FILE: example/src/components/DefaultComponent.tsx
================================================
import React from 'react'
import { CommentSection } from 'react-comments-section'
import 'react-comments-section/dist/index.css'
const DefaultComponent = () => {
const data = [
{
userId: '01a',
comId: '012',
fullName: 'Riya Negi',
avatarUrl: 'https://ui-avatars.com/api/name=Riya&background=random',
userProfile: 'https://www.linkedin.com/in/riya-negi-8879631a9/',
text: 'Hey, Loved your blog! ',
timestamp: '2024-09-28T10:34:56Z',
replies: [
{
userId: '02a',
comId: '013',
userProfile: 'https://www.linkedin.com/in/riya-negi-8879631a9/',
fullName: 'Adam Scott',
avatarUrl: 'https://ui-avatars.com/api/name=Adam&background=random',
text: 'Thanks! It took me 1 month to finish this project but I am glad it helped out someone!🥰',
timestamp: '2024-09-28T12:34:56Z'
},
{
userId: '01a',
comId: '014',
userProfile: 'https://www.linkedin.com/in/riya-negi-8879631a9/',
fullName: 'Riya Negi',
avatarUrl: 'https://ui-avatars.com/api/name=Riya&background=random',
text: 'thanks!😊',
timestamp: '2024-09-28T12:34:56Z'
}
]
},
{
userId: '02b',
comId: '017',
fullName: 'Lily',
userProfile: 'https://www.linkedin.com/in/riya-negi-8879631a9/',
text: 'I have a doubt about the 4th point🤔',
avatarUrl: 'https://ui-avatars.com/api/name=Lily&background=random',
timestamp: '2024-09-28T12:34:56Z',
replies: []
}
]
return (
<div style={{ width: '100%' }}>
<a
style={{ color: 'black', cursor: 'pointer' }}
target='_blank'
rel='noopener noreferrer'
href='https://github.com/RiyaNegi/react-comments-section/blob/main/example/src/components/DefaultComponent.tsx'
>
<span className='title'>Default Component</span>
</a>
<CommentSection
currentUser={{
currentUserId: '01a',
currentUserImg:
'https://ui-avatars.com/api/name=Riya&background=random',
currentUserProfile:
'https://www.linkedin.com/in/riya-negi-8879631a9/',
currentUserFullName: 'Riya Negi'
}}
commentData={data}
logIn={{
onLogin: () => alert('Call login function '),
signUpLink: 'http://localhost:3001/'
}}
placeHolder='Write your comment...'
onSubmitAction={(data: {
userId: string
comId: string
avatarUrl: string
userProfile?: string
fullName: string
text: string
replies: any
commentId: string
}) => console.log('check submit, ', data)}
currentData={(data: any) => {
console.log('current data', data)
}}
/>
</div>
)
}
export default DefaultComponent
================================================
FILE: example/src/components/LogInComponent.tsx
================================================
import React from 'react'
import { CommentSection } from 'react-comments-section'
import 'react-comments-section/dist/index.css'
const LogInComponent = () => {
const data = [
{
userId: '01a',
comId: '012',
fullName: 'Riya Negi',
avatarUrl: 'https://ui-avatars.com/api/name=Riya&background=random',
userProfile: 'https://www.linkedin.com/in/riya-negi-8879631a9/',
text: 'Hey, Loved your blog! ',
replies: [
{
userId: '02a',
comId: '013',
userProfile: 'https://www.linkedin.com/in/riya-negi-8879631a9/',
fullName: 'Adam Scott',
avatarUrl: 'https://ui-avatars.com/api/name=Adam&background=random',
text: 'Thanks! It took me 1 month to finish this project but I am glad it helped out someone!🥰'
},
{
userId: '01a',
comId: '014',
userProfile: 'https://www.linkedin.com/in/riya-negi-8879631a9/',
fullName: 'Riya Negi',
avatarUrl: 'https://ui-avatars.com/api/name=Riya&background=random',
text: 'thanks!😊'
}
]
},
{
userId: '02b',
comId: '017',
fullName: 'Lily',
userProfile: 'https://www.linkedin.com/in/riya-negi-8879631a9/',
text: 'I have a doubt about the 4th point🤔',
avatarUrl: 'https://ui-avatars.com/api/name=Lily&background=random',
replies: []
}
]
return (
<div style={{ width: '100%' }}>
<a
style={{ color: 'black', cursor: 'pointer' }}
target='_blank'
rel='noopener noreferrer'
href='https://github.com/RiyaNegi/react-comments-section/blob/main/example/src/components/LogInComponent.tsx'
>
<span className='title'>Login Component</span>
</a>
<CommentSection
currentUser={null}
commentData={data}
logIn={{
onLogin: () => alert('Call login function '),
signUpLink: 'http://localhost:3001/'
}}
showTimestamp={false}
/>
</div>
)
}
export default LogInComponent
================================================
FILE: example/src/index.css
================================================
body {
margin: 0;
padding: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}
.no-com {
background-color: aqua;
}
.example-div {
display: flex;
width: 100%;
flex-direction: column;
}
.example-row {
display: flex;
width: 100;
}
.title {
font-size: 18px;
font-weight: 700;
margin: 10px 20px;
}
.head-title {
font-size: 24px;
font-weight: 700;
display: flex;
justify-content: center;
margin: 10px;
}
@media screen and (max-width: 600px) {
.example-row {
display: flex;
width: 100;
flex-direction: column;
}
}
================================================
FILE: example/src/index.tsx
================================================
import './index.css'
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'
ReactDOM.render(<App />, document.getElementById('root'))
================================================
FILE: example/src/react-app-env.d.ts
================================================
/// <reference types="react-scripts" />
================================================
FILE: example/src/setupTests.ts
================================================
// jest-dom adds custom jest matchers for asserting on DOM nodes.
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom/extend-expect';
================================================
FILE: example/tsconfig.json
================================================
{
"compilerOptions": {
"outDir": "dist",
"module": "esnext",
"lib": ["dom", "esnext"],
"moduleResolution": "node",
"jsx": "react",
"sourceMap": true,
"declaration": true,
"esModuleInterop": true,
"noImplicitReturns": true,
"noImplicitThis": true,
"noImplicitAny": true,
"strictNullChecks": true,
"suppressImplicitAnyIndexErrors": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"allowSyntheticDefaultImports": true,
"target": "es5",
"allowJs": false,
"skipLibCheck": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true
},
"include": ["src"],
"exclude": ["node_modules", "build"]
}
================================================
FILE: package.json
================================================
{
"name": "react-comments-section",
"version": "3.2.0",
"description": "React component library for a functioning comments section",
"author": "RiyaNegi",
"license": "MIT",
"repository": "RiyaNegi/react-comments-section",
"main": "dist/index.js",
"style": "dist/styles.css",
"module": "dist/index.modern.js",
"source": "src/index.tsx",
"sideEffects": false,
"engines": {
"node": ">=10"
},
"scripts": {
"build": "microbundle-crl-with-assets --css-modules false --no-compress --format modern,cjs",
"start": "microbundle-crl-with-assets --css-modules false watch --no-compress --format modern,cjs",
"prepare": "run-s build",
"test": "run-s test:unit test:lint test:build",
"test:build": "run-s build",
"test:lint": "eslint .",
"test:unit": "cross-env CI=1 react-scripts test --env=jsdom",
"test:watch": "react-scripts test --env=jsdom",
"predeploy": "cd example && npm run build",
"deploy": "gh-pages -d example/build"
},
"peerDependencies": {
"react": ">=16.0.0"
},
"devDependencies": {
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.5.0",
"@testing-library/user-event": "^7.2.1",
"@types/draftjs-to-html": "^0.8.1",
"@types/html-to-draftjs": "^1.4.0",
"@types/jest": "^25.1.4",
"@types/node": "^12.12.38",
"@types/react": "^16.14.60",
"@types/react-dom": "^16.9.24",
"@types/react-draft-wysiwyg": "^1.13.4",
"@typescript-eslint/eslint-plugin": "^2.26.0",
"@typescript-eslint/parser": "^2.26.0",
"babel-eslint": "^10.0.3",
"cross-env": "^7.0.2",
"eslint": "^6.8.0",
"eslint-config-prettier": "^6.7.0",
"eslint-config-standard": "^14.1.0",
"eslint-config-standard-react": "^9.2.0",
"eslint-plugin-import": "^2.18.2",
"eslint-plugin-node": "^11.0.0",
"eslint-plugin-prettier": "^3.1.1",
"eslint-plugin-promise": "^4.2.1",
"eslint-plugin-react": "^7.17.0",
"eslint-plugin-standard": "^4.0.1",
"gh-pages": "^2.2.0",
"microbundle-crl": "^0.13.10",
"microbundle-crl-with-assets": "^0.13.12",
"npm-run-all": "^4.1.5",
"prettier": "^2.0.4",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-scripts": "^3.4.1",
"typescript": "^3.9.10"
},
"files": [
"dist"
],
"dependencies": {
"@emotion/react": "^11.9.0",
"@szhsin/react-menu": "^3.0.1",
"@types/lodash": "^4.14.182",
"@types/uuid": "^8.3.4",
"draft-js": "^0.11.7",
"draftjs-to-html": "^0.9.1",
"emoji-picker-react": "^3.5.1",
"html-to-draftjs": "^1.5.0",
"react-comments-section": "^3.0.1",
"react-draft-wysiwyg": "^1.14.7",
"react-responsive-modal": "^6.2.0",
"sass": "^1.51.0"
}
}
================================================
FILE: packageold.json
================================================
{
"name": "react-comments-section",
"version": "1.0.7",
"description": "A react library for building comments section",
"author": "RiyaNegi",
"license": "MIT",
"repository": "RiyaNegi/react-comments",
"main": "dist/index.js",
"module": "dist/index.modern.js",
"source": "src/index.js",
"engines": {
"node": ">=10"
},
"scripts": {
"build": "microbundle-crl --no-compress --format modern,cjs",
"start": "microbundle-crl watch --no-compress --format modern,cjs",
"prepare": "run-s build",
"test": "run-s test:unit test:lint test:build",
"test:build": "run-s build",
"test:lint": "eslint .",
"test:unit": "cross-env CI=1 react-scripts test --env=jsdom",
"test:watch": "react-scripts test --env=jsdom",
"predeploy": "cd example && npm install && npm run build",
"deploy": "gh-pages -d example/build"
},
"peerDependencies": {
"react": "^16.0.0"
},
"devDependencies": {
"babel-eslint": "^10.0.3",
"cross-env": "^7.0.2",
"eslint": "^6.8.0",
"eslint-config-prettier": "^6.7.0",
"eslint-config-standard": "^14.1.0",
"eslint-config-standard-react": "^9.2.0",
"eslint-plugin-import": "^2.18.2",
"eslint-plugin-node": "^11.0.0",
"eslint-plugin-prettier": "^3.1.1",
"eslint-plugin-promise": "^4.2.1",
"eslint-plugin-react": "^7.17.0",
"eslint-plugin-standard": "^4.0.1",
"gh-pages": "^2.2.0",
"microbundle-crl": "^0.13.10",
"npm-run-all": "^4.1.5",
"prettier": "^2.0.4",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-scripts": "^3.4.1",
"sass": "^1.50.1",
"@types/uuid": "^8.3.4",
"lodash": "^4.17.21",
"node-sass": "^7.0.1",
"@szhsin/react-menu": "^3.0.1",
"react-responsive-modal": "^6.2.0",
"@types/lodash": "^4.14.182"
},
"files": [
"dist"
],
"dependencies": {
"@types/jest": "^27.4.1",
"@types/node": "^17.0.25",
"@types/react": "^18.0.6",
"@types/react-dom": "^18.0.2",
"jquery": "^3.5.1",
"node-sass": "^5.0.0",
"popper.js": "^1.16.1",
"react-uuid": "^1.0.2",
"reactjs-popup": "^2.0.4",
"typescript": "^3.9.10"
}
}
================================================
FILE: src/.eslintrc
================================================
{
"env": {
"jest": true
}
}
================================================
FILE: src/Index.scss
================================================
/* add css module styles here (optional) */
body {
blockquote {
border-left: 5px solid #f1f1f1;
padding-left: 5px;
}
pre {
background: #f1f1f1;
border-radius: 3px;
padding: 7px 10px;
}
}
================================================
FILE: src/components/CommentSectionComponent/CommentSection.css
================================================
@import url('https://fonts.googleapis.com/css2?family=Noto+Sans&display=swap');
.overlay {
display: flex;
flex-direction: column;
padding: 20px;
font-family: 'Noto Sans', sans-serif;
}
.replySection {
border-left: 1px solid rgb(235, 235, 235);
margin-left: 25px;
padding: 0px 0px 0px 15px;
}
.comment-title {
font-family: 'Noto Sans', sans-serif;
font-size: 30px;
font-weight: 700;
color: #202020d1;
}
.no-comDiv {
display: flex;
justify-content: center;
font-size: 18px;
font-weight: 700;
color: #202020d1;
margin-top: 40px;
}
================================================
FILE: src/components/CommentSectionComponent/Index.tsx
================================================
import CommentStructure from '../CommentStructure.tsx/Index'
import InputField from '../InputField/Index'
import './CommentSection.css'
import { useContext } from 'react'
import { GlobalContext } from '../../context/Provider'
import _ from 'lodash'
import React from 'react'
import LoginSection from '../LoginSection/LoginSection'
import NoComments from './NoComments'
interface CommentSectionProps {
overlayStyle?: object
logIn: {
loginLink?: string | (() => void)
signUpLink?: string | (() => void)
onLogin?: string | (() => void)
onSignUp?: string | (() => void)
}
hrStyle?: object
titleStyle?: object
customNoComment?: Function
showTimestamp?: boolean
}
const CommentSection = ({
overlayStyle,
logIn,
hrStyle,
titleStyle,
customNoComment,
showTimestamp = true
}: CommentSectionProps) => {
const handleLogin = () => {
if (typeof logIn.onLogin === 'function') {
logIn.onLogin()
} else if (typeof logIn.loginLink === 'string') {
window.location.href = logIn.loginLink
}
}
const handleSignUp = () => {
if (typeof logIn.onSignUp === 'function') {
logIn.onSignUp()
} else if (typeof logIn.signUpLink === 'string') {
window.location.href = logIn.signUpLink
}
}
const loginMode = () => {
return <LoginSection loginLink={handleLogin} signUpLink={handleSignUp} />
}
const globalStore: any = useContext(GlobalContext)
const totalComments = () => {
let count = 0
globalStore.data.map((i: any) => {
count = count + 1
i.replies.map(() => (count = count + 1))
})
return count
}
return (
<div className='overlay' style={overlayStyle}>
<span className='comment-title' style={titleStyle}>
{globalStore.commentsCount || totalComments()}{' '}
{totalComments() === 1 ? 'Comment' : 'Comments'}
</span>
<hr className='hr-style' style={hrStyle} />
{globalStore.currentUserData === null ? (
loginMode()
) : (
<InputField
placeHolder={globalStore.placeHolder}
formStyle={{ margin: '10px 0px' }}
imgDiv={{ margin: 0 }}
/>
)}
{globalStore.data.length > 0 ? (
globalStore.data.map(
(i: {
userId: string
comId: string
fullName: string
avatarUrl: string
text: string
userProfile?: string
replies: Array<any> | undefined
}) => {
return (
<div key={i.comId}>
<CommentStructure
info={i}
editMode={
_.indexOf(globalStore.editArr, i.comId) === -1
? false
: true
}
replyMode={
_.indexOf(globalStore.replyArr, i.comId) === -1
? false
: true
}
logIn={logIn}
showTimestamp={showTimestamp}
/>
{i.replies &&
i.replies.length > 0 &&
i.replies.map((j) => {
return (
<div className='replySection' key={j.comId}>
<CommentStructure
info={j}
parentId={i.comId}
editMode={
_.indexOf(globalStore.editArr, j.comId) === -1
? false
: true
}
replyMode={
_.indexOf(globalStore.replyArr, j.comId) === -1
? false
: true
}
logIn={logIn}
showTimestamp={showTimestamp}
/>
</div>
)
})}
</div>
)
}
)
) : customNoComment ? (
customNoComment()
) : (
<NoComments />
)}
</div>
)
}
export default CommentSection
================================================
FILE: src/components/CommentSectionComponent/NoComments.tsx
================================================
import React from 'react'
const NoComments = () => {
return (
<div className='no-comDiv'>
{' '}
No comments here. Be the first one to comment!
</div>
)
}
export default NoComments
================================================
FILE: src/components/CommentStructure.tsx/CommentStructure.scss
================================================
.userInfo {
display: flex;
flex-direction: column;
.commentsTwo {
display: flex;
align-items: center;
margin-top: 8px;
.fullName {
display: flex;
margin-left: 10px;
font-size: 16px;
font-weight: 600;
}
.commenttimestamp {
display: flex;
margin-left: 10px;
font-size: 14px;
font-weight: 400;
color: gray;
align-items: flex-end;
}
}
}
.halfDiv {
display: flex;
justify-content: space-between;
}
.replyBtn {
background-color: transparent;
border: none;
color: gray;
outline: none;
font-weight: 600;
font-size: 14px;
margin: 2px 5px 0px 0px !important;
width: 70px;
padding: 5px;
border-radius: 4px;
&:hover {
outline: none;
background-color: rgba(160, 160, 160, 0.315);
}
&:focus {
outline: 0;
}
}
.userActions {
margin-top: 20px;
.actionsBtn {
background-color: transparent;
border: none;
padding: 6px;
border-radius: 50%;
cursor: pointer;
&:focus {
outline: 0;
}
&:hover {
outline: none;
background-color: rgb(123 123 123 / 10%);
border-radius: 50%;
}
}
}
.userLink {
display: flex;
text-decoration: none;
color: inherit;
align-items: center;
.imgdefault {
width: 28px;
height: 28px;
border-radius: 14px;
}
}
.replysection {
display: flex;
flex-direction: column;
}
.infoStyle {
margin-left: 36px;
font-size: 15px;
p {
margin: 0px;
}
}
.replyIcon {
background-image: url('../../assets/reply.svg');
width: 16px;
height: 13px;
filter: invert(67%) sepia(0%) saturate(0%) hue-rotate(110deg) brightness(85%)
contrast(84%);
margin-right: 5px;
position: absolute;
}
.optionIcon {
background-image: url('../../assets/options.svg');
width: 6px;
height: 6px;
filter: invert(24%) sepia(0%) saturate(0%) hue-rotate(155deg) brightness(98%)
contrast(93%);
padding: 7px;
background-repeat: no-repeat;
}
.szh-menu {
font-family: sans-serif;
font-size: 0.925rem;
user-select: none;
box-shadow: 1px 1px 20px 1px rgba(0, 0, 0, 0.1);
border-radius: 6px;
padding: 6px !important;
min-width: 7rem;
left: -70px !important;
top: -5px !important;
color: black;
.szh-menu__item {
padding: 5px;
}
.szh-menu__item:hover {
color: black;
background-color: #f5f5f5;
}
}
.react-responsive-modal-modal {
max-width: 240px !important;
h2,
p {
text-align: center;
}
}
.deleteBtns {
display: flex;
justify-content: center;
}
.delete {
border: none;
border-radius: 4px;
background-color: rgb(255 77 0);
padding: 5px 10px;
color: white;
font-weight: bolder;
font-size: 14px;
cursor: pointer;
}
.cancel {
border: none;
border-radius: 4px;
background-color: rgb(148 148 148);
padding: 5px 10px;
color: white;
font-weight: bolder;
font-size: 14px;
cursor: pointer;
margin-left: 10px;
}
================================================
FILE: src/components/CommentStructure.tsx/DeleteModal.tsx
================================================
import { useState, useContext } from 'react'
import 'react-responsive-modal/styles.css'
import { Modal } from 'react-responsive-modal'
import { GlobalContext } from '../../context/Provider'
import React from 'react'
interface DeleteModalProps {
comId: string
parentId?: string
}
const DeleteModal = ({ comId, parentId }: DeleteModalProps) => {
const [open, setOpen] = useState(false)
const onOpenModal = () => setOpen(true)
const onCloseModal = () => setOpen(false)
const globalStore: any = useContext(GlobalContext)
return (
<div>
<div style={{ width: '100%' }} onClick={onOpenModal}>
delete
</div>
<Modal open={open} onClose={onCloseModal} center>
<h2>Are you sure?</h2>
<p>Once you delete this comment it will be gone forever.</p>
<div className='deleteBtns'>
<button
className='delete'
onClick={async () => (
await globalStore.onDelete(comId, parentId),
globalStore.onDeleteAction &&
(await globalStore.onDeleteAction({
comIdToDelete: comId,
parentOfDeleteId: parentId
}))
)}
>
Delete
</button>
<button className='cancel' onClick={onCloseModal}>
Cancel
</button>
</div>
</Modal>
</div>
)
}
export default DeleteModal
================================================
FILE: src/components/CommentStructure.tsx/Index.tsx
================================================
import './CommentStructure.scss'
import { useContext } from 'react'
import { GlobalContext } from '../../context/Provider'
import InputField from '../InputField/Index'
import { Menu, MenuItem } from '@szhsin/react-menu'
import '@szhsin/react-menu/dist/core.css'
import DeleteModal from './DeleteModal'
import React from 'react'
interface CommentStructureProps {
info: {
userId: string
comId: string
fullName: string
avatarUrl: string
text: string
userProfile?: string
timestamp?: string
replies?: Array<object> | undefined
}
editMode: boolean
parentId?: string
replyMode: boolean
showTimestamp?: boolean
logIn: {
loginLink?: string | (() => void)
signUpLink?: string | (() => void)
onLogin?: string | (() => void)
onSignUp?: string | (() => void)
}
}
const CommentStructure = ({
info,
editMode,
parentId,
replyMode,
showTimestamp
}: CommentStructureProps) => {
const globalStore: any = useContext(GlobalContext)
const currentUser = globalStore.currentUserData
const optionsMenu = () => {
return (
<div className='userActions'>
{info.userId === currentUser.currentUserId && (
<Menu
menuButton={
<button className='actionsBtn'>
{' '}
<div className='optionIcon' />
</button>
}
>
<MenuItem
onClick={() => globalStore.handleAction(info.comId, true)}
>
edit
</MenuItem>
<MenuItem>
<DeleteModal comId={info.comId} parentId={parentId} />
</MenuItem>
</Menu>
)}
</div>
)
}
const timeAgo = (date: string | number | Date): string => {
const units = [
{ label: 'year', seconds: 31536000 },
{ label: 'month', seconds: 2592000 },
{ label: 'day', seconds: 86400 },
{ label: 'hour', seconds: 3600 },
{ label: 'minute', seconds: 60 },
{ label: 'second', seconds: 1 }
]
const time = Math.floor(
(new Date().valueOf() - new Date(date).valueOf()) / 1000
)
for (let { label, seconds } of units) {
const interval = Math.floor(time / seconds)
if (interval >= 1) {
return `${interval} ${label}${interval > 1 ? 's' : ''} ago`
}
}
return 'just now'
}
const userInfo = () => {
return (
<div className='commentsTwo'>
<a className='userLink' target='_blank' href={info.userProfile}>
<div>
<img
src={info.avatarUrl}
alt='userIcon'
className='imgdefault'
style={
globalStore.imgStyle ||
(!globalStore.replyTop
? { position: 'relative', top: 7 }
: null)
}
/>
</div>
<div className='fullName'>
{info.fullName}
<span className='commenttimestamp'>
{showTimestamp &&
(info.timestamp == null ? null : timeAgo(info.timestamp))}
</span>
</div>
</a>
</div>
)
}
const replyTopSection = () => {
return (
<div className='halfDiv'>
<div className='userInfo'>
<div>{info.text}</div>
{userInfo()}
</div>
{currentUser && optionsMenu()}
</div>
)
}
const replyBottomSection = () => {
return (
<div className='halfDiv'>
<div className='userInfo'>
{userInfo()}
{globalStore.advancedInput ? (
<div
className='infoStyle'
dangerouslySetInnerHTML={{
__html: info.text
}}
/>
) : (
<div className='infoStyle'>{info.text}</div>
)}
<div style={{ marginLeft: 32 }}>
{' '}
{currentUser && (
<div>
<button
className='replyBtn'
onClick={() => globalStore.handleAction(info.comId, false)}
>
<div className='replyIcon' />
<span style={{ marginLeft: 17 }}>Reply</span>
</button>
</div>
)}
</div>
</div>
{currentUser && optionsMenu()}
</div>
)
}
const actionModeSection = (mode: string) => {
if (mode === 'reply') {
return (
<div className='replysection'>
{globalStore.replyTop ? replyTopSection() : replyBottomSection()}
<InputField
formStyle={{
backgroundColor: 'transparent',
padding: '20px 0px',
marginLeft: '-15px'
}}
comId={info.comId}
fillerText={''}
mode={'replyMode'}
parentId={parentId}
/>
</div>
)
} else {
return (
<InputField
formStyle={{
backgroundColor: 'transparent',
padding: '20px 0px',
marginLeft: '-15px'
}}
comId={info.comId}
fillerText={info.text}
mode={'editMode'}
parentId={parentId}
/>
)
}
}
return (
<div>
{editMode
? actionModeSection('edit')
: replyMode
? actionModeSection('reply')
: globalStore.replyTop
? replyTopSection()
: replyBottomSection()}
</div>
)
}
export default CommentStructure
================================================
FILE: src/components/CommentStructure.tsx/ModalStyles.tsx
================================================
export const modal = {
fontSize: "16px",
};
export const modalClose = {
cursor: "pointer",
position: "absolute",
display: "block",
padding: "2px 5px",
lineHeight: "20px",
right: "-10px",
top: "-10px",
fontSize: "24px",
background: "#ffffff",
borderRadius: "18px",
border: "1px solid #cfcece",
outline: "none",
};
export const modalHeader = {
width: "100%",
borderBottom: "1px solid gray",
fontSize: "18px",
textAlign: "center",
padding: "5px",
};
export const modalContent = {
width: "100%",
padding: "10px 10px",
};
export const modalActions = {
width: " 100%",
padding: " 10px 5px",
margin: " auto",
textAlign: " center",
};
export const modalActionBtn = {
backgroundColor: "transparent",
outline: "none",
border: "1px solid gray",
padding: "4px 12px",
cursor: "pointer",
};
export const modalDelBtn = {
backgroundColor: "transparent",
outline: "none",
border: "1px solid gray",
marginLeft: "10px",
padding: "4px 12px",
cursor: "pointer",
};
================================================
FILE: src/components/InputField/AdvancedInput.tsx
================================================
import React, { useState, useEffect } from 'react'
import 'react-draft-wysiwyg/dist/react-draft-wysiwyg.css'
import { useContext } from 'react'
import { GlobalContext } from '../../context/Provider'
import { EditorState, ContentState, convertToRaw } from 'draft-js'
import { Editor } from 'react-draft-wysiwyg'
import draftToHtml from 'draftjs-to-html'
import htmlToDraft from 'html-to-draftjs'
interface AdvancedInputProps {
formStyle?: object
handleSubmit: Function
mode?: string
cancelBtnStyle?: object
submitBtnStyle?: object
comId?: string
imgStyle?: object
imgDiv?: object
customImg?: string
text: string
placeHolder?: string
}
const AdvancedInput = ({
formStyle,
handleSubmit,
submitBtnStyle,
cancelBtnStyle,
mode,
comId,
imgDiv,
imgStyle,
customImg,
text,
placeHolder
}: AdvancedInputProps) => {
const [html, setHtml] = useState('<p></p>')
const globalStore: any = useContext(GlobalContext)
useEffect(() => {
if (text != '') {
setHtml(text)
}
}, [text])
useEffect(() => {
if (html != '<p></p>') {
setEditor(EditorState.createWithContent(contentState))
}
}, [html])
const contentBlock = htmlToDraft(html)
const contentState = ContentState.createFromBlockArray(
contentBlock.contentBlocks
)
const [editorState, setEditor] = useState(
EditorState.createWithContent(contentState)
)
const [editText, setEditText] = useState<string>('')
const onEditorStateChange: Function = (editorState: any) => {
setEditor(editorState)
}
useEffect(() => {
setEditText(
draftToHtml(convertToRaw(editorState.getCurrentContent())).trim()
)
}, [editorState])
return (
<div className='advanced-overlay'>
<div className='userImg' style={imgDiv}>
<a
target='_blank'
href={globalStore.currentUserData.currentUserProfile}
>
<img
src={
globalStore.customImg ||
customImg ||
globalStore.currentUserData.currentUserImg
}
style={globalStore.imgStyle || imgStyle}
alt='userIcon'
className='imgdefault'
/>
</a>
</div>
<div className='advanced-input'>
<form
className='form advanced-form '
style={globalStore.formStyle || formStyle}
onSubmit={async (e) =>
editText != '<p></p>'
? (await handleSubmit(e, editText),
setEditor(EditorState.createEmpty()))
: null
}
>
<div className='advanced-border'>
<Editor
editorState={editorState}
placeholder={placeHolder ? placeHolder : 'Type your reply here.'}
onEditorStateChange={(editorState) =>
onEditorStateChange(editorState)
}
toolbar={{
options: [
'inline',
'blockType',
'list',
'colorPicker',
'link',
'emoji',
'image'
],
link: {
inDropdown: false,
className: undefined,
component: undefined,
popupClassName: undefined,
dropdownClassName: undefined,
showOpenOptionOnHover: true,
defaultTargetOption: '_self',
options: ['link'],
linkCallback: undefined
},
image: {
className: undefined,
component: undefined,
popupClassName: undefined,
urlEnabled: true,
uploadEnabled: true,
alignmentEnabled: true,
uploadCallback: undefined,
previewImage: false,
inputAccept:
'image/gif,image/jpeg,image/jpg,image/png,image/svg',
alt: { present: false, mandatory: false },
defaultSize: {
height: 'auto',
width: 'auto'
}
},
inline: {
inDropdown: false,
className: undefined,
component: undefined,
dropdownClassName: undefined,
options: [
'bold',
'italic',
'underline',
'strikethrough',
'monospace'
]
},
blockType: {
inDropdown: true,
options: ['Normal', 'Blockquote', 'Code'],
className: undefined,
component: undefined,
dropdownClassName: undefined
},
list: {
inDropdown: false,
className: undefined,
component: undefined,
dropdownClassName: undefined,
options: ['unordered', 'ordered']
}
}}
/>
</div>
{/* <div
dangerouslySetInnerHTML={{
__html: text
}}
/> */}
<div className='advanced-btns'>
{mode && (
<button
className='advanced-cancel cancelBtn'
style={globalStore.cancelBtnStyle || cancelBtnStyle}
type='button'
onClick={() =>
mode === 'editMode'
? globalStore.handleAction(comId, true)
: globalStore.handleAction(comId, false)
}
>
Cancel
</button>
)}
<button
className='advanced-post postBtn'
type='submit'
disabled={editText === '<p></p>' ? true : false}
style={globalStore.submitBtnStyle || submitBtnStyle}
onClick={async (e) =>
editText != '<p></p>'
? (await handleSubmit(e, editText),
setEditor(EditorState.createEmpty()))
: null
}
>
Post
</button>
</div>
</form>
</div>
</div>
)
}
export default AdvancedInput
================================================
FILE: src/components/InputField/EmojiInput.tsx
================================================
import React, { useRef, useEffect, useState, useContext } from 'react'
import { GlobalContext } from '../../context/Provider'
import Picker from 'emoji-picker-react'
import './InputField.scss'
function useOutsideAlerter(ref: any, setOpen: Function) {
useEffect(() => {
function handleClickOutside(event: any) {
if (ref.current && !ref.current.contains(event.target)) {
setOpen(!open)
}
}
document.addEventListener('mousedown', handleClickOutside)
return () => {
document.removeEventListener('mousedown', handleClickOutside)
}
}, [ref])
}
interface EmojiInputProps {
text: string
setText: Function
mode?: string
inputStyle?: object
placeHolder?: string
}
const EmojiInput = ({
text,
setText,
mode,
inputStyle,
placeHolder
}: EmojiInputProps) => {
const [open, setOpen] = useState(false)
const [chosenEmoji, setChosenEmoji] = useState<{ emoji?: any }>()
const wrapperRef = useRef(null)
useOutsideAlerter(wrapperRef, setOpen)
const globalStore: any = useContext(GlobalContext)
useEffect(() => {
if (chosenEmoji) {
let newText = text + ' ' + chosenEmoji.emoji
setText(newText)
}
}, [chosenEmoji])
const onEmojiClick = (event: any, emojiObject: { emoji?: any }) => {
event
setChosenEmoji(emojiObject)
}
return (
<div className='emoji-input'>
<input
className='postComment'
style={
mode === 'replyMode' || mode === 'editMode'
? globalStore.replyInputStyle
: globalStore.inputStyle || inputStyle
}
placeholder={placeHolder ? placeHolder : 'Type your reply here.'}
type='text'
value={text}
onChange={(e) => setText(e.target.value)}
/>
<div className='emoji-icon' onClick={() => setOpen(!open)}></div>
{open ? (
<div ref={wrapperRef}>
<Picker onEmojiClick={onEmojiClick} />
</div>
) : null}
</div>
)
}
export default EmojiInput
================================================
FILE: src/components/InputField/Index.tsx
================================================
import './InputField.scss'
import { useContext, useEffect, useState } from 'react'
import { GlobalContext } from '../../context/Provider'
import React from 'react'
const { v4: uuidv4 } = require('uuid')
import RegularInput from './RegularInput'
import AdvancedInput from './AdvancedInput'
interface InputFieldProps {
formStyle?: object
comId?: string
fillerText?: string
parentId?: string
mode?: string
customImg?: string
inputStyle?: object
cancelBtnStyle?: object
submitBtnStyle?: object
imgStyle?: object
imgDiv?: object
placeHolder?: string
}
const InputField = ({
formStyle,
comId,
fillerText,
parentId,
mode,
customImg,
inputStyle,
cancelBtnStyle,
submitBtnStyle,
imgStyle,
imgDiv,
placeHolder
}: InputFieldProps) => {
const [text, setText] = useState('')
useEffect(() => {
if (fillerText) {
setText(fillerText)
}
}, [fillerText])
const globalStore: any = useContext(GlobalContext)
const editMode = async (advText?: string) => {
const textToSend = advText ? advText : text
return (
await globalStore.onEdit(textToSend, comId, parentId),
globalStore.onEditAction &&
(await globalStore.onEditAction({
userId: globalStore.currentUserData.currentUserId,
comId: comId,
avatarUrl: globalStore.currentUserData.currentUserImg,
userProfile: globalStore.currentUserData.currentUserProfile
? globalStore.currentUserData.currentUserProfile
: null,
fullName: globalStore.currentUserData.currentUserFullName,
text: textToSend,
parentOfEditedCommentId: parentId
}))
)
}
const replyMode = async (replyUuid: string, advText?: string) => {
const textToSend = advText ? advText : text
return (
await globalStore.onReply(textToSend, comId, parentId, replyUuid),
globalStore.onReplyAction &&
(await globalStore.onReplyAction({
userId: globalStore.currentUserData.currentUserId,
repliedToCommentId: comId,
avatarUrl: globalStore.currentUserData.currentUserImg,
userProfile: globalStore.currentUserData.currentUserProfile
? globalStore.currentUserData.currentUserProfile
: null,
fullName: globalStore.currentUserData.currentUserFullName,
text: textToSend,
parentOfRepliedCommentId: parentId,
comId: replyUuid
}))
)
}
const submitMode = async (createUuid: string, advText?: string) => {
const textToSend = advText ? advText : text
return (
await globalStore.onSubmit(textToSend, createUuid),
globalStore.onSubmitAction &&
(await globalStore.onSubmitAction({
userId: globalStore.currentUserData.currentUserId,
comId: createUuid,
avatarUrl: globalStore.currentUserData.currentUserImg,
userProfile: globalStore.currentUserData.currentUserProfile
? globalStore.currentUserData.currentUserProfile
: null,
fullName: globalStore.currentUserData.currentUserFullName,
text: textToSend,
replies: []
}))
)
}
const handleSubmit = async (event: any, advText?: string) => {
event.preventDefault()
const createUuid = uuidv4()
const replyUuid = uuidv4()
mode === 'editMode'
? editMode(advText)
: mode === 'replyMode'
? replyMode(replyUuid, advText)
: submitMode(createUuid, advText)
setText('')
}
return (
<div>
{globalStore.advancedInput ? (
<AdvancedInput
handleSubmit={handleSubmit}
text={mode === 'editMode' ? text : ''}
formStyle={formStyle}
mode={mode}
cancelBtnStyle={cancelBtnStyle}
submitBtnStyle={submitBtnStyle}
comId={comId}
imgDiv={imgDiv}
imgStyle={imgStyle}
customImg={customImg}
placeHolder={placeHolder}
/>
) : (
<RegularInput
formStyle={formStyle}
imgDiv={imgDiv}
imgStyle={imgStyle}
customImg={customImg}
mode={mode}
inputStyle={inputStyle}
cancelBtnStyle={cancelBtnStyle}
comId={comId}
submitBtnStyle={submitBtnStyle}
handleSubmit={handleSubmit}
text={text}
setText={setText}
placeHolder={placeHolder}
/>
)}
</div>
)
}
export default InputField
================================================
FILE: src/components/InputField/InputField.scss
================================================
.form {
display: flex;
background-color: rgb(243, 243, 243);
padding: 20px;
border-radius: 8px;
.userImg {
display: flex;
justify-content: center;
align-items: center;
margin: 0px 10px;
}
.postComment {
width: 100%;
border: none;
border-bottom: 1px solid rgb(24, 24, 24);
text-decoration: none;
background-color: transparent;
margin-left: 6px;
}
.postComment:focus {
outline: none;
border-bottom: 2px solid rgb(14, 14, 14);
}
.postComment::placeholder {
margin-top: -2px;
}
.postBtn {
border: 2px solid rgb(0, 195, 255);
border-radius: 8px;
background-color: rgb(0, 195, 255);
padding: 5px 10px;
color: white;
font-weight: bolder;
margin-left: 15px;
font-size: 16px;
cursor: pointer;
padding: 5px 20px;
&:hover {
border: 2px solid rgb(0, 184, 240);
background-color: rgb(0, 184, 240);
}
}
.cancelBtn {
border: 2px solid rgb(237, 237, 237);
border-radius: 8px;
background-color: rgb(237, 237, 237);
padding: 5px 10px;
color: rgb(174, 174, 174);
font-weight: bolder;
margin-left: 15px;
font-size: 16px;
cursor: pointer;
padding: 5px 20px;
&:hover {
border: 2px solid rgb(210, 210, 210);
background-color: rgb(210, 210, 210);
}
}
}
.imgdefault {
width: 38px;
height: 38px;
border-radius: 19px;
}
.hr-style {
width: 100%;
border-top: 1px solid;
}
.emoji-input {
display: flex;
width: 100%;
position: relative;
.emoji-icon {
background-image: url('../../assets/smile.svg');
position: relative;
width: 24px;
background-repeat: no-repeat;
top: 14px;
cursor: pointer;
}
}
.emoji-picker-react {
z-index: 1000;
position: absolute !important;
right: -63px;
top: 50px;
}
.rdw-editor-wrapper {
width: 100%;
}
.advanced-form {
padding: 0px;
flex-direction: column;
}
.rdw-editor-main {
max-height: 200px;
overflow: scroll;
}
.advanced-btns {
width: 100%;
display: flex;
margin: 6px 0px 0px 0px;
}
.advanced-border {
border: 1px solid #e8e8e8;
padding: 10px;
border-radius: 10px;
.advanced-border:focus-within {
border: 1px solid #353535;
}
}
.advanced-post {
margin-left: unset !important;
}
.advanced-cancel {
margin-right: 15px;
margin-left: unset !important;
}
.advanced-overlay {
display: flex;
margin: 10px 0px;
width: 100%;
}
.advanced-input {
margin-left: 6px;
width: 100%;
}
================================================
FILE: src/components/InputField/InputFieldStyles.tsx
================================================
export const inputFrame = {
backgroundColor: "gray",
};
================================================
FILE: src/components/InputField/RegularInput.tsx
================================================
import React from 'react'
import './InputField.scss'
import { useContext } from 'react'
import { GlobalContext } from '../../context/Provider'
import EmojiInput from './EmojiInput'
interface RegularInputProps {
formStyle?: object
comId?: string
mode?: string
customImg?: string
inputStyle?: object
cancelBtnStyle?: object
submitBtnStyle?: object
imgStyle?: object
imgDiv?: object
handleSubmit: Function
text: string
setText: Function
placeHolder?: string
}
const RegularInput = ({
formStyle,
imgDiv,
imgStyle,
customImg,
mode,
inputStyle,
cancelBtnStyle,
comId,
submitBtnStyle,
handleSubmit,
text,
setText,
placeHolder
}: RegularInputProps) => {
const globalStore: any = useContext(GlobalContext)
return (
<form
className='form'
style={globalStore.formStyle || formStyle}
onSubmit={() => handleSubmit}
>
<div className='userImg' style={imgDiv}>
<a
target='_blank'
href={globalStore.currentUserData.currentUserProfile}
>
<img
src={
globalStore.customImg ||
customImg ||
globalStore.currentUserData.currentUserImg
}
style={globalStore.imgStyle || imgStyle}
alt='userIcon'
className='imgdefault'
/>
</a>
</div>
{globalStore.removeEmoji ? (
<input
className='postComment'
style={
mode === 'replyMode' || mode === 'editMode'
? globalStore.replyInputStyle
: globalStore.inputStyle || inputStyle
}
type='text'
placeholder={placeHolder ? placeHolder : 'Type your reply here.'}
value={text}
onChange={(e) => setText(e.target.value)}
/>
) : (
<EmojiInput
text={text}
setText={setText}
mode={mode}
inputStyle={inputStyle}
placeHolder={placeHolder}
/>
)}
{mode && (
<button
className='cancelBtn'
style={globalStore.cancelBtnStyle || cancelBtnStyle}
type='button'
onClick={() =>
mode === 'editMode'
? globalStore.handleAction(comId, true)
: globalStore.handleAction(comId, false)
}
>
Cancel
</button>
)}
<button
className='postBtn'
type='submit'
disabled={text != '' ? false : true}
style={globalStore.submitBtnStyle || submitBtnStyle}
onClick={(e) => (text ? handleSubmit(e) : null)}
>
Post
</button>
</form>
)
}
export default RegularInput
================================================
FILE: src/components/LoginSection/LoginSection.scss
================================================
.signBox {
border: 1px solid rgb(221, 221, 221);
border-radius: 8px;
background-color: transparent;
padding: 15px;
display: flex;
justify-content: space-between;
.signLine {
margin-top: 5px;
font-weight: 700;
color: rgb(156, 156, 156);
font-size: 17px;
}
.loginBtn {
border: 2px solid rgb(0, 195, 255);
border-radius: 8px;
background-color: transparent;
padding: 5px 10px;
color: rgb(0, 195, 255);
font-weight: bolder;
margin-right: 10px;
font-size: 16px;
cursor: pointer;
&:hover {
border: 2px solid rgb(0, 183, 238);
color: rgb(0, 183, 238);
}
}
.signBtn {
border: 2px solid rgb(0, 195, 255);
border-radius: 8px;
background-color: rgb(0, 195, 255);
padding: 5px 10px;
color: rgb(255, 255, 255);
font-weight: bolder;
font-size: 16px;
cursor: pointer;
&:hover {
background-color: rgb(0, 183, 238);
border: 2px solid rgb(0, 183, 238);
}
}
}
================================================
FILE: src/components/LoginSection/LoginSection.tsx
================================================
import React from 'react'
import './LoginSection.scss'
interface LoginSectionProps {
loginLink?: string | (() => void)
signUpLink?: string | (() => void)
onLogin?: string | (() => void)
onSignUp?: string | (() => void)
}
const LoginSection = ({
loginLink,
signUpLink,
onLogin,
onSignUp
}: LoginSectionProps) => {
const handleLoginClick = () => {
const loginAction = onLogin || loginLink
if (typeof loginAction === 'function') {
loginAction()
} else if (loginAction) {
window.location.href = loginAction
}
}
const handleSignUpClick = () => {
const signUpAction = onSignUp || signUpLink
if (typeof signUpAction === 'function') {
signUpAction()
} else if (signUpAction) {
window.location.href = signUpAction
}
}
return (
<div className='signBox'>
<div className='signLine'>Log in or sign up to leave a comment</div>
<div>
<button className='loginBtn' name='login' onClick={handleLoginClick}>
Log In
</button>
<button className='signBtn' name='signup' onClick={handleSignUpClick}>
Sign Up
</button>
</div>
</div>
)
}
export default LoginSection
================================================
FILE: src/components/ModalStyles.tsx
================================================
export const modal = {
fontSize: "16px",
};
================================================
FILE: src/components/data.json
================================================
[
{
"userId": "02b",
"comId": "017",
"fullName": "Lily",
"userProfile": "https://www.linkedin.com/in/riya-negi-8879631a9/",
"text": "I have a doubt about the 4th point🤔",
"avatarUrl": "https://ui-avatars.com/api/name=Lily&background=random",
"replies": []
},
{
"userId": "02a",
"comId": "07",
"fullName": "Adam Scott",
"text": "Follow my page for more such interesting blogs!😇",
"avatarUrl": "https://ui-avatars.com/api/name=Adam&background=random",
"userProfile": "https://www.linkedin.com/in/riya-negi-8879631a9/",
"replies": []
},
{
"userId": "02a",
"comId": "015",
"fullName": "Robert Jae",
"avatarUrl": "https://ui-avatars.com/api/name=Robert&background=random",
"text": "Woah pretty helpful! how did you solve for x?",
"replies": [
{
"userId": "01b",
"comId": "016",
"userProfile": "https://www.linkedin.com/in/riya-negi-8879631a9/",
"fullName": "Adam Scott",
"text": "Thanks! refer to this link -> acs.com",
"avatarUrl": "https://ui-avatars.com/api/name=Adam&background=random"
}
]
},
{
"userId": "02c",
"comId": "018",
"fullName": "Robin",
"userProfile": "https://www.linkedin.com/in/riya-negi-8879631a9/",
"text": "Aaaah! got it got it. Pretty cool",
"avatarUrl": "https://ui-avatars.com/api/name=Robin&background=random",
"replies": []
}
]
================================================
FILE: src/context/Provider.tsx
================================================
import React, { createContext, useEffect, useState } from 'react'
// const { v4: uuidv4 } = require('uuid')
import _ from 'lodash'
export const GlobalContext = createContext({})
export const GlobalProvider = ({
children,
currentUser,
replyTop,
customImg,
inputStyle,
formStyle,
submitBtnStyle,
cancelBtnStyle,
imgStyle,
commentsCount,
commentData,
onSubmitAction,
onDeleteAction,
onReplyAction,
onEditAction,
currentData,
replyInputStyle,
removeEmoji,
advancedInput,
placeHolder
}: {
children: any
currentUser?: {
currentUserId: string
currentUserImg: string
currentUserProfile?: string | undefined
currentUserFullName: string
} | null
replyTop?: boolean
customImg?: string
inputStyle?: object
formStyle?: object
submitBtnStyle?: object
cancelBtnStyle?: object
imgStyle?: object
replyInputStyle?: object
commentsCount?: number
removeEmoji?: boolean
commentData?: Array<{
userId: string
comId: string
fullName: string
avatarUrl: string
text: string
timestamp?: string
userProfile?: string
replies?:
| Array<{
userId: string
comId: string
fullName: string
avatarUrl: string
text: string
timestamp?: string
userProfile?: string
}>
| undefined
}>
onSubmitAction?: Function
onDeleteAction?: Function
onReplyAction?: Function
onEditAction?: Function
currentData?: Function
advancedInput?: boolean
placeHolder?: string
}) => {
const [currentUserData] = useState(currentUser)
const [data, setData] = useState<
Array<{
userId: string
comId: string
fullName: string
avatarUrl: string
text: string
userProfile?: string
timestamp?: string
replies?:
| Array<{
userId: string
comId: string
fullName: string
avatarUrl: string
text: string
timestamp?: string
userProfile?: string
}>
| undefined
}>
>([])
const [editArr, setEdit] = useState<string[]>([])
const [replyArr, setReply] = useState<string[]>([])
useEffect(() => {
if (commentData) {
setData(commentData)
}
}, [commentData])
useEffect(() => {
if (currentData) {
currentData(data)
}
}, [data])
const handleAction = (id: string, edit: boolean) => {
if (edit) {
let editArrCopy: string[] = [...editArr]
let indexOfId = _.indexOf(editArrCopy, id)
if (_.includes(editArr, id)) {
editArrCopy.splice(indexOfId, 1)
setEdit(editArrCopy)
} else {
editArrCopy.push(id)
setEdit(editArrCopy)
}
} else {
let replyArrCopy: string[] = [...replyArr]
let indexOfId = _.indexOf(replyArrCopy, id)
if (_.includes(replyArr, id)) {
replyArrCopy.splice(indexOfId, 1)
setReply(replyArrCopy)
} else {
replyArrCopy.push(id)
setReply(replyArrCopy)
}
}
}
const onSubmit = (text: string, uuid: string) => {
let copyData = [...data]
copyData.push({
userId: currentUserData!.currentUserId,
comId: uuid,
avatarUrl: currentUserData!.currentUserImg,
userProfile: currentUserData!.currentUserProfile
? currentUserData!.currentUserProfile
: undefined,
fullName: currentUserData!.currentUserFullName,
text: text,
timestamp: `${new Date().toISOString()}`,
replies: []
})
setData(copyData)
}
const onEdit = (text: string, comId: string, parentId: string) => {
let copyData = [...data]
if (parentId) {
const indexOfParent = _.findIndex(copyData, { comId: parentId })
const indexOfId = _.findIndex(copyData[indexOfParent].replies, {
comId: comId
})
copyData[indexOfParent].replies![indexOfId].text = text
setData(copyData)
handleAction(comId, true)
} else {
const indexOfId = _.findIndex(copyData, { comId: comId })
copyData[indexOfId].text = text
setData(copyData)
handleAction(comId, true)
}
}
const onReply = (
text: string,
comId: string,
parentId: string,
uuid: string
) => {
let copyData = [...data]
if (parentId) {
const indexOfParent = _.findIndex(copyData, { comId: parentId })
copyData[indexOfParent].replies!.push({
userId: currentUserData!.currentUserId,
comId: uuid,
avatarUrl: currentUserData!.currentUserImg,
userProfile: currentUserData!.currentUserProfile
? currentUserData!.currentUserProfile
: undefined,
fullName: currentUserData!.currentUserFullName,
text: text,
timestamp: `${new Date().toISOString()}`
})
setData(copyData)
handleAction(comId, false)
} else {
const indexOfId = _.findIndex(copyData, {
comId: comId
})
copyData[indexOfId].replies!.push({
userId: currentUserData!.currentUserId,
comId: uuid,
avatarUrl: currentUserData!.currentUserImg,
userProfile: currentUserData!.currentUserProfile
? currentUserData!.currentUserProfile
: undefined,
fullName: currentUserData!.currentUserFullName,
text: text,
timestamp: `${new Date().toISOString()}`
})
setData(copyData)
handleAction(comId, false)
}
}
const onDelete = (comId: string, parentId: string) => {
let copyData = [...data]
if (parentId) {
const indexOfParent = _.findIndex(copyData, { comId: parentId })
const indexOfId = _.findIndex(copyData[indexOfParent].replies, {
comId: comId
})
copyData[indexOfParent].replies!.splice(indexOfId, 1)
setData(copyData)
} else {
const indexOfId = _.findIndex(copyData, { comId: comId })
copyData.splice(indexOfId, 1)
setData(copyData)
}
}
return (
<GlobalContext.Provider
value={{
currentUserData: currentUserData,
replyTop: replyTop,
data: data,
handleAction: handleAction,
editArr: editArr,
onSubmit: onSubmit,
onEdit: onEdit,
replyArr: replyArr,
onReply: onReply,
onDelete: onDelete,
customImg: customImg,
inputStyle: inputStyle,
formStyle: formStyle,
submitBtnStyle: submitBtnStyle,
cancelBtnStyle: cancelBtnStyle,
imgStyle: imgStyle,
commentsCount: commentsCount,
onSubmitAction: onSubmitAction,
onDeleteAction: onDeleteAction,
onReplyAction: onReplyAction,
onEditAction: onEditAction,
replyInputStyle: replyInputStyle,
removeEmoji: removeEmoji,
advancedInput: advancedInput,
placeHolder: placeHolder
}}
>
{children}
</GlobalContext.Provider>
)
}
export default GlobalProvider
================================================
FILE: src/index.test.tsx
================================================
import CommentSectionComponent from './components/CommentSectionComponent/Index'
describe('CommentSectionComponent', () => {
it('is truthy', () => {
expect(CommentSectionComponent).toBeTruthy()
})
})
================================================
FILE: src/index.tsx
================================================
import * as React from 'react'
import CommentSectionComponent from './components/CommentSectionComponent/Index'
import GlobalProvider from './context/Provider'
import './Index.scss'
interface CommentSectionProps {
currentUser: {
currentUserId: string
currentUserImg: string
currentUserProfile: string
currentUserFullName: string
} | null
logIn: {
loginLink?: string
signUpLink?: string
onLogin?: () => void
onSignUp?: () => void
}
replyTop?: boolean
customImg?: string
inputStyle?: object
formStyle?: object
submitBtnStyle?: object
cancelBtnStyle?: object
overlayStyle?: object
imgStyle?: object
replyInputStyle?: object
commentsCount?: number
hrStyle?: object
titleStyle?: object
onSubmitAction?: Function
onDeleteAction?: Function
onReplyAction?: Function
onEditAction?: Function
customNoComment?: Function
currentData?: Function
removeEmoji?: boolean
advancedInput?: boolean
placeHolder?: string
showTimestamp?: boolean
commentData: Array<{
userId: string
comId: string
fullName: string
avatarUrl: string
text: string
userProfile?: string
timestamp?: string
replies?:
| Array<{
userId: string
comId: string
fullName: string
avatarUrl: string
text: string
timestamp?: string
userProfile?: string
}>
| undefined
}>
}
export const CommentSection = ({
currentUser,
customImg,
inputStyle,
formStyle,
submitBtnStyle,
cancelBtnStyle,
overlayStyle,
replyInputStyle,
logIn,
imgStyle,
replyTop,
commentsCount,
commentData,
placeHolder,
showTimestamp,
hrStyle,
titleStyle,
removeEmoji,
onSubmitAction,
onDeleteAction,
onReplyAction,
onEditAction,
customNoComment,
currentData,
advancedInput
}: CommentSectionProps) => {
return (
<GlobalProvider
currentUser={currentUser}
replyTop={replyTop}
customImg={customImg}
inputStyle={inputStyle}
formStyle={formStyle}
submitBtnStyle={submitBtnStyle}
cancelBtnStyle={cancelBtnStyle}
replyInputStyle={replyInputStyle}
imgStyle={imgStyle}
commentsCount={commentsCount}
commentData={commentData}
onSubmitAction={onSubmitAction}
onDeleteAction={onDeleteAction}
onReplyAction={onReplyAction}
onEditAction={onEditAction}
currentData={currentData}
removeEmoji={removeEmoji}
advancedInput={advancedInput}
placeHolder={placeHolder}
>
<CommentSectionComponent
overlayStyle={overlayStyle}
hrStyle={hrStyle}
logIn={logIn}
titleStyle={titleStyle}
customNoComment={customNoComment}
showTimestamp={showTimestamp}
/>
</GlobalProvider>
)
}
================================================
FILE: src/react-app-env.d.ts
================================================
/// <reference types="react-scripts" />
================================================
FILE: src/typings.d.ts
================================================
/**
* Default CSS definition for typescript,
* will be overridden with file-specific definitions by rollup
*/
declare module '*.css' {
const content: { [className: string]: string }
export default content
}
interface SvgrComponent
extends React.StatelessComponent<React.SVGAttributes<SVGElement>> {}
declare module '*.svg' {
const svgUrl: string
const svgComponent: SvgrComponent
export default svgUrl
export { svgComponent as ReactComponent }
}
================================================
FILE: tsconfig.json
================================================
{
"compilerOptions": {
"outDir": "dist",
"module": "esnext",
"lib": [
"dom",
"esnext"
],
"moduleResolution": "node",
"jsx": "react",
"sourceMap": true,
"skipLibCheck": true,
"declaration": true,
"esModuleInterop": true,
"noImplicitReturns": true,
"noImplicitThis": true,
"noImplicitAny": true,
"strictNullChecks": true,
"suppressImplicitAnyIndexErrors": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"allowSyntheticDefaultImports": true,
"resolveJsonModule": true,
"target": "es5",
"allowJs": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"isolatedModules": true,
"noEmit": true
},
"include": [
"src",
"src/assets"
],
"exclude": [
"node_modules",
"dist",
"example"
]
}
================================================
FILE: tsconfig.test.json
================================================
{
"extends": "./tsconfig.json",
"compilerOptions": {
"module": "commonjs"
}
}
gitextract_e9r3kx80/ ├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .gitignore ├── .prettierrc ├── .travis.yml ├── README.md ├── example/ │ ├── README.md │ ├── package.json │ ├── public/ │ │ ├── index.html │ │ └── manifest.json │ ├── src/ │ │ ├── App.test.tsx │ │ ├── App.tsx │ │ ├── components/ │ │ │ ├── AdvancedComponent.tsx │ │ │ ├── ClassComponent.tsx │ │ │ ├── CustomComponent.tsx │ │ │ ├── DefaultComponent.tsx │ │ │ └── LogInComponent.tsx │ │ ├── index.css │ │ ├── index.tsx │ │ ├── react-app-env.d.ts │ │ └── setupTests.ts │ └── tsconfig.json ├── package.json ├── packageold.json ├── src/ │ ├── .eslintrc │ ├── Index.scss │ ├── components/ │ │ ├── CommentSectionComponent/ │ │ │ ├── CommentSection.css │ │ │ ├── Index.tsx │ │ │ └── NoComments.tsx │ │ ├── CommentStructure.tsx/ │ │ │ ├── CommentStructure.scss │ │ │ ├── DeleteModal.tsx │ │ │ ├── Index.tsx │ │ │ └── ModalStyles.tsx │ │ ├── InputField/ │ │ │ ├── AdvancedInput.tsx │ │ │ ├── EmojiInput.tsx │ │ │ ├── Index.tsx │ │ │ ├── InputField.scss │ │ │ ├── InputFieldStyles.tsx │ │ │ └── RegularInput.tsx │ │ ├── LoginSection/ │ │ │ ├── LoginSection.scss │ │ │ └── LoginSection.tsx │ │ ├── ModalStyles.tsx │ │ └── data.json │ ├── context/ │ │ └── Provider.tsx │ ├── index.test.tsx │ ├── index.tsx │ ├── react-app-env.d.ts │ └── typings.d.ts ├── tsconfig.json └── tsconfig.test.json
SYMBOL INDEX (13 symbols across 11 files)
FILE: example/src/components/ClassComponent.tsx
class ClassComponent (line 5) | class ClassComponent extends PureComponent {
method render (line 53) | render() {
FILE: src/components/CommentSectionComponent/Index.tsx
type CommentSectionProps (line 11) | interface CommentSectionProps {
FILE: src/components/CommentStructure.tsx/DeleteModal.tsx
type DeleteModalProps (line 7) | interface DeleteModalProps {
FILE: src/components/CommentStructure.tsx/Index.tsx
type CommentStructureProps (line 10) | interface CommentStructureProps {
FILE: src/components/InputField/AdvancedInput.tsx
type AdvancedInputProps (line 10) | interface AdvancedInputProps {
FILE: src/components/InputField/EmojiInput.tsx
function useOutsideAlerter (line 6) | function useOutsideAlerter(ref: any, setOpen: Function) {
type EmojiInputProps (line 20) | interface EmojiInputProps {
FILE: src/components/InputField/Index.tsx
type InputFieldProps (line 9) | interface InputFieldProps {
FILE: src/components/InputField/RegularInput.tsx
type RegularInputProps (line 7) | interface RegularInputProps {
FILE: src/components/LoginSection/LoginSection.tsx
type LoginSectionProps (line 4) | interface LoginSectionProps {
FILE: src/index.tsx
type CommentSectionProps (line 6) | interface CommentSectionProps {
FILE: src/typings.d.ts
type SvgrComponent (line 10) | interface SvgrComponent
Condensed preview — 51 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (91K chars).
[
{
"path": ".editorconfig",
"chars": 147,
"preview": "root = true\n\n[*]\ncharset = utf-8\nindent_style = space\nindent_size = 2\nend_of_line = lf\ninsert_final_newline = true\ntrim_"
},
{
"path": ".eslintignore",
"chars": 47,
"preview": "build/\ndist/\nnode_modules/\n.snapshots/\n*.min.js"
},
{
"path": ".eslintrc",
"chars": 603,
"preview": "{\n \"parser\": \"babel-eslint\",\n \"extends\": [\n \"standard\",\n \"standard-react\",\n \"plugin:prettier/recommended\",\n "
},
{
"path": ".gitignore",
"chars": 281,
"preview": "\n# See https://help.github.com/ignore-files/ for more about ignoring files.\n\n# dependencies\nnode_modules\n\n# builds\nbuild"
},
{
"path": ".prettierrc",
"chars": 197,
"preview": "{\n \"singleQuote\": true,\n \"jsxSingleQuote\": true,\n \"semi\": false,\n \"tabWidth\": 2,\n \"bracketSpacing\": true,\n \"jsxBra"
},
{
"path": ".travis.yml",
"chars": 41,
"preview": "language: node_js\nnode_js:\n - 12\n - 10\n"
},
{
"path": "README.md",
"chars": 5198,
"preview": "# react-comments-section\n\n## Install\n\nInstall the latest version!\n\n```bash\nnpm i react-comments-section\n```\n\n## Detailed"
},
{
"path": "example/README.md",
"chars": 253,
"preview": "This example was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).\n\nIt is linked to th"
},
{
"path": "example/package.json",
"chars": 1531,
"preview": "{\n \"name\": \"rc-example\",\n \"homepage\": \".\",\n \"version\": \"1.0.0\",\n \"private\": true,\n \"scripts\": {\n \"start\": \"node "
},
{
"path": "example/public/index.html",
"chars": 1624,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n <head>\n <meta charset=\"utf-8\" />\n <link rel=\"shortcut icon\" href=\"%PUBLIC_URL%/"
},
{
"path": "example/public/manifest.json",
"chars": 318,
"preview": "{\n \"short_name\": \"react-comments-section\",\n \"name\": \"react-comments-section\",\n \"icons\": [\n {\n \"src\": \"favicon"
},
{
"path": "example/src/App.test.tsx",
"chars": 241,
"preview": "import React from 'react'\nimport ReactDOM from 'react-dom'\nimport App from './App'\n\nit('renders without crashing', () =>"
},
{
"path": "example/src/App.tsx",
"chars": 831,
"preview": "import React from 'react'\nimport AdvancedComponent from './components/AdvancedComponent'\nimport ClassComponent from './c"
},
{
"path": "example/src/components/AdvancedComponent.tsx",
"chars": 4520,
"preview": "import React from 'react'\nimport { CommentSection } from 'react-comments-section'\nimport 'react-comments-section/dist/in"
},
{
"path": "example/src/components/ClassComponent.tsx",
"chars": 2869,
"preview": "import React, { PureComponent } from 'react'\nimport { CommentSection } from 'react-comments-section'\nimport 'react-comme"
},
{
"path": "example/src/components/CustomComponent.tsx",
"chars": 4172,
"preview": "import React from 'react'\nimport { CommentSection } from 'react-comments-section'\nimport 'react-comments-section/dist/in"
},
{
"path": "example/src/components/DefaultComponent.tsx",
"chars": 2919,
"preview": "import React from 'react'\nimport { CommentSection } from 'react-comments-section'\nimport 'react-comments-section/dist/in"
},
{
"path": "example/src/components/LogInComponent.tsx",
"chars": 2067,
"preview": "import React from 'react'\nimport { CommentSection } from 'react-comments-section'\nimport 'react-comments-section/dist/in"
},
{
"path": "example/src/index.css",
"chars": 853,
"preview": "body {\n margin: 0;\n padding: 0;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',\n "
},
{
"path": "example/src/index.tsx",
"chars": 164,
"preview": "import './index.css'\n\nimport React from 'react'\nimport ReactDOM from 'react-dom'\nimport App from './App'\n\nReactDOM.rende"
},
{
"path": "example/src/react-app-env.d.ts",
"chars": 40,
"preview": "/// <reference types=\"react-scripts\" />\n"
},
{
"path": "example/src/setupTests.ts",
"chars": 255,
"preview": "// jest-dom adds custom jest matchers for asserting on DOM nodes.\n// allows you to do things like:\n// expect(element).to"
},
{
"path": "example/tsconfig.json",
"chars": 774,
"preview": "{\n \"compilerOptions\": {\n \"outDir\": \"dist\",\n \"module\": \"esnext\",\n \"lib\": [\"dom\", \"esnext\"],\n \"moduleResoluti"
},
{
"path": "package.json",
"chars": 2725,
"preview": "{\n \"name\": \"react-comments-section\",\n \"version\": \"3.2.0\",\n \"description\": \"React component library for a functioning "
},
{
"path": "packageold.json",
"chars": 2153,
"preview": "{\n \"name\": \"react-comments-section\",\n \"version\": \"1.0.7\",\n \"description\": \"A react library for building comments sect"
},
{
"path": "src/.eslintrc",
"chars": 36,
"preview": "{\n \"env\": {\n \"jest\": true\n }\n}\n"
},
{
"path": "src/Index.scss",
"chars": 215,
"preview": "/* add css module styles here (optional) */\nbody {\n blockquote {\n border-left: 5px solid #f1f1f1;\n padding-left: "
},
{
"path": "src/components/CommentSectionComponent/CommentSection.css",
"chars": 563,
"preview": "@import url('https://fonts.googleapis.com/css2?family=Noto+Sans&display=swap');\n\n.overlay {\n display: flex;\n flex-dire"
},
{
"path": "src/components/CommentSectionComponent/Index.tsx",
"chars": 4209,
"preview": "import CommentStructure from '../CommentStructure.tsx/Index'\nimport InputField from '../InputField/Index'\nimport './Comm"
},
{
"path": "src/components/CommentSectionComponent/NoComments.tsx",
"chars": 206,
"preview": "import React from 'react'\n\nconst NoComments = () => {\n return (\n <div className='no-comDiv'>\n {' '}\n No co"
},
{
"path": "src/components/CommentStructure.tsx/CommentStructure.scss",
"chars": 2917,
"preview": ".userInfo {\n display: flex;\n flex-direction: column;\n\n .commentsTwo {\n display: flex;\n align-items: center;\n "
},
{
"path": "src/components/CommentStructure.tsx/DeleteModal.tsx",
"chars": 1419,
"preview": "import { useState, useContext } from 'react'\nimport 'react-responsive-modal/styles.css'\nimport { Modal } from 'react-res"
},
{
"path": "src/components/CommentStructure.tsx/Index.tsx",
"chars": 5542,
"preview": "import './CommentStructure.scss'\nimport { useContext } from 'react'\nimport { GlobalContext } from '../../context/Provide"
},
{
"path": "src/components/CommentStructure.tsx/ModalStyles.tsx",
"chars": 1019,
"preview": "export const modal = {\n fontSize: \"16px\",\n};\n\nexport const modalClose = {\n cursor: \"pointer\",\n position: \"absolute\",\n"
},
{
"path": "src/components/InputField/AdvancedInput.tsx",
"chars": 6470,
"preview": "import React, { useState, useEffect } from 'react'\nimport 'react-draft-wysiwyg/dist/react-draft-wysiwyg.css'\nimport { us"
},
{
"path": "src/components/InputField/EmojiInput.tsx",
"chars": 2001,
"preview": "import React, { useRef, useEffect, useState, useContext } from 'react'\nimport { GlobalContext } from '../../context/Prov"
},
{
"path": "src/components/InputField/Index.tsx",
"chars": 4486,
"preview": "import './InputField.scss'\nimport { useContext, useEffect, useState } from 'react'\nimport { GlobalContext } from '../../"
},
{
"path": "src/components/InputField/InputField.scss",
"chars": 2475,
"preview": ".form {\n display: flex;\n background-color: rgb(243, 243, 243);\n padding: 20px;\n border-radius: 8px;\n .userImg {\n "
},
{
"path": "src/components/InputField/InputFieldStyles.tsx",
"chars": 58,
"preview": "export const inputFrame = {\n backgroundColor: \"gray\",\n};\n"
},
{
"path": "src/components/InputField/RegularInput.tsx",
"chars": 2712,
"preview": "import React from 'react'\nimport './InputField.scss'\nimport { useContext } from 'react'\nimport { GlobalContext } from '."
},
{
"path": "src/components/LoginSection/LoginSection.scss",
"chars": 990,
"preview": ".signBox {\n border: 1px solid rgb(221, 221, 221);\n border-radius: 8px;\n background-color: transparent;\n padding: 15p"
},
{
"path": "src/components/LoginSection/LoginSection.tsx",
"chars": 1208,
"preview": "import React from 'react'\nimport './LoginSection.scss'\n\ninterface LoginSectionProps {\n loginLink?: string | (() => void"
},
{
"path": "src/components/ModalStyles.tsx",
"chars": 46,
"preview": "export const modal = {\n fontSize: \"16px\",\n};\n"
},
{
"path": "src/components/data.json",
"chars": 1438,
"preview": "[\n {\n \"userId\": \"02b\",\n \"comId\": \"017\",\n \"fullName\": \"Lily\",\n \"userProfile\": \"https://www.linkedin.com/in/r"
},
{
"path": "src/context/Provider.tsx",
"chars": 6935,
"preview": "import React, { createContext, useEffect, useState } from 'react'\n// const { v4: uuidv4 } = require('uuid')\nimport _ fro"
},
{
"path": "src/index.test.tsx",
"chars": 209,
"preview": "import CommentSectionComponent from './components/CommentSectionComponent/Index'\n\ndescribe('CommentSectionComponent', ()"
},
{
"path": "src/index.tsx",
"chars": 2807,
"preview": "import * as React from 'react'\nimport CommentSectionComponent from './components/CommentSectionComponent/Index'\nimport G"
},
{
"path": "src/react-app-env.d.ts",
"chars": 40,
"preview": "/// <reference types=\"react-scripts\" />\n"
},
{
"path": "src/typings.d.ts",
"chars": 465,
"preview": "/**\n * Default CSS definition for typescript,\n * will be overridden with file-specific definitions by rollup\n */\ndeclare"
},
{
"path": "tsconfig.json",
"chars": 843,
"preview": "{\n \"compilerOptions\": {\n \"outDir\": \"dist\",\n \"module\": \"esnext\",\n \"lib\": [\n \"dom\",\n \"esnext\"\n ],\n "
},
{
"path": "tsconfig.test.json",
"chars": 87,
"preview": "{\n \"extends\": \"./tsconfig.json\",\n \"compilerOptions\": {\n \"module\": \"commonjs\"\n }\n}"
}
]
About this extraction
This page contains the full source code of the RiyaNegi/react-comments-section GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 51 files (82.2 KB), approximately 23.6k tokens, and a symbol index with 13 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.