Skip to content
Open
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
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
node_modules/
2 changes: 2 additions & 0 deletions .prettierrc.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
tabWidth: 4
singleQuote: true
26 changes: 26 additions & 0 deletions JS modules/addComment.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { saveComment, loadComments, getComments } from './state.js';
import { sanitize } from './utils.js';
import { renderComments } from './view.js';

export function initAddComment({ nameInput, textInput, button, listRoot }) {
button.addEventListener('click', async () => {
const nameValue = sanitize(nameInput.value);
const textValue = sanitize(textInput.value);
if (!nameValue || !textValue) {
alert('Напиши что-нибудь!');
return;
}
button.disabled = true;
try {
await saveComment({ name: nameValue, text: textValue });
await loadComments();
renderComments(listRoot, getComments());
nameInput.value = '';
textInput.value = '';
} catch (e) {
alert(e.message);
} finally {
button.disabled = false;
}
});
}
14 changes: 14 additions & 0 deletions JS modules/likeHandler.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Лайки
import { toggleLike, getComments } from './state.js';
import { renderComments } from './view.js';

export function initLikeHandler({ listRoot }) {
listRoot.addEventListener('click', (e) => {
const likeBtn = e.target.closest('.like-button');
if (!likeBtn) return;
const i = Number(likeBtn.dataset.index);
if (Number.isNaN(i)) return;
toggleLike(i);
renderComments(listRoot, getComments());
});
}
18 changes: 18 additions & 0 deletions JS modules/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { getComments, loadComments } from './state.js';
import { renderComments } from './view.js';
import { initAddComment } from './addComment.js';
import { initLikeHandler } from './likeHandler.js';
import { initQuoteHandler } from './quoteHandler.js';

const nameInput = document.getElementById('name');
const textInput = document.getElementById('comment');
const button = document.getElementById('button');
const listRoot = document.querySelector('.comments');

initAddComment({ nameInput, textInput, button, listRoot });
initLikeHandler({ listRoot });
initQuoteHandler({ listRoot, textInput });

loadComments().then(() => {
renderComments(listRoot, getComments());
});
21 changes: 21 additions & 0 deletions JS modules/quoteHandler.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Цитирование по клику
export function initQuoteHandler({ listRoot, textInput }) {
listRoot.addEventListener('click', (e) => {
const item = e.target.closest('.comment');
if (!item || !listRoot.contains(item)) return;

const author =
item
.querySelector('.comment-header > div:first-child')
?.textContent.trim() ?? '';
const text =
item.querySelector('.comment-text')?.textContent.trim() ?? '';
const quote = `@${author}: "${text}" `;
textInput.value = quote;
textInput.focus();
textInput.setSelectionRange(
textInput.value.length,
textInput.value.length,
);
});
}
35 changes: 35 additions & 0 deletions JS modules/state.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
const API_URL = 'https://wedev-api.sky.pro/api/v1/origami/comments';

let comments = [];

export const getComments = () => comments.slice();

export const loadComments = async () => {
const res = await fetch(API_URL);
const data = await res.json();
comments = data.comments.map((c) => ({
name: c.author.name,
text: c.text,
date: new Date(c.date).toLocaleString('ru-RU'),
likes: c.likes,
isLiked: false, // лайки локальные, сбрасываем при загрузке
}));
};

export const saveComment = async ({ name, text }) => {
const res = await fetch(API_URL, {
method: 'POST',
body: JSON.stringify({ name, text }),
});
if (!res.ok) {
const err = await res.json();
throw new Error(err.error || 'Ошибка сервера');
}
};

export const toggleLike = (index) => {
const c = comments[index];
if (!c) return;
c.isLiked = !c.isLiked;
c.likes += c.isLiked ? 1 : -1;
};
23 changes: 23 additions & 0 deletions JS modules/utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Время и экранирование
export function nowRu() {
const now = new Date();
const dateStr = now.toLocaleDateString('ru-RU', {
day: '2-digit',
month: '2-digit',
year: '2-digit',
});
const timeStr = now.toLocaleTimeString('ru-RU', {
hour: '2-digit',
minute: '2-digit',
hour12: false,
});
return `${dateStr} ${timeStr}`;
}

export function sanitize(str) {
return String(str)
.replaceAll('&', '&')
.replaceAll('<', '&lt;')
.replaceAll('>', '&gt;')
.trim();
}
24 changes: 24 additions & 0 deletions JS modules/view.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Рендер списка
export function renderComments(rootEl, comments) {
const items = comments.map(
(item, index) => `
<li class="comment">
<div class="comment-header">
<div>${item.name}</div>
<div>${item.date}</div>
</div>
<div class="comment-body">
<div class="comment-text">${item.text}</div>
</div>
<div class="comment-footer">
<div class="likes">
<span class="likes-counter">${item.likes}</span>
<button class="like-button${
item.isLiked ? ' -active-like' : ''
}" data-index="${index}"></button>
</div>
</div>
</li>`,
);
rootEl.innerHTML = items.join('');
}
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,
];
95 changes: 29 additions & 66 deletions index.html
Original file line number Diff line number Diff line change
@@ -1,71 +1,34 @@
<!DOCTYPE html>
<!doctype html>
<html>
<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">
Это будет первый комментарий на этой странице
</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>
<head>
<meta charset="utf-8" />
<title>Проект "Комменты"</title>
<link rel="stylesheet" href="styles.css" />
</head>
<body>
<div class="container">
<ul class="comments"></ul>
<div class="add-form">
<input
type="text"
class="add-form-name"
id="name"
placeholder="Введите ваше имя"
/>
<textarea
class="add-form-text"
id="comment"
placeholder="Введите ваш комментарий"
rows="4"
></textarea>
<div class="add-form-row">
<button class="add-form-button" id="button">
Написать
</button>
</div>
</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>

<script>
"use strict";
// Код писать здесь
console.log("It works!");
</script>
<script type="module" src="./JS modules/main.js"></script>
</body>
</html>
Loading