Projet

Général

Profil

0001-forms-detect-and-suggest-fixes-for-typos-in-email-do.patch

Frédéric Péters, 02 juin 2020 09:50

Télécharger (6,7 ko)

Voir les différences:

Subject: [PATCH] forms: detect and suggest fixes for typos in email domains
 (#42396)

 wcs/qommon/form.py                   |   3 +
 wcs/qommon/static/css/dc2/admin.css  |  36 ++++++++
 wcs/qommon/static/js/qommon.forms.js | 119 +++++++++++++++++++++++++++
 wcs/root.py                          |   2 +
 4 files changed, 160 insertions(+)
wcs/qommon/form.py
882 882
        if not 'size' in kwargs:
883 883
            self.attrs['size'] = '35'
884 884

  
885
    def add_media(self):
886
        get_response().add_javascript(['jquery.js', '../../i18n.js', 'qommon.forms.js'])
887

  
885 888
    def _parse(self, request):
886 889
        StringWidget._parse(self, request)
887 890
        if self.value is not None:
wcs/qommon/static/css/dc2/admin.css
1916 1916
#sidebar-custom-views .active {
1917 1917
	font-weight: bold;
1918 1918
}
1919

  
1920
.field-live-hint {
1921
	position: absolute;
1922
	background: #ffffee;
1923
	color: #333;
1924
	z-index: 1000000;
1925
	padding: 1em 1em;
1926
	box-shadow: 0px 2px 5px 0px rgba(0, 0, 0, 0.16);
1927
}
1928

  
1929
.field-live-hint span::before {
1930
	font-family: FontAwesome;
1931
	content: "\f071";  /* exclamation triangle */
1932
	padding-right: 0.5em;
1933
}
1934

  
1935
.field-live-hint button.action,
1936
.field-live-hint button.close {
1937
	margin: 0 1em;
1938
	padding: 0;
1939
	color: blue !important;
1940
	border: none;
1941
	text-decoration: underline !important;
1942
	background: transparent !important;
1943
	box-shadow: none !important;
1944
}
1945

  
1946
.field-live-hint button.close {
1947
	color: #333 !important;
1948
	margin: 0;
1949
	text-decoration: none !important;
1950
}
1951

  
1952
.field-live-hint button.close::after {
1953
	content: "×";
1954
}
wcs/qommon/static/js/qommon.forms.js
1
String.prototype.similarity = function(string) {
2
  // adapted from https://github.com/jordanthomas/jaro-winkler (licensed as MIT)
3
  var s1 = this, s2 = string;
4
  var m = 0;
5
  var i;
6
  var j;
7

  
8
  // Exit early if either are empty.
9
  if (s1.length === 0 || s2.length === 0) {
10
    return 0;
11
  }
12

  
13
  // Convert to upper
14
  s1 = s1.toUpperCase();
15
  s2 = s2.toUpperCase();
16

  
17
  // Exit early if they're an exact match.
18
  if (s1 === s2) {
19
    return 1;
20
  }
21

  
22
  var range = (Math.floor(Math.max(s1.length, s2.length) / 2)) - 1;
23
  var s1Matches = new Array(s1.length);
24
  var s2Matches = new Array(s2.length);
25

  
26
  for (i = 0; i < s1.length; i++) {
27
    var low  = (i >= range) ? i - range : 0;
28
    var high = (i + range <= (s2.length - 1)) ? (i + range) : (s2.length - 1);
29

  
30
    for (j = low; j <= high; j++) {
31
      if (s1Matches[i] !== true && s2Matches[j] !== true && s1[i] === s2[j]) {
32
        ++m;
33
        s1Matches[i] = s2Matches[j] = true;
34
        break;
35
      }
36
    }
37
  }
38

  
39
  // Exit early if no matches were found.
40
  if (m === 0) {
41
    return 0;
42
  }
43

  
44
  // Count the transpositions.
45
  var k = 0;
46
  var numTrans = 0;
47

  
48
  for (i = 0; i < s1.length; i++) {
49
    if (s1Matches[i] === true) {
50
      for (j = k; j < s2.length; j++) {
51
        if (s2Matches[j] === true) {
52
          k = j + 1;
53
          break;
54
        }
55
      }
56

  
57
      if (s1[i] !== s2[j]) {
58
        ++numTrans;
59
      }
60
    }
61
  }
62

  
63
  var weight = (m / s1.length + m / s2.length + (m - (numTrans / 2)) / m) / 3;
64
  var l = 0;
65
  var p = 0.1;
66

  
67
  if (weight > 0.7) {
68
    while (s1[l] === s2[l] && l < 4) {
69
      ++l;
70
    }
71

  
72
    weight = weight + l * p * (1 - weight);
73
  }
74

  
75
  return weight;
76
}
77

  
1 78
$(function() {
2 79
  var autosave_timeout_id = null;
3 80
  if ($('form[data-has-draft]').length == 1) {
......
30 107
      last_auto_save = $('form[data-has-draft]').serialize();
31 108
    });
32 109
  }
110
  var well_known_domains = ['gmail.com', 'msn.com', 'hotmail.com', 'hotmail.fr', 'wanadoo.fr',
111
                            'free.fr', 'yahoo.fr', 'numericable.fr', "laposte.fr"];
112
  $('input[type=email]').on('change wcs:change', function() {
113
    var $email_input = $(this);
114
    var val = $email_input.val();
115
    var val_domain = val.split('@')[1];
116
    var $domain_hint_div = this.domain_hint_div;
117
    var highest_ratio = 0;
118
    var suggestion = null;
119
    for (var i=0; i < well_known_domains.length; i++) {
120
      var domain = well_known_domains[i];
121
      var ratio = val_domain.similarity(domain);
122
      if (ratio > highest_ratio) {
123
        highest_ratio = ratio;
124
        suggestion = domain;
125
      }
126
    }
127
    if (highest_ratio > 0.80 && highest_ratio < 1) {
128
      if ($domain_hint_div === undefined) {
129
        $domain_hint_div = $('<div class="field-live-hint"><span class="message"></span><button class="action"></button><button class="close"><span class="sr-only"></span></button></div>');
130
        this.domain_hint_div = $domain_hint_div;
131
        $(this).after($domain_hint_div);
132
        $domain_hint_div.find('button.action').on('click', function() {
133
          $email_input.val($email_input.val().replace(/@.*/, '@' + $(this).data('suggestion')));
134
          $email_input.trigger('wcs:change');
135
          $domain_hint_div.hide();
136
          return false;
137
        });
138
        $domain_hint_div.find('button.close').on('click', function() {
139
          $domain_hint_div.hide();
140
          return false;
141
        });
142
      }
143
      $domain_hint_div.find('span').text(WCS_I18N.email_domain_suggest + ' @' + suggestion + ' ?');
144
      $domain_hint_div.find('button.action').text(WCS_I18N.email_domain_fix);
145
      $domain_hint_div.find('button.action').data('suggestion', suggestion);
146
      $domain_hint_div.find('button.close span.sr-only').text(WCS_I18N.close);
147
      $domain_hint_div.show();
148
    } else if ($domain_hint_div) {
149
      $domain_hint_div.hide();
150
    }
151
  });
33 152
  $('.date-pick').each(function() {
34 153
    if (this.type == "date" || this.type == "time") {
35 154
      return;  // prefer native date/time widgets
wcs/root.py
411 411
            's2_loadmore': _('Loading more results...'),
412 412
            's2_searching': _('Searching...'),
413 413
            'close': _('Close'),
414
            'email_domain_suggest': _('Did you want to write'),
415
            'email_domain_fix': _('Apply fix'),
414 416
        }
415 417
        return 'WCS_I18N = %s;\n' % json.dumps(strings)
416 418

  
417
-