diff --git a/src/components/books/bookInfo.jsx b/src/components/books/bookInfo.jsx
index 0bcf090..31e9e90 100644
--- a/src/components/books/bookInfo.jsx
+++ b/src/components/books/bookInfo.jsx
@@ -1,5 +1,5 @@
// 3rd party imports
-import { Space, Typography } from "antd";
+import { Divider, Progress, Space, Typography } from "antd";
// Local imports
import helpers from "../../helpers";
@@ -10,6 +10,7 @@ import { IconText } from "../common/iconText";
import BookStatusIcon from './BookStatusIcon';
import { AiOutlineCopy } from "react-icons/ai";
import { FiLayers } from "react-icons/fi";
+import EditingStatusIcon from "../editingStatusIcon";
// -----------------------------------------
const { Paragraph } = Typography;
// ---------------------------------------------
@@ -44,6 +45,17 @@ const BookInfo = ({ libraryId, book, t }) => {
+
+
+ {book.pageStatus.map(s => (
+
+
+
+
+ ))}
>
);
diff --git a/src/components/books/files/fileDeleteButton.jsx b/src/components/books/files/fileDeleteButton.jsx
new file mode 100644
index 0000000..655df21
--- /dev/null
+++ b/src/components/books/files/fileDeleteButton.jsx
@@ -0,0 +1,48 @@
+// Third party libraries
+import { App, Button, Modal, Tooltip } from "antd";
+import { FaTrash } from "react-icons/fa";
+import { ExclamationCircleFilled } from "@ant-design/icons";
+// Local imports
+import { useDeleteBookContentMutation } from "../../../features/api/booksSlice";
+
+// ------------------------------------------------------
+
+const { confirm } = Modal;
+
+// ------------------------------------------------------
+export default function FileDeleteButton({ content, t, type }) {
+ const { message } = App.useApp();
+ const [deleteBookContent, { isLoading: isDeleting }] = useDeleteBookContentMutation();
+
+ const showConfirm = () => {
+ confirm({
+ title: t("book.actions.deleteFile.title"),
+ icon: ,
+ content: t("book.actions.deleteFile.message", {
+ title: content.fileName,
+ }),
+ okButtonProps: { loading: isDeleting },
+ okType: "danger",
+ cancelButtonProps: { disabled: isDeleting },
+ closable: { isDeleting },
+ onOk() {
+ if (content && content.links && content.links.delete) {
+ return deleteBookContent({ content }).unwrap()
+ .then(() => message.success(t("chapter.actions.delete.success")))
+ .catch((_) => message.error(t("chapter.actions.delete.error"))
+ );
+ }
+ }
+ });
+ };
+
+ return (
+
+ }
+ />
+
+ );
+}
diff --git a/src/components/books/files/fileListItem.jsx b/src/components/books/files/fileListItem.jsx
new file mode 100644
index 0000000..5f25b66
--- /dev/null
+++ b/src/components/books/files/fileListItem.jsx
@@ -0,0 +1,82 @@
+import { Link } from "react-router-dom";
+
+// 3rd Party Libraries
+import { Avatar, Button, List, Tooltip, Typography, Upload } from "antd";
+import { FaFileDownload, FaFileUpload } from "react-icons/fa";
+
+// Local Import
+import { useUpdateBookContentMutation } from "../../../features/api/booksSlice";
+import FileDeleteButton from "./fileDeleteButton";
+import FileTypeIcon from "./fileTypeIcon";
+
+// ------------------------------------------------------
+
+function FileListItem({
+ libraryId,
+ bookId,
+ content,
+ t,
+ message
+}) {
+ const [updateBookContent, { isLoading: isUpdating }] = useUpdateBookContentMutation();
+
+ const title = (
+
+ {content.fileName}
+
+ );
+
+ const uploadFile = (file) => {
+ const isAllowed = ["application/pdf", "application/msword", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"].includes(file.type);
+ if (!isAllowed) {
+ message.error(t("errors.imageRequired"));
+ return;
+ }
+
+ updateBookContent({ content: content, payload: file }).unwrap()
+ .then(() => message.success(t("book.actions.addFile.success")))
+ .catch((_) => message.error(t("book.actions.addFile.error")));
+ }
+
+ return (
+
+ } disabled={isUpdating} />
+
+
+ ),
+ content && content.links.download && (
+
+
+ } />
+
+
+ ),
+ content && content.links.delete && (
+
+ ),
+ ]}
+ >
+
+
+
+ }
+ />
+
+ );
+}
+
+export default FileListItem;
diff --git a/src/components/books/files/fileTypeIcon.jsx b/src/components/books/files/fileTypeIcon.jsx
new file mode 100644
index 0000000..9621b39
--- /dev/null
+++ b/src/components/books/files/fileTypeIcon.jsx
@@ -0,0 +1,15 @@
+import { FileOutlined, FilePdfOutlined, FileWordOutlined } from "@ant-design/icons";
+
+const FileTypeIcon = ({ type }) => {
+ switch (type) {
+ case 'application/pdf':
+ return ()
+ case "application/msword":
+ case "application/vnd.openxmlformats-officedocument.wordprocessingml.document":
+ return ();
+ default:
+ return ()
+ }
+};
+
+export default FileTypeIcon;
diff --git a/src/components/books/files/filesList.jsx b/src/components/books/files/filesList.jsx
index 3428eff..911e00c 100644
--- a/src/components/books/files/filesList.jsx
+++ b/src/components/books/files/filesList.jsx
@@ -1,142 +1,66 @@
// 3rd party libraries
-import { App, Button, Col, List, Row, Skeleton, Typography } from "antd";
-import { FaBook } from "react-icons/fa";
-import { DragDropContext, Droppable } from "react-beautiful-dnd";
+import { App, Button, List, Upload } from "antd";
+import { FaBook, FaFileUpload } from "react-icons/fa";
// Internal Imports
-import {
- useGetBookChaptersQuery,
- useUpdateChapterSequenceMutation,
-} from "../../../features/api/booksSlice";
-import ChapterListItem from "../chapters/chapterListItem";
-import ChapterEditor from "../chapters/chapterEditor";
+import { useAddBookContentMutation } from "../../../features/api/booksSlice";
+import FileListItem from "../files/fileListItem";
import DataContainer from "../../layout/dataContainer";
-
-// ------------------------------------------------------
+// ----------------------------------------------
const FilesList = ({
libraryId,
- bookId,
+ book,
t,
- selectedChapterNumber = null,
- size = "default",
- hideTitle = false,
+ size = "default"
}) => {
const { message } = App.useApp();
- const {
- refetch,
- data: chapters,
- error,
- isFetching,
- } = useGetBookChaptersQuery(
- { libraryId, bookId },
- { skip: !libraryId || !bookId }
- );
- const [updateChapterSequence, { isLoading: isUpdating }] =
- useUpdateChapterSequenceMutation();
-
- const title = hideTitle ? null :
{t("book.chapters.title")}
;
-
- if (isFetching) return ;
-
- const onDragDrop = (result) => {
- const fromIndex = result.source.index;
- const toIndex = result.destination.index;
- let payload = [...chapters.data];
- if (fromIndex !== toIndex) {
- const element = payload[fromIndex];
- payload.splice(fromIndex, 1);
- payload.splice(toIndex, 0, element);
+ const [addBookContent, { isLoading: isAdding }] = useAddBookContentMutation();
- payload = payload.map((item, index) => ({
- id: item.id,
- chapterNumber: index + 1,
- }));
-
- return updateChapterSequence({ libraryId, bookId, payload })
- .unwrap()
- .then(() =>
- message.success(t("chapter.actions.reorder.success"))
- )
- .catch((_) =>
- message.error(t("chapter.actions.reorder.error"))
- );
+ const uploadFile = (file) => {
+ const isAllowed = ["application/pdf", "application/msword", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"].includes(file.type);
+ if (!isAllowed) {
+ message.error(t("errors.imageRequired"));
+ return;
}
- };
- const header = (
-
-
- {title}
-
-
-
-
-
- );
+ addBookContent({ book: book, payload: file }).unwrap()
+ .then(() => message.success(t("book.actions.addFile.success")))
+ .catch((_) => message.error(t("book.actions.addFile.error")));
+ }
+
return (
<>
- {t("actions.retry")}
-
- }
+ busy={isAdding}
emptyImage={}
- emptyDescription={t("chapters.empty.title")}
+ emptyDescription={t("book.empty.title")}
emptyContent={
-
+
+
+
}
- empty={chapters && chapters.data && chapters.data.length < 1}
+ empty={!book || (book.contents && book.contents.length < 1)}
bordered={false}
- >
-
-
- {(provided) => (
-
- (
-
- )}
- >
- {provided.placeholder}
-
-
+ >
+
+
+ }
+ renderItem={(c) => (
+
)}
-
-
+ >
+
>
);
diff --git a/src/components/common/iconText.jsx b/src/components/common/iconText.jsx
index 5e548ca..8ebf43a 100644
--- a/src/components/common/iconText.jsx
+++ b/src/components/common/iconText.jsx
@@ -1,14 +1,15 @@
import React from 'react';
// 3rd party
-import { Space } from 'antd';
+import { Space, Typography } from 'antd';
// ------------------------------------------------
-export function IconText({ icon, text, link = true ,onClick = () => {} }) {
+export function IconText({ icon, text, secondaryText = null, link = true ,onClick = () => {} }) {
return (
{React.createElement(icon)}
{text}
+ {secondaryText && {secondaryText}}
);
}
diff --git a/src/components/editingStatusIcon.jsx b/src/components/editingStatusIcon.jsx
index d9229fb..cf66754 100644
--- a/src/components/editingStatusIcon.jsx
+++ b/src/components/editingStatusIcon.jsx
@@ -4,24 +4,25 @@ import {
FaFileAlt,
FaCheck,
FaGlasses,
+ FaRegFile,
} from "react-icons/fa";
// ------------------------------------------------------
-const EditingStatusIcon = ({ status, style }) => {
+const EditingStatusIcon = ({ status, style, render = true }) => {
switch (status) {
case "Available":
- return ;
+ return render ? : FaFile;
case "Typing":
- return ;
+ return render ? : FaFileSignature;
case "Typed":
- return ;
+ return render ? : FaFileAlt;
case "InReview":
- return ;
+ return render ? : FaGlasses;
case "Completed":
- return ;
+ return render ? : FaCheck;
default:
- return ;
+ return render ? : FaRegFile;
}
};
diff --git a/src/features/api/booksSlice.js b/src/features/api/booksSlice.js
index 9f047ce..e4cd803 100644
--- a/src/features/api/booksSlice.js
+++ b/src/features/api/booksSlice.js
@@ -97,7 +97,7 @@ export const booksApi = createApi({
getBook: builder.query({
query: ({ libraryId, bookId }) => ({ url: `/libraries/${libraryId}/books/${bookId}`, method: 'get' }),
transformResponse: (response) => parseResponse(response),
- providesTags: [ 'Books' ]
+ providesTags: [ 'Book' ]
}),
getBookChapters: builder.query({
query: ({ libraryId, bookId }) => ({ url: `/libraries/${libraryId}/books/${bookId}/chapters`, method: 'get' }),
@@ -128,14 +128,14 @@ export const booksApi = createApi({
method: 'PUT',
payload: removeLinks(payload)
}),
- invalidatesTags: [ 'Books' ]
+ invalidatesTags: [ 'Books', 'Book' ]
}),
deleteBook: builder.mutation({
query: ({ libraryId, bookId }) => ({
url: `/libraries/${libraryId}/books/${bookId}`,
method: 'DELETE'
}),
- invalidatesTags: [ 'Books' ]
+ invalidatesTags: [ 'Books', 'Book' ]
}),
updateBookImage: builder.mutation({
query: ({ libraryId, bookId, payload }) => {
@@ -151,7 +151,7 @@ export const booksApi = createApi({
}
});
},
- invalidatesTags: [ 'Books' ]
+ invalidatesTags: [ 'Book' ]
}),
addChapter: builder.mutation({
query: ({ libraryId, bookId, payload }) => ({
@@ -289,7 +289,45 @@ export const booksApi = createApi({
},
invalidatesTags: [ 'BookPages' ]
}),
-
+ addBookContent: builder.mutation({
+ query: ({ book, payload }) => {
+ const formData = new FormData();
+ formData.append('file', payload, payload.fileName);
+ return ({
+ url: book.links.add_file,
+ method: 'POST',
+ payload: formData,
+ formData: true,
+ headers: {
+ 'content-type': 'multipart/form-data'
+ }
+ });
+ },
+ invalidatesTags: [ 'Book' ]
+ }),
+ updateBookContent: builder.mutation({
+ query: ({ content, payload }) => {
+ const formData = new FormData();
+ formData.append('file', payload, payload.fileName);
+ return ({
+ url: content.links.update,
+ method: 'PUT',
+ payload: formData,
+ formData: true,
+ headers: {
+ 'content-type': 'multipart/form-data'
+ }
+ });
+ },
+ invalidatesTags: [ 'Book' ]
+ }),
+ deleteBookContent: builder.mutation({
+ query: ({ content }) => ({
+ url: content.links.delete,
+ method: 'DELETE',
+ }),
+ invalidatesTags: [ 'Book' ]
+ }),
}),
})
@@ -320,4 +358,7 @@ export const {
useUpdateBookPageImageMutation,
useUpdateBookPageSequenceMutation,
useCreateBookPageWithImageMutation,
+ useAddBookContentMutation,
+ useUpdateBookContentMutation,
+ useDeleteBookContentMutation,
} = booksApi
diff --git a/src/i18n/en.js b/src/i18n/en.js
index 1e7d55f..fe04c7f 100644
--- a/src/i18n/en.js
+++ b/src/i18n/en.js
@@ -355,8 +355,29 @@ const en = {
"message": "Are you sure you want to delete book '{{title}}'? It will remove all of its contents including chapters, pages and files.",
"success" :"Book deleted successfully.",
"error" :"Error deleting book."
+ },
+ "addFile" : {
+ "title" : "Upload file",
+ "success" :"File uploadedsuccessfully.",
+ "error" :"Error uploading file."
+ },
+ "deleteFile": {
+ "title": "Delete file?",
+ "message": "Are you sure you want to delete book '{{title}}'?.",
+ },
+ "downloadFile": {
+ "title" : "Download file"
}
- }
+ },
+ "errors": {
+ "loading": {
+ "title": "Error",
+ "subTitle": "Unable to load book.",
+ }
+ },
+ "empty" : {
+ "title" : "No latest book found",
+ },
},
"chapters" : {
"title": "Chapters",
@@ -1015,7 +1036,8 @@ const en = {
"Typing": "Being Typed",
"Typed": "Typed",
"InReview" : "In Review",
- "Completed" : "Completed"
+ "Completed" : "Completed",
+ "All": "All"
},
"sort": {
"ascending": "Ascending",
diff --git a/src/i18n/ur.js b/src/i18n/ur.js
index 3c7846e..f26756a 100644
--- a/src/i18n/ur.js
+++ b/src/i18n/ur.js
@@ -334,7 +334,7 @@ const ur = {
"title": "ابواب"
},
"pages": {
- "title" : "صفحات",
+ "title" : "صفحات"
},
"files": {
"title" : "دستاویزات",
@@ -356,9 +356,30 @@ const ur = {
"message": "کیا آپ کتاب '{{title}}' کو حذف کرنا چاہیں گے؟ ایسا کرنے سے اس کتاب سے متعلق تمام مواد بشمول ابواب، صفحات اور تمام دستاویزات حذف ہو جائیں گی۔",
"success" :"کتاب حذف کر دی گئی ہے۔",
"error" :"کتاب حذف کرنے میں ناکامی ہوئی۔"
+ },
+ "addFile" : {
+ "title" : "فائل اپلوڈ کریں",
+ "success" :"فائل کامیابی سے اپلوڈ کر دی گئی ہے۔",
+ "error" :"فائل اپلوڈ کرنے میں نامامی ہوئی۔"
+ },
+ "deleteFile": {
+ "title": "فایئل حذف کریں؟",
+ "message": "کیا آپ '{{title}}' فائل کو حذف کرنا چاہیں گے؟",
+ },
+ "downloadFile": {
+ "title" : "فائل ڈائنلوڈ"
}
- }
- },
+ },
+ "errors": {
+ "loading": {
+ "title": "Error",
+ "subTitle": "Unable to load book.",
+ }
+ },
+ "empty" : {
+ "title" : "No latest book found",
+ },
+ },
"chapters" : {
"title": "ابواب",
"errors": {
@@ -1007,7 +1028,8 @@ const ur = {
"Typing": "ٹائپنگ",
"Typed": "ٹائپنگ مکمل",
"InReview" : "پروف خوانی",
- "Completed" : "مکمل"
+ "Completed" : "مکمل",
+ "All" : "تمام"
},
"layouts" : {
"normal" : {
diff --git a/src/pages/articles/article.jsx b/src/pages/articles/article.jsx
index 514a69f..cbf31da 100644
--- a/src/pages/articles/article.jsx
+++ b/src/pages/articles/article.jsx
@@ -17,6 +17,7 @@ import PageHeader from "../../components/layout/pageHeader";
import Loading from "../../components/common/loader";
import AuthorAvatar from "../../components/author/authorAvatar";
import ArticleDeleteButton from "../../components/articles/articleDeleteButton";
+import ReactMarkdown from "react-markdown";
const ArticlePage = () => {
const navigate = useNavigate();
@@ -95,7 +96,7 @@ const ArticlePage = () => {
}>
- {articleContents?.text}
+
>);