From e3ffff5ee495de244ba7ad66ba56905c3622af78 Mon Sep 17 00:00:00 2001 From: Yevhenii Stuzhuk Date: Tue, 24 May 2022 09:00:46 +0300 Subject: [PATCH 01/70] Update article.md 2 chapters done --- 6-data-storage/03-indexeddb/article.md | 92 +++++++++++++------------- 1 file changed, 46 insertions(+), 46 deletions(-) diff --git a/6-data-storage/03-indexeddb/article.md b/6-data-storage/03-indexeddb/article.md index 546e24907..d860e193b 100644 --- a/6-data-storage/03-indexeddb/article.md +++ b/6-data-storage/03-indexeddb/article.md @@ -5,63 +5,63 @@ libs: # IndexedDB -IndexedDB is a database that is built into a browser, much more powerful than `localStorage`. +IndexedDB — це база даних, вбудована в браузер, набагато потужніша, ніж `localStorage`. -- Stores almost any kind of values by keys, multiple key types. -- Supports transactions for reliability. -- Supports key range queries, indexes. -- Can store much bigger volumes of data than `localStorage`. +- Зберігає майже будь-які значення за ключами, використовує кілька типів ключів. +- Підтримує надійність транзакцій. +- Підтримує запити за диапазоном ключів та індекси. +- Може зберігати набагато більші обсяги даних, ніж `localStorage`. -That power is usually excessive for traditional client-server apps. IndexedDB is intended for offline apps, to be combined with ServiceWorkers and other technologies. +Ця потужність зазвичай надмірна для традиційних програм клієнт-сервер. IndexedDB призначена для автономних додатків для поєднання із ServiceWorkers та іншими технологіями. -The native interface to IndexedDB, described in the specification , is event-based. +Нативний інтерфейс для IndexedDB, описаний у специфікації , базується на подіях. -We can also use `async/await` with the help of a promise-based wrapper, like . That's pretty convenient, but the wrapper is not perfect, it can't replace events for all cases. So we'll start with events, and then, after we gain an understanding of IndexedDb, we'll use the wrapper. +Ми також можемо використовувати `async/await` за допомогою обгортки на основі промісів, наприклад . Це досить зручно, але обгортка не ідеальна, вона не може замінити події для всіх випадків. Отже, ми почнемо з подій, а потім, коли зрозуміємо IndexedDb, скористаємося обгорткою. -```smart header="Where's the data?" -Technically, the data is usually stored in the visitor's home directory, along with browser settings, extensions, etc. +```smart header="Де зберігаються дані?" +Технічно дані зазвичай зберігаються в домашньому каталозі відвідувача разом з налаштуваннями браузера, розширеннями тощо. -Different browsers and OS-level users have each their own independant storage. +У різних браузерів і користувачів на рівні ОС є власне незалежне сховище. ``` -## Open database +## Відкрити базу даних -To start working with IndexedDB, we first need to `open` (connect to) a database. +Щоб почати працювати з IndexedDB, нам спочатку потрібно `відкрити` (підключитися до) бази даних. -The syntax: +Синтаксис: ```js let openRequest = indexedDB.open(name, version); ``` -- `name` -- a string, the database name. -- `version` -- a positive integer version, by default `1` (explained below). +- `name` -- рядок, ім'я бази даних. +- `version` -- версія є цілим числом, за замовчунням `1` (пояснення нижче). -We can have many databases with different names, but all of them exist within the current origin (domain/protocol/port). Different websites can't access each other's databases. +У нас може бути багато баз даних з різними іменами, але всі вони існують у поточному джерелі (домен/протокол/порт). Різні веб-сайти не мають доступу до баз даних один одного. -The call returns `openRequest` object, we should listen to events on it: -- `success`: database is ready, there's the "database object" in `openRequest.result`, we should use it for further calls. -- `error`: opening failed. -- `upgradeneeded`: database is ready, but its version is outdated (see below). +Виклик повертає об’єкт `openRequest`, ми повинні прослухати події на ньому: +- `success`: база даних готова, в `openRequest.result`є "об'єкт бази даних", ми повинні використовувати його для подальших викликів. +- `error`: не вдалося відкрити. +- `upgradeneeded`: база даних готова, але її версія застаріла (див. нижче). -**IndexedDB has a built-in mechanism of "schema versioning", absent in server-side databases.** +**IndexedDB має вбудований механізм «cхему контролю версій», який відсутній у серверних базах даних.** -Unlike server-side databases, IndexedDB is client-side, the data is stored in the browser, so we, developers, don't have full-time access to it. So, when we have published a new version of our app, and the user visits our webpage, we may need to update the database. +На відміну від серверних баз даних, IndexedDB є клієнтською, дані зберігаються в браузері, тому ми, розробники, не маємо до них постійного доступу. Отже, коли ми опублікували нову версію нашого застосунку, і користувач відвідує веб-сторінку, нам може знадобитися оновити базу даних. -If the local database version is less than specified in `open`, then a special event `upgradeneeded` is triggered, and we can compare versions and upgrade data structures as needed. +Якщо версія локальної бази даних менша за вказану в `open`, тоді запускається спеціальна подія `upgradeneeded`, і ми можемо порівнювати версії та оновлювати структури даних за потребою. -The `upgradeneeded` event also triggers when the database doesn't yet exist (technically, its version is `0`), so we can perform the initialization. +Подія `upgradeneeded` також запускається, коли база даних ще не існує (технічно її версія дорівнює `0`), тому ми можемо виконати ініціалізацію. -Let's say we published the first version of our app. +Скажімо, ми опублікували першу версію нашого застосунку. -Then we can open the database with version `1` and perform the initialization in an `upgradeneeded` handler like this: +Тоді ми можемо відкрити базу даних з версією `1` та виконати ініціалізацію в `upgradeneeded` обробника, таким чином: ```js let openRequest = indexedDB.open("store", *!*1*/!*); openRequest.onupgradeneeded = function() { - // triggers if the client had no database - // ...perform initialization... + // спрацьовує, якщо на клієнті немає бази даних + // ...виконати ініціалізацію... }; openRequest.onerror = function() { @@ -70,48 +70,48 @@ openRequest.onerror = function() { openRequest.onsuccess = function() { let db = openRequest.result; - // continue working with database using db object + // продовжити роботу з базою даних за допомогою об'єкта db }; ``` -Then, later, we publish the 2nd version. +Потім, пізніше, ми публікуємо 2-у версію. -We can open it with version `2` and perform the upgrade like this: +Ми можемо зробити це за допомогою версії `2` і виконати оновлення таким чином: ```js let openRequest = indexedDB.open("store", *!*2*/!*); openRequest.onupgradeneeded = function(event) { - // the existing database version is less than 2 (or it doesn't exist) + // існуюча версія бази даних менше 2 (або її не існує). let db = openRequest.result; - switch(event.oldVersion) { // existing db version + switch(event.oldVersion) { // існуюча версія БД case 0: - // version 0 means that the client had no database - // perform initialization + // версія 0 означає, що клієнт не мав бази даних + // виконати ініціалізацію case 1: - // client had version 1 - // update + // клієнт мав версію 1 + // оновлення } }; ``` -Please note: as our current version is `2`, the `onupgradeneeded` handler has a code branch for version `0`, suitable for users that are accessing for the first time and have no database, and also for version `1`, for upgrades. +Зверніть увагу: оскільки наша поточна версія `2`, обробник `onupgradeneeded` що має гілку коду для версії `0`, підходить для користувачів, які звертаються вперше і не мають бази даних, а також для версії `1`, для оновлення. -And then, only if `onupgradeneeded` handler finishes without errors, `openRequest.onsuccess` triggers, and the database is considered successfully opened. +І лише якщо обробник `onupgradeneeded` завершиться без помилок, запускається `openRequest.onsuccess`, і база даних вважається успішно відкритою. -To delete a database: +Щоб видалити базу даних: ```js let deleteRequest = indexedDB.deleteDatabase(name) -// deleteRequest.onsuccess/onerror tracks the result +// deleteRequest.onsuccess/onerror відстежує результат ``` -```warn header="We can't open a database using an older open call version" -If the current user database has a higher version than in the `open` call, e.g. the existing DB version is `3`, and we try to `open(...2)`, then that's an error, `openRequest.onerror` triggers. +```warn header="Ми не можемо відкрити базу даних за допомогою виклику для відкриття старішої версії" +Якщо поточна база даних користувача має вищу версію, ніж у виклику "open", напр. існуюча версія БД `3`, а ми намагаємося викликати `open(...2)`, тоді це помилка, і запускається `openRequest.onerror`. -That's rare, but such a thing may happen when a visitor loads outdated JavaScript code, e.g. from a proxy cache. So the code is old, but his database is new. +Це рідкість, але таке може статися, коли відвідувач завантажує застарілий код JavaScript, напр. з кешу проксі. Отже, код старий, але його база даних нова. -To protect from errors, we should check `db.version` and suggest a page reload. Use proper HTTP caching headers to avoid loading the old code, so that you'll never have such problems. +Щоб захиститися від помилок, ми повинні перевірити `db.version` і якщо потрібно запропонувати перезавантажити сторінку. Використовуйте правильні заголовки кешування HTTP, щоб уникнути завантаження старого коду та щоб у вас ніколи не виникло таких проблем. ``` ### Parallel update problem From ceb39461b38854fa7671210c828db255c9d0b3f6 Mon Sep 17 00:00:00 2001 From: Yevhenii Stuzhuk Date: Tue, 24 May 2022 20:15:59 +0300 Subject: [PATCH 02/70] Update article.md 251 --- 6-data-storage/03-indexeddb/article.md | 110 ++++++++++++------------- 1 file changed, 55 insertions(+), 55 deletions(-) diff --git a/6-data-storage/03-indexeddb/article.md b/6-data-storage/03-indexeddb/article.md index d860e193b..915151df7 100644 --- a/6-data-storage/03-indexeddb/article.md +++ b/6-data-storage/03-indexeddb/article.md @@ -34,13 +34,13 @@ IndexedDB — це база даних, вбудована в браузер, н let openRequest = indexedDB.open(name, version); ``` -- `name` -- рядок, ім'я бази даних. +- `name` -- рядок, ім’я бази даних. - `version` -- версія є цілим числом, за замовчунням `1` (пояснення нижче). У нас може бути багато баз даних з різними іменами, але всі вони існують у поточному джерелі (домен/протокол/порт). Різні веб-сайти не мають доступу до баз даних один одного. Виклик повертає об’єкт `openRequest`, ми повинні прослухати події на ньому: -- `success`: база даних готова, в `openRequest.result`є "об'єкт бази даних", ми повинні використовувати його для подальших викликів. +- `success`: база даних готова, в `openRequest.result`є "об’єкт бази даних", ми повинні використовувати його для подальших викликів. - `error`: не вдалося відкрити. - `upgradeneeded`: база даних готова, але її версія застаріла (див. нижче). @@ -70,7 +70,7 @@ openRequest.onerror = function() { openRequest.onsuccess = function() { let db = openRequest.result; - // продовжити роботу з базою даних за допомогою об'єкта db + // продовжити роботу з базою даних за допомогою об’єкта db }; ``` @@ -114,24 +114,24 @@ let deleteRequest = indexedDB.deleteDatabase(name) Щоб захиститися від помилок, ми повинні перевірити `db.version` і якщо потрібно запропонувати перезавантажити сторінку. Використовуйте правильні заголовки кешування HTTP, щоб уникнути завантаження старого коду та щоб у вас ніколи не виникло таких проблем. ``` -### Parallel update problem +### Проблема паралельного оновлення -As we're talking about versioning, let's tackle a small related problem. +Оскільки ми говоримо про версіоність, давайте розглянемо невеличку проблему пов’язану з цим. -Let's say: -1. A visitor opened our site in a browser tab, with database version `1`. -2. Then we rolled out an update, so our code is newer. -3. And then the same visitor opens our site in another tab. +Скажімо: +1. Відвідувач відкрив наш сайт у вкладці браузера з версією бази даних `1`. +2. Потім ми випустили оновлення, тож наш код новіший. +3. А потім той же відвідувач відкриває наш сайт в іншій вкладці. -So there's a tab with an open connection to DB version `1`, while the second one attempts to update it to version `2` in its `upgradeneeded` handler. +Отже, є вкладка з відкритим підключенням до БД версії `1`, а друга намагається оновити її до версії `2` у своєму обробнику `upgradeneeded`. -The problem is that a database is shared between two tabs, as it's the same site, same origin. And it can't be both version `1` and `2`. To perform the update to version `2`, all connections to version 1 must be closed, including the one in the first tab. +Проблема полягає в тому, що база даних є спільною між двома вкладками, оскільки це той самий сайт, з того самого джерела. І це не може бути одночасно версія `1` і `2`. Щоб виконати оновлення до версії `2`, усі підключення до версії `1` мають бути закриті, включно з підключенням на першій вкладці. -In order to organize that, the `versionchange` event triggers on the "outdated" database object. We should listen for it and close the old database connection (and probably suggest a page reload, to load the updated code). +Щоб це організувати, подія `versionchange` запускається на "застарілому" об’єкті бази даних. Нам слід прислухатися до цього та закрити старе підключення до бази даних (і, ймовірно, запропонувати перезавантажити сторінку, щоб завантажити оновлений код). -If we don't listen for the `versionchange` event and don't close the old connection, then the second, new connection won't be made. The `openRequest` object will emit the `blocked` event instead of `success`. So the second tab won't work. +Якщо ми не прослухаємо подію `versionchange` і не закриємо старе з’єднання, то друге, нове з’єднання не буде встановлено. Об’єкт `openRequest` видаватиме подію `blocked` замість `success`. Тому друга вкладка не працюватиме. -Here's the code to correctly handle the parallel upgrade. It installs the `onversionchange` handler, that triggers if the current database connection becomes outdated (db version is updated elsewhere) and closes the connection. +Ось код для правильної обробки паралельного оновлення. Він встановлює обробник `onversionchange`, який запускається, якщо поточне з’єднання з базою даних стає застарілим (версія db оновлюється в іншому місці), і закриває з’єднання. ```js let openRequest = indexedDB.open("store", 2); @@ -145,105 +145,105 @@ openRequest.onsuccess = function() { *!* db.onversionchange = function() { db.close(); - alert("Database is outdated, please reload the page.") + alert("База даних застаріла, перезавантажте сторінку.") }; */!* - // ...the db is ready, use it... + // ...БД готова, використовуйте її... }; *!* openRequest.onblocked = function() { - // this event shouldn't trigger if we handle onversionchange correctly + // ця подія не має спрацьовувати, якщо ми правильно обробимо зміну версії - // it means that there's another open connection to the same database - // and it wasn't closed after db.onversionchange triggered for it + // це означає, що є ще одне відкрите підключення до тієї ж бази даних + // і воно не буде закрите після того, як для нього спрацьовує db.onversionchange }; */!* ``` -...In other words, here we do two things: +...Іншими словами, тут ми робимо дві речі: -1. The `db.onversionchange` listener informs us about a parallel update attempt, if the current database version becomes outdated. -2. The `openRequest.onblocked` listener informs us about the opposite situation: there's a connection to an outdated version elsewhere, and it doesn't close, so the newer connection can't be made. +1. Слухач `db.onversionchange` повідомляє нас про спробу паралельного оновлення, якщо поточна версія бази даних стає застарілою. +2. Слухач `openRequest.onblocked` інформує нас про протилежну ситуацію: є підключення до застарілої версії в іншому місці, і воно не закривається, тому нове з’єднання неможливо встановити. -We can handle things more gracefully in `db.onversionchange`, prompt the visitor to save the data before the connection is closed and so on. +Ми можемо обробляти подібні речі більш витончено в `db.onversionchange`, запропонувати відвідувачу зберегти дані до закриття з’єднання. -Or, an alternative approach would be to not close the database in `db.onversionchange`, but instead use the `onblocked` handler (in the new tab) to alert the visitor, tell him that the newer version can't be loaded until they close other tabs. +Або, альтернативним підходом було б не закривати базу даних у `db.onversionchange`, а замість цього використовувати обробник `onblocked` (у новій вкладці), щоб попередити відвідувача про те, що новішу версію не можна завантажити, доки він не закриє іншу вкладку. -These update collisions happen rarely, but we should at least have some handling for them, at least an `onblocked` handler, to prevent our script from dying silently. +Ці зіткнення оновлень трапляються рідко, але ми повинні принаймні мати певну обробку для них, обробник `onblocked`, щоб запобігти безшумній смерті нашого сценарію. -## Object store +## Сховище об’єктів -To store something in IndexedDB, we need an *object store*. +Щоб зберегти щось у IndexedDB, нам потріне *сховище об’єктів*. -An object store is a core concept of IndexedDB. Counterparts in other databases are called "tables" or "collections". It's where the data is stored. A database may have multiple stores: one for users, another one for goods, etc. +Сховище об’єктів є основною концепцією IndexedDB. Аналоги в інших базах даних називаються «таблицями» або «колекціями». Саме там зберігаються дані. База даних може мати кілька сховищ: одне для користувачів, інше для товарів тощо. -Despite being named an "object store", primitives can be stored too. +Незважаючи на те, що це називають «сховищем об’єктів», примітиви також можна зберігати. -**We can store almost any value, including complex objects.** +**Ми можемо зберігати практично будь-які значення, включаючи складні об’єкти.** -IndexedDB uses the [standard serialization algorithm](https://www.w3.org/TR/html53/infrastructure.html#section-structuredserializeforstorage) to clone-and-store an object. It's like `JSON.stringify`, but more powerful, capable of storing much more datatypes. +IndexedDB використовує [стандартний алгоритм серіалізації](https://www.w3.org/TR/html53/infrastructure.html#section-structuredserializeforstorage) щоб клонувати та зберігати об’єкт. Це як `JSON.stringify`, але потужніший, здатний зберігати набагато більше типів даних. -An example of an object that can't be stored: an object with circular references. Such objects are not serializable. `JSON.stringify` also fails for such objects. +Приклад об’єкта, який не можливо зберегти: об’єкт з круговими посиланнями. Такі об’єкти не можна серіалізувати. `JSON.stringify` також не придатний для таких об’єктів. -**There must be a unique `key` for every value in the store.** +**Для кожного значення в сховищі має бути унікальний "ключ".** -A key must be one of these types - number, date, string, binary, or array. It's a unique identifier, so we can search/remove/update values by the key. +Ключ повинен бути одним з цих типів - число, дата, рядок, двійковий або масив. Це унікальний ідентифікатор, тому ми можемо шукати/видаляти/оновлювати значення за ключем. ![](indexeddb-structure.svg) -As we'll see very soon, we can provide a key when we add a value to the store, similar to `localStorage`. But when we store objects, IndexedDB allows setting up an object property as the key, which is much more convenient. Or we can auto-generate keys. +Як побачимо незабаром, ми можемо надати ключ, коли додамо значення до сховища, подібне до `localStorage`. Але коли ми зберігаємо об’єкти, IndexedDB дозволяє налаштувати властивість об’єкта як ключ, що набагато зручніше. Або ми можемо автоматично згенерувати ключі. -But we need to create an object store first. +Але спочатку нам потрібно створити сховище об’єктів. -The syntax to create an object store: +Синтаксис створення сховища об’єктів: ```js db.createObjectStore(name[, keyOptions]); ``` -Please note, the operation is synchronous, no `await` needed. +Зауважте, що операція синхронна, та не потребує `await`. -- `name` is the store name, e.g. `"books"` for books, -- `keyOptions` is an optional object with one of two properties: - - `keyPath` -- a path to an object property that IndexedDB will use as the key, e.g. `id`. - - `autoIncrement` -- if `true`, then the key for a newly stored object is generated automatically, as an ever-incrementing number. +- `name` -- назва сховища, наприклад. `"books"` для книжок, +- `keyOptions` є необов’язковим об’єктом з однією з двох властивостей: + - `keyPath` -- шлях до властивості об’єкта, який IndexedDB використовуватиме як ключ, напр. `id`. + - `autoIncrement` -- якщо `true`, тоді ключ для щойно збереженого об’єкта генерується автоматично у вигляді постійно зростаючого числа. -If we don't supply `keyOptions`, then we'll need to provide a key explicitly later, when storing an object. +Якщо ми не постачаємо `keyOptions`, тоді нам потрібно буде надати ключ явно пізніше, коли будемо зберігати об’єкт. -For instance, this object store uses `id` property as the key: +Наприклад, це сховище об’єктів використовує властивість `id` як ключ: ```js db.createObjectStore('books', {keyPath: 'id'}); ``` -**An object store can only be created/modified while updating the DB version, in `upgradeneeded` handler.** +**Сховище об’єктів можна створити/змінити лише під час оновлення версії БД в обробнику `upgradeneeded`.** -That's a technical limitation. Outside of the handler we'll be able to add/remove/update the data, but object stores can only be created/removed/altered during a version update. +Це технічне обмеження. За межами обробника ми зможемо додавати/вилучати/оновлювати дані, але сховища об’єктів можна створювати/вилучати/змінювати лише під час оновлення версії. -To perform a database version upgrade, there are two main approaches: -1. We can implement per-version upgrade functions: from 1 to 2, from 2 to 3, from 3 to 4 etc. Then, in `upgradeneeded` we can compare versions (e.g. old 2, now 4) and run per-version upgrades step by step, for every intermediate version (2 to 3, then 3 to 4). -2. Or we can just examine the database: get a list of existing object stores as `db.objectStoreNames`. That object is a [DOMStringList](https://html.spec.whatwg.org/multipage/common-dom-interfaces.html#domstringlist) that provides `contains(name)` method to check for existance. And then we can do updates depending on what exists and what doesn't. +Щоб виконати оновлення версії бази даних, існує два основних підходи: +1. Ми можемо реалізувати функції оновлення для кожної версії: від 1 до 2, від 2 до 3, від 3 до 4 тощо. Потім у `upgradeneeded` ми можемо порівняти версії (наприклад, стару 2, тепер 4) та запустити крок оновлення для кожної версії покроково, для кожної проміжної версії (2 до 3, потім від 3 до 4). +2. Або ми можемо просто перевірити базу даних: отримати список існуючих сховищ об’єктів як `db.objectStoreNames`. Цим об’єктом є [DOMStringList](https://html.spec.whatwg.org/multipage/common-dom-interfaces.html#domstringlist) який надає метод `contains(name)` для перевірки існування. А потім ми можемо виконувати оновлення залежно від того, що існує, а що ні. -For small databases the second variant may be simpler. +Для невеликих баз даних другий варіант може бути простішим. -Here's the demo of the second approach: +Ось демонстрація другого підходу: ```js let openRequest = indexedDB.open("db", 2); -// create/upgrade the database without version checks +// створити/оновити базу даних без перевірки версій openRequest.onupgradeneeded = function() { let db = openRequest.result; - if (!db.objectStoreNames.contains('books')) { // if there's no "books" store - db.createObjectStore('books', {keyPath: 'id'}); // create it + if (!db.objectStoreNames.contains('books')) { // якщо не існує сховища "books" + db.createObjectStore('books', {keyPath: 'id'}); // створити його } }; ``` -To delete an object store: +Щоб видалити сховище об’єктів: ```js db.deleteObjectStore('books') From f6aafd1f60fa04c5d47438159f6592353897dedf Mon Sep 17 00:00:00 2001 From: Yevhenii Stuzhuk Date: Wed, 25 May 2022 07:10:00 +0300 Subject: [PATCH 03/70] Update article.md 33.8% --- 6-data-storage/03-indexeddb/article.md | 36 +++++++++++++------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/6-data-storage/03-indexeddb/article.md b/6-data-storage/03-indexeddb/article.md index 915151df7..7e2e60f1d 100644 --- a/6-data-storage/03-indexeddb/article.md +++ b/6-data-storage/03-indexeddb/article.md @@ -249,39 +249,39 @@ openRequest.onupgradeneeded = function() { db.deleteObjectStore('books') ``` -## Transactions +## Транзакції -The term "transaction" is generic, used in many kinds of databases. +Термін "транзакція" є загальним і використовується в багатьох видах баз даних. -A transaction is a group of operations, that should either all succeed or all fail. +Транзакція — це група операцій, які повинні або всі завершитися успішно, або всі невдало. -For instance, when a person buys something, we need to: -1. Subtract the money from their account. -2. Add the item to their inventory. +Наприклад, коли людина щось купує, нам потрібно: +1. Відняти гроші з рахунку. +2. Додати товар до списку. -It would be pretty bad if we complete the 1st operation, and then something goes wrong, e.g. lights out, and we fail to do the 2nd. Both should either succeed (purchase complete, good!) or both fail (at least the person kept their money, so they can retry). +Було б дуже прикро, якби ми завершили 1-у операцію, а потім щось пішло не так, напр. гасне світло, і ми не можемо зробити 2-у. Обидві мають досягти успіху (купівля завершена, добре!), або обидві потерпіли невдачу (принаймні, людина залишила свої гроші, щоб повторити спробу). -Transactions can guarantee that. +Це можуть гарантувати транзакції. -**All data operations must be made within a transaction in IndexedDB.** +**Усі операції з даними повинні виконуватися в рамках транзакції в IndexedDB.** -To start a transaction: +Почати транзакцію: ```js db.transaction(store[, type]); ``` -- `store` is a store name that the transaction is going to access, e.g. `"books"`. Can be an array of store names if we're going to access multiple stores. -- `type` – a transaction type, one of: - - `readonly` -- can only read, the default. - - `readwrite` -- can only read and write the data, but not create/remove/alter object stores. +- `store` це назва сховища, до якого має отримати доступ транзакція, напр. `"books"`. Це може бути масив імен сховищ, якщо ми збираємося отримати доступ до кількох сховищ. +- `type` – тип транзакції, один з: + - `readonly` -- може лише читати дані, за замовчуванням. + - `readwrite` -- може лише читати та записувати дані, але не створювати/видаляти/змінювати сховища об’єктів. -There's also `versionchange` transaction type: such transactions can do everything, but we can't create them manually. IndexedDB automatically creates a `versionchange` transaction when opening the database, for `updateneeded` handler. That's why it's a single place where we can update the database structure, create/remove object stores. +Існує також тип транзакції `versionchange`: такі транзакції можуть робити все, але ми не можемо створити їх вручну. IndexedDB автоматично створює транзакцію `versionchange` під час відкриття бази даних для обробника `updateneeded`. That's why it's a single place where we can update the database structure, create/remove object stores. -```smart header="Why are there different types of transactions?" -Performance is the reason why transactions need to be labeled either `readonly` and `readwrite`. +```smart header="Чому існують різні види транзакцій?" +Продуктивність є причиною, чому транзакції мають бути позначені як `readonly` та `readwrite`. -Many `readonly` transactions are able to access the same store concurrently, but `readwrite` transactions can't. A `readwrite` transaction "locks" the store for writing. The next transaction must wait before the previous one finishes before accessing the same store. +Багато `readonly` транзакцій можуть отримати доступ до того самого сховища одночасно, але `readwrite` транзакції - не можуть. Транзакція `readwrite` "блокує" сховище для запису. Наступна транзакція повинна почекати до завершення попередньої, перш ніж отримати доступ до того самого сховища. ``` After the transaction is created, we can add an item to the store, like this: From 0f3f2a9e684b560ace2cbb7b9959a5b13883ce9b Mon Sep 17 00:00:00 2001 From: Yevhenii Stuzhuk Date: Wed, 25 May 2022 18:47:35 +0300 Subject: [PATCH 04/70] Update article.md 39.8% --- 6-data-storage/03-indexeddb/article.md | 30 +++++++++++++------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/6-data-storage/03-indexeddb/article.md b/6-data-storage/03-indexeddb/article.md index 7e2e60f1d..3e3b62d2c 100644 --- a/6-data-storage/03-indexeddb/article.md +++ b/6-data-storage/03-indexeddb/article.md @@ -284,12 +284,12 @@ db.transaction(store[, type]); Багато `readonly` транзакцій можуть отримати доступ до того самого сховища одночасно, але `readwrite` транзакції - не можуть. Транзакція `readwrite` "блокує" сховище для запису. Наступна транзакція повинна почекати до завершення попередньої, перш ніж отримати доступ до того самого сховища. ``` -After the transaction is created, we can add an item to the store, like this: +Після створення транзакції ми можемо додати елемент до сховища, ось так: ```js let transaction = db.transaction("books", "readwrite"); // (1) -// get an object store to operate on it +// отримати сховище об’єктів для роботи з ним *!* let books = transaction.objectStore("books"); // (2) */!* @@ -305,33 +305,33 @@ let request = books.add(book); // (3) */!* request.onsuccess = function() { // (4) - console.log("Book added to the store", request.result); + console.log("Книгу додано в сховище", request.result); }; request.onerror = function() { - console.log("Error", request.error); + console.log("Помилка", request.error); }; ``` -There were basically four steps: +Ось чотири основні кроки: -1. Create a transaction, mentioning all the stores it's going to access, at `(1)`. -2. Get the store object using `transaction.objectStore(name)`, at `(2)`. -3. Perform the request to the object store `books.add(book)`, at `(3)`. -4. ...Handle request success/error `(4)`, then we can make other requests if needed, etc. +1. Створіть транзакцію, вказавши всі сховища, в які вона збирається отримати доступ `(1)`. +2. Отримайте об’єкт магазину за допомогою `transaction.objectStore(name)` `(2)`. +3. Виконайте запит до сховища об’єктів `books.add(book)` `(3)`. +4. ...Обробіть запит успішно/помилка `(4)`, потім можливо робити інші запити, якщо потрібно. -Object stores support two methods to store a value: +Сховища об’єктів підтримують два методи збереження значення: - **put(value, [key])** - Add the `value` to the store. The `key` is supplied only if the object store did not have `keyPath` or `autoIncrement` option. If there's already a value with the same key, it will be replaced. + Додайте `value` до сховища. `Key` надається лише в тому випадку, якщо в сховищі об’єктів не було параметра `keyPath` або `autoIncrement`. `keyPath` чи `autoIncrement` опція. Якщо вже є значення з таким самим ключем, воно буде замінено. - **add(value, [key])** - Same as `put`, but if there's already a value with the same key, then the request fails, and an error with the name `"ConstraintError"` is generated. + Те саме що `put`, але якщо вже є значення з тим самим ключем, запит не вдається, і генерується помилка з назвою `"ConstraintError"`. -Similar to opening a database, we can send a request: `books.add(book)`, and then wait for `success/error` events. +Подібно до відкриття бази даних, ми можемо відправити запит: `books.add(book)`, а потім чекати події `success/error`. -- The `request.result` for `add` is the key of the new object. -- The error is in `request.error` (if any). +- `request.result` для `add` є ключем нового об’єкта. +- Помилкою, якщо є, буде `request.error`. ## Transactions' autocommit From ba4c2b9a6d7fad32be66f4a8f25c233ca2b5b33d Mon Sep 17 00:00:00 2001 From: Yevhenii Stuzhuk Date: Wed, 25 May 2022 19:54:41 +0300 Subject: [PATCH 05/70] Update article.md 56.8% --- 6-data-storage/03-indexeddb/article.md | 100 ++++++++++++------------- 1 file changed, 50 insertions(+), 50 deletions(-) diff --git a/6-data-storage/03-indexeddb/article.md b/6-data-storage/03-indexeddb/article.md index 3e3b62d2c..b7411747f 100644 --- a/6-data-storage/03-indexeddb/article.md +++ b/6-data-storage/03-indexeddb/article.md @@ -333,23 +333,23 @@ request.onerror = function() { - `request.result` для `add` є ключем нового об’єкта. - Помилкою, якщо є, буде `request.error`. -## Transactions' autocommit +## Автозавершення транзакцій -In the example above we started the transaction and made `add` request. But as we stated previously, a transaction may have multiple associated requests, that must either all succeed or all fail. How do we mark the transaction as finished, with no more requests to come? +У наведеному вище прикладі ми запустили транзакцію та зробили запит на додавання. Але, як ми зазначали раніше, транзакція може мати кілька пов’язаних запитів, усі вони повинні бути успішними або невдалими. Як позначити транзакцію як завершену, без запитів? -The short answer is: we don't. +Коротка відповідь: це не потрібно. -In the next version 3.0 of the specification, there will probably be a manual way to finish the transaction, but right now in 2.0 there isn't. +У наступній версії специфікації 3.0, ймовірно, буде ручний спосіб завершити транзакцію, але зараз у 2.0 його немає. -**When all transaction requests are finished, and the [microtasks queue](info:microtask-queue) is empty, it is committed automatically.** +**Коли всі запити транзакції завершено, а [черга мікрозадач](info:microtask-queue) порожня, вона завершується автоматично.** -Usually, we can assume that a transaction commits when all its requests are complete, and the current code finishes. +Зазвичай ми можемо припустити, що транзакція завершується, коли всі її запити завершені, а поточний код виконаний. -So, in the example above no special call is needed to finish the transaction. +Отже, у наведеному вище прикладі не потрібен особливий виклик для завершення транзакції. -Transactions auto-commit principle has an important side effect. We can't insert an async operation like `fetch`, `setTimeout` in the middle of a transaction. IndexedDB will not keep the transaction waiting till these are done. +Принцип автозавершення транзакцій має важливий побічний ефект. Ми не можемо вставити асинхронну операцію, як-от `fetch`, `setTimeout`, у середину транзакції. IndexedDB не буде чекати транзакцію, поки вона не виконана. -In the code below, `request2` in the line `(*)` fails, because the transaction is already committed, and can't make any request in it: +У наведеному нижче коді `request2` у рядку `(*)` не працює, оскільки транзакція вже завершена, і в ній не можливо зробити жодного запиту: ```js let request1 = books.add(book); @@ -366,54 +366,54 @@ request1.onsuccess = function() { }; ``` -That's because `fetch` is an asynchronous operation, a macrotask. Transactions are closed before the browser starts doing macrotasks. +Це тому, що `fetch` є асинхронною операцією, макрозавданням. Трансакції закриваються до того, як браузер почне виконувати макрозавдання. -Authors of IndexedDB spec believe that transactions should be short-lived. Mostly for performance reasons. +Автори специфікації IndexedDB вважають, що транзакції мають бути короткочасними. В основному з міркувань продуктивності. -Notably, `readwrite` transactions "lock" the stores for writing. So if one part of the application initiated `readwrite` on `books` object store, then another part that wants to do the same has to wait: the new transaction "hangs" till the first one is done. That can lead to strange delays if transactions take a long time. +Примітно, що транзакції `readwrite` "блокують" сховища для запису. Отже, якщо одна частина програми ініціювала `readwrite` у сховищі об’єктів `books`, тоді інша частина, яка хоче зробити те ж саме, повинна почекати: нова транзакція "зависає", доки не буде виконана перша. Це може призвести до дивних затримок, якщо транзакції займають багато часу. -So, what to do? +Отже, що робити? -In the example above we could make a new `db.transaction` right before the new request `(*)`. +У наведеному вище прикладі ми могли б створити новий `db.transaction` безпосередньо перед новим запитом `(*)`. -But it will be even better, if we'd like to keep the operations together, in one transaction, to split apart IndexedDB transactions and "other" async stuff. +Але буде ще краще, якщо ми захочемо зберегти операції разом, в одній транзакції, розділити транзакції IndexedDB та «інші» асинхронні речі. -First, make `fetch`, prepare the data if needed, afterwards create a transaction and perform all the database requests, it'll work then. +Спочатку зробіть `fetch`, підготуйте дані, якщо потрібно, потім створіть транзакцію та виконайте всі запити до бази даних, тоді це запрацює. -To detect the moment of successful completion, we can listen to `transaction.oncomplete` event: +Щоб визначити момент успішного завершення, ми можемо прослухати подію `transaction.oncomplete`: ```js let transaction = db.transaction("books", "readwrite"); -// ...perform operations... +// ...виконувати операції... transaction.oncomplete = function() { - console.log("Transaction is complete"); + console.log("Транзакція завершена"); }; ``` -Only `complete` guarantees that the transaction is saved as a whole. Individual requests may succeed, but the final write operation may go wrong (e.g. I/O error or something). +Тільки `complete` гарантує, що транзакція буде збережена в цілому. Окремі запити можуть бути успішними, але остаточна операція запису може піти не так (наприклад, помилка введення-виводу чи щось подібне). -To manually abort the transaction, call: +Щоб вручну припинити транзакцію, викличте: ```js transaction.abort(); ``` -That cancels all modification made by the requests in it and triggers `transaction.onabort` event. +Це скасовує всі зміни, зроблені запитами, і запускає подію `transaction.onabort`. -## Error handling +## Обробка помилок -Write requests may fail. +Запити на запис можуть не виконуватися. -That's to be expected, not only because of possible errors at our side, but also for reasons not related to the transaction itself. For instance, the storage quota may be exceeded. So we must be ready to handle such case. +Цього можна очікувати не лише через можливі помилки з нашого боку, а й з причин, не пов’язаних із самою транзакцією. Наприклад, закінчилося пам’ять. Тож ми повинні бути готові впоратися з такою справою. -**A failed request automatically aborts the transaction, canceling all its changes.** +**Невдалий запит автоматично перериває транзакцію, скасовуючи всі зміни.** -In some situations, we may want to handle the failure (e.g. try another request), without canceling existing changes, and continue the transaction. That's possible. The `request.onerror` handler is able to prevent the transaction abort by calling `event.preventDefault()`. +У деяких ситуаціях ми можемо забажати обробити помилку (наприклад, спробувати інший запит), не скасовуючи наявні зміни, і продовжити транзакцію. Це можливо. Обробник `request.onerror` може запобігти перериванню транзакції, викликавши `event.preventDefault()`. -In the example below a new book is added with the same key (`id`) as the existing one. The `store.add` method generates a `"ConstraintError"` in that case. We handle it without canceling the transaction: +У наведеному нижче прикладі додається нова книга з тим самим ключем (`id`), що й існуюча. У цьому випадку метод `store.add` генерує "ConstraintError". Ми обробляємо це, не скасовуючи транзакцію: ```js let transaction = db.transaction("books", "readwrite"); @@ -423,54 +423,54 @@ let book = { id: 'js', price: 10 }; let request = transaction.objectStore("books").add(book); request.onerror = function(event) { - // ConstraintError occurs when an object with the same id already exists + // ConstraintError виникає, коли об’єкт з таким же ідентифікатором вже існує if (request.error.name == "ConstraintError") { - console.log("Book with such id already exists"); // handle the error - event.preventDefault(); // don't abort the transaction - // use another key for the book? + console.log("Книга з таким ідентифікатором вже існує"); // обробіть помилку + event.preventDefault(); // запобігаємо скасуванню транзакції + // використовувати інший ключ для книги? } else { - // unexpected error, can't handle it - // the transaction will abort + // несподівана помилка, не можу впоратися з нею + // транзакція буде перервана } }; transaction.onabort = function() { - console.log("Error", transaction.error); + console.log("Помилка", transaction.error); }; ``` -### Event delegation +### Делегування події -Do we need onerror/onsuccess for every request? Not every time. We can use event delegation instead. +Чи потрібно нам onerror/onsuccess для кожного запиту? Не завжди. Замість цього ми можемо використовувати делегування подій. -**IndexedDB events bubble: `request` -> `transaction` -> `database`.** +**Спливаюча подія IndexedDB: `запит` -> `транзакція` -> `база даних`.** -All events are DOM events, with capturing and bubbling, but usually only bubbling stage is used. +Всі події є DOM-подіями з фазами перехоплення та спливання, але зазвичай використовується тільки спливання. -So we can catch all errors using `db.onerror` handler, for reporting or other purposes: +Тож ми можемо відловити всі помилки за допомогою обробника `db.onerror` для звітів чи інших цілей: ```js db.onerror = function(event) { - let request = event.target; // the request that caused the error + let request = event.target; // запит, який спричинив помилку - console.log("Error", request.error); + console.log("Помилка", request.error); }; ``` -...But what if an error is fully handled? We don't want to report it in that case. +...Але що, якщо помилка повністю оброблена? Ми не хочемо повідомляти про це в такому випадку. -We can stop the bubbling and hence `db.onerror` by using `event.stopPropagation()` in `request.onerror`. +Ми можемо зупинити спливання і, відповідно, `db.onerror`, використовуючи `event.stopPropagation()` у `request.onerror`. ```js request.onerror = function(event) { if (request.error.name == "ConstraintError") { - console.log("Book with such id already exists"); // handle the error - event.preventDefault(); // don't abort the transaction - event.stopPropagation(); // don't bubble error up, "chew" it + console.log("Книга з таким ідентифікатором вже існує"); // обробіть помилку + event.preventDefault(); // запобігаємо скасуванню транзакції + event.stopPropagation(); // запобігаємо спливанню помилки } else { - // do nothing - // transaction will be aborted - // we can take care of error in transaction.onabort + // нічого не робимо + // транзакція буде скасована + // ми можемо подбати про помилку в transaction.onabort } }; ``` From 9a855d90a232acb015ee1b9a9d50ace1b729ca55 Mon Sep 17 00:00:00 2001 From: Yevhenii Stuzhuk Date: Thu, 26 May 2022 10:55:03 +0300 Subject: [PATCH 06/70] Update article.md 76% --- 6-data-storage/03-indexeddb/article.md | 126 ++++++++++++------------- 1 file changed, 63 insertions(+), 63 deletions(-) diff --git a/6-data-storage/03-indexeddb/article.md b/6-data-storage/03-indexeddb/article.md index b7411747f..7b64d3b13 100644 --- a/6-data-storage/03-indexeddb/article.md +++ b/6-data-storage/03-indexeddb/article.md @@ -475,90 +475,90 @@ request.onerror = function(event) { }; ``` -## Searching +## Пошук -There are two main types of search in an object store: +Існує два основних типи пошуку в сховищі об’єктів: -1. By a key value or a key range. In our "books" storage that would be a value or range of values of `book.id`. -2. By another object field, e.g. `book.price`. This required an additional data structure, named "index". +1. За значенням ключа або діапазоном ключа. У нашому сховищі «книги» це буде значення або діапазон значень `book.id`. +2. За іншим полем об’єкта, напр. `book.price`. Для цього потрібна додаткова структура даних під назвою «index». -### By key +### За ключем -First let's deal with the first type of search: by key. +Спочатку розберемося з першим типом пошуку: за ключем. -Searching methods support both exact key values and so-called "ranges of values" -- [IDBKeyRange](https://www.w3.org/TR/IndexedDB/#keyrange) objects that specify an acceptable "key range". +Методи пошуку підтримують як точні значення ключів, та так звані "діапазони значень" -- [IDBKeyRange](https://www.w3.org/TR/IndexedDB/#keyrange) об’єкти, які визначають прийнятний "діапазон ключів". -`IDBKeyRange` objects are created using following calls: +Об’єкти `IDBKeyRange` створюються за допомогою наступних викликів: -- `IDBKeyRange.lowerBound(lower, [open])` means: `≥lower` (or `>lower` if `open` is true) -- `IDBKeyRange.upperBound(upper, [open])` means: `≤upper` (or `lower` якщо `open` є істина) +- `IDBKeyRange.upperBound(upper, [open])` означає: `≤upper` (чи ` 'js' +// отримати всі ключи, де id > 'js' books.getAllKeys(IDBKeyRange.lowerBound('js', true)) ``` -```smart header="Object store is always sorted" -An object store sorts values by key internally. +```smart header="Сховище об’єктів завжди відсортовано" +Сховище об’єктів внутрішньо сортує значення за ключем. -So requests that return many values always return them in sorted by key order. +Тому запити, які повертають багато значень, завжди повертають їх у відсортованому за ключем порядку. ``` -### By a field using an index +### За полем за допомогою індексу -To search by other object fields, we need to create an additional data structure named "index". +Для пошуку за іншими полями об’єкта нам потрібно створити додаткову структуру даних під назвою «індекс». -An index is an "add-on" to the store that tracks a given object field. For each value of that field, it stores a list of keys for objects that have that value. There will be a more detailed picture below. +Індекс — це «доповнення» до сховища, яке відстежує дане поле об’єкта. Для кожного значення цього поля він зберігає список ключів для об’єктів, які мають це значення. Нижче буде більш детальна картинка. -The syntax: +Синтаксис: ```js objectStore.createIndex(name, keyPath, [options]); ``` -- **`name`** -- index name, -- **`keyPath`** -- path to the object field that the index should track (we're going to search by that field), -- **`option`** -- an optional object with properties: - - **`unique`** -- if true, then there may be only one object in the store with the given value at the `keyPath`. The index will enforce that by generating an error if we try to add a duplicate. - - **`multiEntry`** -- only used if the value on `keyPath` is an array. In that case, by default, the index will treat the whole array as the key. But if `multiEntry` is true, then the index will keep a list of store objects for each value in that array. So array members become index keys. +- **`name`** -- ім’я індексу, +- **`keyPath`** -- шлях до поля об’єкта, яке має відстежувати індекс (ми будемо шукати за цим полем), +- **`option`** -- необов’язковий об’єкт з властивостями: + - **`unique`** -- якщо значення true, то в сховищі може бути лише один об’єкт із заданим значенням у `keyPath`. Індекс забезпечить це, генеруючи помилку, якщо ми спробуємо додати дублікат. + - **`multiEntry`** -- використовується лише якщо значення `keyPath` є масивом. У цьому випадку за замовчуванням індекс розглядатиме весь масив як ключ. Але якщо `multiEntry` має значення true, то індекс зберігатиме список об’єктів сховища для кожного значення в цьому масиві. Таким чином, члени масиву стають ключами індексу. -In our example, we store books keyed by `id`. +У нашому прикладі ми зберігаємо книги з ключем `id`. -Let's say we want to search by `price`. +Скажімо, ми хочемо шукати за `price`. -First, we need to create an index. It must be done in `upgradeneeded`, just like an object store: +Спочатку нам потрібно створити індекс. Це потрібно зробити в `upgradeneeded`, як у сховищі об’єктів: ```js openRequest.onupgradeneeded = function() { - // we must create the index here, in versionchange transaction + // ми повинні створити індекс тут, у транзакції зміни версії let books = db.createObjectStore('books', {keyPath: 'id'}); *!* let index = books.createIndex('price_idx', 'price'); @@ -566,19 +566,19 @@ openRequest.onupgradeneeded = function() { }; ``` -- The index will track `price` field. -- The price is not unique, there may be multiple books with the same price, so we don't set `unique` option. -- The price is not an array, so `multiEntry` flag is not applicable. +- Індекс буде відстежувати поле `price`. +- Ціна не є унікальною, може бути кілька книг з однаковою ціною, тому ми не встановлюємо параметр `unique`. +- Ціна не є масивом, тому прапор `multiEntry` не застосовується. -Imagine that our `inventory` has 4 books. Here's the picture that shows exactly what the `index` is: +Уявіть собі, що в нашому `inventory` є 4 книги. Ось малюнок, який показує, що саме таке `index`. ![](indexeddb-index.svg) -As said, the index for each value of `price` (second argument) keeps the list of keys that have that price. +Як сказано, індекс для кожного значення `price` (другий аргумент) зберігає список ключів, які мають таку ціну. -The index keeps itself up to date automatically, we don't have to care about it. +Індекс оновлюється автоматично, нам не потрібно дбати про це. -Now, when we want to search for a given price, we simply apply the same search methods to the index: +Тепер, коли ми хочемо шукати за заданою ціною, ми просто застосовуємо ті самі методи пошуку до індексу: ```js let transaction = db.transaction("books"); // readonly @@ -591,38 +591,38 @@ let request = priceIndex.getAll(10); request.onsuccess = function() { if (request.result !== undefined) { - console.log("Books", request.result); // array of books with price=10 + console.log("Книги", request.result); // масив книг із ціною=10 } else { - console.log("No such books"); + console.log("Немає таких книжок"); } }; ``` -We can also use `IDBKeyRange` to create ranges and looks for cheap/expensive books: +Ми також можемо використовувати `IDBKeyRange` для створення діапазонів і пошуку дешевих/дорогих книг: ```js -// find books where price <= 5 +// знайти книги, де ціна <= 5 let request = priceIndex.getAll(IDBKeyRange.upperBound(5)); ``` -Indexes are internally sorted by the tracked object field, `price` in our case. So when we do the search, the results are also sorted by `price`. +Індекси внутрішньо відсортовані за полем відстежуваного об’єкта, у нашому випадку `price`. Тому, коли ми виконуємо пошук, результати також сортуються за `price`. -## Deleting from store +## Видалення зі сховища -The `delete` method looks up values to delete by a query, the call format is similar to `getAll`: +Метод `delete` шукає значення для видалення за запитом, формат виклику подібний до `getAll`: -- **`delete(query)`** -- delete matching values by query. +- **`delete(query)`** -- видалити відповідні значення за запитом. -For instance: +Наприклад: ```js -// delete the book with id='js' +// видалити книгу з id='js' books.delete('js'); ``` -If we'd like to delete books based on a price or another object field, then we should first find the key in the index, and then call `delete`: +Якщо ми хочемо видалити книги на основі ціни або іншого поля об’єкта, то спочатку ми повинні знайти ключ в індексі, а потім викликати `delete`: ```js -// find the key where price = 5 +// знайти ключ з price = 5 let request = priceIndex.getKey(5); request.onsuccess = function() { @@ -631,9 +631,9 @@ request.onsuccess = function() { }; ``` -To delete everything: +Щоб видалити все: ```js -books.clear(); // clear the storage. +books.clear(); // очистити сховище. ``` ## Cursors From ca0b5389ff088be91512d62fe18845ba9b7716b5 Mon Sep 17 00:00:00 2001 From: Yevhenii Stuzhuk Date: Thu, 26 May 2022 17:30:20 +0300 Subject: [PATCH 07/70] Update article.md 85.9% --- 6-data-storage/03-indexeddb/article.md | 66 +++++++++++++------------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/6-data-storage/03-indexeddb/article.md b/6-data-storage/03-indexeddb/article.md index 7b64d3b13..62828e95d 100644 --- a/6-data-storage/03-indexeddb/article.md +++ b/6-data-storage/03-indexeddb/article.md @@ -636,37 +636,37 @@ request.onsuccess = function() { books.clear(); // очистити сховище. ``` -## Cursors +## Курсори -Methods like `getAll/getAllKeys` return an array of keys/values. +Такі методи, як `getAll/getAllKeys` повертають масив ключів/значень. -But an object storage can be huge, bigger than the available memory. Then `getAll` will fail to get all records as an array. +Але сховище об’єктів може бути величезним, більшим за доступну пам’ять. Тоді `getAll` не зможе отримати всі записи у вигляді масиву. -What to do? +Що робити? -Cursors provide the means to work around that. +Курсори забезпечують засоби, щоб обійти це. -**A *cursor* is a special object that traverses the object storage, given a query, and returns one key/value at a time, thus saving memory.** +***cursor — це спеціальний об’єкт, який обходить сховище об’єктів за запитом і повертає один ключ/значення за раз, економлячи таким чином пам’ять.** -As an object store is sorted internally by key, a cursor walks the store in key order (ascending by default). +Оскільки сховище об’єктів внутрішньо відсортовано за ключем, курсор проходить по сховищу в порядку знаходження ключа (зростаючий за замовчуванням). -The syntax: +Синтаксис: ```js -// like getAll, but with a cursor: +// як getAll, але з використанням курсору: let request = store.openCursor(query, [direction]); -// to get keys, not values (like getAllKeys): store.openKeyCursor +// отримати ключі, не значення (як getAllKeys): store.openKeyCursor ``` -- **`query`** is a key or a key range, same as for `getAll`. -- **`direction`** is an optional argument, which order to use: - - `"next"` -- the default, the cursor walks up from the record with the lowest key. - - `"prev"` -- the reverse order: down from the record with the biggest key. - - `"nextunique"`, `"prevunique"` -- same as above, but skip records with the same key (only for cursors over indexes, e.g. for multiple books with price=5 only the first one will be returned). +- **`query`** є ключем або діапазоном ключів, як і для `getAll`. +- **`direction`** є необов’язковим аргументом, що вказує який порядок використовувати: + - `"next"` -- за замовчуванням, курсор піднімається від найнижчого ключа до найвищчого. + - `"prev"` -- зворотний порядок: вниз від запису з найбільшим ключем. + - `"nextunique"`, `"prevunique"` -- те саме, що й вище, але пропускає записи з тим же ключем, який вже був (лише для курсорів за індексами, наприклад, для кількох книг із ціною=5 буде повернута лише перша). -**The main difference of the cursor is that `request.onsuccess` triggers multiple times: once for each result.** +**Основна відмінність курсору полягає в тому, що `request.onsuccess` запускається кілька разів: один раз для кожного результату.** -Here's an example of how to use a cursor: +Ось приклад того, як використовувати курсор: ```js let transaction = db.transaction("books"); @@ -674,47 +674,47 @@ let books = transaction.objectStore("books"); let request = books.openCursor(); -// called for each book found by the cursor +// викликається для кожної книги, знайденої курсором request.onsuccess = function() { let cursor = request.result; if (cursor) { - let key = cursor.key; // book key (id field) - let value = cursor.value; // book object + let key = cursor.key; // ключ книги (поле id) + let value = cursor.value; // книжковий об’єкт console.log(key, value); cursor.continue(); } else { - console.log("No more books"); + console.log("Книг більше немає"); } }; ``` -The main cursor methods are: +Основними методами курсора є: -- `advance(count)` -- advance the cursor `count` times, skipping values. -- `continue([key])` -- advance the cursor to the next value in range matching (or immediately after `key` if given). +- `advance(count)` -- пересунути курсор `count` разів, пропускаючи значення. +- `continue([key])` -- пересунути курсор до наступного значення у відповідності з діапазоном (або відразу після `key`, якщо вказано). -Whether there are more values matching the cursor or not -- `onsuccess` gets called, and then in `result` we can get the cursor pointing to the next record, or `undefined`. +Незалежно від того, чи є значення, що відповідають курсору, чи ні – викликається `onsuccess`, а потім у `result` ми можемо отримати курсор, що вказує на наступний запис, або `undefined`. -In the example above the cursor was made for the object store. +У наведеному вище прикладі курсор був створений для сховища об’єктів. -But we also can make a cursor over an index. As we remember, indexes allow to search by an object field. Cursors over indexes do precisely the same as over object stores -- they save memory by returning one value at a time. +Але ми також можемо навести курсор на індекс. Як ми пам’ятаємо, індекси дозволяють здійснювати пошук по полю об’єкта. Курсори над індексами діють точно так само, як і над сховищами об’єктів — вони економлять пам’ять, повертаючи по одному значенню за раз. -For cursors over indexes, `cursor.key` is the index key (e.g. price), and we should use `cursor.primaryKey` property for the object key: +Для курсорів над індексами `cursor.key` є ключем індексу (наприклад, ціна), і ми повинні використовувати властивість `cursor.primaryKey` для ключа об’єкта: ```js let request = priceIdx.openCursor(IDBKeyRange.upperBound(5)); -// called for each record +// викликані для кожного запису request.onsuccess = function() { let cursor = request.result; if (cursor) { - let primaryKey = cursor.primaryKey; // next object store key (id field) - let value = cursor.value; // next object store object (book object) - let key = cursor.key; // next index key (price) + let primaryKey = cursor.primaryKey; // ключ сховища наступного об’єкта (поле id) + let value = cursor.value; // наступний об’єкт сховища (об’єкт книги) + let key = cursor.key; // наступний ключ індексу (price) console.log(key, value); cursor.continue(); } else { - console.log("No more books"); + console.log("Книг більше немає"); } }; ``` From 52e5ab92ca5a7ff457eacb4f11f0d6052eb0f523 Mon Sep 17 00:00:00 2001 From: Yevhenii Stuzhuk Date: Thu, 26 May 2022 20:33:32 +0300 Subject: [PATCH 08/70] Update article.md 95% --- 6-data-storage/03-indexeddb/article.md | 44 +++++++++++++------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/6-data-storage/03-indexeddb/article.md b/6-data-storage/03-indexeddb/article.md index 62828e95d..dd9d566cf 100644 --- a/6-data-storage/03-indexeddb/article.md +++ b/6-data-storage/03-indexeddb/article.md @@ -719,18 +719,18 @@ request.onsuccess = function() { }; ``` -## Promise wrapper +## Обгортка промісів -Adding `onsuccess/onerror` to every request is quite a cumbersome task. Sometimes we can make our life easier by using event delegation, e.g. set handlers on the whole transactions, but `async/await` is much more convenient. +Додавання `onsuccess/onerror` до кожного запиту є досить громіздким завданням. Іноді ми можемо полегшити життя, використовуючи делегування подій, напр. встановити обробники для всіх транзакцій, але `async/await` набагато зручніше. -Let's use a thin promise wrapper further in this chapter. It creates a global `idb` object with [promisified](info:promisify) IndexedDB methods. +Давайте використаємо тонку обгортку промісів далі в цьому розділі. Вона створює глобальний об’єкт `idb` з методами IndexedDB [promisified](info:promisify). -Then, instead of `onsuccess/onerror` we can write like this: +Тоді замість `onsuccess/onerror` ми можемо написати так: ```js let db = await idb.openDB('store', 1, db => { if (db.oldVersion == 0) { - // perform the initialization + // виконати ініціалізацію db.createObjectStore('books', {keyPath: 'id'}); } }); @@ -751,33 +751,33 @@ try { ``` -So we have all the sweet "plain async code" and "try..catch" stuff. +Тож маємо все солоденьке «простий асинхронний код» та "try..catch". -### Error handling +### Обробка помилок -If we don't catch an error, then it falls through, till the closest outer `try..catch`. +Якщо ми не ловимо помилку, вона провалюється до найближчого зовнішнього `try..catch`. -An uncaught error becomes an "unhandled promise rejection" event on `window` object. +Невиявлена помилка стає подією "необробленого відхилення промісу" на об’єкті `window`. -We can handle such errors like this: +Ми можемо обробляти такі помилки таким чином: ```js window.addEventListener('unhandledrejection', event => { - let request = event.target; // IndexedDB native request object - let error = event.reason; // Unhandled error object, same as request.error - ...report about the error... + let request = event.target; // Власний об’єкт запиту IndexedDB + let error = event.reason; // Необроблений об’єкт помилки, те саме, що request.error + ...повідомити про помилку... }); ``` -### "Inactive transaction" pitfall +### Підводний камінь «Неактивна транзакція». -As we already know, a transaction auto-commits as soon as the browser is done with the current code and microtasks. So if we put a *macrotask* like `fetch` in the middle of a transaction, then the transaction won't wait for it to finish. It just auto-commits. So the next request in it would fail. +Як ми вже знаємо, транзакція автоматично завершується, як тільки браузер закінчить роботу з поточним кодом і мікрозавданнями. Отже, якщо ми помістимо *макрозавдання* на кшталт `fetch` в середині транзакції, то транзакція не чекатиме завершення. Вона просто автоматично завершається. Отже, наступний запит буде невдалим. -For a promise wrapper and `async/await` the situation is the same. +Для обгортки промісів і `async/await` ситуація однакова. -Here's an example of `fetch` in the middle of the transaction: +Ось приклад `fetch` у середині транзакції: ```js let transaction = db.transaction("inventory", "readwrite"); @@ -787,14 +787,14 @@ await inventory.add({ id: 'js', price: 10, created: new Date() }); await fetch(...); // (*) -await inventory.add({ id: 'js', price: 10, created: new Date() }); // Error +await inventory.add({ id: 'js', price: 10, created: new Date() }); // Помилка ``` -The next `inventory.add` after `fetch` `(*)` fails with an "inactive transaction" error, because the transaction is already committed and closed at that time. +Наступний `inventory.add` після `fetch` `(*)` не вдається з помилкою "неактивна транзакція", оскільки на той момент транзакція вже завершена та закрита. -The workaround is the same as when working with native IndexedDB: either make a new transaction or just split things apart. -1. Prepare the data and fetch all that's needed first. -2. Then save in the database. +Обхідний шлях такий же, як і під час роботи з рідною IndexedDB: або зробіть нову транзакцію, або просто розділіть речі. +1. Підготуйте дані та спершу отримайте все необхідне. +2. Потім збережіть у базі даних. ### Getting native objects From 7d621a63ca0b0e4b1b74b1497b5c44369ac7ba99 Mon Sep 17 00:00:00 2001 From: Yevhenii Stuzhuk Date: Thu, 26 May 2022 21:02:15 +0300 Subject: [PATCH 09/70] Update article.md 99% --- 6-data-storage/03-indexeddb/article.md | 46 +++++++++++++------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/6-data-storage/03-indexeddb/article.md b/6-data-storage/03-indexeddb/article.md index dd9d566cf..892f87584 100644 --- a/6-data-storage/03-indexeddb/article.md +++ b/6-data-storage/03-indexeddb/article.md @@ -796,43 +796,43 @@ await inventory.add({ id: 'js', price: 10, created: new Date() }); // Помил 1. Підготуйте дані та спершу отримайте все необхідне. 2. Потім збережіть у базі даних. -### Getting native objects +### Отримання вбудованих об’єктів -Internally, the wrapper performs a native IndexedDB request, adding `onerror/onsuccess` to it, and returns a promise that rejects/resolves with the result. +Внутрішньо обгортка виконує запит IndexedDB, додаючи до нього `onerror/onsuccess`, і повертає проміс, який відхиляється/розв’язується з результатом. -That works fine most of the time. The examples are at the lib page . +Це добре працює більшість часу. Приклади є на сторінці lib . -In few rare cases, when we need the original `request` object, we can access it as `promise.request` property of the promise: +У кількох рідкісних випадках, коли нам потрібен оригінальний об’єкт `request`, ми можемо отримати до нього доступ як властивості `promise.request` промісу: ```js -let promise = books.add(book); // get a promise (don't await for its result) +let promise = books.add(book); // отримати проміс (не чекаємо результату) -let request = promise.request; // native request object -let transaction = request.transaction; // native transaction object +let request = promise.request; // вбудований об’єкт запиту +let transaction = request.transaction; // вбудований об’єкт транзакції -// ...do some native IndexedDB voodoo... +// ...працюємо з IndexedDB... -let result = await promise; // if still needed +let result = await promise; // якщо ще потрібно ``` -## Summary +## Резюме -IndexedDB can be thought of as a "localStorage on steroids". It's a simple key-value database, powerful enough for offline apps, yet simple to use. +IndexedDB можна розглядати як «локальне сховище на стероїдах». Це проста база даних ключ-значення, достатньо потужна для автономних програм, але проста у використанні. -The best manual is the specification, [the current one](https://www.w3.org/TR/IndexedDB-2/) is 2.0, but few methods from [3.0](https://w3c.github.io/IndexedDB/) (it's not much different) are partially supported. +Найкращий посібник — це специфікація,, [поточна](https://www.w3.org/TR/IndexedDB-2/) версія 2.0, але кілька методів із [3.0](https://w3c.github.io/IndexedDB/) (це мало чим відрізняється) частково підтримуються. -The basic usage can be described with a few phrases: +Використання можна описати кількома фразами: -1. Get a promise wrapper like [idb](https://github.com/jakearchibald/idb). -2. Open a database: `idb.openDb(name, version, onupgradeneeded)` - - Create object storages and indexes in `onupgradeneeded` handler or perform version update if needed. -3. For requests: - - Create transaction `db.transaction('books')` (readwrite if needed). - - Get the object store `transaction.objectStore('books')`. -4. Then, to search by a key, call methods on the object store directly. - - To search by an object field, create an index. -5. If the data does not fit in memory, use a cursor. +1. Підключити обгортку над промісами, наприклад [idb](https://github.com/jakearchibald/idb). +2. Відкрити базу даних: `idb.openDb(name, version, onupgradeneeded)` + - Створити сховища об’єктів та індекси в обробнику `onupgradeneeded` або виконати оновлення версії, якщо потрібно. +3. Для запитів: + - Створити транзакцію `db.transaction('books')` (можна вказати readwrite якщо потрібно). + - Отримати сховище об’єктів `transaction.objectStore('books')`. +4. Потім для пошуку за ключем викличте методи безпосередньо в сховищі об’єктів. + - Для пошуку по полю об’єкта створіть індекс. +5. Якщо дані не поміщаються в пам’ять, скористайтеся курсором. -Here's a small demo app: +Ось невелика демонстраційна програма: [codetabs src="books" current="index.html"] From b3108102b6d0fc3a0d9c178bea3cc6c5d2307f99 Mon Sep 17 00:00:00 2001 From: Yevhenii Stuzhuk Date: Thu, 26 May 2022 21:05:58 +0300 Subject: [PATCH 10/70] Update index.html 100% --- 6-data-storage/03-indexeddb/books.view/index.html | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/6-data-storage/03-indexeddb/books.view/index.html b/6-data-storage/03-indexeddb/books.view/index.html index 11c12da7b..13bb47aa6 100644 --- a/6-data-storage/03-indexeddb/books.view/index.html +++ b/6-data-storage/03-indexeddb/books.view/index.html @@ -1,10 +1,10 @@ - - + + -

Books list:

+

Список книг:

    @@ -32,7 +32,7 @@ name: ${book.name}, price: ${book.price} `).join(''); } else { - listElem.innerHTML = '
  • No books yet. Please add books.
  • ' + listElem.innerHTML = '
  • Книг поки немає. Будь ласка, додайте книги.
  • ' } @@ -45,8 +45,8 @@ } async function addBook() { - let name = prompt("Book name?"); - let price = +prompt("Book price?"); + let name = prompt("Назва книги?"); + let price = +prompt("Ціна книги?"); let tx = db.transaction('books', 'readwrite'); @@ -55,7 +55,7 @@ await list(); } catch(err) { if (err.name == 'ConstraintError') { - alert("Such book exists already"); + alert("Така книга вже існує"); await addBook(); } else { throw err; From 46d37a347865e6693884f2e5d5c3926d92537b98 Mon Sep 17 00:00:00 2001 From: Yevhenii Stuzhuk Date: Sat, 4 Jun 2022 07:43:31 +0300 Subject: [PATCH 11/70] Update article.md 54,8% --- 5-network/12-server-sent-events/article.md | 118 ++++++++++----------- 1 file changed, 59 insertions(+), 59 deletions(-) diff --git a/5-network/12-server-sent-events/article.md b/5-network/12-server-sent-events/article.md index c59d671a4..4e00fa284 100644 --- a/5-network/12-server-sent-events/article.md +++ b/5-network/12-server-sent-events/article.md @@ -1,80 +1,80 @@ # Server Sent Events -The [Server-Sent Events](https://html.spec.whatwg.org/multipage/comms.html#the-eventsource-interface) specification describes a built-in class `EventSource`, that keeps connection with the server and allows to receive events from it. +Специфікація [Server-Sent Events](https://html.spec.whatwg.org/multipage/comms.html#the-eventsource-interface) описує вбудований клас `EventSource`, який підтримує з’єднання з сервером і дозволяє отримувати від нього події. -Similar to `WebSocket`, the connection is persistent. +Подібно до `WebSocket`, з’єднання є постійним. -But there are several important differences: +Але є кілька важливих відмінностей: | `WebSocket` | `EventSource` | |-------------|---------------| -| Bi-directional: both client and server can exchange messages | One-directional: only server sends data | -| Binary and text data | Only text | -| WebSocket protocol | Regular HTTP | +| Двонаправлений: клієнт і сервер можуть обмінюватися повідомленнями | Односпрямований: дані надсилає лише сервер | +| Двійкові та текстові дані | Тільки текст | +| WebSocket протокол | Звичайний HTTP | -`EventSource` is a less-powerful way of communicating with the server than `WebSocket`. +`EventSource` є менш потужним способом зв’язку з сервером, ніж `WebSocket`. -Why should one ever use it? +Навіщо його використовувати? -The main reason: it's simpler. In many applications, the power of `WebSocket` is a little bit too much. +Основна причина: він простіший. У багатьох програмах потужність `WebSocket` є дещо занадто великою. -We need to receive a stream of data from server: maybe chat messages or market prices, or whatever. That's what `EventSource` is good at. Also it supports auto-reconnect, something we need to implement manually with `WebSocket`. Besides, it's a plain old HTTP, not a new protocol. +Нам потрібно отримати потік даних із сервера: можливо, повідомлення в чаті чи ринкові ціни, чи що завгодно. Це те, у чому сильный `EventSource`. Також він підтримує автоматичне перепідключення, що зазвичай потрібно реалізовувати вручну за допомогою `WebSocket`. Крім того, це звичайний старий HTTP, а не новий протокол. -## Getting messages +## Отримання повідомлень -To start receiving messages, we just need to create `new EventSource(url)`. +Щоб почати отримувати повідомлення, необхідно створити `new EventSource(url)`. -The browser will connect to `url` and keep the connection open, waiting for events. +Браузер підключиться до `url` і залишить з’єднання відкритим, чекаючи на події. -The server should respond with status 200 and the header `Content-Type: text/event-stream`, then keep the connection and write messages into it in the special format, like this: +Сервер повинен відповісти статусом 200 і заголовком `Content-Type: text/event-stream`, а потім зберегти з’єднання та писати повідомлення в спеціальному форматі, наприклад: ``` -data: Message 1 +data: Повідомлення 1 -data: Message 2 +data: Повідомлення 2 -data: Message 3 -data: of two lines +data: Повідомлення 3 +data: з двох рядків ``` -- A message text goes after `data:`, the space after the colon is optional. -- Messages are delimited with double line breaks `\n\n`. -- To send a line break `\n`, we can immediately send one more `data:` (3rd message above). +- Текст повідомлення йде після `data:`, пробіл після двокрапки необов’язковий. +- Повідомлення розділені подвійними розривами рядків `\n\n`. +- Щоб надіслати розрив рядка `\n`, ми можемо негайно надіслати ще одне `data:` (3-е повідомлення вище). -In practice, complex messages are usually sent JSON-encoded. Line-breaks are encoded as `\n` within them, so multiline `data:` messages are not necessary. +На практиці складні повідомлення зазвичай надсилаються в кодуванні JSON. Розриви рядків у них кодуються як `\n`, тому багаторядкові повідомлення `data:` не потрібні. -For instance: +Наприклад: ```js -data: {"user":"John","message":"First line*!*\n*/!* Second line"} +data: {"user":"Тарас","message":"Перший рядок*!*\n*/!* Другий рядок"} ``` -...So we can assume that one `data:` holds exactly one message. +...Отже, можемо припустити, що один `data:` містить рівно одне повідомлення. -For each such message, the `message` event is generated: +Для кожного такого повідомлення генерується подія `message`: ```js let eventSource = new EventSource("/events/subscribe"); eventSource.onmessage = function(event) { - console.log("New message", event.data); - // will log 3 times for the data stream above + console.log("Нове повідомлення", event.data); + // буде зареєстровано 3 рази для потоку даних вище }; -// or eventSource.addEventListener('message', ...) +// чи eventSource.addEventListener('message', ...) ``` -### Cross-origin requests +### Запити з перехресних доменів -`EventSource` supports cross-origin requests, like `fetch` and any other networking methods. We can use any URL: +`EventSource` підтримує запити між різними джерелами, як-от `fetch` та будь-які інші мережеві методи. Ми можемо використовувати будь-яку URL-адресу: ```js let source = new EventSource("https://another-site.com/events"); ``` -The remote server will get the `Origin` header and must respond with `Access-Control-Allow-Origin` to proceed. +Віддалений сервер отримає заголовок `Origin` і повинен відповісти `Access-Control-Allow-Origin` , щоб продовжити. -To pass credentials, we should set the additional option `withCredentials`, like this: +Щоб передати облікові дані, ми повинні встановити додатковий параметр `withCredentials`, наприклад: ```js let source = new EventSource("https://another-site.com/events", { @@ -82,30 +82,30 @@ let source = new EventSource("https://another-site.com/events", { }); ``` -Please see the chapter for more details about cross-origin headers. +Будь ласка, перегляньте розділ , щоб дізнатися більше про заголовки з перехресними джерелами. -## Reconnection +## Повторне з’єднання -Upon creation, `new EventSource` connects to the server, and if the connection is broken -- reconnects. +Після створення `new EventSource` підключається до сервера, і якщо з’єднання розривається - автоматично підключається знову. -That's very convenient, as we don't have to care about it. +Це дуже зручно, оскільки не потрібно дбати про це. -There's a small delay between reconnections, a few seconds by default. +Між повторними з’єднаннями є невелика затримка, за замовчуванням кілька секунд. -The server can set the recommended delay using `retry:` in response (in milliseconds): +Сервер може встановити рекомендовану затримку, використовуючи `retry:` у відповідь (у мілісекундах): ```js retry: 15000 -data: Hello, I set the reconnection delay to 15 seconds +data: Привіт, я встановив затримку повторного з’єднання на 15 секунд ``` -The `retry:` may come both together with some data, or as a standalone message. +`retry:` може надсилатись як разом із деякими даними, так і окремим повідомленням. -The browser should wait that many milliseconds before reconnecting. Or longer, e.g. if the browser knows (from OS) that there's no network connection at the moment, it may wait until the connection appears, and then retry. +Браузер повинен зачекати вказану кількість мілісекунд перед повторним з’єднанням. Або довше, напр. якщо браузер знає (з ОС), що на даний момент немає підключення до мережі, він може зачекати, доки з’єднання з’явиться, а потім повторити спробу. -- If the server wants the browser to stop reconnecting, it should respond with HTTP status 204. -- If the browser wants to close the connection, it should call `eventSource.close()`: +- Якщо сервер бажає, щоб браузер припинив повторне з’єднання, він повинен відповісти HTTP статусом 204. +- Якщо браузер хоче закрити з’єднання, він повинен викликати `eventSource.close()`: ```js let eventSource = new EventSource(...); @@ -113,40 +113,40 @@ let eventSource = new EventSource(...); eventSource.close(); ``` -Also, there will be no reconnection if the response has an incorrect `Content-Type` or its HTTP status differs from 301, 307, 200 and 204. In such cases the `"error"` event will be emitted, and the browser won't reconnect. +Крім того, не буде повторного з’єднання, якщо відповідь містить неправильний `Content-Type` або його статус HTTP відрізняється від 301, 307, 200 і 204. У таких випадках буде створено подію `"помилка"`, і браузер не підключатиметься повторно. ```smart -When a connection is finally closed, there's no way to "reopen" it. If we'd like to connect again, just create a new `EventSource`. +Коли з’єднання остаточно закрито, його неможливо «відкрити» знову. Якщо ми хочемо знову під’єднатися, доведеться створити новий `EventSource`. ``` -## Message id +## Ідентифікатор повідомлення -When a connection breaks due to network problems, either side can't be sure which messages were received, and which weren't. +Коли з’єднання розривається через проблеми з мережею, жодна сторона не може бути впевнена, які повідомлення були отримані, а які ні. -To correctly resume the connection, each message should have an `id` field, like this: +Щоб правильно відновити з’єднання, кожне повідомлення має мати поле `id`, наприклад: ``` -data: Message 1 +data: Повідомлення 1 id: 1 -data: Message 2 +data: Повідомлення 2 id: 2 -data: Message 3 -data: of two lines +data: Повідомлення 3 +data: з двох рядків id: 3 ``` -When a message with `id:` is received, the browser: +Коли повідомлення з `id:` отримане браузером: -- Sets the property `eventSource.lastEventId` to its value. -- Upon reconnection sends the header `Last-Event-ID` with that `id`, so that the server may re-send following messages. +- Встановлюється значення властивості `eventSource.lastEventId`. +- Після повторного підключення надсилається заголовок `Last-Event-ID` з цим `id`, щоб сервер міг повторно надіслати наступні повідомлення. -```smart header="Put `id:` after `data:`" -Please note: the `id` is appended below message `data` by the server, to ensure that `lastEventId` is updated after the message is received. +```smart header="Зазначайте `id:` після `data:`" +Зверніть увагу: `id` додається сервером під повідомленням `data` , щоб гарантувати, що `lastEventId` оновлюється після отримання повідомлення. ``` -## Connection status: readyState +## Статус підключення: readyState The `EventSource` object has `readyState` property, that has one of three values: From d5d59f6b9e49abdb8156f26904755ee31613ccbc Mon Sep 17 00:00:00 2001 From: Yevhenii Stuzhuk Date: Sat, 4 Jun 2022 15:23:57 +0300 Subject: [PATCH 12/70] Update article.md 88,9% --- 5-network/12-server-sent-events/article.md | 78 +++++++++++----------- 1 file changed, 39 insertions(+), 39 deletions(-) diff --git a/5-network/12-server-sent-events/article.md b/5-network/12-server-sent-events/article.md index 4e00fa284..c1e7306be 100644 --- a/5-network/12-server-sent-events/article.md +++ b/5-network/12-server-sent-events/article.md @@ -148,98 +148,98 @@ id: 3 ## Статус підключення: readyState -The `EventSource` object has `readyState` property, that has one of three values: +Об'єкт `EventSource` має властивість `readyState`, яка має одне з трьох значень: ```js no-beautify -EventSource.CONNECTING = 0; // connecting or reconnecting -EventSource.OPEN = 1; // connected -EventSource.CLOSED = 2; // connection closed +EventSource.CONNECTING = 0; // підключення або перепідключення +EventSource.OPEN = 1; // сполучено +EventSource.CLOSED = 2; // з'єднання закрите ``` -When an object is created, or the connection is down, it's always `EventSource.CONNECTING` (equals `0`). +Коли створюється об’єкт або з’єднання розривається `EventSource.CONNECTING` (дорівнює `0`). -We can query this property to know the state of `EventSource`. +Ми можемо запитати цю властивість, щоб дізнатися стан `EventSource`. -## Event types +## Типи подій -By default `EventSource` object generates three events: +Типово об’єкт `EventSource` генерує три події: -- `message` -- a message received, available as `event.data`. -- `open` -- the connection is open. -- `error` -- the connection could not be established, e.g. the server returned HTTP 500 status. +- `message` -- отримане повідомлення, доступне як `event.data`. +- `open` -- з'єднання відкрите. +- `error` -- не вдалося встановити з’єднання, напр. сервер повернув статус HTTP 500. -The server may specify another type of event with `event: ...` at the event start. +Сервер може вказати інший тип події з `event: ...` на початку події. -For example: +Наприклад: ``` event: join -data: Bob +data: Боб -data: Hello +data: Привіт event: leave -data: Bob +data: Боб ``` -To handle custom events, we must use `addEventListener`, not `onmessage`: +Щоб обробляти спеціальні події, ми повинні використовувати `addEventListener`, а не `onmessage`: ```js eventSource.addEventListener('join', event => { - alert(`Joined ${event.data}`); + alert(`Приєднався ${event.data}`); }); eventSource.addEventListener('message', event => { - alert(`Said: ${event.data}`); + alert(`Сказав: ${event.data}`); }); eventSource.addEventListener('leave', event => { - alert(`Left ${event.data}`); + alert(`Вийшов ${event.data}`); }); ``` -## Full example +## Повний приклад -Here's the server that sends messages with `1`, `2`, `3`, then `bye` and breaks the connection. +Ось сервер, який надсилає повідомлення з `1`, `2`, `3`, потім `bye` та розриває з'єднання. -Then the browser automatically reconnects. +Потім браузер автоматично відновить з’єднання. [codetabs src="eventsource"] -## Summary +## Резюме -`EventSource` object automatically establishes a persistent connection and allows the server to send messages over it. +Об’єкт `EventSource` автоматично встановлює постійне з’єднання і дозволяє серверу надсилати повідомлення через нього. -It offers: -- Automatic reconnect, with tunable `retry` timeout. -- Message ids to resume events, the last received identifier is sent in `Last-Event-ID` header upon reconnection. -- The current state is in the `readyState` property. +Він пропонує: +- Автоматичне перепідключення, з затримкою `retry` що налаштовується. +- Ідентифікатори повідомлень для відновлення подій, останній отриманий id надсилається в заголовку `Last-Event-ID` після повторного з’єднання. +- Поточний стан знаходиться у властивості `readyState`. -That makes `EventSource` a viable alternative to `WebSocket`, as the latter is more low-level and lacks such built-in features (though they can be implemented). +Це робить `EventSource` життєздатною альтернативою `WebSocket`, оскільки останній є більш низькорівневим і не має таких вбудованих функцій (хоча їх можна реалізувати). -In many real-life applications, the power of `EventSource` is just enough. +У багатьох реальних програмах потужності `EventSource` якраз достатньо. -Supported in all modern browsers (not IE). +Підтримується у всіх сучасних браузерах (не в IE). -The syntax is: +Синтаксис такий: ```js let source = new EventSource(url, [credentials]); ``` -The second argument has only one possible option: `{ withCredentials: true }`, it allows sending cross-origin credentials. +Другий аргумент має лише один можливий варіант: `{ withCredentials: true }`, він дозволяє надсилати облікові дані між різними джерелами. -Overall cross-origin security is same as for `fetch` and other network methods. +Загальна безпека між різними джерелами така ж, як і для `fetch` та інших мережевих методів. -### Properties of an `EventSource` object +### Властивості об'єкта `EventSource` `readyState` -: The current connection state: either `EventSource.CONNECTING (=0)`, `EventSource.OPEN (=1)` or `EventSource.CLOSED (=2)`. +: Поточний стан підключення: або `EventSource.CONNECTING (=0)`, `EventSource.OPEN (=1)` чи `EventSource.CLOSED (=2)`. `lastEventId` -: The last received `id`. Upon reconnection the browser sends it in the header `Last-Event-ID`. +: Останній отриманний `id`. Після повторного з’єднання браузер надсилає його в заголовку `Last-Event-ID`. -### Methods +### Методик `close()` : Closes the connection. From 3fe04de73506ef3af055bfafeea59e8035429b4e Mon Sep 17 00:00:00 2001 From: Yevhenii Stuzhuk Date: Sun, 5 Jun 2022 08:13:04 +0300 Subject: [PATCH 13/70] Update article.md 100% --- 5-network/12-server-sent-events/article.md | 62 +++++++++++----------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/5-network/12-server-sent-events/article.md b/5-network/12-server-sent-events/article.md index c1e7306be..a34094c2c 100644 --- a/5-network/12-server-sent-events/article.md +++ b/5-network/12-server-sent-events/article.md @@ -18,13 +18,13 @@ Основна причина: він простіший. У багатьох програмах потужність `WebSocket` є дещо занадто великою. -Нам потрібно отримати потік даних із сервера: можливо, повідомлення в чаті чи ринкові ціни, чи що завгодно. Це те, у чому сильный `EventSource`. Також він підтримує автоматичне перепідключення, що зазвичай потрібно реалізовувати вручну за допомогою `WebSocket`. Крім того, це звичайний старий HTTP, а не новий протокол. +Нам потрібно отримати потік даних із сервера: можливо, повідомлення в чаті чи ринкові ціни, чи що завгодно. Це те, у чому сильный `EventSource`. Також він підтримує автоматичне повторне з’єднання, що зазвичай потрібно реалізовувати вручну за допомогою `WebSocket`. Крім того, це звичайний старий HTTP, а не новий протокол. ## Отримання повідомлень Щоб почати отримувати повідомлення, необхідно створити `new EventSource(url)`. -Браузер підключиться до `url` і залишить з’єднання відкритим, чекаючи на події. +Браузер приєднається до `url` і залишить з’єднання відкритим, чекаючи на події. Сервер повинен відповісти статусом 200 і заголовком `Content-Type: text/event-stream`, а потім зберегти з’єднання та писати повідомлення в спеціальному форматі, наприклад: @@ -49,7 +49,7 @@ data: з двох рядків data: {"user":"Тарас","message":"Перший рядок*!*\n*/!* Другий рядок"} ``` -...Отже, можемо припустити, що один `data:` містить рівно одне повідомлення. +...Отже, можемо припустити, що одне `data:` містить рівно одне повідомлення. Для кожного такого повідомлення генерується подія `message`: @@ -87,11 +87,11 @@ let source = new EventSource("https://another-site.com/events", { ## Повторне з’єднання -Після створення `new EventSource` підключається до сервера, і якщо з’єднання розривається - автоматично підключається знову. +Після створення `new EventSource` приєднується до сервера, і якщо з’єднання розривається - автоматично приєднується знову. Це дуже зручно, оскільки не потрібно дбати про це. -Між повторними з’єднаннями є невелика затримка, за замовчуванням кілька секунд. +Між повторними з’єднаннями є невелика затримка, типово кілька секунд. Сервер може встановити рекомендовану затримку, використовуючи `retry:` у відповідь (у мілісекундах): @@ -102,7 +102,7 @@ data: Привіт, я встановив затримку повторного `retry:` може надсилатись як разом із деякими даними, так і окремим повідомленням. -Браузер повинен зачекати вказану кількість мілісекунд перед повторним з’єднанням. Або довше, напр. якщо браузер знає (з ОС), що на даний момент немає підключення до мережі, він може зачекати, доки з’єднання з’явиться, а потім повторити спробу. +Браузер повинен зачекати вказану кількість мілісекунд перед повторним з’єднанням. Або довше, напр. якщо браузер знає (з ОС), що на даний момент немає з’єднання із мережею, він може зачекати, доки з’єднання з’явиться, а потім повторити спробу. - Якщо сервер бажає, щоб браузер припинив повторне з’єднання, він повинен відповісти HTTP статусом 204. - Якщо браузер хоче закрити з’єднання, він повинен викликати `eventSource.close()`: @@ -113,7 +113,7 @@ let eventSource = new EventSource(...); eventSource.close(); ``` -Крім того, не буде повторного з’єднання, якщо відповідь містить неправильний `Content-Type` або його статус HTTP відрізняється від 301, 307, 200 і 204. У таких випадках буде створено подію `"помилка"`, і браузер не підключатиметься повторно. +Крім того, не буде повторного з’єднання, якщо відповідь містить неправильний `Content-Type` або його статус HTTP відрізняється від 301, 307, 200 і 204. У таких випадках буде створено подію `"error"`, і браузер не підключатиметься повторно. ```smart Коли з’єднання остаточно закрито, його неможливо «відкрити» знову. Якщо ми хочемо знову під’єднатися, доведеться створити новий `EventSource`. @@ -140,20 +140,20 @@ id: 3 Коли повідомлення з `id:` отримане браузером: - Встановлюється значення властивості `eventSource.lastEventId`. -- Після повторного підключення надсилається заголовок `Last-Event-ID` з цим `id`, щоб сервер міг повторно надіслати наступні повідомлення. +- Після повторного з’єднання надсилається заголовок `Last-Event-ID` з цим `id`, щоб сервер міг повторно надіслати наступні повідомлення. ```smart header="Зазначайте `id:` після `data:`" Зверніть увагу: `id` додається сервером під повідомленням `data` , щоб гарантувати, що `lastEventId` оновлюється після отримання повідомлення. ``` -## Статус підключення: readyState +## Статус з’єднання: readyState Об'єкт `EventSource` має властивість `readyState`, яка має одне з трьох значень: ```js no-beautify -EventSource.CONNECTING = 0; // підключення або перепідключення +EventSource.CONNECTING = 0; // з’єднання або повторне з’єднання EventSource.OPEN = 1; // сполучено -EventSource.CLOSED = 2; // з'єднання закрите +EventSource.CLOSED = 2; // з’єднання закрите ``` Коли створюється об’єкт або з’єднання розривається `EventSource.CONNECTING` (дорівнює `0`). @@ -165,8 +165,8 @@ EventSource.CLOSED = 2; // з'єднання закрите Типово об’єкт `EventSource` генерує три події: - `message` -- отримане повідомлення, доступне як `event.data`. -- `open` -- з'єднання відкрите. -- `error` -- не вдалося встановити з’єднання, напр. сервер повернув статус HTTP 500. +- `open` -- з’єднання відкрите. +- `error` -- не вдалося приєднатися, напр. сервер повернув статус HTTP 500. Сервер може вказати інший тип події з `event: ...` на початку події. @@ -200,7 +200,7 @@ eventSource.addEventListener('leave', event => { ## Повний приклад -Ось сервер, який надсилає повідомлення з `1`, `2`, `3`, потім `bye` та розриває з'єднання. +Ось сервер, який надсилає повідомлення з `1`, `2`, `3`, потім `bye` та розриває з’єднання. Потім браузер автоматично відновить з’єднання. @@ -231,41 +231,41 @@ let source = new EventSource(url, [credentials]); Загальна безпека між різними джерелами така ж, як і для `fetch` та інших мережевих методів. -### Властивості об'єкта `EventSource` +### Властивості об’єкта `EventSource` `readyState` -: Поточний стан підключення: або `EventSource.CONNECTING (=0)`, `EventSource.OPEN (=1)` чи `EventSource.CLOSED (=2)`. +: Поточний стан з’єднання: або `EventSource.CONNECTING (=0)`, `EventSource.OPEN (=1)` чи `EventSource.CLOSED (=2)`. `lastEventId` : Останній отриманний `id`. Після повторного з’єднання браузер надсилає його в заголовку `Last-Event-ID`. -### Методик +### Методи `close()` -: Closes the connection. +: Замикає з’єднання. -### Events +### Події `message` -: Message received, the data is in `event.data`. +: Повідомлення отримано, дані в `event.data`. `open` -: The connection is established. +: З’єднання встановлено. `error` -: In case of an error, including both lost connection (will auto-reconnect) and fatal errors. We can check `readyState` to see if the reconnection is being attempted. +: У разі помилки, включає як втрачене з’єднання (буде автоматично відновлено), так і фатальні помилки. Ми можемо перевірити `readyState`, щоб побачити, чи робиться спроба повторного з’єднання. -The server may set a custom event name in `event:`. Such events should be handled using `addEventListener`, not `on`. +Сервер може встановити спеціальне ім’я події в `event:`. Такі події слід обробляти за допомогою `addEventListener`, а не `on`. -### Server response format +### Формат відповіді сервера -The server sends messages, delimited by `\n\n`. +Сервер надсилає повідомлення, розділені `\n\n`. -A message may have following fields: +Повідомлення може мати такі поля: -- `data:` -- message body, a sequence of multiple `data` is interpreted as a single message, with `\n` between the parts. -- `id:` -- renews `lastEventId`, sent in `Last-Event-ID` on reconnect. -- `retry:` -- recommends a retry delay for reconnections in ms. There's no way to set it from JavaScript. -- `event:` -- event name, must precede `data:`. +- `data:` -- у тілі повідомлення, послідовність кількох `data` інтерпретується як одне повідомлення з `\n` між його частинами. +- `id:` -- поновлює `lastEventId`, надісланий у `Last-Event-ID` під час повторного з’єднання. +- `retry:` -- радить затримку повторного з’єднання у мс. Немає способу встановити його за допомогою JavaScript +- `event:` -- ім’я події має передувати `data:`. -A message may include one or more fields in any order, but `id:` usually goes the last. +Повідомлення може містити один або кілька рядків у будь-якому порядку, але `id:` зазвичай йде останнім.. From d161dd19dc5bf6232cd67222319d248782bd71c7 Mon Sep 17 00:00:00 2001 From: Yevhenii Stuzhuk Date: Sun, 5 Jun 2022 08:19:34 +0300 Subject: [PATCH 14/70] Update index.html --- .../eventsource.view/index.html | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/5-network/12-server-sent-events/eventsource.view/index.html b/5-network/12-server-sent-events/eventsource.view/index.html index 795b07ebb..0faf72e79 100644 --- a/5-network/12-server-sent-events/eventsource.view/index.html +++ b/5-network/12-server-sent-events/eventsource.view/index.html @@ -2,38 +2,38 @@ - Press the "Start" to begin. + Натісніть "Пуск" для початку.
    - "Stop" to finish. + "Стоп" для зупинки. From 52bf3afb362b86767f556094fe19236dea995d18 Mon Sep 17 00:00:00 2001 From: Yevhenii Stuzhuk Date: Sun, 5 Jun 2022 12:36:53 +0300 Subject: [PATCH 15/70] Update article.md 26% --- .../06-clickjacking/article.md | 44 +++++++++---------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/3-frames-and-windows/06-clickjacking/article.md b/3-frames-and-windows/06-clickjacking/article.md index 1daa87dd0..d06707c23 100644 --- a/3-frames-and-windows/06-clickjacking/article.md +++ b/3-frames-and-windows/06-clickjacking/article.md @@ -1,59 +1,59 @@ -# The clickjacking attack +# Clickjacking атака -The "clickjacking" attack allows an evil page to click on a "victim site" *on behalf of the visitor*. +Атака типу "clickjacking" (англ. "захоплення кліка") дозволяє шкідливій сторінці натиснути посилання на "сайт-жертви" *від імені відвідувача*. -Many sites were hacked this way, including Twitter, Facebook, Paypal and other sites. They have all been fixed, of course. +Багато сайтів були зламані подібним способом, включаючи Twitter, Facebook, Paypal та інші. Усі вони, звісно ж, зараз захищені. -## The idea +## Ідея -The idea is very simple. +Ідея дуже проста. -Here's how clickjacking was done with Facebook: +Ось як clickjacking було зроблено на Facebook: -1. A visitor is lured to the evil page. It doesn't matter how. -2. The page has a harmless-looking link on it (like "get rich now" or "click here, very funny"). -3. Over that link the evil page positions a transparent ` - + */!* -
    ...And you're cool (I'm a cool hacker actually)!
    +
    ...І ти крутий (насправді я крутий хакер)!
    ``` -The full demo of the attack: +Повна демонстрація атаки: [codetabs src="clickjacking-visible" height=160] -Here we have a half-transparent ` ``` -There are other ways to work around that simple protection too. +Є й інші способи обходу цього простого захисту. -## X-Frame-Options +## Опції X-Frame -The server-side header `X-Frame-Options` can permit or forbid displaying the page inside a frame. +Заголовок на стороні сервера `X-Frame-Options` може дозволяти або забороняти відображення сторінки всередині фрейму. -It must be sent exactly as HTTP-header: the browser will ignore it if found in HTML `` tag. So, `` won't do anything. +Він має бути надісланий точно як HTTP-заголовок: браузер проігнорує його, якщо знайде в HTML `` тегу. Отже, `` нічого не дасть. -The header may have 3 values: +Заголовок може мати 3 значення: `DENY` -: Never ever show the page inside a frame. +: Ніколи не показувати сторінку всередині фрейму. `SAMEORIGIN` -: Allow inside a frame if the parent document comes from the same origin. +: Дозволити всередині фрейму, якщо батьківський документ походить із того самого джерела. `ALLOW-FROM domain` -: Allow inside a frame if the parent document is from the given domain. +: Дозволити всередині фрейму, якщо батьківський документ із заданого домену. -For instance, Twitter uses `X-Frame-Options: SAMEORIGIN`. +Наприклад, Twitter використовує `X-Frame-Options: SAMEORIGIN`. ````online -Here's the result: +Ось результат: ```html @@ -149,16 +149,16 @@ Here's the result: -Depending on your browser, the `iframe` above is either empty or alerting you that the browser won't permit that page to be navigating in this way. +Залежно від вашого браузера, `iframe` вище або порожній, або попереджає вас про те, що браузер не дозволяє відобразити цю сторінку. ```` -## Showing with disabled functionality +## Відображення з вимкненою функціональністю -The `X-Frame-Options` header has a side-effect. Other sites won't be able to show our page in a frame, even if they have good reasons to do so. +Заголовок `X-Frame-Options` має побічний ефект. Інші сайти не зможуть показати нашу сторінку у фреймі, навіть якщо у них є для цього вагомі причини. -So there are other solutions... For instance, we can "cover" the page with a `
    ` with styles `height: 100%; width: 100%;`, so that it will intercept all clicks. That `
    ` is to be removed if `window == top` or if we figure out that we don't need the protection. +Тому є інші рішення...Наприклад, ми можемо "покрити" сторінку `
    ` зі стилями `height: 100%; width: 100%;`, щоб він перехоплював усі клацання. Цей `
    ` можна видалити, якщо `window == top` або якщо ми зрозуміли, що захист нам не потрібен. -Something like this: +Щось на зразок цього: ```html ``` -The demo: +Демо: [codetabs src="protector"] -## Samesite cookie attribute +## Атрибут cookie: samesite -The `samesite` cookie attribute can also prevent clickjacking attacks. +Атрибут cookie `samesite` також може запобігти атакам клікджекінгу. -A cookie with such attribute is only sent to a website if it's opened directly, not via a frame, or otherwise. More information in the chapter . +Файл cookie з таким атрибутом надсилається на веб-сайт, лише якщо його відкрито безпосередньо, а не через фрейм чи іншим чином. Більше інформації в розділі . -If the site, such as Facebook, had `samesite` attribute on its authentication cookie, like this: +Якби сайт, наприклад Facebook, при аутентифікації мав атрибут `samesite` у файлі cookie , наприклад: ``` Set-Cookie: authorization=secret; samesite ``` -...Then such cookie wouldn't be sent when Facebook is open in iframe from another site. So the attack would fail. +...Тоді такий файл cookie не надсилатиметься, коли Facebook буде відкрито в iframe з іншого сайту. Тож атака не вдасться. -The `samesite` cookie attribute will not have an effect when cookies are not used. This may allow other websites to easily show our public, unauthenticated pages in iframes. +Атрибут cookie `samesite` не матиме ефекту, якщо файли cookie не використовуються. Це може дозволити іншим веб-сайтам легко показувати наші загальнодоступні, неавтентифіковані сторінки в iframes. -However, this may also allow clickjacking attacks to work in a few limited cases. An anonymous polling website that prevents duplicate voting by checking IP addresses, for example, would still be vulnerable to clickjacking because it does not authenticate users using cookies. +Однак це також може дозволяти атакам за допомогою clickjacking працювати в кількох обмежених випадках. Наприклад, веб-сайт анонімного опитування, який запобігає дублюванню голосування шляхом перевірки IP-адреси, все одно буде вразливим до клікджекінгу, оскільки він не автентифікує користувачів за допомогою файлів cookie. ## Summary From 3bd140eaf59da1c7e1063b0ed50d2ebbe15a386b Mon Sep 17 00:00:00 2001 From: Yevhenii Stuzhuk Date: Tue, 7 Jun 2022 05:55:46 +0300 Subject: [PATCH 18/70] Update article.md 100% --- .../06-clickjacking/article.md | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/3-frames-and-windows/06-clickjacking/article.md b/3-frames-and-windows/06-clickjacking/article.md index 412ade3e7..1305b4666 100644 --- a/3-frames-and-windows/06-clickjacking/article.md +++ b/3-frames-and-windows/06-clickjacking/article.md @@ -107,7 +107,7 @@ window.onbeforeunload = function() { ### Атрибут sandbox -Однією з речей, обмежених атрибутом `sandbox` є навігація. Ізольований iframe може не змінювати `top.location`. +Однією з речей, які можна обмежити атрибутом `sandbox` є навігація. Ізольований iframe може не змінювати `top.location`. Тож ми можемо додати iframe за допомогою `sandbox="allow-scripts allow-forms"`. Це послабить обмеження, дозволивши сценарії та форми. Але ми опускаємо `allow-top-navigation`, щоб заборонити зміну `top.location`. @@ -132,10 +132,10 @@ window.onbeforeunload = function() { : Ніколи не показувати сторінку всередині фрейму. `SAMEORIGIN` -: Дозволити всередині фрейму, якщо батьківський документ походить із того самого джерела. +: Дозволити показ всередині фрейму, якщо батьківський документ походить із того самого джерела. `ALLOW-FROM domain` -: Дозволити всередині фрейму, якщо батьківський документ із заданого домену. +: Дозволити показ всередині фрейму, якщо батьківський документ із заданого домену. Наприклад, Twitter використовує `X-Frame-Options: SAMEORIGIN`. @@ -207,15 +207,15 @@ Set-Cookie: authorization=secret; samesite Однак це також може дозволяти атакам за допомогою clickjacking працювати в кількох обмежених випадках. Наприклад, веб-сайт анонімного опитування, який запобігає дублюванню голосування шляхом перевірки IP-адреси, все одно буде вразливим до клікджекінгу, оскільки він не автентифікує користувачів за допомогою файлів cookie. -## Summary +## Резюме -Clickjacking is a way to "trick" users into clicking on a victim site without even knowing what's happening. That's dangerous if there are important click-activated actions. +Clickjacking — це спосіб «обдурити» користувачів, щоб вони натиснули на сайт-жертву, навіть не знаючи, що відбувається. Це небезпечно, якщо є важливі дії, активовані кліком. -A hacker can post a link to their evil page in a message, or lure visitors to their page by some other means. There are many variations. +Хакер може розмістити в повідомленні посилання на свою шкідливу сторінку або заманити відвідувачів якимось іншим способом. Існує багато варіацій. -From one perspective -- the attack is "not deep": all a hacker is doing is intercepting a single click. But from another perspective, if the hacker knows that after the click another control will appear, then they may use cunning messages to coerce the user into clicking on them as well. +З однієї точки зору — атака «не глибока»: все, що робить хакер, це перехоплює один клік. Але з іншої - якщо хакер знає, що після натискання з’явиться інший елемент керування, він може використовувати хитрі повідомлення, щоб змусити користувача натиснути на них. -The attack is quite dangerous, because when we engineer the UI we usually don't anticipate that a hacker may click on behalf of the visitor. So vulnerabilities can be found in totally unexpected places. +Атака досить небезпечна, тому що коли ми розробляємо інтерфейс користувача, ми зазвичай не очікуємо, що хакер може клацнути від імені відвідувача. Тому вразливість можна знайти в абсолютно несподіваних місцях. -- It is recommended to use `X-Frame-Options: SAMEORIGIN` on pages (or whole websites) which are not intended to be viewed inside frames. -- Use a covering `
    ` if we want to allow our pages to be shown in iframes, but still stay safe. +- Рекомендується використовувати `X-Frame-Options: SAMEORIGIN` на сторінках (або веб-сайтах загалом), які не призначені для перегляду всередині фреймів. +- Використовуйте `
    ` прикриття, якщо потрібно дозволити показ сторінок у iframes та залишатися в безпеці. From 18a38a375d58f102466df0e1aefd6dfaee7b095b Mon Sep 17 00:00:00 2001 From: Yevhenii Stuzhuk Date: Tue, 7 Jun 2022 05:56:51 +0300 Subject: [PATCH 19/70] Update iframe.html --- .../06-clickjacking/top-location.view/iframe.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/3-frames-and-windows/06-clickjacking/top-location.view/iframe.html b/3-frames-and-windows/06-clickjacking/top-location.view/iframe.html index da477f400..c2e9c2a66 100644 --- a/3-frames-and-windows/06-clickjacking/top-location.view/iframe.html +++ b/3-frames-and-windows/06-clickjacking/top-location.view/iframe.html @@ -7,7 +7,7 @@ -
    Changes top.location to javascript.info
    +
    Змінює top.location на javascript.info
    - This text is always visible. + Цей текст завжди видно. - But if the page was open inside a document from another domain, the div over it would prevent any actions. + Але якщо сторінка була відкрита всередині документа з іншого домену, div над нею завадить будь-яким діям. - + From fb0898901ea20d35efabdd2044f31d29fe313ac3 Mon Sep 17 00:00:00 2001 From: Yevhenii Stuzhuk Date: Tue, 7 Jun 2022 06:05:08 +0300 Subject: [PATCH 22/70] Update facebook.html --- .../06-clickjacking/clickjacking.view/facebook.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/3-frames-and-windows/06-clickjacking/clickjacking.view/facebook.html b/3-frames-and-windows/06-clickjacking/clickjacking.view/facebook.html index 6b8231c71..e0c0c3691 100644 --- a/3-frames-and-windows/06-clickjacking/clickjacking.view/facebook.html +++ b/3-frames-and-windows/06-clickjacking/clickjacking.view/facebook.html @@ -3,7 +3,7 @@ - + From 0ef68a94feef71fd81a19d2eccca87e6c16b9c99 Mon Sep 17 00:00:00 2001 From: Yevhenii Stuzhuk Date: Tue, 7 Jun 2022 06:06:43 +0300 Subject: [PATCH 23/70] Update index.html --- .../06-clickjacking/clickjacking.view/index.html | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/3-frames-and-windows/06-clickjacking/clickjacking.view/index.html b/3-frames-and-windows/06-clickjacking/clickjacking.view/index.html index 05ec48cb8..127cb4ae1 100644 --- a/3-frames-and-windows/06-clickjacking/clickjacking.view/index.html +++ b/3-frames-and-windows/06-clickjacking/clickjacking.view/index.html @@ -19,14 +19,14 @@ } -
    Click to get rich now:
    +
    Натисніть, щоб розбагатіти:
    - + - + -
    ...And you're cool (I'm a cool hacker actually)!
    +
    ...І ти крутий (насправді я крутий хакер)!
    From 6e53413b6725a83513075a7c18e1186df059ab99 Mon Sep 17 00:00:00 2001 From: Yevhenii Stuzhuk Date: Tue, 7 Jun 2022 06:07:21 +0300 Subject: [PATCH 24/70] Update facebook.html --- .../06-clickjacking/clickjacking-visible.view/facebook.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/3-frames-and-windows/06-clickjacking/clickjacking-visible.view/facebook.html b/3-frames-and-windows/06-clickjacking/clickjacking-visible.view/facebook.html index 6b8231c71..e0c0c3691 100644 --- a/3-frames-and-windows/06-clickjacking/clickjacking-visible.view/facebook.html +++ b/3-frames-and-windows/06-clickjacking/clickjacking-visible.view/facebook.html @@ -3,7 +3,7 @@ - + From c80ed0f95c975118447aee2abe55d708d83d277e Mon Sep 17 00:00:00 2001 From: Yevhenii Stuzhuk Date: Tue, 7 Jun 2022 06:08:22 +0300 Subject: [PATCH 25/70] Update index.html --- .../06-clickjacking/clickjacking-visible.view/index.html | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/3-frames-and-windows/06-clickjacking/clickjacking-visible.view/index.html b/3-frames-and-windows/06-clickjacking/clickjacking-visible.view/index.html index 9f8d82197..3e592c467 100644 --- a/3-frames-and-windows/06-clickjacking/clickjacking-visible.view/index.html +++ b/3-frames-and-windows/06-clickjacking/clickjacking-visible.view/index.html @@ -19,14 +19,14 @@ } -
    Click to get rich now:
    +
    Натісніть, щоб розбагатіти:
    - + - + -
    ...And you're cool (I'm a cool hacker actually)!
    +
    ...І ти крутий (насправді я крутий хакер)!
    From 469e2d376a58d7f0551e7220f07b5013d0779f8c Mon Sep 17 00:00:00 2001 From: Yevhenii Stuzhuk Date: Wed, 8 Jun 2022 05:56:54 +0300 Subject: [PATCH 26/70] Update article.md 52.2% --- 7-animation/2-css-animations/article.md | 116 ++++++++++++------------ 1 file changed, 58 insertions(+), 58 deletions(-) diff --git a/7-animation/2-css-animations/article.md b/7-animation/2-css-animations/article.md index 8dd64f39a..0a71bcc92 100644 --- a/7-animation/2-css-animations/article.md +++ b/7-animation/2-css-animations/article.md @@ -1,16 +1,16 @@ -# CSS-animations +# CSS-анімації -CSS animations make it possible to do simple animations without JavaScript at all. +CSS-анімація дає змогу робити прості анімації взагалі без JavaScript. -JavaScript can be used to control CSS animations and make them even better, with little code. +JavaScript можна використовувати для керування анімацією CSS та покращення її використовуючи трохи коду. -## CSS transitions [#css-transition] +## CSS-переходи [#css-transition] -The idea of CSS transitions is simple. We describe a property and how its changes should be animated. When the property changes, the browser paints the animation. +Ідея переходів CSS проста. Ми описуємо властивість і як її зміни мають бути анімовані. Коли властивість змінюється, браузер малює анімацію. -That is, all we need is to change the property, and the fluid transition will be done by the browser. +Тобто все, що нам потрібно, це змінити властивість, і плавний перехід буде здійснюватися браузером. -For instance, the CSS below animates changes of `background-color` for 3 seconds: +Наприклад, наведений нижче CSS анімує зміни `background-color` протягом 3 секунд: ```css .animated { @@ -19,12 +19,12 @@ For instance, the CSS below animates changes of `background-color` for 3 seconds } ``` -Now if an element has `.animated` class, any change of `background-color` is animated during 3 seconds. +Якщо елемент має клас `.animated`, будь-яка зміна `background-color` анімується протягом 3 секунд. -Click the button below to animate the background: +Натисніть кнопку нижче, щоб анімувати фон: ```html run autorun height=60 - + ``` -There are many articles about `@keyframes` and a [detailed specification](https://drafts.csswg.org/css-animations/). +Є багато статей про `@keyframes` та [датальна специфікація](https://drafts.csswg.org/css-animations/). -You probably won't need `@keyframes` often, unless everything is in constant motion on your sites. +Ймовірно, `@keyframes` не знадобляться вам часто, хіба якщо на ваших сайтах все постійно рухається. -## Performance +## Продуктивність Most CSS properties can be animated, because most of them are numeric values. For instance, `width`, `color`, `font-size` are all numbers. When you animate them, the browser gradually changes these numbers frame by frame, creating a smooth effect. From 5f35c4602ee4bb79e64de180b4bfe364fba48f41 Mon Sep 17 00:00:00 2001 From: Yevhenii Stuzhuk Date: Fri, 10 Jun 2022 08:57:02 +0300 Subject: [PATCH 29/70] Update article.md 100% --- 7-animation/2-css-animations/article.md | 64 ++++++++++++------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/7-animation/2-css-animations/article.md b/7-animation/2-css-animations/article.md index 10a1dca2c..4053ca956 100644 --- a/7-animation/2-css-animations/article.md +++ b/7-animation/2-css-animations/article.md @@ -409,39 +409,39 @@ boat.onclick = function() { ## Продуктивність -Most CSS properties can be animated, because most of them are numeric values. For instance, `width`, `color`, `font-size` are all numbers. When you animate them, the browser gradually changes these numbers frame by frame, creating a smooth effect. +Більшість властивостей CSS можна анімувати, оскільки більшість із них є числовими значеннями. Наприклад, `width`, `color`, `font-size` є числами. Коли ви анімуєте їх, браузер поступово змінює ці числа кадр за кадром, створюючи плавний ефект. -However, not all animations will look as smooth as you'd like, because different CSS properties cost differently to change. +Однак не всі анімації виглядатимуть так гладко, як хотілося б, оскільки різні властивості змінюються по-різному. -In more technical details, when there's a style change, the browser goes through 3 steps to render the new look: +Якщо говорити більше про технічні деталі, коли відбувається зміна стилю, браузер виконує 3 кроки для відтворення нового вигляду: -1. **Layout**: re-compute the geometry and position of each element, then -2. **Paint**: re-compute how everything should look like at their places, including background, colors, -3. **Composite**: render the final results into pixels on screen, apply CSS transforms if they exist. +1. **Макет**: перерахувати геометрію, а потім положення кожного елемента +2. **Фарбування**: перерахувати, як все має виглядати на своїх місцях, включаючи фон, кольори, +3. **Композиція**: відтворити кінцеві результати в пікселях на екрані, застосувати CSS-перетворення, якщо вони існують. -During a CSS animation, this process repeats every frame. However, CSS properties that never affect geometry or position, such as `color`, may skip the Layout step. If a `color` changes, the browser doesn't calculate any new geometry, it goes to Paint -> Composite. And there are few properties that directly go to Composite. You can find a longer list of CSS properties and which stages they trigger at . +Під час CSS-анімації цей процес повторюється для кожного кадра. Однак властивості CSS, які ніколи не впливають на геометрію або положення, наприклад, `color`, можуть пропускатись кроці макета. Якщо `color` змінюється, браузер не обчислює нову геометрію, він переходить до кроків Фарбування -> Композиція. І є кілька властивостей, які безпосередньо переходять до Композиції. Ви можете знайти довший список властивостей CSS та на яких етапах вони запускаються тут . -The calculations may take time, especially on pages with many elements and a complex layout. And the delays are actually visible on most devices, leading to "jittery", less fluid animations. +Обчислення може зайняти багато часу, особливо на сторінках із великою кількістю елементів і складним макетом. І затримки фактично помітні на більшості пристроїв, що призводить до «тривожності», менш плавної анімації. -Animations of properties that skip the Layout step are faster. It's even better if Paint is skipped too. +Анімація властивостей, які пропускають крок макета, швидше. Навіть ліпше, ніж у випадку з пропусканням кроку фарбування. -The `transform` property is a great choice, because: -- CSS transforms affect the target element box as a whole (rotate, flip, stretch, shift it). -- CSS transforms never affect neighbour elements. +Властивість `transform` є чудовим вибором, оскільки: +- Перетворення CSS впливають на блок цільового елемента в цілому (повертають, перевертають, розтягують, зміщують його). +- CSS-перетворення ніколи не впливають на сусідні елементи. -...So browsers apply `transform` "on top" of existing Layout and Paint calculations, in the Composite stage. +...Тому браузери застосовують `transform` "попереду" наявних обчислень Макету та Фарбування на етапі Композиції. -In other words, the browser calculates the Layout (sizes, positions), paints it with colors, backgrounds, etc at the Paint stage, and then applies `transform` to element boxes that need it. +Іншими словами, браузер обчислює макет (розміри, положення), розфарбовує його за допомогою colors, background тощо на етапі Фарбування, а потім застосовує `transform` до блоків елементів, які цього потребують. -Changes (animations) of the `transform` property never trigger Layout and Paint steps. More than that, the browser leverages the graphics accelerator (a special chip on the CPU or graphics card) for CSS transforms, thus making them very efficient. +Зміни (анімації) властивості `transform` ніколи не запускають кроки Макет та Фарбування. Більше того, браузер використовує графічний прискорювач (спеціальний чіп на ЦП або відеокарті) для CSS-перетворень, що робить їх дуже ефективними. -Luckily, the `transform` property is very powerful. By using `transform` on an element, you could rotate and flip it, stretch and shrink it, move it around, and [much more](https://developer.mozilla.org/docs/Web/CSS/transform#syntax). So instead of `left/margin-left` properties we can use `transform: translateX(…)`, use `transform: scale` for increasing element size, etc. +На щастя, властивість `transform` дуже потужна. Використовуючи `transform` для елемента, ви можете обертати та повертати його, розтягувати та зменшувати, переміщувати та [багато іншого](https://developer.mozilla.org/docs/Web/CSS/transform#syntax ). Тому замість властивостей `left/margin-left` ми можемо використовувати `transform: translateX(…)`, `transform: scale` для збільшення розміру елемента, тощо. -The `opacity` property also never triggers Layout (also skips Paint in Mozilla Gecko). We can use it for show/hide or fade-in/fade-out effects. +Властивість `opacity` ніколи не запускає Макет (в Mozilla Gecko також пропускається Фарбування). Ми можемо використовувати його для ефектів відображення/приховування або посилення/згасання. -Paring `transform` with `opacity` can usually solve most of our needs, providing fluid, good-looking animations. +Поєднання `transform` з `opacity` зазвичай може вирішувати більшість наших потреб, забезпечуючи плавну, гарну анімацію. -For example, here clicking on the `#boat` element adds the class with `transform: translateX(300)` and `opacity: 0`, thus making it move `300px` to the right and disappear: +Наприклад, тут клік на елемент `#boat` додає клас із `transform: translateX(300)` та `opacity: 0`, таким чином змушуючи його рухатись на `300px` праворуч і зникати: ```html run height=260 autorun no-beautify @@ -462,10 +462,10 @@ For example, here clicking on the `#boat` element adds the class with `transform ``` -Here's a more complex example, with `@keyframes`: +Ось більш складний приклад із `@keyframes`: ```html run height=80 autorun no-beautify -

    click me to start / stop

    +

    натисніть на мене, щоб почати/зупинити

    ``` -## Summary +## Підсумки -CSS animations allow smoothly (or step-by-step) animated changes of one or multiple CSS properties. +Анімація CSS дозволяє плавно (або покроково) анімувати зміну однієї або кількох властивостей CSS. -They are good for most animation tasks. We're also able to use JavaScript for animations, the next chapter is devoted to that. +Вона гарна для більшості завданнь анімації. Ми також можемо використовувати JavaScript для анімації, цьому присвячений наступний розділ. -Limitations of CSS animations compared to JavaScript animations: +Обмеження анімації CSS порівняно з JavaScript: ```compare plus="CSS animations" minus="JavaScript animations" -+ Simple things done simply. -+ Fast and lightweight for CPU. -- JavaScript animations are flexible. They can implement any animation logic, like an "explosion" of an element. -- Not just property changes. We can create new elements in JavaScript as part of the animation. ++ Прості речі робляться просто. ++ Швидка та легкиа для процесора. +- Анімації JavaScript є гнучкими. Вони можуть реалізувати будь-яку логіку анімації, навіть «вибух» елемента. +- Не тільки властивості змінюються. В JavaScript ми можемо створювати нові елементи як частину анімації. ``` -In early examples in this chapter, we animate `font-size`, `left`, `width`, `height`, etc. In real life projects, we should use `transform: scale()` and `transform: translate()` for better performance. +У перших прикладах цього розділу ми анімували `font-size`, `left`, `width`, `height` тощо. У реальних проектах ми повинні використовувати `transform: scale()` і `transform: translate()` для кращої продуктивності. -The majority of animations can be implemented using CSS as described in this chapter. And the `transitionend` event allows JavaScript to be run after the animation, so it integrates fine with the code. +Більшість анімацій можна реалізувати за допомогою CSS, як описано в цьому розділі. А подія `transitionend` дозволяє запускати JavaScript після анімації, тому вона добре інтегрується з кодом. -But in the next chapter we'll do some JavaScript animations to cover more complex cases. +Але в наступному розділі ми зробимо кілька анімацій JavaScript, щоб охопити більш складні випадки. From 999ab9d7da561e8f1b251ab4173a33946dbf3948 Mon Sep 17 00:00:00 2001 From: Yevhenii Stuzhuk Date: Sun, 12 Jun 2022 08:07:35 +0300 Subject: [PATCH 30/70] Update task.md --- .../4-animate-circle-callback/task.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/7-animation/2-css-animations/4-animate-circle-callback/task.md b/7-animation/2-css-animations/4-animate-circle-callback/task.md index 4a20ca604..f2aaa7097 100644 --- a/7-animation/2-css-animations/4-animate-circle-callback/task.md +++ b/7-animation/2-css-animations/4-animate-circle-callback/task.md @@ -1,15 +1,15 @@ -# Animated circle with callback +# Коло анимоване за допомогою функції. -In the task an animated growing circle is shown. +У завданні показана анімація зростання кола. -Now let's say we need not just a circle, but to show a message inside it. The message should appear *after* the animation is complete (the circle is fully grown), otherwise it would look ugly. +Припустимо, що нам потрібно показати повідомлення всередині нього. Повідомлення має з’явитися *після* завершення анімації (коли коло повністю виросло), інакше воно виглядатиме негарно. -In the solution of the task, the function `showCircle(cx, cy, radius)` draws the circle, but gives no way to track when it's ready. +У вирішенні завдання функція `showCircle(cx, cy, radius)` малює коло, але не дає можливості відстежити, коли воно готове. -Add a callback argument: `showCircle(cx, cy, radius, callback)` to be called when the animation is complete. The `callback` should receive the circle `
    ` as an argument. +Додайте аргументом функцію зворотного виклику: `showCircle(cx, cy, radius, callback)`, яка буде викликатися після завершення анімації. Вона має отримувати коло `
    ` як аргумент. -Here's the example: +Ось приклад: ```js showCircle(150, 150, 100, div => { @@ -18,8 +18,8 @@ showCircle(150, 150, 100, div => { }); ``` -Demo: +Демо: [iframe src="solution" height=260] -Take the solution of the task as the base. +За основу візьмемо рішення задачі From fafe78519ad75d2969c25ca82deea8a4aaf15335 Mon Sep 17 00:00:00 2001 From: Yevhenii Stuzhuk Date: Sun, 12 Jun 2022 08:07:53 +0300 Subject: [PATCH 31/70] Update task.md --- 7-animation/2-css-animations/4-animate-circle-callback/task.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/7-animation/2-css-animations/4-animate-circle-callback/task.md b/7-animation/2-css-animations/4-animate-circle-callback/task.md index f2aaa7097..e4ec4a546 100644 --- a/7-animation/2-css-animations/4-animate-circle-callback/task.md +++ b/7-animation/2-css-animations/4-animate-circle-callback/task.md @@ -1,5 +1,5 @@ -# Коло анимоване за допомогою функції. +# Коло анімоване за допомогою функції. У завданні показана анімація зростання кола. From 76007bcca785e38c196b40f26ae7832153cbebc2 Mon Sep 17 00:00:00 2001 From: Yevhenii Stuzhuk Date: Sun, 12 Jun 2022 08:09:00 +0300 Subject: [PATCH 32/70] Update index.html --- 7-animation/2-css-animations/boat.view/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/7-animation/2-css-animations/boat.view/index.html b/7-animation/2-css-animations/boat.view/index.html index 60b704262..395114f51 100644 --- a/7-animation/2-css-animations/boat.view/index.html +++ b/7-animation/2-css-animations/boat.view/index.html @@ -13,7 +13,7 @@ From 22fdad6ed64fc9e22bebe53275087fd9fe64e279 Mon Sep 17 00:00:00 2001 From: Yevhenii Stuzhuk Date: Sun, 12 Jun 2022 08:11:33 +0300 Subject: [PATCH 34/70] Update index.html --- 7-animation/2-css-animations/step-end.view/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/7-animation/2-css-animations/step-end.view/index.html b/7-animation/2-css-animations/step-end.view/index.html index 2c8df7275..bae85ed49 100644 --- a/7-animation/2-css-animations/step-end.view/index.html +++ b/7-animation/2-css-animations/step-end.view/index.html @@ -8,7 +8,7 @@ - Click below to animate: + Натисніть нижче, для анімації:
    0123456789
    From d051d1eba0ba7aae02f4816226d51938c91f127f Mon Sep 17 00:00:00 2001 From: Yevhenii Stuzhuk Date: Sun, 12 Jun 2022 08:11:49 +0300 Subject: [PATCH 35/70] Update index.html --- 7-animation/2-css-animations/step-end.view/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/7-animation/2-css-animations/step-end.view/index.html b/7-animation/2-css-animations/step-end.view/index.html index bae85ed49..daee173bb 100644 --- a/7-animation/2-css-animations/step-end.view/index.html +++ b/7-animation/2-css-animations/step-end.view/index.html @@ -8,7 +8,7 @@ - Натисніть нижче, для анімації: + Натісніть нижче, для анімації:
    0123456789
    From f0f4d93e5d5d279056fbd68e4f504616eca1d685 Mon Sep 17 00:00:00 2001 From: Yevhenii Stuzhuk Date: Sun, 12 Jun 2022 08:13:26 +0300 Subject: [PATCH 36/70] Update index.html --- 7-animation/2-css-animations/step.view/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/7-animation/2-css-animations/step.view/index.html b/7-animation/2-css-animations/step.view/index.html index 2c8df7275..13506bf75 100644 --- a/7-animation/2-css-animations/step.view/index.html +++ b/7-animation/2-css-animations/step.view/index.html @@ -8,7 +8,7 @@ - Click below to animate: + Натисніть для анімації:
    0123456789
    From 58e0228d400de6656ce514dc899f565040469fbb Mon Sep 17 00:00:00 2001 From: Yevhenii Stuzhuk Date: Sun, 12 Jun 2022 08:14:21 +0300 Subject: [PATCH 37/70] Update index.html --- .../2-css-animations/digits-negative-delay.view/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/7-animation/2-css-animations/digits-negative-delay.view/index.html b/7-animation/2-css-animations/digits-negative-delay.view/index.html index 3972fa1e9..3d09d8df3 100644 --- a/7-animation/2-css-animations/digits-negative-delay.view/index.html +++ b/7-animation/2-css-animations/digits-negative-delay.view/index.html @@ -8,7 +8,7 @@ - Натісніть нижче, для анімації + Натисніть для анімації:
    0123456789
    From d3cdbac448291f7025257758bada469931c3c567 Mon Sep 17 00:00:00 2001 From: Yevhenii Stuzhuk Date: Sun, 12 Jun 2022 08:14:42 +0300 Subject: [PATCH 38/70] Update index.html --- 7-animation/2-css-animations/digits.view/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/7-animation/2-css-animations/digits.view/index.html b/7-animation/2-css-animations/digits.view/index.html index a156d8189..7ea2e3593 100644 --- a/7-animation/2-css-animations/digits.view/index.html +++ b/7-animation/2-css-animations/digits.view/index.html @@ -8,7 +8,7 @@ - Click below to animate: + Натисніть для анімації:
    0123456789
    From 410a0e183225200a21f99b5abc165e3096e484fe Mon Sep 17 00:00:00 2001 From: Yevhenii Stuzhuk Date: Sun, 12 Jun 2022 08:15:01 +0300 Subject: [PATCH 39/70] Update index.html --- 7-animation/2-css-animations/step-end.view/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/7-animation/2-css-animations/step-end.view/index.html b/7-animation/2-css-animations/step-end.view/index.html index daee173bb..3d9796047 100644 --- a/7-animation/2-css-animations/step-end.view/index.html +++ b/7-animation/2-css-animations/step-end.view/index.html @@ -8,7 +8,7 @@ - Натісніть нижче, для анімації: + Натисніть для анімації:
    0123456789
    From 18f9da2e66759272ce9beda35e5f9c1b38beb804 Mon Sep 17 00:00:00 2001 From: Yevhenii Stuzhuk Date: Sun, 3 Jul 2022 09:15:17 +0300 Subject: [PATCH 40/70] Update 3-frames-and-windows/06-clickjacking/article.md Co-authored-by: Stanislav --- 3-frames-and-windows/06-clickjacking/article.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/3-frames-and-windows/06-clickjacking/article.md b/3-frames-and-windows/06-clickjacking/article.md index 1305b4666..151bad808 100644 --- a/3-frames-and-windows/06-clickjacking/article.md +++ b/3-frames-and-windows/06-clickjacking/article.md @@ -39,7 +39,7 @@ iframe { /* iframe із сайту жертви */ *!* - + */!*
    ...І ти крутий (насправді я крутий хакер)!
    From a6806c06e10effb50ba6f8d3e4d11532240627c0 Mon Sep 17 00:00:00 2001 From: Yevhenii Stuzhuk Date: Sun, 3 Jul 2022 09:16:13 +0300 Subject: [PATCH 41/70] Update 3-frames-and-windows/06-clickjacking/article.md Co-authored-by: Stanislav --- 3-frames-and-windows/06-clickjacking/article.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/3-frames-and-windows/06-clickjacking/article.md b/3-frames-and-windows/06-clickjacking/article.md index 151bad808..7b1f4b853 100644 --- a/3-frames-and-windows/06-clickjacking/article.md +++ b/3-frames-and-windows/06-clickjacking/article.md @@ -57,7 +57,7 @@ iframe { /* iframe із сайту жертви */ [codetabs src="clickjacking" height=160] -Все, що нам потрібно для атаки – це розташувати ` From 3a101413dbc2f773be61ae4c08f3998284e68e3e Mon Sep 17 00:00:00 2001 From: Yevhenii Stuzhuk Date: Sun, 3 Jul 2022 09:17:31 +0300 Subject: [PATCH 48/70] Update 3-frames-and-windows/06-clickjacking/clickjacking-visible.view/index.html Co-authored-by: Stanislav --- .../06-clickjacking/clickjacking-visible.view/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/3-frames-and-windows/06-clickjacking/clickjacking-visible.view/index.html b/3-frames-and-windows/06-clickjacking/clickjacking-visible.view/index.html index 4882eab11..65dfdda17 100644 --- a/3-frames-and-windows/06-clickjacking/clickjacking-visible.view/index.html +++ b/3-frames-and-windows/06-clickjacking/clickjacking-visible.view/index.html @@ -24,7 +24,7 @@ - +
    ...І ти крутий (насправді я крутий хакер)!
    From 08ed6b13ff30847304f97fc8d24a70358d49bc03 Mon Sep 17 00:00:00 2001 From: Yevhenii Stuzhuk Date: Sun, 3 Jul 2022 09:17:38 +0300 Subject: [PATCH 49/70] Update 3-frames-and-windows/06-clickjacking/clickjacking.view/index.html Co-authored-by: Stanislav --- .../06-clickjacking/clickjacking.view/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/3-frames-and-windows/06-clickjacking/clickjacking.view/index.html b/3-frames-and-windows/06-clickjacking/clickjacking.view/index.html index 127cb4ae1..c982bf44d 100644 --- a/3-frames-and-windows/06-clickjacking/clickjacking.view/index.html +++ b/3-frames-and-windows/06-clickjacking/clickjacking.view/index.html @@ -24,7 +24,7 @@ - +
    ...І ти крутий (насправді я крутий хакер)!
    From 74de077af71fbada3681e07957b80bbb8381612e Mon Sep 17 00:00:00 2001 From: Stanislav Date: Tue, 12 Jul 2022 11:42:09 +0300 Subject: [PATCH 50/70] Apply suggestions from code review --- 3-frames-and-windows/06-clickjacking/article.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/3-frames-and-windows/06-clickjacking/article.md b/3-frames-and-windows/06-clickjacking/article.md index daf20d606..1822a5f57 100644 --- a/3-frames-and-windows/06-clickjacking/article.md +++ b/3-frames-and-windows/06-clickjacking/article.md @@ -191,11 +191,11 @@ window.onbeforeunload = function() { ## Атрибут cookie: samesite -Атрибут cookie `samesite` також може запобігти атакам клікджекінгу. +Атрибут cookie `samesite` також може запобігти clickjacking атакам. Файл cookie з таким атрибутом надсилається на веб-сайт, лише якщо його відкрито безпосередньо, а не через фрейм чи іншим чином. Більше інформації в розділі . -Якби сайт, наприклад Facebook, при аутентифікації мав атрибут `samesite` у файлі cookie , наприклад: +Якби сайт, наприклад Facebook, при аутентифікації мав атрибут `samesite` у файлі cookie, ось так: ``` Set-Cookie: authorization=secret; samesite @@ -203,11 +203,11 @@ Set-Cookie: authorization=secret; samesite ...Тоді такий файл cookie не надсилатиметься, коли Facebook буде відкрито в iframe з іншого сайту. Тож атака не вдасться. -Атрибут cookie `samesite` не матиме ефекту, якщо файли cookie не використовуються. Це може дозволити іншим веб-сайтам легко показувати наші загальнодоступні, неавтентифіковані сторінки в iframes. +Атрибут cookie `samesite` не матиме ефекту, якщо файли cookies не використовуються. Це може дозволити іншим веб-сайтам легко показувати наші загальнодоступні, неавтентифіковані сторінки в iframes. -Однак це також може дозволяти атакам за допомогою clickjacking працювати в кількох обмежених випадках. Наприклад, веб-сайт анонімного опитування, який запобігає дублюванню голосування шляхом перевірки IP-адреси, все одно буде вразливим до клікджекінгу, оскільки він не автентифікує користувачів за допомогою файлів cookie. +Однак це також може дозволяти атакам за допомогою clickjacking працювати в кількох обмежених випадках. Наприклад, веб-сайт анонімного опитування, який запобігає дублюванню голосування шляхом перевірки IP-адреси, все одно буде вразливим до clickjacking атаки, оскільки він не автентифікує користувачів за допомогою файлів cookie. -## Резюме +## Підсумки Clickjacking -- це спосіб "обдурити" користувачів, щоб вони натиснули на сайт-жертву, навіть не знаючи, що відбувається. Це небезпечно, якщо є важливі дії, які активуються кліком. From 494b88df7fc36233f5c931454b532594613bfa10 Mon Sep 17 00:00:00 2001 From: Yevhenii Stuzhuk Date: Thu, 21 Jul 2022 05:40:36 +0300 Subject: [PATCH 51/70] Update 5-network/12-server-sent-events/article.md Co-authored-by: Stanislav --- 5-network/12-server-sent-events/article.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/5-network/12-server-sent-events/article.md b/5-network/12-server-sent-events/article.md index a34094c2c..56f367910 100644 --- a/5-network/12-server-sent-events/article.md +++ b/5-network/12-server-sent-events/article.md @@ -8,7 +8,7 @@ | `WebSocket` | `EventSource` | |-------------|---------------| -| Двонаправлений: клієнт і сервер можуть обмінюватися повідомленнями | Односпрямований: дані надсилає лише сервер | +| Двонаправлений: клієнт і сервер можуть обмінюватися повідомленнями | Однонаправлений: дані надсилає лише сервер | | Двійкові та текстові дані | Тільки текст | | WebSocket протокол | Звичайний HTTP | From f3db6f51d5c1529dba7af23caf8b2c6bd84a4333 Mon Sep 17 00:00:00 2001 From: Yevhenii Stuzhuk Date: Thu, 21 Jul 2022 05:40:43 +0300 Subject: [PATCH 52/70] Update 5-network/12-server-sent-events/article.md Co-authored-by: Stanislav --- 5-network/12-server-sent-events/article.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/5-network/12-server-sent-events/article.md b/5-network/12-server-sent-events/article.md index 56f367910..abe2224fb 100644 --- a/5-network/12-server-sent-events/article.md +++ b/5-network/12-server-sent-events/article.md @@ -18,7 +18,7 @@ Основна причина: він простіший. У багатьох програмах потужність `WebSocket` є дещо занадто великою. -Нам потрібно отримати потік даних із сервера: можливо, повідомлення в чаті чи ринкові ціни, чи що завгодно. Це те, у чому сильный `EventSource`. Також він підтримує автоматичне повторне з’єднання, що зазвичай потрібно реалізовувати вручну за допомогою `WebSocket`. Крім того, це звичайний старий HTTP, а не новий протокол. +Нам потрібно отримати потік даних із сервера: можливо, повідомлення в чаті чи ринкові ціни, чи що завгодно. Це те, у чому сильный `EventSource`. Також він підтримує автоматичне повторне з’єднання, що зазвичай потрібно реалізовувати вручну за допомогою `WebSocket`. Крім того, це звичайний добре відомий HTTP, а не новий протокол. ## Отримання повідомлень From afeb15b986c49b5a284b9875269581bf9958a749 Mon Sep 17 00:00:00 2001 From: Yevhenii Stuzhuk Date: Thu, 21 Jul 2022 05:41:11 +0300 Subject: [PATCH 53/70] Update 5-network/12-server-sent-events/article.md Co-authored-by: Stanislav --- 5-network/12-server-sent-events/article.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/5-network/12-server-sent-events/article.md b/5-network/12-server-sent-events/article.md index abe2224fb..9f5461f04 100644 --- a/5-network/12-server-sent-events/article.md +++ b/5-network/12-server-sent-events/article.md @@ -39,7 +39,7 @@ data: з двох рядків - Текст повідомлення йде після `data:`, пробіл після двокрапки необов’язковий. - Повідомлення розділені подвійними розривами рядків `\n\n`. -- Щоб надіслати розрив рядка `\n`, ми можемо негайно надіслати ще одне `data:` (3-е повідомлення вище). +- Щоб надіслати розрив рядка `\n`, ми можемо негайно надіслати ще одне `data:` (3-тє повідомлення вище). На практиці складні повідомлення зазвичай надсилаються в кодуванні JSON. Розриви рядків у них кодуються як `\n`, тому багаторядкові повідомлення `data:` не потрібні. From 10e610c266169ba21d082ec52f9bb41280ff21e3 Mon Sep 17 00:00:00 2001 From: Yevhenii Stuzhuk Date: Thu, 21 Jul 2022 05:41:24 +0300 Subject: [PATCH 54/70] Update 5-network/12-server-sent-events/article.md Co-authored-by: Stanislav --- 5-network/12-server-sent-events/article.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/5-network/12-server-sent-events/article.md b/5-network/12-server-sent-events/article.md index 9f5461f04..bb9b6078f 100644 --- a/5-network/12-server-sent-events/article.md +++ b/5-network/12-server-sent-events/article.md @@ -87,7 +87,7 @@ let source = new EventSource("https://another-site.com/events", { ## Повторне з’єднання -Після створення `new EventSource` приєднується до сервера, і якщо з’єднання розривається - автоматично приєднується знову. +Після створення `new EventSource` приєднується до сервера, і якщо з’єднання розривається -- автоматично приєднується знову. Це дуже зручно, оскільки не потрібно дбати про це. From 5d29d2da10788e1608aa93689419d9f2d054f270 Mon Sep 17 00:00:00 2001 From: Yevhenii Stuzhuk Date: Thu, 21 Jul 2022 05:41:45 +0300 Subject: [PATCH 55/70] Update 5-network/12-server-sent-events/article.md Co-authored-by: Stanislav --- 5-network/12-server-sent-events/article.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/5-network/12-server-sent-events/article.md b/5-network/12-server-sent-events/article.md index bb9b6078f..e34a756b5 100644 --- a/5-network/12-server-sent-events/article.md +++ b/5-network/12-server-sent-events/article.md @@ -89,7 +89,7 @@ let source = new EventSource("https://another-site.com/events", { Після створення `new EventSource` приєднується до сервера, і якщо з’єднання розривається -- автоматично приєднується знову. -Це дуже зручно, оскільки не потрібно дбати про це. +Це дуже зручно, оскільки не потрібно дбати про це додатково. Між повторними з’єднаннями є невелика затримка, типово кілька секунд. From 118c6cf52231dd7bd30b0e1f533e73d3ba5db216 Mon Sep 17 00:00:00 2001 From: Yevhenii Stuzhuk Date: Thu, 21 Jul 2022 05:42:34 +0300 Subject: [PATCH 56/70] Update 7-animation/2-css-animations/article.md Co-authored-by: Stanislav --- 7-animation/2-css-animations/article.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/7-animation/2-css-animations/article.md b/7-animation/2-css-animations/article.md index 4053ca956..03a6a7589 100644 --- a/7-animation/2-css-animations/article.md +++ b/7-animation/2-css-animations/article.md @@ -439,7 +439,7 @@ boat.onclick = function() { Властивість `opacity` ніколи не запускає Макет (в Mozilla Gecko також пропускається Фарбування). Ми можемо використовувати його для ефектів відображення/приховування або посилення/згасання. -Поєднання `transform` з `opacity` зазвичай може вирішувати більшість наших потреб, забезпечуючи плавну, гарну анімацію. +Поєднання `transform` з `opacity` зазвичай може вирішувати більшість наших потреб, забезпечуючи плавну, яскраву анімацію. Наприклад, тут клік на елемент `#boat` додає клас із `transform: translateX(300)` та `opacity: 0`, таким чином змушуючи його рухатись на `300px` праворуч і зникати: From c0b84c4e45ea27bd59803d43cf1e09784f1973ee Mon Sep 17 00:00:00 2001 From: Yevhenii Stuzhuk Date: Thu, 21 Jul 2022 05:43:08 +0300 Subject: [PATCH 57/70] Update 7-animation/2-css-animations/article.md Co-authored-by: Stanislav --- 7-animation/2-css-animations/article.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/7-animation/2-css-animations/article.md b/7-animation/2-css-animations/article.md index 03a6a7589..66da22ba1 100644 --- a/7-animation/2-css-animations/article.md +++ b/7-animation/2-css-animations/article.md @@ -492,7 +492,7 @@ boat.onclick = function() { Анімація CSS дозволяє плавно (або покроково) анімувати зміну однієї або кількох властивостей CSS. -Вона гарна для більшості завданнь анімації. Ми також можемо використовувати JavaScript для анімації, цьому присвячений наступний розділ. +Це підходить для більшості задач. Ми також можемо використовувати JavaScript для анімації, цьому присвячений наступний розділ. Обмеження анімації CSS порівняно з JavaScript: From ab8be27521c3c84e55217da0c0a5f63eb0325766 Mon Sep 17 00:00:00 2001 From: Yevhenii Stuzhuk Date: Thu, 21 Jul 2022 05:43:24 +0300 Subject: [PATCH 58/70] Update 7-animation/2-css-animations/article.md Co-authored-by: Stanislav --- 7-animation/2-css-animations/article.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/7-animation/2-css-animations/article.md b/7-animation/2-css-animations/article.md index 66da22ba1..4d74b9f0a 100644 --- a/7-animation/2-css-animations/article.md +++ b/7-animation/2-css-animations/article.md @@ -490,7 +490,7 @@ boat.onclick = function() { ## Підсумки -Анімація CSS дозволяє плавно (або покроково) анімувати зміну однієї або кількох властивостей CSS. +CSS-анімація дозволяє плавно (або покроково) анімувати зміну однієї або кількох властивостей CSS. Це підходить для більшості задач. Ми також можемо використовувати JavaScript для анімації, цьому присвячений наступний розділ. From 5ae6e120b7fc2368b1ef183ce611616cb4606d1e Mon Sep 17 00:00:00 2001 From: Yevhenii Stuzhuk Date: Thu, 21 Jul 2022 05:43:30 +0300 Subject: [PATCH 59/70] Update 7-animation/2-css-animations/article.md Co-authored-by: Stanislav --- 7-animation/2-css-animations/article.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/7-animation/2-css-animations/article.md b/7-animation/2-css-animations/article.md index 4d74b9f0a..d8dc507e3 100644 --- a/7-animation/2-css-animations/article.md +++ b/7-animation/2-css-animations/article.md @@ -498,7 +498,7 @@ CSS-анімація дозволяє плавно (або покроково) ```compare plus="CSS animations" minus="JavaScript animations" + Прості речі робляться просто. -+ Швидка та легкиа для процесора. ++ Швидка та легка для процесора. - Анімації JavaScript є гнучкими. Вони можуть реалізувати будь-яку логіку анімації, навіть «вибух» елемента. - Не тільки властивості змінюються. В JavaScript ми можемо створювати нові елементи як частину анімації. ``` From d2973b34d10386abea00f8a7700742ad2fe69434 Mon Sep 17 00:00:00 2001 From: Yevhenii Stuzhuk Date: Thu, 21 Jul 2022 05:43:36 +0300 Subject: [PATCH 60/70] Update 7-animation/2-css-animations/article.md Co-authored-by: Stanislav --- 7-animation/2-css-animations/article.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/7-animation/2-css-animations/article.md b/7-animation/2-css-animations/article.md index d8dc507e3..2188ef5a2 100644 --- a/7-animation/2-css-animations/article.md +++ b/7-animation/2-css-animations/article.md @@ -499,7 +499,7 @@ CSS-анімація дозволяє плавно (або покроково) ```compare plus="CSS animations" minus="JavaScript animations" + Прості речі робляться просто. + Швидка та легка для процесора. -- Анімації JavaScript є гнучкими. Вони можуть реалізувати будь-яку логіку анімації, навіть «вибух» елемента. +- Анімації JavaScript є гнучкими. Вони можуть реалізувати будь-яку логіку анімації, навіть "вибух" елемента. - Не тільки властивості змінюються. В JavaScript ми можемо створювати нові елементи як частину анімації. ``` From 1da1d541f903dacc042f172d0d1b7bf48408c0c3 Mon Sep 17 00:00:00 2001 From: Yevhenii Stuzhuk Date: Thu, 28 Jul 2022 15:27:51 +0300 Subject: [PATCH 61/70] Update 5-network/12-server-sent-events/article.md Co-authored-by: Stanislav --- 5-network/12-server-sent-events/article.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/5-network/12-server-sent-events/article.md b/5-network/12-server-sent-events/article.md index e34a756b5..c71f8295c 100644 --- a/5-network/12-server-sent-events/article.md +++ b/5-network/12-server-sent-events/article.md @@ -116,7 +116,7 @@ eventSource.close(); Крім того, не буде повторного з’єднання, якщо відповідь містить неправильний `Content-Type` або його статус HTTP відрізняється від 301, 307, 200 і 204. У таких випадках буде створено подію `"error"`, і браузер не підключатиметься повторно. ```smart -Коли з’єднання остаточно закрито, його неможливо «відкрити» знову. Якщо ми хочемо знову під’єднатися, доведеться створити новий `EventSource`. +Коли з’єднання остаточно закрито, його неможливо відкрити знову. Якщо ми хочемо знову під’єднатися, доведеться створити новий `EventSource`. ``` ## Ідентифікатор повідомлення From 2078a030b2d5f3501370ed627695dedc12481cec Mon Sep 17 00:00:00 2001 From: Yevhenii Stuzhuk Date: Thu, 28 Jul 2022 15:29:42 +0300 Subject: [PATCH 62/70] Update 5-network/12-server-sent-events/article.md Co-authored-by: Stanislav --- 5-network/12-server-sent-events/article.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/5-network/12-server-sent-events/article.md b/5-network/12-server-sent-events/article.md index c71f8295c..345c0d2ef 100644 --- a/5-network/12-server-sent-events/article.md +++ b/5-network/12-server-sent-events/article.md @@ -137,7 +137,7 @@ data: з двох рядків id: 3 ``` -Коли повідомлення з `id:` отримане браузером: +Коли браузер отримає повідомлення з `id:`: - Встановлюється значення властивості `eventSource.lastEventId`. - Після повторного з’єднання надсилається заголовок `Last-Event-ID` з цим `id`, щоб сервер міг повторно надіслати наступні повідомлення. From 9a56401fec035cf2e8f893d1c6f2bb5c9c414364 Mon Sep 17 00:00:00 2001 From: Yevhenii Stuzhuk Date: Thu, 28 Jul 2022 15:31:52 +0300 Subject: [PATCH 63/70] Update 5-network/12-server-sent-events/article.md Co-authored-by: Stanislav --- 5-network/12-server-sent-events/article.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/5-network/12-server-sent-events/article.md b/5-network/12-server-sent-events/article.md index 345c0d2ef..c93faae8c 100644 --- a/5-network/12-server-sent-events/article.md +++ b/5-network/12-server-sent-events/article.md @@ -206,7 +206,7 @@ eventSource.addEventListener('leave', event => { [codetabs src="eventsource"] -## Резюме +## Підсумки Об’єкт `EventSource` автоматично встановлює постійне з’єднання і дозволяє серверу надсилати повідомлення через нього. From 400fbf6639b7c973abe3d80fb799b60815a2a131 Mon Sep 17 00:00:00 2001 From: Yevhenii Stuzhuk Date: Thu, 28 Jul 2022 15:33:19 +0300 Subject: [PATCH 64/70] Update 5-network/12-server-sent-events/article.md Co-authored-by: Stanislav --- 5-network/12-server-sent-events/article.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/5-network/12-server-sent-events/article.md b/5-network/12-server-sent-events/article.md index c93faae8c..4d4a91548 100644 --- a/5-network/12-server-sent-events/article.md +++ b/5-network/12-server-sent-events/article.md @@ -265,7 +265,7 @@ let source = new EventSource(url, [credentials]); - `data:` -- у тілі повідомлення, послідовність кількох `data` інтерпретується як одне повідомлення з `\n` між його частинами. - `id:` -- поновлює `lastEventId`, надісланий у `Last-Event-ID` під час повторного з’єднання. -- `retry:` -- радить затримку повторного з’єднання у мс. Немає способу встановити його за допомогою JavaScript +- `retry:` -- радить затримку повторного з’єднання у мс. Немає способу встановити його за допомогою JavaScript. - `event:` -- ім’я події має передувати `data:`. Повідомлення може містити один або кілька рядків у будь-якому порядку, але `id:` зазвичай йде останнім.. From 51a327f1ad6322885102c070b723f5be3cbef113 Mon Sep 17 00:00:00 2001 From: Yevhenii Stuzhuk Date: Thu, 28 Jul 2022 15:33:38 +0300 Subject: [PATCH 65/70] Update 6-data-storage/03-indexeddb/article.md Co-authored-by: Stanislav --- 6-data-storage/03-indexeddb/article.md | 1 - 1 file changed, 1 deletion(-) diff --git a/6-data-storage/03-indexeddb/article.md b/6-data-storage/03-indexeddb/article.md index 77bfa83e6..7833d0b3e 100644 --- a/6-data-storage/03-indexeddb/article.md +++ b/6-data-storage/03-indexeddb/article.md @@ -35,7 +35,6 @@ let openRequest = indexedDB.open(name, version); ``` - `name` -- рядок, ім’я бази даних. - У нас може бути багато баз даних з різними іменами, але всі вони існують у поточному джерелі (домен/протокол/порт). Різні веб-сайти не мають доступу до баз даних один одного. - `version` -- версія є цілим числом, типово `1` (пояснення нижче). From 80213a9a1d7880b340c629c3a8d3eba1cb5edd49 Mon Sep 17 00:00:00 2001 From: Yevhenii Stuzhuk Date: Thu, 28 Jul 2022 15:34:44 +0300 Subject: [PATCH 66/70] Update 6-data-storage/03-indexeddb/article.md Co-authored-by: Stanislav --- 6-data-storage/03-indexeddb/article.md | 1 - 1 file changed, 1 deletion(-) diff --git a/6-data-storage/03-indexeddb/article.md b/6-data-storage/03-indexeddb/article.md index 7833d0b3e..3d3710853 100644 --- a/6-data-storage/03-indexeddb/article.md +++ b/6-data-storage/03-indexeddb/article.md @@ -35,7 +35,6 @@ let openRequest = indexedDB.open(name, version); ``` - `name` -- рядок, ім’я бази даних. -У нас може бути багато баз даних з різними іменами, але всі вони існують у поточному джерелі (домен/протокол/порт). Різні веб-сайти не мають доступу до баз даних один одного. - `version` -- версія є цілим числом, типово `1` (пояснення нижче). From 51fc021d1013b497ffbbb424c8eded80c45c9f23 Mon Sep 17 00:00:00 2001 From: Yevhenii Stuzhuk Date: Thu, 28 Jul 2022 15:35:28 +0300 Subject: [PATCH 67/70] Update 6-data-storage/03-indexeddb/article.md Co-authored-by: Stanislav --- 6-data-storage/03-indexeddb/article.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/6-data-storage/03-indexeddb/article.md b/6-data-storage/03-indexeddb/article.md index 3d3710853..f1ab67bed 100644 --- a/6-data-storage/03-indexeddb/article.md +++ b/6-data-storage/03-indexeddb/article.md @@ -274,7 +274,7 @@ db.transaction(store[, type]); - `store` це назва сховища, до якого має отримати доступ транзакція, напр. `"books"`. Це може бути масив імен сховищ, якщо ми збираємося отримати доступ до кількох сховищ. - `type` – тип транзакції, один з: -- `readonly` -- може лише читати дані, типово. + - `readonly` -- може лише читати дані, типово. - `readwrite` -- може лише читати та записувати дані, але не створювати/видаляти/змінювати сховища об’єктів. Існує також тип транзакції `versionchange`: такі транзакції можуть робити все, але ми не можемо створити їх вручну. IndexedDB автоматично створює транзакцію `versionchange` під час відкриття бази даних для обробника `updateneeded`. That's why it's a single place where we can update the database structure, create/remove object stores. From 6529a81d7954ada3767c2d0dca926df0504578ee Mon Sep 17 00:00:00 2001 From: Yevhenii Stuzhuk Date: Thu, 28 Jul 2022 15:35:58 +0300 Subject: [PATCH 68/70] Update 6-data-storage/03-indexeddb/article.md Co-authored-by: Stanislav --- 6-data-storage/03-indexeddb/article.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/6-data-storage/03-indexeddb/article.md b/6-data-storage/03-indexeddb/article.md index f1ab67bed..81e6d6372 100644 --- a/6-data-storage/03-indexeddb/article.md +++ b/6-data-storage/03-indexeddb/article.md @@ -275,7 +275,7 @@ db.transaction(store[, type]); - `store` це назва сховища, до якого має отримати доступ транзакція, напр. `"books"`. Це може бути масив імен сховищ, якщо ми збираємося отримати доступ до кількох сховищ. - `type` – тип транзакції, один з: - `readonly` -- може лише читати дані, типово. -- `readwrite` -- може лише читати та записувати дані, але не створювати/видаляти/змінювати сховища об’єктів. + - `readwrite` -- може лише читати та записувати дані, але не створювати/видаляти/змінювати сховища об’єктів. Існує також тип транзакції `versionchange`: такі транзакції можуть робити все, але ми не можемо створити їх вручну. IndexedDB автоматично створює транзакцію `versionchange` під час відкриття бази даних для обробника `updateneeded`. That's why it's a single place where we can update the database structure, create/remove object stores. From fd843a192dbd1424a21f1e90726cd2f538d32bd7 Mon Sep 17 00:00:00 2001 From: Yevhenii Stuzhuk Date: Thu, 28 Jul 2022 15:37:35 +0300 Subject: [PATCH 69/70] Update 7-animation/2-css-animations/article.md Co-authored-by: Stanislav --- 7-animation/2-css-animations/article.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/7-animation/2-css-animations/article.md b/7-animation/2-css-animations/article.md index 2188ef5a2..3a65e562b 100644 --- a/7-animation/2-css-animations/article.md +++ b/7-animation/2-css-animations/article.md @@ -236,7 +236,7 @@ CSS: Властивість `left` має бути анімованою від `100px` до `400px`. -Але якщо ви клацнете потяг, ви побачите, що: +Але якщо ви клацнете по потягу, ви побачите, що: - Спочатку потяг повертається *назад*: `left` стає меншим ніж `100px`. - Потім він рухається вперед, трохи далі, ніж `400px`. From 4e193b5ff64209673a7bba00dc490f78f60e8df2 Mon Sep 17 00:00:00 2001 From: Stanislav Date: Thu, 4 Aug 2022 11:04:30 +0300 Subject: [PATCH 70/70] Apply suggestions from code review --- 6-data-storage/03-indexeddb/article.md | 5 +- .../4-animate-circle-callback/task.md | 2 +- 7-animation/2-css-animations/article.md | 108 +++++++++--------- 3 files changed, 57 insertions(+), 58 deletions(-) diff --git a/6-data-storage/03-indexeddb/article.md b/6-data-storage/03-indexeddb/article.md index 81e6d6372..33dcf7617 100644 --- a/6-data-storage/03-indexeddb/article.md +++ b/6-data-storage/03-indexeddb/article.md @@ -548,8 +548,8 @@ objectStore.createIndex(name, keyPath, [options]); - **`name`** -- ім’я індексу, - **`keyPath`** -- шлях до поля об’єкта, яке має відстежувати індекс (ми будемо шукати за цим полем), - **`option`** -- необов’язковий об’єкт з властивостями: -- **`unique`** -- якщо значення true, то в сховищі може бути лише один об’єкт із заданим значенням у `keyPath`. Індекс забезпечить це, генеруючи помилку, якщо ми спробуємо додати дублікат. -- **`multiEntry`** -- використовується лише якщо значення `keyPath` є масивом. У цьому випадку індекс розглядатиме весь масив як ключ. Але якщо `multiEntry` має значення true, то індекс зберігатиме список об’єктів сховища для кожного значення в цьому масиві. Таким чином, члени масиву стають ключами індексу. + - **`unique`** -- якщо значення true, то в сховищі може бути лише один об’єкт із заданим значенням у `keyPath`. Індекс забезпечить це, генеруючи помилку, якщо ми спробуємо додати дублікат. + - **`multiEntry`** -- використовується лише якщо значення `keyPath` є масивом. У цьому випадку індекс розглядатиме весь масив як ключ. Але якщо `multiEntry` має значення true, то індекс зберігатиме список об’єктів сховища для кожного значення в цьому масиві. Таким чином, члени масиву стають ключами індексу. У нашому прикладі ми зберігаємо книги з ключем `id`. @@ -661,7 +661,6 @@ let request = store.openCursor(query, [direction]); - **`query`** є ключем або діапазоном ключів, як і для `getAll`. - **`direction`** є необов’язковим аргументом, що вказує який порядок використовувати: - - `"next"` -- типово, курсор піднімається від найнижчого ключа до найвищчого. - `"prev"` -- зворотний порядок: вниз від запису з найбільшим ключем. - `"nextunique"`, `"prevunique"` -- те саме, що й вище, але пропускає записи з тим же ключем, який вже був (лише для курсорів за індексами, наприклад, для кількох книг із ціною=5 буде повернута лише перша). diff --git a/7-animation/2-css-animations/4-animate-circle-callback/task.md b/7-animation/2-css-animations/4-animate-circle-callback/task.md index e4ec4a546..cacf19d36 100644 --- a/7-animation/2-css-animations/4-animate-circle-callback/task.md +++ b/7-animation/2-css-animations/4-animate-circle-callback/task.md @@ -3,7 +3,7 @@ У завданні показана анімація зростання кола. -Припустимо, що нам потрібно показати повідомлення всередині нього. Повідомлення має з’явитися *після* завершення анімації (коли коло повністю виросло), інакше воно виглядатиме негарно. +Припустимо, що нам потрібно показати повідомлення всередині нього. Повідомлення має з’явитися *після* завершення анімації (коли коло повністю виросло), інакше це виглядатиме погано. У вирішенні завдання функція `showCircle(cx, cy, radius)` малює коло, але не дає можливості відстежити, коли воно готове. diff --git a/7-animation/2-css-animations/article.md b/7-animation/2-css-animations/article.md index 3a65e562b..c10e7c826 100644 --- a/7-animation/2-css-animations/article.md +++ b/7-animation/2-css-animations/article.md @@ -1,14 +1,14 @@ # CSS-анімації -CSS-анімація дає змогу робити прості анімації взагалі без JavaScript. +CSS дозволяє робити прості анімації взагалі без JavaScript. -JavaScript можна використовувати для керування анімацією CSS та покращення її використовуючи трохи коду. +JavaScript можна використовувати для керування CSS-анімацією та покращення її, додавши зовсім трохи коду. ## CSS-переходи [#css-transition] -Ідея переходів CSS проста. Ми описуємо властивість і як її зміни мають бути анімовані. Коли властивість змінюється, браузер малює анімацію. +Ідея CSS-переходів дуже проста. Ми описуємо властивість і як саме її зміни мають бути анімовані. Коли властивість змінюється, браузер анімую цей перехід, тобто зміну значення. -Тобто все, що нам потрібно, це змінити властивість, і плавний перехід буде здійснюватися браузером. +Тобто все, що нам потрібно, це змінити властивість, а плавний перехід буде здійснюватися браузером. Наприклад, наведений нижче CSS анімує зміни `background-color` протягом 3 секунд: @@ -49,7 +49,7 @@ JavaScript можна використовувати для керування Зараз ми їх розглянемо, а поки зазначимо, що загальна властивість `transition` дозволяє оголошувати їх разом у порядку: `property duration timing-function delay`, а також анімувати декілька властивостей одночасно. -Наприклад, ця кнопка анімує і `color`, і `font-size` одночасно: +Наприклад, ця кнопка анімує `color` і `font-size` одночасно: ```html run height=80 autorun no-beautify @@ -80,7 +80,7 @@ growing.onclick = function() { ## transition-duration -У `transition-duration` вказуємл, скільки часу має займати анімація. Час має бути в [форматі часу CSS](http://www.w3.org/TR/css3-values/#time): в секундах `s` або мілісекундах `ms`. +У `transition-duration` задає, скільки часу має тривати анімація. Час має бути в [форматі часу CSS](http://www.w3.org/TR/css3-values/#time): в секундах `s` або мілісекундах `ms`. ## transition-delay @@ -110,7 +110,7 @@ stripe.classList.add('animate'); Ми також могли б розпочати її десь із середини, точного вказавши число, напр. відповідно до поточної секунди, використовуючи негативне значення `transition-delay`. -Якщо ви зараз клацнете цифру - анімація почеться з поточної секунди: +Якщо ви зараз клацнете по цифрі -- анімація почнеться з поточної секунди: [codetabs src="digits-negative-delay"] @@ -120,7 +120,7 @@ JavaScript робить це за допомогою додаткового ря stripe.onclick = function() { let sec = new Date().getSeconds() % 10; *!* - // наприклад, -3s запускає анімацію з 3-ї секунди + // наприклад, -3s запускає анімацію з 3-ої секунди stripe.style.transitionDelay = '-' + sec + 's'; */!* stripe.classList.add('animate'); @@ -129,28 +129,28 @@ stripe.onclick = function() { ## transition-timing-function -Функція хронометражу описує, як процес анімації розподіляється по часовій шкалі. Почнеться повільно, а потім швидко, чи навпаки. +Функція часу описує, як процес анімації має розподілятися у часі. Наприклад, починається повільно, а потім іде швидко, чи навпаки. Спочатку це здається найскладнішою властивістю. Але все стає дуже просто, якщо ми приділимо цьому трохи часу. -Ця властивість приймає два види значень: крива Без'є або кроки. Почнемо з кривої, оскільки вона використовується частіше. +Ця властивість приймає два типи значень: крива Безʼє або кроки. Почнемо з кривої, оскільки вона використовується частіше. -### Крива Без'є +### Крива Безʼє -Функцію хронометражу можна встановити як [криву Без'є](/bezier-curve) з 4 контрольними точками, які задовольняють умовам: +Функцію часу можна встановити як [криву Безʼє](/bezier-curve) з 4 контрольними точками, які задовольняють умовам: 1. Перша контрольна точка: `(0,0)`. 2. Остання контрольна точка: `(1,1)`. 3. Для проміжних точок значення `x` мають бути в інтервалі `0..1`, `y` може бути будь-яким. -Синтаксис кривої Без'є в CSS: `cubic-bezier(x2, y2, x3, y3)`. Тут нам потрібно вказати лише 2-ю і 3-ю контрольні точки, тому що 1-а фіксована на `(0,0)`, а 4-а — `(1,1)`. +Синтаксис кривої Без’є в CSS: `cubic-bezier(x2, y2, x3, y3)`. Тут нам потрібно вказати лише 2-гу і 3-тю контрольні точки, тому що 1-ша фіксована на `(0,0)`, а 4-та -- `(1,1)`. -Функція часу описує, наскільки швидко проходить процес анімації. +Функція часу описує, швидкість процесу анімації. -- Вісь `x` - це час: `0` - початок, `1` - кінець `transition-duration`. -- Вісь `y` визначає завершення процесу: `0` -- початкове значення властивості, `1` -- кінцеве. +- Вісь `x` -- це час, де `0` -- початок, `1` -- кінець `transition-duration`. +- Вісь `y` визначає завершення процесу, де `0` -- початкове значення властивості, `1` -- кінцеве. -Найпростіший варіант – коли анімація йде рівномірно, з однаковою лінійною швидкістю. За допомогою кривої це можна визначити `cubic-bezier(0, 0, 1, 1)`. +Найпростіший варіант -- коли анімація йде рівномірно, з однаковою лінійною швидкістю. За допомогою кривої це можна визначити `cubic-bezier(0, 0, 1, 1)`. Ось як виглядає ця крива: @@ -158,7 +158,7 @@ stripe.onclick = function() { ...Як бачимо, це просто пряма лінія. З плином часу (`x`) завершення (`y`) анімації постійно змінюється від `0` до `1`. -Потяг у прикладі нижче рухається зліва направо з постійною швидкістю (натисніть): +Потяг у прикладі нижче рухається зліва направо з постійною швидкістю (клацніть по ньому): [codetabs src="train-linear"] @@ -168,7 +168,7 @@ stripe.onclick = function() { .train { left: 0; transition: left 5s cubic-bezier(0, 0, 1, 1); - /* натискаючи на поїзд властивість left стає 450px, анімація запускається */ + /* натискаючи на потяг властивість left стає 450px, анімація запускається */ } ``` @@ -180,7 +180,7 @@ stripe.onclick = function() { ![](train-curve.svg) -Як бачимо, процес починається швидко: крива злітає вгору, а потім все повільніше. +Як бачимо, процес починається швидко: крива злітає вгору, а потім все повільніше і повільніше. Ось функція часу в дії (натисніть на потяг): @@ -191,13 +191,13 @@ CSS: .train { left: 0; transition: left 5s cubic-bezier(0, .5, .5, 1); - /* натискаючи на поїзд властивість left стає 450px, анімація запускається */ + /* натискаючи на потяг властивість left стає 450px, анімація запускається */ } ``` Існує кілька вбудованих кривих: `linear`, `ease`, `ease-in`, `ease-out` та `ease-in-out`. -`linear` це скорочення від `cubic-bezier(0, 0, 1, 1)` -- пряма лінія, яку ми вже описували. +`linear` скорочення від `cubic-bezier(0, 0, 1, 1)` -- пряма лінія, яку ми вже описували. Інші назви є скороченнями наступних `cubic-bezier`: @@ -221,7 +221,7 @@ CSS: Але виглядає це трохи інакше. -**Крива Без'є може змусити анімацію вийти за межі її діапазону.** +**Крива Безʼє може змусити анімацію вийти за межі її діапазону.** Контрольні точки на кривій можуть мати будь-які координати `y`: навіть негативні або величезні. Тоді крива Без'є також буде дуже низькою або високою, що змушує анімацію виходити за межі нормального діапазону. @@ -248,7 +248,7 @@ CSS: ![](bezier-train-over.svg) -Ми перемістили координату `y` 2-ї точки нижче нуля, а для 3-ї точки ми зробили її на `1`, тому крива виходить із "звичайного" квадранта. Координата `y` виходить за "стандартний" діапазон `0..1`. +Ми перемістили координату `y` 2-ої точки нижче нуля, а для 3-ої точки ми зробили її більше за `1`, тому крива виходить із "звичайного" квадранта. Координата `y` виходить за "стандартний" діапазон `0..1`. Як відомо, `y` вимірює "завершення процесу анімації". Значення `y = 0` відповідає початковому значенню властивості, а `y = 1` -- кінцевому. Таким чином, значення `y<0` переміщує властивість за межі початкового `left` і `y>1` -- за останній `left`. @@ -262,11 +262,11 @@ CSS: Побачимо це на прикладі з цифрами. -Ось список цифр, без жодної анімації, просто як джерело: +Ось список цифр, без жодної анімації, просто як основа: [codetabs src="step-list"] -Ми зробимо так, щоб цифри відображалися дискретно, роблячи частину списку за межами червоного «вікна» незримою та зміщуючи список ліворуч з кожним кроком. +Ми зробимо так, щоб цифри відображалися дискретно, роблячи частину списку за межами червоного "вікна" незримою та зміщуючи список ліворуч з кожним кроком. Буде 9 кроків, крок-хід для кожної цифри: @@ -281,17 +281,17 @@ CSS: [codetabs src="step"] -Першим аргументом `steps(9, start)` є кількість кроків. Перетворення буде розділено на 9 частин (по 10% кожна). Часовий інтервал також автоматично ділиться на 9 частин, тому `transition: 9s` дає нам 9 секунд на всю анімацію – 1 секунда на цифру. +Першим аргументом `steps(9, start)` є кількість кроків. Перетворення буде розділено на 9 частин (по 10% кожна). Часовий інтервал також автоматично ділиться на 9 частин, тому `transition: 9s` дає нам 9 секунд на всю анімацію -- 1 секунда на цифру. -Другий аргумент - це одне з двох слів: `start` чи `end`. +Другий аргумент -- це одне з двох слів: `start` чи `end`. Значення `start` означає, що на початку анімації нам потрібно негайно зробити перший крок. -Ми можемо бачити, що під час анімації: коли ми клацаємо по цифрі, вона відразу змінюється на `1` (перший крок), а потім змінюється на початку наступної секунди. +Можна помітити, що під час анімації: коли ми клацаємо по цифрі, вона відразу змінюється на `1` (перший крок), а потім змінюється на початку наступної секунди. -Процес протікає так: +Процес іде так: -- `0s` -- `-10%` (перша зміна на початку 1-ї секунди, відразу) +- `0s` -- `-10%` (перша зміна на початку 1-ої секунди, відразу) - `1s` -- `-20%` - ... - `8s` -- `-90%` @@ -302,7 +302,7 @@ CSS: Отже, процес для `steps(9, end)` буде виглядати так: - `0s` -- `0` (протягом першої секунди нічого не змінюється) -- `1s` -- `-10%` (перша зміна в кінці 1-ї секунди) +- `1s` -- `-10%` (перша зміна в кінці 1-ої секунди) - `2s` -- `-20%` - ... - `9s` -- `-90%` @@ -313,8 +313,8 @@ CSS: Існують також скорочені значення: -- `step-start` -- те саме, що `steps(1, start)`. Тобто анімація починається відразу і займає 1 крок. Вона починається й закінчується одразу, ніби не було анімації. -- `step-end` -- те саме, що `steps(1, end)`: створіть анімацію за один крок у кінці `transition-duration`. +- `step-start` -- те саме, що й `steps(1, start)`. Тобто анімація починається відразу і займає 1 крок. Вона починається й закінчується одразу, ніби не було анімації. +- `step-end` -- те саме, що й `steps(1, end)`: зробіть анімацію за один крок у кінці `transition-duration`. Ці значення рідко використовуються, тому що це насправді не анімація, а однокрокова зміна. @@ -322,13 +322,13 @@ CSS: Коли закінчується CSS анімація, запускається подія `transitionend`. -Вона широко використовується для виконання дії після завершення анімації. Також ми можемо приєднатися до анімації з її допомогою. +Вона широко використовується для виконання дії після завершення анімації. Також ми можемо обʼєднувати анімації з її допомогою. Наприклад, якщо натиснути на корабель у наведеному нижче прикладі, він починає плисти туди й назад, щоразу все далі й далі праворуч: [iframe src="boat" height=300 edit link] -Анімація ініціюється функцією `go`, яка повторно запускається кожного разу, коли закінчується перехід, і повертає напрямок: +Анімація ініціюється функцією `go`, яка повторно запускається кожного разу, коли закінчується перехід, і змінює напрямок на протилежний: ```js boat.onclick = function() { @@ -357,15 +357,15 @@ boat.onclick = function() { }; ``` -Об’єкт події для `transitionend` має кілька конкретних властивостей: +Об’єкт події для `transitionend` має кілька специфічних властивостей: `event.propertyName` : Властивість, яка завершила анімацію. Може бути корисна коли ми анімуємо кілька властивостей одночасно. `event.elapsedTime` -: Час (у секундах), який займала анімація, без `transition-delay`. +: Час (у секундах), який тривала анімація, без `transition-delay`. -## Ключові кадри +## Keyframes Ми можемо об’єднати кілька простих анімацій разом за допомогою правила CSS `@keyframes`. @@ -387,7 +387,7 @@ boat.onclick = function() { .progress { *!* animation: go-left-right 3s infinite alternate; - /* застосувати анімацію "go-left-right" до еменета + /* застосувати анімацію "go-left-right" до елемента тривалість 3 секунди кількість разів: нескінченно щоразу змінювати напрямок @@ -403,41 +403,41 @@ boat.onclick = function() { ``` -Є багато статей про `@keyframes` та [датальна специфікація](https://drafts.csswg.org/css-animations/). +Є багато статей про `@keyframes` та [детальна специфікація](https://drafts.csswg.org/css-animations/). -Ймовірно, `@keyframes` не знадобляться вам часто, хіба якщо на ваших сайтах все постійно рухається. +Ймовірно, `@keyframes` не знадобиться вам часто, хіба якщо на ваших сайтах все постійно рухається. ## Продуктивність -Більшість властивостей CSS можна анімувати, оскільки більшість із них є числовими значеннями. Наприклад, `width`, `color`, `font-size` є числами. Коли ви анімуєте їх, браузер поступово змінює ці числа кадр за кадром, створюючи плавний ефект. +Більшість властивостей CSS можна анімувати, оскільки більшість із них є числовими значеннями. Наприклад, `width`, `color`, `font-size` всі є числами. Коли ви анімуєте їх, браузер поступово змінює ці числа кадр за кадром, створюючи плавний ефект. Однак не всі анімації виглядатимуть так гладко, як хотілося б, оскільки різні властивості змінюються по-різному. Якщо говорити більше про технічні деталі, коли відбувається зміна стилю, браузер виконує 3 кроки для відтворення нового вигляду: -1. **Макет**: перерахувати геометрію, а потім положення кожного елемента -2. **Фарбування**: перерахувати, як все має виглядати на своїх місцях, включаючи фон, кольори, -3. **Композиція**: відтворити кінцеві результати в пікселях на екрані, застосувати CSS-перетворення, якщо вони існують. +1. **Layout**: перерахувати геометрію, а потім положення кожного елемента +2. **Paint**: перерахувати, як все має виглядати на своїх місцях, включаючи фон, кольори, +3. **Composite**: відтворити кінцеві результати в пікселях на екрані, застосувати CSS-перетворення, якщо вони існують. -Під час CSS-анімації цей процес повторюється для кожного кадра. Однак властивості CSS, які ніколи не впливають на геометрію або положення, наприклад, `color`, можуть пропускатись кроці макета. Якщо `color` змінюється, браузер не обчислює нову геометрію, він переходить до кроків Фарбування -> Композиція. І є кілька властивостей, які безпосередньо переходять до Композиції. Ви можете знайти довший список властивостей CSS та на яких етапах вони запускаються тут . +Під час CSS-анімації цей процес повторюється для кожного кадра. Однак властивості CSS, які ніколи не впливають на геометрію або положення, наприклад, `color`, можуть пропускатись крок Layout. Якщо `color` змінюється, браузер не обчислює нову геометрію, він переходить до кроків Paint -> Composite. І є кілька властивостей, які безпосередньо переходять до Composite. Ви можете знайти довший список властивостей CSS та на яких етапах вони запускаються тут . -Обчислення може зайняти багато часу, особливо на сторінках із великою кількістю елементів і складним макетом. І затримки фактично помітні на більшості пристроїв, що призводить до «тривожності», менш плавної анімації. +Обчислення може зайняти багато часу, особливо на сторінках із великою кількістю елементів і складною структурою. І затримки фактично помітні на більшості пристроїв, що призводить до "тремтіння", менш плавної анімації. -Анімація властивостей, які пропускають крок макета, швидше. Навіть ліпше, ніж у випадку з пропусканням кроку фарбування. +Анімація властивостей, які пропускають Layout крок, працюють швидше. Навіть ліпше, ніж коли пропускається крок Paint. Властивість `transform` є чудовим вибором, оскільки: - Перетворення CSS впливають на блок цільового елемента в цілому (повертають, перевертають, розтягують, зміщують його). - CSS-перетворення ніколи не впливають на сусідні елементи. -...Тому браузери застосовують `transform` "попереду" наявних обчислень Макету та Фарбування на етапі Композиції. +...Тому браузери застосовують `transform` після наявних обчислень Layout та Paint на кроці Composite. -Іншими словами, браузер обчислює макет (розміри, положення), розфарбовує його за допомогою colors, background тощо на етапі Фарбування, а потім застосовує `transform` до блоків елементів, які цього потребують. +Іншими словами, браузер обчислює макет (розміри, положення), розфарбовує його за допомогою colors, background тощо на етапі Paint, а потім застосовує `transform` до елементів, які цього потребують. -Зміни (анімації) властивості `transform` ніколи не запускають кроки Макет та Фарбування. Більше того, браузер використовує графічний прискорювач (спеціальний чіп на ЦП або відеокарті) для CSS-перетворень, що робить їх дуже ефективними. +Зміни (анімації) властивості `transform` ніколи не запускають кроки Layout та Paint. Більше того, браузер використовує графічний прискорювач (спеціальний чіп на ЦП або відеокарті) для CSS-перетворень, що робить їх дуже ефективними. -На щастя, властивість `transform` дуже потужна. Використовуючи `transform` для елемента, ви можете обертати та повертати його, розтягувати та зменшувати, переміщувати та [багато іншого](https://developer.mozilla.org/docs/Web/CSS/transform#syntax ). Тому замість властивостей `left/margin-left` ми можемо використовувати `transform: translateX(…)`, `transform: scale` для збільшення розміру елемента, тощо. +На щастя, властивість `transform` дуже потужна. Використовуючи `transform` для елемента, ви можете обертати та крутити його, розтягувати та зменшувати, переміщувати та [багато іншого](https://developer.mozilla.org/docs/Web/CSS/transform#syntax ). Тому замість властивостей `left/margin-left` ми можемо використовувати `transform: translateX(…)`, `transform: scale` для збільшення розміру елемента, тощо. -Властивість `opacity` ніколи не запускає Макет (в Mozilla Gecko також пропускається Фарбування). Ми можемо використовувати його для ефектів відображення/приховування або посилення/згасання. +Властивість `opacity` ніколи не запускає Layout (в Mozilla Gecko також пропускається Paint). Ми можемо використовувати його для ефектів відображення/приховування або посилення/згасання. Поєднання `transform` з `opacity` зазвичай може вирішувати більшість наших потреб, забезпечуючи плавну, яскраву анімацію.