Sitic has uploaded a new change for review.
https://gerrit.wikimedia.org/r/222222
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, 271 insertions(+), 45 deletions(-)
git pull ssh://gerrit.wikimedia.org:29418/labs/tools/crosswatch
refs/changes/22/222222/1
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..d51fee4 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,68 @@
mw.publish(message)
[email protected]
+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)
+
+
[email protected]
+def notifications_mark_read(obj):
+ 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..626e68f 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;
}
@@ -161,6 +170,42 @@
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..e839e98 100644
--- a/frontend/src/app/index.js
+++ b/frontend/src/app/index.js
@@ -51,6 +51,7 @@
function socketFactory (socketFactory, $browser, $location) {
var baseHref = $browser.baseHref();
var sockjsUrl = baseHref + 'sockjs';
+
if ($location.host() === 'localhost') { // debug – use tools backend when
developing
sockjsUrl = 'https://tools.wmflabs.org/crosswatch/sockjs'
}
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}} </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: newchange
Gerrit-Change-Id: Ifb06262bfc6916884301e028675f84daee7085be
Gerrit-PatchSet: 1
Gerrit-Project: labs/tools/crosswatch
Gerrit-Branch: master
Gerrit-Owner: Sitic <[email protected]>
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits