Sitic has submitted this change and it was merged.

Change subject: Add cross-wiki notifications
......................................................................


Add cross-wiki notifications

Shows notifications if there are new ones.

Bug: T103678
Change-Id: Ifb06262bfc6916884301e028675f84daee7085be
---
M backend/celery/api.py
M backend/celery/tasks.py
M backend/server/__init__.py
M frontend/src/app/index.css
M frontend/src/app/index.js
M frontend/src/app/main/main.html
M frontend/src/app/runBlock.js
M frontend/src/app/services.js
A frontend/src/components/notifications/notifications.controller.js
A frontend/src/components/notifications/notifications.html
M frontend/src/components/watchlist/watchlist.controller.js
M frontend/src/components/watchlist/watchlist.html
M frontend/src/i18n/locale-en.json
13 files changed, 276 insertions(+), 45 deletions(-)

Approvals:
  Sitic: Verified; Looks good to me, approved



diff --git a/backend/celery/api.py b/backend/celery/api.py
index 987c4b6..3ed8643 100644
--- a/backend/celery/api.py
+++ b/backend/celery/api.py
@@ -55,19 +55,22 @@
         time = now + delta
         return time.strftime("%Y%m%d%H%M%S")
 
+    def handle_response(self, response):
+        if 'error' in response:
+            logger.error(response['error'])
+            if response['error']['code'] == "mwoauth-invalid-authorization":
+                raise Exception("OAuth authentication failed")
+
+            raise Exception(str(response['error']))
+        if 'warnings' in response:
+            logger.warn("API-request warning: " + str(response['warnings']))
+
     def query(self, params):
         params['format'] = "json"
         response = requests.get(self.api_url, params=params, auth=self.auth,
                                 headers=self.headers).json()
 
-        if 'error' in response:
-            logger.error(response['error']['code'])
-            if response['error']['code'] == "mwoauth-invalid-authorization":
-                raise Exception("OAuth authentication failed")
-
-            raise Exception(str(response['error']['code']))
-        if 'warnings' in response:
-            logger.warn("API-request warning: " + str(response['warnings']))
+        self.handle_response(response)
         return response
 
     def query_gen(self, params):
@@ -91,6 +94,29 @@
                 break
             last_continue = response['continue']
 
+    def post(self, params, payload, token_type='csrf'):
+        params['format'] = "json"
+        token = self.get_token(token_type)
+        payload['token'] = token
+
+        print params
+        print payload
+        response = requests.post(self.api_url,
+                                 params=params,
+                                 data=payload,
+                                 auth=self.auth,
+                                 headers=self.headers)
+
+        self.handle_response(json.loads(response.text))
+
+    def get_token(self, type='csrf'):
+        params = {'action': "query",
+                  'meta': "tokens",
+                  'type': type}
+        r = self.query(params)
+        token = r['query']['tokens'][type + 'token']
+        return token
+
     def get_username(self):
         try:
             params = {
diff --git a/backend/celery/tasks.py b/backend/celery/tasks.py
index 86e5a7a..f8a1cab 100644
--- a/backend/celery/tasks.py
+++ b/backend/celery/tasks.py
@@ -39,6 +39,7 @@
     for project in preload_projects:
         obj['wiki'] = wikis[project]
         watchlistgetter.delay(obj)
+        notificationgetter.delay(obj)
 
     db = MySQLdb.connect(
         host='centralauth.labsdb',
@@ -88,6 +89,7 @@
             if result and int(result[0]) >= 1:
                 obj['wiki'] = wiki
                 watchlistgetter.delay(obj)
+                notificationgetter.delay(obj)
 
     db.close()
 
@@ -130,14 +132,14 @@
         for item in response['watchlist']:
             item['project'] = obj['wiki']['dbname']
             item['projecturl'] = obj['wiki']['url']
+            item['projectgroup'] = obj['wiki']['group']
+            item['projectlang'] = obj['wiki']['lang']
+            item['projectlangname'] = obj['wiki']['langname']
 
             if 'commenthidden' in item:
                 item['parsedcomment'] = "<s>edit summary removed</s>"
             item['parsedcomment'] = fix_urls(item['parsedcomment'],
                                              obj['wiki']['url'])
-            item['projectgroup'] = obj['wiki']['group']
-            item['projectlang'] = obj['wiki']['lang']
-            item['projectlangname'] = obj['wiki']['langname']
             if 'bot' in item:
                 item['bot'] = "b"
             if 'minor' in item:
@@ -156,5 +158,71 @@
             mw.publish(message)
 
 
+@app.task
+def notificationgetter(obj):
+    """
+    Get the echo notifications for a wiki
+    :param obj: dict with wiki and connection information
+    """
+    project = obj['wiki']['dbname']
+
+    # Now, accessing the API on behalf of a user
+    logger.info("Reading notifications for  " + project)
+    mw = MediaWiki(host=obj['wiki']['url'],
+                   access_token=obj['access_token'],
+                   redis_channel=obj['redis_channel'])
+    params = {
+        'action': "query",
+        'meta': "notifications",
+        'notprop': "list",
+        'notformat': "html",
+        'notalertunreadfirst': "",
+        'notmessagecontinue': ""
+        }
+    response = mw.query(params)
+
+    result = response['query']['notifications']['list']
+    if not result:
+        return
+
+    event = {
+        'msgtype': 'notification',
+        'project': obj['wiki']['dbname'],
+        'projecturl': obj['wiki']['url'],
+        'projectgroup': obj['wiki']['group'],
+        'projectlang': obj['wiki']['lang'],
+        'projectlangname': obj['wiki']['langname']
+    }
+    for item in result.values():
+        if 'read' in item:
+            continue
+
+        event['id'] = item['id']
+        # random id
+        event['uuid'] = uuid4().hex[:8]
+
+        event['comment'] = fix_urls(item['*'], obj['wiki']['url'])
+        event['timestamp'] = item['timestamp']['utcunix']
+
+        mw.publish(event)
+
+
+@app.task
+def notifications_mark_read(obj):
+    """
+    Mark echo notifications as read
+    """
+    mw = MediaWiki(access_token=obj['access_token'])
+    wikis = mw.get_wikis()
+    params = {'action': "echomarkread"}
+
+    for project, notifications in obj['notifications'].iteritems():
+        projecturl = wikis[project]['url']
+        mw = MediaWiki(host=projecturl, access_token=obj['access_token'])
+
+        payload = {'list': notifications}
+        mw.post(params, payload)
+
+
 if __name__ == '__main__':
     app.start()
diff --git a/backend/server/__init__.py b/backend/server/__init__.py
index 79ee85c..29979d9 100644
--- a/backend/server/__init__.py
+++ b/backend/server/__init__.py
@@ -56,6 +56,9 @@
             data['redis_channel'] = redis_channel
             celery_app.send_task('backend.celery.tasks.initial_task',
                                  (data, ), expires=60)
+        elif data[u'action'] == u'notifications_mark_read':
+            
celery_app.send_task('backend.celery.tasks.notifications_mark_read',
+                                 (data, ), expires=60)
 
 
 class NoChacheStaticFileHandler(StaticFileHandler):
diff --git a/frontend/src/app/index.css b/frontend/src/app/index.css
index 6434707..3ec86fa 100644
--- a/frontend/src/app/index.css
+++ b/frontend/src/app/index.css
@@ -110,12 +110,14 @@
 .watchlist-list-item-oneline {
   min-height: 32px;
 }
+
 watchlist-entry {
   overflow: hidden;
   width: 100%;
 }
 
-watchlist-entry a{
+watchlist-entry a,
+.notifications a{
   color:#0645ad;
 }
 
@@ -123,7 +125,12 @@
   min-width: 8em;
 }
 
-watchlist-entry a.project {
+.notifications .left {
+  min-width: 6em;
+}
+
+watchlist-entry a.project,
+.notifications a.project{
   color: rgba(0, 0, 0, 0.87);
 }
 
@@ -135,7 +142,9 @@
   outline:none;
 }
 
-#watchlist:last-child md-divider {
+.watchlist:last-child md-divider,
+.notifications:last-child md-divider
+{
   border-top: 0;
 }
 
@@ -156,11 +165,49 @@
   font-weight: bold;
 }
 
+
+/** Styles imported from MediaWiki CSS **/
 .mw-title
 {
   font-weight: bold;
 }
 
+.mw-echo-notification{
+  list-style: none none;
+}
+.mw-echo-notifications{
+  margin:0;
+  padding:0;
+  list-style: none none;
+  overflow:auto;
+  background-color: rgb(238, 238,238);
+}
+.mw-echo-notification-wrapper {
+  display: block;
+  background-color: rgb(241, 241, 241);
+  border-bottom: 1px solid rgb(221, 221, 221);
+  padding: 15px 40px 10px 10px;
+  white-space: normal;
+  font-size: 13px;
+  line-height: 16px;
+  color: inherit;
+  text-decoration: inherit;
+}
+.mw-echo-icon {
+  width: 30px;
+  height: 30px;
+  float: left;
+  margin-right: 10px;
+  margin-left: 10px;
+}
+.mw-echo-content {
+  font-size: 13px;
+  font-weight:normal;
+  line-height: 16px;
+  overflow: hidden;
+}
+
+
 /** material design icons **/
 @font-face {
   font-family: 'Material Icons';
diff --git a/frontend/src/app/index.js b/frontend/src/app/index.js
index aa7d01a..ef185a8 100644
--- a/frontend/src/app/index.js
+++ b/frontend/src/app/index.js
@@ -54,6 +54,7 @@
   if ($location.host() === 'localhost') { // debug – use tools backend when 
developing
     sockjsUrl = 'https://tools.wmflabs.org/crosswatch/sockjs'
   }
+
   return socketFactory({
     url: sockjsUrl
   });
diff --git a/frontend/src/app/main/main.html b/frontend/src/app/main/main.html
index cac7489..42ff85a 100644
--- a/frontend/src/app/main/main.html
+++ b/frontend/src/app/main/main.html
@@ -1,6 +1,7 @@
 <header ng-include="'components/navbar/navbar.html'"></header>
 <div layout="column" flex class="md-padding" role="main">
   <div ng-include="'components/settings/settings.html'"></div>
+  <div ng-include="'components/notifications/notifications.html'"></div>
   <div ng-include="'components/watchlist/watchlist.html'"></div>
 </div>
 
diff --git a/frontend/src/app/runBlock.js b/frontend/src/app/runBlock.js
index 0965138..f52f1de 100644
--- a/frontend/src/app/runBlock.js
+++ b/frontend/src/app/runBlock.js
@@ -56,6 +56,8 @@
     var data = angular.fromJson(msg.data);
     if (data.msgtype === 'watchlist') {
       dataService.addWatchlistEntries(data.entires);
+    } else if (data.msgtype === 'notification') {
+      dataService.addNotificationEntries(data)
     } else if (data.msgtype === 'loginerror') {
       $log.error('login failed!');
 
diff --git a/frontend/src/app/services.js b/frontend/src/app/services.js
index bcb7c83..8fcd7eb 100644
--- a/frontend/src/app/services.js
+++ b/frontend/src/app/services.js
@@ -50,6 +50,12 @@
   vm.watchlist.loading = true;
 
   /**
+   * Array that contains new echo notifications.
+   * @type {Array}
+   */
+  vm.notifications = [];
+
+  /**
    * Initialize user settings
    */
   vm.defaultconfig = {
@@ -163,6 +169,35 @@
   };
 
   /**
+   * Process a new echo entry.
+   * @param entry
+   */
+  vm.addNotificationEntries = function (entry) {
+    vm.notifications.push(entry);
+  };
+
+  /**
+   * Mark all notifications as read.
+   */
+  vm.markNotificationsRead = function () {
+    var notifications = {};
+    for (var i=0; i<vm.notifications.length; i++) {
+      var not = vm.notifications[i];
+      if (!notifications.hasOwnProperty(not.project)) {
+        notifications[not.project] = [];
+      }
+
+      notifications[not.project].push(not.id);
+    }
+    var query = {
+      action: 'notifications_mark_read',
+      access_token: authService.tokens(),
+      notifications: notifications
+    };
+    socket.send(angular.toJson(query));
+  };
+
+  /**
    * Save user config to local storage
    */
   vm.saveConfig = function() {
@@ -193,6 +228,34 @@
       }
     }
   };
+
+  vm.icons = {};
+  vm.icons['wikibooks']   = 
"//upload.wikimedia.org/wikipedia/commons/f/fa/Wikibooks-logo.svg";
+  vm.icons['wiktionary']  = 
"//upload.wikimedia.org/wikipedia/commons/e/ef/Wikitionary.svg";
+  vm.icons['wikiquote']   = 
"//upload.wikimedia.org/wikipedia/commons/f/fa/Wikiquote-logo.svg";
+  vm.icons['wikipedia']   = 
"//upload.wikimedia.org/wikipedia/commons/8/80/Wikipedia-logo-v2.svg";
+  vm.icons['wikinews']    = 
"//upload.wikimedia.org/wikipedia/commons/2/24/Wikinews-logo.svg";
+  vm.icons['wikivoyage']  = 
"//upload.wikimedia.org/wikipedia/commons/8/8a/Wikivoyage-logo.svg";
+  vm.icons['wikisource']  = 
"//upload.wikimedia.org/wikipedia/commons/4/4c/Wikisource-logo.svg";
+  vm.icons['wikiversity'] = 
"//upload.wikimedia.org/wikipedia/commons/9/91/Wikiversity-logo.svg";
+  vm.icons['foundation']  = 
"//upload.wikimedia.org/wikipedia/commons/c/c4/Wikimedia_Foundation_RGB_logo_with_text.svg";
+  vm.icons['mediawiki']   = 
"//upload.wikimedia.org/wikipedia/commons/3/3d/Mediawiki-logo.png";
+  vm.icons['meta']        = 
"//upload.wikimedia.org/wikipedia/commons/7/75/Wikimedia_Community_Logo.svg";
+  vm.icons['wikidata']    = 
"//upload.wikimedia.org/wikipedia/commons/f/ff/Wikidata-logo.svg";
+  vm.icons['commons']     = 
"//upload.wikimedia.org/wikipedia/commons/4/4a/Commons-logo.svg";
+  vm.icons['species']     = 
"//upload.wikimedia.org/wikipedia/en/b/bf/Wikispecies-logo-35px.png";
+  vm.icons['incubator']   = 
"//upload.wikimedia.org/wikipedia/commons/e/e3/Incubator-logo.svg";
+  vm.icons['test']        = 
"//upload.wikimedia.org/wikipedia/commons/4/4a/Wikipedia_logo_v2_%28black%29.svg";
+
+  vm.flags = ["ad", "ae", "af", "ag", "ai", "al", "am", "an", "ao", "ar", 
"as", "at", "au", "aw", "ax", "az", "ba", "bb", "bd", "be", "bf", "bg", "bh", 
"bi", "bj", "bm", "bn", "bo", "br", "bs", "bt", "bv", "bw", "by", "bz", "ca", 
"cc", "cd", "cf", "cg", "ch", "ci", "ck", "cl", "cm", "cn", "co", "cr", "cs", 
"cu", "cv", "cx", "cy", "cz", "da", "de", "dj", "dk", "dm", "do", "dz", "ec", 
"ee", "eg", "eh", "en", "er", "es", "et", "fam", "fi", "fj", "fk", "fm", "fo", 
"fr", "ga", "gb", "gd", "ge", "gf", "gh", "gi", "gl", "gm", "gn", "gp", "gq", 
"gr", "gs", "gt", "gu", "gw", "gy", "he", "hk", "hm", "hn", "hr", "ht", "hu", 
"id", "ie", "il", "in", "io", "iq", "ir", "is", "it", "jm", "jo", "jp", "ke", 
"kg", "kh", "ki", "km", "kn", "kp", "kr", "kw", "ky", "kz", "la", "lb", "lc", 
"li", "lk", "lr", "ls", "lt", "lu", "lv", "ly", "ma", "mc", "md", "me", "mg", 
"mh", "mk", "ml", "mm", "mn", "mo", "mp", "mq", "mr", "ms", "mt", "mu", "mv", 
"mw", "mx", "my", "mz", "na", "nc", "ne", "nf", "ng", "ni", "nl", "no", "np", 
"nr", "nu", "nz", "om", "pa", "pe", "pf", "pg", "ph", "pk", "pl", "pm", "pn", 
"pr", "ps", "pt", "pw", "py", "qa", "re", "ro", "rs", "ru", "rw", "sa", "sb", 
"scotland", "sc", "sd", "se", "sg", "sh", "si", "sj", "sk", "sl", "sm", "sn", 
"so", "sr", "st", "sv", "sy", "sz", "tc", "td", "tf", "tg", "th", "tj", "tk", 
"tl", "tm", "tn", "to", "tr", "tt", "tv", "tw", "tz", "ua", "ug", "um", "us", 
"uy", "uz", "va", "vc", "ve", "vg", "vi", "vn", "vu", "wales", "wf", "ws", 
"ye", "yt", "za", "zh", "zm", "zw"];
+
+  vm.flagurl = function (lang) {
+    if (vm.flags.indexOf(lang) >= 0) {
+      return "assets/images/flags/png/" + lang + ".png";
+    } else {
+      return false;
+    }
+  };
 }
 
 // Debounce function
diff --git a/frontend/src/components/notifications/notifications.controller.js 
b/frontend/src/components/notifications/notifications.controller.js
new file mode 100644
index 0000000..c280f77
--- /dev/null
+++ b/frontend/src/components/notifications/notifications.controller.js
@@ -0,0 +1,11 @@
+'use strict';
+
+angular.module('crosswatch')
+  .controller('NotificationsCtrl', function ($translate, $log, dataService) {
+    var vm = this;
+    vm.icons = dataService.icons;
+    vm.flagurl = dataService.flagurl;
+    vm.notifications = dataService.notifications;
+    vm.config = dataService.config;
+    vm.markNotificationsRead = dataService.markNotificationsRead;
+  });
diff --git a/frontend/src/components/notifications/notifications.html 
b/frontend/src/components/notifications/notifications.html
new file mode 100644
index 0000000..012acba
--- /dev/null
+++ b/frontend/src/components/notifications/notifications.html
@@ -0,0 +1,32 @@
+<div ng-controller="NotificationsCtrl as ctrl" layout="row" 
layout-align="center center" class="md-padding">
+  <md-whiteframe class="md-whiteframe-z5 whitebox" 
ng-if="ctrl.notifications.length">
+    <md-toolbar class='md-small-tall'>
+      <div class="md-toolbar-tools">
+        <h1>
+          <span translate="NOTIFICATIONS"></span>
+        </h1>
+        <div flex></div>
+        <md-button class="md-raised md-accent" translate="MARKALLREAD"
+                   ng-click="read = !read; ctrl.markNotificationsRead()" 
ng-disabled="read">
+        </md-button>
+      </div>
+    </md-toolbar>
+    <md-list>
+      <md-list-item layout="row" class="notifications" style="padding: 6px 
16px;"
+                    ng-repeat="event in ctrl.notifications | 
orderBy:'-timestamp' track by event.uuid">
+        <div class="left">
+          <a href="{{::event.projecturl}}/wiki/Special:Notifications" 
class="project"
+             title="{{::event.project}}" target="_blank">
+            <span ng-if="::event.projectlang">
+              <span ng-if="!ctrl.config.flagsenable || 
!ctrl.flagurl(event.projectlang)">{{::event.projectlangname}}&#32;</span>
+              <img ng-if="ctrl.config.flagsenable && 
ctrl.flagurl(event.projectlang)" height="12px" 
ng-src="{{::ctrl.flagurl(event.projectlang)}}">
+            </span>
+            <img height="16px" ng-src="{{::ctrl.icons[event.projectgroup]}}" 
alt="{{::event.projectgroup}}">
+          </a>
+        </div>
+        <div ng-bind-html="::event.comment"></div>
+        <md-divider></md-divider>
+      </md-list-item>
+    </md-list>
+  </md-whiteframe>
+</div>
diff --git a/frontend/src/components/watchlist/watchlist.controller.js 
b/frontend/src/components/watchlist/watchlist.controller.js
index 4461c87..0e967de 100644
--- a/frontend/src/components/watchlist/watchlist.controller.js
+++ b/frontend/src/components/watchlist/watchlist.controller.js
@@ -1,41 +1,15 @@
 'use strict';
 
 angular.module('crosswatch')
-  .controller('WatchlistCtrl', function ($translate, socket, $log, 
dataService, $rootScope, $timeout, $scope) {
+  .controller('WatchlistCtrl', function ($log, dataService) {
     var vm = this;
+    vm.icons = dataService.icons;
+    vm.flagurl = dataService.flagurl;
     vm.watchlist = dataService.watchlist;
     vm.config = dataService.config;
     vm.moreWatchlistEntries = dataService.moreWatchlistEntries;
 
     vm.search = function (text) {
       dataService.filterWatchlist(text);
-    };
-
-    vm.icons = {};
-    vm.icons['wikibooks']   = 
"//upload.wikimedia.org/wikipedia/commons/f/fa/Wikibooks-logo.svg";
-    vm.icons['wiktionary']  = 
"//upload.wikimedia.org/wikipedia/commons/e/ef/Wikitionary.svg";
-    vm.icons['wikiquote']   = 
"//upload.wikimedia.org/wikipedia/commons/f/fa/Wikiquote-logo.svg";
-    vm.icons['wikipedia']   = 
"//upload.wikimedia.org/wikipedia/commons/8/80/Wikipedia-logo-v2.svg";
-    vm.icons['wikinews']    = 
"//upload.wikimedia.org/wikipedia/commons/2/24/Wikinews-logo.svg";
-    vm.icons['wikivoyage']  = 
"//upload.wikimedia.org/wikipedia/commons/8/8a/Wikivoyage-logo.svg";
-    vm.icons['wikisource']  = 
"//upload.wikimedia.org/wikipedia/commons/4/4c/Wikisource-logo.svg";
-    vm.icons['wikiversity'] = 
"//upload.wikimedia.org/wikipedia/commons/9/91/Wikiversity-logo.svg";
-    vm.icons['foundation']  = 
"//upload.wikimedia.org/wikipedia/commons/c/c4/Wikimedia_Foundation_RGB_logo_with_text.svg";
-    vm.icons['mediawiki']   = 
"//upload.wikimedia.org/wikipedia/commons/3/3d/Mediawiki-logo.png";
-    vm.icons['meta']        = 
"//upload.wikimedia.org/wikipedia/commons/7/75/Wikimedia_Community_Logo.svg";
-    vm.icons['wikidata']    = 
"//upload.wikimedia.org/wikipedia/commons/f/ff/Wikidata-logo.svg";
-    vm.icons['commons']     = 
"//upload.wikimedia.org/wikipedia/commons/4/4a/Commons-logo.svg";
-    vm.icons['species']     = 
"//upload.wikimedia.org/wikipedia/en/b/bf/Wikispecies-logo-35px.png";
-    vm.icons['incubator']   = 
"//upload.wikimedia.org/wikipedia/commons/e/e3/Incubator-logo.svg";
-    vm.icons['test']        = 
"//upload.wikimedia.org/wikipedia/commons/4/4a/Wikipedia_logo_v2_%28black%29.svg";
-
-    vm.flags = ["ad", "ae", "af", "ag", "ai", "al", "am", "an", "ao", "ar", 
"as", "at", "au", "aw", "ax", "az", "ba", "bb", "bd", "be", "bf", "bg", "bh", 
"bi", "bj", "bm", "bn", "bo", "br", "bs", "bt", "bv", "bw", "by", "bz", "ca", 
"cc", "cd", "cf", "cg", "ch", "ci", "ck", "cl", "cm", "cn", "co", "cr", "cs", 
"cu", "cv", "cx", "cy", "cz", "da", "de", "dj", "dk", "dm", "do", "dz", "ec", 
"ee", "eg", "eh", "en", "er", "es", "et", "fam", "fi", "fj", "fk", "fm", "fo", 
"fr", "ga", "gb", "gd", "ge", "gf", "gh", "gi", "gl", "gm", "gn", "gp", "gq", 
"gr", "gs", "gt", "gu", "gw", "gy", "he", "hk", "hm", "hn", "hr", "ht", "hu", 
"id", "ie", "il", "in", "io", "iq", "ir", "is", "it", "jm", "jo", "jp", "ke", 
"kg", "kh", "ki", "km", "kn", "kp", "kr", "kw", "ky", "kz", "la", "lb", "lc", 
"li", "lk", "lr", "ls", "lt", "lu", "lv", "ly", "ma", "mc", "md", "me", "mg", 
"mh", "mk", "ml", "mm", "mn", "mo", "mp", "mq", "mr", "ms", "mt", "mu", "mv", 
"mw", "mx", "my", "mz", "na", "nc", "ne", "nf", "ng", "ni", "nl", "no", "np", 
"nr", "nu", "nz", "om", "pa", "pe", "pf", "pg", "ph", "pk", "pl", "pm", "pn", 
"pr", "ps", "pt", "pw", "py", "qa", "re", "ro", "rs", "ru", "rw", "sa", "sb", 
"scotland", "sc", "sd", "se", "sg", "sh", "si", "sj", "sk", "sl", "sm", "sn", 
"so", "sr", "st", "sv", "sy", "sz", "tc", "td", "tf", "tg", "th", "tj", "tk", 
"tl", "tm", "tn", "to", "tr", "tt", "tv", "tw", "tz", "ua", "ug", "um", "us", 
"uy", "uz", "va", "vc", "ve", "vg", "vi", "vn", "vu", "wales", "wf", "ws", 
"ye", "yt", "za", "zh", "zm", "zw"];
-
-    vm.flagurl = function (lang) {
-      if (vm.flags.indexOf(lang) >= 0) {
-        return "assets/images/flags/png/" + lang + ".png";
-      } else {
-        return false;
-      }
     };
   });
diff --git a/frontend/src/components/watchlist/watchlist.html 
b/frontend/src/components/watchlist/watchlist.html
index 2d164d5..0380ad8 100644
--- a/frontend/src/components/watchlist/watchlist.html
+++ b/frontend/src/components/watchlist/watchlist.html
@@ -24,7 +24,8 @@
     </md-list>
     <md-list infinite-scroll="ctrl.moreWatchlistEntries()" 
infinite-scroll-immediate-check="false"
              infinite-scroll-distance="1">
-      <md-list-item layout="row" id="watchlist" 
ng-class="(ctrl.config.oneline) ? 'watchlist-list-item-oneline' : ''"
+      <md-list-item layout="row" id="watchlist" class="watchlist"
+                    ng-class="(ctrl.config.oneline) ? 
'watchlist-list-item-oneline' : ''"
                     ng-repeat="event in ctrl.watchlist.active track by 
event.id">
         <watchlist-entry ng-click="event.clicked = !event.clicked" 
md-ink-ripple></watchlist-entry>
         <md-divider></md-divider>
diff --git a/frontend/src/i18n/locale-en.json b/frontend/src/i18n/locale-en.json
index 63b8ce4..6b6b241 100644
--- a/frontend/src/i18n/locale-en.json
+++ b/frontend/src/i18n/locale-en.json
@@ -66,5 +66,7 @@
   "NS_OTHER": "Other namespaces",
   "HISTORY": "history",
   "CONTRIBS": "contribs",
-  "ONELINE": "Traditional watchlist layout"
+  "ONELINE": "Traditional watchlist layout",
+  "NOTIFICATIONS": "Notifications",
+  "MARKALLREAD": "mark all as read"
 }

-- 
To view, visit https://gerrit.wikimedia.org/r/222222
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: merged
Gerrit-Change-Id: Ifb06262bfc6916884301e028675f84daee7085be
Gerrit-PatchSet: 2
Gerrit-Project: labs/tools/crosswatch
Gerrit-Branch: master
Gerrit-Owner: Sitic <jan.leb...@online.de>
Gerrit-Reviewer: Siebrand <siebr...@kitano.nl>
Gerrit-Reviewer: Sitic <jan.leb...@online.de>

_______________________________________________
MediaWiki-commits mailing list
MediaWiki-commits@lists.wikimedia.org
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to