-
Notifications
You must be signed in to change notification settings - Fork 1
Перламутровые пуговицы #14
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 4 commits
df032cc
10c5717
a366654
e494846
3ddfc4d
5245334
719e87c
a54b4a0
65d0ece
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| const BASE_URL = 'https://31.javascript.htmlacademy.pro/kekstagram'; | ||
| const Route = { | ||
| GET_DATA: '/data', | ||
| SEND_DATA: '/', | ||
| }; | ||
| const Method = { | ||
| GET: 'GET', | ||
| POST: 'POST', | ||
| }; | ||
| const ErrorText = { | ||
| GET_DATA: 'Не удалось загрузить данные. Попробуйте обновить страницу', | ||
| SEND_DATA: 'Не удалось отправить форму. Попробуйте ещё раз', | ||
| }; | ||
|
|
||
| const load = (route, errorText, 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(errorText); | ||
| }); | ||
|
|
||
| export const getPhotos = () => load(Route.GET_DATA, ErrorText.GET_DATA); | ||
| export const sendPhoto = (body) => load(Route.SEND_DATA, ErrorText.SEND_DATA, Method.POST, body); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,89 @@ | ||
| import { isEscapeKey } from './utils.js'; | ||
|
|
||
| const onDocumentKeydown = (value) => (evt) => { | ||
| if (isEscapeKey(evt.key)) { | ||
| evt.preventDefault(); | ||
| value(); | ||
| } | ||
| }; | ||
|
|
||
| const onOutsideClick = (value, sendingSuccess) => (evt) => { | ||
| const Element = document.querySelector(`.${value}`); | ||
| const InnerElement = document.querySelector(`.${value}__inner`); | ||
|
|
||
| if (Element && !InnerElement.contains(evt.target)) { | ||
| sendingSuccess(); | ||
| } | ||
| }; | ||
|
|
||
| const onCloseErrorButtonClick = () => | ||
| closeErrorMessageSendPhoto(); | ||
|
|
||
| function closeErrorMessageSendPhoto () { | ||
| const messageElement = document.querySelector('.error'); | ||
| const errorButtonMessageSend = document.querySelector('.error__button'); | ||
|
|
||
| if (messageElement) { | ||
| messageElement.remove(); | ||
| document.removeEventListener('keydown', onDocumentKeydown(closeErrorMessageSendPhoto)); | ||
| document.removeEventListener('click', onOutsideClick('error', closeErrorMessageSendPhoto)); | ||
| errorButtonMessageSend.removeEventListener('click', onCloseErrorButtonClick); | ||
| } | ||
| } | ||
|
|
||
| export const errorMessageSendPhoto = () => { | ||
| const errorTemplate = document.querySelector('#error'); | ||
| const errorElement = errorTemplate.content.cloneNode(true); | ||
|
|
||
| document.body.appendChild(errorElement); | ||
|
|
||
| const errorButtonMessageSend = document.querySelector('.error__button'); | ||
|
|
||
| document.addEventListener('keydown', onDocumentKeydown(closeErrorMessageSendPhoto)); | ||
| document.addEventListener('click', onOutsideClick('error', closeErrorMessageSendPhoto)); | ||
| errorButtonMessageSend.addEventListener('click', onCloseErrorButtonClick); | ||
| }; | ||
|
|
||
| export const errorMessageGetPhotos = () => { | ||
| const errorTemplate = document.querySelector('#data-error'); | ||
| const errorElement = errorTemplate.content.cloneNode(true); | ||
|
|
||
| document.body.appendChild(errorElement); | ||
|
|
||
| const errorMessage = document.body.lastElementChild; | ||
|
|
||
| setTimeout(() => { | ||
| if (errorMessage && errorMessage.parentNode) { | ||
| errorMessage.remove(); | ||
| } | ||
| }, 5000); | ||
| }; | ||
|
|
||
| const onCloseSuccessButtonClick = () => | ||
| closeSuccessMessageSendPhoto(); | ||
|
|
||
| function closeSuccessMessageSendPhoto () { | ||
| const messageElement = document.querySelector('.success'); | ||
| const successButtonMessageSend = document.querySelector('.success__button'); | ||
|
|
||
| if (messageElement) { | ||
| messageElement.remove(); | ||
| document.removeEventListener('keydown', onDocumentKeydown(closeSuccessMessageSendPhoto)); | ||
| document.removeEventListener('click', onOutsideClick('success', closeSuccessMessageSendPhoto)); | ||
| successButtonMessageSend.removeEventListener('click', onCloseSuccessButtonClick); | ||
| } | ||
| } | ||
|
|
||
| export const successMessageSendPhoto = () => { | ||
| const SuccessTemplate = document.querySelector('#success'); | ||
| const SuccessElement = SuccessTemplate.content.cloneNode(true); | ||
|
|
||
| document.body.appendChild(SuccessElement); | ||
|
|
||
| const successButtonMessageSend = document.querySelector('.success__button'); | ||
|
|
||
| document.addEventListener('keydown', onDocumentKeydown(closeSuccessMessageSendPhoto)); | ||
| document.addEventListener('click', onOutsideClick('success', closeSuccessMessageSendPhoto)); | ||
| successButtonMessageSend.addEventListener('click', onCloseSuccessButtonClick); | ||
| }; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| import { getRandomInteger } from './utils.js'; | ||
|
|
||
| const MAX_RANDOM_PHOTO = 10; | ||
|
|
||
| export const shuffleArray = (array) => { | ||
|
||
| const shuffled = [...array]; | ||
|
|
||
| for (let i = shuffled.length - 1; i > 0; i--) { | ||
| const randomIndex = getRandomInteger(0, i); | ||
| [shuffled[i], shuffled[randomIndex]] = [shuffled[randomIndex], shuffled[i]]; | ||
| } | ||
|
|
||
| return shuffled.slice(0, MAX_RANDOM_PHOTO); | ||
| }; | ||
|
|
||
| export const resetActiveButtons = (arr) => { | ||
|
||
| arr.forEach((button) => { | ||
| button.classList.remove('img-filters__button--active'); | ||
| }); | ||
| }; | ||
|
|
||
| export const sortedByArray = (data) => | ||
| [...data].sort((a, b) => b.comments.length - a.comments.length); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,6 @@ | ||
| import {renderGallery, setGalleryData} from './gallery.js'; | ||
| import {showErrorMessage} from './utils.js'; | ||
| import {errorMessageSendPhoto, successMessageSendPhoto} from './dialogs.js'; | ||
| import { onCloseImageEditor } from './upload-picture.js'; | ||
| import { sendPhoto } from './api.js'; | ||
|
|
||
| const hashtagPattern = /^#[a-zа-яё0-9]{1,19}$/i; | ||
| const MAX_HASHTAGS = 5; | ||
|
|
@@ -10,52 +10,11 @@ const imgUploadSubmit = imgUploadForm.querySelector('.img-upload__submit'); | |
| const descriptionInput = imgUploadForm.querySelector('.text__description'); | ||
| const hashtagsInput = imgUploadForm.querySelector('.text__hashtags'); | ||
|
|
||
| const BASE_URL = 'https://31.javascript.htmlacademy.pro/kekstagram'; | ||
| const Route = { | ||
| GET_DATA: '/data', | ||
| SEND_DATA: '/', | ||
| }; | ||
| const Method = { | ||
| GET: 'GET', | ||
| POST: 'POST', | ||
| }; | ||
| const ErrorText = { | ||
| GET_DATA: 'Не удалось загрузить данные. Попробуйте обновить страницу', | ||
| SEND_DATA: 'Не удалось отправить форму. Попробуйте ещё раз', | ||
| }; | ||
| const SubmitButtonText = { | ||
| IDLE: 'Сохранить', | ||
| SENDING: 'Сохраняю...' | ||
| }; | ||
|
|
||
|
|
||
| const load = (route, errorText, 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(errorText); | ||
| }); | ||
|
|
||
| const getData = () => load(Route.GET_DATA, ErrorText.GET_DATA); | ||
|
|
||
| export const sendData = (body) => load(Route.SEND_DATA, ErrorText.SEND_DATA, Method.POST, body); | ||
|
|
||
| getData() | ||
| .then((gallery) => { | ||
| renderGallery(gallery); | ||
| setGalleryData(gallery); | ||
| }) | ||
| .catch( | ||
| (err) => { | ||
| showErrorMessage(err.message); | ||
| } | ||
| ); | ||
|
|
||
| const toggleSubmitButton = (disabled) => { | ||
| imgUploadSubmit.disabled = disabled; | ||
| imgUploadSubmit.textContent = disabled | ||
|
|
@@ -78,24 +37,38 @@ const isHashtagsValid = (value) => { | |
|
|
||
| const hashtagsArray = trimmedValue.split(/\s+/); | ||
|
|
||
| if (hashtagsArray.length > MAX_HASHTAGS) { | ||
| return false; | ||
| return hashtagsArray.every((hashtag) => hashtagPattern.test(hashtag)); | ||
| }; | ||
|
|
||
| const isMaxHashtagsValid = (value) => { | ||
| const trimmedValue = value.trim(); | ||
| const hashtags = trimmedValue.split(/\s+/); | ||
Musa-Ismailov marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| if (trimmedValue === '') { | ||
|
||
| return true; | ||
| } | ||
|
|
||
| const uniqueHashtags = new Set(hashtagsArray.map((h) => h.toLowerCase())); | ||
| return hashtags.length <= MAX_HASHTAGS; | ||
| }; | ||
|
|
||
| const isUniqueHashtagsValid = (value) => { | ||
| const trimmedValue = value.trim(); | ||
|
|
||
| if (uniqueHashtags.size !== hashtagsArray.length) { | ||
| return false; | ||
| if (trimmedValue === '') { | ||
|
||
| return true; | ||
| } | ||
|
|
||
| return hashtagsArray.every((hashtag) => hashtagPattern.test(hashtag)); | ||
| const hashtags = trimmedValue.split(/\s+/); | ||
| const uniqueHashtags = new Set(hashtags.map((h) => h.toLowerCase())); | ||
| return uniqueHashtags.size === hashtags.length; | ||
| }; | ||
|
|
||
| const isDescriptionValid = (value) => | ||
| value.trim() === '' || value.length <= MAX_DESCRIPTION_LEN; | ||
|
|
||
| pristine.addValidator(hashtagsInput, isHashtagsValid, 'введён невалидный хэштег'); | ||
| pristine.addValidator(descriptionInput, isDescriptionValid, 'длина комментария больше 140 символов.'); | ||
Musa-Ismailov marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| pristine.addValidator(hashtagsInput, isMaxHashtagsValid, `Не более ${MAX_HASHTAGS} хэштегов`); | ||
Musa-Ismailov marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| pristine.addValidator(hashtagsInput, isUniqueHashtagsValid, 'Хэштеги повторяются.'); | ||
Musa-Ismailov marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| imgUploadForm.addEventListener('submit', async (evt) => { | ||
| evt.preventDefault(); | ||
|
|
@@ -105,10 +78,11 @@ imgUploadForm.addEventListener('submit', async (evt) => { | |
| try { | ||
| toggleSubmitButton(true); | ||
|
|
||
| await sendData(new FormData(evt.target)); | ||
| await sendPhoto(new FormData(evt.target)); | ||
| onCloseImageEditor(); | ||
| successMessageSendPhoto(); | ||
| } catch (err) { | ||
| showErrorMessage(err.message); | ||
| errorMessageSendPhoto(err.message); | ||
| } finally { | ||
| toggleSubmitButton(false); | ||
| } | ||
|
|
@@ -118,4 +92,3 @@ imgUploadForm.addEventListener('submit', async (evt) => { | |
| hashtagsInput.focus(); | ||
| } | ||
| }); | ||
|
|
||
Musa-Ismailov marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -92,7 +92,9 @@ imgUploadInput.addEventListener('change', onOpenImageEditor); | |
| imgUploadCancel.addEventListener('click', onCloseImageEditor); | ||
|
|
||
| function onDocumentKeydown (evt) { | ||
| if (isEscapeKey(evt.key) && !isInputFocused()) { | ||
| const isErrorMessageOpen = document.querySelector('.error'); | ||
|
||
|
|
||
| if (isEscapeKey(evt.key) && !isInputFocused() && !isErrorMessageOpen) { | ||
| evt.preventDefault(); | ||
| onCloseImageEditor(); | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,16 +1,8 @@ | ||
| export const isEscapeKey = (key) => key === 'Escape'; | ||
|
|
||
| export const showErrorMessage = () => { | ||
| const errorTemplate = document.querySelector('#data-error'); | ||
| const errorElement = errorTemplate.content.cloneNode(true); | ||
|
|
||
| document.body.appendChild(errorElement); | ||
|
|
||
| const errorMessage = document.body.lastElementChild; | ||
|
|
||
| setTimeout(() => { | ||
| if (errorMessage && errorMessage.parentNode) { | ||
| errorMessage.remove(); | ||
| } | ||
| }, 5000); | ||
| export const getRandomInteger = (a, b) => { | ||
| const lower = Math.ceil(Math.min(a, b)); | ||
| const upper = Math.floor(Math.max(a, b)); | ||
| const result = Math.random() * (upper - lower + 1) + lower; | ||
| return Math.floor(result); | ||
Musa-Ismailov marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| }; | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Этот модуль нужно переделать:
1 - функция открытия диалогов и закрытия у тебя очень похожи, не считая errorMessageGetPhotos, поэтому вынеси общую логику, постарайся оставить 2 функции openDialog / closeDialog, ты можешь указывать параметром template который открыть, явно передавая его, за них отвечает messageElement
2 - все поиски элементов сделай за переделами функций
const messageElement = document.querySelector('.error');и подобное, нам не нужно каждый раз при открытии искать элемент, ты их знаешь заранее, достаточно клонировать
3 - функции - это глагол, сейчас у тебя это не так: errorMessageSendPhoto, errorMessageGetPhotos и тд
4 - ты можешь запоминать последний открытый dialog в переменную, и потом этим воспользоваться в onOutsideClick, тебе не нужно искать постоянно элемент, ты его знаешь, а по закрытию очищаешь
5 - чтобы не делать проверку isErrorMessageOpen, ты можешь в onDocumentKeydown добавить вызов дополнительной функции, которая отвечает за отмену всплытия события