bro got linted

This commit is contained in:
Barunes Padhy 2024-06-13 20:46:15 +01:00
parent 2369d5c8e6
commit d41d3501c3
38 changed files with 7328 additions and 1398 deletions

View File

@ -0,0 +1,34 @@
{
"env": {
"browser": true,
"es2021": true
},
"extends": [
"eslint:recommended",
"plugin:react/recommended"
],
"parserOptions": {
"ecmaFeatures": {
"jsx": true
},
"ecmaVersion": 12,
"sourceType": "module"
},
"plugins": [
"react"
],
"rules": {
"indent": [
"error",
2
],
"react/react-in-jsx-scope": "off",
"react/prop-types": "off",
"react/display-name": "off",
"quotes": [
"error",
"single"
],
"no-console": "error"
}
}

File diff suppressed because it is too large Load Diff

View File

@ -43,9 +43,11 @@
"@types/react-dom": "^18.2.22",
"@vitejs/plugin-react": "^4.2.1",
"eslint": "^8.57.0",
"eslint-config-react-app": "^7.0.1",
"eslint-plugin-react": "^7.34.1",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.6",
"vite": "^5.2.0"
"vite": "^5.2.0",
"vite-plugin-eslint": "^1.8.1"
}
}

View File

@ -21,9 +21,9 @@ import EditableDataService from './services/editable-data-service'
function App() {
const [userData, setUserData] = useState(null);
const [themeConfig, setThemeConfig] = useState(null);
const [globalTheme, setGlobalTheme] = useState("lightTheme");
const [globalTheme, setGlobalTheme] = useState('lightTheme');
const [isOpen, setIsOpen] = useState(false);
const [notificationMessage, setNotificationMessage] = useState("")
const [notificationMessage, setNotificationMessage] = useState('')
const notificationToggler = (message, color) => {
setIsOpen(true)
@ -47,23 +47,22 @@ function App() {
EditableDataService.getData('/data/shared/user-data/').then( response => {
let responseData = response.data[0]
setUserData({
"name": responseData["name"],
"introContent": responseData["intro_content"],
"profilePhoto": responseData["profile_photo"],
"builtWith": responseData["built_with"]
'name': responseData['name'],
'introContent': responseData['intro_content'],
'profilePhoto': responseData['profile_photo'],
'builtWith': responseData['built_with']
})
document.title = responseData.name
}
)
})
EditableDataService.getData('/data/shared/theme-config/').then( response =>{
let responseData = response.data[0]
setThemeConfig({
"defaultTheme": responseData["default_theme"],
"darkTheme": JSON.parse(responseData["dark_theme"]),
"lightTheme": JSON.parse(responseData["light_theme"])
'defaultTheme': responseData['default_theme'],
'darkTheme': JSON.parse(responseData['dark_theme']),
'lightTheme': JSON.parse(responseData['light_theme'])
})
})
}
)
}
useEffect(() => {
@ -76,19 +75,19 @@ function App() {
if (themeConfig && userData && globalTheme)
return (
<div className="app-container">
<div className='app-container'>
<Router>
<Header className="header" ThemeSwitcher={themeSwitcher} GlobalTheme={globalTheme} ThemeConfig={themeConfig} UserData={userData} notificationToggler={notificationToggler} setInfo={setInfo} />
<Header className='header' ThemeSwitcher={themeSwitcher} GlobalTheme={globalTheme} ThemeConfig={themeConfig} UserData={userData} notificationToggler={notificationToggler} setInfo={setInfo} />
<div className={`p-0 ${themeConfig[globalTheme].background}`}>
<Routes>
<Route path="/" element={<Home notificationToggler={notificationToggler} GlobalTheme={globalTheme} ThemeConfig={themeConfig} UserData={userData} setInfo={setInfo} />} />
<Route path="/categories" element={<CategoryList notificationToggler={notificationToggler} GlobalTheme={globalTheme} ThemeConfig={themeConfig} />} />
<Route path="/categories/:categoryID" element={<BlogList notificationToggler={notificationToggler} GlobalTheme={globalTheme} ThemeConfig={themeConfig} />} />
<Route path="/blog/:blogID" element={<Blog notificationToggler={notificationToggler} GlobalTheme={globalTheme} ThemeConfig={themeConfig} />} />
<Route path='/' element={<Home notificationToggler={notificationToggler} GlobalTheme={globalTheme} ThemeConfig={themeConfig} UserData={userData} setInfo={setInfo} />} />
<Route path='/categories' element={<CategoryList notificationToggler={notificationToggler} GlobalTheme={globalTheme} ThemeConfig={themeConfig} />} />
<Route path='/categories/:categoryID' element={<BlogList notificationToggler={notificationToggler} GlobalTheme={globalTheme} ThemeConfig={themeConfig} />} />
<Route path='/blog/:blogID' element={<Blog notificationToggler={notificationToggler} GlobalTheme={globalTheme} ThemeConfig={themeConfig} />} />
</Routes>
</div>
<Footer notificationToggler={notificationToggler} setInfo={setInfo} className="footer" ThemeSwitcher={themeSwitcher} GlobalTheme={globalTheme} ThemeConfig={themeConfig} UserData={userData} />
<Notification style={{width: '20%'}} className="fixed-top" isOpen={isOpen} message={notificationMessage} />
<Footer notificationToggler={notificationToggler} setInfo={setInfo} className='footer' ThemeSwitcher={themeSwitcher} GlobalTheme={globalTheme} ThemeConfig={themeConfig} UserData={userData} />
<Notification style={{width: '20%'}} className='fixed-top' isOpen={isOpen} message={notificationMessage} />
</Router>
</div>
);

View File

@ -33,29 +33,28 @@ function BlogList(props) {
let responseData = response.data
let blogMetadata = []
let localCategoryData = {
"id": responseData["category_id"],
"name": responseData["name"],
"coverImage": responseData["cover_image"],
"tagLine": responseData["tagline"],
"description": responseData["description"],
"featuredBlog": responseData["featured_id"],
"blogMetadata": responseData["blog_metadata"]
'id': responseData['category_id'],
'name': responseData['name'],
'coverImage': responseData['cover_image'],
'tagLine': responseData['tagline'],
'description': responseData['description'],
'featuredBlog': responseData['featured_id'],
'blogMetadata': responseData['blog_metadata']
}
for (let eachBlog of responseData["blog_metadata"]){
for (let eachBlog of responseData['blog_metadata']){
blogMetadata.push({
"id": eachBlog["blog_id"],
"name": eachBlog["name"],
"description": eachBlog["description"],
"tagLine": eachBlog["tagline"],
"coverImage": eachBlog["cover_image"],
"parentCategory": eachBlog["parent_category"]
'id': eachBlog['blog_id'],
'name': eachBlog['name'],
'description': eachBlog['description'],
'tagLine': eachBlog['tagline'],
'coverImage': eachBlog['cover_image'],
'parentCategory': eachBlog['parent_category']
})
}
localCategoryData.blogMetadata = blogMetadata
setCategoryData(localCategoryData)
setFeaturedBlogData(blogMetadata.find(blog => blog.id === localCategoryData.featuredBlog))
}
);
});
}
useEffect(() => {
@ -63,23 +62,23 @@ function BlogList(props) {
}, [categoryID]);
const addNewBlog = () => {
EditableDataService.createData(`/data/blog/create/`, {
"name": "Enter a blog name",
"description": "Enter a description",
"tagline": "Enter a tagline",
"cover_image": "",
"content_body": "<p></p>",
"parent_category": categoryID
}).then(response => {
props.notificationToggler("New blog created")
EditableDataService.createData('/data/blog/create/', {
'name': 'Enter a blog name',
'description': 'Enter a description',
'tagline': 'Enter a tagline',
'cover_image': '',
'content_body': '<p></p>',
'parent_category': categoryID
}).then(() => {
props.notificationToggler('New blog created')
loadBlogs()
}).catch(error => {
}).catch(() => {
props.notificationToggler('Failed to add a new blog', 'danger');
});
}
const updateFeaturedBlog = (featuredId) => {
EditableDataService.updateData(`/data/category/update/${categoryID}/`, {"featured_id": featuredId}).then(response=>{
EditableDataService.updateData(`/data/category/update/${categoryID}/`, {'featured_id': featuredId}).then(() =>{
if (featuredId)
props.notificationToggler('Blog set as featured')
else props.notificationToggler('Featured blog removed')
@ -90,28 +89,28 @@ function BlogList(props) {
if (GlobalTheme && ThemeConfig) {
return (
<Container fluid className={`mb-2 p-0 ${ThemeConfig[GlobalTheme].background}`}>
<Col className="d-md-block"><Button color={ThemeConfig[GlobalTheme].buttonColor} onClick={() => navigate(`/categories/`)} className="ms-5 mt-5" outline><FontAwesomeIcon icon={faLeftLong}/></Button></Col>
<Col className='d-md-block'><Button color={ThemeConfig[GlobalTheme].buttonColor} onClick={() => navigate('/categories/')} className='ms-5 mt-5' outline><FontAwesomeIcon icon={faLeftLong}/></Button></Col>
<CategoryBar currentPage={categoryID} GlobalTheme={GlobalTheme} ThemeConfig={ThemeConfig} />
<Row className="justify-content-center align-items-center">
<Col className="d-flex flex-column align-items-center">
<div className="w-100">
<Card className={`my-2 ${ThemeConfig[GlobalTheme].background}`} style={{ width: "100%", border: "none" }}>
<Row className='justify-content-center align-items-center'>
<Col className='d-flex flex-column align-items-center'>
<div className='w-100'>
<Card className={`my-2 ${ThemeConfig[GlobalTheme].background}`} style={{ width: '100%', border: 'none' }}>
<CardBody>
<CardTitle style={{ display: "grid" }} className={`${ThemeConfig[GlobalTheme].textColor} justify-content-center`} tag="h1">
<CardTitle style={{ display: 'grid' }} className={`${ThemeConfig[GlobalTheme].textColor} justify-content-center`} tag='h1'>
{`Blogs in ${categoryData.name}`}
<Button className='mt-2' color={ThemeConfig[GlobalTheme].buttonColor} outline onClick={() => addNewBlog()}>Add New</Button>
</CardTitle>
</CardBody>
</Card>
</div>
<div className="container">
<div className='container'>
{
featuredBlogData ?
<CardListViewer
key={featuredBlogData.id}
totalItems={featuredBlogData === 'nodata' ? 0 : 1}
cardType={"longCard"}
resourceType={"blog"}
cardType={'longCard'}
resourceType={'blog'}
textColor={ThemeConfig[GlobalTheme].textColor}
bgColor={ThemeConfig[GlobalTheme].background}
borderColor={ThemeConfig[GlobalTheme].borderColor}
@ -120,15 +119,15 @@ return (
}
<Row>
{ categoryData === 'loading' ? <Spinner /> :
categoryData.blogMetadata.map((item, index) => (
categoryData.blogMetadata.map((item) => (
<Col key={item.blog_id}>
<div className={`p-2 ml-2 ${ThemeConfig[GlobalTheme].textColor}`}>
<CardListViewer
totalItems={categoryData.blogMetadata.length}
featuredBlog={categoryData.featuredBlog}
updateFeaturedBlog={updateFeaturedBlog}
cardType={"smallCard"}
resourceType={"blog"}
cardType={'smallCard'}
resourceType={'blog'}
textColor={ThemeConfig[GlobalTheme].textColor}
bgColor={ThemeConfig[GlobalTheme].background}
borderColor={ThemeConfig[GlobalTheme].borderColor}
@ -147,7 +146,5 @@ return (
} else {
return null;
}
}
export default BlogList

View File

@ -11,7 +11,7 @@ import { faLeftLong } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import {
Container,Row, Col,Spinner, UncontrolledCollapse, Button, ButtonGroup, Card, CardBody, Input, InputGroup, InputGroupText
Container,Row, Col,Spinner, Button, ButtonGroup, Input, InputGroup, InputGroupText
} from 'reactstrap';
import { useNavigate, useParams } from 'react-router-dom';
@ -39,17 +39,17 @@ function Blog(props) {
const toggle = () => setModal(!modal);
const toggleFileModal = () => setFileModal(!fileModal);
const setInfo = (event) => {
const setInfo = () => {
EditableDataService.updateData(`/data/blog/update/${blogID}/`,{
"name": nameField.current.value,
"description": descriptionField.current.value,
"tagline": tagLineField.current.value,
"content_body": blogContent,
"cover_image": coverImage ? coverImage !== '-' ? coverImage : '' : blogData.coverImage
}).then(response => {
'name': nameField.current.value,
'description': descriptionField.current.value,
'tagline': tagLineField.current.value,
'content_body': blogContent,
'cover_image': coverImage ? coverImage !== '-' ? coverImage : '' : blogData.coverImage
}).then(() => {
props.notificationToggler('Blog data saved!');
getInfo()
}).catch(error => {
}).catch(() => {
props.notificationToggler('Failed to update blog!', 'danger');
});
}
@ -61,10 +61,10 @@ function Blog(props) {
}
const deleteResource = () => {
EditableDataService.deleteData(`/data/blog/delete/${blogData.id}/`).then(response => {
EditableDataService.deleteData(`/data/blog/delete/${blogData.id}/`).then(() => {
props.notificationToggler('Blog successfully deleted')
navigate(`/categories/${blogData.parentCategory}`);
}).catch(error => {
}).catch(() => {
props.notificationToggler('Failed to delete blog', 'danger');
});
toggle()
@ -74,16 +74,15 @@ function Blog(props) {
EditableDataService.getData(`/data/blog/${blogID}/`).then(response => {
let responseData = response.data
setBlogData({
"id": responseData["blog_id"],
"name": responseData["name"],
"description": responseData["description"],
"tagLine": responseData["tagline"],
"coverImage": responseData["cover_image"],
"parentCategory": responseData["parent_category"]
'id': responseData['blog_id'],
'name': responseData['name'],
'description': responseData['description'],
'tagLine': responseData['tagline'],
'coverImage': responseData['cover_image'],
'parentCategory': responseData['parent_category']
})
setBlogContent(responseData["content_body"])
}
);
setBlogContent(responseData['content_body'])
});
}
useEffect(() => {
@ -102,25 +101,25 @@ function Blog(props) {
<Container fluid className={`${ThemeConfig[GlobalTheme].background}`}>
<ModalComponent modalText={modalText} modalTitle={modalTitle} modal={modal} toggle={toggle} confirmAction={deleteResource}/>
<MediaUpload setMedia={setCoverImage} notificationToggler={props.notificationToggler} modal={fileModal} toggle={toggleFileModal} resourceType='blog' resourceId={blogData.id}></MediaUpload>
<Col xs="3" className="d-md-block"><Button color={ThemeConfig[GlobalTheme].buttonColor} onClick={() => navigate(`/categories/${blogData.parentCategory}`)} className="ms-5 mt-5" outline><FontAwesomeIcon icon={faLeftLong}/></Button></Col>
<Col xs='3' className='d-md-block'><Button color={ThemeConfig[GlobalTheme].buttonColor} onClick={() => navigate(`/categories/${blogData.parentCategory}`)} className='ms-5 mt-5' outline><FontAwesomeIcon icon={faLeftLong}/></Button></Col>
<CategoryBar currentPage={blogData.parentCategory} GlobalTheme={GlobalTheme} ThemeConfig={ThemeConfig}/>
<Row className="mb-4">
<Col className="p-0">
<Row className='mb-4'>
<Col className='p-0'>
{
blogData.coverImage !== "" ?
blogData.coverImage !== '' ?
<img
src={EditableMediaService.getMedia(blogData.coverImage)}
alt="Banner"
alt='Banner'
style={{ width: '100%', height: 'auto', maxHeight: '20vh', objectFit: 'cover' }}
/>:""
/>:''
}
</Col>
</Row>
<Row className="mr-2 ml-2 mb-2 mt-1 blogContent">
<Col xs="3" className="d-none d-md-block"></Col>
<Row className='mr-2 ml-2 mb-2 mt-1 blogContent'>
<Col xs='3' className='d-none d-md-block'></Col>
<Col xs={`${window.screen.width >= 765 ? '6':''}`}>
<Button color='danger' onClick={() => showModal()} className="mb-5">Delete Blog</Button>
<ButtonGroup className="mb-5 ms-5">
<Button color='danger' onClick={() => showModal()} className='mb-5'>Delete Blog</Button>
<ButtonGroup className='mb-5 ms-5'>
<Button
outline
color={ThemeConfig[GlobalTheme].buttonColor}
@ -136,39 +135,33 @@ function Blog(props) {
Remove Cover Image
</Button>
</ButtonGroup>
<InputGroup className="mb-3">
<InputGroupText>
Name
</InputGroupText>
<InputGroup className='mb-3'>
<InputGroupText>Name</InputGroupText>
<Input innerRef={nameField} defaultValue={blogData.name} />
</InputGroup>
<InputGroup className="mb-3">
<InputGroupText>
Description
</InputGroupText>
<InputGroup className='mb-3'>
<InputGroupText>Description</InputGroupText>
<Input innerRef={descriptionField} defaultValue={blogData.description} />
</InputGroup>
<InputGroup>
<InputGroupText>
Tagline
</InputGroupText>
<InputGroupText>Tagline</InputGroupText>
<Input innerRef={tagLineField} defaultValue={blogData.tagLine} />
</InputGroup>
</Col>
<Col xs="3" className="d-none d-md-block"></Col>
<Col xs='3' className='d-none d-md-block'></Col>
</Row>
<Row className={`my-2 ${ThemeConfig[GlobalTheme].background}`}>
<Col>
<hr style={{"borderColor": `${ThemeConfig[GlobalTheme].borderColor}`}} />
<hr style={{'borderColor': `${ThemeConfig[GlobalTheme].borderColor}`}} />
</Col>
</Row>
<Row className="mr-2 ml-2 mt-1">
<Col xs="3" className="d-none d-md-block"></Col>
<Row className='mr-2 ml-2 mt-1'>
<Col xs='3' className='d-none d-md-block'></Col>
<Col className={`blogContent ${ThemeConfig[GlobalTheme].textColor}`} style={{marginBottom: '25px'}}>
<EditorComponent notificationToggler={props.notificationToggler} setContent={setBlogContent} GlobalTheme={GlobalTheme} ThemeConfig={ThemeConfig} content={blogContent} resourceType='blog' resourceId={blogData.id}/>
<Button className='mt-3 mb-2' onClick={(event) => setInfo(event)} color={ThemeConfig[GlobalTheme].buttonColor} outline>Save Data</Button>
</Col>
<Col xs="3" className="d-none d-md-block"></Col>
<Col xs='3' className='d-none d-md-block'></Col>
</Row>
</Container>
);

View File

@ -12,12 +12,9 @@ import {
Col,
Container,
Card,
CardImg,
CardTitle,
CardText,
CardBody,
Button,
ButtonGroup
} from 'reactstrap';
import { useNavigate } from 'react-router-dom';
import { faLeftLong } from '@fortawesome/free-solid-svg-icons';
@ -42,61 +39,58 @@ function Blogs(props) {
let localCategoryMetadata = []
for (let eachResponse of responseData){
localCategoryMetadata.push({
"id": eachResponse["category_id"],
"name": eachResponse["name"],
"featuredBlog": eachResponse["featured_id"],
"description": eachResponse["description"],
"tagLine": eachResponse["tagline"],
"coverImage": eachResponse["cover_image"]
'id': eachResponse['category_id'],
'name': eachResponse['name'],
'featuredBlog': eachResponse['featured_id'],
'description': eachResponse['description'],
'tagLine': eachResponse['tagline'],
'coverImage': eachResponse['cover_image']
})
}
setCategoryMetadata(localCategoryMetadata)
}
);
});
}
const addToIdsToUpdate = (resourceObject) => {
let localIdsToUpdate = {...idsToUpdate}
localIdsToUpdate[resourceObject.id]={
"name": resourceObject.name,
"featured_blog": resourceObject.featuredBlog,
"description": resourceObject.description,
"tagline": resourceObject.tagLine,
"cover_image": resourceObject.coverImage
'name': resourceObject.name,
'featured_blog': resourceObject.featuredBlog,
'description': resourceObject.description,
'tagline': resourceObject.tagLine,
'cover_image': resourceObject.coverImage
}
setIdsToUpdate(localIdsToUpdate)
}
const deleteResource = (id) => {
EditableDataService.deleteData(`/data/category/delete/${id}/`).then(response => {
EditableDataService.deleteData(`/data/category/delete/${id}/`).then(() => {
props.notificationToggler('Category successfully deleted')
setCategoryData()
}).catch(error => {
}).catch(() => {
props.notificationToggler('Failed to delete category', 'danger');
});
}
const addNewCategory = () => {
EditableDataService.createData('/data/category/create/', {
"name": "Enter name",
"featured_id": "",
"description": "Enter description",
"tagline": "Enter tagline",
"cover_image": ""
}).then(response => {
'name': 'Enter name',
'featured_id': '',
'description': 'Enter description',
'tagline': 'Enter tagline',
'cover_image': ''
}).then(() => {
props.notificationToggler('Category created successfully')
setCategoryData()
}
).catch(error => {
setCategoryData()}).catch(() => {
props.notificationToggler('Failed to add category', 'danger');
});
}
const updateInfo = () => {
for (let id of Object.keys(idsToUpdate)) {
EditableDataService.updateData(`/data/category/update/${id}/`, idsToUpdate[id]).then(response=>{
EditableDataService.updateData(`/data/category/update/${id}/`, idsToUpdate[id]).then( () =>{
props.notificationToggler('Category data updated successfully')
}).catch(error => {
}).catch( () => {
props.notificationToggler('Failed to update category data', 'danger');
});
}
@ -106,32 +100,30 @@ function Blogs(props) {
if (GlobalTheme && ThemeConfig) {
return (
<Container fluid className={`p-0 mb-2 ${ThemeConfig[GlobalTheme].background}`}>
<Col xs="3" className="d-md-block"><Button color={ThemeConfig[GlobalTheme].buttonColor} onClick={() => navigate(`/`)} className="ms-5 mt-5" outline><FontAwesomeIcon icon={faLeftLong}/></Button></Col>
<Row className="justify-content-center align-items-center">
<Col className="d-flex flex-column align-items-center">
<div className="w-100">
<Card className={`my-2 ${ThemeConfig[GlobalTheme].background}`} style={{width: "100%", border: "none"}}>
<Col xs='3' className='d-md-block'><Button color={ThemeConfig[GlobalTheme].buttonColor} onClick={() => navigate('/')} className='ms-5 mt-5' outline><FontAwesomeIcon icon={faLeftLong}/></Button></Col>
<Row className='justify-content-center align-items-center'>
<Col className='d-flex flex-column align-items-center'>
<div className='w-100'>
<Card className={`my-2 ${ThemeConfig[GlobalTheme].background}`} style={{width: '100%', border: 'none'}}>
<CardBody>
<CardTitle style={{ display: "grid" }} className={`${ThemeConfig[GlobalTheme].textColor} justify-content-center`} tag="h1">
{"Categories"}
<CardTitle style={{ display: 'grid' }} className={`${ThemeConfig[GlobalTheme].textColor} justify-content-center`} tag='h1'>
{'Categories'}
<Button className='mt-2' color={ThemeConfig[GlobalTheme].buttonColor} outline onClick={() => addNewCategory()}>Add New</Button>
</CardTitle>
</CardBody>
</Card>
</div>
<div className="" style={{ width: '70%', margin: 'auto' }}>
<div className='' style={{ width: '70%', margin: 'auto' }}>
{categoryMetadata.length > 0 ?
categoryMetadata.map((item, index) => (
categoryMetadata.map((item) => (
<CardListViewer
key={item.id}
id = {item.id}
totalItems={categoryMetadata.length}
addToIdsToUpdate={addToIdsToUpdate}
cardType={"longCard"}
cardType={'longCard'}
deleteResource={deleteResource}
resourceType={"categories"}
resourceType={'categories'}
textColor={ThemeConfig[GlobalTheme].textColor}
bgColor={ThemeConfig[GlobalTheme].background}
borderColor={ThemeConfig[GlobalTheme].borderColor}
@ -143,7 +135,6 @@ function Blogs(props) {
<Button className='mt-3 mb-2' onClick={() => updateInfo()} color={ThemeConfig[GlobalTheme].buttonColor} outline>Save Data</Button>
</div>
</Col>
</Row>
</Container>
);

View File

@ -5,7 +5,7 @@ import MediaUpload from './shared/media-upload'
import EditableMediaService from '../services/editable-media-service'
function HomePage(props) {
const [introContent, setIntroContent] = useState("")
const [introContent, setIntroContent] = useState('')
const [profilePhoto, setProfilePhoto] = useState(false)
const [nameFieldInvalid, setNameFieldInvalid] = useState(false)
const [modal, setModal] = useState(false)
@ -16,21 +16,19 @@ function HomePage(props) {
const setInfo = async () => {
let response = await props.setInfo('/data/shared/update/user-data/', {
"name": nameField.current.value,
"intro_content": introContent,
"profile_photo": profilePhoto ? profilePhoto !== '-' ? profilePhoto : '' : UserData.profilePhoto
'name': nameField.current.value,
'intro_content': introContent,
'profile_photo': profilePhoto ? profilePhoto !== '-' ? profilePhoto : '' : UserData.profilePhoto
})
console.log(response)
if (response === 200)
props.notificationToggler("Data saved successfully!")
props.notificationToggler('Data saved successfully!')
if ([500, 404, 403, 400].includes(response))
props.notificationToggler("Something failed!", "danger")
props.notificationToggler('Something failed!', 'danger')
}
const toggle = () => {setModal(!modal)}
const showError = (elementValue, fieldType) => {
console.log(elementValue)
if (fieldType === 'nameField'){
if (elementValue === '')
setNameFieldInvalid(true)
@ -54,15 +52,15 @@ function HomePage(props) {
return (
<Container fluid className={`${ThemeConfig[GlobalTheme].textColor} ${ThemeConfig[GlobalTheme].background}`}>
<MediaUpload setMedia={setProfilePhoto} notificationToggler={props.notificationToggler} modal={modal} toggle={toggle} resourceType='homepage' resourceId='homepage' />
<Row className="mb-4">
<Col xs="3" className="d-none d-md-block"></Col>
<Col className="p-0">
<Row className='mb-4'>
<Col xs='3' className='d-none d-md-block'></Col>
<Col className='p-0'>
<Row className='d-md-block'>
<Col xs="4" className="d-none d-md-block"></Col>
{UserData.profilePhoto !== "" && (
<center><img style={{ width: '180px', height: '180px', objectFit: 'cover' }} className="mt-5 mb-2 rounded-circle" src={EditableMediaService.getMedia(UserData.profilePhoto)} alt="Profile Photo" /></center>
<Col xs='4' className='d-none d-md-block'></Col>
{UserData.profilePhoto !== '' && (
<center><img style={{ width: '180px', height: '180px', objectFit: 'cover' }} className='mt-5 mb-2 rounded-circle' src={EditableMediaService.getMedia(UserData.profilePhoto)} alt='Profile Photo' /></center>
)}
<Col xs="4" className="d-none d-md-block"></Col>
<Col xs='4' className='d-none d-md-block'></Col>
</Row>
<Row>
<ButtonGroup className='mt-4'>
@ -83,25 +81,23 @@ function HomePage(props) {
</ButtonGroup>
</Row>
</Col>
<Col xs="3" className="d-none d-md-block"></Col>
<Col xs='3' className='d-none d-md-block'></Col>
</Row>
<Row className="mr-2 ml-2 mb-2 mt-1 blogContent">
<Col xs="3" className="d-none d-md-block"></Col>
<Col md="6" xs="12">
<Row className='mr-2 ml-2 mb-2 mt-1 blogContent'>
<Col xs='3' className='d-none d-md-block'></Col>
<Col md='6' xs='12'>
<InputGroup className='mb-5'>
<InputGroupText>Name</InputGroupText>
<Input invalid={nameFieldInvalid} innerRef={nameField} defaultValue={UserData.name} onChange={() => showError(nameField.current.value, 'nameField')}/>
{nameFieldInvalid && <FormFeedback tooltip className="mt-1">
{nameFieldInvalid && <FormFeedback tooltip className='mt-1'>
This field cannot be empty
</FormFeedback>}
</InputGroup>
<EditorComponent notificationToggler={props.notificationToggler} GlobalTheme={GlobalTheme} ThemeConfig={ThemeConfig} content={UserData.introContent} setContent={setIntroContent} resourceType='homepage' resourceId='homepage' />
<Button className='mt-3 mb-2' onClick={() => setInfo()} color={ThemeConfig[GlobalTheme].buttonColor} outline>Save Data</Button>
</Col>
<Col xs="3" className="d-none d-md-block"></Col>
<Col xs='3' className='d-none d-md-block'></Col>
</Row>
</Container>
);
}

View File

@ -46,11 +46,11 @@ function CardListViewer(props) {
setTaglineFieldInvalid(false)
}
props.addToIdsToUpdate({
"id": props.id,
"name": nameField.current.value,
"featuredBlog": "",
"description": descriptionField.current.value,
"tagLine": taglineField.current.value,
'id': props.id,
'name': nameField.current.value,
'featuredBlog': '',
'description': descriptionField.current.value,
'tagLine': taglineField.current.value,
})
}
@ -72,15 +72,15 @@ function CardListViewer(props) {
return (
<>
<ModalComponent modalText={modalText} modalTitle={modalTitle} modal={modal} toggle={toggle} confirmAction={deleteResource}/>
<Card color={props.borderColor} outline className={`my-2 ${props.bgColor}`} style={{"width": props.cardType === "smallCard" ? "18rem": "100%"}}>
<Card color={props.borderColor} outline className={`my-2 ${props.bgColor}`} style={{'width': props.cardType === 'smallCard' ? '18rem': '100%'}}>
<CardBody>
<CardTitle className={`mb-3 ${props.textColor}`} tag="h5">
<CardTitle className={`mb-3 ${props.textColor}`} tag='h5'>
<InputGroup>
<InputGroupText>
Name
</InputGroupText>
<Input defaultValue={itemObject.name} invalid={nameFieldInvalid} innerRef={nameField} onChange={() => handleInputUpdate(nameField.current.value, 'nameField')}/>
{nameFieldInvalid ? <FormFeedback tooltip className="mt-1">
{nameFieldInvalid ? <FormFeedback tooltip className='mt-1'>
This field cannot be empty
</FormFeedback>:''}
</InputGroup>
@ -91,7 +91,7 @@ function CardListViewer(props) {
Description
</InputGroupText>
<Input defaultValue={itemObject.description} invalid={descriptionFieldInvalid} innerRef={descriptionField} onChange={() => handleInputUpdate(descriptionField.current.value, 'descriptionField')}/>
{descriptionFieldInvalid ? <FormFeedback tooltip className="mt-1">
{descriptionFieldInvalid ? <FormFeedback tooltip className='mt-1'>
This field cannot be empty
</FormFeedback>:''}
</InputGroup>
@ -103,7 +103,7 @@ function CardListViewer(props) {
Tagline
</InputGroupText>
<Input defaultValue={itemObject.tagLine} invalid={taglineFieldInvalid} innerRef={taglineField} onChange={() => handleInputUpdate(taglineField.current.value, 'taglineField')} />
{taglineFieldInvalid ? <FormFeedback tooltip className="mt-1">
{taglineFieldInvalid ? <FormFeedback tooltip className='mt-1'>
This field cannot be empty
</FormFeedback>:''}
</InputGroup>
@ -113,7 +113,7 @@ function CardListViewer(props) {
<Link className={`${props.textColor}`} to={`/${props.resourceType}/${itemObject.id}`}>
Open this resource
</Link>
<Button color='danger' onClick={() => showModal()} className="m-2">Delete Category</Button>
<Button color='danger' onClick={() => showModal()} className='m-2'>Delete Category</Button>
</CardText>
</CardBody>
</Card>
@ -121,17 +121,17 @@ function CardListViewer(props) {
)
else
return (
<Card color={props.borderColor} outline className={`my-2 ${props.bgColor}`} style={{"width": props.cardType === "smallCard" ? "18rem": "100%"}}>
{itemObject.coverImage !== "" ? <CardImg src={EditableMediaService.getMedia(itemObject.coverImage)} style={{ "height": "180px", "objectFit": "cover" }} top width="100%" /> : ""}
<Card color={props.borderColor} outline className={`my-2 ${props.bgColor}`} style={{'width': props.cardType === 'smallCard' ? '18rem': '100%'}}>
{itemObject.coverImage !== '' ? <CardImg src={EditableMediaService.getMedia(itemObject.coverImage)} style={{ 'height': '180px', 'objectFit': 'cover' }} top width='100%' /> : ''}
<CardBody>
<Link to={`/${props.resourceType}/${itemObject.id}`}>
<CardTitle className={`${props.textColor}`} tag="h3">
<CardTitle className={`${props.textColor}`} tag='h3'>
{itemObject.name}
</CardTitle>
<CardText className={`${props.textColor}`} tag="h5">
<CardText className={`${props.textColor}`} tag='h5'>
{itemObject.description}
</CardText>
<CardText tag="h6">
<CardText tag='h6'>
<small className={`${props.textColor}`}>
{itemObject.tagLine}
</small>

View File

@ -13,12 +13,12 @@ function CategoryBar(props) {
let localCategoryMetadata = []
for (let eachResponse of responseData){
localCategoryMetadata.push({
"id": eachResponse["category_id"],
"name": eachResponse["name"],
"featuredBlog": eachResponse["featured_id"],
"description": eachResponse["description"],
"tagLine": eachResponse["tagline"],
"coverImage": eachResponse["cover_image"]
'id': eachResponse['category_id'],
'name': eachResponse['name'],
'featuredBlog': eachResponse['featured_id'],
'description': eachResponse['description'],
'tagLine': eachResponse['tagline'],
'coverImage': eachResponse['cover_image']
})
}
setCategoryMetadata(localCategoryMetadata)
@ -46,15 +46,15 @@ function CategoryBar(props) {
<Col>
<ButtonGroup style={{marginTop: '15px', marginBottom: '15px'}}>
{categoryMetadata.length > 0 ?
categoryMetadata.map((item, index) => (
categoryMetadata.map((item) => (
<Button
key={item.id}
className="btn-lg"
className='btn-lg'
color={`${ThemeConfig[GlobalTheme].buttonColor}`}
outline
active={props.currentPage === item.id}
>
<Link className="p-3" to={`/categories/${item.id}`}>
<Link className='p-3' to={`/categories/${item.id}`}>
{item.name}
</Link></Button>
)) : <Spinner />
@ -65,6 +65,6 @@ function CategoryBar(props) {
</Row>
</Container>
);
};
}
export default CategoryBar;

View File

@ -4,8 +4,6 @@ import EditableDataService from '../../services/editable-data-service';
function FileComponent(props) {
const [file, setFile] = useState(null);
const [resourceType, setResourceType] = useState('');
const [resourceId, setResourceId] = useState('');
const handleFileChange = (event) => {
setFile(event.target.files[0]); // Assuming single file upload
@ -20,7 +18,7 @@ function FileComponent(props) {
formData.append('resource_id', props.resourceId);
try {
const response = await EditableDataService.createData('/data/upload/', formData);
await EditableDataService.createData('/data/upload/', formData);
props.notificationToggler('Media uploaded successfully')
} catch (error) {
props.notificationToggler('Media upload failed', 'danger')

View File

@ -4,14 +4,12 @@ import {
Container,
Row,
Col,
Nav,
NavLink,
Spinner,
Button,
ButtonGroup
} from 'reactstrap';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faSun, faMoon, faPen, faBrush } from '@fortawesome/free-solid-svg-icons';
import { faBrush } from '@fortawesome/free-solid-svg-icons';
const Footer = (props) => {
const GlobalTheme = props.GlobalTheme;
@ -21,23 +19,23 @@ const Footer = (props) => {
const setInfo = async (colorArea, color) => {
let localThemeConfig = {...ThemeConfig}
localThemeConfig[GlobalTheme].footer[colorArea] = `${color}`
let response = await props.setInfo('/data/shared/update/theme-config/', GlobalTheme === "darkTheme" ? {
"dark_theme": JSON.stringify(localThemeConfig[GlobalTheme]),
let response = await props.setInfo('/data/shared/update/theme-config/', GlobalTheme === 'darkTheme' ? {
'dark_theme': JSON.stringify(localThemeConfig[GlobalTheme]),
}:{
"light_theme": JSON.stringify(localThemeConfig[GlobalTheme]),
'light_theme': JSON.stringify(localThemeConfig[GlobalTheme]),
})
if (response === 200)
props.notificationToggler(`Color set for ${ThemeConfig[GlobalTheme].theme} successfully!`)
if ([500, 404, 403].includes(response))
props.notificationToggler("Something failed!", "danger")
props.notificationToggler('Something failed!', 'danger')
}
return (
<footer className={`footer p-4 ${ThemeConfig ? ThemeConfig[GlobalTheme].footer['background'] + ' ' + ThemeConfig[GlobalTheme].footer['text'] : ""}`} id="site-footer">
<footer className={`footer p-4 ${ThemeConfig ? ThemeConfig[GlobalTheme].footer['background'] + ' ' + ThemeConfig[GlobalTheme].footer['text'] : ''}`} id='site-footer'>
<Container className='p-1'>
<Row>
<ButtonGroup style={{marginTop: '15px', marginBottom: '15px', marginRight: '15px'}}>
<Button disabled color={`${ThemeConfig ? ThemeConfig[GlobalTheme].navBar['buttonColor'] : ""}`}>
<Button disabled color={`${ThemeConfig ? ThemeConfig[GlobalTheme].navBar['buttonColor'] : ''}`}>
<FontAwesomeIcon icon={faBrush} /> Set color
</Button>
<Button
@ -66,7 +64,7 @@ const Footer = (props) => {
onClick={() => setInfo('background', 'bg-dark')}/>
</ButtonGroup>
<ButtonGroup style={{marginTop: '15px', marginBottom: '15px'}}>
<Button disabled color={`${ThemeConfig ? ThemeConfig[GlobalTheme].navBar['buttonColor'] : ""}`}>
<Button disabled color={`${ThemeConfig ? ThemeConfig[GlobalTheme].navBar['buttonColor'] : ''}`}>
<FontAwesomeIcon icon={faBrush} /> Set footer text color
</Button>
<Button
@ -76,12 +74,12 @@ const Footer = (props) => {
color='dark'
onClick={() => setInfo('text', 'text-black')}>Black</Button>
</ButtonGroup>
<Col md="12">
<div className="blogContent text-center text-md-left mt-3">
{new Date().getFullYear()}, <a className={`${ThemeConfig[GlobalTheme].linkBackground} ${ThemeConfig[GlobalTheme].linkTextColor}`} href="/">{ UserData ? UserData.name : <Spinner> Loading... </Spinner> }</a>
<Col md='12'>
<div className='blogContent text-center text-md-left mt-3'>
{new Date().getFullYear()}, <a className={`${ThemeConfig[GlobalTheme].linkBackground} ${ThemeConfig[GlobalTheme].linkTextColor}`} href='/'>{ UserData ? UserData.name : <Spinner> Loading... </Spinner> }</a>
<br />
<div className='m-2'>
{ UserData.builtWith ? <span>Built with <a target="_blank" className={`${ThemeConfig[GlobalTheme].linkBackground} ${ThemeConfig[GlobalTheme].linkTextColor}`} href="https://github.com/barunespadhy/rangolio">Rangolio</a></span>:""}
{ UserData.builtWith ? <span>Built with <a target='_blank' className={`${ThemeConfig[GlobalTheme].linkBackground} ${ThemeConfig[GlobalTheme].linkTextColor}`} href='https://github.com/barunespadhy/rangolio' rel="noreferrer">Rangolio</a></span>:''}
</div>
</div>
</Col>

View File

@ -27,7 +27,7 @@ function MediaLister(props) {
props.notificationToggler('Media deleted')
fetchMedia()
})
.catch(error => {
.catch(() => {
props.notificationToggler('Error deleting media', 'danger')
});
};

View File

@ -21,9 +21,7 @@ function Header(props) {
const ThemeConfig = props.ThemeConfig;
const UserData = props.UserData;
const [collapseClasses, setCollapseClasses] = useState('');
const [themeSelected, setThemeSelected] = useState('lightTheme');
const [defaultThemeConfig, setDefaultThemeConfig] = useState('lightTheme');
const [modal, setModal] = useState(false)
const toggle = () => {setModal(!modal)}
@ -34,17 +32,17 @@ function Header(props) {
if (colorArea && color)
localThemeConfig[GlobalTheme].navBar[colorArea] = `${color}`
let response = await props.setInfo('/data/shared/update/theme-config/', GlobalTheme === "darkTheme" ? {
"default_theme": defaultThemeConfig,
"dark_theme": JSON.stringify(localThemeConfig[GlobalTheme]),
let response = await props.setInfo('/data/shared/update/theme-config/', GlobalTheme === 'darkTheme' ? {
'default_theme': defaultThemeConfig,
'dark_theme': JSON.stringify(localThemeConfig[GlobalTheme]),
}:{
"default_theme": defaultThemeConfig,
"light_theme": JSON.stringify(localThemeConfig[GlobalTheme]),
'default_theme': defaultThemeConfig,
'light_theme': JSON.stringify(localThemeConfig[GlobalTheme]),
})
if (response === 200)
props.notificationToggler(`Color set for ${ThemeConfig[GlobalTheme].theme} successfully!`)
if ([500, 404, 403].includes(response))
props.notificationToggler("Something failed!", "danger")
props.notificationToggler('Something failed!', 'danger')
}
useEffect(() => {
@ -53,41 +51,40 @@ function Header(props) {
useEffect(() => {
setThemeSelected(props.ThemeConfig.defaultTheme)
setDefaultThemeConfig(props.ThemeConfig.defaultTheme)
}, [])
if (GlobalTheme && ThemeConfig && UserData)
return (
<header className="header-global" id="site-header">
<header className='header-global' id='site-header'>
<Navbar className={`navbar-horizontal ${ThemeConfig[GlobalTheme].navBar['navBarTheme']} ${ThemeConfig[GlobalTheme].navBar['background']}`}
expand="lg">
expand='lg'>
<Container>
<Publish notificationToggler={props.notificationToggler} modal={modal} toggle={toggle} />
<NavbarBrand>
<Link to="/">
<Link to='/'>
{
UserData.profilePhoto !== "" ?
UserData.profilePhoto !== '' ?
<img
style={{ width: '40px', height: '40px', objectFit: 'cover', 'marginRight': '10px' }}
className="rounded-circle"
className='rounded-circle'
src={EditableMediaService.getMedia(UserData.profilePhoto)}
/> : ""
/> : ''
}
<Button color={`${ThemeConfig ? ThemeConfig[GlobalTheme].navBar['buttonColor'] : ""}`} size="lg">
<Button color={`${ThemeConfig ? ThemeConfig[GlobalTheme].navBar['buttonColor'] : ''}`} size='lg'>
{ UserData ? UserData.name : <Spinner> Loading... </Spinner> }
</Button>
</Link>
</NavbarBrand>
<Nav className="ml-lg-auto" navbar>
<Nav className='ml-lg-auto' navbar>
<NavItem>
<ButtonGroup style={{marginTop: '15px', marginBottom: '15px', marginRight: '15px'}}>
<Button color={`${ThemeConfig ? ThemeConfig[GlobalTheme].navBar['buttonColor'] : ""}`}>
<Link to="/categories">
<Button color={`${ThemeConfig ? ThemeConfig[GlobalTheme].navBar['buttonColor'] : ''}`}>
<Link to='/categories'>
<FontAwesomeIcon icon={faPen} /> Blogs
</Link>
</Button>
<Button
color={`${ThemeConfig ? ThemeConfig[GlobalTheme].navBar['buttonColor'] : ""}`}
color={`${ThemeConfig ? ThemeConfig[GlobalTheme].navBar['buttonColor'] : ''}`}
outline
onClick={() => {setThemeSelected('lightTheme')}}
active={themeSelected === 'lightTheme'}
@ -95,7 +92,7 @@ function Header(props) {
<FontAwesomeIcon icon={faSun} /> Light Theme
</Button>
<Button
color={`${ThemeConfig ? ThemeConfig[GlobalTheme].navBar['buttonColor'] : ""}`}
color={`${ThemeConfig ? ThemeConfig[GlobalTheme].navBar['buttonColor'] : ''}`}
outline
onClick={() => {setThemeSelected('darkTheme')}}
active={themeSelected === 'darkTheme'}
@ -105,7 +102,7 @@ function Header(props) {
</ButtonGroup>
<ButtonGroup style={{marginTop: '15px', marginBottom: '15px', marginRight: '15px'}}>
<Button disabled color={`${ThemeConfig ? ThemeConfig[GlobalTheme].navBar['buttonColor'] : ""}`}>
<Button disabled color={`${ThemeConfig ? ThemeConfig[GlobalTheme].navBar['buttonColor'] : ''}`}>
<FontAwesomeIcon icon={faBrush} /> Set color
</Button>
<Button
@ -134,7 +131,7 @@ function Header(props) {
onClick={() => setInfo('background', 'bg-dark', ThemeConfig['defaultTheme'])}/>
</ButtonGroup>
<ButtonGroup style={{marginTop: '15px', marginBottom: '15px'}}>
<Button disabled color={`${ThemeConfig ? ThemeConfig[GlobalTheme].navBar['buttonColor'] : ""}`}>
<Button disabled color={`${ThemeConfig ? ThemeConfig[GlobalTheme].navBar['buttonColor'] : ''}`}>
<FontAwesomeIcon icon={faBrush} /> Set button color
</Button>
<Button
@ -145,20 +142,20 @@ function Header(props) {
onClick={() => setInfo('buttonColor', 'black', ThemeConfig['defaultTheme'])}>Black</Button>
</ButtonGroup>
<ButtonGroup style={{marginTop: '15px', marginBottom: '15px'}}>
<Button disabled color={`${ThemeConfig ? ThemeConfig[GlobalTheme].navBar['buttonColor'] : ""}`}>
<Button disabled color={`${ThemeConfig ? ThemeConfig[GlobalTheme].navBar['buttonColor'] : ''}`}>
<FontAwesomeIcon icon={faBrush} /> Default Theme
</Button>
<Button
color={`${ThemeConfig ? ThemeConfig[GlobalTheme].navBar['buttonColor'] : ""}`}
color={`${ThemeConfig ? ThemeConfig[GlobalTheme].navBar['buttonColor'] : ''}`}
outline
onClick={() => setInfo(null, null, 'lightTheme')}>Light Theme</Button>
<Button
color={`${ThemeConfig ? ThemeConfig[GlobalTheme].navBar['buttonColor'] : ""}`}
color={`${ThemeConfig ? ThemeConfig[GlobalTheme].navBar['buttonColor'] : ''}`}
outline
onClick={() => setInfo(null, null, 'darkTheme')}>Dark Theme</Button>
</ButtonGroup>
<Button
color={`${ThemeConfig ? ThemeConfig[GlobalTheme].navBar['buttonColor'] : ""}`}
color={`${ThemeConfig ? ThemeConfig[GlobalTheme].navBar['buttonColor'] : ''}`}
className='ms-5'
outline
onClick={() => toggle()}

View File

@ -1,5 +1,5 @@
import React from 'react';
import { Collapse, Button, CardBody, Card, Alert } from 'reactstrap';
import { Collapse, CardBody, Card, Alert } from 'reactstrap';
function Notification(props) {
return (

View File

@ -16,8 +16,8 @@ function Publish(props) {
let publishMethods = []
Object.entries(response.data).map(([key, value]) => (
publishMethods.push({
"key_name": key,
"name": value["name"]
'key_name': key,
'name': value['name']
})
))
setPublishMethods(publishMethods)
@ -29,7 +29,7 @@ function Publish(props) {
const publishData = async (deploy_method) => {
try {
setPublishSpinner(true)
const response = await EditableDataService.getData(`/data/publish/${deploy_method}/`);
await EditableDataService.getData(`/data/publish/${deploy_method}/`);
props.notificationToggler('Deployment Sucess')
setPublishSpinner(false)
} catch (error) {
@ -37,39 +37,30 @@ function Publish(props) {
setPublishSpinner(false)
}
};
return (
<div>
<Modal isOpen={props.modal} toggle={props.toggle}>
<ModalHeader toggle={props.toggle}>Publish Website</ModalHeader>
<ModalBody>
<h4>
Select a publish method
</h4>
<h4>Select a publish method</h4>
{
publishMethods ?
publishMethods.map((item) =>
<div className='mb-3'>
<div key={item.key_name} className='mb-3'>
<Button
key={item.key_name}
color='danger'
onClick={() => publishData(item.key_name)}
>
{item.name}
</Button>
</div>
):""
</div>):''
}
{ publishSpinner ?
<h5>
Publishing <Spinner />
</h5> : ""
<h5>Publishing <Spinner /></h5> : ''
}
</ModalBody>
<ModalFooter>
<Button color="secondary" onClick={props.toggle}>
Cancel
</Button>
<Button color='secondary' onClick={props.toggle}>Cancel</Button>
</ModalFooter>
</Modal>
</div>

View File

@ -1,7 +1,5 @@
/*
extension credits: Angelika Tyborska: https://angelika.me/2023/02/26/how-to-add-editing-image-alt-text-tiptap/
*/
import Image from '@tiptap/extension-image'
@ -32,7 +30,7 @@ function ImageNode(props) {
<span>!</span>
}
{ alt ?
<span className="text">Alt text: "{alt}".</span>:
<span className="text">Alt text: &ldquo;{alt}&ldquo;.</span>:
<span className="text">Alt text missing.</span>
}
<Button className="edit" type="button" onClick={onEditAlt}>

View File

@ -1,6 +1,5 @@
import React, { useCallback, useEffect, useState } from 'react'
import {
Button, ButtonGroup, Label, Input } from 'reactstrap';
import { Button, ButtonGroup } from 'reactstrap';
import { Color } from '@tiptap/extension-color'
import ListItem from '@tiptap/extension-list-item'
import TextStyle from '@tiptap/extension-text-style'
@ -87,8 +86,7 @@ const MenuBar = (props) => {
<Button
color={ThemeConfig[GlobalTheme].buttonColor}
onClick={() => editor.chain().focus().setTextAlign('left').run()}
outline
active={editor.isActive('left')}
outline active={editor.isActive('left')}
>
<FontAwesomeIcon icon={faAlignLeft}/>
</Button>
@ -103,8 +101,7 @@ const MenuBar = (props) => {
<Button
color={ThemeConfig[GlobalTheme].buttonColor}
onClick={() => editor.chain().focus().setTextAlign('right').run()}
outline
active={editor.isActive('right')}
outline active={editor.isActive('right')}
>
<FontAwesomeIcon icon={faAlignRight}/>
</Button>

View File

@ -1,4 +1,4 @@
import React, { Suspense, lazy } from 'react';
import React from 'react';
import ReactDOM from 'react-dom/client'
import './index.css'
import App from './App'

View File

@ -1,7 +0,0 @@
import axios from 'axios'
const getData = (endPoint) => {
return axios.get(`/data/${endPoint}.json`)
}
export default { getData }

View File

@ -1,5 +0,0 @@
const getMedia = (mediaPath) => {
return `/data/${mediaPath}`;
}
export default { getMedia };

View File

@ -1,9 +1,10 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import eslint from 'vite-plugin-eslint';
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
plugins: [react(), eslint()],
base: '/static/',
server: {
proxy: {

View File

@ -0,0 +1,34 @@
{
"env": {
"browser": true,
"es2021": true
},
"extends": [
"eslint:recommended",
"plugin:react/recommended"
],
"parserOptions": {
"ecmaFeatures": {
"jsx": true
},
"ecmaVersion": 12,
"sourceType": "module"
},
"plugins": [
"react"
],
"rules": {
"indent": [
"error",
2
],
"react/react-in-jsx-scope": "off",
"react/prop-types": "off",
"react/display-name": "off",
"quotes": [
"error",
"single"
],
"no-console": "error"
}
}

File diff suppressed because it is too large Load Diff

View File

@ -46,9 +46,11 @@
"@vitejs/plugin-react": "^4.2.1",
"cross-env": "^7.0.3",
"eslint": "^8.57.0",
"eslint-config-react-app": "^7.0.1",
"eslint-plugin-react": "^7.34.1",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.6",
"vite": "^5.2.0"
"vite": "^5.2.0",
"vite-plugin-eslint": "^1.8.1"
}
}

View File

@ -22,9 +22,9 @@ import DataService from './services/data-service'
function App() {
const [userData, setUserData] = useState(null);
const [themeConfig, setThemeConfig] = useState(null);
const [globalTheme, setGlobalTheme] = useState("lightTheme");
const [globalTheme, setGlobalTheme] = useState('lightTheme');
const [isOpen, setIsOpen] = useState(false);
const [notificationMessage, setNotificationMessage] = useState("")
const [notificationMessage, setNotificationMessage] = useState('')
const notificationToggler = (message) => {
setIsOpen(true)
@ -38,13 +38,11 @@ function App() {
DataService.getData('shared/user-data').then( response =>{
setUserData(response.data)
document.title = response.data.name
}
)
})
DataService.getData('shared/theme-config').then( response =>{
setThemeConfig(response.data)
setGlobalTheme(response.data.defaultTheme)
}
)
})
},[])
const themeSwitcher = (theme) => {
@ -53,20 +51,20 @@ function App() {
if (themeConfig)
return (
<div className="app-container">
<div className='app-container'>
<Router>
<Header className="header" ThemeSwitcher={themeSwitcher} GlobalTheme={globalTheme} ThemeConfig={themeConfig} UserData={userData} />
<Header className='header' ThemeSwitcher={themeSwitcher} GlobalTheme={globalTheme} ThemeConfig={themeConfig} UserData={userData} />
<div className={`p-0 ${themeConfig[globalTheme].background}`}>
<Notification isOpen={isOpen} message={notificationMessage} />
<Routes>
<Route path="/" element={<Home GlobalTheme={globalTheme} ThemeConfig={themeConfig} UserData={userData} />} />
<Route path="/categories" element={<CategoryList notificationToggler={notificationToggler} GlobalTheme={globalTheme} ThemeConfig={themeConfig} />} />
<Route path="/categories/:categoryID" element={<BlogList notificationToggler={notificationToggler} GlobalTheme={globalTheme} ThemeConfig={themeConfig} />} />
<Route path="/blog/:blogID" element={<Blog notificationToggler={notificationToggler} GlobalTheme={globalTheme} ThemeConfig={themeConfig} />} />
<Route path="*" element={<NotFound validRoutes={['categories', 'blog']} GlobalTheme={globalTheme} ThemeConfig={themeConfig}/>} />
<Route path='/' element={<Home GlobalTheme={globalTheme} ThemeConfig={themeConfig} UserData={userData} />} />
<Route path='/categories' element={<CategoryList notificationToggler={notificationToggler} GlobalTheme={globalTheme} ThemeConfig={themeConfig} />} />
<Route path='/categories/:categoryID' element={<BlogList notificationToggler={notificationToggler} GlobalTheme={globalTheme} ThemeConfig={themeConfig} />} />
<Route path='/blog/:blogID' element={<Blog notificationToggler={notificationToggler} GlobalTheme={globalTheme} ThemeConfig={themeConfig} />} />
<Route path='*' element={<NotFound validRoutes={['categories', 'blog']} GlobalTheme={globalTheme} ThemeConfig={themeConfig}/>} />
</Routes>
</div>
<Footer className="footer" ThemeSwitcher={themeSwitcher} GlobalTheme={globalTheme} ThemeConfig={themeConfig} UserData={userData} />
<Footer className='footer' ThemeSwitcher={themeSwitcher} GlobalTheme={globalTheme} ThemeConfig={themeConfig} UserData={userData} />
</Router>
</div>
);

View File

@ -28,41 +28,39 @@ function BlogList(props) {
const [categoryData, setCategoryData] = useState(null);
const [featuredBlogData, setFeaturedBlogData] = useState('loading');
const [currentPage, setCurrentPage] = useState('loading');
useEffect(() => {
DataService.getData(`category/${categoryID}/category-data`).then(response =>{
setCategoryData(response.data);
setFeaturedBlogData(response.data.blogMetadata.find(blog => blog.id === response.data.featuredBlog))
document.title = 'Blogs in ' + response.data.name
}
);
});
}, [categoryID]);
if (GlobalTheme && ThemeConfig) {
return (
<Container fluid className={`mb-2 p-0 ${ThemeConfig[GlobalTheme].background}`}>
<Col className="d-md-block"><Button color={ThemeConfig[GlobalTheme].buttonColor} onClick={() => navigate(`/categories/`)} className="ms-5 mt-5" outline><FontAwesomeIcon icon={faLeftLong}/></Button></Col>
<Col className="d-md-block"><Button color={ThemeConfig[GlobalTheme].buttonColor} onClick={() => navigate('/categories/')} className="ms-5 mt-5" outline><FontAwesomeIcon icon={faLeftLong}/></Button></Col>
<CategoryBar currentPage={categoryID} GlobalTheme={GlobalTheme} ThemeConfig={ThemeConfig} />
<Row className="justify-content-center align-items-center">
<Col className="d-flex flex-column align-items-center">
<div className="w-100">
<Card className={`my-2 ${ThemeConfig[GlobalTheme].background}`} style={{ width: "100%", border: "none" }}>
<Card className={`my-2 ${ThemeConfig[GlobalTheme].background}`} style={{ width: '100%', border: 'none' }}>
<CardBody>
<CardTitle style={{ display: "grid" }} className={`${ThemeConfig[GlobalTheme].textColor} justify-content-center`} tag="h1">
<CardTitle style={{ display: 'grid' }} className={`${ThemeConfig[GlobalTheme].textColor} justify-content-center`} tag='h1'>
{categoryData ? `Blogs in ${categoryData.name}`:`Loading blogs ${<Spinner/>}`}
</CardTitle>
</CardBody>
</Card>
</div>
<div className="container">
<div className='container'>
{
featuredBlogData ?
<CardListViewer
key={featuredBlogData.id}
totalItems={featuredBlogData === 'nodata' ? 0 : 1}
cardType={"longCard"}
resourceType={"blog"}
cardType={'longCard'}
resourceType={'blog'}
textColor={ThemeConfig[GlobalTheme].textColor}
bgColor={ThemeConfig[GlobalTheme].background}
borderColor={ThemeConfig[GlobalTheme].borderColor}
@ -71,14 +69,14 @@ function BlogList(props) {
}
<Row>
{categoryData ?
categoryData.blogMetadata.map((item, index) => (
categoryData.blogMetadata.map((item) => (
<Col key={item.blog_id}>
<div className={`p-2 ml-2 ${ThemeConfig[GlobalTheme].textColor}`}>
<CardListViewer
totalItems={categoryData.blogMetadata.length}
featuredBlog={categoryData.featuredBlog}
cardType={"smallCard"}
resourceType={"blog"}
cardType={'smallCard'}
resourceType={'blog'}
textColor={ThemeConfig[GlobalTheme].textColor}
bgColor={ThemeConfig[GlobalTheme].background}
borderColor={ThemeConfig[GlobalTheme].borderColor}

View File

@ -34,7 +34,7 @@ function Blog(props) {
node.attribs.target = '_blank';
}
if (node.name === 'img') {
const newClasses = `img-fluid mt-2 mb-2 rounded mx-auto d-block`;
const newClasses = 'img-fluid mt-2 mb-2 rounded mx-auto d-block';
const existingClasses = node.attribs.class ? `${node.attribs.class} ` : '';
node.attribs.class = `${existingClasses}${newClasses}`;
}
@ -47,8 +47,7 @@ function Blog(props) {
const parsedContent = parse(response.data.contentBody, { replace });
setBlogContent(parsedContent);
document.title = response.data.name
}
);
});
}, []);
useEffect(() => {
@ -71,7 +70,7 @@ function Blog(props) {
src={MediaService.getMedia(blogData.coverImage)}
alt="Banner"
style={{ width: '100%', height: 'auto', maxHeight: '20vh', objectFit: 'cover' }}
/>:""
/>:''
}
</Col>
</Row>
@ -101,7 +100,7 @@ function Blog(props) {
<Link className="p-3" to="#" onClick={(e) => {
e.preventDefault();
navigator.clipboard.writeText(window.location.href).then(() => {
props.notificationToggler("Link copied")
props.notificationToggler('Link copied')
})
return false;
}}>
@ -145,7 +144,7 @@ function Blog(props) {
</Row>
<Row className={`my-2 ${ThemeConfig[GlobalTheme].background}`}>
<Col>
<hr style={{"borderColor": `${ThemeConfig[GlobalTheme].borderColor}`}} />
<hr style={{'borderColor': `${ThemeConfig[GlobalTheme].borderColor}`}} />
</Col>
</Row>

View File

@ -12,9 +12,7 @@ import {
Col,
Container,
Card,
CardImg,
CardTitle,
CardText,
CardBody,
Button
} from 'reactstrap';
@ -40,28 +38,28 @@ function Blogs(props) {
return (
<Container fluid className={`p-0 mb-2 ${ThemeConfig[GlobalTheme].background}`}>
<Row className="justify-content-center align-items-center">
<Col className="d-flex flex-column align-items-center">
<Row className='justify-content-center align-items-center'>
<Col className='d-flex flex-column align-items-center'>
{/* Top Section - Categories */}
<div className="w-100">
<Col xs="3" className="d-md-block"><Button color={ThemeConfig[GlobalTheme].buttonColor} onClick={() => navigate(`/`)} className="ms-5 mt-5" outline><FontAwesomeIcon icon={faLeftLong}/></Button></Col>
<Card className={`my-2 ${ThemeConfig[GlobalTheme].background}`} style={{width: "100%", border: "none"}}>
<div className='w-100'>
<Col xs='3' className='d-md-block'><Button color={ThemeConfig[GlobalTheme].buttonColor} onClick={() => navigate('/')} className='ms-5 mt-5' outline><FontAwesomeIcon icon={faLeftLong}/></Button></Col>
<Card className={`my-2 ${ThemeConfig[GlobalTheme].background}`} style={{width: '100%', border: 'none'}}>
<CardBody>
<CardTitle style={{ display: "grid" }} className={`${ThemeConfig[GlobalTheme].textColor} justify-content-center`} tag="h1">
{"Categories"}
<CardTitle style={{ display: 'grid' }} className={`${ThemeConfig[GlobalTheme].textColor} justify-content-center`} tag='h1'>
{'Categories'}
</CardTitle>
</CardBody>
</Card>
</div>
{/* Bottom Section - Category Metadata or Spinner */}
<div className="" style={{ width: '70%', margin: 'auto' }}>
<div className='' style={{ width: '70%', margin: 'auto' }}>
{categoryMetadata ?
categoryMetadata.length > 0 ? categoryMetadata.map((item, index) => (
categoryMetadata.length > 0 ? categoryMetadata.map((item) => (
<CardListViewer
key={item.id}
totalItems={categoryMetadata.length}
cardType={"longCard"}
resourceType={"categories"}
cardType={'longCard'}
resourceType={'categories'}
textColor={ThemeConfig[GlobalTheme].textColor}
bgColor={ThemeConfig[GlobalTheme].background}
borderColor={ThemeConfig[GlobalTheme].borderColor}

View File

@ -15,7 +15,7 @@ function HomePage(props) {
node.attribs.target = '_blank';
}
if (node.name === 'img') {
const newClasses = `img-fluid mt-2 mb-2 rounded mx-auto d-block`;
const newClasses = 'img-fluid mt-2 mb-2 rounded mx-auto d-block';
const existingClasses = node.attribs.class ? `${node.attribs.class} ` : '';
node.attribs.class = `${existingClasses}${newClasses}`;
}
@ -29,25 +29,25 @@ function HomePage(props) {
const UserData = props.UserData ? props.UserData : <Spinner> Loading... </Spinner>
const GlobalTheme = props.GlobalTheme;
const ThemeConfig = props.ThemeConfig;
const introContent = props.UserData ? parse(props.UserData.introContent, { replace }) : ""
const introContent = props.UserData ? parse(props.UserData.introContent, { replace }) : ''
if (GlobalTheme && ThemeConfig)
return (
<Container fluid className={`p-0 mt-5 ${ThemeConfig[GlobalTheme].background}`}>
<div className="d-flex flex-column justify-content-center align-items-center min-vh-82">
<Row className="mb-4">
<Col xs="3" className="d-none d-md-block"></Col>
<Col className="p-0">
{UserData.profilePhoto !== "" ? <img style={{ width: '180px', height: '180px', objectFit: 'cover' }} className="rounded-circle" src={MediaService.getMedia(UserData.profilePhoto)} /> : ""}
<div className='d-flex flex-column justify-content-center align-items-center min-vh-82'>
<Row className='mb-4'>
<Col xs='3' className='d-none d-md-block'></Col>
<Col className='p-0'>
{UserData.profilePhoto !== '' ? <img style={{ width: '180px', height: '180px', objectFit: 'cover' }} className='rounded-circle' src={MediaService.getMedia(UserData.profilePhoto)} /> : ''}
</Col>
<Col xs="3" className="d-none d-md-block"></Col>
<Col xs='3' className='d-none d-md-block'></Col>
</Row>
<Row className={`mb-5 mt-2 ${ThemeConfig[GlobalTheme].textColor}`}>
<Col xs="3" className="d-none d-md-block"></Col>
<Col className="p-4 blogContent">
<Col xs='3' className='d-none d-md-block'></Col>
<Col className='p-4 blogContent'>
<div className={`blogContent ${ThemeConfig[GlobalTheme].textColor}`}>{introContent}</div>
</Col>
<Col xs="3" className="d-none d-md-block"></Col>
<Col xs='3' className='d-none d-md-block'></Col>
</Row>
</div>
</Container>

View File

@ -10,17 +10,15 @@ import { Link } from 'react-router-dom';
function CardListViewer(props) {
const itemObject = props.itemObject
console.log(itemObject)
if (props.totalItems > 0 && itemObject && Object.keys(itemObject).length !== 0)
return (
<Card color={props.borderColor} outline className={`my-2 ${props.bgColor}`} style={{"width": props.cardType === "smallCard" ? "18rem": "100%"}}>
{itemObject.coverImage ? <CardImg src={MediaService.getMedia(itemObject.coverImage)} style={{ "height": "180px", "objectFit": "cover" }} top width="100%" /> : ""}
<Card color={props.borderColor} outline className={`my-2 ${props.bgColor}`} style={{'width': props.cardType === 'smallCard' ? '18rem': '100%'}}>
{itemObject.coverImage ? <CardImg src={MediaService.getMedia(itemObject.coverImage)} style={{ 'height': '180px', 'objectFit': 'cover' }} top width='100%' /> : ''}
<CardBody>
<Link to={`/${props.resourceType}/${itemObject.id}`}>
<CardTitle className={`${props.textColor}`} tag="h5">
<CardTitle className={`${props.textColor}`} tag='h5'>
{itemObject.name}
</CardTitle>
<CardText className={`${props.textColor}`}>

View File

@ -1,7 +1,7 @@
import { useEffect, useState } from 'react';
import DataService from '../../services/data-service';
import { Link } from 'react-router-dom';
import { Container, Row, Col, Button, Spinner, ListGroup, ListGroupItem, ButtonGroup } from 'reactstrap';
import { Container, Row, Col, Button, Spinner, ButtonGroup } from 'reactstrap';
function CategoryBar(props) {
@ -30,7 +30,7 @@ function CategoryBar(props) {
<Col>
<ButtonGroup style={{marginTop: '15px', marginBottom: '15px'}}>
{categoryMetadata.length > 0 ?
categoryMetadata.map((item, index) => (
categoryMetadata.map((item) => (
<Button
key={item.id}
className="btn-lg"
@ -49,6 +49,6 @@ function CategoryBar(props) {
</Row>
</Container>
);
};
}
export default CategoryBar;

View File

@ -14,7 +14,7 @@ const Footer = (props) => {
if (UserData)
return (
<footer className={`footer p-4 ${ThemeConfig ? ThemeConfig[GlobalTheme].footer['background'] + ' ' + ThemeConfig[GlobalTheme].footer['text'] : ""}`} id="site-footer">
<footer className={`footer p-4 ${ThemeConfig ? ThemeConfig[GlobalTheme].footer['background'] + ' ' + ThemeConfig[GlobalTheme].footer['text'] : ''}`} id="site-footer">
<Container className='p-1'>
<Row>
<Col md="12">
@ -22,7 +22,7 @@ const Footer = (props) => {
{new Date().getFullYear()}, <a className={`${ThemeConfig[GlobalTheme].linkBackground} ${ThemeConfig[GlobalTheme].linkTextColor}`} href="/">{ UserData ? UserData.name : <Spinner> Loading... </Spinner> }</a>
<br />
<div className='m-2'>
{ UserData.builtWith ? <span>Built with <a target="_blank" className={`${ThemeConfig[GlobalTheme].linkBackground} ${ThemeConfig[GlobalTheme].linkTextColor}`} href="https://github.com/barunespadhy/rangolio">Rangolio</a></span>:""}
{ UserData.builtWith ? <span>Built with <a target="_blank" className={`${ThemeConfig[GlobalTheme].linkBackground} ${ThemeConfig[GlobalTheme].linkTextColor}`} href="https://github.com/barunespadhy/rangolio" rel="noreferrer">Rangolio</a></span>:''}
</div>
</div>
</Col>
@ -30,6 +30,6 @@ const Footer = (props) => {
</Container>
</footer>
);
};
}
export default Footer;

View File

@ -2,15 +2,11 @@
import {
Navbar,
NavbarBrand,
UncontrolledCollapse,
Row,
Col,
Nav,
NavItem,
NavLink,
Container,
Spinner,
Button, ButtonGroup, Label, Input
Button, ButtonGroup
} from 'reactstrap';
import { useState, useEffect } from 'react';
import MediaService from '../../services/media-service'
@ -23,8 +19,6 @@ function Header(props) {
const GlobalTheme = props.GlobalTheme;
const ThemeConfig = props.ThemeConfig;
const UserData = props.UserData;
const [collapseClasses, setCollapseClasses] = useState('');
const [themeSelected, setThemeSelected] = useState('lightTheme');
useEffect(() => {
@ -37,35 +31,35 @@ function Header(props) {
if (GlobalTheme && ThemeConfig && UserData)
return (
<header className="header-global" id="site-header">
<header className='header-global' id='site-header'>
<Navbar className={`navbar-horizontal ${ThemeConfig[GlobalTheme].navBar['navBarTheme']} ${ThemeConfig[GlobalTheme].navBar['background']}`}
expand="lg">
expand='lg'>
<Container>
<NavbarBrand>
<Link to="/">
<Link to='/'>
{
UserData.profilePhoto !== "" ?
UserData.profilePhoto !== '' ?
<img
style={{ width: '40px', height: '40px', objectFit: 'cover', 'marginRight': '10px' }}
className="rounded-circle"
className='rounded-circle'
src={MediaService.getMedia(UserData.profilePhoto)}
/> : ""
/> : ''
}
<Button color={`${ThemeConfig ? ThemeConfig[GlobalTheme].navBar['buttonColor'] : ""}`} size="lg">
<Button color={`${ThemeConfig ? ThemeConfig[GlobalTheme].navBar['buttonColor'] : ''}`} size='lg'>
{ UserData ? UserData.name : <Spinner> Loading... </Spinner> }
</Button>
</Link>
</NavbarBrand>
<Nav className="ml-lg-auto" navbar>
<Nav className='ml-lg-auto' navbar>
<NavItem>
<ButtonGroup style={{marginTop: '15px', marginBottom: '15px'}}>
<Button color={`${ThemeConfig ? ThemeConfig[GlobalTheme].navBar['buttonColor'] : ""}`}>
<Link to="/categories">
<Button color={`${ThemeConfig ? ThemeConfig[GlobalTheme].navBar['buttonColor'] : ''}`}>
<Link to='/categories'>
<FontAwesomeIcon icon={faPen} /> Blogs
</Link>
</Button>
<Button
color={`${ThemeConfig ? ThemeConfig[GlobalTheme].navBar['buttonColor'] : ""}`}
color={`${ThemeConfig ? ThemeConfig[GlobalTheme].navBar['buttonColor'] : ''}`}
outline
onClick={() => {setThemeSelected('lightTheme')}}
active={themeSelected === 'lightTheme'}
@ -73,7 +67,7 @@ function Header(props) {
<FontAwesomeIcon icon={faSun} /> Light Theme
</Button>
<Button
color={`${ThemeConfig ? ThemeConfig[GlobalTheme].navBar['buttonColor'] : ""}`}
color={`${ThemeConfig ? ThemeConfig[GlobalTheme].navBar['buttonColor'] : ''}`}
outline
onClick={() => {setThemeSelected('darkTheme')}}
active={themeSelected === 'darkTheme'}

View File

@ -1,5 +1,5 @@
import React from 'react';
import { Collapse, Button, CardBody, Card, Alert } from 'reactstrap';
import { Collapse, CardBody, Card, Alert } from 'reactstrap';
function Notification(props) {
return (

View File

@ -1,4 +1,4 @@
import React, { Suspense, lazy } from 'react';
import React from 'react';
import ReactDOM from 'react-dom/client'
import './index.css'
import App from './App.jsx'

View File

@ -1,17 +1,18 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import eslint from 'vite-plugin-eslint';
console.log(process.env.BUILD_ENV)
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
react(),
eslint(),
process.env.BUILD_ENV === 'ghpages' ? {
name: 'inject-ghpages-fix',
transformIndexHtml(html) {
return html.replace(
'<div id="root"></div>',
"<div id='root'></div><script type='text/javascript'>(function(l) {if (l.search[1] === '/' ) {var decoded = l.search.slice(1).split('&').map(function(s) {return s.replace(/~and~/g, '&')}).join('?');window.history.replaceState(null, null,l.pathname.slice(0, -1) + decoded + l.hash);}}(window.location))</script>"
'<div id="root"></div><script type="text/javascript">(function(l) {if (l.search[1] === "/" ) {var decoded = l.search.slice(1).split("&").map(function(s) {return s.replace(/~and~/g, "&")}).join("?");window.history.replaceState(null, null,l.pathname.slice(0, -1) + decoded + l.hash);}}(window.location))</script>'
);
}
} : ''