diff --git a/js/check-hashtag-validity.js b/js/check-hashtag-validity.js index c4af41c..809a2f3 100644 --- a/js/check-hashtag-validity.js +++ b/js/check-hashtag-validity.js @@ -14,12 +14,13 @@ const hasValidHashtagCount = (value) => const hasValidHashtagLength = (value) => normalizeHashtags(value).every((hashtag) => hashtag.length <= MAX_HASHTAG_LENGTH); -const hasValidHashtagFormat = (value) => - normalizeHashtags(value).every((hashtag) => - hashtag.startsWith('#') && - hashtag.length > 1 && - HASHTAG_REGEXP.test(hashtag) - ); +const hasValidHashtagFormat = (value) => { + const hashtags = normalizeHashtags(value); + if (hashtags.length === 0) { + return true; + } + return hashtags.every((hashtag) => HASHTAG_REGEXP.test(hashtag)); +}; const hasUniqueHashtags = (value) => { const hashtags = normalizeHashtags(value); diff --git a/js/create-array-photos.js b/js/create-array-photos.js deleted file mode 100644 index 6ec24f5..0000000 --- a/js/create-array-photos.js +++ /dev/null @@ -1,75 +0,0 @@ - -import { getRandomInteger, getRandomUniqueId, getRandomArrayElement } from './util.js'; -import { getDataArrayPhotos } from './data.js'; - -const LIKES_RANGE = { - MIN: 15, - MAX: 200, -}; - -const COMMENT_SENTECES_RANGE = { - MIN: 1, - MAX: 2, -}; - -const AVATAR_RANGE = { - MIN: 1, - MAX: 6, -}; - -const PHOTO_ID_RANGE = { - MIN: 1, - MAX: 25, -}; - -const COMMENT_COUNT_RANGE = { - MIN: 0, - MAX: 30, -}; - -const PHOTO_ARRAY_SIZE = 25; -const { MESSAGES, NAMES, DESCRIPTIONS } = getDataArrayPhotos(); - -//Функция создания фабрики для генерации комментариев -const createCommentFactory = () => { - let commentIdCounter = 1; - //Возвращает массив случайных комментариев (1-2 комментария) - return () => { - const numSentences = getRandomInteger( - COMMENT_SENTECES_RANGE.MIN, - COMMENT_SENTECES_RANGE.MAX - ); - const sentences = Array.from({length: numSentences}, () => getRandomArrayElement(MESSAGES)); - //Возвращает объект - комментарий - return { - id: commentIdCounter++, - avatar: `img/avatar-${getRandomInteger(AVATAR_RANGE.MIN, AVATAR_RANGE.MAX)}.svg`, - message: sentences.join(' '), - name: getRandomArrayElement(NAMES) - }; - }; -}; - -const createComment = createCommentFactory(); - -const createPhotoId = getRandomUniqueId(PHOTO_ID_RANGE.MIN, PHOTO_ID_RANGE.MAX); - -const createPhotoDescription = () => { - const photoId = createPhotoId(); - - return { - id: photoId, - url: `photos/${photoId}.jpg`, - description: getRandomArrayElement(DESCRIPTIONS), - likes: getRandomInteger(LIKES_RANGE.MIN, LIKES_RANGE.MAX), - comments: Array.from( - { length: getRandomInteger(COMMENT_COUNT_RANGE.MIN, COMMENT_COUNT_RANGE.MAX) }, - createComment - ), - }; -}; - -//Функция создания массива из 25 объектов -const photos = Array.from({ length: PHOTO_ARRAY_SIZE }, createPhotoDescription); - -export { photos }; diff --git a/js/data.js b/js/data.js deleted file mode 100644 index 96a7904..0000000 --- a/js/data.js +++ /dev/null @@ -1,48 +0,0 @@ - -const getDataArrayPhotos = () => { - const MESSAGES = [ - 'Всё отлично!', - 'В целом всё неплохо. Но не всё.', - 'Когда вы делаете фотографию, хорошо бы убирать палец из кадра. В конце концов это просто непрофессионально.', - 'Моя бабушка случайно чихнула с фотоаппаратом в руках и у неё получилась фотография лучше.', - 'Я поскользнулся на банановой кожуре и уронил фотоаппарат на кота и у меня получилась фотография лучше.', - 'Лица у людей на фотке перекошены, как будто их избивают. Как можно было поймать такой неудачный момент?!', - ]; - - const NAMES = [ - 'Мария', - 'Максим', - 'Вероника', - 'Татьяна', - 'Надежда', - 'Дарья', - 'Сергей', - 'Александр', - 'Егор', - 'Пётр', - 'Ирина', - 'Светлана', - 'Константин', - ]; - - const DESCRIPTIONS = [ - 'Закат над морем', - 'Осенняя тропинка', - 'Поле лаванды', - 'Ночной рынок', - 'Снежный лес', - 'Кофе и книга', - 'Пляж на рассвете', - 'Горы в тумане', - 'Милые пёсели', - 'Семейное селфи', - 'Поле подсолнухов', - 'Конюшня', - 'Старая библиотека', - ]; - - return { MESSAGES, NAMES, DESCRIPTIONS }; -}; - - -export { getDataArrayPhotos }; diff --git a/js/filters.js b/js/filters.js index 45b9937..d07585a 100644 --- a/js/filters.js +++ b/js/filters.js @@ -11,37 +11,41 @@ const FilterId = { DISCUSSED: 'filter-discussed', }; -const filtersSection = document.querySelector('.img-filters'); -const filtersForm = document.querySelector('.img-filters__form'); +const filtersSectionNode = document.querySelector('.img-filters'); +const filtersFormNode = document.querySelector('.img-filters__form'); let currentFilter = FilterId.DEFAULT; -let photos = []; +let loadedPhotos = []; -const getRandomPhotos = (photosArray) => { - const shuffled = [...photosArray].sort(() => Math.random() - 0.5); +const getRandomPhotos = (photos) => { + const shuffled = [...photos]; + for (let i = shuffled.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)); + [shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]]; + } return shuffled.slice(0, RANDOM_PHOTOS_COUNT); }; -const getDiscussedPhotos = (photosArray) => - [...photosArray].sort((a, b) => b.comments.length - a.comments.length); +const getDiscussedPhotos = (photos) => + [...photos].sort((a, b) => b.comments.length - a.comments.length); const filterPhotos = () => { switch (currentFilter) { case FilterId.RANDOM: - return getRandomPhotos(photos); + return getRandomPhotos(loadedPhotos); case FilterId.DISCUSSED: - return getDiscussedPhotos(photos); + return getDiscussedPhotos(loadedPhotos); default: - return [...photos]; + return [...loadedPhotos]; } }; -const updateActiveButton = (clickedButton) => { - const activeButton = filtersForm.querySelector(`.${ACTIVE_BUTTON_CLASS}`); - if (activeButton) { - activeButton.classList.remove(ACTIVE_BUTTON_CLASS); +const updateActiveButton = (clickedButtonNode) => { + const activeButtonNode = filtersFormNode.querySelector(`.${ACTIVE_BUTTON_CLASS}`); + if (activeButtonNode) { + activeButtonNode.classList.remove(ACTIVE_BUTTON_CLASS); } - clickedButton.classList.add(ACTIVE_BUTTON_CLASS); + clickedButtonNode.classList.add(ACTIVE_BUTTON_CLASS); }; const debouncedRenderThumbnails = debounce( @@ -66,13 +70,13 @@ const onFilterButtonClick = (evt) => { }; const showFilters = () => { - filtersSection.classList.remove('img-filters--inactive'); + filtersSectionNode.classList.remove('img-filters--inactive'); }; -const initFilters = (loadedPhotos) => { - photos = loadedPhotos; +const initFilters = (photos) => { + loadedPhotos = photos; showFilters(); - filtersForm.addEventListener('click', onFilterButtonClick); + filtersFormNode.addEventListener('click', onFilterButtonClick); }; export { initFilters }; diff --git a/js/full-size-picture.js b/js/full-size-picture.js index 4f740ea..2f37667 100644 --- a/js/full-size-picture.js +++ b/js/full-size-picture.js @@ -14,26 +14,26 @@ const likesCountNode = bigPictureNode.querySelector('.likes-count'); const commentsCaptionNode = bigPictureNode.querySelector('.social__caption'); const bigPictureCancelNode = bigPictureNode.querySelector('.big-picture__cancel'); -const onEscKeydown = (evt) => { +const handlers = {}; + +handlers.onEscKeydown = (evt) => { if (evt.key === 'Escape') { evt.preventDefault(); - clearComments(); - bigPictureNode.classList.add('hidden'); - document.body.classList.remove('modal-open'); - document.removeEventListener('keydown', onEscKeydown); + handlers.closeBigPicture(); } }; -const closeBigPicture = () => { +handlers.closeBigPicture = () => { clearComments(); bigPictureNode.classList.add('hidden'); document.body.classList.remove('modal-open'); - document.removeEventListener('keydown', onEscKeydown); + document.removeEventListener('keydown', handlers.onEscKeydown); }; + const onBigPictureCancelClick = (evt) => { evt.preventDefault(); - closeBigPicture(); + handlers.closeBigPicture(); }; const openBigPicture = (pictureId) => { @@ -51,7 +51,7 @@ const openBigPicture = (pictureId) => { bigPictureNode.classList.remove('hidden'); document.body.classList.add('modal-open'); - document.addEventListener('keydown', onEscKeydown); + document.addEventListener('keydown', handlers.onEscKeydown); }; bigPictureCancelNode.addEventListener('click', onBigPictureCancelClick); diff --git a/js/functions.js b/js/functions.js deleted file mode 100644 index e500d07..0000000 --- a/js/functions.js +++ /dev/null @@ -1,68 +0,0 @@ -// Задача 1. Проверяет длину строки - -function checkStringLength (string = '', maxLength = 1) { - return string.length <= maxLength; -} - -checkStringLength(); - -// другой вариант записи с помощью стрелочной функции -// const checkStringLength = (string = '', maxLength = 1) => string.length <= maxLength; - - -// Задача 2. Проверяет, является ли строка полиндромом - -function isPalindrome (string = '') { - string = string.replaceAll(' ', '').toLowerCase(); - let reversedLine = ''; - - for (let i = string.length - 1; i >= 0; i--) { - reversedLine += string[i]; - } - - return string === reversedLine; -} - -isPalindrome(); - -// Задача 3. Принимает строку и извлекает содержащиеся в ней цифры - -function extractNumber (string) { - let result = ''; - - string = string.toString(); - - for (let i = 0; i <= string.length - 1; i++) { - if (Number.isNaN(parseInt(string[i], 10)) === false) { - result += string[i]; - } - } - - return result === '' ? NaN : Number(result); -} - -extractNumber(); - -//ДЗ module5-task2 - -const isMeetingWithinWorkday = (workStart, workEnd, meetingStart, duration) => { - //Преобразует строку времени (чч:мм) в минуты от начала суток - const toMinutes = (time) => { - const [hours, minutes] = time.split(':').map(Number); - return hours * 60 + minutes; - }; - - const workStartMinutes = toMinutes(workStart); - const workEndMinutes = toMinutes(workEnd); - const meetingStartMinutes = toMinutes(meetingStart); - const meetingEndMinutes = meetingStartMinutes + duration; - - //Проверяет, что встреча полностью внутри рабочего дня - return meetingStartMinutes >= workStartMinutes && meetingEndMinutes <= workEndMinutes; -}; - -isMeetingWithinWorkday('08:00', '17:30', '14:00', 90); // true -isMeetingWithinWorkday('8:0', '10:0', '8:0', 120); // true -isMeetingWithinWorkday('08:00', '14:30', '14:00', 90); // false -isMeetingWithinWorkday('14:00', '17:30', '08:0', 90); // false -isMeetingWithinWorkday('8:00', '17:30', '08:00', 900); // false diff --git a/js/image-editor.js b/js/image-editor.js index 468d9d0..bd33440 100644 --- a/js/image-editor.js +++ b/js/image-editor.js @@ -121,7 +121,9 @@ const onEffectsListChange = (evt) => { return; } - updateSliderOptions(evt.target.value); + const effectName = evt.target.value; + updateSliderOptions(effectName); + applyEffect(effectName, effectsConfig[effectName].start); }; const onEffectLevelSliderUpdate = () => { diff --git a/js/messages.js b/js/messages.js index f5d6510..a36f39f 100644 --- a/js/messages.js +++ b/js/messages.js @@ -2,66 +2,64 @@ const ALERT_SHOW_TIME = 5000; -const dataErrorTemplate = document.querySelector('#data-error').content.querySelector('.data-error'); -const successTemplate = document.querySelector('#success').content.querySelector('.success'); -const errorTemplate = document.querySelector('#error').content.querySelector('.error'); +const dataErrorTemplateNode = document.querySelector('#data-error').content.querySelector('.data-error'); +const successTemplateNode = document.querySelector('#success').content.querySelector('.success'); +const errorTemplateNode = document.querySelector('#error').content.querySelector('.error'); // Показывает сообщение об ошибке загрузки данных const showDataLoadError = (message) => { - const dataErrorElement = dataErrorTemplate.cloneNode(true); - dataErrorElement.querySelector('.data-error__title').textContent = message; - document.body.appendChild(dataErrorElement); + const dataErrorNode = dataErrorTemplateNode.cloneNode(true); + dataErrorNode.querySelector('.data-error__title').textContent = message; + document.body.appendChild(dataErrorNode); setTimeout(() => { - dataErrorElement.remove(); + dataErrorNode.remove(); }, ALERT_SHOW_TIME); }; // Создает и показывает модальное сообщение (успех или ошибка) -const showMessage = (template, buttonClass, titleClass, message) => { - const messageElement = template.cloneNode(true); +const showMessage = (template, buttonClass) => { + const messageNode = template.cloneNode(true); + document.body.appendChild(messageNode); - if (message) { - messageElement.querySelector(titleClass).textContent = message; - } + const closeButtonNode = messageNode.querySelector(buttonClass); - document.body.appendChild(messageElement); + const handlers = {}; - const closeButton = messageElement.querySelector(buttonClass); - - const closeMessage = () => { - messageElement.remove(); - document.removeEventListener('keydown', onEscKeydown); - document.removeEventListener('click', onOutsideClick); - }; - - function onEscKeydown(evt) { + handlers.onEscKeydown = (evt) => { if (evt.key === 'Escape') { evt.preventDefault(); - closeMessage(); + evt.stopPropagation(); + handlers.closeMessage(); } - } + }; - function onOutsideClick(evt) { - if (evt.target === messageElement) { - closeMessage(); + handlers.onOutsideClick = (evt) => { + if (evt.target === messageNode) { + handlers.closeMessage(); } - } + }; + + handlers.closeMessage = () => { + messageNode.remove(); + document.removeEventListener('keydown', handlers.onEscKeydown, true); + document.removeEventListener('click', handlers.onOutsideClick); + }; - closeButton.addEventListener('click', closeMessage); - document.addEventListener('keydown', onEscKeydown); - document.addEventListener('click', onOutsideClick); + closeButtonNode.addEventListener('click', handlers.closeMessage); + document.addEventListener('keydown', handlers.onEscKeydown, true); + document.addEventListener('click', handlers.onOutsideClick); }; const showSuccessMessage = () => { - showMessage(successTemplate, '.success__button', '.success__title'); + showMessage(successTemplateNode, '.success__button'); }; -const showErrorMessage = (message) => { - showMessage(errorTemplate, '.error__button', '.error__title', message); +const showErrorMessage = () => { + showMessage(errorTemplateNode, '.error__button'); }; export { showDataLoadError, showSuccessMessage, showErrorMessage }; diff --git a/js/render-comments.js b/js/render-comments.js index b122016..f30efab 100644 --- a/js/render-comments.js +++ b/js/render-comments.js @@ -9,12 +9,12 @@ const bigPictureNode = document.querySelector('.big-picture'); const socialCommentsNode = bigPictureNode.querySelector('.social__comments'); const commentsShownCountNode = bigPictureNode.querySelector('.social__comment-shown-count'); const commentsTotalCountNode = bigPictureNode.querySelector('.social__comment-total-count'); -const socialCommentTemplate = socialCommentsNode.querySelector('.social__comment'); +const socialCommentTemplateNode = socialCommentsNode.querySelector('.social__comment'); const commentsCountNode = bigPictureNode.querySelector('.social__comment-count'); const commentsLoaderNode = bigPictureNode.querySelector('.social__comments-loader'); const createComment = ({ avatar, name, message}) => { - const socialCommentNode = socialCommentTemplate.cloneNode(true); + const socialCommentNode = socialCommentTemplateNode.cloneNode(true); const socialPictureNode = socialCommentNode.querySelector('.social__picture'); socialPictureNode.src = avatar; @@ -49,7 +49,6 @@ const clearComments = () => { state.currentCount = 0; state.comments = []; socialCommentsNode.innerHTML = ''; - commentsCountNode.classList.add('hidden'); commentsLoaderNode.classList.add('hidden'); commentsLoaderNode.removeEventListener('click', renderNextComments); }; @@ -60,15 +59,14 @@ const renderComments = (currentPhotoComments) => { socialCommentsNode.innerHTML = ''; commentsCountNode.classList.remove('hidden'); + commentsLoaderNode.removeEventListener('click', renderNextComments); + if (state.comments.length > COUNT_STEP) { commentsLoaderNode.classList.remove('hidden'); + commentsLoaderNode.addEventListener('click', renderNextComments); } else { commentsLoaderNode.classList.add('hidden'); } - // commentsLoaderNode.classList.remove('hidden'); - - commentsLoaderNode.removeEventListener('click', renderNextComments); - commentsLoaderNode.addEventListener('click', renderNextComments); renderNextComments(); }; diff --git a/js/render-thumbnails.js b/js/render-thumbnails.js index bb896da..4b1e1fd 100644 --- a/js/render-thumbnails.js +++ b/js/render-thumbnails.js @@ -1,14 +1,14 @@ import { openBigPicture } from './full-size-picture.js'; const picturesNode = document.querySelector('.pictures'); -const pictureTemplate = document.querySelector('#picture').content.querySelector('.picture'); +const pictureTemplateNode = document.querySelector('#picture').content.querySelector('.picture'); -if (!picturesNode || !pictureTemplate) { +if (!picturesNode || !pictureTemplateNode) { throw new Error('Не найден контейнер .pictures или шаблон #picture'); } const createPicture = ({id, url, description, comments, likes }) => { - const pictureNode = pictureTemplate.cloneNode(true); + const pictureNode = pictureTemplateNode.cloneNode(true); pictureNode.dataset.pictureId = id; const imageNode = pictureNode.querySelector('.picture__img'); diff --git a/js/upload-photo-form.js b/js/upload-photo-form.js index ff60c9c..41de9d1 100644 --- a/js/upload-photo-form.js +++ b/js/upload-photo-form.js @@ -20,7 +20,7 @@ const SubmitButtonText = { const bodyNode = document.body; const uploadFormNode = document.querySelector('.img-upload__form'); -const uploadFileControl = uploadFormNode.querySelector('#upload-file'); +const uploadFileControlNode = uploadFormNode.querySelector('#upload-file'); const uploadOverlayNode = uploadFormNode.querySelector('.img-upload__overlay'); const uploadCancelButtonNode = uploadFormNode.querySelector('#upload-cancel'); const hashtagsInputNode = uploadFormNode.querySelector('.text__hashtags'); @@ -62,10 +62,7 @@ const resetPreview = () => { }; const resetUploadPhotoForm = () => { - uploadFormNode.reset(); - uploadFileControl.value = ''; - // pristine.reset(); - // resetImageEditor(); + uploadFileControlNode.value = ''; resetFormFields(); resetPreview(); }; @@ -100,7 +97,7 @@ const closeUploadPhotoForm = () => { }; const onUploadFileControlChange = () => { - const file = uploadFileControl.files[0]; + const file = uploadFileControlNode.files[0]; if (!file) { return; @@ -153,11 +150,6 @@ const onUploadFormSubmit = (evt) => { } }; -const onUploadFormReset = (evt) => { - evt.preventDefault(); - closeUploadPhotoForm(); -}; - pristine.addValidator( hashtagsInputNode, hasValidHashtagFormat, @@ -189,10 +181,9 @@ pristine.addValidator( ); const initUploadPhotoForm = () => { - uploadFileControl.addEventListener('change', onUploadFileControlChange); + uploadFileControlNode.addEventListener('change', onUploadFileControlChange); uploadCancelButtonNode.addEventListener('click', onUploadCancelButtonClick); uploadFormNode.addEventListener('submit', onUploadFormSubmit); - uploadFormNode.addEventListener('reset', onUploadFormReset); hashtagsInputNode.addEventListener('keydown', stopEscapePropagation); descriptionInputNode.addEventListener('keydown', stopEscapePropagation); }; diff --git a/js/util.js b/js/util.js index e246afd..9654357 100644 --- a/js/util.js +++ b/js/util.js @@ -5,7 +5,7 @@ const debounce = (callback, timeoutDelay = 500) => { return (...rest) => { clearTimeout(timeoutId); - timeoutId = setTimeout(() => callback.apply(this, rest), timeoutDelay); + timeoutId = setTimeout(() => callback(...rest), timeoutDelay); }; };