Проблема автозаповнення браузера AngularJS за допомогою директиви


111

Під час подання форми в AngularJS та використання браузера пам'ятайте про функціональність пароля, а при наступній спробі входу ви дозволяєте браузеру заповнити форму для входу з ім'ям користувача та паролем, $scopeмодель не буде змінена на основі автозаповнення.

Єдиний брудний злом, який я знайшов, - це використання наступної директиви:

app.directive("xsInputSync", ["$timeout" , function($timeout) {
    return {
        restrict : "A",
        require: "?ngModel",
        link : function(scope, element, attrs, ngModel) {
            $timeout(function() {
                if (ngModel.$viewValue && ngModel.$viewValue !== element.val()) {
                    scope.apply(function() {
                        ngModel.$setViewValue(element.val());
                    });
                }
                console.log(scope);
                console.log(ngModel.$name);
                console.log(scope[ngModel.$name]);
            }, 3000);
        }
    };
}]);

Проблема полягає в тому, що ngModel.$setViewValue(element.val());не змінюється ні модель, ні погляд на основі element.val()повернутого значення. Як я можу це досягти?


Цей код спочатку виглядає нормально червоніти ... але де решта (розмітка тощо)? Чи є у вас скрипка чи планк, який ми можемо бачити?
Бен Леш

Ось планк : plnkr.co/edit/CHrBAVU9Ycl2ex2DRr6R Я не впевнений, чи працює він безпосередньо на plunker, оскільки він працює в iframe.
lucassp

1
Не потрібно використовувати область застосування $. Можливо, він вам знадобиться в рідному window.setTimeout. Але краще використовувати кутовий.
gorpacrate

1
Для цієї проблеми існує "офіційна" фіксація поліфілів від Angular dev tbosch . Деталі див. У відповіді stackoverflow.com/a/25687396/3009639 нижче.
orszaczky

На жаль, "офіційне" виправлення - це залишення програмного забезпечення та не працює у багатьох браузерах. Проблеми були подані, але їх не розглядають, тому пошук триває ...
cyberwombat

Відповіді:


45

Мабуть, це відома проблема з Angular і наразі відкрита

Я не впевнений, що ти міг би тут зробити, окрім якоїсь роботи, як ти намагаєшся. Здається, ви на правильному шляху. Не вдалося змусити браузер спробувати запам'ятати пароль для вашої планки, тому я не впевнений, чи це спрацює, але подивіться:

app.directive('autoFillSync', function($timeout) {
   return {
      require: 'ngModel',
      link: function(scope, elem, attrs, ngModel) {
          var origVal = elem.val();
          $timeout(function () {
              var newVal = elem.val();
              if(ngModel.$pristine && origVal !== newVal) {
                  ngModel.$setViewValue(newVal);
              }
          }, 500);
      }
   }
});
<form name="myForm" ng-submit="login()">
   <label for="username">Username</label>
   <input type="text" id="username" name="username" ng-model="username" auto-fill-sync/><br/>
   <label for="password">Password</label>
   <input type="password" id="password" name="password" ng-model="password" auto-fill-sync/><br/>
   <button type="submit">Login</button>
</form>

Я думаю, вам просто потрібно трохи спростити свій підхід. Я однозначно рекомендую перевірити ngModel.$pristineта переконатися, що ви не перезаписуєте деякі дані поганого користувача. Крім того, 3 секунди, мабуть, занадто довгі. Вам не доведеться викликати $ apply () у $ timeout, BTW, він повинен чекати дайджесту $ для вас автоматично.

Справжня уловка: Чи переможе ваш браузер Angular до виконання? Що з моїм браузером?

Це, мабуть, неминуча війна, саме тому Angular (або Knockout) не змогли її легко вирішити. Немає гарантії стану даних у вашому введенні під час первинного виконання директиви. Навіть під час ініціалізації Angular .... Тому вирішити це складно.


1
Хороший момент про $ незайманий. Ну, 3 секунди є тільки для тестування. Діалогове вікно "Зберегти пароль" працює на Safari. Це ще одне питання, яке мені доведеться дослідити.
lucassp

Просто обдури його місцевим сховищем. :) ... я продовжуватиму думати про це. Але у мене є сумніви щодо доцільності змусити роботу автозаповнення з будь-якими двосторонніми прив'язками.
Бен Леш

Так, але мені все ж потрібно мати можливість оновити модель з директиви, щоб вона працювала.
lucassp

1
Я жартував про localStorage, ви не хочете зберігати паролі там.
Бен Леш

1
Це не буде працювати з Safari та автоматичним заповненням кредитної картки, оскільки це автозаповнення в будь-який момент запускається користувачем
Даллас Кларк

35

Ось рішення, яке є менш хакітним, ніж інші представлені рішення, і є семантично здоровим AngularJS: http://victorblog.com/2014/01/12/fixing-autocomplete-autofill-on-angularjs-form-submit/

myApp.directive('formAutofillFix', function() {
  return function(scope, elem, attrs) {
    // Fixes Chrome bug: https://groups.google.com/forum/#!topic/angular/6NlucSskQjY
    elem.prop('method', 'POST');

    // Fix autofill issues where Angular doesn't know about autofilled inputs
    if(attrs.ngSubmit) {
      setTimeout(function() {
        elem.unbind('submit').submit(function(e) {
          e.preventDefault();
          elem.find('input, textarea, select').trigger('input').trigger('change').trigger('keydown');
          scope.$apply(attrs.ngSubmit);
        });
      }, 0);
    }
  };
});

Тоді ви просто додаєте директиву до своєї форми:

<form ng-submit="submitLoginForm()" form-autofill-fix>
  <div>
    <input type="email" ng-model="email" ng-required />
    <input type="password" ng-model="password" ng-required />
    <button type="submit">Log In</button>
  </div>
</form>

2
Цей працював. Ми спробували більшу частину вище сказаного без жодного результату. Дякую!!! Результат - на mobbr.com
Патрік Савалле

1
Оскільки для цього використовується jQuery, не очікується, що він буде працювати без jQuery.
Квінн Шрел

1
Ось ми у 2018 році, і це все ще виправлення. Дякую Єзекіїлю!
Скотт Менні

35

Вам не потрібно використовувати $timeoutщось подібне. Ви можете використовувати систему подій.

Я думаю, що це більш англєльсько і не залежить від jQuery чи спеціального лову подій.

Наприклад, у вашому оброблювачі подання:

$scope.doLogin = function() {
    $scope.$broadcast("autofill:update");

    // Continue with the login.....
};

І тоді у вас може бути така autofillдиректива:

.directive("autofill", function () {
    return {
        require: "ngModel",
        link: function (scope, element, attrs, ngModel) {
            scope.$on("autofill:update", function() {
                ngModel.$setViewValue(element.val());
            });
        }
    }
});

Нарешті, ваш HTML буде таким:

<input type="text" name="username" ng-model="user.id" autofill="autofill"/>

14
У моєму випадку кнопка подання відключена, коли введення порожні.
gorpacrate

4
у вас ніколи не виникає проблем з цим, як це асинхронізація? де виклик входу на сервер вже відходить до того, як подія буде повторена, і директива встигла оновити область застосування?
Сандер

12

Більше не потрібно ламати! Кутовий dev tbosch зробив полізаполнення, яке запускає подію зміни, коли браузер змінює поля форми, не викликаючи події зміни:

https://github.com/tbosch/autofill-event

Наразі вони не вбудують це в кутовий код, оскільки це помилка для браузера, а також працює без Angular (наприклад, для звичайних додатків jQuery).

"Поліфайла буде перевіряти зміни в завантаженні документа, а також коли залишиться вхід (лише в тій же формі). Однак ви можете запустити перевірку вручну, якщо захочете.

У проекті є одиничні тести, а також напівавтоматичні тести, тому ми нарешті маємо змогу зібрати всі різні випадки використання разом із необхідними налаштуваннями браузера.

Зверніть увагу: ця поліфазова заправка працює із звичайними програмами AngularJS, із програмами AngularJS / jQuery, але також із звичайними програмами jQuery, які не використовують Angular. "

Його можна встановити за допомогою:

bower install autofill-event --save

Додайте сценарій autofill-event.js після jQuery або Angular на свою сторінку.

Це зробить наступне:

  • після DOMContentLoaded: перевірити всі поля введення
  • поле залишилося: перевірте всі інші поля в тій же формі

API (вручну запускати перевірку):

  • $el.checkAndTriggerAutoFillEvent(): Виконайте перевірку всіх елементів DOM у даному елементі jQuery / jQLite.

Як це працює

  1. Запам’ятайте всі зміни вхідних елементів користувачем (прослуховування подій змін), а також JavaScript (перехопленням $ el.val () для елементів jQuery / jQLite). Це змінене значення зберігається в елементі приватної власності.

  2. Перевірка елемента для автоматичного заповнення: Порівняйте поточне значення елемента із запам’ятовуваним значенням. Якщо це інакше, запустіть подію зміни.

Залежності

AngularJS або jQuery (працює з одним або обома)

Більше інформації та джерела на сторінці github .

Оригінальний кутовий випуск № 1460 про Github можна прочитати тут.


2
Це не працює для моїх справ. checkAndTriggerAutoFillEvent () спрацьовує і генерує події, але AngularJS ніколи не оновлює свої моделі і вважає, що поля порожні.
Splaktar

У мене не було жодних проблем із використанням цього поліліну. Якщо ви не можете змусити його працювати належним чином, вам слід створити окреме запитання, включаючи якийсь код. Якщо ви знайшли помилку, слід перевірити квитки на github або відкрити новий.
orszaczky

Так, я думаю, що це пов’язано з цією помилкою: github.com/tbosch/autofill-event/isissue/11
Splaktar

1
ngModelOptionsу поєднанні із autofill-eventзасобами, які тепер ви можете вказати changeяк одну з updateOnподій, щоб переконатися, що модель правильно синхронізована.
morloch

10

Брудний код, перевірте, чи виправлена проблема https://github.com/angular/angular.js/isissue/1460#issuecomment-18572604 перед використанням цього коду. Ця директива запускає події, коли поле заповнюється, не тільки перед подачею (це необхідно, якщо вам потрібно обробити вхід перед подачею)

 .directive('autoFillableField', function() {
    return {
                   restrict: "A",
                   require: "?ngModel",
                   link: function(scope, element, attrs, ngModel) {
                       setInterval(function() {
                           var prev_val = '';
                           if (!angular.isUndefined(attrs.xAutoFillPrevVal)) {
                               prev_val = attrs.xAutoFillPrevVal;
                           }
                           if (element.val()!=prev_val) {
                               if (!angular.isUndefined(ngModel)) {
                                   if (!(element.val()=='' && ngModel.$pristine)) {
                                       attrs.xAutoFillPrevVal = element.val();
                                       scope.$apply(function() {
                                           ngModel.$setViewValue(element.val());
                                       });
                                   }
                               }
                               else {
                                   element.trigger('input');
                                   element.trigger('change');
                                   element.trigger('keyup');
                                   attrs.xAutoFillPrevVal = element.val();
                               }
                           }
                       }, 300);
                   }
               };
});

5

Здається, чітке рішення прямо наперед. Не потрібно jQuery.

ОНОВЛЕННЯ:

  • Модель оновлюється лише тоді, коли значення моделі не дорівнює фактичному вхідному значенню.
  • Перевірка не зупиняється під час першого автозаповнення. Якщо ви хочете, наприклад, використовувати інший обліковий запис.

app.directive('autofillable', ['$timeout', function ($timeout) {
    return {
        scope: true,
        require: 'ngModel',
        link: function (scope, elem, attrs, ctrl) {
            scope.check = function(){
                var val = elem[0].value;
                if(ctrl.$viewValue !== val){
                    ctrl.$setViewValue(val)
                }
                $timeout(scope.check, 300);
            };
            scope.check();
        }
    }
}]);

4
+1 хороше рішення, подібне до того, що я придумав. Єдине доповнення, яке я б запропонував, - це спочатку перевірити, чи є модель $pristineперед її зміною, а потім після її зміни зберегти $pristineстан. Інакше ви виявите, що якщо форма завантажена без даних про автозаповнення, вищевказана директива все одно оновить модель і згодом зробить її, dirtyнавіть якщо контроль форми все ще вважається недоторканим. Приклад тут, використовуючи вашу директиву. Я прокоментував код, який би додав до нього: jsfiddle.net/OACDesigns/L8Sja/4
OACDesigns

1
також, я думаю, scope:trueмає бутиscope:{}
OACDesigns

4

Рішення 1 [Використання $ timeout]:

Директива:

app.directive('autoFillSync', function($timeout) {
    return {
      require: 'ngModel',
      link: function(scope, elem, attrs, model) {
          var origVal = elem.val();
          $timeout(function () {
              var newVal = elem.val();
              if(model.$pristine && origVal !== newVal) {
                  model.$setViewValue(newVal);
              }
          }, 500);
      }
    };
});

HTML:

<form name="myForm" ng-submit="login()">
  <label for="username">Username</label>
  <input type="text" id="username" name="username" ng-model="username" auto-fill-sync/><br/>
  <label for="password">Password</label>
  <input type="password" id="password" name="password" ng-model="password" auto-fill-sync/><br/>
  <button type="submit">Login</button>
</form>

Рішення 2 [Використання кутових подій]:

Реф: відповідь Бечко

Директива:

app.directive("autofill", function () {
    return {
        require: "ngModel",
        link: function (scope, element, attrs, ngModel) {
            scope.$on("autofill:update", function() {
                ngModel.$setViewValue(element.val());
            });
        }
    };
});

HTML:

<form name="myForm" ng-submit="login()">
  <label for="username">Username</label>
  <input type="text" id="username" name="username" ng-model="username" autofill/><br/>
  <label for="password">Password</label>
  <input type="password" id="password" name="password" ng-model="password" autofill/><br/>
  <button type="submit">Login</button>
</form>

Рішення 3 [Використання викликів методу ретрансляції]:

Директива:

app.directive('autoFill', function() {
    return {
        restrict: 'A',
        link: function(scope,element) {
            scope.submit = function(){
                scope.username = element.find("#username").val();
                scope.password = element.find("#password").val();
                scope.login();//call a login method in your controller or write the code here itself
            }

        }
    };
});

HTML:

<form name="myForm" auto-fill ng-submit="submit()">
   <label for="username">Username</label>
   <input type="text" id="username" name="username" ng-model="username" />
   <label for="password">Password</label>
   <input type="password" id="password" name="password" ng-model="password" />
   <button type="submit">Login</button>
</form>

2
Рішення 1 працювало дуже добре певний час, але тепер на angularjs 1.4.9 воно більше не працює. Перевірка model.$validдиректив повертає істину і до, і після $setViewValue, але елемент все ще є у ng-invalidкласі
Джон

4

Що ж, найпростіший спосіб наслідувати поведінку веб-переглядача, тому, якщо є проблема із подією зміни, просто зніміть її. Набагато простіше.

Директива:

yourModule.directive('triggerChange', function($sniffer) {
    return {
        link : function(scope, elem, attrs) {
            elem.on('click', function(){
                $(attrs.triggerChange).trigger(
                    $sniffer.hasEvent('input') ? 'input' : 'change'
                );
            });
        },
        priority : 1
    }
});

HTML:

<form >
    <input data-ng-model="user.nome" type="text" id="username">

    <input data-ng-model="user.senha" type="password" id="password" >

    <input type="submit" data-ng-click="login.connect()" id="btnlogin" 
           data-trigger-change="#password,#username"/>
</form>

Ви можете зробити деякі варіанти, наприклад, нанести директиву на форму та запустити подію на всі входи з класом .dirty на форму подання.


3

Це спосіб jQuery:

$(window).load(function() {
   // updates autofilled fields
   window.setTimeout(function() {
     $('input[ng-model]').trigger('input');
   }, 100);
 });

Це кутовий спосіб:

 app.directive('autofill', ['$timeout', function ($timeout) {
    return {
        scope: true,
        require: 'ngModel',
        link: function (scope, elem, attrs, ctrl) {
            $timeout(function(){
                $(elem[0]).trigger('input');
                // elem.trigger('input'); try this if above don't work
            }, 200)
        }
    }
}]);

HTML

<input type="number" autofill /> 

2
Зовсім некутний спосіб.
gorpacrate

1
@gorpacrate: Тепер перевірте це кутовим способом.
Nishchit Dhanani

2
На вкладках Angular Way є занадто багато пробілів. Просто киддін. Я люблю горизонтальну прокрутку.
Данило Біровський Попески

це не працює у кутку, оскільки на елементі немає тригера функції. Думаю, для цього потрібно було б включити jquery
Tomas

1
triggerHandler('input')повинно бути нормально, якщо у вас немає повноцінного jquery
Mutmatt

1

Ось ще одне рішення, яке менш хакітне, але вимагає додаткового коду в контролері.

HTML:

<form ng-submit="submitForm()" ng-controller="FormController">
    <input type="text" ng-model="username" autocomplete-username>
    <input type="submit">
</form>

Директива (CoffeeScript):

directives.directive 'autocompleteUsername', ->
    return (scope, element) ->
        scope.getUsername = ->
            element.val()

Контролер:

controllers.controller 'FormController', [->
    $scope.submitForm = ->
        username = $scope.getUsername?() ? $scope.username
        # HTTP stuff...
]

У вас це є у ванільному JavaScript?
f1lt3r

CoffeeScript.org надає перекладача: зробити тут
джеб

1

Це єдине рішення, яке я знайшов, що дозволило всі мої кутові перевірки працювати як розроблено, включаючи відключення / включення кнопки подання. Встановлюється з bower та 1 тегом сценарію. Базінга!

https://github.com/tbosch/autofill-event


1
це заповнення полі не відповідає кнопці подання / прив'язці входу до стану форми. якщо ви не натиснете на сторінку в будь-яку точку, яку ви хочете запустити дайджест (здається, що дія запускає браузер зробити справжнє заповнення), тоді кнопка стає включеною. Тестова сторінка з ручними тестами
e-cloud

1

Зміна значення моделі, замість використання функції тайм-аута, яка працювала на мене.

Ось мій код:

module.directive('autoFill', [ function() {
    return {
        require: 'ngModel',
        link:function(scope, element, attr, ngModel) {
            var origVal = element.val();
            if(origVal){
                ngModel.$modelValue = ngModel.$modelValue || origVal;
            }
        }
    };
}]);

1

Однолінійне вирішення обробника подання (вимагає jQuery):

if (!$scope.model) $scope.model = $('#input_field').val();

Це насправді не однолінійний, якщо він є в директиві, якою він, безумовно, повинен бути.
Ізервуд

0

Я змушую $ setValue (val ()) при подачі: (це працює без jQuery)

   var ValidSubmit = ['$parse', function ($parse) {
    return {
        compile: function compile(tElement, tAttrs, transclude) {
            return {
                post: function postLink(scope, element, iAttrs, controller) {
                    var form = element.controller('form');
                    form.$submitted = false;
                    var fn = $parse(iAttrs.validSubmit);
                    element.on('submit', function(event) {
                        scope.$apply(function() {
                            var inputs = element.find('input');
                            for(var i=0; i < inputs.length; i++) {
                                var ele = inputs.eq(i);
                                var field = form[inputs[i].name];
                                field.$setViewValue(ele.val());
                            }
                            element.addClass('ng-submitted');
                            form.$submitted = true;
                            if(form.$valid) {
                                fn(scope, {$event:event});
                            }
                        });
                    });
                    scope.$watch(function() { return form.$valid}, function(isValid) {
                        if(form.$submitted == false) return;
                        if(isValid) {
                            element.removeClass('has-error').addClass('has-success');
                        } else {
                            element.removeClass('has-success');
                            element.addClass('has-error');
                        }
                    });
                }
            }
        }
    }
}]
app.directive('validSubmit', ValidSubmit);

0

Я дуже новачок у Angularjs, але я знайшов просте рішення цієї проблеми => Змусити Angular переоцінити вираз ... змінивши його! (звичайно, вам потрібно запам’ятати початкове значення для повернення до початкового стану) Ось шлях, який він переходить у функцію контролера для подання форми:

    $scope.submit = function () {
                var oldpassword = $scope.password;
                $scope.password = '';
                $scope.password = oldpassword;
//rest of your code of the submit function goes here...

де, звичайно, значення, введене у вашому введенні пароля, було встановлено Windows, а не користувачем.


0

Ви можете спробувати цей код:

yourapp.directive('autofill',function () {

    return {
        scope: true,
        require: 'ngModel',
        link: function (scope, elem, attrs, ctrl) {
            var origVal = elem.val();
            if (origVal != '') {
                elem.trigger('input');
            }
        }
    }
});

0

Незначна модифікація цієї відповіді ( https://stackoverflow.com/a/14966711/3443828 ): використовуйте інтервал $ замість $ timeout, щоб не потрібно переходити в браузер.

mod.directive('autoFillSync', function($interval) {
    function link(scope, element, attrs, ngModel) {
        var origVal = element.val();
        var refresh = $interval(function() {
          if (!ngModel.$pristine) {
            $interval.cancel(refresh);
          }else{
            var newVal = element.val();
            if (origVal !== newVal) {
              ngModel.$setViewValue(newVal);
              $interval.cancel(refresh);
            }
          }
        }, 100);
    }

    return {
      require: 'ngModel',
      link: link
    }
  });

0

Це рішення, яке я отримав у своїх формах.

.directive('autofillSync', [ function(){
  var link = function(scope, element, attrs, ngFormCtrl){
    element.on('submit', function(event){
      if(ngFormCtrl.$dirty){
        console.log('returning as form is dirty');
        return;
      }   
      element.find('input').each(function(index, input){
        angular.element(input).trigger('input');
      }); 
    }); 
  };  
  return {
    /* negative priority to make this post link function run first */
    priority:-1,
    link: link,
    require: 'form'
  };  
}]);

І шаблон форми буде

<form autofill-sync name="user.loginForm" class="login-form" novalidate ng-submit="signIn()">
    <!-- Input fields here -->
</form>

Таким чином, мені вдалося запустити будь-які парсери / формати, які я маю на своїй ng-моделі, і забезпечити прозорість функцій подання.


0

Рішення без директив:

.run(["$window", "$rootElement", "$timeout", function($window, $rootElement, $timeout){

        var event =$window.document.createEvent("HTMLEvents");
        event.initEvent("change", true, true);

        $timeout(function(){

            Array.apply(null, $rootElement.find("input")).forEach(function(item){
                if (item.value.length) {
                    item.$$currentValue = item.value;
                    item.dispatchEvent(event);
                }
            });

        }, 500);
    }])

0

Це просте виправлення, яке працює у всіх випадках, які я перевіряв як у Firefox, так і в Chrome. Зауважте, що з найвищою відповіддю (директива w / timeout) у мене виникли проблеми з -

  • Кнопки назад / вперед браузера, не повторюйте події завантаження сторінки (тому виправлення не застосовується)
  • Завантаження облікових даних через деякий час після завантаження сторінки. наприклад, у Firefox, двічі клацніть на полі входу та виберіть із збережених облікових даних.
  • Потрібне рішення, яке оновлюється перед подачею форми, оскільки я відключаю кнопку "Увійти", поки не буде надано дійсний ввід

Це виправлення, очевидно, дуже німе і хакі, але воно працює 100% часу -

function myScope($scope, $timeout) {
    // ...
    (function autoFillFix() {
        $timeout(function() { 
            $('#username').trigger('change'); 
            $('#password').trigger('change'); 
            autoFillFix(); }, 500);                    
    })();
}

1
може замінити $timeoutз , $intervalщоб дати майбутнім УБС трохи більше контексту і позбутися від дивного вигляду рекурсивних викликів там відбувається
Mutmatt

0

Жодне з цих рішень не працювало на моє використання. У мене є кілька форм форм, які використовують ng-change для спостереження за змінами. Використання$watch не допомагає, оскільки воно не викликається автозаповненням. Оскільки у мене немає кнопки подання, не існує простого способу запустити деякі рішення, і я не мав успіху з використанням інтервалів.

У мене в результаті вимкнення автоматичного заповнення - не ідеально, але набагато менш заплутано для користувача.

<input readonly onfocus="this.removeAttribute('readonly');">

Знайшов відповідь тут


-1

Якщо ви використовуєте jQuery, ви можете це зробити під час подання форми:

HTML:

<form ng-submit="submit()">
    <input id="email" ng-model="password" required 
           type="text" placeholder="Your email">
    <input id="password" ng-model="password" required 
           type="password" placeholder="Password">
</form>

JS:

 $scope.submit = function() {
     $scope.password = $('#password').val();
}

1
Хоча це може спрацювати, складніші форми не є розумним.
origin1tech

-1

Якщо ви хочете зробити це просто, просто отримайте значення за допомогою JavaScript

У вашому кутовому js-контролері:

var username = document.getElementById ('ім'я користувача'). значення;


Я думаю, ти не розумієш, у чому проблема
аелор
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.