diff --git a/package.json b/package.json
index 2d9140b..e91a7f4 100644
--- a/package.json
+++ b/package.json
@@ -28,6 +28,8 @@
"webpack-dev-server": "^5.0.2"
},
"dependencies": {
+ "@ckeditor/ckeditor5-build-classic": "^41.4.2",
+ "@ckeditor/ckeditor5-react": "^7.0.0",
"@fortawesome/fontawesome-svg-core": "^6.5.1",
"@fortawesome/free-solid-svg-icons": "^6.5.1",
"@fortawesome/react-fontawesome": "^0.2.0",
diff --git a/src/assets/styles/articles.module.scss b/src/assets/styles/articles.module.scss
new file mode 100644
index 0000000..ff9f769
--- /dev/null
+++ b/src/assets/styles/articles.module.scss
@@ -0,0 +1,94 @@
+.main {
+ width: 100%;
+ height: 100%;
+ padding: 4% 8%;
+}
+
+.wrapper {
+ width: 100%;
+ height: 100%;
+}
+
+.panel {
+ width: 100%;
+ height: 10%;
+ display: flex;
+ justify-content: space-between;
+ align-items: end;
+ padding: 0 5%;
+}
+
+.listArticles {
+ box-shadow: 0 0 5px 1px rgb(200, 200, 200);
+ border-radius: 5px;
+ margin-top: 5%;
+ height: 70%;
+ width: 100%;
+ &__columns {
+ display: flex;
+ justify-content: start;
+ align-items: center;
+ padding: 0 2%;
+ height: 15%;
+ width: 100%;
+ border-bottom: 1px solid rgb(220, 220, 220);
+ &__item {
+ width: 33.3%;
+ font-size: 15px;
+ // text-align: center;
+ font-family: "Montserrat", sans-serif;
+ }
+ }
+ &__forms {
+ width: 100%;
+ height: 85%;
+ overflow-y: auto;
+ &::-webkit-scrollbar {
+ width: 7px;
+ }
+ &::-webkit-scrollbar-thumb {
+ background-color: rgb(200, 200, 200);
+ }
+ &__item {
+ display: flex;
+ justify-content: start;
+ align-items: center;
+ padding: 0 2%;
+ height: 25%;
+ width: 100%;
+ font-family: "Montserrat", sans-serif;
+ position: relative;
+ &:hover {
+ background-color: rgba(240, 240, 240, 0.8);
+ }
+ &__title {
+ width: 33.3%;
+ // text-align: center;
+ cursor: pointer;
+ &:hover {
+ text-decoration: underline;
+ }
+ }
+ &__date {
+ width: 33.3%;
+ // text-align: center;
+ }
+ &__author {
+ width: 33.3%;
+ // text-align: center;
+ }
+ i {
+ position: absolute;
+ font-size: 15px;
+ right: 30px;
+ top: calc(50% - 7px);
+ cursor: pointer;
+ }
+ ul {
+ li {
+ cursor: pointer;
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/assets/styles/components/myButton.module.scss b/src/assets/styles/components/myButton.module.scss
index 3985573..1f72d0e 100644
--- a/src/assets/styles/components/myButton.module.scss
+++ b/src/assets/styles/components/myButton.module.scss
@@ -24,4 +24,14 @@
border: 1px solid rgba(0, 0, 0, 0.3);
}
}
+ &__transparent {
+ background-color: rgba(0, 0, 0, 0);
+ border: 1px solid rgba(0, 0, 0, 0);
+ transition: 0.3s;
+ &:hover {
+ background-color: rgba(180, 180, 180, 0.5);
+ color: white;
+ transition: 0.3s;
+ }
+ }
}
\ No newline at end of file
diff --git a/src/assets/styles/newArticle.module.scss b/src/assets/styles/newArticle.module.scss
new file mode 100644
index 0000000..f725434
--- /dev/null
+++ b/src/assets/styles/newArticle.module.scss
@@ -0,0 +1,121 @@
+.main {
+ width: 100%;
+ height: 100%;
+ padding: 4% 8%;
+}
+
+.wrapper {
+ width: 100%;
+ height: 100%;
+}
+
+.header {
+ display: flex;
+ justify-content: space-between;
+ width: 100%;
+ height: 8%;
+ &__listInput {
+ width: 40%;
+ height: 100%;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ &__date {
+ width: 100%;
+ height: 100%;
+ display: flex;
+ justify-content: space-between;
+ position: relative;
+ span {
+ position: absolute;
+ font-size: 8px;
+ font-family: "Montserrat", sans-serif;
+ top: -40%;
+ left: 2%;
+ }
+ }
+ &__title {
+ width: 100%;
+ height: 100%;
+ display: flex;
+ justify-content: space-between;
+ position: relative;
+ span {
+ position: absolute;
+ font-size: 8px;
+ font-family: "Montserrat", sans-serif;
+ top: -40%;
+ left: 2%;
+ }
+ }
+ }
+ &__listBtn {
+ display: flex;
+ justify-content: space-between;
+
+ width: 30%;
+ }
+}
+
+.tags {
+ width: 100%;
+ margin: 30px 0;
+ &__wrapper {
+ width: 100%;
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+ align-items: start;
+ &__input {
+ position: relative;
+ &__title {
+ top: -40%;
+ left: 2px;
+ position: absolute;
+ font-size: 8px;
+ font-family: "Montserrat", sans-serif;
+ color: rgb(100, 100, 100);
+ }
+ }
+ &__list {
+ width: 100%;
+ // height: 100%;
+ flex-wrap: wrap;
+ display: flex;
+ justify-content: start;
+ align-items: center;
+ margin-top: 5px;
+ &__item {
+ padding: 0 5px;
+ border-radius: 5px;
+ border: 1px solid rgb(100, 100, 100);
+ margin: 1px 3px;
+ position: relative;
+ span {
+ font-size: 13px;
+ font-family: "Montserrat", sans-serif;
+ color: rgb(100, 100, 100);
+ }
+ &:hover span {
+ visibility: hidden;
+ }
+ &:hover i {
+ visibility: visible;
+ }
+ i {
+ visibility: hidden;
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ color: rgb(239, 73, 73);
+ cursor: pointer;
+ }
+ }
+ }
+ }
+}
+
+.content {
+
+}
\ No newline at end of file
diff --git a/src/assets/styles/viewArticle.module.scss b/src/assets/styles/viewArticle.module.scss
new file mode 100644
index 0000000..1fdc006
--- /dev/null
+++ b/src/assets/styles/viewArticle.module.scss
@@ -0,0 +1,159 @@
+.main {
+ width: 100%;
+ height: 100%;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+}
+
+.wrapper {
+ width: 90%;
+ height: 90%;
+}
+
+.header {
+ width: 100%;
+ height: 10%;
+ &__wrapper {
+ width: 100%;
+ height: 100%;
+ border: 1px solid rgb(180, 180, 180);
+ border-bottom: none;
+ border-radius: 5px 5px 0 0;
+ padding: 0 5px;
+ &__article {
+ width: 100%;
+ height: 100%;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ &__title {
+ width: 20%;
+ height: 75%;
+ display: flex;
+ border-right: 1px solid rgb(180, 180, 180);
+ justify-content: center;
+ align-items: center;
+ position: relative;
+ &__name {
+ position: absolute;
+ top: -20%;
+ left: 3px;
+ font-size: 8px;
+ font-family: "Montserrat", sans-serif;
+ }
+ &__text {
+ font-size: 15px;
+ font-family: "Montserrat", sans-serif;
+ }
+ }
+ &__tags {
+ width: 60%;
+ height: 75%;
+ display: flex;
+ justify-content: start;
+ align-items: center;
+ flex-wrap: wrap;
+ padding: 0 5px;
+ position: relative;
+ overflow-y: auto;
+ // &::-webkit-scrollbar {
+ // width: 10px;
+ // }
+ // &::-webkit-scrollbar-thumb {
+ // background-color: rgb(200, 200, 200);
+ // }
+ // &::-webkit-scrollbar-button:single-button {
+ // background-color: #bbbbbb;
+ // display: block;
+ // border-style: solid;
+ // height: 10px;
+ // width: 16px;
+ // }
+ &__item {
+ padding: 0 5px;
+ border-radius: 5px;
+ border: 1px solid rgb(100, 100, 100);
+ margin: 1px 3px;
+ span {
+
+ }
+ }
+ }
+ &__owner {
+ width: 20%;
+ height: 75%;
+ border-left: 1px solid rgb(180, 180, 180);
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ position: relative;
+ &__name {
+ position: absolute;
+ top: -20%;
+ right: 3px;
+ font-size: 8px;
+ font-family: "Montserrat", sans-serif;
+ }
+ &__text {
+ font-size: 15px;
+ font-family: "Montserrat", sans-serif;
+ }
+ }
+ }
+ &__tags {
+ width: 100%;
+ height: 50%;
+ display: flex;
+ justify-content: start;
+ align-items: center;
+ margin-top: 20px;
+ &__item {
+ padding: 0 5px;
+ border-radius: 5px;
+ border: 1px solid rgb(100, 100, 100);
+ margin: 1px 3px;
+ span {
+
+ }
+ }
+ }
+ }
+}
+
+.content {
+ width: 100%;
+ height: 90%;
+ &__wrapper {
+ width: 100%;
+ height: 100%;
+ border: 1px solid rgb(180, 180, 180);
+ padding: 5px;
+ // border-radius: 0 0 5px 5px;
+ overflow: auto;
+ &::-webkit-scrollbar {
+ width: 7px;
+ }
+ &::-webkit-scrollbar {
+ height: 7px;
+ }
+ &::-webkit-scrollbar-thumb {
+ background-color: rgb(200, 200, 200);
+ }
+ &__article {
+ max-width: 100%;
+ height: 100%;
+ }
+ }
+}
+
+// .image {
+// width: 400px;
+// height: 200px;
+// border: 4px solid red
+// img {
+// width: 400px;
+// height: 200px;
+// aspect-ratio: 1000/700;
+// }
+// }
\ No newline at end of file
diff --git a/src/components/MyButton.jsx b/src/components/MyButton.jsx
index b9b60c2..bab86a0 100644
--- a/src/components/MyButton.jsx
+++ b/src/components/MyButton.jsx
@@ -4,7 +4,7 @@ import classes from "../assets/styles/components/myButton.module.scss"
const MyButton = (props) => {
return (
-
+
+ )
+}
+
+export default Articles;
\ No newline at end of file
diff --git a/src/pages/NewArticle.jsx b/src/pages/NewArticle.jsx
new file mode 100644
index 0000000..44cf081
--- /dev/null
+++ b/src/pages/NewArticle.jsx
@@ -0,0 +1,143 @@
+import React, { useState, useContext, useEffect } from "react";
+import { useNavigate, useLocation, useParams } from 'react-router-dom';
+import { useCookies } from "react-cookie";
+import classes from "../assets/styles/newArticle.module.scss";
+import MyButton from "../components/MyButton.jsx";
+import Loading from "../components/Loading.jsx";
+import MyInput from "../components/MyInput.jsx";
+import TextEditor from "../components/TextEditor.jsx";
+import { getArticleApi, editTagsApi, editTitleArticleApi, editArticleApi, addArticleApi } from "../hooks/api/articleApi.js";
+
+const NewArticle = () => {
+ const navigate = useNavigate();
+ const location = useLocation();
+ const { articleId } = useParams();
+ const [loading, setLoading] = useState(false);
+
+ const [title, setTitle] = useState("");
+ const [tags, setTags] = useState([]);
+ const [newTag, setNewTag] = useState("")
+ const [ownerId, setOwnerId] = useState("");
+
+ const [contentArticle, setContentArticle] = useState('');
+ const [blocks, setBlocks] = useState('');
+
+ const [cookies, _, __] = useCookies(["user"]);
+
+ useEffect(() => {
+ async function getArticle() {
+ const response = await getArticleApi(cookies.token, articleId)
+
+ if (response.status === 200) {
+ console.log(response)
+ setTitle(response.data.article.title)
+ setTags(response.data.article.tags ? response.data.article.tags : [])
+ setOwnerId(response.data.article.owner_id)
+ setContentArticle(response.data.blocks ? response.data.blocks[0].data : '')
+ setBlocks(response.data.blocks ? response.data.blocks : false)
+ }
+ }
+
+ getArticle()
+ }, [])
+
+ async function addTag() {
+ if (newTag.length > 0 && !tags.find(item => item === newTag)) {
+ const response = await editTagsApi(cookies.token, articleId, [newTag], false)
+
+ if (response.status === 200) {
+ setTags([...tags, newTag])
+ setNewTag("")
+ }
+ }
+ }
+
+ async function removeTag(item, index) {
+ const response = await editTagsApi(cookies.token, articleId, [item], true)
+
+ if (response.status === 200) {
+ const cTags = [...tags]
+ cTags.splice(index, 1)
+ setTags(cTags)
+ }
+ }
+
+ async function saveArticle() {
+ const responseTitle = await editTitleArticleApi(cookies.token, articleId, title)
+ console.log(blocks)
+ const responseContentArticle = blocks ?
+ await editArticleApi(cookies.token, articleId, contentArticle) :
+ await addArticleApi(cookies.token, articleId, contentArticle)
+
+ console.log(responseContentArticle)
+
+ if (responseTitle.status === 200 && responseContentArticle.status === 200) {
+ navigate("/articles")
+ }
+ }
+
+ return (
+
+
+
+
+
+ Дата создания
+
+
+
+ Название статьи
+
+
+
+
+ {/* */}
+ saveArticle()}/>
+
+
+
+
+
+ Добавить тэг
+
+ }
+ class={"main__transparent"}
+ mainStyle={
+ {
+ position: "absolute",
+ right: "0",
+ top: "0",
+ height: "100%",
+ }
+ }
+ otherStyle={{borderRadius: "0 5px 5px 0", padding: "3px 7px 0 7px"}}
+ click={() => addTag()}
+ />
+
+
+ {tags ? tags.map((item, i) =>
+
+ #{item}
+ removeTag(item, i)}>
+
+ ) : <>>}
+
+
+
+
+
+
+
+
+ )
+}
+
+export default NewArticle;
\ No newline at end of file
diff --git a/src/pages/ViewArticle.jsx b/src/pages/ViewArticle.jsx
new file mode 100644
index 0000000..1494db0
--- /dev/null
+++ b/src/pages/ViewArticle.jsx
@@ -0,0 +1,78 @@
+import React, { useState, useContext, useEffect, useLocation } from "react";
+import { useNavigate, useParams } from "react-router-dom";
+import { useCookies } from "react-cookie";
+import classes from "../assets/styles/viewArticle.module.scss";
+import { FormsData, TypeAnswerData, answersData, UserData } from "../context";
+import MyButton from "../components/MyButton.jsx";
+import { getArticleApi } from "../hooks/api/articleApi.js";
+import { getListUserApi } from "../hooks/api/profileApi.js";
+
+
+const ViewArticle = () => {
+ const navigate = useNavigate();
+ // const location = useLocation();
+ const { articleId } = useParams();
+
+ const [title, setTitle] = useState("");
+ const [tags, setTags] = useState([]);
+ const [newTag, setNewTag] = useState("")
+ const [owner, setOwner] = useState("");
+
+ const [contentArticle, setContentArticle] = useState('');
+
+ const [cookies, _, __] = useCookies(["user"]);
+
+ useEffect(() => {
+ async function getArticle() {
+ const response = await getArticleApi(cookies.token, articleId)
+ const user = await getListUserApi(cookies.token)
+
+ if (response.status === 200) {
+ console.log(response)
+ setTitle(response.data.article.title)
+ setTags(response.data.article.tags ? response.data.article.tags : [])
+ setOwner(user.status === 200 ? user.data.find(item => item.id === response.data.article.owner_id).login : response.data.article.owner_id)
+ setContentArticle(response.data.blocks ? response.data.blocks[0].data : '')
+ }
+ }
+
+ getArticle()
+ }, [])
+
+ return (
+
+
+
+
+
+
+ Название
+ {title}
+
+
+ {/*
Тэги */}
+ {tags.map(item =>
+ #{item}
+
)}
+
+
+ Автор
+ {owner}
+
+
+
+
+
+
+
+ )
+}
+
+
+export default ViewArticle
\ No newline at end of file
diff --git a/src/router/router.js b/src/router/router.js
index cbc8b09..2c2c3ff 100644
--- a/src/router/router.js
+++ b/src/router/router.js
@@ -10,6 +10,9 @@ import ViewForm from "../pages/ViewForm.jsx";
import AdminPanel from '../pages/AdminPanel.jsx';
import AnswersForm from '../pages/AnswersForm.jsx';
import TokensForm from '../pages/TokensForm.jsx';
+import Articles from '../pages/Articles.jsx';
+import NewArticle from '../pages/NewArticle.jsx';
+import ViewArticle from '../pages/ViewArticle.jsx';
const router = createBrowserRouter([
{
@@ -50,6 +53,18 @@ const router = createBrowserRouter([
{
path: "/tokens/:formId",
element:
+ },
+ {
+ path: "/articles",
+ element:
+ },
+ {
+ path: "/articles/:articleId/edit",
+ element:
+ },
+ {
+ path: "/articles/:articleId/",
+ element:
}
]
}