Skip to content
Closed

Api3 #1413

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
3 changes: 3 additions & 0 deletions .prettierrc.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
tabWidth: 4
semi: false
singleQuote: true
12 changes: 12 additions & 0 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import globals from 'globals'
import pluginJs from '@eslint/js'
import config from 'eslint-config-prettier'
import plugin from 'eslint-plugin-prettier/recommended'

/** @type {import('eslint').Linter.Config[]} */
export default [
{ languageOptions: { globals: globals.browser } },
pluginJs.configs.recommended,
config,
plugin,
]
126 changes: 61 additions & 65 deletions index.html
Original file line number Diff line number Diff line change
@@ -1,71 +1,67 @@
<!DOCTYPE html>
<!doctype html>
<html>
<head>
<title>Проект "Комменты"</title>
<meta charset="utf-8" />
<link rel="stylesheet" href="styles.css" />
</head>
<head>
<title>Проект "Комменты"</title>
<meta charset="utf-8" />
<link rel="stylesheet" href="styles.css" />
</head>

<body>
<div class="container">
<ul class="comments">
<li class="comment">
<div class="comment-header">
<div>Глеб Фокин</div>
<div>12.02.22 12:18</div>
</div>
<div class="comment-body">
<div class="comment-text">
Это будет первый комментарий на этой странице
<body>
<div class="container">
<ul class="comments">
<li class="comment">
<div class="comment-header">
<div>Глеб Фокин</div>
<div>12.02.22 12:18</div>
</div>
<div class="comment-body">
<div class="comment-text">
Это будет первый комментарий на этой странице
</div>
</div>
<div class="comment-footer">
<div class="likes">
<span class="likes-counter">3</span>
<button class="like-button"></button>
</div>
</div>
</li>
<li class="comment">
<div class="comment-header">
<div>Варвара Н.</div>
<div>13.02.22 19:22</div>
</div>
<div class="comment-body">
<div class="comment-text">
Мне нравится как оформлена эта страница! ❤
</div>
</div>
<div class="comment-footer">
<div class="likes">
<span class="likes-counter">75</span>
<button class="like-button -active-like"></button>
</div>
</div>
</li>
</ul>
<div class="add-form">
<input
type="text"
class="add-form-name"
placeholder="Введите ваше имя"
/>
<textarea
type="textarea"
class="add-form-text"
placeholder="Введите ваш коментарий"
rows="4"
></textarea>
<div class="add-form-row">
<button class="add-form-button">Написать</button>
</div>
</div>
</div>
<div class="comment-footer">
<div class="likes">
<span class="likes-counter">3</span>
<button class="like-button"></button>
</div>
</div>
</li>
<li class="comment">
<div class="comment-header">
<div>Варвара Н.</div>
<div>13.02.22 19:22</div>
</div>
<div class="comment-body">
<div class="comment-text">
Мне нравится как оформлена эта страница! ❤
</div>
</div>
<div class="comment-footer">
<div class="likes">
<span class="likes-counter">75</span>
<button class="like-button -active-like"></button>
</div>
</div>
</li>
</ul>
<div class="add-form">
<input
type="text"
class="add-form-name"
placeholder="Введите ваше имя"
/>
<textarea
type="textarea"
class="add-form-text"
placeholder="Введите ваш коментарий"
rows="4"
></textarea>
<div class="add-form-row">
<button class="add-form-button">Написать</button>
</div>
</div>
</div>
</body>
</body>

<script>
"use strict";
// Код писать здесь
console.log("It works!");
</script>
<script type="module" src="script.js"></script>
</html>
64 changes: 64 additions & 0 deletions module/addEventHandlers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { comments } from './comments.js'
import { escapeHtml } from './escape.js'
import { renderComments } from './renderComments.js'

const textInput = document.querySelector('.add-form-text')

function delay(interval = 300) {
return new Promise((resolve) => {
setTimeout(() => {
resolve()
}, interval)
})
}

function addEventHandlers() {
const likeButtons = document.querySelectorAll('.like-button')
const commentElements = document.querySelectorAll('.comment')

likeButtons.forEach((button) => {
button.addEventListener('click', (event) => {
event.stopPropagation()

const commentElement = event.target.closest('.comment')
const commentId = parseInt(commentElement.dataset.id)
const comment = comments.find((c) => c.id === commentId)

if (comment.isLikeLoading) {
return
}

button.classList.add('-loading-like')

comment.isLikeLoading = true

delay(2000).then(() => {
comment.likes = comment.isLiked
? comment.likes - 1
: comment.likes + 1
comment.isLiked = !comment.isLiked
comment.isLikeLoading = false

button.classList.remove('-loading-like')

renderComments()
})
})
})

commentElements.forEach((commentElement) => {
commentElement.addEventListener('click', (event) => {
if (event.target.closest('.like-button')) {
return
}

const commentId = parseInt(commentElement.dataset.id)
const comment = comments.find((c) => c.id === commentId)

textInput.value = `> ${escapeHtml(comment.name)}\n\n${escapeHtml(comment.text)}\n\n`
textInput.focus()
})
})
}

export { addEventHandlers }
178 changes: 178 additions & 0 deletions module/api.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
const host = 'https://wedev-api.sky.pro/api/v1/kristina-boykova'

const fetchWithRetry = (url, options = {}, retries = 3, delay = 1000) => {
return new Promise((resolve, reject) => {
const attempt = (remainingRetries) => {
fetch(url, options)
.then((response) => {
if (response.status === 500 && remainingRetries > 0) {
setTimeout(() => {
console.log(
`Повторная попытка запроса, осталось попыток: ${remainingRetries - 1}`,
)
attempt(remainingRetries - 1)
}, delay)
} else {
resolve(response)
}
})
.catch((error) => {
if (remainingRetries > 0) {
setTimeout(() => {
console.log(
`Повторная попытка запроса после сетевой ошибки, осталось попыток: ${remainingRetries - 1}`,
)
attempt(remainingRetries - 1)
}, delay)
} else {
reject(error)
}
})
}

attempt(retries)
})
}

export const fetchComments = () => {
return fetchWithRetry(host + '/comments')
.then((response) => {
if (response.status === 500) {
return Promise.reject(
new Error(
'Серверная ошибка. Пожалуйста, попробуйте позже.',
),
)
}
if (!response.ok) {
return Promise.reject(
new Error(`Ошибка сервера: ${response.status}`),
)
}
return response.json()
})
.then((data) => {
console.log(data)
const appComments = data.comments.map((comment) => {
return {
id: comment.id,
name: comment.author.name,
date: new Date(comment.date).toLocaleString(),
text: comment.text,
likes: comment.likes,
isLiked: false,
}
})
return appComments
})
.catch((error) => {
if (
(error.message &&
(error.message.includes('Failed to fetch') ||
error.message.includes('NetworkError'))) ||
!navigator.onLine
) {
return Promise.reject(
new Error(
'Проблемы с интернетом. Проверьте подключение и попробуйте снова.',
),
)
}

if (error instanceof Error) {
return Promise.reject(error)
}

return Promise.reject(
new Error(
'Произошла неизвестная ошибка. Пожалуйста, попробуйте позже.',
),
)
})
}

export const postComment = (text, name, forceError = false) => {
if (!text || text.trim().length < 5) {
return Promise.reject(
new Error(
'Текст комментария слишком короткий. Минимальная длина - 5 символов',
),
)
}

if (!name || name.trim().length < 3) {
return Promise.reject(
new Error('Имя слишком короткое. Минимальная длина - 3 символа'),
)
}

const requestData = {
text: text.trim(),
name: name.trim(),
}

if (forceError) {
requestData.forceError = true
}

return fetchWithRetry(host + '/comments', {
method: 'POST',
body: JSON.stringify(requestData),
})
.then((response) => {
if (response.status === 400) {
return response.json().then((errorData) => {
return Promise.reject(
new Error(errorData.error || 'Ошибка валидации данных'),
)
})
}
if (response.status === 500) {
return Promise.reject(
new Error(
'Серверная ошибка. Пожалуйста, попробуйте позже.',
),
)
}
if (!response.ok) {
return Promise.reject(
new Error(`Ошибка сервера: ${response.status}`),
)
}
return response.json()
})
.then(() => {
return fetchComments()
})
.catch((error) => {
if (
(error.message &&
(error.message.includes('Failed to fetch') ||
error.message.includes('NetworkError'))) ||
!navigator.onLine
) {
return Promise.reject(
new Error(
'Проблемы с интернетом. Комментарий не отправлен. Проверьте подключение и попробуйте снова.',
),
)
}

// Если ошибка уже в формате Error, просто пробрасываем дальше
if (error instanceof Error) {
return Promise.reject(error)
}

// Для любых других ошибок создаем Error объект
return Promise.reject(
new Error(
'Ошибка при отправке комментария: ' +
(error.message || 'неизвестная ошибка'),
),
)
})
}

export const postCommentWithForceError = (text, name) => {
return postComment(text, name, true)
}
Loading