Added preliminary image support and improved functionality around application
This commit is contained in:
parent
2d8cc1e7de
commit
d450ac3cb4
@ -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
|
||||||
|
|||||||
@ -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'),
|
||||||
]
|
]
|
||||||
56
frontend/package-lock.json
generated
56
frontend/package-lock.json
generated
@ -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",
|
||||||
|
|||||||
@ -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",
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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">
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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'
|
||||||
@ -18,7 +19,7 @@ import { faBold, faItalic,
|
|||||||
faAlignJustify, faHighlighter,
|
faAlignJustify, faHighlighter,
|
||||||
faStrikethrough, faCode,
|
faStrikethrough, faCode,
|
||||||
faListUl, faLink,
|
faListUl, faLink,
|
||||||
faListOl, faQuoteLeft,
|
faListOl, faQuoteLeft,
|
||||||
faQuoteRight, faRulerHorizontal,
|
faQuoteRight, faRulerHorizontal,
|
||||||
faRotateLeft, faRotateRight } from '@fortawesome/free-solid-svg-icons';
|
faRotateLeft, faRotateRight } from '@fortawesome/free-solid-svg-icons';
|
||||||
|
|
||||||
@ -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'],
|
||||||
}),
|
}),
|
||||||
|
|||||||
@ -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">
|
||||||
|
|||||||
@ -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>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -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">
|
||||||
|
|||||||
@ -38,4 +38,11 @@ a {
|
|||||||
border: solid grey;
|
border: solid grey;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
padding: 1em;
|
padding: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blogContent img {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center; /* Center horizontally */
|
||||||
|
align-items: center;
|
||||||
|
width: 50%;
|
||||||
}
|
}
|
||||||
@ -13,4 +13,4 @@ export default defineConfig({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
Loading…
Reference in New Issue
Block a user