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 ![commentbox](https://github.com/RiyaNegi/react-comments-section/blob/main/example/blob/default.png?raw=true) ## Advanced Input (rich text editor) ![commentbox](https://github.com/RiyaNegi/react-comments-section/blob/main/example/blob/advanced.png?raw=true) ## 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 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 = () =>
No comments wohoooo!
render() { return ( 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 ================================================ react-comments-section
================================================ 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(, 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 (
Demo Examples

) } 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: `

Hey loved your blog! Can you show me some other ways to fix solve this?🤔
Here's my Linkedin Profile to reach out.

`, 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: `

Yeah sure try adding this line to your code. You need to pass event as a param.

event.preventDefault()

Best of luck with your project!

undefined

`, 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: '

OMG! it worked! DO NOT stop this blog series!!!! 💃

', timestamp: `${new Date()}` } ] }, { userId: '02b', comId: '017', fullName: 'Lily', userProfile: 'https://www.linkedin.com/in/riya-negi-8879631a9/', text: `
DRY - is the right of passage to good coding

True story brother!! Amen to that! For anyone wondering DRY is 

  1. Don't
  2. Repeat
  3. Yoursef
`, avatarUrl: 'https://ui-avatars.com/api/name=Lily&background=random', timestamp: `${new Date( date.getTime() - 3 * 60 * 60 * 1000 ).toISOString()}`, replies: [] } ]) return (
Advanced Input Component { 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' }} />
) } 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 (
Class Component this.onSubmitAction(data)} logIn={{ onLogin: () => alert('Call login function '), signUpLink: 'http://localhost:3001/' }} />
) } } 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 = () => (
No comments wohoooo!
) return (
Custom Component { 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' }} />
) } 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 (
Default Component 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) }} />
) } 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 (
Login Component alert('Call login function '), signUpLink: 'http://localhost:3001/' }} showTimestamp={false} />
) } 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(, document.getElementById('root')) ================================================ FILE: example/src/react-app-env.d.ts ================================================ /// ================================================ 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 } 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 (
{globalStore.commentsCount || totalComments()}{' '} {totalComments() === 1 ? 'Comment' : 'Comments'}
{globalStore.currentUserData === null ? ( loginMode() ) : ( )} {globalStore.data.length > 0 ? ( globalStore.data.map( (i: { userId: string comId: string fullName: string avatarUrl: string text: string userProfile?: string replies: Array | undefined }) => { return (
{i.replies && i.replies.length > 0 && i.replies.map((j) => { return (
) })}
) } ) ) : customNoComment ? ( customNoComment() ) : ( )}
) } export default CommentSection ================================================ FILE: src/components/CommentSectionComponent/NoComments.tsx ================================================ import React from 'react' const NoComments = () => { return (
{' '} No comments here. Be the first one to comment!
) } 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 (
delete

Are you sure?

Once you delete this comment it will be gone forever.

) } 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 | 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 (
{info.userId === currentUser.currentUserId && ( {' '}
} > globalStore.handleAction(info.comId, true)} > edit
)}
) } 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 ( ) } const replyTopSection = () => { return (
{info.text}
{userInfo()}
{currentUser && optionsMenu()}
) } const replyBottomSection = () => { return (
{userInfo()} {globalStore.advancedInput ? (
) : (
{info.text}
)}
{' '} {currentUser && (
)}
{currentUser && optionsMenu()}
) } const actionModeSection = (mode: string) => { if (mode === 'reply') { return (
{globalStore.replyTop ? replyTopSection() : replyBottomSection()}
) } else { return ( ) } } return (
{editMode ? actionModeSection('edit') : replyMode ? actionModeSection('reply') : globalStore.replyTop ? replyTopSection() : replyBottomSection()}
) } 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('

') const globalStore: any = useContext(GlobalContext) useEffect(() => { if (text != '') { setHtml(text) } }, [text]) useEffect(() => { if (html != '

') { 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('') const onEditorStateChange: Function = (editorState: any) => { setEditor(editorState) } useEffect(() => { setEditText( draftToHtml(convertToRaw(editorState.getCurrentContent())).trim() ) }, [editorState]) return (
editText != '

' ? (await handleSubmit(e, editText), setEditor(EditorState.createEmpty())) : null } >
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'] } }} />
{/*
*/}
{mode && ( )}
) } 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 (
setText(e.target.value)} />
setOpen(!open)}>
{open ? (
) : null}
) } 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 (
{globalStore.advancedInput ? ( ) : ( )}
) } 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 (
handleSubmit} > {globalStore.removeEmoji ? ( setText(e.target.value)} /> ) : ( )} {mode && ( )} ) } 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 (
Log in or sign up to leave a comment
) } 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([]) const [replyArr, setReply] = useState([]) 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 ( {children} ) } 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 ( ) } ================================================ FILE: src/react-app-env.d.ts ================================================ /// ================================================ 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> {} 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" } }