[Разработка веб-сайтов, JavaScript, SvelteJS] Компилируем Svelte в уме. Часть 1/3 (перевод)
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
ВведениеДавайте вспомним как мы пишем веб-приложения без фреймворков:Создаем элемент
// создаем элемент h1
const h1 = document.createElement('h1');
h1.textContent = 'Hello World';
// ...и добавляем его в body
document.body.appendChild(h1);
Обновляем элемент
// обновляем текст элемента h1
h1.textContent = 'Bye World';
Удаляем элемент
// наконец, мы удаляем элемент h1
document.body.removeChild(h1);
Добавляем стили к элементу
const h1 = document.createElement('h1');
h1.textContent = 'Hello World';
// добавляем класс к элементу h1
h1.setAttribute('class', 'abc');
// ...и добавляем тег <style> в head
const style = document.createElement('style');
style.textContent = '.abc { color: blue; }';
document.head.appendChild(style);
document.body.appendChild(h1);
Слушаем события click на элементе
const button = document.createElement('button');
button.textContent = 'Click Me!';
// слушаем событие click
button.addEventListener('click', () => {
console.log('Hi!');
});
document.body.appendChild(button);
На чистом JavaScript нам нужно написать что-то подобное.Основная цель данной статьи в том чтобы показать как компилятор Svelte преобразует синтаксис Svelte в блоки кода, которые я показал выше.Синтаксис SvelteДалее я покажу базовые примеры Svelte синтаксиса.
Если вы хотите узнать подробнее, рекомендую попробовать интерактивный Svelte туториал.
Итак, вот простейший компонент Svelte:
<h1>Hello World</h1>
Svelte REPLДля добавления стилей, нужно добавить тег <style>:
<style>
h1 {
color: rebeccapurple;
}
</style>
<h1>Hello World</h1>
Svelte REPLНа этом этапе написание Svelte компонента ощущается аналогично тому как мы пишем обычный HTML, потому что синтаксис Svelte является надмножеством HTML синтаксиса.Давайте посмотрим, как мы добавляем данные в наш компонент:
<script>
let name = 'World';
</script>
<h1>Hello {name}</h1>
Svelte REPLМы помещаем переменную JavaScript в фигурные скобки.Чтобы добавить обработчик клика, мы используем директиву on:
<script>
let count = 0;
function onClickButton(event) {
console.log(count);
}
</script>
<button on:click={onClickButton}>Clicked {count}</button>
Svelte REPLДля изменения данных мы используем операторы присваивания:
<script>
let count = 0;
function onClickButton(event) {
count += 1;
}
</script>
<button on:click={onClickButton}>Clicked {count}</button>
Svelte REPLДавайте посмотрим как синтаксис Svelte компилируется в JavaScript, который мы видели ранее.Компилируем Svelte в умеКомпилятор Svelte анализирует код, который вы пишете и генерирует оптимизированный JavaScript.Чтобы понять, как Svelte компилирует код, давайте начнем с наименьшего возможного примера и постепенно будем усложнять его. В процессе вы увидите, что именно Svelte добавляет к конечному коду на основе наших изменений.Первый пример на который мы посмотрим:
<h1>Hello World</h1>
Svelte REPLКод который получится на выходе:
function create_fragment(ctx) {
let h1;
return {
c() {
h1 = element('h1');
h1.textContent = 'Hello world';
},
m(target, anchor) {
insert(target, h1, anchor);
},
d(detaching) {
if (detaching) detach(h1);
},
};
}
export default class App extends SvelteComponent {
constructor(options) {
super();
init(this, options, null, create_fragment, safe_not_equal, {});
}
}
Мы можем разделить данный код на 2 части:
- create_fragment
- class App extends SvelteComponent
create_fragmentКомпоненты Svelte - это строительные блоки приложения Svelte. Каждый компонент Svelte фокусируется на построении своей части или фрагменте финального DOM дерева.Функция create_fragment дает компоненту Svelte руководство по созданию фрагмента DOM дерева.Посмотрите на возвращаемый объект функции create_fragment. В нем есть такие методы, как:
- c()
Сокращенно от create. Содержит инструкции по созданию всех элементов во фрагменте.В этом примере метод содержит инструкции по созданию элемента h1:
h1 = element('h1');
h1.textContent = 'Hello World';
- m(target, anchor)
Сокращенно от mount. Содержит инструкции для вставки элементов в указанную цель.В этом примере метод содержит инструкции по вставке элемента h1 в target:
insert(target, h1, anchor);
// http://github.com/sveltejs/svelte/tree/master/src/runtime/internal/dom.ts
export function insert(target, node, anchor) {
target.insertBefore(node, anchor || null);
}
- d(detaching)
Сокращенно от destroy. Содержит инструкции по удалению элементов из указанной цели.В этом примере мы удаляем элемент h1 из DOM дерева:
detach(h1);
// http://github.com/sveltejs/svelte/tree/master/src/runtime/internal/dom.ts
function detach(node) {
node.parentNode.removeChild(node);
}
Имена методов сокращены для лучшей минификации. Посмотрите, что не может быть минифицированно.export default class App extends SvelteComponentКаждый компонент - это класс, который вы можете импортировать и создать экземпляр через этот API.В конструкторе мы инициализируем компонент с информацией из которой он состоит, например create_fragment. Svelte будет передавать только необходимую информацию и удалять ее всякий раз, когда в этом нет необходимости.Попробуйте удалить тег <h1> и посмотрите, что произойдет с выводом:
<!-- empty -->
Svelte REPL
class App extends SvelteComponent {
constructor(options) {
super();
init(this, options, null, null, safe_not_equal, {});
}
}
Svelte передаст null вместо create_fragment!Функция init - это то место, где Svelte настраивает большинство внутренних частей, таких как:
- входные параметры компонента, ctx и контекст
- события жизненного цикла
- механизм обновления компонента
в самом конце Svelte вызывает create_fragment для создания и монтирования элементов в DOM.Если вы заметили, все внутренние состояния и методы привязаны к this.$$.Поэтому, если вы обращаетесь к свойству $$ компонента, вы подключаетесь к внутренним частям компонента. Вы предупреждены!Добавление данныхТеперь когда мы рассмотрели минимальный Svelte компонент, давайте посмотрим как добавление данных изменит скомпилированный код:
<script>
let name = 'World';
</script>
<h1>Hello {name}</h1>
Svelte REPLОбратите внимание на изменение вывода:
function create_fragment(ctx) {
// ...
return {
c() {
h1 = element('h1');
h1.textContent = `Hello ${name}`;},
// ...
};
}
let name = 'World';
class App extends SvelteComponent {
// ...
}
Некоторые наблюдения:
- то, что мы написали в теге <script>, перемещается на верхний уровень кода
- текстовое содержимое элемента h1 теперь является шаблонной строкой
Прямо сейчас под капотом происходит много интересных вещей, но давайте немного подождем, потому что это лучше всего объясняется при сравнении со следующим изменением кода.Обновление данныхДавайте добавим функцию для обновления имени:
<script>
let name = 'World';
function update() {
name = 'Svelte';
}
</script>
<h1>Hello {name}</h1>
Svelte REPL… и посмотрим на изменение скомпилированного кода:
function create_fragment(ctx) {
return {
c() {
h1 = element('h1');
t0 = text('Hello ');
t1 = text(/*name*/ ctx[0]);
},
m(target, anchor) {
insert(target, h1, anchor);
append(h1, t0);
append(h1, t1);
},
p(ctx, [dirty]) {
if (dirty & /*name*/ 1) set_data(t1, /*name*/ ctx[0]);
},
d(detaching) {
if (detaching) detach(h1);
},
};
}
function instance($$self, $$props, $$invalidate) {
let name = 'World';
function update() {
$$invalidate(0, (name = 'Svelte'));
}
return [name];
}
export default class App extends SvelteComponent {
constructor(options) {
super();
init(this, options, instance, create_fragment, safe_not_equal, {});
}
}
Некоторые наблюдения:
- текстовое содержимое элемента <h1> теперь разбито на 2 текстовых узла, созданных функцией text (...)
- объект возвращаемый функцией create_fragment получил новый метод p(ctx, dirty)
- появилась новая функция instance
- то что мы написали в теге script было перенесено в функцию instance
- имя переменной, которое использовалось в create_fragment, теперь заменено на ctx[0]
Почему произошли такие изменения?Компилятор Svelte отслеживает все переменные, объявленные в теге <script>.Он отслеживает следующие факторы переменной:
- может быть изменена? например: count++
- может быть переназначена? например: name = 'Svelte'
- на переменную ссылаются в шаблоне? например <h1>Hello {name}</h1>
- доступна для записи? например const i = 1; или let i = 1;
- ... и многое другое
Когда компилятор Svelte понимает, что имя переменной можно переназначить (из-за name = 'Svelte'; при обновлении), он разбивает текстовое содержимое h1 на части, чтобы он мог динамически обновлять часть текста.И в самом деле, вы можете видеть, что есть новый метод p для обновления текстового узла.
- p(ctx, dirty)
Сокращенно от u_p_datep(ctx, dirty) содержит инструкции по обновлению элементов в зависимости от того, что изменилось в состоянии (dirty) и состоянии (ctx) компонента.Функция instanceКомпилятор понимает, что имя переменной не может использоваться в разных экземплярах компонента App.Вот почему он перемещает объявление имени переменной в функцию с именем instance.В предыдущем примере, независимо от того, сколько экземпляров компонента App, значение имени переменной одинаково и не изменяется во всех экземплярах:
<App />
<App />
<App />
<!-- выведет -->
<h1>Hello world</h1>
<h1>Hello world</h1>
<h1>Hello world</h1>
Но в этом примере переменную name можно изменить в пределах 1 экземпляра компонента, поэтому объявление этой переменной теперь перемещено в функцию instance:
<App />
<App />
<App />
<!-- может быть -->
<h1>Hello world</h1>
<h1>Hello Svelte</h1>
<h1>Hello world</h1>
<!-- в зависимости от внутреннего состояния компонента -->
instance($$self, $$props, $$invalidate)Функция instance возвращает список переменных компонента:
- на которые ссылаются в шаблоне
- которые могут быть изменены или переназначены в рамках экземпляра компонента
В Svelte мы называем этот список переменных ctx.В функции init Svelte вызывает функцию instance для создания ctx и использует его при создания фрагмента для компонента:
// концептуально,
const ctx = instance(/*...*/);
const fragment = create_fragment(ctx);
// создаем фрагмент
fragment.c();
// монтируем фрагмент в DOM дерево
fragment.m(target);
Теперь вместо доступа к переменной name вне компонента мы ссылаемся на name, переданную через ctx:
t1 = text(/* name */ ctx[0]);
Причина, по которой ctx является массивом, а не коллекцией Map или объектом, связана с оптимизацией. Вы можете увидеть обсуждение этого здесь.$$invalidateСекрет системы реактивности в Svelte - кроется в функции $$invalidate.Для каждой переменной, которая была
- переназначена или изменена
- упомянута в шаблоне
будет вставлена функция $$invalidate сразу после присвоения или изменения:
name = 'Svelte';
count++;
foo.a = 1;
// скомпилируется в примерно такой код
name = 'Svelte';
$$invalidate(/* name */, name);
count++;
$$invalidate(/* count */, count);
foo.a = 1;
$$invalidate(/* foo */, foo);
Функция $$invalidate отмечает переменную как грязную и планирует обновление для компонента:
// концептуально...
const ctx = instance(/*...*/);
const fragment = create_fragment(ctx);
// чтобы отслеживать, какая переменная изменилась
const dirty = new Set();
const $$invalidate = (variable, newValue) => {
// обновляем ctx
ctx[variable] = newValue;
// помечаем переменную как грязную
dirty.add(variable);
// планируем обновление для компонента
scheduleUpdate(component);
};
// вызывается, когда запланировано обновление
function flushUpdate() {
// обновить фрагмент
fragment.p(ctx, dirty);
// очистить список помеченных переменных
dirty.clear();
}
Добавляем слушатели событийТеперь добавим слушателя событий
<script>
let name = 'world';
function update() {
name = 'Svelte';
}
</script>
<h1 on:click={update}>Hello {name}</h1>
Svelte REPLИ обратите внимание на разницу:
function create_fragment(ctx) {
// ...
return {
c() {
h1 = element('h1');
t0 = text('Hello ');
t1 = text(/*name*/ ctx[0]);
},
m(target, anchor) {
insert(target, h1, anchor);
append(h1, t0);
append(h1, t1);
dispose = listen(h1, 'click', /*update*/ ctx[1]);},
p(ctx, [dirty]) {
if (dirty & /*name*/ 1) set_data(t1, /*name*/ ctx[0]);
},
d(detaching) {
if (detaching) detach(h1);
dispose();},
};
}
function instance($$self, $$props, $$invalidate) {
let name = 'world';
function update() {
$$invalidate(0, (name = 'Svelte'));
}
return [name, update];}
// ...
Некоторые наблюдения:
- функция instance теперь возвращает 2 переменных вместо одной
- добавлен вызов listen в функции mount и dispose в функции destroy
Как я упоминал ранее, функция instance возвращает переменные, на которые есть ссылка в шаблоне и которые изменены или переназначены.Поскольку мы только что сослались на функцию update в шаблоне, теперь она возвращается в функции instance как часть ctx.Svelte пытается сгенерировать как можно более компактный вывод JavaScript, не возвращая лишнюю переменную, если в этом нет необходимости.listen и disposeКаждый раз, когда вы добавляете слушатель событий в шаблоне, Svelte добавляет соответствующий слушатель и удаляет его, когда фрагмент удаляется из DOM.Попробуем добавить больше слушателей событий,
<h1
on:click={update}
on:mousedown={update}
on:touchstart={update}>
Hello {name}!
</h1>
Svelte REPLи посмотрим на вывод компилятора:
// ...
dispose = [
listen(h1, 'click', /*update*/ ctx[1]),
listen(h1, 'mousedown', /*update*/ ctx[1]),
listen(h1, 'touchstart', /*update*/ ctx[1], { passive: true }),
];
// ...
run_all(dispose);
Вместо того чтобы объявлять и создавать новую переменную для удаления каждого слушателя, Svelte присваивает их в массив:
// вместо вот такого
dispose1 = listen(h1, 'click', /*update*/ ctx[1]);
dispose2 = listen(h1, 'mousedown', /*update*/ ctx[1]);
dispose2 = listen(h1, 'touchstart', /*update*/ ctx[1], { passive: true });
// ...
dispose1();
dispose2();
dispose3();
Минификация может сжать имя переменной, но скобки убрать нельзя.Опять же, это еще один отличный пример того, как Svelte пытается сгенерировать компактный вывод JavaScript. Svelte не создает массив dispose, если имеется только один слушатель событий.ИтогоСинтаксис Svelte - это надмножество HTML.Когда вы пишете компонент Svelte, компилятор анализирует ваш код и генерирует оптимизированный JavaScript код.Который можно разделить на 3 сегмента:1. create_fragment
- Возвращает фрагмент, который представляет собой инструкцию по созданию фрагмента DOM для компонента.
2. instance
- Большая часть кода, написанного в теге <script>, находится здесь.
- Возвращает список переменных экземпляра, на которые есть ссылка в шаблоне.
- $$invalidate вставляется после каждого присваивания и изменения переменной экземпляра
3. class App extends SvelteComponent
- Инициализирует компонент с помощью create_fragment и instance
- Устанавливает внутренние части компонента
- Предоставляет API компонента
Svelte стремится создать как можно более компактный JavaScript, например:
- Разбиение текстового содержимого h1 на отдельные текстовые узлы только тогда, когда часть текста может быть обновлена
- Не определяет create_fragment или instance, когда это не нужно
- Генерирует dispose как массив или функцию, в зависимости от количества слушателей событий.
- ...
ЗаключениеМы рассмотрели базовую структуру кода, которую генерирует компилятор Svelte и это только начало.Надеюсь данный материал был для вас полезен!
===========
Источник:
habr.com
===========
===========
Автор оригинала: Tan Li Hau
===========Похожие новости:
- [Разработка веб-сайтов, JavaScript, Программирование, Проектирование и рефакторинг] Как я реализовал MVC в JavaScript (перевод)
- [JavaScript, Программирование, Java, Микросервисы] Мониторинг бизнес-процессов Camunda
- [Разработка веб-сайтов] Перед коммитом
- [Веб-дизайн, Разработка веб-сайтов, Интерфейсы, Usability, Дизайн] Вспоминаем все важные события в UI/UX дизайне за 2020-й
- [Разработка веб-сайтов, JavaScript, HTML, ReactJS] React.js — формошлепство или работа с формами при помощи пользовательских хуков
- [Ненормальное программирование, JavaScript, Google Chrome, PDF] Пугающие эксперименты с PDF: запускаем «Арканоид» в документе (перевод)
- [JavaScript, TensorFlow] Фронтендер пишет нейронки. Уровень сложности «хочу на ручки»
- [JavaScript, Программирование, Atlassian] Как я подружил BPMN и Bitbucket
- [Программирование, Разработка игр, WebGL, Прототипирование, Godot] Как собрать паука в Godot, Unigine или PlayCanvas
- [JavaScript, Программирование, HTML, Node.JS] Что такое рендеринг на стороне сервера и нужен ли он мне? (перевод)
Теги для поиска: #_razrabotka_vebsajtov (Разработка веб-сайтов), #_javascript, #_sveltejs, #_svelte, #_kompiljatory (компиляторы), #_javascript, #_nikto_ne_chitaet_tegi (никто не читает теги), #_razrabotka_vebsajtov (
Разработка веб-сайтов
), #_javascript, #_sveltejs
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 12:39
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
ВведениеДавайте вспомним как мы пишем веб-приложения без фреймворков:Создаем элемент // создаем элемент h1
const h1 = document.createElement('h1'); h1.textContent = 'Hello World'; // ...и добавляем его в body document.body.appendChild(h1); // обновляем текст элемента h1
h1.textContent = 'Bye World'; // наконец, мы удаляем элемент h1
document.body.removeChild(h1); const h1 = document.createElement('h1');
h1.textContent = 'Hello World'; // добавляем класс к элементу h1 h1.setAttribute('class', 'abc'); // ...и добавляем тег <style> в head const style = document.createElement('style'); style.textContent = '.abc { color: blue; }'; document.head.appendChild(style); document.body.appendChild(h1); const button = document.createElement('button');
button.textContent = 'Click Me!'; // слушаем событие click button.addEventListener('click', () => { console.log('Hi!'); }); document.body.appendChild(button); Если вы хотите узнать подробнее, рекомендую попробовать интерактивный Svelte туториал.
<h1>Hello World</h1>
<style>
h1 { color: rebeccapurple; } </style> <h1>Hello World</h1> <script>
let name = 'World'; </script> <h1>Hello {name}</h1> <script>
let count = 0; function onClickButton(event) { console.log(count); } </script> <button on:click={onClickButton}>Clicked {count}</button> <script>
let count = 0; function onClickButton(event) { count += 1; } </script> <button on:click={onClickButton}>Clicked {count}</button> <h1>Hello World</h1>
function create_fragment(ctx) {
let h1; return { c() { h1 = element('h1'); h1.textContent = 'Hello world'; }, m(target, anchor) { insert(target, h1, anchor); }, d(detaching) { if (detaching) detach(h1); }, }; } export default class App extends SvelteComponent { constructor(options) { super(); init(this, options, null, create_fragment, safe_not_equal, {}); } }
h1 = element('h1');
h1.textContent = 'Hello World';
insert(target, h1, anchor);
// http://github.com/sveltejs/svelte/tree/master/src/runtime/internal/dom.ts export function insert(target, node, anchor) { target.insertBefore(node, anchor || null); }
detach(h1);
// http://github.com/sveltejs/svelte/tree/master/src/runtime/internal/dom.ts function detach(node) { node.parentNode.removeChild(node); } <!-- empty -->
class App extends SvelteComponent {
constructor(options) { super(); init(this, options, null, null, safe_not_equal, {}); } }
<script>
let name = 'World'; </script> <h1>Hello {name}</h1> function create_fragment(ctx) {
// ... return { c() { h1 = element('h1'); h1.textContent = `Hello ${name}`;}, // ... }; } let name = 'World'; class App extends SvelteComponent { // ... }
<script>
let name = 'World'; function update() { name = 'Svelte'; } </script> <h1>Hello {name}</h1> function create_fragment(ctx) {
return { c() { h1 = element('h1'); t0 = text('Hello '); t1 = text(/*name*/ ctx[0]); }, m(target, anchor) { insert(target, h1, anchor); append(h1, t0); append(h1, t1); }, p(ctx, [dirty]) { if (dirty & /*name*/ 1) set_data(t1, /*name*/ ctx[0]); }, d(detaching) { if (detaching) detach(h1); }, }; } function instance($$self, $$props, $$invalidate) { let name = 'World'; function update() { $$invalidate(0, (name = 'Svelte')); } return [name]; } export default class App extends SvelteComponent { constructor(options) { super(); init(this, options, instance, create_fragment, safe_not_equal, {}); } }
<App />
<App /> <App /> <!-- выведет --> <h1>Hello world</h1> <h1>Hello world</h1> <h1>Hello world</h1> <App />
<App /> <App /> <!-- может быть --> <h1>Hello world</h1> <h1>Hello Svelte</h1> <h1>Hello world</h1> <!-- в зависимости от внутреннего состояния компонента -->
// концептуально,
const ctx = instance(/*...*/); const fragment = create_fragment(ctx); // создаем фрагмент fragment.c(); // монтируем фрагмент в DOM дерево fragment.m(target); t1 = text(/* name */ ctx[0]);
name = 'Svelte';
count++; foo.a = 1; // скомпилируется в примерно такой код name = 'Svelte'; $$invalidate(/* name */, name); count++; $$invalidate(/* count */, count); foo.a = 1; $$invalidate(/* foo */, foo); // концептуально...
const ctx = instance(/*...*/); const fragment = create_fragment(ctx); // чтобы отслеживать, какая переменная изменилась const dirty = new Set(); const $$invalidate = (variable, newValue) => { // обновляем ctx ctx[variable] = newValue; // помечаем переменную как грязную dirty.add(variable); // планируем обновление для компонента scheduleUpdate(component); }; // вызывается, когда запланировано обновление function flushUpdate() { // обновить фрагмент fragment.p(ctx, dirty); // очистить список помеченных переменных dirty.clear(); } <script>
let name = 'world'; function update() { name = 'Svelte'; } </script> <h1 on:click={update}>Hello {name}</h1> function create_fragment(ctx) {
// ... return { c() { h1 = element('h1'); t0 = text('Hello '); t1 = text(/*name*/ ctx[0]); }, m(target, anchor) { insert(target, h1, anchor); append(h1, t0); append(h1, t1); dispose = listen(h1, 'click', /*update*/ ctx[1]);}, p(ctx, [dirty]) { if (dirty & /*name*/ 1) set_data(t1, /*name*/ ctx[0]); }, d(detaching) { if (detaching) detach(h1); dispose();}, }; } function instance($$self, $$props, $$invalidate) { let name = 'world'; function update() { $$invalidate(0, (name = 'Svelte')); } return [name, update];} // ...
<h1
on:click={update} on:mousedown={update} on:touchstart={update}> Hello {name}! </h1> // ...
dispose = [ listen(h1, 'click', /*update*/ ctx[1]), listen(h1, 'mousedown', /*update*/ ctx[1]), listen(h1, 'touchstart', /*update*/ ctx[1], { passive: true }), ]; // ... run_all(dispose); // вместо вот такого
dispose1 = listen(h1, 'click', /*update*/ ctx[1]); dispose2 = listen(h1, 'mousedown', /*update*/ ctx[1]); dispose2 = listen(h1, 'touchstart', /*update*/ ctx[1], { passive: true }); // ... dispose1(); dispose2(); dispose3();
=========== Источник: habr.com =========== =========== Автор оригинала: Tan Li Hau ===========Похожие новости:
Разработка веб-сайтов ), #_javascript, #_sveltejs |
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 12:39
Часовой пояс: UTC + 5