Added preliminary image support and improved functionality around application

This commit is contained in:
Barunes Padhy 2024-06-02 07:27:24 +03:00
parent 2d8cc1e7de
commit d450ac3cb4
14 changed files with 203 additions and 70 deletions

View File

@ -93,7 +93,6 @@ class BlogsByCategoryAPIView(APIView):
class BlogCreateAPIView(generics.CreateAPIView): class BlogCreateAPIView(generics.CreateAPIView):
queryset = Blog.objects.all() queryset = Blog.objects.all()
serializer_class = BlogSerializer serializer_class = BlogSerializer
lookup_field = 'blog_id'
class BlogUpdateAPIView(generics.RetrieveUpdateAPIView): class BlogUpdateAPIView(generics.RetrieveUpdateAPIView):
queryset = Blog.objects.all() queryset = Blog.objects.all()
@ -105,16 +104,6 @@ class BlogRetrieveAPIView(generics.RetrieveAPIView):
serializer_class = BlogSerializer serializer_class = BlogSerializer
lookup_field = 'blog_id' lookup_field = 'blog_id'
class CategoryDetailView(APIView):
def get(self, request, category_id):
try:
category = Category.objects.get(category_id=category_id)
except Category.DoesNotExist:
return Response({'message': 'Category not found'}, status=404)
serializer = UnifiedCategoryBlogSerializer(category)
return Response(serializer.data)
class BlogDeleteAPIView(generics.DestroyAPIView): class BlogDeleteAPIView(generics.DestroyAPIView):
queryset = Blog.objects.all() queryset = Blog.objects.all()
serializer_class = BlogSerializer serializer_class = BlogSerializer

View File

@ -45,8 +45,8 @@ urlpatterns = [
path('data/category/<slug:category_id>/', BlogsByCategoryAPIView.as_view(), name='blogs-by-category-view'), path('data/category/<slug:category_id>/', BlogsByCategoryAPIView.as_view(), name='blogs-by-category-view'),
path('data/category/update/<slug:category_id>/', CategoryUpdateAPIView.as_view(), name='category-update-view'), path('data/category/update/<slug:category_id>/', CategoryUpdateAPIView.as_view(), name='category-update-view'),
path('data/category/delete/<slug:category_id>/', CategoryDeleteAPIView.as_view(), name='category-delete-view'), path('data/category/delete/<slug:category_id>/', CategoryDeleteAPIView.as_view(), name='category-delete-view'),
path('data/blog/create/', BlogCreateAPIView.as_view(), name='blog-create-view'),
path('data/blog/<slug:blog_id>/', BlogRetrieveAPIView.as_view(), name='blog-retrieve-view'), path('data/blog/<slug:blog_id>/', BlogRetrieveAPIView.as_view(), name='blog-retrieve-view'),
path('data/blog/create/<slug:blog_id>/', BlogCreateAPIView.as_view(), name='blog-create-view'),
path('data/blog/update/<slug:blog_id>/', BlogUpdateAPIView.as_view(), name='blog-update-view'), path('data/blog/update/<slug:blog_id>/', BlogUpdateAPIView.as_view(), name='blog-update-view'),
path('data/blog/delete/<slug:blog_id>/', BlogDeleteAPIView.as_view(), name='blog-delete-view'), path('data/blog/delete/<slug:blog_id>/', BlogDeleteAPIView.as_view(), name='blog-delete-view'),
] ]

View File

@ -14,7 +14,7 @@
"@tiptap/extension-blockquote": "^2.3.2", "@tiptap/extension-blockquote": "^2.3.2",
"@tiptap/extension-color": "^2.3.2", "@tiptap/extension-color": "^2.3.2",
"@tiptap/extension-highlight": "^2.3.2", "@tiptap/extension-highlight": "^2.3.2",
"@tiptap/extension-image": "^2.3.2", "@tiptap/extension-image": "^2.4.0",
"@tiptap/extension-link": "^2.3.2", "@tiptap/extension-link": "^2.3.2",
"@tiptap/extension-list-item": "^2.3.2", "@tiptap/extension-list-item": "^2.3.2",
"@tiptap/extension-text-align": "^2.3.2", "@tiptap/extension-text-align": "^2.3.2",
@ -25,8 +25,10 @@
"axios": "^1.6.8", "axios": "^1.6.8",
"bootstrap": "^5.3.3", "bootstrap": "^5.3.3",
"html-react-parser": "^5.1.10", "html-react-parser": "^5.1.10",
"interactjs": "^1.10.27",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-resizable": "^3.0.5",
"react-router-dom": "^6.22.3", "react-router-dom": "^6.22.3",
"reactstrap": "^9.2.2", "reactstrap": "^9.2.2",
"sass": "^1.75.0", "sass": "^1.75.0",
@ -920,6 +922,11 @@
"integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==",
"dev": true "dev": true
}, },
"node_modules/@interactjs/types": {
"version": "1.10.27",
"resolved": "https://registry.npmjs.org/@interactjs/types/-/types-1.10.27.tgz",
"integrity": "sha512-BUdv0cvs4H5ODuwft2Xp4eL8Vmi3LcihK42z0Ft/FbVJZoRioBsxH+LlsBdK4tAie7PqlKGy+1oyOncu1nQ6eA=="
},
"node_modules/@jridgewell/gen-mapping": { "node_modules/@jridgewell/gen-mapping": {
"version": "0.3.5", "version": "0.3.5",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz",
@ -1453,9 +1460,9 @@
} }
}, },
"node_modules/@tiptap/extension-image": { "node_modules/@tiptap/extension-image": {
"version": "2.3.2", "version": "2.4.0",
"resolved": "https://registry.npmjs.org/@tiptap/extension-image/-/extension-image-2.3.2.tgz", "resolved": "https://registry.npmjs.org/@tiptap/extension-image/-/extension-image-2.4.0.tgz",
"integrity": "sha512-otkhqToHnjjpWOIswuotfK/PTPEOhhKRFPf1NuXvqHpMNulz+J1uIuA9R/B1m+bXkxZzCMKkWQi50vjqH9idVg==", "integrity": "sha512-NIVhRPMO/ONo8OywEd+8zh0Q6Q7EbFHtBxVsvfOKj9KtZkaXQfUO4MzONTyptkvAchTpj9pIzeaEY5fyU87gFA==",
"funding": { "funding": {
"type": "github", "type": "github",
"url": "https://github.com/sponsors/ueberdosis" "url": "https://github.com/sponsors/ueberdosis"
@ -2211,6 +2218,14 @@
"resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz",
"integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==" "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow=="
}, },
"node_modules/clsx": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz",
"integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==",
"engines": {
"node": ">=6"
}
},
"node_modules/color-convert": { "node_modules/color-convert": {
"version": "1.9.3", "version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
@ -3501,6 +3516,14 @@
"resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.3.tgz", "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.3.tgz",
"integrity": "sha512-qlD8YNDqyTKTyuITrDOffsl6Tdhv+UC4hcdAVuQsK4IMQ99nSgd1MIA/Q+jQYoh9r3hVUXhYh7urSRmXPkW04g==" "integrity": "sha512-qlD8YNDqyTKTyuITrDOffsl6Tdhv+UC4hcdAVuQsK4IMQ99nSgd1MIA/Q+jQYoh9r3hVUXhYh7urSRmXPkW04g=="
}, },
"node_modules/interactjs": {
"version": "1.10.27",
"resolved": "https://registry.npmjs.org/interactjs/-/interactjs-1.10.27.tgz",
"integrity": "sha512-y/8RcCftGAF24gSp76X2JS3XpHiUvDQyhF8i7ujemBz77hwiHDuJzftHx7thY8cxGogwGiPJ+o97kWB6eAXnsA==",
"dependencies": {
"@interactjs/types": "1.10.27"
}
},
"node_modules/internal-slot": { "node_modules/internal-slot": {
"version": "1.0.7", "version": "1.0.7",
"resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz",
@ -4706,6 +4729,19 @@
"react": "^18.2.0" "react": "^18.2.0"
} }
}, },
"node_modules/react-draggable": {
"version": "4.4.6",
"resolved": "https://registry.npmjs.org/react-draggable/-/react-draggable-4.4.6.tgz",
"integrity": "sha512-LtY5Xw1zTPqHkVmtM3X8MUOxNDOUhv/khTgBgrUvwaS064bwVvxT+q5El0uUFNx5IEPKXuRejr7UqLwBIg5pdw==",
"dependencies": {
"clsx": "^1.1.1",
"prop-types": "^15.8.1"
},
"peerDependencies": {
"react": ">= 16.3.0",
"react-dom": ">= 16.3.0"
}
},
"node_modules/react-fast-compare": { "node_modules/react-fast-compare": {
"version": "3.2.2", "version": "3.2.2",
"resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz", "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz",
@ -4744,6 +4780,18 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/react-resizable": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/react-resizable/-/react-resizable-3.0.5.tgz",
"integrity": "sha512-vKpeHhI5OZvYn82kXOs1bC8aOXktGU5AmKAgaZS4F5JPburCtbmDPqE7Pzp+1kN4+Wb81LlF33VpGwWwtXem+w==",
"dependencies": {
"prop-types": "15.x",
"react-draggable": "^4.0.3"
},
"peerDependencies": {
"react": ">= 16.3"
}
},
"node_modules/react-router": { "node_modules/react-router": {
"version": "6.22.3", "version": "6.22.3",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.22.3.tgz", "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.22.3.tgz",

View File

@ -19,7 +19,7 @@
"@tiptap/extension-blockquote": "^2.3.2", "@tiptap/extension-blockquote": "^2.3.2",
"@tiptap/extension-color": "^2.3.2", "@tiptap/extension-color": "^2.3.2",
"@tiptap/extension-highlight": "^2.3.2", "@tiptap/extension-highlight": "^2.3.2",
"@tiptap/extension-image": "^2.3.2", "@tiptap/extension-image": "^2.4.0",
"@tiptap/extension-link": "^2.3.2", "@tiptap/extension-link": "^2.3.2",
"@tiptap/extension-list-item": "^2.3.2", "@tiptap/extension-list-item": "^2.3.2",
"@tiptap/extension-text-align": "^2.3.2", "@tiptap/extension-text-align": "^2.3.2",
@ -30,8 +30,10 @@
"axios": "^1.6.8", "axios": "^1.6.8",
"bootstrap": "^5.3.3", "bootstrap": "^5.3.3",
"html-react-parser": "^5.1.10", "html-react-parser": "^5.1.10",
"interactjs": "^1.10.27",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-resizable": "^3.0.5",
"react-router-dom": "^6.22.3", "react-router-dom": "^6.22.3",
"reactstrap": "^9.2.2", "reactstrap": "^9.2.2",
"sass": "^1.75.0", "sass": "^1.75.0",

View File

@ -11,56 +11,80 @@ import {
Card, Card,
Row, Row,
Col, Col,
Button,
CardImg, CardImg,
CardTitle, CardTitle,
CardText, CardText,
CardBody CardBody
} from 'reactstrap'; } from 'reactstrap';
import { Link, useParams } from 'react-router-dom'; import { useNavigate, useParams } from 'react-router-dom';
import { faLeftLong } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
function BlogList(props) { function BlogList(props) {
const { categoryID } = useParams(); const { categoryID } = useParams();
let navigate = useNavigate();
const GlobalTheme = props.GlobalTheme; const GlobalTheme = props.GlobalTheme;
const ThemeConfig = props.ThemeConfig; const ThemeConfig = props.ThemeConfig;
const [categoryData, setCategoryData] = useState('loading'); const [categoryData, setCategoryData] = useState('loading');
const [currentPage, setCurrentPage] = useState('loading');
useEffect(() => { const loadBlogs = () => {
EditableDataService.getData(`/data/category/${categoryID}/`).then(response => { EditableDataService.getData(`/data/category/${categoryID}/`).then(response => {
let responseData = response.data let responseData = response.data
let blogMetadata = [] let blogMetadata = []
console.log(responseData) console.log(responseData)
let localCategoryData = { let localCategoryData = {
"id": responseData["category_id"], "id": responseData["category_id"],
"name": responseData["name"], "name": responseData["name"],
"coverImage": responseData["cover_image"], "coverImage": responseData["cover_image"],
"tagLine": responseData["tagline"], "tagLine": responseData["tagline"],
"description": responseData["description"], "description": responseData["description"],
"featuredBlog": responseData["featured_id"], "featuredBlog": responseData["featured_id"],
"blogMetadata": responseData["blog_metadata"] "blogMetadata": responseData["blog_metadata"]
} }
for (let eachBlog of responseData["blog_metadata"]){ for (let eachBlog of responseData["blog_metadata"]){
blogMetadata.push({ blogMetadata.push({
"id": eachBlog["blog_id"], "id": eachBlog["blog_id"],
"name": eachBlog["name"], "name": eachBlog["name"],
"description": eachBlog["description"], "description": eachBlog["description"],
"tagLine": eachBlog["tagline"], "tagLine": eachBlog["tagline"],
"coverImage": eachBlog["cover_image"], "coverImage": eachBlog["cover_image"],
"parentCategory": eachBlog["parent_category"] "parentCategory": eachBlog["parent_category"]
}) })
} }
localCategoryData.blogMetadata = blogMetadata localCategoryData.blogMetadata = blogMetadata
setCategoryData(localCategoryData) setCategoryData(localCategoryData)
} }
); );
}
useEffect(() => {
loadBlogs()
}, [categoryID]); }, [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');
});
}
if (GlobalTheme && ThemeConfig) { if (GlobalTheme && ThemeConfig) {
return ( return (
<Container fluid className={`mb-2 p-0 ${ThemeConfig[GlobalTheme].background}`}> <Container fluid className={`mb-2 p-0 ${ThemeConfig[GlobalTheme].background}`}>
<Col xs="3" className="d-none d-md-block"><Button color={ThemeConfig[GlobalTheme].buttonColor} onClick={() => navigate(`/categories/`)} className="ms-5" outline><FontAwesomeIcon icon={faLeftLong}/></Button></Col>
<CategoryBar currentPage={categoryID} GlobalTheme={GlobalTheme} ThemeConfig={ThemeConfig} /> <CategoryBar currentPage={categoryID} GlobalTheme={GlobalTheme} ThemeConfig={ThemeConfig} />
<Row className="justify-content-center align-items-center"> <Row className="justify-content-center align-items-center">
<Col className="d-flex flex-column align-items-center"> <Col className="d-flex flex-column align-items-center">
@ -69,6 +93,7 @@ return (
<CardBody> <CardBody>
<CardTitle style={{ display: "grid" }} className={`${ThemeConfig[GlobalTheme].textColor} justify-content-center`} tag="h1"> <CardTitle style={{ display: "grid" }} className={`${ThemeConfig[GlobalTheme].textColor} justify-content-center`} tag="h1">
{`Blogs in ${categoryData.name}`} {`Blogs in ${categoryData.name}`}
<Button className='mt-2' color={ThemeConfig[GlobalTheme].buttonColor} outline onClick={() => addNewBlog()}>Add New</Button>
</CardTitle> </CardTitle>
</CardBody> </CardBody>
</Card> </Card>

View File

@ -4,14 +4,20 @@ import EditableDataService from '../../services/editable-data-service';
import MediaService from '../../services/media-service' import MediaService from '../../services/media-service'
import CategoryBar from './shared/category-bar'; import CategoryBar from './shared/category-bar';
import EditorComponent from './shared/tiptap'; import EditorComponent from './shared/tiptap';
import ModalComponent from './shared/modal-component';
import { faLeftLong } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { import {
Container,Row, Col,Spinner, UncontrolledCollapse, Button, ButtonGroup, Card, CardBody, Input, InputGroup, InputGroupText Container,Row, Col,Spinner, UncontrolledCollapse, Button, ButtonGroup, Card, CardBody, Input, InputGroup, InputGroupText
} from 'reactstrap'; } from 'reactstrap';
import { Link, useParams } from 'react-router-dom'; import { useNavigate, useParams } from 'react-router-dom';
function Blog(props) { function Blog(props) {
let navigate = useNavigate();
const nameField = useRef(null); const nameField = useRef(null);
const descriptionField = useRef(null); const descriptionField = useRef(null);
const tagLineField = useRef(null); const tagLineField = useRef(null);
@ -23,6 +29,11 @@ function Blog(props) {
const [blogData, setBlogData] = useState([]); const [blogData, setBlogData] = useState([]);
const [blogContent, setBlogContent] = useState(); const [blogContent, setBlogContent] = useState();
const [modal, setModal] = useState(false);
const [modalText, setModalText] = useState(false);
const [modalTitle, setModalTitle] = useState(false);
const toggle = () => setModal(!modal);
const setInfo = (event) => { const setInfo = (event) => {
EditableDataService.updateData(`/data/blog/update/${blogID}/`,{ EditableDataService.updateData(`/data/blog/update/${blogID}/`,{
@ -38,6 +49,22 @@ function Blog(props) {
}); });
} }
const showModal = () => {
setModalTitle('Confirm')
setModalText('Are you sure that you wish to delete this blog?')
toggle()
}
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');
});
toggle()
}
const getInfo = () => { const getInfo = () => {
EditableDataService.getData(`/data/blog/${blogID}/`).then(response => { EditableDataService.getData(`/data/blog/${blogID}/`).then(response => {
let responseData = response.data let responseData = response.data
@ -61,19 +88,25 @@ function Blog(props) {
if (GlobalTheme && ThemeConfig && blogData) { if (GlobalTheme && ThemeConfig && blogData) {
return ( return (
<Container fluid className={`${ThemeConfig[GlobalTheme].background}`}> <Container fluid className={`${ThemeConfig[GlobalTheme].background}`}>
<CategoryBar currentPage={blogData.parentCategory} GlobalTheme={GlobalTheme} ThemeConfig={ThemeConfig}/> <ModalComponent modalText={modalText} modalTitle={modalTitle} modal={modal} toggle={toggle} confirmAction={deleteResource}/>
<Col xs="3" className="d-none d-md-block"><Button color={ThemeConfig[GlobalTheme].buttonColor} onClick={() => navigate(`/categories/${blogData.parentCategory}`)} className="ms-5" outline><FontAwesomeIcon icon={faLeftLong}/></Button></Col>
<CategoryBar currentPage={blogData.parentCategory} GlobalTheme={GlobalTheme} ThemeConfig={ThemeConfig}/>
<Row className="mb-4"> <Row className="mb-4">
<Col className="p-0"> <Col className="p-0">
<img {
src={MediaService.getMedia(blogData.coverImage)} blogData.coverImage !== "" ?
alt="Banner" <img
style={{ width: '100%', height: 'auto', maxHeight: '20vh', objectFit: 'cover' }} src={MediaService.getMedia(blogData.coverImage)}
/> alt="Banner"
style={{ width: '100%', height: 'auto', maxHeight: '20vh', objectFit: 'cover' }}
/>:""
}
</Col> </Col>
</Row> </Row>
<Row className="mr-2 ml-2 mb-2 mt-1 blogContent"> <Row className="mr-2 ml-2 mb-2 mt-1 blogContent">
<Col xs="3" className="d-none d-md-block"></Col> <Col xs="3" className="d-none d-md-block"></Col>
<Col xs={`${window.screen.width >= 765 ? '6':''}`}> <Col xs={`${window.screen.width >= 765 ? '6':''}`}>
<Button color='danger' onClick={() => showModal()} className="mb-5">Delete Blog</Button>
<InputGroup className="mb-3"> <InputGroup className="mb-3">
<InputGroupText> <InputGroupText>
Name Name

View File

@ -19,9 +19,13 @@ import {
Button, Button,
ButtonGroup ButtonGroup
} from 'reactstrap'; } from 'reactstrap';
import { Link } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { faLeftLong } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
function Blogs(props) { function Blogs(props) {
let navigate = useNavigate();
const GlobalTheme = props.GlobalTheme; const GlobalTheme = props.GlobalTheme;
const ThemeConfig = props.ThemeConfig; const ThemeConfig = props.ThemeConfig;
@ -65,7 +69,7 @@ function Blogs(props) {
const deleteResource = (id) => { const deleteResource = (id) => {
EditableDataService.deleteData(`/data/category/delete/${id}/`).then(response => { EditableDataService.deleteData(`/data/category/delete/${id}/`).then(response => {
props.notificationToggler('Category delete successfully') props.notificationToggler('Category successfully deleted')
setCategoryData() setCategoryData()
}).catch(error => { }).catch(error => {
props.notificationToggler('Failed to delete category', 'danger'); props.notificationToggler('Failed to delete category', 'danger');
@ -74,10 +78,10 @@ function Blogs(props) {
const addNewCategory = () => { const addNewCategory = () => {
EditableDataService.createData('/data/category/create/', { EditableDataService.createData('/data/category/create/', {
"name": "Enter a blog name", "name": "Enter name",
"featured_blog": "", "featured_blog": "",
"description": "Enter description", "description": "Enter description",
"tagline": "Enter category tagline", "tagline": "Enter tagline",
"cover_image": "" "cover_image": ""
}).then(response => { }).then(response => {
props.notificationToggler('Category created successfully') props.notificationToggler('Category created successfully')
@ -99,9 +103,10 @@ function Blogs(props) {
setCategoryData() setCategoryData()
} }
if (GlobalTheme && ThemeConfig && categoryMetadata.length > 0) { if (GlobalTheme && ThemeConfig) {
return ( return (
<Container fluid className={`p-0 mb-2 ${ThemeConfig[GlobalTheme].background}`}> <Container fluid className={`p-0 mb-2 ${ThemeConfig[GlobalTheme].background}`}>
<Col xs="3" className="d-none d-md-block"><Button color={ThemeConfig[GlobalTheme].buttonColor} onClick={() => navigate(`/`)} className="ms-5" outline><FontAwesomeIcon icon={faLeftLong}/></Button></Col>
<Row className="justify-content-center align-items-center"> <Row className="justify-content-center align-items-center">
<Col className="d-flex flex-column align-items-center"> <Col className="d-flex flex-column align-items-center">
<div className="w-100"> <div className="w-100">

View File

@ -116,7 +116,7 @@ function CardListViewer(props) {
<Link className={`${props.textColor}`} to={`/${props.resourceType}/${itemObject.id}`}> <Link className={`${props.textColor}`} to={`/${props.resourceType}/${itemObject.id}`}>
Open this resource Open this resource
</Link> </Link>
<Button color={props.buttonColor} onClick={() => showModal()} outline className="m-1">Delete</Button> <Button color='danger' onClick={() => showModal()} className="m-2">Delete Blog</Button>
</CardText> </CardText>
</CardBody> </CardBody>
</Card> </Card>
@ -128,13 +128,13 @@ function CardListViewer(props) {
{itemObject.coverImage !== "" ? <CardImg src={MediaService.getMedia(itemObject.coverImage)} style={{ "height": "180px", "objectFit": "cover" }} top width="100%" /> : ""} {itemObject.coverImage !== "" ? <CardImg src={MediaService.getMedia(itemObject.coverImage)} style={{ "height": "180px", "objectFit": "cover" }} top width="100%" /> : ""}
<CardBody> <CardBody>
<Link to={`/${props.resourceType}/${itemObject.id}`}> <Link to={`/${props.resourceType}/${itemObject.id}`}>
<CardTitle className={`${props.textColor}`} tag="h5"> <CardTitle className={`${props.textColor}`} tag="h3">
{itemObject.name} {itemObject.name}
</CardTitle> </CardTitle>
<CardText className={`${props.textColor}`}> <CardText className={`${props.textColor}`} tag="h5">
{itemObject.description} {itemObject.description}
</CardText> </CardText>
<CardText> <CardText tag="h6">
<small className={`${props.textColor}`}> <small className={`${props.textColor}`}>
{itemObject.tagLine} {itemObject.tagLine}
</small> </small>

View File

@ -8,6 +8,7 @@ import Highlight from '@tiptap/extension-highlight'
import TextAlign from '@tiptap/extension-text-align' import TextAlign from '@tiptap/extension-text-align'
import Underline from '@tiptap/extension-underline' import Underline from '@tiptap/extension-underline'
import Blockquote from '@tiptap/extension-blockquote' import Blockquote from '@tiptap/extension-blockquote'
import Image from '@tiptap/extension-image'
import Link from '@tiptap/extension-link' import Link from '@tiptap/extension-link'
import { EditorProvider, useCurrentEditor } from '@tiptap/react' import { EditorProvider, useCurrentEditor } from '@tiptap/react'
import StarterKit from '@tiptap/starter-kit' import StarterKit from '@tiptap/starter-kit'
@ -328,6 +329,12 @@ const extensions = [
}), }),
Underline, Underline,
Blockquote, Blockquote,
Image.configure({
allowBase64: true,
HTMLAttributes: {
class: 'mx-auto d-block',
},
}),
TextAlign.configure({ TextAlign.configure({
types: ['heading', 'paragraph'], types: ['heading', 'paragraph'],
}), }),

View File

@ -11,15 +11,18 @@ import {
Card, Card,
Row, Row,
Col, Col,
CardImg, Button,
CardTitle, CardTitle,
CardText,
CardBody CardBody
} from 'reactstrap'; } from 'reactstrap';
import { Link, useParams } from 'react-router-dom'; import { useParams, useNavigate } from 'react-router-dom';
import { faLeftLong } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
function BlogList(props) { function BlogList(props) {
let navigate = useNavigate();
const { categoryID } = useParams(); const { categoryID } = useParams();
const GlobalTheme = props.GlobalTheme; const GlobalTheme = props.GlobalTheme;
@ -47,7 +50,8 @@ function BlogList(props) {
if (GlobalTheme && ThemeConfig) { if (GlobalTheme && ThemeConfig) {
return ( return (
<Container fluid className={` mb-2 p-0 ${ThemeConfig[GlobalTheme].background}`}> <Container fluid className={` mb-2 p-0 ${ThemeConfig[GlobalTheme].background}`}>
<CategoryBar currentPage={categoryID} GlobalTheme={GlobalTheme} ThemeConfig={ThemeConfig}/> <Col xs="3" className="d-none 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"> <Row className="justify-content-center align-items-center">
<Col className="d-flex flex-column align-items-center"> <Col className="d-flex flex-column align-items-center">
<div className="w-100"> <div className="w-100">

View File

@ -8,10 +8,13 @@ import CategoryBar from './shared/category-bar';
import { import {
Container,Row, Col,Spinner, UncontrolledCollapse, Button, ButtonGroup, Card, CardBody Container,Row, Col,Spinner, UncontrolledCollapse, Button, ButtonGroup, Card, CardBody
} from 'reactstrap'; } from 'reactstrap';
import { Link, useParams } from 'react-router-dom'; import { Link, useParams, useNavigate } from 'react-router-dom';
import { faLeftLong } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
function Blog(props) { function Blog(props) {
let navigate = useNavigate();
const { blogID } = useParams(); const { blogID } = useParams();
const GlobalTheme = props.GlobalTheme; const GlobalTheme = props.GlobalTheme;
@ -56,14 +59,18 @@ function Blog(props) {
if (GlobalTheme && ThemeConfig) { if (GlobalTheme && ThemeConfig) {
return ( return (
<Container fluid className={`${ThemeConfig[GlobalTheme].background}`}> <Container fluid className={`${ThemeConfig[GlobalTheme].background}`}>
<CategoryBar currentPage={blogData.parentCategory} GlobalTheme={GlobalTheme} ThemeConfig={ThemeConfig}/> <Col xs="3" className="d-none 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"> <Row className="mb-4">
<Col className="p-0"> <Col className="p-0">
<img {
src={MediaService.getMedia(blogData.coverImage)} blogData.coverImage !== "" ?
alt="Banner" <img
style={{ width: '100%', height: 'auto', maxHeight: '20vh', objectFit: 'cover' }} src={MediaService.getMedia(blogData.coverImage)}
/> alt="Banner"
style={{ width: '100%', height: 'auto', maxHeight: '20vh', objectFit: 'cover' }}
/>:""
}
</Col> </Col>
</Row> </Row>
<Row className="mr-2 ml-2 mb-2 mt-1 blogContent"> <Row className="mr-2 ml-2 mb-2 mt-1 blogContent">
@ -142,13 +149,13 @@ function Blog(props) {
</Row> </Row>
<Row className="mr-2 ml-2 mt-1"> <Row className="mr-2 ml-2 mt-1">
<Col xs="3" className="d-none d-md-block"></Col> <Col xs="4" className="d-none d-md-block"></Col>
<Col style={{marginBottom: '25px'}}> <Col style={{marginBottom: '25px'}}>
<div className={`blogContent ${ThemeConfig[GlobalTheme].textColor}`}>{blogContent}</div> <div className={`blogContent ${ThemeConfig[GlobalTheme].textColor}`}>{blogContent}</div>
</Col> </Col>
<Col xs="3" className="d-none d-md-block"></Col> <Col xs="4" className="d-none d-md-block"></Col>
</Row> </Row>
</Container> </Container>
); );

View File

@ -15,11 +15,15 @@ import {
CardImg, CardImg,
CardTitle, CardTitle,
CardText, CardText,
CardBody CardBody,
Button
} from 'reactstrap'; } from 'reactstrap';
import { Link } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { faLeftLong } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
function Blogs(props) { function Blogs(props) {
let navigate = useNavigate();
const GlobalTheme = props.GlobalTheme; const GlobalTheme = props.GlobalTheme;
const ThemeConfig = props.ThemeConfig; const ThemeConfig = props.ThemeConfig;
@ -34,10 +38,12 @@ function Blogs(props) {
if (GlobalTheme && ThemeConfig) { if (GlobalTheme && ThemeConfig) {
return ( return (
<Container fluid className={`p-0 mb-2 ${ThemeConfig[GlobalTheme].background}`}> <Container fluid className={`p-0 mb-2 ${ThemeConfig[GlobalTheme].background}`}>
<Row className="justify-content-center align-items-center"> <Row className="justify-content-center align-items-center">
<Col className="d-flex flex-column align-items-center"> <Col className="d-flex flex-column align-items-center">
{/* Top Section - Categories */} {/* Top Section - Categories */}
<div className="w-100"> <div className="w-100">
<Col xs="3" className="d-none 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"}}> <Card className={`my-2 ${ThemeConfig[GlobalTheme].background}`} style={{width: "100%", border: "none"}}>
<CardBody> <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">

View File

@ -39,3 +39,10 @@ a {
border-radius: 10px; border-radius: 10px;
padding: 1em; padding: 1em;
} }
.blogContent img {
display: flex;
justify-content: center; /* Center horizontally */
align-items: center;
width: 50%;
}