bro got linted
This commit is contained in:
parent
2369d5c8e6
commit
d41d3501c3
34
frontend/editable-ui/.eslintrc
Normal file
34
frontend/editable-ui/.eslintrc
Normal 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"
|
||||
}
|
||||
}
|
||||
3226
frontend/editable-ui/package-lock.json
generated
3226
frontend/editable-ui/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)
|
||||
@ -45,25 +45,24 @@ function App() {
|
||||
|
||||
const setConfigData = () => {
|
||||
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"]
|
||||
})
|
||||
document.title = responseData.name
|
||||
}
|
||||
)
|
||||
let responseData = response.data[0]
|
||||
setUserData({
|
||||
'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"])
|
||||
})
|
||||
}
|
||||
)
|
||||
let responseData = response.data[0]
|
||||
setThemeConfig({
|
||||
'defaultTheme': responseData['default_theme'],
|
||||
'darkTheme': JSON.parse(responseData['dark_theme']),
|
||||
'lightTheme': JSON.parse(responseData['light_theme'])
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
@ -75,23 +74,23 @@ function App() {
|
||||
}
|
||||
|
||||
if (themeConfig && userData && globalTheme)
|
||||
return (
|
||||
<div className="app-container">
|
||||
<Router>
|
||||
<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} />} />
|
||||
</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} />
|
||||
</Router>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className='app-container'>
|
||||
<Router>
|
||||
<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} />} />
|
||||
</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} />
|
||||
</Router>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
@ -29,33 +29,32 @@ function BlogList(props) {
|
||||
const [featuredBlogData, setFeaturedBlogData] = useState(null);
|
||||
|
||||
const loadBlogs = () => {
|
||||
EditableDataService.getData(`/data/category/${categoryID}/`).then(response => {
|
||||
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"]
|
||||
}
|
||||
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"]
|
||||
})
|
||||
}
|
||||
localCategoryData.blogMetadata = blogMetadata
|
||||
setCategoryData(localCategoryData)
|
||||
setFeaturedBlogData(blogMetadata.find(blog => blog.id === localCategoryData.featuredBlog))
|
||||
EditableDataService.getData(`/data/category/${categoryID}/`).then( response => {
|
||||
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']
|
||||
}
|
||||
);
|
||||
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']
|
||||
})
|
||||
}
|
||||
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")
|
||||
loadBlogs()
|
||||
}).catch(error => {
|
||||
props.notificationToggler('Failed to add a new blog', 'danger');
|
||||
});
|
||||
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(() => {
|
||||
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')
|
||||
@ -88,66 +87,64 @@ 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>
|
||||
<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" }}>
|
||||
<CardBody>
|
||||
<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">
|
||||
{
|
||||
featuredBlogData ?
|
||||
<CardListViewer
|
||||
key={featuredBlogData.id}
|
||||
totalItems={featuredBlogData === 'nodata' ? 0 : 1}
|
||||
cardType={"longCard"}
|
||||
resourceType={"blog"}
|
||||
textColor={ThemeConfig[GlobalTheme].textColor}
|
||||
bgColor={ThemeConfig[GlobalTheme].background}
|
||||
borderColor={ThemeConfig[GlobalTheme].borderColor}
|
||||
itemObject={featuredBlogData}
|
||||
/> : ''
|
||||
}
|
||||
<Row>
|
||||
{categoryData === 'loading' ? <Spinner /> :
|
||||
categoryData.blogMetadata.map((item, index) => (
|
||||
<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"}
|
||||
textColor={ThemeConfig[GlobalTheme].textColor}
|
||||
bgColor={ThemeConfig[GlobalTheme].background}
|
||||
borderColor={ThemeConfig[GlobalTheme].borderColor}
|
||||
itemObject={item}
|
||||
/>
|
||||
</div>
|
||||
</Col>
|
||||
))
|
||||
}
|
||||
</Row>
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
</Container>
|
||||
);
|
||||
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>
|
||||
<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' }}>
|
||||
<CardBody>
|
||||
<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'>
|
||||
{
|
||||
featuredBlogData ?
|
||||
<CardListViewer
|
||||
key={featuredBlogData.id}
|
||||
totalItems={featuredBlogData === 'nodata' ? 0 : 1}
|
||||
cardType={'longCard'}
|
||||
resourceType={'blog'}
|
||||
textColor={ThemeConfig[GlobalTheme].textColor}
|
||||
bgColor={ThemeConfig[GlobalTheme].background}
|
||||
borderColor={ThemeConfig[GlobalTheme].borderColor}
|
||||
itemObject={featuredBlogData}
|
||||
/> : ''
|
||||
}
|
||||
<Row>
|
||||
{ categoryData === 'loading' ? <Spinner /> :
|
||||
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'}
|
||||
textColor={ThemeConfig[GlobalTheme].textColor}
|
||||
bgColor={ThemeConfig[GlobalTheme].background}
|
||||
borderColor={ThemeConfig[GlobalTheme].borderColor}
|
||||
itemObject={item}
|
||||
/>
|
||||
</div>
|
||||
</Col>
|
||||
))
|
||||
}
|
||||
</Row>
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
</Container>
|
||||
);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default BlogList
|
||||
@ -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,33 +61,32 @@ function Blog(props) {
|
||||
}
|
||||
|
||||
const deleteResource = () => {
|
||||
EditableDataService.deleteData(`/data/blog/delete/${blogData.id}/`).then(response => {
|
||||
props.notificationToggler('Blog successfully deleted')
|
||||
navigate(`/categories/${blogData.parentCategory}`);
|
||||
}).catch(error => {
|
||||
props.notificationToggler('Failed to delete blog', 'danger');
|
||||
});
|
||||
EditableDataService.deleteData(`/data/blog/delete/${blogData.id}/`).then(() => {
|
||||
props.notificationToggler('Blog successfully deleted')
|
||||
navigate(`/categories/${blogData.parentCategory}`);
|
||||
}).catch(() => {
|
||||
props.notificationToggler('Failed to delete blog', 'danger');
|
||||
});
|
||||
toggle()
|
||||
}
|
||||
|
||||
const getInfo = () => {
|
||||
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"]
|
||||
})
|
||||
setBlogContent(responseData["content_body"])
|
||||
}
|
||||
);
|
||||
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']
|
||||
})
|
||||
setBlogContent(responseData['content_body'])
|
||||
});
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
getInfo()
|
||||
getInfo()
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
@ -98,80 +97,74 @@ function Blog(props) {
|
||||
}, [coverImage])
|
||||
|
||||
if (GlobalTheme && ThemeConfig && blogData) {
|
||||
return (
|
||||
<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>
|
||||
<CategoryBar currentPage={blogData.parentCategory} GlobalTheme={GlobalTheme} ThemeConfig={ThemeConfig}/>
|
||||
<Row className="mb-4">
|
||||
<Col className="p-0">
|
||||
{
|
||||
blogData.coverImage !== "" ?
|
||||
<img
|
||||
src={EditableMediaService.getMedia(blogData.coverImage)}
|
||||
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>
|
||||
<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
|
||||
outline
|
||||
color={ThemeConfig[GlobalTheme].buttonColor}
|
||||
onClick={() => toggleFileModal()}
|
||||
>
|
||||
return (
|
||||
<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>
|
||||
<CategoryBar currentPage={blogData.parentCategory} GlobalTheme={GlobalTheme} ThemeConfig={ThemeConfig}/>
|
||||
<Row className='mb-4'>
|
||||
<Col className='p-0'>
|
||||
{
|
||||
blogData.coverImage !== '' ?
|
||||
<img
|
||||
src={EditableMediaService.getMedia(blogData.coverImage)}
|
||||
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>
|
||||
<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
|
||||
outline
|
||||
color={ThemeConfig[GlobalTheme].buttonColor}
|
||||
onClick={() => toggleFileModal()}
|
||||
>
|
||||
Set Cover Image
|
||||
</Button>
|
||||
<Button
|
||||
outline
|
||||
color={ThemeConfig[GlobalTheme].buttonColor}
|
||||
onClick={() => setCoverImage('-')}
|
||||
>
|
||||
</Button>
|
||||
<Button
|
||||
outline
|
||||
color={ThemeConfig[GlobalTheme].buttonColor}
|
||||
onClick={() => setCoverImage('-')}
|
||||
>
|
||||
Remove Cover Image
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
<InputGroup className="mb-3">
|
||||
<InputGroupText>
|
||||
Name
|
||||
</InputGroupText>
|
||||
<Input innerRef={nameField} defaultValue={blogData.name} />
|
||||
</InputGroup>
|
||||
<InputGroup className="mb-3">
|
||||
<InputGroupText>
|
||||
Description
|
||||
</InputGroupText>
|
||||
<Input innerRef={descriptionField} defaultValue={blogData.description} />
|
||||
</InputGroup>
|
||||
<InputGroup>
|
||||
<InputGroupText>
|
||||
Tagline
|
||||
</InputGroupText>
|
||||
<Input innerRef={tagLineField} defaultValue={blogData.tagLine} />
|
||||
</InputGroup>
|
||||
</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}`}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<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>
|
||||
</Row>
|
||||
</Container>
|
||||
);
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
<InputGroup className='mb-3'>
|
||||
<InputGroupText>Name</InputGroupText>
|
||||
<Input innerRef={nameField} defaultValue={blogData.name} />
|
||||
</InputGroup>
|
||||
<InputGroup className='mb-3'>
|
||||
<InputGroupText>Description</InputGroupText>
|
||||
<Input innerRef={descriptionField} defaultValue={blogData.description} />
|
||||
</InputGroup>
|
||||
<InputGroup>
|
||||
<InputGroupText>Tagline</InputGroupText>
|
||||
<Input innerRef={tagLineField} defaultValue={blogData.tagLine} />
|
||||
</InputGroup>
|
||||
</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}`}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<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>
|
||||
</Row>
|
||||
</Container>
|
||||
);
|
||||
} else {
|
||||
return (<Spinner />)
|
||||
}
|
||||
|
||||
@ -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';
|
||||
@ -38,65 +35,62 @@ function Blogs(props) {
|
||||
|
||||
const setCategoryData = () => {
|
||||
EditableDataService.getData('/data/category/').then(response => {
|
||||
let responseData = response.data
|
||||
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"]
|
||||
})
|
||||
}
|
||||
setCategoryMetadata(localCategoryMetadata)
|
||||
let responseData = response.data
|
||||
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']
|
||||
})
|
||||
}
|
||||
);
|
||||
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 => {
|
||||
props.notificationToggler('Category created successfully')
|
||||
setCategoryData()
|
||||
}
|
||||
).catch(error => {
|
||||
'name': 'Enter name',
|
||||
'featured_id': '',
|
||||
'description': 'Enter description',
|
||||
'tagline': 'Enter tagline',
|
||||
'cover_image': ''
|
||||
}).then(() => {
|
||||
props.notificationToggler('Category created successfully')
|
||||
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"}}>
|
||||
<CardBody>
|
||||
<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>
|
||||
<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'}
|
||||
<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}
|
||||
@ -140,10 +132,9 @@ function Blogs(props) {
|
||||
itemObject={item}
|
||||
/>
|
||||
)) : <Spinner />}
|
||||
<Button className='mt-3 mb-2' onClick={() => updateInfo()} color={ThemeConfig[GlobalTheme].buttonColor} outline>Save Data</Button>
|
||||
<Button className='mt-3 mb-2' onClick={() => updateInfo()} color={ThemeConfig[GlobalTheme].buttonColor} outline>Save Data</Button>
|
||||
</div>
|
||||
</Col>
|
||||
|
||||
</Row>
|
||||
</Container>
|
||||
);
|
||||
|
||||
@ -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,54 +52,52 @@ 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'>
|
||||
<Button
|
||||
outline
|
||||
color={ThemeConfig[GlobalTheme].buttonColor}
|
||||
onClick={() => toggle()}
|
||||
>
|
||||
<ButtonGroup className='mt-4'>
|
||||
<Button
|
||||
outline
|
||||
color={ThemeConfig[GlobalTheme].buttonColor}
|
||||
onClick={() => toggle()}
|
||||
>
|
||||
Set Profile Photo
|
||||
</Button>
|
||||
<Button
|
||||
outline
|
||||
color={ThemeConfig[GlobalTheme].buttonColor}
|
||||
onClick={() => setProfilePhoto('-')}
|
||||
>
|
||||
</Button>
|
||||
<Button
|
||||
outline
|
||||
color={ThemeConfig[GlobalTheme].buttonColor}
|
||||
onClick={() => setProfilePhoto('-')}
|
||||
>
|
||||
Remove Profile Photo
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
</Button>
|
||||
</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>
|
||||
);
|
||||
}
|
||||
|
||||
@ -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,
|
||||
})
|
||||
}
|
||||
|
||||
@ -71,71 +71,71 @@ function CardListViewer(props) {
|
||||
if (props.resourceType === 'categories')
|
||||
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%"}}>
|
||||
<CardBody>
|
||||
<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">
|
||||
This field cannot be empty
|
||||
</FormFeedback>:''}
|
||||
</InputGroup>
|
||||
</CardTitle>
|
||||
<CardText className={`${props.textColor}`}>
|
||||
<InputGroup>
|
||||
<InputGroupText>
|
||||
Description
|
||||
</InputGroupText>
|
||||
<Input defaultValue={itemObject.description} invalid={descriptionFieldInvalid} innerRef={descriptionField} onChange={() => handleInputUpdate(descriptionField.current.value, 'descriptionField')}/>
|
||||
{descriptionFieldInvalid ? <FormFeedback tooltip className="mt-1">
|
||||
This field cannot be empty
|
||||
</FormFeedback>:''}
|
||||
</InputGroup>
|
||||
</CardText>
|
||||
<CardText>
|
||||
<small className={`${props.textColor}`}>
|
||||
<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%'}}>
|
||||
<CardBody>
|
||||
<CardTitle className={`mb-3 ${props.textColor}`} tag='h5'>
|
||||
<InputGroup>
|
||||
<InputGroupText>
|
||||
Tagline
|
||||
Name
|
||||
</InputGroupText>
|
||||
<Input defaultValue={itemObject.tagLine} invalid={taglineFieldInvalid} innerRef={taglineField} onChange={() => handleInputUpdate(taglineField.current.value, 'taglineField')} />
|
||||
{taglineFieldInvalid ? <FormFeedback tooltip className="mt-1">
|
||||
This field cannot be empty
|
||||
</FormFeedback>:''}
|
||||
<Input defaultValue={itemObject.name} invalid={nameFieldInvalid} innerRef={nameField} onChange={() => handleInputUpdate(nameField.current.value, 'nameField')}/>
|
||||
{nameFieldInvalid ? <FormFeedback tooltip className='mt-1'>
|
||||
This field cannot be empty
|
||||
</FormFeedback>:''}
|
||||
</InputGroup>
|
||||
</small>
|
||||
</CardText>
|
||||
<CardText>
|
||||
<Link className={`${props.textColor}`} to={`/${props.resourceType}/${itemObject.id}`}>
|
||||
Open this resource
|
||||
</Link>
|
||||
<Button color='danger' onClick={() => showModal()} className="m-2">Delete Category</Button>
|
||||
</CardText>
|
||||
</CardBody>
|
||||
</Card>
|
||||
</CardTitle>
|
||||
<CardText className={`${props.textColor}`}>
|
||||
<InputGroup>
|
||||
<InputGroupText>
|
||||
Description
|
||||
</InputGroupText>
|
||||
<Input defaultValue={itemObject.description} invalid={descriptionFieldInvalid} innerRef={descriptionField} onChange={() => handleInputUpdate(descriptionField.current.value, 'descriptionField')}/>
|
||||
{descriptionFieldInvalid ? <FormFeedback tooltip className='mt-1'>
|
||||
This field cannot be empty
|
||||
</FormFeedback>:''}
|
||||
</InputGroup>
|
||||
</CardText>
|
||||
<CardText>
|
||||
<small className={`${props.textColor}`}>
|
||||
<InputGroup>
|
||||
<InputGroupText>
|
||||
Tagline
|
||||
</InputGroupText>
|
||||
<Input defaultValue={itemObject.tagLine} invalid={taglineFieldInvalid} innerRef={taglineField} onChange={() => handleInputUpdate(taglineField.current.value, 'taglineField')} />
|
||||
{taglineFieldInvalid ? <FormFeedback tooltip className='mt-1'>
|
||||
This field cannot be empty
|
||||
</FormFeedback>:''}
|
||||
</InputGroup>
|
||||
</small>
|
||||
</CardText>
|
||||
<CardText>
|
||||
<Link className={`${props.textColor}`} to={`/${props.resourceType}/${itemObject.id}`}>
|
||||
Open this resource
|
||||
</Link>
|
||||
<Button color='danger' onClick={() => showModal()} className='m-2'>Delete Category</Button>
|
||||
</CardText>
|
||||
</CardBody>
|
||||
</Card>
|
||||
</>
|
||||
)
|
||||
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">
|
||||
{itemObject.name}
|
||||
</CardTitle>
|
||||
<CardText className={`${props.textColor}`} tag="h5">
|
||||
{itemObject.description}
|
||||
</CardText>
|
||||
<CardText tag="h6">
|
||||
<small className={`${props.textColor}`}>
|
||||
<CardTitle className={`${props.textColor}`} tag='h3'>
|
||||
{itemObject.name}
|
||||
</CardTitle>
|
||||
<CardText className={`${props.textColor}`} tag='h5'>
|
||||
{itemObject.description}
|
||||
</CardText>
|
||||
<CardText tag='h6'>
|
||||
<small className={`${props.textColor}`}>
|
||||
{itemObject.tagLine}
|
||||
</small>
|
||||
</CardText>
|
||||
</small>
|
||||
</CardText>
|
||||
</Link>
|
||||
</CardBody>
|
||||
<ButtonGroup>
|
||||
@ -158,6 +158,6 @@ function CardListViewer(props) {
|
||||
}
|
||||
else
|
||||
return(<h3 className={`${props.textColor}`}>No items found in this section</h3>)
|
||||
}
|
||||
}
|
||||
|
||||
export default CardListViewer
|
||||
@ -9,20 +9,20 @@ function CategoryBar(props) {
|
||||
|
||||
const setCategoryData = () => {
|
||||
EditableDataService.getData('/data/category/').then(response => {
|
||||
let responseData = response.data
|
||||
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"]
|
||||
})
|
||||
}
|
||||
setCategoryMetadata(localCategoryMetadata)
|
||||
let responseData = response.data
|
||||
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']
|
||||
})
|
||||
}
|
||||
setCategoryMetadata(localCategoryMetadata)
|
||||
}
|
||||
);
|
||||
}
|
||||
useEffect(() => {
|
||||
@ -39,32 +39,32 @@ function CategoryBar(props) {
|
||||
const GlobalTheme = props.GlobalTheme;
|
||||
const ThemeConfig = props.ThemeConfig;
|
||||
if (GlobalTheme && ThemeConfig)
|
||||
return (
|
||||
<Container fluid className={`${ThemeConfig[GlobalTheme].background}`}>
|
||||
<Row style={rowStyle}>
|
||||
<center style={{marginTop: '1.5em', marginBottom: '1.5em'}}>
|
||||
<Col>
|
||||
<ButtonGroup style={{marginTop: '15px', marginBottom: '15px'}}>
|
||||
{categoryMetadata.length > 0 ?
|
||||
categoryMetadata.map((item, index) => (
|
||||
<Button
|
||||
key={item.id}
|
||||
className="btn-lg"
|
||||
color={`${ThemeConfig[GlobalTheme].buttonColor}`}
|
||||
outline
|
||||
active={props.currentPage === item.id}
|
||||
>
|
||||
<Link className="p-3" to={`/categories/${item.id}`}>
|
||||
{item.name}
|
||||
</Link></Button>
|
||||
)) : <Spinner />
|
||||
}
|
||||
</ButtonGroup>
|
||||
</Col>
|
||||
</center>
|
||||
</Row>
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
return (
|
||||
<Container fluid className={`${ThemeConfig[GlobalTheme].background}`}>
|
||||
<Row style={rowStyle}>
|
||||
<center style={{marginTop: '1.5em', marginBottom: '1.5em'}}>
|
||||
<Col>
|
||||
<ButtonGroup style={{marginTop: '15px', marginBottom: '15px'}}>
|
||||
{categoryMetadata.length > 0 ?
|
||||
categoryMetadata.map((item) => (
|
||||
<Button
|
||||
key={item.id}
|
||||
className='btn-lg'
|
||||
color={`${ThemeConfig[GlobalTheme].buttonColor}`}
|
||||
outline
|
||||
active={props.currentPage === item.id}
|
||||
>
|
||||
<Link className='p-3' to={`/categories/${item.id}`}>
|
||||
{item.name}
|
||||
</Link></Button>
|
||||
)) : <Spinner />
|
||||
}
|
||||
</ButtonGroup>
|
||||
</Col>
|
||||
</center>
|
||||
</Row>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
export default CategoryBar;
|
||||
@ -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')
|
||||
|
||||
@ -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,67 +19,67 @@ 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'] : ""}`}>
|
||||
<FontAwesomeIcon icon={faBrush} /> Set color
|
||||
</Button>
|
||||
<Button
|
||||
color='primary'
|
||||
onClick={() => setInfo('background', 'bg-primary')}/>
|
||||
<Button
|
||||
color='secondary'
|
||||
onClick={() => setInfo('background', 'bg-secondary')}/>
|
||||
<Button
|
||||
color='success'
|
||||
onClick={() => setInfo('background', 'bg-success')}/>
|
||||
<Button
|
||||
color='danger'
|
||||
onClick={() => setInfo('background', 'bg-danger')}/>
|
||||
<Button
|
||||
color='warning'
|
||||
onClick={() => setInfo('background', 'bg-warning')}/>
|
||||
<Button
|
||||
color='info'
|
||||
onClick={() => setInfo('background', 'bg-info')}/>
|
||||
<Button
|
||||
color='light'
|
||||
onClick={() => setInfo('background', 'bg-light')}/>
|
||||
<Button
|
||||
color='dark'
|
||||
onClick={() => setInfo('background', 'bg-dark')}/>
|
||||
</ButtonGroup>
|
||||
<ButtonGroup style={{marginTop: '15px', marginBottom: '15px'}}>
|
||||
<Button disabled color={`${ThemeConfig ? ThemeConfig[GlobalTheme].navBar['buttonColor'] : ""}`}>
|
||||
<FontAwesomeIcon icon={faBrush} /> Set footer text color
|
||||
</Button>
|
||||
<Button
|
||||
color='light'
|
||||
onClick={() => setInfo('text', 'text-white')}>White</Button>
|
||||
<Button
|
||||
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>
|
||||
<ButtonGroup style={{marginTop: '15px', marginBottom: '15px', marginRight: '15px'}}>
|
||||
<Button disabled color={`${ThemeConfig ? ThemeConfig[GlobalTheme].navBar['buttonColor'] : ''}`}>
|
||||
<FontAwesomeIcon icon={faBrush} /> Set color
|
||||
</Button>
|
||||
<Button
|
||||
color='primary'
|
||||
onClick={() => setInfo('background', 'bg-primary')}/>
|
||||
<Button
|
||||
color='secondary'
|
||||
onClick={() => setInfo('background', 'bg-secondary')}/>
|
||||
<Button
|
||||
color='success'
|
||||
onClick={() => setInfo('background', 'bg-success')}/>
|
||||
<Button
|
||||
color='danger'
|
||||
onClick={() => setInfo('background', 'bg-danger')}/>
|
||||
<Button
|
||||
color='warning'
|
||||
onClick={() => setInfo('background', 'bg-warning')}/>
|
||||
<Button
|
||||
color='info'
|
||||
onClick={() => setInfo('background', 'bg-info')}/>
|
||||
<Button
|
||||
color='light'
|
||||
onClick={() => setInfo('background', 'bg-light')}/>
|
||||
<Button
|
||||
color='dark'
|
||||
onClick={() => setInfo('background', 'bg-dark')}/>
|
||||
</ButtonGroup>
|
||||
<ButtonGroup style={{marginTop: '15px', marginBottom: '15px'}}>
|
||||
<Button disabled color={`${ThemeConfig ? ThemeConfig[GlobalTheme].navBar['buttonColor'] : ''}`}>
|
||||
<FontAwesomeIcon icon={faBrush} /> Set footer text color
|
||||
</Button>
|
||||
<Button
|
||||
color='light'
|
||||
onClick={() => setInfo('text', 'text-white')}>White</Button>
|
||||
<Button
|
||||
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>
|
||||
<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>
|
||||
|
||||
@ -27,7 +27,7 @@ function MediaLister(props) {
|
||||
props.notificationToggler('Media deleted')
|
||||
fetchMedia()
|
||||
})
|
||||
.catch(error => {
|
||||
.catch(() => {
|
||||
props.notificationToggler('Error deleting media', 'danger')
|
||||
});
|
||||
};
|
||||
|
||||
@ -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,124 +51,123 @@ function Header(props) {
|
||||
|
||||
useEffect(() => {
|
||||
setThemeSelected(props.ThemeConfig.defaultTheme)
|
||||
setDefaultThemeConfig(props.ThemeConfig.defaultTheme)
|
||||
}, [])
|
||||
|
||||
if (GlobalTheme && ThemeConfig && UserData)
|
||||
return (
|
||||
<header className="header-global" id="site-header">
|
||||
<Navbar className={`navbar-horizontal ${ThemeConfig[GlobalTheme].navBar['navBarTheme']} ${ThemeConfig[GlobalTheme].navBar['background']}`}
|
||||
expand="lg">
|
||||
<Container>
|
||||
<Publish notificationToggler={props.notificationToggler} modal={modal} toggle={toggle} />
|
||||
<NavbarBrand>
|
||||
<Link to="/">
|
||||
{
|
||||
UserData.profilePhoto !== "" ?
|
||||
<img
|
||||
style={{ width: '40px', height: '40px', objectFit: 'cover', 'marginRight': '10px' }}
|
||||
className="rounded-circle"
|
||||
src={EditableMediaService.getMedia(UserData.profilePhoto)}
|
||||
/> : ""
|
||||
}
|
||||
<Button color={`${ThemeConfig ? ThemeConfig[GlobalTheme].navBar['buttonColor'] : ""}`} size="lg">
|
||||
{ UserData ? UserData.name : <Spinner> Loading... </Spinner> }
|
||||
</Button>
|
||||
</Link>
|
||||
</NavbarBrand>
|
||||
<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">
|
||||
<FontAwesomeIcon icon={faPen} /> Blogs
|
||||
</Link>
|
||||
</Button>
|
||||
<Button
|
||||
color={`${ThemeConfig ? ThemeConfig[GlobalTheme].navBar['buttonColor'] : ""}`}
|
||||
outline
|
||||
onClick={() => {setThemeSelected('lightTheme')}}
|
||||
active={themeSelected === 'lightTheme'}
|
||||
>
|
||||
<FontAwesomeIcon icon={faSun} /> Light Theme
|
||||
</Button>
|
||||
<Button
|
||||
color={`${ThemeConfig ? ThemeConfig[GlobalTheme].navBar['buttonColor'] : ""}`}
|
||||
outline
|
||||
onClick={() => {setThemeSelected('darkTheme')}}
|
||||
active={themeSelected === 'darkTheme'}
|
||||
>
|
||||
<FontAwesomeIcon icon={faMoon}/> Dark Theme
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
return (
|
||||
<header className='header-global' id='site-header'>
|
||||
<Navbar className={`navbar-horizontal ${ThemeConfig[GlobalTheme].navBar['navBarTheme']} ${ThemeConfig[GlobalTheme].navBar['background']}`}
|
||||
expand='lg'>
|
||||
<Container>
|
||||
<Publish notificationToggler={props.notificationToggler} modal={modal} toggle={toggle} />
|
||||
<NavbarBrand>
|
||||
<Link to='/'>
|
||||
{
|
||||
UserData.profilePhoto !== '' ?
|
||||
<img
|
||||
style={{ width: '40px', height: '40px', objectFit: 'cover', 'marginRight': '10px' }}
|
||||
className='rounded-circle'
|
||||
src={EditableMediaService.getMedia(UserData.profilePhoto)}
|
||||
/> : ''
|
||||
}
|
||||
<Button color={`${ThemeConfig ? ThemeConfig[GlobalTheme].navBar['buttonColor'] : ''}`} size='lg'>
|
||||
{ UserData ? UserData.name : <Spinner> Loading... </Spinner> }
|
||||
</Button>
|
||||
</Link>
|
||||
</NavbarBrand>
|
||||
<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'>
|
||||
<FontAwesomeIcon icon={faPen} /> Blogs
|
||||
</Link>
|
||||
</Button>
|
||||
<Button
|
||||
color={`${ThemeConfig ? ThemeConfig[GlobalTheme].navBar['buttonColor'] : ''}`}
|
||||
outline
|
||||
onClick={() => {setThemeSelected('lightTheme')}}
|
||||
active={themeSelected === 'lightTheme'}
|
||||
>
|
||||
<FontAwesomeIcon icon={faSun} /> Light Theme
|
||||
</Button>
|
||||
<Button
|
||||
color={`${ThemeConfig ? ThemeConfig[GlobalTheme].navBar['buttonColor'] : ''}`}
|
||||
outline
|
||||
onClick={() => {setThemeSelected('darkTheme')}}
|
||||
active={themeSelected === 'darkTheme'}
|
||||
>
|
||||
<FontAwesomeIcon icon={faMoon}/> Dark Theme
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
|
||||
<ButtonGroup style={{marginTop: '15px', marginBottom: '15px', marginRight: '15px'}}>
|
||||
<Button disabled color={`${ThemeConfig ? ThemeConfig[GlobalTheme].navBar['buttonColor'] : ""}`}>
|
||||
<FontAwesomeIcon icon={faBrush} /> Set color
|
||||
</Button>
|
||||
<Button
|
||||
color='primary'
|
||||
onClick={() => setInfo('background', 'bg-primary', ThemeConfig['defaultTheme'])}/>
|
||||
<Button
|
||||
color='secondary'
|
||||
onClick={() => setInfo('background', 'bg-secondary', ThemeConfig['defaultTheme'])}/>
|
||||
<Button
|
||||
color='success'
|
||||
onClick={() => setInfo('background', 'bg-success', ThemeConfig['defaultTheme'])}/>
|
||||
<Button
|
||||
color='danger'
|
||||
onClick={() => setInfo('background', 'bg-danger', ThemeConfig['defaultTheme'])}/>
|
||||
<Button
|
||||
color='warning'
|
||||
onClick={() => setInfo('background', 'bg-warning', ThemeConfig['defaultTheme'])}/>
|
||||
<Button
|
||||
color='info'
|
||||
onClick={() => setInfo('background', 'bg-info', ThemeConfig['defaultTheme'])}/>
|
||||
<Button
|
||||
color='light'
|
||||
onClick={() => setInfo('background', 'bg-light', ThemeConfig['defaultTheme'])}/>
|
||||
<Button
|
||||
color='dark'
|
||||
onClick={() => setInfo('background', 'bg-dark', ThemeConfig['defaultTheme'])}/>
|
||||
</ButtonGroup>
|
||||
<ButtonGroup style={{marginTop: '15px', marginBottom: '15px'}}>
|
||||
<Button disabled color={`${ThemeConfig ? ThemeConfig[GlobalTheme].navBar['buttonColor'] : ""}`}>
|
||||
<FontAwesomeIcon icon={faBrush} /> Set button color
|
||||
</Button>
|
||||
<Button
|
||||
color='light'
|
||||
onClick={() => setInfo('buttonColor', 'light', ThemeConfig['defaultTheme'])}>White</Button>
|
||||
<Button
|
||||
color='dark'
|
||||
onClick={() => setInfo('buttonColor', 'black', ThemeConfig['defaultTheme'])}>Black</Button>
|
||||
</ButtonGroup>
|
||||
<ButtonGroup style={{marginTop: '15px', marginBottom: '15px'}}>
|
||||
<Button disabled color={`${ThemeConfig ? ThemeConfig[GlobalTheme].navBar['buttonColor'] : ""}`}>
|
||||
<FontAwesomeIcon icon={faBrush} /> Default Theme
|
||||
</Button>
|
||||
<Button
|
||||
color={`${ThemeConfig ? ThemeConfig[GlobalTheme].navBar['buttonColor'] : ""}`}
|
||||
outline
|
||||
onClick={() => setInfo(null, null, 'lightTheme')}>Light Theme</Button>
|
||||
<Button
|
||||
color={`${ThemeConfig ? ThemeConfig[GlobalTheme].navBar['buttonColor'] : ""}`}
|
||||
outline
|
||||
onClick={() => setInfo(null, null, 'darkTheme')}>Dark Theme</Button>
|
||||
</ButtonGroup>
|
||||
<Button
|
||||
color={`${ThemeConfig ? ThemeConfig[GlobalTheme].navBar['buttonColor'] : ""}`}
|
||||
className='ms-5'
|
||||
outline
|
||||
onClick={() => toggle()}
|
||||
>
|
||||
Publish Data
|
||||
</Button>
|
||||
</NavItem>
|
||||
</Nav>
|
||||
</Container>
|
||||
</Navbar>
|
||||
</header>
|
||||
);
|
||||
<ButtonGroup style={{marginTop: '15px', marginBottom: '15px', marginRight: '15px'}}>
|
||||
<Button disabled color={`${ThemeConfig ? ThemeConfig[GlobalTheme].navBar['buttonColor'] : ''}`}>
|
||||
<FontAwesomeIcon icon={faBrush} /> Set color
|
||||
</Button>
|
||||
<Button
|
||||
color='primary'
|
||||
onClick={() => setInfo('background', 'bg-primary', ThemeConfig['defaultTheme'])}/>
|
||||
<Button
|
||||
color='secondary'
|
||||
onClick={() => setInfo('background', 'bg-secondary', ThemeConfig['defaultTheme'])}/>
|
||||
<Button
|
||||
color='success'
|
||||
onClick={() => setInfo('background', 'bg-success', ThemeConfig['defaultTheme'])}/>
|
||||
<Button
|
||||
color='danger'
|
||||
onClick={() => setInfo('background', 'bg-danger', ThemeConfig['defaultTheme'])}/>
|
||||
<Button
|
||||
color='warning'
|
||||
onClick={() => setInfo('background', 'bg-warning', ThemeConfig['defaultTheme'])}/>
|
||||
<Button
|
||||
color='info'
|
||||
onClick={() => setInfo('background', 'bg-info', ThemeConfig['defaultTheme'])}/>
|
||||
<Button
|
||||
color='light'
|
||||
onClick={() => setInfo('background', 'bg-light', ThemeConfig['defaultTheme'])}/>
|
||||
<Button
|
||||
color='dark'
|
||||
onClick={() => setInfo('background', 'bg-dark', ThemeConfig['defaultTheme'])}/>
|
||||
</ButtonGroup>
|
||||
<ButtonGroup style={{marginTop: '15px', marginBottom: '15px'}}>
|
||||
<Button disabled color={`${ThemeConfig ? ThemeConfig[GlobalTheme].navBar['buttonColor'] : ''}`}>
|
||||
<FontAwesomeIcon icon={faBrush} /> Set button color
|
||||
</Button>
|
||||
<Button
|
||||
color='light'
|
||||
onClick={() => setInfo('buttonColor', 'light', ThemeConfig['defaultTheme'])}>White</Button>
|
||||
<Button
|
||||
color='dark'
|
||||
onClick={() => setInfo('buttonColor', 'black', ThemeConfig['defaultTheme'])}>Black</Button>
|
||||
</ButtonGroup>
|
||||
<ButtonGroup style={{marginTop: '15px', marginBottom: '15px'}}>
|
||||
<Button disabled color={`${ThemeConfig ? ThemeConfig[GlobalTheme].navBar['buttonColor'] : ''}`}>
|
||||
<FontAwesomeIcon icon={faBrush} /> Default Theme
|
||||
</Button>
|
||||
<Button
|
||||
color={`${ThemeConfig ? ThemeConfig[GlobalTheme].navBar['buttonColor'] : ''}`}
|
||||
outline
|
||||
onClick={() => setInfo(null, null, 'lightTheme')}>Light Theme</Button>
|
||||
<Button
|
||||
color={`${ThemeConfig ? ThemeConfig[GlobalTheme].navBar['buttonColor'] : ''}`}
|
||||
outline
|
||||
onClick={() => setInfo(null, null, 'darkTheme')}>Dark Theme</Button>
|
||||
</ButtonGroup>
|
||||
<Button
|
||||
color={`${ThemeConfig ? ThemeConfig[GlobalTheme].navBar['buttonColor'] : ''}`}
|
||||
className='ms-5'
|
||||
outline
|
||||
onClick={() => toggle()}
|
||||
>
|
||||
Publish Data
|
||||
</Button>
|
||||
</NavItem>
|
||||
</Nav>
|
||||
</Container>
|
||||
</Navbar>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
|
||||
export default Header;
|
||||
@ -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 (
|
||||
|
||||
@ -3,77 +3,68 @@ import { Button, Modal, ModalHeader, ModalBody, ModalFooter, Spinner } from 'rea
|
||||
import EditableDataService from '../../services/editable-data-service';
|
||||
|
||||
function Publish(props) {
|
||||
const [publishMethods, setPublishMethods] = useState(null)
|
||||
const [publishSpinner, setPublishSpinner] = useState(false)
|
||||
const [publishMethods, setPublishMethods] = useState(null)
|
||||
const [publishSpinner, setPublishSpinner] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
fetchPublishMethods()
|
||||
}, [])
|
||||
useEffect(() => {
|
||||
fetchPublishMethods()
|
||||
}, [])
|
||||
|
||||
const fetchPublishMethods = async () => {
|
||||
try {
|
||||
const response = await EditableDataService.getData('/data/publish/methods/');
|
||||
let publishMethods = []
|
||||
Object.entries(response.data).map(([key, value]) => (
|
||||
publishMethods.push({
|
||||
"key_name": key,
|
||||
"name": value["name"]
|
||||
})
|
||||
))
|
||||
setPublishMethods(publishMethods)
|
||||
} catch (error) {
|
||||
props.notificationToggler('Error fetching methods', 'danger')
|
||||
}
|
||||
};
|
||||
const fetchPublishMethods = async () => {
|
||||
try {
|
||||
const response = await EditableDataService.getData('/data/publish/methods/');
|
||||
let publishMethods = []
|
||||
Object.entries(response.data).map(([key, value]) => (
|
||||
publishMethods.push({
|
||||
'key_name': key,
|
||||
'name': value['name']
|
||||
})
|
||||
))
|
||||
setPublishMethods(publishMethods)
|
||||
} catch (error) {
|
||||
props.notificationToggler('Error fetching methods', 'danger')
|
||||
}
|
||||
};
|
||||
|
||||
const publishData = async (deploy_method) => {
|
||||
try {
|
||||
setPublishSpinner(true)
|
||||
const response = await EditableDataService.getData(`/data/publish/${deploy_method}/`);
|
||||
props.notificationToggler('Deployment Sucess')
|
||||
setPublishSpinner(false)
|
||||
} catch (error) {
|
||||
props.notificationToggler('Deployment Failed', 'danger')
|
||||
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>
|
||||
{
|
||||
publishMethods ?
|
||||
publishMethods.map((item) =>
|
||||
<div className='mb-3'>
|
||||
<Button
|
||||
key={item.key_name}
|
||||
color='danger'
|
||||
onClick={() => publishData(item.key_name)}
|
||||
>
|
||||
{item.name}
|
||||
</Button>
|
||||
</div>
|
||||
):""
|
||||
}
|
||||
{ publishSpinner ?
|
||||
<h5>
|
||||
Publishing <Spinner />
|
||||
</h5> : ""
|
||||
}
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button color="secondary" onClick={props.toggle}>
|
||||
Cancel
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
const publishData = async (deploy_method) => {
|
||||
try {
|
||||
setPublishSpinner(true)
|
||||
await EditableDataService.getData(`/data/publish/${deploy_method}/`);
|
||||
props.notificationToggler('Deployment Sucess')
|
||||
setPublishSpinner(false)
|
||||
} catch (error) {
|
||||
props.notificationToggler('Deployment Failed', 'danger')
|
||||
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>
|
||||
{
|
||||
publishMethods ?
|
||||
publishMethods.map((item) =>
|
||||
<div key={item.key_name} className='mb-3'>
|
||||
<Button
|
||||
color='danger'
|
||||
onClick={() => publishData(item.key_name)}
|
||||
>
|
||||
{item.name}
|
||||
</Button>
|
||||
</div>):''
|
||||
}
|
||||
{ publishSpinner ?
|
||||
<h5>Publishing <Spinner /></h5> : ''
|
||||
}
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button color='secondary' onClick={props.toggle}>Cancel</Button>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Publish;
|
||||
@ -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: “{alt}“.</span>:
|
||||
<span className="text">Alt text missing.</span>
|
||||
}
|
||||
<Button className="edit" type="button" onClick={onEditAlt}>
|
||||
|
||||
@ -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'
|
||||
@ -13,14 +12,14 @@ import { EditorProvider, useCurrentEditor } from '@tiptap/react'
|
||||
import StarterKit from '@tiptap/starter-kit'
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faBold, faItalic,
|
||||
faUnderline, faAlignLeft,
|
||||
faAlignCenter, faAlignRight,
|
||||
faAlignJustify, faHighlighter,
|
||||
faStrikethrough, faCode,
|
||||
faListUl, faLink,
|
||||
faListOl, faQuoteLeft,
|
||||
faQuoteRight, faRulerHorizontal,
|
||||
faRotateLeft, faRotateRight, faImage } from '@fortawesome/free-solid-svg-icons';
|
||||
faUnderline, faAlignLeft,
|
||||
faAlignCenter, faAlignRight,
|
||||
faAlignJustify, faHighlighter,
|
||||
faStrikethrough, faCode,
|
||||
faListUl, faLink,
|
||||
faListOl, faQuoteLeft,
|
||||
faQuoteRight, faRulerHorizontal,
|
||||
faRotateLeft, faRotateRight, faImage } from '@fortawesome/free-solid-svg-icons';
|
||||
|
||||
|
||||
import CustomImageExtension from './tiptap-custom-extensions/custom-image-extension.jsx'
|
||||
@ -83,57 +82,55 @@ const MenuBar = (props) => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<ButtonGroup className='mt-2'>
|
||||
<Button
|
||||
color={ThemeConfig[GlobalTheme].buttonColor}
|
||||
onClick={() => editor.chain().focus().setTextAlign('left').run()}
|
||||
outline
|
||||
active={editor.isActive('left')}
|
||||
>
|
||||
<ButtonGroup className='mt-2'>
|
||||
<Button
|
||||
color={ThemeConfig[GlobalTheme].buttonColor}
|
||||
onClick={() => editor.chain().focus().setTextAlign('left').run()}
|
||||
outline active={editor.isActive('left')}
|
||||
>
|
||||
<FontAwesomeIcon icon={faAlignLeft}/>
|
||||
</Button>
|
||||
<Button
|
||||
color={ThemeConfig[GlobalTheme].buttonColor}
|
||||
onClick={() => editor.chain().focus().setTextAlign('center').run()}
|
||||
outline
|
||||
active={editor.isActive('center')}
|
||||
>
|
||||
<Button
|
||||
color={ThemeConfig[GlobalTheme].buttonColor}
|
||||
onClick={() => editor.chain().focus().setTextAlign('center').run()}
|
||||
outline
|
||||
active={editor.isActive('center')}
|
||||
>
|
||||
<FontAwesomeIcon icon={faAlignCenter}/>
|
||||
</Button>
|
||||
<Button
|
||||
color={ThemeConfig[GlobalTheme].buttonColor}
|
||||
onClick={() => editor.chain().focus().setTextAlign('right').run()}
|
||||
outline
|
||||
active={editor.isActive('right')}
|
||||
>
|
||||
<Button
|
||||
color={ThemeConfig[GlobalTheme].buttonColor}
|
||||
onClick={() => editor.chain().focus().setTextAlign('right').run()}
|
||||
outline active={editor.isActive('right')}
|
||||
>
|
||||
<FontAwesomeIcon icon={faAlignRight}/>
|
||||
</Button>
|
||||
<Button
|
||||
color={ThemeConfig[GlobalTheme].buttonColor}
|
||||
onClick={() => editor.chain().focus().setTextAlign('justify').run()}
|
||||
outline
|
||||
active={editor.isActive('justify')}
|
||||
>
|
||||
color={ThemeConfig[GlobalTheme].buttonColor}
|
||||
onClick={() => editor.chain().focus().setTextAlign('justify').run()}
|
||||
outline
|
||||
active={editor.isActive('justify')}
|
||||
>
|
||||
<FontAwesomeIcon icon={faAlignJustify}/>
|
||||
</Button>
|
||||
</ButtonGroup >
|
||||
</ButtonGroup>
|
||||
<ButtonGroup className='mt-2' style={{marginLeft: '10px'}}>
|
||||
<Button
|
||||
color={ThemeConfig[GlobalTheme].buttonColor}
|
||||
onClick={() => editor.chain().focus().toggleBold().run()}
|
||||
disabled={
|
||||
!editor.can()
|
||||
.chain()
|
||||
.focus()
|
||||
.toggleBold()
|
||||
.run()
|
||||
}
|
||||
outline
|
||||
active={editor.isActive('bold')}
|
||||
>
|
||||
<FontAwesomeIcon icon={faBold}/>
|
||||
</Button>
|
||||
<Button
|
||||
<Button
|
||||
color={ThemeConfig[GlobalTheme].buttonColor}
|
||||
onClick={() => editor.chain().focus().toggleBold().run()}
|
||||
disabled={
|
||||
!editor.can()
|
||||
.chain()
|
||||
.focus()
|
||||
.toggleBold()
|
||||
.run()
|
||||
}
|
||||
outline
|
||||
active={editor.isActive('bold')}
|
||||
>
|
||||
<FontAwesomeIcon icon={faBold}/>
|
||||
</Button>
|
||||
<Button
|
||||
color={ThemeConfig[GlobalTheme].buttonColor}
|
||||
onClick={() => editor.chain().focus().toggleItalic().run()}
|
||||
disabled={
|
||||
@ -179,15 +176,15 @@ const MenuBar = (props) => {
|
||||
>
|
||||
<FontAwesomeIcon icon={faStrikethrough}/>
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
<Button
|
||||
className='mt-2'
|
||||
color={ThemeConfig[GlobalTheme].buttonColor}
|
||||
onClick={setLink}
|
||||
style={{marginLeft: '10px'}}
|
||||
outline
|
||||
active={editor.isActive('link')}
|
||||
>
|
||||
</ButtonGroup>
|
||||
<Button
|
||||
className='mt-2'
|
||||
color={ThemeConfig[GlobalTheme].buttonColor}
|
||||
onClick={setLink}
|
||||
style={{marginLeft: '10px'}}
|
||||
outline
|
||||
active={editor.isActive('link')}
|
||||
>
|
||||
<FontAwesomeIcon icon={faLink}/>
|
||||
</Button>
|
||||
<Button
|
||||
@ -209,7 +206,7 @@ const MenuBar = (props) => {
|
||||
>
|
||||
p
|
||||
</Button>
|
||||
<Button
|
||||
<Button
|
||||
color={ThemeConfig[GlobalTheme].buttonColor}
|
||||
onClick={() => editor.chain().focus().toggleHeading({ level: 1 }).run()}
|
||||
outline
|
||||
@ -257,9 +254,9 @@ const MenuBar = (props) => {
|
||||
>
|
||||
h6
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
<ButtonGroup className='mt-2' style={{marginLeft: '10px'}}>
|
||||
<Button
|
||||
</ButtonGroup>
|
||||
<ButtonGroup className='mt-2' style={{marginLeft: '10px'}}>
|
||||
<Button
|
||||
color={ThemeConfig[GlobalTheme].buttonColor}
|
||||
onClick={() => editor.chain().focus().toggleBulletList().run()}
|
||||
outline
|
||||
@ -275,7 +272,7 @@ const MenuBar = (props) => {
|
||||
>
|
||||
<FontAwesomeIcon icon={faListOl}/>
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
</ButtonGroup>
|
||||
<Button
|
||||
className='mt-2'
|
||||
color={ThemeConfig[GlobalTheme].buttonColor}
|
||||
@ -289,39 +286,39 @@ const MenuBar = (props) => {
|
||||
<Button
|
||||
className='mt-2'
|
||||
color={ThemeConfig[GlobalTheme].buttonColor}
|
||||
onClick={() => editor.chain().focus().setHorizontalRule().run()}
|
||||
outline
|
||||
style={{marginLeft: '10px'}}
|
||||
onClick={() => editor.chain().focus().setHorizontalRule().run()}
|
||||
outline
|
||||
style={{marginLeft: '10px'}}
|
||||
>
|
||||
<FontAwesomeIcon icon={faRulerHorizontal}/>
|
||||
</Button>
|
||||
<ButtonGroup className='mt-2' style={{marginLeft: '10px'}}>
|
||||
<Button
|
||||
color={ThemeConfig[GlobalTheme].buttonColor}
|
||||
onClick={() => editor.chain().focus().undo().run()}
|
||||
disabled={
|
||||
!editor.can()
|
||||
.chain()
|
||||
.focus()
|
||||
.undo()
|
||||
.run()
|
||||
}
|
||||
>
|
||||
<FontAwesomeIcon icon={faRotateLeft}/>
|
||||
</Button>
|
||||
<Button
|
||||
color={ThemeConfig[GlobalTheme].buttonColor}
|
||||
onClick={() => editor.chain().focus().redo().run()}
|
||||
disabled={
|
||||
!editor.can()
|
||||
.chain()
|
||||
.focus()
|
||||
.redo()
|
||||
.run()
|
||||
}
|
||||
>
|
||||
<FontAwesomeIcon icon={faRotateRight}/>
|
||||
</Button>
|
||||
<Button
|
||||
color={ThemeConfig[GlobalTheme].buttonColor}
|
||||
onClick={() => editor.chain().focus().undo().run()}
|
||||
disabled={
|
||||
!editor.can()
|
||||
.chain()
|
||||
.focus()
|
||||
.undo()
|
||||
.run()
|
||||
}
|
||||
>
|
||||
<FontAwesomeIcon icon={faRotateLeft}/>
|
||||
</Button>
|
||||
<Button
|
||||
color={ThemeConfig[GlobalTheme].buttonColor}
|
||||
onClick={() => editor.chain().focus().redo().run()}
|
||||
disabled={
|
||||
!editor.can()
|
||||
.chain()
|
||||
.focus()
|
||||
.redo()
|
||||
.run()
|
||||
}
|
||||
>
|
||||
<FontAwesomeIcon icon={faRotateRight}/>
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
<Button
|
||||
className='mt-2 ms-2'
|
||||
@ -353,13 +350,13 @@ const extensions = [
|
||||
Blockquote,
|
||||
CustomImageExtension,
|
||||
TextAlign.configure({
|
||||
types: ['heading', 'paragraph'],
|
||||
}),
|
||||
types: ['heading', 'paragraph'],
|
||||
}),
|
||||
Highlight,
|
||||
Link.configure({
|
||||
openOnClick: false,
|
||||
autolink: true,
|
||||
}),
|
||||
openOnClick: false,
|
||||
autolink: true,
|
||||
}),
|
||||
]
|
||||
|
||||
export default (props) => {
|
||||
@ -370,9 +367,9 @@ export default (props) => {
|
||||
const toggle = () => setModal(!modal);
|
||||
|
||||
if (props.content && GlobalTheme && ThemeConfig)
|
||||
return (
|
||||
<>
|
||||
<EditorProvider slotBefore={<MenuBar resourceType={props.resourceType} resourceId={props.resourceId} modal={modal} toggle={toggle} notificationToggler={props.notificationToggler} setContent={props.setContent} GlobalTheme={GlobalTheme} ThemeConfig={ThemeConfig}/>} extensions={extensions} content={props.content}></EditorProvider>
|
||||
</>
|
||||
)
|
||||
return (
|
||||
<>
|
||||
<EditorProvider slotBefore={<MenuBar resourceType={props.resourceType} resourceId={props.resourceId} modal={modal} toggle={toggle} notificationToggler={props.notificationToggler} setContent={props.setContent} GlobalTheme={GlobalTheme} ThemeConfig={ThemeConfig}/>} extensions={extensions} content={props.content}></EditorProvider>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@ -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'
|
||||
|
||||
@ -1,7 +0,0 @@
|
||||
import axios from 'axios'
|
||||
|
||||
const getData = (endPoint) => {
|
||||
return axios.get(`/data/${endPoint}.json`)
|
||||
}
|
||||
|
||||
export default { getData }
|
||||
@ -1,5 +0,0 @@
|
||||
const getMedia = (mediaPath) => {
|
||||
return `/data/${mediaPath}`;
|
||||
}
|
||||
|
||||
export default { getMedia };
|
||||
@ -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: {
|
||||
|
||||
34
frontend/viewable-ui/.eslintrc
Normal file
34
frontend/viewable-ui/.eslintrc
Normal 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"
|
||||
}
|
||||
}
|
||||
3226
frontend/viewable-ui/package-lock.json
generated
3226
frontend/viewable-ui/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)
|
||||
@ -36,15 +36,13 @@ function App() {
|
||||
|
||||
useEffect(() => {
|
||||
DataService.getData('shared/user-data').then( response =>{
|
||||
setUserData(response.data)
|
||||
document.title = response.data.name
|
||||
}
|
||||
)
|
||||
setUserData(response.data)
|
||||
document.title = response.data.name
|
||||
})
|
||||
DataService.getData('shared/theme-config').then( response =>{
|
||||
setThemeConfig(response.data)
|
||||
setGlobalTheme(response.data.defaultTheme)
|
||||
}
|
||||
)
|
||||
setThemeConfig(response.data)
|
||||
setGlobalTheme(response.data.defaultTheme)
|
||||
})
|
||||
},[])
|
||||
|
||||
const themeSwitcher = (theme) => {
|
||||
@ -52,24 +50,24 @@ function App() {
|
||||
}
|
||||
|
||||
if (themeConfig)
|
||||
return (
|
||||
<div className="app-container">
|
||||
<Router>
|
||||
<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}/>} />
|
||||
</Routes>
|
||||
</div>
|
||||
<Footer className="footer" ThemeSwitcher={themeSwitcher} GlobalTheme={globalTheme} ThemeConfig={themeConfig} UserData={userData} />
|
||||
</Router>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className='app-container'>
|
||||
<Router>
|
||||
<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}/>} />
|
||||
</Routes>
|
||||
</div>
|
||||
<Footer className='footer' ThemeSwitcher={themeSwitcher} GlobalTheme={globalTheme} ThemeConfig={themeConfig} UserData={userData} />
|
||||
</Router>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
@ -28,57 +28,55 @@ 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
|
||||
}
|
||||
);
|
||||
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"}
|
||||
textColor={ThemeConfig[GlobalTheme].textColor}
|
||||
bgColor={ThemeConfig[GlobalTheme].background}
|
||||
borderColor={ThemeConfig[GlobalTheme].borderColor}
|
||||
itemObject={featuredBlogData}
|
||||
/> : ''
|
||||
<CardListViewer
|
||||
key={featuredBlogData.id}
|
||||
totalItems={featuredBlogData === 'nodata' ? 0 : 1}
|
||||
cardType={'longCard'}
|
||||
resourceType={'blog'}
|
||||
textColor={ThemeConfig[GlobalTheme].textColor}
|
||||
bgColor={ThemeConfig[GlobalTheme].background}
|
||||
borderColor={ThemeConfig[GlobalTheme].borderColor}
|
||||
itemObject={featuredBlogData}
|
||||
/> : ''
|
||||
}
|
||||
<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}
|
||||
|
||||
@ -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}`;
|
||||
}
|
||||
@ -43,12 +43,11 @@ function Blog(props) {
|
||||
|
||||
useEffect(() => {
|
||||
DataService.getData(`blog/${blogID}/blog-data`).then(response =>{
|
||||
setBlogData(response.data)
|
||||
const parsedContent = parse(response.data.contentBody, { replace });
|
||||
setBlogContent(parsedContent);
|
||||
document.title = response.data.name
|
||||
}
|
||||
);
|
||||
setBlogData(response.data)
|
||||
const parsedContent = parse(response.data.contentBody, { replace });
|
||||
setBlogContent(parsedContent);
|
||||
document.title = response.data.name
|
||||
});
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
@ -59,105 +58,105 @@ function Blog(props) {
|
||||
}, [GlobalTheme])
|
||||
|
||||
if (GlobalTheme && ThemeConfig) {
|
||||
return (
|
||||
<Container fluid className={`${ThemeConfig[GlobalTheme].background}`}>
|
||||
<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">
|
||||
{
|
||||
blogData.coverImage ?
|
||||
<img
|
||||
src={MediaService.getMedia(blogData.coverImage)}
|
||||
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>
|
||||
<Col xs={`${window.screen.width >= 765 ? '6':''}`}>
|
||||
<h1 className={`${ThemeConfig[GlobalTheme].textColor}`}>{blogData.name}</h1>
|
||||
<h4 className={`${ThemeConfig[GlobalTheme].textColor}`}>{blogData.description}</h4>
|
||||
<div>
|
||||
<Button
|
||||
color="primary"
|
||||
id="toggler"
|
||||
style={{
|
||||
marginBottom: '1rem'
|
||||
}}
|
||||
>
|
||||
Share
|
||||
</Button>
|
||||
<UncontrolledCollapse toggler="#toggler">
|
||||
<Card style={{overflowX: 'auto'}}>
|
||||
<CardBody>
|
||||
<ButtonGroup
|
||||
vertical
|
||||
className="my-2"
|
||||
>
|
||||
<Button outline>
|
||||
<Link className="p-3" to="#" onClick={(e) => {
|
||||
e.preventDefault();
|
||||
navigator.clipboard.writeText(window.location.href).then(() => {
|
||||
props.notificationToggler("Link copied")
|
||||
})
|
||||
return false;
|
||||
}}>
|
||||
<FontAwesomeIcon icon={faCopy}/> Copy Link
|
||||
</Link>
|
||||
</Button>
|
||||
<Button outline>
|
||||
<Link className="p-3" to="#" onClick={(e) => {
|
||||
e.preventDefault();
|
||||
window.open(`https://www.facebook.com/sharer/sharer.php?u=${encodeURIComponent(window.location.href)}`, 'facebook-share-dialog', 'width=800,height=600');
|
||||
return false;
|
||||
}}>
|
||||
<FontAwesomeIcon icon={faFacebook}/> Facebook
|
||||
</Link>
|
||||
</Button>
|
||||
<Button outline>
|
||||
<Link className="p-3" to="#" onClick={(e) => {
|
||||
e.preventDefault();
|
||||
window.open(`https://www.reddit.com/submit?url=${window.location.href}&title=${blogData.name}`, 'facebook-share-dialog', 'width=800,height=600');
|
||||
return false;
|
||||
}}>
|
||||
<FontAwesomeIcon icon={faReddit}/> Reddit
|
||||
</Link>
|
||||
</Button>
|
||||
<Button outline>
|
||||
<Link className="p-3" to="#" onClick={(e) => {
|
||||
e.preventDefault();
|
||||
window.open(`https://twitter.com/intent/tweet?text=Check%20out%20this%20article!&url=${window.location.href}`, 'facebook-share-dialog', 'width=800,height=600');
|
||||
return false;
|
||||
}}>
|
||||
<FontAwesomeIcon icon={faXTwitter}/>
|
||||
</Link>
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
</CardBody>
|
||||
</Card>
|
||||
</UncontrolledCollapse>
|
||||
</div>
|
||||
</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}`}} />
|
||||
</Col>
|
||||
</Row>
|
||||
return (
|
||||
<Container fluid className={`${ThemeConfig[GlobalTheme].background}`}>
|
||||
<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">
|
||||
{
|
||||
blogData.coverImage ?
|
||||
<img
|
||||
src={MediaService.getMedia(blogData.coverImage)}
|
||||
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>
|
||||
<Col xs={`${window.screen.width >= 765 ? '6':''}`}>
|
||||
<h1 className={`${ThemeConfig[GlobalTheme].textColor}`}>{blogData.name}</h1>
|
||||
<h4 className={`${ThemeConfig[GlobalTheme].textColor}`}>{blogData.description}</h4>
|
||||
<div>
|
||||
<Button
|
||||
color="primary"
|
||||
id="toggler"
|
||||
style={{
|
||||
marginBottom: '1rem'
|
||||
}}
|
||||
>
|
||||
Share
|
||||
</Button>
|
||||
<UncontrolledCollapse toggler="#toggler">
|
||||
<Card style={{overflowX: 'auto'}}>
|
||||
<CardBody>
|
||||
<ButtonGroup
|
||||
vertical
|
||||
className="my-2"
|
||||
>
|
||||
<Button outline>
|
||||
<Link className="p-3" to="#" onClick={(e) => {
|
||||
e.preventDefault();
|
||||
navigator.clipboard.writeText(window.location.href).then(() => {
|
||||
props.notificationToggler('Link copied')
|
||||
})
|
||||
return false;
|
||||
}}>
|
||||
<FontAwesomeIcon icon={faCopy}/> Copy Link
|
||||
</Link>
|
||||
</Button>
|
||||
<Button outline>
|
||||
<Link className="p-3" to="#" onClick={(e) => {
|
||||
e.preventDefault();
|
||||
window.open(`https://www.facebook.com/sharer/sharer.php?u=${encodeURIComponent(window.location.href)}`, 'facebook-share-dialog', 'width=800,height=600');
|
||||
return false;
|
||||
}}>
|
||||
<FontAwesomeIcon icon={faFacebook}/> Facebook
|
||||
</Link>
|
||||
</Button>
|
||||
<Button outline>
|
||||
<Link className="p-3" to="#" onClick={(e) => {
|
||||
e.preventDefault();
|
||||
window.open(`https://www.reddit.com/submit?url=${window.location.href}&title=${blogData.name}`, 'facebook-share-dialog', 'width=800,height=600');
|
||||
return false;
|
||||
}}>
|
||||
<FontAwesomeIcon icon={faReddit}/> Reddit
|
||||
</Link>
|
||||
</Button>
|
||||
<Button outline>
|
||||
<Link className="p-3" to="#" onClick={(e) => {
|
||||
e.preventDefault();
|
||||
window.open(`https://twitter.com/intent/tweet?text=Check%20out%20this%20article!&url=${window.location.href}`, 'facebook-share-dialog', 'width=800,height=600');
|
||||
return false;
|
||||
}}>
|
||||
<FontAwesomeIcon icon={faXTwitter}/>
|
||||
</Link>
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
</CardBody>
|
||||
</Card>
|
||||
</UncontrolledCollapse>
|
||||
</div>
|
||||
</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}`}} />
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<Row className="mr-2 ml-2 mt-1">
|
||||
<Col xs="3" className="d-none d-md-block"></Col>
|
||||
<Col style={{marginBottom: '25px'}}>
|
||||
<div className={`blogContent ${ThemeConfig[GlobalTheme].textColor}`}>{blogContent}</div>
|
||||
</Col>
|
||||
<Col xs="3" className="d-none d-md-block"></Col>
|
||||
</Row>
|
||||
</Container>
|
||||
);
|
||||
<Row className="mr-2 ml-2 mt-1">
|
||||
<Col xs="3" className="d-none d-md-block"></Col>
|
||||
<Col style={{marginBottom: '25px'}}>
|
||||
<div className={`blogContent ${ThemeConfig[GlobalTheme].textColor}`}>{blogContent}</div>
|
||||
</Col>
|
||||
<Col xs="3" className="d-none d-md-block"></Col>
|
||||
</Row>
|
||||
</Container>
|
||||
);
|
||||
} else {
|
||||
return (<Spinner />)
|
||||
}
|
||||
|
||||
@ -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"}}>
|
||||
<CardBody>
|
||||
<CardTitle style={{ display: "grid" }} className={`${ThemeConfig[GlobalTheme].textColor} justify-content-center`} tag="h1">
|
||||
{"Categories"}
|
||||
</CardTitle>
|
||||
</CardBody>
|
||||
</Card>
|
||||
<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>
|
||||
</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}
|
||||
|
||||
@ -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,29 +29,29 @@ 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)} /> : ""}
|
||||
</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">
|
||||
<div className={`blogContent ${ThemeConfig[GlobalTheme].textColor}`}>{introContent}</div>
|
||||
</Col>
|
||||
<Col xs="3" className="d-none d-md-block"></Col>
|
||||
</Row>
|
||||
</div>
|
||||
</Container>
|
||||
);
|
||||
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)} /> : ''}
|
||||
</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'>
|
||||
<div className={`blogContent ${ThemeConfig[GlobalTheme].textColor}`}>{introContent}</div>
|
||||
</Col>
|
||||
<Col xs='3' className='d-none d-md-block'></Col>
|
||||
</Row>
|
||||
</div>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
export default HomePage;
|
||||
@ -9,34 +9,32 @@ import {
|
||||
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%" /> : ""}
|
||||
<CardBody>
|
||||
<Link to={`/${props.resourceType}/${itemObject.id}`}>
|
||||
<CardTitle className={`${props.textColor}`} tag="h5">
|
||||
{itemObject.name}
|
||||
</CardTitle>
|
||||
<CardText className={`${props.textColor}`}>
|
||||
{itemObject.description}
|
||||
</CardText>
|
||||
<CardText>
|
||||
<small className={`${props.textColor}`}>
|
||||
{itemObject.tagLine}
|
||||
</small>
|
||||
</CardText>
|
||||
</Link>
|
||||
</CardBody>
|
||||
</Card>
|
||||
)
|
||||
else
|
||||
return(<h3 className={`${props.textColor}`}>No items found in this section</h3>)
|
||||
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%' /> : ''}
|
||||
<CardBody>
|
||||
<Link to={`/${props.resourceType}/${itemObject.id}`}>
|
||||
<CardTitle className={`${props.textColor}`} tag='h5'>
|
||||
{itemObject.name}
|
||||
</CardTitle>
|
||||
<CardText className={`${props.textColor}`}>
|
||||
{itemObject.description}
|
||||
</CardText>
|
||||
<CardText>
|
||||
<small className={`${props.textColor}`}>
|
||||
{itemObject.tagLine}
|
||||
</small>
|
||||
</CardText>
|
||||
</Link>
|
||||
</CardBody>
|
||||
</Card>
|
||||
)
|
||||
else
|
||||
return(<h3 className={`${props.textColor}`}>No items found in this section</h3>)
|
||||
}
|
||||
|
||||
export default CardListViewer
|
||||
@ -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) {
|
||||
|
||||
@ -23,32 +23,32 @@ function CategoryBar(props) {
|
||||
const GlobalTheme = props.GlobalTheme;
|
||||
const ThemeConfig = props.ThemeConfig;
|
||||
if (GlobalTheme && ThemeConfig)
|
||||
return (
|
||||
<Container fluid className={`${ThemeConfig[GlobalTheme].background}`}>
|
||||
<Row style={rowStyle}>
|
||||
<center style={{marginTop: '1.5em', marginBottom: '1.5em'}}>
|
||||
<Col>
|
||||
<ButtonGroup style={{marginTop: '15px', marginBottom: '15px'}}>
|
||||
{categoryMetadata.length > 0 ?
|
||||
categoryMetadata.map((item, index) => (
|
||||
<Button
|
||||
key={item.id}
|
||||
className="btn-lg"
|
||||
color={`${ThemeConfig[GlobalTheme].buttonColor}`}
|
||||
outline
|
||||
active={props.currentPage === item.id}
|
||||
>
|
||||
<Link className="p-3" to={`/categories/${item.id}`}>
|
||||
{item.name}
|
||||
</Link></Button>
|
||||
)) : <Spinner />
|
||||
}
|
||||
</ButtonGroup>
|
||||
</Col>
|
||||
</center>
|
||||
</Row>
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
return (
|
||||
<Container fluid className={`${ThemeConfig[GlobalTheme].background}`}>
|
||||
<Row style={rowStyle}>
|
||||
<center style={{marginTop: '1.5em', marginBottom: '1.5em'}}>
|
||||
<Col>
|
||||
<ButtonGroup style={{marginTop: '15px', marginBottom: '15px'}}>
|
||||
{categoryMetadata.length > 0 ?
|
||||
categoryMetadata.map((item) => (
|
||||
<Button
|
||||
key={item.id}
|
||||
className="btn-lg"
|
||||
color={`${ThemeConfig[GlobalTheme].buttonColor}`}
|
||||
outline
|
||||
active={props.currentPage === item.id}
|
||||
>
|
||||
<Link className="p-3" to={`/categories/${item.id}`}>
|
||||
{item.name}
|
||||
</Link></Button>
|
||||
)) : <Spinner />
|
||||
}
|
||||
</ButtonGroup>
|
||||
</Col>
|
||||
</center>
|
||||
</Row>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
export default CategoryBar;
|
||||
@ -13,23 +13,23 @@ const Footer = (props) => {
|
||||
const UserData = props.UserData;
|
||||
|
||||
if (UserData)
|
||||
return (
|
||||
<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">
|
||||
<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>:""}
|
||||
return (
|
||||
<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">
|
||||
<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" rel="noreferrer">Rangolio</a></span>:''}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
</Container>
|
||||
</footer>
|
||||
);
|
||||
};
|
||||
</Col>
|
||||
</Row>
|
||||
</Container>
|
||||
</footer>
|
||||
);
|
||||
}
|
||||
|
||||
export default Footer;
|
||||
@ -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(() => {
|
||||
@ -36,57 +30,57 @@ function Header(props) {
|
||||
}, [])
|
||||
|
||||
if (GlobalTheme && ThemeConfig && UserData)
|
||||
return (
|
||||
<header className="header-global" id="site-header">
|
||||
<Navbar className={`navbar-horizontal ${ThemeConfig[GlobalTheme].navBar['navBarTheme']} ${ThemeConfig[GlobalTheme].navBar['background']}`}
|
||||
expand="lg">
|
||||
<Container>
|
||||
<NavbarBrand>
|
||||
<Link to="/">
|
||||
{
|
||||
UserData.profilePhoto !== "" ?
|
||||
<img
|
||||
style={{ width: '40px', height: '40px', objectFit: 'cover', 'marginRight': '10px' }}
|
||||
className="rounded-circle"
|
||||
src={MediaService.getMedia(UserData.profilePhoto)}
|
||||
/> : ""
|
||||
}
|
||||
<Button color={`${ThemeConfig ? ThemeConfig[GlobalTheme].navBar['buttonColor'] : ""}`} size="lg">
|
||||
{ UserData ? UserData.name : <Spinner> Loading... </Spinner> }
|
||||
</Button>
|
||||
</Link>
|
||||
</NavbarBrand>
|
||||
<Nav className="ml-lg-auto" navbar>
|
||||
<NavItem>
|
||||
<ButtonGroup style={{marginTop: '15px', marginBottom: '15px'}}>
|
||||
<Button color={`${ThemeConfig ? ThemeConfig[GlobalTheme].navBar['buttonColor'] : ""}`}>
|
||||
<Link to="/categories">
|
||||
<FontAwesomeIcon icon={faPen} /> Blogs
|
||||
</Link>
|
||||
</Button>
|
||||
<Button
|
||||
color={`${ThemeConfig ? ThemeConfig[GlobalTheme].navBar['buttonColor'] : ""}`}
|
||||
outline
|
||||
onClick={() => {setThemeSelected('lightTheme')}}
|
||||
active={themeSelected === 'lightTheme'}
|
||||
>
|
||||
<FontAwesomeIcon icon={faSun} /> Light Theme
|
||||
</Button>
|
||||
<Button
|
||||
color={`${ThemeConfig ? ThemeConfig[GlobalTheme].navBar['buttonColor'] : ""}`}
|
||||
outline
|
||||
onClick={() => {setThemeSelected('darkTheme')}}
|
||||
active={themeSelected === 'darkTheme'}
|
||||
>
|
||||
<FontAwesomeIcon icon={faMoon}/> Dark Theme
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
</NavItem>
|
||||
</Nav>
|
||||
</Container>
|
||||
</Navbar>
|
||||
</header>
|
||||
);
|
||||
return (
|
||||
<header className='header-global' id='site-header'>
|
||||
<Navbar className={`navbar-horizontal ${ThemeConfig[GlobalTheme].navBar['navBarTheme']} ${ThemeConfig[GlobalTheme].navBar['background']}`}
|
||||
expand='lg'>
|
||||
<Container>
|
||||
<NavbarBrand>
|
||||
<Link to='/'>
|
||||
{
|
||||
UserData.profilePhoto !== '' ?
|
||||
<img
|
||||
style={{ width: '40px', height: '40px', objectFit: 'cover', 'marginRight': '10px' }}
|
||||
className='rounded-circle'
|
||||
src={MediaService.getMedia(UserData.profilePhoto)}
|
||||
/> : ''
|
||||
}
|
||||
<Button color={`${ThemeConfig ? ThemeConfig[GlobalTheme].navBar['buttonColor'] : ''}`} size='lg'>
|
||||
{ UserData ? UserData.name : <Spinner> Loading... </Spinner> }
|
||||
</Button>
|
||||
</Link>
|
||||
</NavbarBrand>
|
||||
<Nav className='ml-lg-auto' navbar>
|
||||
<NavItem>
|
||||
<ButtonGroup style={{marginTop: '15px', marginBottom: '15px'}}>
|
||||
<Button color={`${ThemeConfig ? ThemeConfig[GlobalTheme].navBar['buttonColor'] : ''}`}>
|
||||
<Link to='/categories'>
|
||||
<FontAwesomeIcon icon={faPen} /> Blogs
|
||||
</Link>
|
||||
</Button>
|
||||
<Button
|
||||
color={`${ThemeConfig ? ThemeConfig[GlobalTheme].navBar['buttonColor'] : ''}`}
|
||||
outline
|
||||
onClick={() => {setThemeSelected('lightTheme')}}
|
||||
active={themeSelected === 'lightTheme'}
|
||||
>
|
||||
<FontAwesomeIcon icon={faSun} /> Light Theme
|
||||
</Button>
|
||||
<Button
|
||||
color={`${ThemeConfig ? ThemeConfig[GlobalTheme].navBar['buttonColor'] : ''}`}
|
||||
outline
|
||||
onClick={() => {setThemeSelected('darkTheme')}}
|
||||
active={themeSelected === 'darkTheme'}
|
||||
>
|
||||
<FontAwesomeIcon icon={faMoon}/> Dark Theme
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
</NavItem>
|
||||
</Nav>
|
||||
</Container>
|
||||
</Navbar>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
|
||||
export default Header;
|
||||
@ -11,17 +11,17 @@ function NotFound(props) {
|
||||
const ThemeConfig = props.ThemeConfig;
|
||||
|
||||
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">
|
||||
<h1 className={`${ThemeConfig[GlobalTheme].textColor}`}>
|
||||
404 Page not found
|
||||
</h1>
|
||||
</Row>
|
||||
</div>
|
||||
</Container>
|
||||
);
|
||||
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">
|
||||
<h1 className={`${ThemeConfig[GlobalTheme].textColor}`}>
|
||||
404 Page not found
|
||||
</h1>
|
||||
</Row>
|
||||
</div>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
export default NotFound;
|
||||
@ -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 (
|
||||
|
||||
@ -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'
|
||||
|
||||
@ -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>'
|
||||
);
|
||||
}
|
||||
} : ''
|
||||
|
||||
Loading…
Reference in New Issue
Block a user