ЗаказатьПо трубке


98

Я не могу перевести этот код с Angualr 1 на Angular 2:

ng-repeat="todo in todos | orderBy: 'completed'"

Вот что я сделал после ответа Тьерри Темплиера:

Шаблон компонента:

*ngFor="#todo of todos | sort"

Код компонента:

@Component({
    selector: 'my-app',
    templateUrl: "./app/todo-list.component.html",
    providers: [TodoService],
    pipes: [ TodosSortPipe ]

})

Код трубы:

import { Pipe } from "angular2/core";
import {Todo} from './todo';

@Pipe({
  name: "sort"
})
export class TodosSortPipe {
  transform(array: Array<Todo>, args: string): Array<Todo> {
    array.sort((a: any, b: any) => {
      if (a < b) {
        return -1;
      } else if (a > b) {
        return 1;
      } else {
        return 0;
      }
    });
    return array;
  }
}

Я пытаюсь отсортировать массив Todos, упорядоченный по свойству completed. Сначала, todo.completed = falseа затем todo.complete = true.

Я не очень хорошо понимаю transformметод и то, как передавать аргументы в этом методе и в sortметоде.

Какой args: stringаргумент? Что такое aи bоткуда они берутся?


Я нашел этот пакет модуля для OrderBy в версиях Angular5
Code Spy

Ответы:


79

Я изменил ответ @Thierry Templier, чтобы канал мог сортировать пользовательские объекты в angular 4:

import { Pipe, PipeTransform } from "@angular/core";

@Pipe({
  name: "sort"
})
export class ArraySortPipe  implements PipeTransform {
  transform(array: any, field: string): any[] {
    if (!Array.isArray(array)) {
      return;
    }
    array.sort((a: any, b: any) => {
      if (a[field] < b[field]) {
        return -1;
      } else if (a[field] > b[field]) {
        return 1;
      } else {
        return 0;
      }
    });
    return array;
  }
}

И использовать это:

*ngFor="let myObj of myArr | sort:'fieldName'"

Надеюсь, это кому-то поможет.


1
Я получил сообщение: The pipe 'sort' could not be found. Могу ли я каким-то образом вставить канал в свой компонент, как в угловых двух каналах: [ArraySortPipe]?
Матия

См. Ответ @Thierry Templier о том, как вставить канал в компонент вашего приложения
Сал,

Вам необходимо включить "ArraySortPipe" в объявления иерархии ваших модулей. Что-то вроде: import {ArraySortPipe} from './../../shared/filters.pipe'; В app.module.ts и любом модуле под ним. положить: декларации: [ArraySortPipe]
Дуди

72

См. Https://angular.io/guide/pipes#appendix-no-filterpipe-or-orderbypipe для полного обсуждения. Эта цитата наиболее актуальна. По сути, для крупномасштабных приложений, которые следует агрессивно минимизировать, логика фильтрации и сортировки должна перемещаться в сам компонент.

«Некоторые из нас могут не заботиться об этом агрессивно. Это наш выбор. Но продукт Angular не должен мешать кому-то еще агрессивно минимизировать. Поэтому команда Angular решила, что все, что поставляется в Angular, будет безопасно минимизировать.

Команда Angular и многие опытные разработчики Angular настоятельно рекомендуют перенести логику фильтрации и сортировки в сам компонент. Компонент может предоставлять свойство filterHeroes или sortedHeroes и контролировать, когда и как часто выполнять вспомогательную логику. Любые возможности, которые вы бы поместили в канал и совместно использовали в приложении, могут быть записаны в службе фильтрации / сортировки и внедрены в компонент ".


7
Как вы должны переместить логику «в сам компонент» таким образом, чтобы «взять под контроль, когда и как часто выполнять вспомогательную логику»? Есть ли хорошие примеры для подражания?
Mzzzzzz

1
@Mzzzzzz Там, где упоминается такое свойство, как filteredHeroesи sortedHeroes, я думаю, идея заключается в том, что при инициализации компонента вы запускаете некоторую логику сортировки / фильтрации (возможно, вызываете метод из ngOnInit), затем устанавливаете это свойство с отсортированными / отфильтрованными результатами и только повторно запускайте логику / обновляйте свойство, если есть что-то, что вызывает необходимость (например, взаимодействие с пользователем запускает вызов AJAX, чтобы получить больше героев, или пользователь нажимает флажок, чтобы отфильтровать половину из них на основе некоторых критериев и т. д.)
jmq

42

Для этого вы можете реализовать собственный канал, который использует sortметод массивов:

import { Pipe } from "angular2/core";

@Pipe({
  name: "sort"
})
export class ArraySortPipe {
  transform(array: Array<string>, args: string): Array<string> {
    array.sort((a: any, b: any) => {
      if (a < b) {
        return -1;
      } else if (a > b) {
        return 1;
      } else {
        return 0;
      }
    });
    return array;
  }
}

Затем используйте эту трубу, как описано ниже. Не забудьте указать свой канал в pipesатрибуте компонента:

@Component({
  (...)
  template: `
    <li *ngFor="list | sort"> (...) </li>
  `,
  pipes: [ ArraySortPipe ]
})
(...)

Это простой пример для массивов со строковыми значениями, но у вас может быть расширенная обработка сортировки (на основе атрибутов объекта в случае массива объектов, на основе параметров сортировки ...).

Вот план для этого: https://plnkr.co/edit/WbzqDDOqN1oAhvqMkQRQ?p=preview .

Надеюсь, это поможет тебе, Тьерри


1
Спасибо за ответ, не могли бы вы объяснить метод сортировки?

1
Фактически sortметод является методом Arrayобъекта JavaScript . См. Эту ссылку: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… .
Тьерри Темплиер

Хорошо, я понял, он использует метод сортировки javascript с функцией сравнения в качестве аргумента. Спасибо!

1
К сожалению, плункер устарел. Тьерри?

4
не pipes: [..]объявление больше не действует (и больше не надо)
phil294

9

Обновлен OrderByPipe: исправлено отсутствие сортировки строк.

создать класс OrderByPipe:

import { Pipe, PipeTransform } from "@angular/core";
@Pipe( {
name: 'orderBy'
} )
export class OrderByPipe implements PipeTransform {
transform( array: Array<any>, orderField: string, orderType: boolean ): Array<string> {
    array.sort( ( a: any, b: any ) => {
        let ae = a[ orderField ];
        let be = b[ orderField ];
        if ( ae == undefined && be == undefined ) return 0;
        if ( ae == undefined && be != undefined ) return orderType ? 1 : -1;
        if ( ae != undefined && be == undefined ) return orderType ? -1 : 1;
        if ( ae == be ) return 0;
        return orderType ? (ae.toString().toLowerCase() > be.toString().toLowerCase() ? -1 : 1) : (be.toString().toLowerCase() > ae.toString().toLowerCase() ? -1 : 1);
    } );
    return array;
  }
}

в вашем контроллере:

@Component({
pipes: [OrderByPipe]
})

или в вашем

 declarations: [OrderByPipe]

в вашем html:

<tr *ngFor="let obj of objects | orderBy : ObjFieldName: OrderByType">

ObjFieldName: имя поля объекта, которое вы хотите отсортировать;

OrderByType: логический; true: по убыванию; false: по возрастанию;


Для строковых аргументов при сравнении a [orderField] - b [orderField] возвращает NaN
Петр Пенчек

Для аргументов даты это не работает. Формат даты как текст будет неправильно упорядочен.
Рафаэль Пизао,

9

Angular не поставляется с фильтром orderBy из коробки, но если мы решим, что он нам нужен, мы легко его создадим. Однако есть некоторые предостережения, о которых мы должны знать, связанные со скоростью и минимизацией. Увидеть ниже.

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

import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
  name: 'sort'
})
export class SortPipe implements PipeTransform {
  transform(ary: any, fn: Function = (a,b) => a > b ? 1 : -1): any {
    return ary.sort(fn)
  }
}

Этот канал принимает функцию сортировки ( fn) и дает ей значение по умолчанию, которое будет разумно сортировать массив примитивов. У нас есть возможность переопределить эту функцию сортировки, если мы хотим.

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

Сортировка примитивов (числа и строки)

Мы могли бы использовать это для сортировки массива чисел или строк, используя компаратор по умолчанию:

import { Component } from '@angular/core';

@Component({
  selector: 'cat',
  template: `
    {{numbers | sort}}
    {{strings | sort}}
  `
})
export class CatComponent
  numbers:Array<number> = [1,7,5,6]
  stringsArray<string> = ['cats', 'hats', 'caveats']
}

Сортировка массива объектов

Если мы хотим отсортировать массив объектов, мы можем дать ему функцию компаратора.

import { Component } from '@angular/core';

@Component({
  selector: 'cat',
  template: `
    {{cats | sort:byName}}
  `
})
export class CatComponent
  cats:Array<Cat> = [
    {name: "Missy"},
    {name: "Squoodles"},
    {name: "Madame Pompadomme"}
  ]
  byName(a,b) {
    return a.name > b.name ? 1 : -1
  }
}

Предостережения - чистые и нечистые трубы

Angular 2 имеет концепцию чистых и нечистых труб.

Чистый канал оптимизирует обнаружение изменений с помощью идентификации объекта. Это означает, что конвейер будет работать только в том случае, если входной объект изменит идентификатор, например, если мы добавим новый элемент в массив. Он не будет спускаться в предметы. Это означает, что если мы изменим вложенный атрибут: this.cats[2].name = "Fluffy"например, конвейер не будет запускаться повторно. Это помогает Angular быть быстрым. Угловые трубы по умолчанию чистые.

С другой стороны, нечистый канал будет проверять атрибуты объекта. Это потенциально делает его намного медленнее. Поскольку он не может гарантировать, что будет делать функция конвейера (возможно, он по-разному сортируется в зависимости от времени суток, например), нечистый канал будет запускаться каждый раз, когда происходит асинхронное событие. Это значительно замедлит работу вашего приложения, если массив большой.

Труба выше чистая. Это означает, что он будет работать только в том случае, если объекты в массиве неизменны. Если вы меняете кота, вы должны заменить весь объект кота новым.

this.cats[2] = {name:"Tomy"}

Мы можем изменить указанное выше на нечистую трубу, установив чистый атрибут:

import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
  name: 'sort',
  pure: false
})
export class SortPipe implements PipeTransform {
  transform(ary: any, fn: Function = (a,b) => a > b ? 1 : -1): any {
    return ary.sort(fn)
  }
}

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


Спасибо .. Очень помогло. Но один вопрос ... Если мы не должны использовать конвейер или фильтр для сортировки, что лучше всего? Я искал везде, все дают решение, создавая трубу.
Паван Шукла

@PavanShukla Вы можете использовать канал, просто убедитесь, что записи вашего массива неизменяемы, и создайте чистый канал. Или, если у вас нет большого массива, сделайте нечистый канал и отсортируйте каждый рендер. В качестве альтернативы создайте отсортированный массив как атрибут вашего компонента и визуализируйте его.
superluminary

Я использовал логику array.sort при нажатии на каждый заголовок облака. Я делаю эту операцию с массивом данных дисплея .. это хороший способ?
Паван Шукла

7

Я создал канал OrderBy, который делает именно то, что вам нужно. Он также поддерживает сортировку по нескольким столбцам перечисляемого объекта.

<li *ngFor="#todo in todos | orderBy : ['completed']">{{todo.name}} {{todo.completed}}</li>

Этот канал позволяет добавлять дополнительные элементы в массив после рендеринга страницы и динамически сортирует массив с обновлениями.

У меня есть запись о процессе здесь .

А вот рабочая демонстрация: http://fuelinteractive.github.io/fuel-ui/#/pipe/orderby и https://plnkr.co/edit/DHLVc0?p=info


Вы не обрабатываете нулевые значения.
Али Хабибзаде

если (а == ноль) а = 0; если (b == null) b = 0;
Али Хабибзаде

Также значения с равным значением являются скачкообразными и перемещаются, когда вы щелкаете мышью в интерфейсе
Али Хабибзаде

@XGreen спасибо за это. Я добавлю поддержку значений null / undefined в следующем обновлении fuel-ui. Что касается резкости равных значений, я этого не вижу. Какой браузер вы используете?
Кори Шоу

Версия Chrome 50.0.2661.86 (64-разрядная), OSX El Capitan
Али Хабибзаде,

4

Рекомендую использовать lodash с угловым, тогда ваша труба будет следующей:

import {Pipe, PipeTransform} from '@angular/core';
import * as _ from 'lodash'
@Pipe({
    name: 'orderBy'
})
export class OrderByPipe implements PipeTransform {

    transform(array: Array<any>, args?: any): any {
        return _.sortBy(array, [args]);
    }

}

и используйте его в html, например

*ngFor = "#todo of todos | orderBy:'completed'"

и не забудьте добавить Pipe в свой модуль

@NgModule({
    ...,
    declarations: [OrderByPipe, ...],
    ...
})

Мне нравится ваш подход Александр Петрик, но я предпочитаю отправлять массив в шаблоне: orderBy: ['field1', 'field2'], а затем вызывать канал: return _.sortBy (array, args);
Эрик

1
Проблема использования _.sortBy заключается в том, что вы не можете указать порядок потомков. Я обнаружил, что с помощью _.orderBy вы можете указать собственный порядок для каждого поля. т.е.: _.orderBy (array, ['field1', 'field2'], ['asc', 'desc'])
Эрик

3

Это будет работать для любого поля, которое вы ему передадите. ( ВАЖНО: он будет упорядочен только в алфавитном порядке, поэтому, если вы передадите дату, он упорядочит ее как алфавит, а не как дату)

/*
 *      Example use
 *      Basic Array of single type: *ngFor="let todo of todoService.todos | orderBy : '-'"
 *      Multidimensional Array Sort on single column: *ngFor="let todo of todoService.todos | orderBy : ['-status']"
 *      Multidimensional Array Sort on multiple columns: *ngFor="let todo of todoService.todos | orderBy : ['status', '-title']"
 */

import {Pipe, PipeTransform} from "@angular/core";

@Pipe({name: "orderBy", pure: false})
export class OrderByPipe implements PipeTransform {

    value: string[] = [];

    static _orderByComparator(a: any, b: any): number {

        if (a === null || typeof a === "undefined") { a = 0; }
        if (b === null || typeof b === "undefined") { b = 0; }

        if (
            (isNaN(parseFloat(a)) ||
            !isFinite(a)) ||
            (isNaN(parseFloat(b)) || !isFinite(b))
        ) {
            // Isn"t a number so lowercase the string to properly compare
            a = a.toString();
            b = b.toString();
            if (a.toLowerCase() < b.toLowerCase()) { return -1; }
            if (a.toLowerCase() > b.toLowerCase()) { return 1; }
        } else {
            // Parse strings as numbers to compare properly
            if (parseFloat(a) < parseFloat(b)) { return -1; }
            if (parseFloat(a) > parseFloat(b)) { return 1; }
        }

        return 0; // equal each other
    }

    public transform(input: any, config = "+"): any {
        if (!input) { return input; }

        // make a copy of the input"s reference
        this.value = [...input];
        let value = this.value;
        if (!Array.isArray(value)) { return value; }

        if (!Array.isArray(config) || (Array.isArray(config) && config.length === 1)) {
            let propertyToCheck: string = !Array.isArray(config) ? config : config[0];
            let desc = propertyToCheck.substr(0, 1) === "-";

            // Basic array
            if (!propertyToCheck || propertyToCheck === "-" || propertyToCheck === "+") {
                return !desc ? value.sort() : value.sort().reverse();
            } else {
                let property: string = propertyToCheck.substr(0, 1) === "+" || propertyToCheck.substr(0, 1) === "-"
                    ? propertyToCheck.substr(1)
                    : propertyToCheck;

                return value.sort(function(a: any, b: any) {
                    let aValue = a[property];
                    let bValue = b[property];

                    let propertySplit = property.split(".");

                    if (typeof aValue === "undefined" && typeof bValue === "undefined" && propertySplit.length > 1) {
                        aValue = a;
                        bValue = b;
                        for (let j = 0; j < propertySplit.length; j++) {
                            aValue = aValue[propertySplit[j]];
                            bValue = bValue[propertySplit[j]];
                        }
                    }

                    return !desc
                        ? OrderByPipe._orderByComparator(aValue, bValue)
                        : -OrderByPipe._orderByComparator(aValue, bValue);
                });
            }
        } else {
            // Loop over property of the array in order and sort
            return value.sort(function(a: any, b: any) {
                for (let i = 0; i < config.length; i++) {
                    let desc = config[i].substr(0, 1) === "-";
                    let property = config[i].substr(0, 1) === "+" || config[i].substr(0, 1) === "-"
                        ? config[i].substr(1)
                        : config[i];

                    let aValue = a[property];
                    let bValue = b[property];

                    let propertySplit = property.split(".");

                    if (typeof aValue === "undefined" && typeof bValue === "undefined" && propertySplit.length > 1) {
                        aValue = a;
                        bValue = b;
                        for (let j = 0; j < propertySplit.length; j++) {
                            aValue = aValue[propertySplit[j]];
                            bValue = bValue[propertySplit[j]];
                        }
                    }

                    let comparison = !desc
                        ? OrderByPipe._orderByComparator(aValue, bValue)
                        : -OrderByPipe._orderByComparator(aValue, bValue);

                    // Don"t return 0 yet in case of needing to sort by next property
                    if (comparison !== 0) { return comparison; }
                }

                return 0; // equal each other
            });
        }
    }
}

Вы можете опубликовать пример использования?
TheUnreal

Я не могу скомпилировать предоставленный вами код. Я получаю сообщение об @Componentотсутствии pipesсобственности.
Азимут

3

Это хорошая замена для AngularJs orderby pipe в angular 4 . Легко и просто использовать.

Это URL-адрес github для получения дополнительной информации https://github.com/VadimDez/ngx-order-pipe

import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
  name: 'orderBy'
})
export class OrderPipe implements PipeTransform {

  transform(value: any | any[], expression?: any, reverse?: boolean): any {
    if (!value) {
      return value;
    }

    const isArray = value instanceof Array;

    if (isArray) {
      return this.sortArray(value, expression, reverse);
    }

    if (typeof value === 'object') {
      return this.transformObject(value, expression, reverse);
    }

    return value;
  }

  /**
   * Sort array
   *
   * @param value
   * @param expression
   * @param reverse
   * @returns {any[]}
   */
  private sortArray(value: any[], expression?: any, reverse?: boolean): any[] {
    const isDeepLink = expression && expression.indexOf('.') !== -1;

    if (isDeepLink) {
      expression = OrderPipe.parseExpression(expression);
    }

    let array: any[] = value.sort((a: any, b: any): number => {
      if (!expression) {
        return a > b ? 1 : -1;
      }

      if (!isDeepLink) {
        return a[expression] > b[expression] ? 1 : -1;
      }

      return OrderPipe.getValue(a, expression) > OrderPipe.getValue(b, expression) ? 1 : -1;
    });

    if (reverse) {
      return array.reverse();
    }

    return array;
  }


  /**
   * Transform Object
   *
   * @param value
   * @param expression
   * @param reverse
   * @returns {any[]}
   */
  private transformObject(value: any | any[], expression?: any, reverse?: boolean): any {
    let parsedExpression = OrderPipe.parseExpression(expression);
    let lastPredicate = parsedExpression.pop();
    let oldValue = OrderPipe.getValue(value, parsedExpression);

    if (!(oldValue instanceof Array)) {
      parsedExpression.push(lastPredicate);
      lastPredicate = null;
      oldValue = OrderPipe.getValue(value, parsedExpression);
    }

    if (!oldValue) {
      return value;
    }

    const newValue = this.transform(oldValue, lastPredicate, reverse);
    OrderPipe.setValue(value, newValue, parsedExpression);
    return value;
  }

  /**
   * Parse expression, split into items
   * @param expression
   * @returns {string[]}
   */
  private static parseExpression(expression: string): string[] {
    expression = expression.replace(/\[(\w+)\]/g, '.$1');
    expression = expression.replace(/^\./, '');
    return expression.split('.');
  }

  /**
   * Get value by expression
   *
   * @param object
   * @param expression
   * @returns {any}
   */
  private static getValue(object: any, expression: string[]) {
    for (let i = 0, n = expression.length; i < n; ++i) {
      const k = expression[i];
      if (!(k in object)) {
        return;
      }
      object = object[k];
    }

    return object;
  }

  /**
   * Set value by expression
   *
   * @param object
   * @param value
   * @param expression
   */
  private static setValue(object: any, value: any, expression: string[]) {
    let i;
    for (i = 0; i < expression.length - 1; i++) {
      object = object[expression[i]];
    }

    object[expression[i]] = value;
  }
}

2

Поскольку мы знаем, что filter и order by удалены из ANGULAR 2, и нам нужно написать свой собственный, вот хороший пример плункера и подробная статья

Он использовал как фильтр, так и orderby, вот код для трубы заказа

import { Pipe, PipeTransform } from '@angular/core';    
@Pipe({  name: 'orderBy' })
export class OrderrByPipe implements PipeTransform {

  transform(records: Array<any>, args?: any): any {       
    return records.sort(function(a, b){
          if(a[args.property] < b[args.property]){
            return -1 * args.direction;
          }
          else if( a[args.property] > b[args.property]){
            return 1 * args.direction;
          }
          else{
            return 0;
          }
        });
    };
 }

2

Вы можете использовать это для объектов:

@Pipe({
  name: 'sort',
})
export class SortPipe implements PipeTransform {

  transform(array: any[], field: string): any[] {
    return array.sort((a, b) => a[field].toLowerCase() !== b[field].toLowerCase() ? a[field].toLowerCase() < b[field].toLowerCase() ? -1 : 1 : 0);
  }

}

2

В package.json добавьте что-нибудь вроде (эта версия подходит для Angular 2):

  "ngx-order-pipe": "^1.1.3",

В вашем модуле машинописного текста (и массиве импорта):

  import { OrderModule } from 'ngx-order-pipe';

1
<!-- const cars=['Audi','Merc','BMW','Volvo','Tesla'] -->

<ul>
  <li *ngFor="let car of cars">{{car}}</li>
</ul>


/*
 *ngFor="let c of oneDimArray | sortBy:'asc'"
 *ngFor="let c of arrayOfObjects | sortBy:'asc':'propertyName'"
*/
import { Pipe, PipeTransform } from '@angular/core';
import { orderBy } from 'lodash';

@Pipe({ name: 'sortBy' })
export class SortByPipe implements PipeTransform {

  transform(value: any[], order = '', column: string = ''): any[] {
    if (!value || order === '' || !order) { return value; } // no array
    if (!column || column === '') { return sortBy(value); } // sort 1d array
    if (value.length <= 1) { return value; } // array with only one item
    return orderBy(value, [column], [order]);
  }
}

1
Спасибо, отличный ответ
AM - EVS

0

В текущей версии Angular2 каналы orderBy и ArraySort не поддерживаются. Для этого вам нужно написать / использовать несколько пользовательских каналов.


0

Для версии Angular 5+ мы можем использовать пакет ngx-order-pipe

Ссылка на исходный учебник

Установить пакет

$ npm install ngx-order-pipe --save

Модуль импорта в приложениях

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { FormsModule } from '@angular/forms';
import { AppComponent } from './app.component';
import { OrderModule } from 'ngx-order-pipe';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    FormsModule,
    OrderModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

использовать где угодно

  <ul>
    <li *ngFor="let item of (dummyData | orderBy:'name') ">
      {{item.name}}
    </li>
  </ul>


-1
Component template:
todos| sort: ‘property’:’asc|desc’

Pipe code:

import { Pipe,PipeTransform  } from "angular/core";
import {Todo} from './todo';

@Pipe({
  name: "sort"
})
export class TodosSortPipe implements PipeTransform {
  transform(array: Array<Todo>, args: string): Array<Todo> {
    array.sort((a: any, b: any) => {
      if (a < b) {
        return -1;
      } else if (a > b) {
        return 1;
      } else {`enter code here`
        return 0;
      }
    });
    return array;
  }
}
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.