From f8100adf4bdb4b123c680715f352509c12d94235 Mon Sep 17 00:00:00 2001 From: Vadim Golubev Date: Wed, 20 May 2026 11:15:51 +0300 Subject: [PATCH 1/3] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D1=8F?= =?UTF-8?q?=D0=B5=D1=82=20=D1=84=D1=83=D0=BD=D0=BA=D1=86=D0=B8=D0=BE=D0=BD?= =?UTF-8?q?=D0=B0=D0=BB=20=D0=BF=D0=BE=D0=BB=D1=83=D1=87=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D1=8F=20=D0=B4=D0=B0=D0=BD=D0=BD=D1=8B=D1=85=20=D1=81=20=D1=81?= =?UTF-8?q?=D0=B5=D1=80=D0=B2=D0=B5=D1=80=D0=B0,=20=D0=BE=D1=82=D0=BF?= =?UTF-8?q?=D1=80=D0=B0=D0=B2=D0=BA=D0=B8=20=D1=84=D0=BE=D1=80=D0=BC=D1=8B?= =?UTF-8?q?=20=D1=87=D0=B5=D1=80=D0=B5=D0=B7=20fetch=20=D0=B8=20=D0=BE?= =?UTF-8?q?=D0=B1=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D0=BA=D0=B8=20=D0=BE=D1=88?= =?UTF-8?q?=D0=B8=D0=B1=D0=BE=D0=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- js/api.js | 27 +++++++++++++ js/components/alerts.js | 45 +++++++++++++++++++++ js/components/upload-modal.js | 7 +--- js/data.js | 73 ----------------------------------- js/main.js | 20 +++++++--- js/upload-validation.js | 27 ++++++++++--- 6 files changed, 110 insertions(+), 89 deletions(-) create mode 100644 js/api.js create mode 100644 js/components/alerts.js delete mode 100644 js/data.js diff --git a/js/api.js b/js/api.js new file mode 100644 index 0000000..6928299 --- /dev/null +++ b/js/api.js @@ -0,0 +1,27 @@ +const BASE_URL = 'https://31.javascript.htmlacademy.pro/kekstagram'; +const Route = { + GET_DATA: '/data', + SEND_DATA: '/', +}; +const Method = { + GET: 'GET', + POST: 'POST', +}; + +const load = (route, method = Method.GET, body = null) => + fetch(`${BASE_URL}${route}`, { method, body }) + .then((response) => { + if (!response.ok) { + throw new Error(); + } + + return response.json(); + }) + .catch(() => { + throw new Error(); + }); + +const getData = () => load(Route.GET_DATA); +const sendData = (body) => load(Route.SEND_DATA, Method.POST, body); + +export { getData, sendData }; diff --git a/js/components/alerts.js b/js/components/alerts.js new file mode 100644 index 0000000..57f138a --- /dev/null +++ b/js/components/alerts.js @@ -0,0 +1,45 @@ +import { isEscapeKey } from '../util.js'; + +const bodyElement = document.body; +const uploadErrorTemplate = document + .querySelector('#error') + .content.querySelector('.error'); +const uploadSuccessTemplate = document + .querySelector('#success') + .content.querySelector('.success'); +const dataLoadErrorTemplate = document + .querySelector('#data-error') + .content.querySelector('.data-error'); + +const alerts = { + success: uploadSuccessTemplate, + error: uploadErrorTemplate, + dataError: dataLoadErrorTemplate, +}; + +const showAlert = (type = 'success') => { + const alertItem = alerts[type].cloneNode(true); + bodyElement.append(alertItem); + + if (alertItem.classList.contains('data-error')) { + setTimeout(() => { + alertItem.remove(); + }, 5000); + } else { + alertItem + .querySelector('button') + .addEventListener('click', () => alertItem.remove()); + document.addEventListener('keydown', (evt) => { + if (isEscapeKey(evt)) { + alertItem.remove(); + } + }); + document.addEventListener('click', (evt) => { + if (!alertItem.querySelector('div').contains(evt.target)) { + alertItem.remove(); + } + }); + } +}; + +export { showAlert }; diff --git a/js/components/upload-modal.js b/js/components/upload-modal.js index bc88ed7..4c03072 100644 --- a/js/components/upload-modal.js +++ b/js/components/upload-modal.js @@ -1,12 +1,11 @@ import { isEscapeKey } from '../util.js'; -import { onUploadFormSubmit } from '../upload-validation.js'; import { createOnEffectChange, clearEffects, createOnScaleButtonClick, } from '../effects.js'; -const bodyElement = document.querySelector('body'); +const bodyElement = document.body; const formElement = document.querySelector('#upload-select-image'); const hashtagsElement = formElement.querySelector('input[name="hashtags"]'); const descriptionElement = formElement.querySelector('.text__description'); @@ -58,7 +57,6 @@ function closeUploadModal() { ); formElement.removeEventListener('change', onEffectChange); - formElement.removeEventListener('submit', onUploadFormSubmit); document.removeEventListener('keydown', onDocumentKeydown); [hashtagsElement, descriptionElement].forEach((input) => { @@ -77,7 +75,6 @@ const renderUploadModal = () => { ); formElement.addEventListener('change', onEffectChange); - formElement.addEventListener('submit', onUploadFormSubmit); overlayCloseElement.addEventListener('click', closeUploadModal); document.addEventListener('keydown', onDocumentKeydown); @@ -88,4 +85,4 @@ const renderUploadModal = () => { }); }; -export { renderUploadModal }; +export { closeUploadModal, renderUploadModal }; diff --git a/js/data.js b/js/data.js deleted file mode 100644 index 7923b59..0000000 --- a/js/data.js +++ /dev/null @@ -1,73 +0,0 @@ -import { getRandomNumber, getRandomItemFrom } from './util.js'; - -const NAMES = [ - 'Александр', - 'Дмитрий', - 'Иван', - 'Сергей', - 'Андрей', - 'Михаил', - 'Николай', - 'Евгений', - 'Владимир', - 'Алексей', - 'Анна', - 'Мария', - 'Екатерина', - 'Ольга', - 'Наталья' -]; - -const COMMENTS = [ - 'Всё отлично!', - 'В целом всё неплохо. Но не всё.', - 'Когда вы делаете фотографию, хорошо бы убирать палец из кадра. В конце концов это просто непрофессионально.', - 'Моя бабушка случайно чихнула с фотоаппаратом в руках и у неё получилась фотография лучше.', - 'Я поскользнулся на банановой кожуре и уронил фотоаппарат на кота и у меня получилась фотография лучше.', - 'Лица у людей на фотке перекошены, как будто их избивают. Как можно было поймать такой неудачный момент?!' -]; - -const PHOTOS_AMOUNT = 25; - -const createRandomIdGenerator = (min, max) => { - const usedIds = []; - - return () => { - if (usedIds.length >= (max - min + 1)) { - return null; - } - - let currentId = getRandomNumber(min, max); - while (usedIds.includes(currentId)) { - currentId = getRandomNumber(min, max); - } - - usedIds.push(currentId); - return currentId; - }; -}; - -const getPhotoId = createRandomIdGenerator(1, PHOTOS_AMOUNT); - -const genereteComment = function () { - return { - id: crypto.randomUUID(), - avatar: `img/avatar-${getRandomNumber(1, 6)}.svg`, - message: getRandomItemFrom(COMMENTS), - name: getRandomItemFrom(NAMES) - }; -}; - -const generetePhoto = function () { - return { - id: crypto.randomUUID(), - url: `photos/${getPhotoId()}.jpg`, - description: 'user photo', - likes: getRandomNumber(15, 200), - comments: Array.from({length: getRandomNumber(0, 30)}, genereteComment) - }; -}; - -const genereteData = () => Array.from({length: PHOTOS_AMOUNT}, generetePhoto); - -export { genereteData }; diff --git a/js/main.js b/js/main.js index 61aa577..e730028 100644 --- a/js/main.js +++ b/js/main.js @@ -1,11 +1,21 @@ -import { genereteData } from './data.js'; +import { getData } from './api.js'; import { renderGalley } from './components/gallery.js'; import { renderPhotoModal } from './components/photo-modal.js'; -import { renderUploadModal } from './components/upload-modal.js'; +import { + renderUploadModal, + closeUploadModal, +} from './components/upload-modal.js'; +import { setUploadFormSubmit } from './upload-validation.js'; +import { showAlert } from './components/alerts.js'; -const data = genereteData(); const picturesElement = document.querySelector('.pictures'); -renderGalley(data, picturesElement); -renderPhotoModal(data, picturesElement); +getData() + .then((data) => { + renderGalley(data, picturesElement); + renderPhotoModal(data, picturesElement); + }) + .catch(() => showAlert('dataError')); + renderUploadModal(); +setUploadFormSubmit(closeUploadModal); diff --git a/js/upload-validation.js b/js/upload-validation.js index c569ce9..af08d94 100644 --- a/js/upload-validation.js +++ b/js/upload-validation.js @@ -1,4 +1,8 @@ +import { sendData } from './api.js'; +import { showAlert } from './components/alerts.js'; + const formElement = document.querySelector('#upload-select-image'); +const submitElement = formElement.querySelector('button[type="submit"]'); const hashtagsElement = formElement.querySelector('input[name="hashtags"]'); const descriptionElement = formElement.querySelector('.text__description'); const hashtagRegex = /^#[a-zа-яё0-9]{1,19}$/i; @@ -69,12 +73,23 @@ pristine.addValidator( 'Максимум 140 символов', ); -const onUploadFormSubmit = (event) => { - event.preventDefault(); +const setUploadFormSubmit = (onSuccess) => { + formElement.addEventListener('submit', (evt) => { + evt.preventDefault(); - if (pristine.validate()) { - formElement.submit(); - } + if (pristine.validate()) { + submitElement.disabled = true; + sendData(new FormData(evt.target)) + .then(onSuccess) + .then(showAlert()) + .catch(() => { + showAlert('error'); + }) + .finally(() => { + submitElement.disabled = false; + }); + } + }); }; -export { onUploadFormSubmit }; +export { setUploadFormSubmit }; From 971b16802da4e06c606fb1a3a28a646a610bbce6 Mon Sep 17 00:00:00 2001 From: Vadim Golubev Date: Wed, 27 May 2026 10:52:11 +0300 Subject: [PATCH 2/3] =?UTF-8?q?=D0=A0=D0=B5=D1=84=D0=B0=D0=BA=D1=82=D0=BE?= =?UTF-8?q?=D1=80=D0=B8=D0=BD=D0=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- js/api.js | 6 ++-- js/components/alerts.js | 57 +++++++++++++++++++++-------------- js/components/upload-modal.js | 2 +- js/main.js | 6 ++-- js/upload-validation.js | 12 ++++---- 5 files changed, 48 insertions(+), 35 deletions(-) diff --git a/js/api.js b/js/api.js index 6928299..a9b5f53 100644 --- a/js/api.js +++ b/js/api.js @@ -21,7 +21,7 @@ const load = (route, method = Method.GET, body = null) => throw new Error(); }); -const getData = () => load(Route.GET_DATA); -const sendData = (body) => load(Route.SEND_DATA, Method.POST, body); +const fetchPhotos = () => load(Route.GET_DATA); +const sendPhoto = (body) => load(Route.SEND_DATA, Method.POST, body); -export { getData, sendData }; +export { fetchPhotos, sendPhoto }; diff --git a/js/components/alerts.js b/js/components/alerts.js index 57f138a..b26920e 100644 --- a/js/components/alerts.js +++ b/js/components/alerts.js @@ -11,34 +11,47 @@ const dataLoadErrorTemplate = document .querySelector('#data-error') .content.querySelector('.data-error'); -const alerts = { - success: uploadSuccessTemplate, - error: uploadErrorTemplate, - dataError: dataLoadErrorTemplate, +const Alerts = { + SUCCESS: uploadSuccessTemplate, + ERROR: uploadErrorTemplate, + DATA_ERROR: dataLoadErrorTemplate, }; -const showAlert = (type = 'success') => { - const alertItem = alerts[type].cloneNode(true); - bodyElement.append(alertItem); +const onDocumentKeydown = (event) => { + if (isEscapeKey(event)) { + event.preventDefault(); + closeAlert(); + } +}; + +const onDocumentClick = (event) => { + const alertElement = bodyElement.querySelector('.alert'); + + if (!alertElement.querySelector('div').contains(event.target)) { + closeAlert(); + } +}; + +function closeAlert() { + bodyElement.querySelector('.alert').remove(); + + document.removeEventListener('keydown', onDocumentKeydown); + document.removeEventListener('click', onDocumentClick); +} + +const showAlert = (type = 'SUCCESS') => { + const alertElement = Alerts[type].cloneNode(true); + alertElement.classList.add('alert'); + bodyElement.append(alertElement); - if (alertItem.classList.contains('data-error')) { + if (alertElement.classList.contains('data-error')) { setTimeout(() => { - alertItem.remove(); + alertElement.remove(); }, 5000); } else { - alertItem - .querySelector('button') - .addEventListener('click', () => alertItem.remove()); - document.addEventListener('keydown', (evt) => { - if (isEscapeKey(evt)) { - alertItem.remove(); - } - }); - document.addEventListener('click', (evt) => { - if (!alertItem.querySelector('div').contains(evt.target)) { - alertItem.remove(); - } - }); + alertElement.querySelector('button').addEventListener('click', closeAlert); + document.addEventListener('keydown', onDocumentKeydown); + document.addEventListener('click', onDocumentClick); } }; diff --git a/js/components/upload-modal.js b/js/components/upload-modal.js index 4c03072..230251b 100644 --- a/js/components/upload-modal.js +++ b/js/components/upload-modal.js @@ -21,7 +21,7 @@ const uploadedPreviewElement = formElement.querySelector( ); const onDocumentKeydown = (event) => { - if (isEscapeKey(event)) { + if (isEscapeKey(event) && !bodyElement.querySelector('.alert')) { event.preventDefault(); closeUploadModal(); } diff --git a/js/main.js b/js/main.js index e730028..ea2f65e 100644 --- a/js/main.js +++ b/js/main.js @@ -1,4 +1,4 @@ -import { getData } from './api.js'; +import { fetchPhotos } from './api.js'; import { renderGalley } from './components/gallery.js'; import { renderPhotoModal } from './components/photo-modal.js'; import { @@ -10,12 +10,12 @@ import { showAlert } from './components/alerts.js'; const picturesElement = document.querySelector('.pictures'); -getData() +fetchPhotos() .then((data) => { renderGalley(data, picturesElement); renderPhotoModal(data, picturesElement); }) - .catch(() => showAlert('dataError')); + .catch(() => showAlert('DATA_ERROR')); renderUploadModal(); setUploadFormSubmit(closeUploadModal); diff --git a/js/upload-validation.js b/js/upload-validation.js index af08d94..2621b8a 100644 --- a/js/upload-validation.js +++ b/js/upload-validation.js @@ -1,4 +1,4 @@ -import { sendData } from './api.js'; +import { sendPhoto } from './api.js'; import { showAlert } from './components/alerts.js'; const formElement = document.querySelector('#upload-select-image'); @@ -74,16 +74,16 @@ pristine.addValidator( ); const setUploadFormSubmit = (onSuccess) => { - formElement.addEventListener('submit', (evt) => { - evt.preventDefault(); + formElement.addEventListener('submit', (event) => { + event.preventDefault(); if (pristine.validate()) { submitElement.disabled = true; - sendData(new FormData(evt.target)) + sendPhoto(new FormData(event.target)) .then(onSuccess) - .then(showAlert()) + .then(showAlert) .catch(() => { - showAlert('error'); + showAlert('ERROR'); }) .finally(() => { submitElement.disabled = false; From 13163a1dbe92a393c180337d1ee5f299f53c988e Mon Sep 17 00:00:00 2001 From: Vadim Golubev Date: Wed, 27 May 2026 11:15:41 +0300 Subject: [PATCH 3/3] =?UTF-8?q?=D0=A0=D0=B5=D1=84=D0=B0=D0=BA=D1=82=D0=BE?= =?UTF-8?q?=D1=80=D0=B8=D0=BD=D0=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- js/upload-validation.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/upload-validation.js b/js/upload-validation.js index 2621b8a..dafc232 100644 --- a/js/upload-validation.js +++ b/js/upload-validation.js @@ -22,7 +22,7 @@ const validateHashtagsFormat = (value) => { return hashtags.every((hashtag) => hashtagRegex.test(hashtag)); }; -const validatekHashtagsAmount = (value) => { +const validateHashtagsAmount = (value) => { if (value.length === 0) { return true; } @@ -59,7 +59,7 @@ pristine.addValidator( ); pristine.addValidator( hashtagsElement, - validatekHashtagsAmount, + validateHashtagsAmount, 'Максимум 5 хэштегов', ); pristine.addValidator(