Все потоки
Поиск
Написать публикацию
Обновить
219.08

JavaScript *

Прототипно-ориентированный язык программирования

Сначала показывать
Порог рейтинга

Опубликовано исследование о том что индексирование сайтов поисковиком (Google) не зависит от того, SPA ли это или же он SSR. Также пару лет назад делал аналогичное расследование и пришел к тому же выводу.

Вообще, мы пришли к идеалу достаточно давно - когда сервер занимается своими делами, а клиент статический, минифицирован, и раздается из CDN для быстроты и без траты ресурсов сервера.

https://vercel.com/blog/how-google-handles-javascript-throughout-the-indexing-process

Теги:
Всего голосов 2: ↑2 и ↓0+2
Комментарии9

ИИ-ассистенты (промпты) - это тоже low-code. Бизнес-аналитики сами разрабатывают ИТ-продукты. Сайт какой сложности можно разработать без знания JavaScript? Кейс app.2lead.ru

Возможности low-code растут. Бизнес-аналитики сами разрабатывают ИТ-продукты, фактически добавляя к своим обязанностям часть функций программистов. При этом эксперты не прогнозируют, что low-code полностью заменит традиционное программирование в ближайшие годы.

ИИ-помощники (промпты) - это тоже low-code

Чаще всего low-code определяют как создание ПО с помощью графических интерфейсов и настроек, а не через традиционное программирование. Мне ближе более широкое определение: low-code – это системы и платформы, которые предполагают изменение, дополнение кода пользователем, в том числе с применением ИИ-помощников (промптов).

Кейс: Сервисы app.2lead.ru разработаны бизнес-аналитиком

ИИ-ассистенты значительно ускорили разработку сервисов app.2lead.ru, созданных мной на JavaScript - без опыта программирования на этом языке. Это подтверждает возможность разработки ПО силами бизнес-аналитиков, продуктовых менеджеров и так далее. Ранее это была прерогатива только профессиональных программистов. 

Сервисы app.2lead.ru разработаны в конце 2024 года – начале 2025 года. Вошли в топ-5 Яндекса по запросу «калькулятор инфляции». Уже увеличили органический трафик сайта 2lead.ru более чем в 5 раз, без расходов на рекламу. // См. также «Нерекламные методы продвижения» https://habr.com/ru/articles/686736/

Сервисы app.2lead.ru // Калькуляторы инфляции. Аналитика. Прогнозы
Сервисы app.2lead.ru // Калькуляторы инфляции. Аналитика. Прогнозы

Что дальше? Массовое применение таких методов разработки ПО, более совершенные low-code системы и программирование голосом? Считаю, что практику low-code нужно расширять, разрабатывать учебные материалы по этому направлению и т.д. В этих инструментах есть большое пространство для развития.

Теги:
Всего голосов 4: ↑0 и ↓4-4
Комментарии0

На картинке — код на JavaScript с ошибкой, который написал специально для Хабра руководитель группы разработки интерфейсов компании «Криптонит» Василий Беляев.

Кто увидел, где ошибка и как её можно решить — пишите в комментариях! А решение этой ошибки оставили под картинкой.

Где здесь ошибка?
Где здесь ошибка?

Если запустим этот код, то получим ошибку

Uncaught TypeError: this.clearHistory is not a function

Проблема находится на строчках 11-13 (на картинке)

this.timeout = setTimeout (function () {
        this.clearHistory();
    }, 0);

Когда мы вызываем setTimeout, то вызываем метод объекта window, и меняем область видимости с нашего конструктора на window ( window.setTimeout() ).

Именно по этой причине у нас метод clearHistory пытается вызываться у объекта window ( window.clearHistory() )

Как можно решить эту проблему?

Вариант 1

const MyItem = function () {
    this.clearStorage = function () {
        console.log ('Очищаем хранилища...');
    };
    this.clearHistory = function () {
        console.log ('Очищаем историю...');
    };
};
MyItem.prototype.refresh = function () {
    this.clearStorage();
    this.timeout = setTimeout (() => {
        this.clearHistory();
    }, 0);
};
const mуItem = new MyItem();
mуItem.refresh();

Заменить анонимную функцию на стрелочную функцию. У них отсутствует свой контекст, и они работают в контексте области видимости, включающий их самих.

Вариант 2

const MyItem = function () {
    this.clearStorage = function () {
        console.log ('Очищаем хранилища...');
    };
    this.clearHistory = function () {
        console.log ('Очищаем историю...');
    };
};
MyItem.prototype.refresh = function () {
    this.clearStorage();
    this.timeout = setTimeout (
        this.clearHistory.bind(this), 0
    );
};
const mуItem = new MyItem();
mуItem.refresh();

Явно передать контекст через метод .bind()

Вариант 3

const MyItem = function () {
    this.clearStorage = function () {
        console.log ('Очищаем хранилища...');
    };
    this.clearHistory = function () {
        console.log ('Очищаем историю...');
    };
};
MyItem.prototype.refresh = function () {
    this.clearStorage();
    const self = this;
    this.timeout = setTimeout (() => {
        self.clearHistory();
    }, 0);
};
const mуItem = new MyItem();
mуItem.refresh();

Выносим контекст в отдельную переменную и работаем с ней. Этот вариант подойдет для поддержки старых браузеров.

А как бы вы решили эту ошибку?

Теги:
Всего голосов 6: ↑3 и ↓3+2
Комментарии5

Регулярно на Хабре выходят статьи с рекомендацией использовать moment.js. В комментариях обязательно начинают советовать какой-нибудь dayjs или js-joda, но не потому, что они чем-то сильно лучше, а потому, что первый задепрекейчен авторами.. в пользу luxon.

Что за мания такая у JS-еров использовать раздутые тормозные библиотеки? Есть же быстрый и миниатюрный $mol_time с гораздо более удобным и функциональным API, почти полностью поддерживающим ISO8601, в отличие от всех остальных библиотек.

Бенчмарки говорят сами за себя
Бенчмарки говорят сами за себя

Что мотивирует людей довольствоваться не самым лучшим решением в индустрии? Я, наверно, странный, но я не могу этого понять.

Теги:
Всего голосов 18: ↑7 и ↓11-2
Комментарии39

Полезные ресурсы для начинающего JavaScript-разработчика

Делимся полезными материалами, которые помогут разобраться в основах и сделать первые шаги в программировании на JavaScript. 

«Зачем учить JavaScript» — объясняем на примерах, почему без JavaScript не будет современных сайтов и что делает этот язык программирования таким востребованным. Расскажем о преимуществах JS и его перспективах.

«Полезные ресурсы для начинающего разработчика JavaScript» — делимся подборкой курсов, статей, каналов и книг, которые помогут развиваться в профессии.

«Что такое TypeScript и чем он отличается от JavaScript» — у главного языка фронтенда JavaScript есть улучшенная версия — TypeScript. Рассказываем, как он работает и чем отличается от старой версии.

«Всё про JavaScript» — большая подборка статей про основы JavaScript, возможности его использования и типичные ошибки новичков. Также в подборке можно найти задачи для собеседований и туториалы разных проектов.

Теория — это хорошо, а для практики подойдёт бесплатная часть курса «Фронтенд-разработчик». В ней вы изучите базовый синтаксис HTML и CSS, попробуете размещать блоки на веб-странице, менять шрифт и цвета, а также напишете с нуля простую программу на JavaScript.

Теги:
Рейтинг0
Комментарии0

Стоит ли идти во фронтенд сейчас? Честный ответ разработчика

Артем несколько лет в сфере (вот его история). Сейчас он разработчик в крупном финтех-проекте. Вот его мысли:

— Да, фронтенд перенасыщен. Фреймворков много, технологии постоянно меняются. Все говорят об одном, но пишут по-разному. Но именно это и держит в тонусе — приходится регулярно обновлять знания. Сожалею ли я о своем выборе? Нет. Всегда любил погружаться в математические задачи, а фронтенд затягивает. Можно сутки биться над багом, ненавидеть его, плеваться… А потом решить — и словить кайф. В такие моменты код полностью поглощает, заставляя забыть о сне и еде.

Стоит ли идти во фронтенд сейчас?

— Никогда не поздно. Да, путь у каждого свой, и рынок сейчас нестабилен. Но при глубоком погружении за год можно набрать нужные скиллы и попасть на коммерческий проект.

🔥 Открыт набор на новый марафон!

Сейчас в Clevertec проходит марафон для начинающих фронтенд‑разработчиков. Это возможность погрузиться в профессию, получить реальный опыт и, возможно, стать частью команды. Участие бесплатное. Успей зарегистрироваться!

Теги:
Всего голосов 3: ↑1 и ↓2-1
Комментарии1

Запускаем бесплатный онлайн-марафон по фронтенд-разработке. Будет как в «Рокки» 

Спринты, дедлайны и финальный тест-бой. Кто пройдет с нами три месяца подготовки — получит шанс на титул. Без метафор — мы реально ищем таланты и готовы взять их на стажировку.

Как записаться?

Заполни анкету по ссылке в профиле и скинь другу. Заявки принимаем до 26 марта.

Что будет?

Интенсивный 3-месячный тренинг. Это бесплатно. По сути, ты будешь поэтапно с дедлайнами писать приложение. Каждый спринт проверяем с помощью автотестов. Плюс будет поддержка менторов, наших крепких разработчиков. Это реальная прокачка твоих навыков и готовый проект в резюме.

Это уже четвертый марафон — после каждого наша команда фронтенд-разработчиков растет.

Кого ждём?

Начинающих веб-разработчиков (JS, React), которые уже изучали теорию и хотят прокачаться на практике в условиях, максимально близких к реальному проекту. Главное — желание кодить. Подойдут:

- студенты профильных вузов

- выпускники курсов

- самоучки

Важное условие: приглашаем участников из Беларуси и России.

Что дальше?

После 26 марта отправим на почту инструкции и первые задания. Старт марафона 1 апреля (это не шутка). До связи, Рокки. 

Теги:
Всего голосов 4: ↑3 и ↓1+4
Комментарии0

SRE PUB #5 Судный день

Интерактивный стримкаст со зрителями 9 марта в 19:00

Максим будет писать веб интерфейс для вики (с выделением текста и возможностью отставлять инлайн комментарии) с помощью последней версии Copilot agent.
А все мы (включая вас) — токсично (и не очень) комментировать.

Посмотреть нас можно будет на следующих платформах:
Youtube - https://www.youtube.com/@srepubstreamcast
Twitch - https://www.twitch.tv/srepubstreamcast
Telegram - https://t.me/sre_pub

Готовьте попкорн, напитки, хорошее настроение и колкие комментарии.

Пишите в календари: 9 марта, 19:00

Теги:
Всего голосов 3: ↑3 и ↓0+3
Комментарии0

База по JavaScript

Если вы только начинаете (или хотите начать) изучать JavaScript, то для вас есть подборка обучающих материалов. Все они доступны бесплатно. Регистрироваться, оставлять контактные данные или оформлять подписку тоже не нужно.

Если вы новичок, после прочтения материалов можете попробовать решить задачу про JavaScript-функцию (чур, не подглядывать в решение).

Теги:
Всего голосов 2: ↑2 и ↓0+2
Комментарии0

Новые фичи JS, о которых стоит знать

Если долго использовать какой-то инструмент, мозг вырабатывает паттерны: мы повторяем одни и те же решения, не всегда задумываясь, что появились новые, более удобные способы. Так происходит и с JavaScript.

JS развивается каждый год, но многие продолжают писать код, как привыкли, не используя свежие возможности языка. Я стараюсь бороться с этим и внедрять новшества в свою повседневную работу и недавно пошел посмотреть что там нового подвезли в ECMAScript 2024 (ES15). А там оказалось довольно много интересного.

Например, новые методы массивов. Раньше методы вроде .sort() и .reverse() изменяли исходный массив, что могло привести к неожиданным багам.

const arr = [4,1,6,5]
const sortedArr = arr.sort()
console.log(sortedArr) //[1,4,5,6]
console.log(arr) // [1,4,5,6] оригинал также изменился

В ECMAScript2024 (ES15) были добавлены новые методы, которые позволяют работать с копиями массивов не трогая оригинал без дополнительного кода:

const nums = [1, 2, 3];
const reversedNums = nums.toReversed();
console.log(reversedNums); // [3, 2, 1]
console.log(nums); // [1, 2, 3] оригинал не изменился

Точно так же работают новые методы .toSorted() и .toSpliced(). Они делают код более предсказуемым и чистым.

Также, я с огромным восхищением прочитал про Temporal - новый объект для работы с датами и временем. Например, меня всегда поражало , что в Date месяцы индексируются с 0 (январь – это 0), а вот дни месяца начинаются с 1. Temporal фиксит эти проблемы и теперь можно:
✅ Создавать объекты только с датой или только со временем
✅ Удобно складывать и вычитать даты
✅ Работать с часовыми поясами без боли
В общем делать все, для чего мы раньше тащили в проекты всякие moment и day.js.

К огромному сожалению Temporal пока не поддерживается в браузерах, но когда выйдет – работа с датами в JS станет в разы удобнее.

Если вам интересны такие обновления подписывайтесь на мой Telegram-канал!

🔗 t.me/+qbK9ZPuAocI2MWUy

Там делюсь продакшн-опытом,новостями из мира веб-разработки и разбираю реальные кейсы. Подписывайтесь! 🚀

Теги:
Всего голосов 5: ↑3 и ↓2+2
Комментарии2

flushSync в React – костыль или спасение?

Вчера впервые попробовал flushSyncб который подвезли в React еще в 18 версии.
Классное решение для определенных моментов! Выглядит так, будто React выдал нам костыль, но сразу предупредил: пользоваться с осторожностью.

❓ Почему это вообще нужно? (Для тех, кто не совсем в теме)
В React изменения в useState или в useEffect выглядят синхронными, но на самом деле они асинхронны.

Простой пример:

...

const [count, setCount] = useState(0);

console.log(count); // 0

setCount(1); console.log(count); // Всё ещё 0! 😲

...

Кажется, что setCount(1) сразу меняет count, но на самом деле новое значение попадёт в консоль только при следующем ререндере.

То же самое в useEffect:

...

useEffect(() => { console.log("Эффект сработал!"); }, [count]);

setCount(1); console.log("А это после setCount");

...

Лог "А это после setCount" появится в консоли раньше, чем "Эффект сработал!", потому что useEffect выполняется уже после рендера.

Как flushSync меняет поведение?

Обычно React группирует обновления (batching) и откладывает ререндер до конца текущего цикла. flushSync ломает это поведение и заставляет React сразу выполнить ререндер.

function Example() { 
    const [count, setCount] = React.useState(0); 
    const ref = React.useRef(null);
    const handleClick = () => { 
        flushSync(() => setCount(count + 1)); 
        console.log("Высота элемента:", 
            ref.current?.offsetHeight); 
        };

    return (<button onClick={handleClick}>
        Добавить  {count}
    </button>); 
} 

Что тут происходит?
Без flushSync React подождал бы до конца текущего вызова и только потом обновил DOM.
С flushSync обновление происходит немедленно, и console.log видит уже новый DOM.

React нас предупреждает
В документации прямо сказано:

"flushSync – это низкоуровневый API. Используйте его только тогда, когда вам действительно нужно измерить DOM сразу после обновления состояния."

Когда не стоит использовать flushSync?
Если можно обойтись обычными useEffect или useLayoutEffect.
Если batching работает нормально и не мешает.
Если нет необходимости немедленного ререндера (иначе можно уронить производительность).

Итог
flushSync – мощный инструмент, но использовать его нужно осознанно. Он нужен в случаях, когда важно немедленно обновить стейт и тут же прочитать DOM (например, для анимаций).

Если понравился пост присоединяйтесь к моему каналу в Telegram по ссылке https://t.me/+qbK9ZPuAocI2MWUy.
Там я делюсь своим опытом и пишу материалы которые будут полезны как новичкам, так и матерым разработчикам.

Теги:
Рейтинг0
Комментарии0

По итогам жарких обсуждений и критики по поводу медленного кода и плохого fps в тесте вывода на экран графика sin()+noise для Matplolib были внесены усовершенствования и привлечен ИИ для полировки. Исходная статья и код https://habr.com/ru/articles/878002/

Отказ от медленного вывода текста, применение FuncAnimation вместо простого цикла, применение мэджик команды для подключения PyQT backend. FPS поднялся с 12 до 35. Подробности читайте в исходной статье https://habr.com/ru/articles/878002/

Оригинальная идея второго графика позволила отказаться от медленного вывода текста
Оригинальная идея второго графика позволила отказаться от медленного вывода текста

м

Теги:
Всего голосов 1: ↑1 и ↓0+2
Комментарии7

Написана статья о тестировании (и сравнении FPS) на скорость рисования 2D графиков на python популярных и относительно малоизвестных графических пакетов 2D и 3D (Mayavi 3D, PyVista, Matplotlib, PyQTGraph, Plotly, PyGame, Arcade, pyOpenGL, VisPy, Bokeh) Возникли некоторые технические проблемы и срок публикации пока не ясен (надеюсь, на следующей неделе). Поэтому, заинтересовавшиеся коллеги, прошу подписаться на мой профиль на хабре, чтобы не пропустить публикацию этой статьи. В статье будут видео с отрисовкой в реальном времени 2D графиков и будут измерены FPS. Специально использовался слабенький мини ПК без дискретки. Тем не мене FPS достигал в некоторых случаях 100. Пример видео ниже:

https://habr.com/ru/articles/878002/

Файлы к статье

Теги:
Всего голосов 5: ↑3 и ↓2+2
Комментарии0

Ближайшие события

Признавайтесь, кто любил в детстве пострелять уток? Для скучающих по Nintendo товарищ jslegend написал веб-версию игры Duck Hunt (прежде, чем уходить играть, не забудьте поставить нам лайк) на JavaScript с использованием библиотеки KAPLAY.

Управление максимально простое — стрелять ЛКМ, а кнопка P ставит игру на паузу. Мышкой играть, конечно, не так удобно, но достаточно, чтобы вызвать ностальгическую улыбку.

Бросите вызов уткам и злодейски хихикающей собаке?

Теги:
Всего голосов 3: ↑3 и ↓0+3
Комментарии0

Разработчик под ником jslegend представил онлайн версию игры Duck Hunt в HTML5, написанную с помощью библиотеки JavaScript KAPLAY - Duck Hunter.

Игра доступна в браузерах на ПК, управление мышкой.

Теги:
Всего голосов 2: ↑2 и ↓0+2
Комментарии1

Как избавиться от всех костылей? Достаточно всего лишь одной функции...

Если считаете, что это кликбейт — настоятельно рекомендуем посмотреть доклад Марата Зимнурова, техлида Авито. В своем выступлении Марат раскрывает все подробности функционального программирования:

  • что это такое — функциональное программирование — на практике?

  • какие методы оттуда уже проникли в повседневную разработку? 

  • какие конкретные недостатки существуют для применения такого способа, в том числе в JavaScript?

В примерах — детальный разбор, как написать веб-приложения и бэкенды, используя практики функционального программирования.

Подписывайтесь на канал AvitoTech в Telegram, там мы рассказываем больше о профессиональном опыте наших инженеров, проектах и работе в Авито, а также анонсируем митапы и статьи.

Теги:
Всего голосов 22: ↑19 и ↓3+16
Комментарии0

Буквально на днях со мной произошла интересная история, которой я хочу поделится с Вами.

Я состою в одном чатике, где собрались энтузиасты Telegram Mini Apps. Там фаундеры, разработчики и просто интересующиеся обсуждают идеи, делятся опытом и помогают друг другу. Мне очень интересна эта тема, и я планирую запустить что-то своё на базе Telegram.

Но ближе к делу. 2 января в чат зашёл человек в поисках разработчика для своего проекта. Он выглядел весьма взволнованным и объяснил, что помимо долгосрочного сотрудничества ему срочно нужен фикс для его мини-приложения. Это небольшое приложение с игровой механикой, у которого Monthly Active Users (MAU) — около 30 тысяч. Впечатляет, согласитесь! Проблема заключалась в том, что текущие разработчики ушли на праздники и не выходили на связь.

Я решил попробовать помочь просто ради интереса. Сидел за компьютером, пытаясь дописать свою дипломную работу, а мозг требовал смены деятельности. Когда мне дали доступ к репозиториям, я увидел следующее:

  • Проект довольно крупный: около 10k строк кода на фронтенде и 5k строк на бэкенде.

  • Фронтенд написан на React с использованием Jotai для управления состоянием. Интерфейс реализован с помощью Tailwind CSS.

  • Бэкенд — Express.js, взаимодействующий с MongoDB.

Весь проект написан на чистом JavaScript, и вот что меня поразило:

  • Полное отсутствие типов. Никакого TypeScript, PropTypes или хотя бы JSDoc. После нескольких лет работы с TS мне было трудно принять, что я не могу быстро понять, какие аргументы ожидаются в функциях. Я думал что все крутые проекты используют TS, а JS только для маленьких проектов, обучения и чего-то не совсем серьезного.

  • Полное отсутствие безопасности. Захардкоженные ключи для подключения к телеграм боту и базе данных. Сервер обрабатывает запросы с любого origin. Это буквально учебник по тому, как не надо делать.

  • Нарушение принципов DRY. Например, в коде вручную прописывались заголовки для fetch-запросов — везде, где только можно.

  • Неоптимальный код. Неправильный вызов хуков, отсутствие lazy loading, дублирование логики и так далее. Я могу продолжать список почти бесконечно.

В общем, код был ужасен. Даже мои студенты, которые только начинают изучать программирование, иногда пишут лучше. Но! Несмотря на всё это, продукт работал, пользователи были довольны, и проект приносил прибыль.

Я быстро разобрался с багом, исправил его и в итоге получил предложение помочь улучшить проект. В ближайшие пару недель как раз займусь этим — заработаю сыну на шоколадки и, надеюсь, сделаю код немного лучше. А теперь — к выводам, которые я сделал (а точнее получил подтверждение в очередной раз):

Выводы:

  • Работа и подработка всегда найдутся. Если у вас есть знания и навыки, найти проект даже в текущее время— не проблема. Главное — свободное время и скилл. Я получил подтверждение этого тезиса в очередной раз и последние несколько лет я понимаю, что возможность зарабатывания денег ограничивается только наличием свободного времени для этого. Ну и осознав это я пытаюсь найти варианты, которые напрямую не связаны с обменом моего времени на деньги. Во многом моя попытка раскачать телеграмм канал именно об этом.

  • JS жив и приносит прибыль. Люди продолжают писать серьёзные проекты на JavaScript. Да, TypeScript стал стандартом в моей работе, но это не мешает другим писать на чистом JS и чувствовать себя отлично.

  • Маркетинг побеждает. Код, который я видел, был ужасен. Но приложение успешно благодаря сильному маркетингу. Это очередное подтверждение фразы: “Best products never win. But best sales & marketing always win.” Как программисты, мы должны стремиться к чистому коду и хорошей архитектуре, но успех продукта в конечном счёте определяется вовсе не этим.

А что вы думаете о роли маркетинга? Согласны ли вы, что он может перевесить техническую сторону?

Если вам интересны темы веб-разработки, тимлидства, управления проектами и вы хотите получать больше полезного контента, присоединяйтесь к моему Telegram-каналу.

Теги:
Всего голосов 13: ↑9 и ↓4+6
Комментарии12

Объектно-ориентированное программирование и его реализация в JavaScript

Именно так звучит тема нового выпуска нашего открытого курса по JavaScript. В 12 серии вместе с веб-разработчиком Василием Новиковым разберёмся:

  • что такое объектно-ориентированное программирование (ООП) и его функции;

  • как реализовывать ООП на конкретном примере;

  • как применять метод проверки getPrototypeOf.

Больше информации о сериях, а также полезных материалах к ним можно найти на сайте курса JS.

Подписывайтесь на канал AvitoTech в Telegram, там мы рассказываем больше о профессиональном опыте наших инженеров, проектах и работе в Авито, а также анонсируем митапы и статьи.

Теги:
Всего голосов 13: ↑13 и ↓0+13
Комментарии1

Немного о резолверах в Angular 19 (теперь в них есть редиректы).

В логике использования гвардов применяется подход:

проверить что-то, если все ок то вернуть true или кинуть редирект на другую страницу

Выглядит достаточно удобно. Но если мне не изменяет память в резолверах такого нет, вместо этого приходилось натягивать Router и рулить navigate или navigateByUrl и т.д.

В 19 же версии нам немного упростили жизнь и резолвер научили в RedirectCommand.

Пример с angular.dev

export const heroResolver: ResolveFn<Hero>= async (
  route: ActivatedRouteSnapshot,
  state: RouterStateSnapshot,
) => {
  const router = inject(Router);
  const heroService = inject(HeroService);
  
  try {
    return await heroService.getHero(route.paramMap.get('id')!);
  } catch {
    return new RedirectCommand(router.parseUrl('/404'));
  }
  
};

Ну просто сказка какая-то, а не только сигналы =)

Теги:
Всего голосов 2: ↑2 и ↓0+2
Комментарии0

Вклад авторов