Sitic has submitted this change and it was merged. Change subject: Redesign, switch from bootstrap to material design ......................................................................
Redesign, switch from bootstrap to material design Started as an experiment, looks much better. Refactored frontend code. Has preformance issues on IE (barely usable). Bug: T100341 Change-Id: I3fa2dba3aac6da73e2f6e8fc03169d4f1e71feeb --- M backend/server/__init__.py M frontend/bower.json M frontend/gulp/build.js A frontend/src/app/filters.js M frontend/src/app/index.css M frontend/src/app/index.js M frontend/src/app/main/main.controller.js M frontend/src/app/main/main.html A frontend/src/app/runBlock.js A frontend/src/app/services.js A frontend/src/app/welcome/welcome.html M frontend/src/assets/images/flags/readme.txt A frontend/src/components/footer/footer.html M frontend/src/components/navbar/navbar.controller.js M frontend/src/components/navbar/navbar.html A frontend/src/components/settings/settings.controller.js A frontend/src/components/settings/settings.html M frontend/src/components/watchlist/entry.directive.js M frontend/src/components/watchlist/watchlist.controller.js M frontend/src/components/watchlist/watchlist.html M frontend/src/i18n/locale-de.json M frontend/src/i18n/locale-en.json M frontend/src/i18n/locale-pt.json M frontend/src/index.html 24 files changed, 687 insertions(+), 731 deletions(-) Approvals: Sitic: Verified; Looks good to me, approved diff --git a/backend/server/__init__.py b/backend/server/__init__.py index c3fe141..00edefc 100644 --- a/backend/server/__init__.py +++ b/backend/server/__init__.py @@ -85,7 +85,7 @@ index_path = os.path.join(pwd_path, 'public/' + toolname + '/index.html') static_handlers = [ (r"/()", StaticFileHandler, {"path": index_path}), - (r"/" + toolname + r"/()", StaticFileHandler, {"path": index_path}), + (r"/" + toolname + r"/\w*()", StaticFileHandler, {"path": index_path}), (r"/" + toolname, RedirectHandler, {"url": "/" + toolname + "/"}), (r"/(.*)", StaticFileHandler, {"path": static_path}) ] diff --git a/frontend/bower.json b/frontend/bower.json index fd3f0b0..dd3f642 100644 --- a/frontend/bower.json +++ b/frontend/bower.json @@ -7,16 +7,15 @@ "angular-route": "~1.4.0", "angular-sanitize": "~1.4.0", "angular-sockjs": "0.0.x", - "angular-strap": "~2.2.4", "angular-translate": "~2.7.0", "angular-translate-loader-static-files": "~2.7.0", "angular-translate-storage": "~0.0.3", - "fontawesome": "~4.3.0", "sockjs-client": "~1.0.0", "angular-moment": "0.x.x", "angular-local-storage": "0.2.x", - "bootstrap": "~3.3.4", - "angular-motion": "~0.4.2" + "angular-motion": "~0.4.2", + "angular-material": "~0.10.0", + "material-design-icons": "~2.0.0" }, "devDependencies": { "angular-mocks": "~1.4.0" @@ -27,6 +26,9 @@ "overrides": { "moment": { "main": "min/moment-with-locales.min.js" + }, + "material-design-icons": { + "main": "./iconfont/*" } } } diff --git a/frontend/gulp/build.js b/frontend/gulp/build.js index 1e43438..3013bbe 100644 --- a/frontend/gulp/build.js +++ b/frontend/gulp/build.js @@ -84,7 +84,7 @@ gulp.task('fonts', function () { return gulp.src($.mainBowerFiles()) - .pipe($.filter('**/*.{eot,svg,ttf,woff}')) + .pipe($.filter('**/*.{eot,svg,ttf,woff,woff2}')) .pipe($.flatten()) .pipe(gulp.dest(paths.dist + '/fonts/')); }); diff --git a/frontend/src/app/filters.js b/frontend/src/app/filters.js new file mode 100644 index 0000000..b057e63 --- /dev/null +++ b/frontend/src/app/filters.js @@ -0,0 +1,66 @@ +'use strict'; +angular + .module('crosswatch') + .filter('urlEncode', urlEncodeFilter) + .filter('list', listFilter) + .filter('projects', projectsFilter) + .filter('editflags', editflagsFilter) +; + +/** + * Filter for encoding strings for use in URL query strings + */ +function urlEncodeFilter () { + return window.encodeURIComponent; +} + +/** + * Formats lists: + * ["a", "b", "c"] -> "(a, b, c)" + * [] -> "(–)" + */ +function listFilter () { + return function (list) { + list = list || []; + var result = list.join(', '); + if (result.length === 0) { + result = "–"; + } + result = "(" + result + ")"; + + return result; + } +} + +/** + * Filters watchlist based on selected / blacklisted wikis + */ +function projectsFilter () { + return function (items, projects) { + return items.filter(filter, projects) + }; + + function filter (item) { + return (this.indexOf(item.project) > -1); + } +} + +/** + * Filters watchlist based on editflags (minor, bot, registered) + */ +function editflagsFilter () { + return function (items, flags) { + return items.filter(filter, flags) + }; + + function filter (item) { + if (item.type === 'log') { + return true; + } + var minor = this.minor || !item.minor; + var bot = this.bot || !item.bot; + var anon = this.anon || !(item.anon === ""); + var registered = this.registered || !(item.userid !== 0); + return (minor && bot && anon && registered); + } +} diff --git a/frontend/src/app/index.css b/frontend/src/app/index.css index 57f3fd6..e39a44b 100644 --- a/frontend/src/app/index.css +++ b/frontend/src/app/index.css @@ -1,92 +1,109 @@ -.browsehappy { - margin: 0.2em 0; - background: #ccc; - color: #000; - padding: 0.2em 0; +/* +html { + font-size: 62.5%; + line-height: 1.4; + background-color: #edecec; } - +*/ body { - padding:10px + background-color: #edecec; + font-family: 'Roboto', sans-serif; + /* font-size: 1.6rem; */ } -.navbar{ - margin-bottom:30px +div[role=main] { + background-color: #edecec; + min-height: calc(100vh - 165px); + } + +#welcome div { + background-color: #edecec; + min-height: calc(100vh - 180px); + height: 0; /* IE fix https://stackoverflow.com/questions/19371626 */ } - -.jumbotron { - padding: 60px; - margin-bottom: 30px; - background-color: gainsboro; - border-radius: 6px; - text-align: center; +div[role=footer] { + background-color: #edecec; } -.mw-echo-notifications-badge { - min-width: 7px; - border-radius: 2px 2px 2px 2px; - padding: 0.25em 0.45em 0.2em; - margin-left: -4px; - text-align: center; - background-color: rgb(210, 210, 210); - font-weight: bold; +.md-content { + background-color: #edecec; +} + +#projects md-select { + padding-left: 20px; + padding-right: 20px; +} + +md-input-container.md-default-theme .md-input { + color: rgba(255,255,255,0.87); + border-color: rgba(255,255,255,0.87); + margin-top: 24px; +} +/*** Browser fixes ***/ +::-webkit-input-placeholder { /* WebKit browsers */ + color: rgba(255,255,255,0.87); +} +::-moz-placeholder { /* Mozilla Firefox 19+ */ + color: rgba(255,255,255,0.87); + opacity: 1; +} +:-ms-input-placeholder { /* Internet Explorer 10+ */ + color: rgba(255,255,255,0.87); +} + +md-divider[role=vertical] { + border-top: 0; + border-left: 1px solid; + border-left-color: rgba(0, 0, 0, 0.12); +} + +/* Should be fixed in ngMaterial 0.10 +md-select.md-default-theme:not([disabled]):focus .md-select-label { color: white; - cursor: pointer; +} +*/ + +a { + /*color: #106CC8;*/ + color: #0645ad; text-decoration: none; + font-weight: 400; + border-bottom: none; + -webkit-transition: border-bottom 0.25s; + -moz-transition: border-bottom 0.25s; + -ms-transition: border-bottom 0.25s; + -o-transition: border-bottom 0.25s; + transition: border-bottom 0.25s; } -.mw-echo-overlay { - position: absolute; - top: 30px; - left: 0; - border: 1px solid silver; - background-color: rgb(255, 255, 255); - width: 450px; - min-height: 2em; - padding: 0; - color: rgb(109, 109, 109); - z-index: 100; - box-shadow: 0 3px 8px rgba(50, 50, 50, 0.35); +a:hover, a:focus { + /*border-bottom: 1px solid #4054B2;*/ + border-bottom: 1px solid #0645ad; } -.mw-echo-notification{ - list-style: none none; +a.md-button { + border-bottom: none; } -.mw-echo-notifications{ - margin:0; - padding:0; - list-style: none none; - overflow:auto; - background-color: rgb(238, 238,238); + +.text-success{ + color:#3c763d } -.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; +.text-danger{ + color:#a94442 } -.mw-echo-icon { - width: 30px; - height: 30px; - float: left; - margin-right: 10px; - margin-left: 10px; +.text-muted{ + color:#777 } -.mw-echo-content { - font-size: 13px; - font-weight:normal; - line-height: 16px; - overflow: hidden; + +.whitebox{ + background-color: white; + width: 100%; + max-width: 1200px; } .watchlist{ - list-style: none none; - padding-bottom: 8px; overflow: hidden; + width: 100%; } .watchlist a{ @@ -96,16 +113,19 @@ .watchlist .left { float: left; width: 125px; -} - -.watchlist .right { - width: auto; overflow: hidden; - word-break: break-all; } .watchlist a.project { - color: #333; + color: rgba(0, 0, 0, 0.87); +} + +.watchlist .right { + overflow: hidden; +} + +#watchlist:last-child md-divider { + border-top: 0; } span.comment{ @@ -127,115 +147,70 @@ .mw-title { font-weight: bold; - color:#0645ad; } - -/* -a { - text-decoration: none; +/** material design icons **/ +@font-face { + font-family: 'Material Icons'; + font-style: normal; + font-weight: 400; + src: url(//tools.wmflabs.org/crosswatch/fonts/MaterialIcons-Regular.eot); /* For IE6-8 */ + src: local('Material Icons'), + local('MaterialIcons-Regular'), + url(//tools.wmflabs.org/crosswatch/fonts/MaterialIcons-Regular.woff2) format('woff2'), + url(//tools.wmflabs.org/crosswatch/fonts/MaterialIcons-Regular.woff) format('woff'), + url(//tools.wmflabs.org/crosswatch/fonts/MaterialIcons-Regular.ttf) format('truetype'); } -.container { - width: 1170px; - margin-right: auto; - margin-left: auto; - padding-left: 15px; - padding-right: 15px; -} - -.navbar { - position: relative; - min-height: 50px; - margin-bottom: 20px; - background-color: #222; -} - -.navbar > a { - float: left; - color: lightgrey; - padding: 15px 15px; - font-size: 18px; - line-height: 20px; - height: 50px; -} - -.navbar ul { - float: left; - padding: 0; - margin: 0; -} - -.navbar ul li { - float: left; - position: relative; - display: block; -} - -.navbar ul li a { - color: white; - position: relative; - display: block; - padding: 15px; - line-height: 20px; -} - -.navbar ul li.active a { - background-color: black; -} - -.jumbotron { - padding: 60px; - margin-bottom: 30px; - background-color: gainsboro; - border-radius: 6px; - text-align: center; -} - -.btn { +.material-icons { + font-family: 'Material Icons'; + font-weight: normal; + font-style: normal; + font-size: 24px; /* Preferred icon size */ display: inline-block; - text-align: center; - vertical-align: middle; - cursor: pointer; - border: 1px solid transparent; - color: white; - background-color: #5cb85c; - border-color: #4cae4c; - padding: 10px 16px; - font-size: 18px; - line-height: 1.33; - border-radius: 6px; + width: 1em; + height: 1em; + line-height: 1; + text-transform: none; + letter-spacing: normal; + word-wrap: normal; + + /* Support for all WebKit browsers. */ + -webkit-font-smoothing: antialiased; + /* Support for Safari and Chrome. */ + text-rendering: optimizeLegibility; + + /* Support for Firefox. */ + -moz-osx-font-smoothing: grayscale; + + /* Support for IE. */ + font-feature-settings: 'liga'; } -.col { - float: left; - width: 33.33%; - position: relative; - padding-left: 15px; - padding-right: 15px; - box-sizing: border-box; +.material-icons.md-18 { font-size: 18px; } +.material-icons.md-24 { font-size: 24px; } + +/*** IE fixes ***/ +.browsehappy { + margin: 0.2em 0; + background: #ccc; + color: #000; + padding: 0.2em 0; } -.col .thumbnail { - height: 200px; - border: 1px solid lightgrey; - border-radius: 4px; - margin-bottom: 20px; +a img{ + border: 0; } -.col .thumbnail .caption { - padding: 9px; +/* https://github.com/angular/material/issues/2213 */ +@media screen and (min-width:0\0) { + md-select .md-select-icon { + float: right; + } } -.col .thumbnail img.pull-right { - width: 50px; +/* https://github.com/angular/material/issues/2769 */ +_:-ms-fullscreen, :root /* IE11 */ md-select-label *:first-child{ + flex-basis: auto; + overflow: visible; } - -.pull-right { - float: right; -} - -hr { - clear: both; -} -*/ diff --git a/frontend/src/app/index.js b/frontend/src/app/index.js index 062d496..bc1a3e1 100644 --- a/frontend/src/app/index.js +++ b/frontend/src/app/index.js @@ -4,9 +4,9 @@ 'ngAnimate', 'ngSanitize', 'ngRoute', + 'ngMaterial', 'LocalStorageModule', 'angular-translate-storage', - 'mgcrea.ngStrap', 'pascalprecht.translate', 'bd.sockjs', 'angularMoment' @@ -15,13 +15,6 @@ .config(locationConfig) .config(storageConfig) .factory('socket', socketFactory) - .filter('urlEncode', urlEncodeFilter) - .filter('list', listFilter) - .filter('projects', projectsFilter) - .filter('editflags', editflagsFilter) - .service('authService', authService) - .service('dataService', dataService) - .run(runBlock) ; function routeConfig ($routeProvider) { @@ -30,6 +23,9 @@ templateUrl: 'app/main/main.html', controller: 'MainCtrl', controllerAs: 'ctrl' + }) + .when('/welcome', { + templateUrl: 'app/welcome/welcome.html' }) .otherwise({ redirectTo: '/' @@ -56,284 +52,7 @@ if ($location.host() === 'localhost') { // debug – use tools backend when developing sockjsUrl = 'https://tools.wmflabs.org/crosswatch/sockjs' } - return socketFactory({ url: sockjsUrl }); -} - -/** - * Filter for encoding strings for use in URL query strings - */ -function urlEncodeFilter () { - return window.encodeURIComponent; -} - -/** - * Formats lists: - * ["a", "b", "c"] -> "(a, b, c)" - * [] -> "(–)" - */ -function listFilter () { - return function (list) { - list = list || []; - var result = list.join(', '); - if (result.length === 0) { - result = "–"; - } - result = "(" + result + ")"; - - return result; - } -} - -/** - * Filters watchlist based on selected / blacklisted wikis - */ -function projectsFilter () { - return function (items, projects) { - return items.filter(filter, projects) - }; - - function filter (item) { - return (this.indexOf(item.project) > -1); - } -} - -/** - * Filters watchlist based on editflags (minor, bot, registered) - */ -function editflagsFilter () { - return function (items, flags) { - return items.filter(filter, flags) - }; - - function filter (item) { - if (item.type === 'log') { - return true; - } - var minor = this.minor || !item.minor; - var bot = this.bot || !item.bot; - var anon = this.anon || !(item.anon === ""); - var registered = this.registered || !(item.userid !== 0); - return (minor && bot && anon && registered); - } -} - -function authService (localStorageService, $rootScope, $log) { - $rootScope.$watch(function () { return localStorageService.cookie.get('user');}, function (newValue) { - if (newValue !== null) { - $log.info('Singed in as ' + newValue); - $rootScope.$emit('login', newValue); - } - }); - - this.tokens = function () { - return angular.fromJson(localStorageService.cookie.get('auth').replace(/\\054/g, ',')); - }; - - this.user = function () { - return localStorageService.cookie.get('user'); - }; - - this.isloggedin = function () { - return (localStorageService.cookie.get('auth') !== null); - }; - -} - -function dataService (socket, authService, localStorageService, $log) { - var vm = this; - - 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"]; - - /** - * Array that contains all watchlist entries. - * @type {Array} - */ - vm.watchlist = []; - - /** - * Initialize user settings - */ - vm.defaultconfig = { - /** - * true: show only latest change - * false: show all changes - */ - lastrevonly: false, - /** - * Timeperiod for which the watchlist is retrieved in days - */ - watchlistperiod: 1.5, - flagsenable: false, - /** - * Options to show minor, bot, anon and registered user edits - */ - editflags: false, - /** - * List of all known projects (wikis) - */ - projectsList: false, - /** - * List of selected projects (wikis) - */ - projectsSelected: false - }; - if (localStorageService.get('config') !== null) { - vm.config = localStorageService.get('config'); - vm.config.__proto__ = vm.defaultconfig; - } else { - vm.config = Object.create(vm.defaultconfig); - } - // Sadly no other way to do this right - vm.config.projectsList = vm.config.projectsList || []; - vm.config.projectsSelected = vm.config.projectsSelected || []; - vm.config.editflags = vm.config.editflags || { - minor: true, - bot: false, - anon: true, - registered: true - }; - - /** - * Process an array of new watchlist entries. - * @param entries - */ - vm.addWatchlistEntries = function (entries) { - vm.watchlist.push.apply(vm.watchlist, entries); - var project = entries[0].project; - if (vm.config.projectsList.indexOf(project) === -1) { - vm.config.projectsList.push(project); - vm.config.projectsSelected.push(project); - vm.saveConfig(); - } - }; - - /** - * Reset watchlist and query it again - */ - vm.resetWatchlist = function () { - vm.watchlist = []; - vm.queryWatchlist(); - vm.saveConfig(); - }; - - /** - * Save user config to local storage - */ - vm.saveConfig = function() { - localStorageService.set('config', vm.config); - }; - - /** - * If logged in, query watchlist. - */ - vm.queryWatchlist = function () { - if (authService.isloggedin()) { - var watchlistQuery = { - action: 'watchlist', - access_token: authService.tokens(), - watchlistperiod: vm.config.watchlistperiod, - allrev: !vm.config.lastrevonly, - projects: vm.config.projectsList - }; - try { - socket.send(angular.toJson(watchlistQuery)); - } catch(err) { - // INVALID_STATE_ERR when sockjs is not yet connected - if (err.message === 'InvalidStateError: The connection has not been established yet') { - $log.warn('Wachtlist queried before socketjs connected.'); - } else { - $log.error(err); - } - } - } - }; -} - -function runBlock (socket, $rootScope, dataService, $log, $alert, $timeout, $translate, amMoment, localStorageService) { - var connectionError = true; - - /** - * Set Moment.js language - * Strange bug, $translate.use() is sometimes undefined if loaded by angular-translate from local storage - */ - var lang = $translate.use(); - if (typeof lang === "undefined") { - lang = localStorageService.get('NG_TRANSLATE_LANG_KEY'); - $translate.use(lang); - } - amMoment.changeLocale(lang); - - /** - * Query watchlist on login. - * Not part of authService to prevent circular dependency. - */ - $rootScope.$on('login', function () { - dataService.queryWatchlist(); - $timeout(errorHandler, 15000); // show error if no watchlist after 15 secs - }); - - /** - * If logged in before sockjs connected, query watchlist again. - */ - socket.setHandler('open', function () { - $log.info('sockjs connected'); - dataService.queryWatchlist(); - }); - - /** - * Handle all sockjs incoming messages here. - */ - socket.setHandler('message', function (msg) { - msg = angular.fromJson(msg); - var data = angular.fromJson(msg.data); - if (data.msgtype === 'watchlist') { - dataService.addWatchlistEntries(data.entires); - } else if (data.msgtype === 'loginerror') { - $rootScope.$emit('error', 'loginerror'); - $log.error('login failed!'); - } else { - $log.error(data); - } - - if (connectionError) { - connectionError = false; - } - }); - - /** - * Show error message if no watchlist entries are retrieved. - */ - function errorHandler () { - if (connectionError) { - $log.warn('No websocket message after 15 seconds.'); - $alert({ - title: 'Error,', - content: 'no watchlist could be retrieved. This might be an internal server error.', - type: 'danger', - container: '#message-container', - show: true - }); - } - } } diff --git a/frontend/src/app/main/main.controller.js b/frontend/src/app/main/main.controller.js index dedac98..09f4719 100644 --- a/frontend/src/app/main/main.controller.js +++ b/frontend/src/app/main/main.controller.js @@ -1,56 +1,11 @@ 'use strict'; angular.module('crosswatch') - .controller('MainCtrl', function ($alert, socket, $rootScope, $translate, authService, $timeout, $log) { + .controller('MainCtrl', function ($rootScope, authService) { var vm = this; vm.loggedin = authService.isloggedin(); - showLoggedinAlert(); - - $alert({ - title: 'Caution,', - content: 'this tool is an early prototype. It\'s not ready for the prime time.', - type: 'warning', - container: '#message-container', - show: true - }); $rootScope.$on('login', function (event, msg) { vm.loggedin = true; - showLoggedinAlert(); }); - - $rootScope.$on('error', function (event, msg) { - if (msg === 'loginerror') { - $alert({ - title: '', - content: 'You must <a href="login" target="_blank">sign in</a> to save changes.', - type: 'danger', - container: '#message-container', - show: true - }); - } - }); - - $rootScope.$on('$translateChangeSuccess', function () { - if (authService.isloggedin() && (typeof vm.loginAlert !== "undefined")) { - vm.loginAlert.destroy(); - showLoggedinAlert(); - } - }); - - function showLoggedinAlert() { - if (!authService.isloggedin()) { - return; - } - vm.loginAlertDict = { - title: '', - type: 'success', - container: '#message-container', - show: true - }; - $translate('SIGNED_IN_MSG', { username: authService.user() }).then(function (msg) { - vm.loginAlertDict.content = msg; - vm.loginAlert = $alert(vm.loginAlertDict); - }); - } }); diff --git a/frontend/src/app/main/main.html b/frontend/src/app/main/main.html index 2c9d437..cac7489 100644 --- a/frontend/src/app/main/main.html +++ b/frontend/src/app/main/main.html @@ -1,48 +1,7 @@ -<div class="container"> - <div ng-include="'components/navbar/navbar.html'"></div> - - <!-- - <div ng-if="ctrl.loggedin"> - <div class="bs-header" id="content"> - <div class="container"> - <div class="col-md-9"> - <h1>crosswatch</h1> - <p class="lead" translate="DESCRIPTION"> - </p> - </div> - </div> - </div> - </div> - --> - - <div class="container-fluid"> - <div id="message-container"></div> - </div> - - <div ng-if="!ctrl.loggedin"> - <div class="jumbotron"> - <h1>Welcome to crosswatch</h1> - <p class="lead" translate="DESCRIPTION"></p> - <p><a href="login" target="_self" class="btn btn-primary btn-lg" role="button"><i class="fa fa-sign-in"></i> - <span translate="SIGN_IN_BUTTON"></span> - </a></p> - </div> - </div> - - - <div ng-if="ctrl.loggedin" ng-include="'components/watchlist/watchlist.html'"></div> - - <hr> - - <div id="footer"> - <p class="pull-right"><a href="//tools.wmflabs.org"> - <img id="footer-icon" src="//tools.wmflabs.org/static/res/logos/powered-by-tool-labs.png" width="105" height="40" - title="Powered by Wikimedia Tool Labs" alt="Powered by Wikimedia Tool Labs"> - </a></p> - <p class="text-muted"> - Maintained by <a href="//de.wikipedia.org/wiki/Benutzer:Sitic" title="sitic @ de.wikipedia">sitic</a>. - <a href="https://git.wikimedia.org/summary/labs%2Ftools%2Fcrosswatch">Code</a> licensed under the - <a href="https://git.wikimedia.org/summary/labs%2Ftools%2Fcrosswatch.git/master/LICENSE">ISC License</a>. - </p> - </div> +<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/watchlist/watchlist.html'"></div> </div> + +<ng-include src="'components/footer/footer.html'"></ng-include> diff --git a/frontend/src/app/runBlock.js b/frontend/src/app/runBlock.js new file mode 100644 index 0000000..0965138 --- /dev/null +++ b/frontend/src/app/runBlock.js @@ -0,0 +1,105 @@ +'use strict'; +angular + .module('crosswatch') + .run(runBlock) +; + +function runBlock (socket, $rootScope, dataService, $log, $timeout, $translate, amMoment, localStorageService, + $mdDialog, authService, $location) { + + /** + * Redirect if not logged in + */ + $rootScope.$on('$routeChangeStart', function (event) { + if (!authService.isloggedin()) { + $location.path('/welcome'); + } + else { + $location.path('/'); + } + }); + + /** + * Set Moment.js language + * Strange bug, $translate.use() is sometimes undefined if loaded by angular-translate from local storage + */ + var lang = $translate.use(); + if (typeof lang === "undefined") { + lang = localStorageService.get('NG_TRANSLATE_LANG_KEY'); + $translate.use(lang); + } + amMoment.changeLocale(lang); + + /** + * Query watchlist on login. + * Not part of authService to prevent circular dependency. + */ + $rootScope.$on('login', function () { + dataService.queryWatchlist(); + $timeout(errorHandler, 20000); // show error if no watchlist after 20 secs + }); + + /** + * If logged in before sockjs connected, query watchlist again. + */ + socket.setHandler('open', function () { + $log.info('sockjs connected'); + dataService.queryWatchlist(); + }); + + /** + * Handle all sockjs incoming messages here. + */ + var connectionError = true; + socket.setHandler('message', function (msg) { + msg = angular.fromJson(msg); + var data = angular.fromJson(msg.data); + if (data.msgtype === 'watchlist') { + dataService.addWatchlistEntries(data.entires); + } else if (data.msgtype === 'loginerror') { + $log.error('login failed!'); + + $translate(['OAUTH_FAILURE_TITLE', 'OAUTH_FAILURE_CONTENT', 'CLOSE']).then(function (translations) { + var alert = $mdDialog.alert({ + title: translations['OAUTH_FAILURE_TITLE'], + content: translations['OAUTH_FAILURE_CONTENT'], + ok: translations['CLOSE'] + }); + + $mdDialog + .show( alert ) + .finally(function() { + alert = undefined; + }); + }); + } else { + $log.error(data); + } + + if (connectionError) { + connectionError = false; + } + }); + + /** + * Show error message if no watchlist entries are retrieved. + */ + function errorHandler () { + if (connectionError) { + $log.warn('No websocket message after 20 seconds.'); + $translate(['SERVER_ERROR_TITLE', 'SERVER_ERROR_CONTENT', 'CLOSE']).then(function (translations) { + var alert = $mdDialog.alert({ + title: translations['SERVER_ERROR_TITLE'], + content: translations['SERVER_ERROR_CONTENT'], + ok: translations['CLOSE'] + }); + + $mdDialog + .show(alert) + .finally(function () { + alert = undefined; + }); + }); + } + } +} diff --git a/frontend/src/app/services.js b/frontend/src/app/services.js new file mode 100644 index 0000000..8330af0 --- /dev/null +++ b/frontend/src/app/services.js @@ -0,0 +1,136 @@ +'use strict'; +angular + .module('crosswatch') + .service('authService', authService) + .service('dataService', dataService) +; + +function authService (localStorageService, $rootScope, $log) { + $rootScope.$watch(function () { return localStorageService.cookie.get('user');}, function (newValue) { + if (newValue !== null) { + $log.info('Singed in as ' + newValue); + $rootScope.$emit('login', newValue); + } + }); + + this.tokens = function () { + return angular.fromJson(localStorageService.cookie.get('auth').replace(/\\054/g, ',')); + }; + + this.user = function () { + return localStorageService.cookie.get('user'); + }; + + this.isloggedin = function () { + return (localStorageService.cookie.get('auth') !== null); + }; + +} + +function dataService (socket, authService, localStorageService, $log) { + var vm = this; + + /** + * Array that contains all watchlist entries. + * @type {Array} + */ + vm.watchlist = []; + + /** + * Initialize user settings + */ + vm.defaultconfig = { + /** + * true: show only latest change + * false: show all changes + */ + lastrevonly: false, + /** + * Timeperiod for which the watchlist is retrieved in days + */ + watchlistperiod: 1.5, + flagsenable: false, + /** + * Options to show minor, bot, anon and registered user edits + */ + editflags: false, + /** + * List of all known projects (wikis) + */ + projectsList: false, + /** + * List of selected projects (wikis) + */ + projectsSelected: false + }; + if (localStorageService.get('config') !== null) { + vm.config = localStorageService.get('config'); + vm.config.__proto__ = vm.defaultconfig; + } else { + vm.config = Object.create(vm.defaultconfig); + } + // Sadly no other way to do this right + vm.config.projectsList = vm.config.projectsList || []; + vm.config.projectsSelected = vm.config.projectsSelected || []; + vm.config.editflags = vm.config.editflags || { + minor: true, + bot: false, + anon: true, + registered: true + }; + + /** + * Process an array of new watchlist entries. + * @param entries + */ + vm.addWatchlistEntries = function (entries) { + vm.watchlist.push.apply(vm.watchlist, entries); + var project = entries[0].project; + if (vm.config.projectsList.indexOf(project) === -1) { + vm.config.projectsList.push(project); + vm.config.projectsSelected.push(project); + vm.saveConfig(); + } + }; + + /** + * Reset watchlist and query it again + */ + vm.resetWatchlist = function () { + vm.watchlist.length = 0; + vm.queryWatchlist(); + vm.saveConfig(); + }; + + /** + * Save user config to local storage + */ + vm.saveConfig = function() { + localStorageService.set('config', vm.config); + }; + + /** + * If logged in, query watchlist. + */ + vm.queryWatchlist = function () { + if (authService.isloggedin()) { + var watchlistQuery = { + action: 'watchlist', + access_token: authService.tokens(), + watchlistperiod: vm.config.watchlistperiod, + allrev: !vm.config.lastrevonly, + projects: vm.config.projectsList + }; + try { + socket.send(angular.toJson(watchlistQuery)); + } catch(err) { + // INVALID_STATE_ERR when sockjs is not yet connected + if (err.message === 'InvalidStateError: The connection has not been established yet') { + $log.warn('Wachtlist queried before socketjs connected.'); + } else { + $log.error(err); + } + } + } + }; +} diff --git a/frontend/src/app/welcome/welcome.html b/frontend/src/app/welcome/welcome.html new file mode 100644 index 0000000..1a55e41 --- /dev/null +++ b/frontend/src/app/welcome/welcome.html @@ -0,0 +1,15 @@ +<header ng-include="'components/navbar/navbar.html'"></header> +<div layout="column" flex class="md-padding" id="welcome"> + <div layout="row" layout-align="center center" flex> + <md-card flex-sm="90"> + <md-card-content layout="column" layout-align="center center"> + <h1>crosswatch</h1> + <p class="text-center" translate="DESCRIPTION"></p> + <md-button href="login" target="_self" class="md-raised md-accent"> + {{'SIGN_IN_BUTTON' | translate}} + </md-button> + </md-card-content> + </md-card> + </div> +</div> +<ng-include src="'components/footer/footer.html'"></ng-include> diff --git a/frontend/src/assets/images/flags/readme.txt b/frontend/src/assets/images/flags/readme.txt old mode 100755 new mode 100644 diff --git a/frontend/src/components/footer/footer.html b/frontend/src/components/footer/footer.html new file mode 100644 index 0000000..05c3f12 --- /dev/null +++ b/frontend/src/components/footer/footer.html @@ -0,0 +1,11 @@ +<div role="footer" layout="row" layout-align="space-around center"> + <p class="text-muted"> + Maintained by <a href="//de.wikipedia.org/wiki/Benutzer:Sitic" title="sitic @ de.wikipedia">sitic</a>. + <a href="https://git.wikimedia.org/summary/labs%2Ftools%2Fcrosswatch">Code</a> licensed under the + <a href="https://en.wikipedia.org/wiki/ISC_license">ISC License</a>. + </p> + <p class="pull-right"><a href="//tools.wmflabs.org"> + <img id="footer-icon" src="//tools.wmflabs.org/static/res/logos/powered-by-tool-labs.png" width="105" height="40" + title="Powered by Wikimedia Tool Labs" alt="Powered by Wikimedia Tool Labs"> + </a></p> +</div> diff --git a/frontend/src/components/navbar/navbar.controller.js b/frontend/src/components/navbar/navbar.controller.js index 4b71cff..c6c4713 100644 --- a/frontend/src/components/navbar/navbar.controller.js +++ b/frontend/src/components/navbar/navbar.controller.js @@ -1,7 +1,8 @@ 'use strict'; angular.module('crosswatch') - .controller('NavbarCtrl', function (translationList, $translate, $rootScope, authService, amMoment, localStorageService) { + .controller('NavbarCtrl', function (translationList, $translate, $rootScope, authService, amMoment, + localStorageService) { var vm = this; vm.langs = translationList; vm.selectedLang = $translate.use(); diff --git a/frontend/src/components/navbar/navbar.html b/frontend/src/components/navbar/navbar.html index 59636db..52eeeae 100644 --- a/frontend/src/components/navbar/navbar.html +++ b/frontend/src/components/navbar/navbar.html @@ -1,45 +1,22 @@ -<nav class="navbar navbar-default" ng-controller="NavbarCtrl as ctrl"> - <div class="container-fluid"> - <form class="navbar-form navbar-left" role="language"> - <button type="button" class="btn btn-default" - ng-model="ctrl.selectedLang" - bs-options="lang.key as lang.language for lang in ctrl.langs" - ng-change="ctrl.changeLanguage(ctrl.selectedLang)" - bs-select></button> - </form> - <div> - <ul class="nav navbar-nav"> - <li> - <a href="https://meta.wikimedia.org/wiki/Crosswatch" target="_blank"> - <i class="fa fa-info-circle"></i> <span translate="DOCUMENTATION"></span> - </a> - </li> - <li> - <a href="https://meta.wikimedia.org/wiki/Talk:Crosswatch" target="_blank"> - <i class="fa fa-comments"></i> <span translate="TALK"></span> - </a> - </li> - <li> - <a href="https://phabricator.wikimedia.org/project/view/1224/" target="_blank"> - <i class="fa fa-bug"></i> <span translate="REPORT_BUGS"></span> - </a> - </li> - <li> - <a href="https://git.wikimedia.org/summary/labs%2Ftools%2Fcrosswatch" target="_blank"> - <i class="fa fa-github"></i> Git - </a> - </li> - </ul> - </div> - <ul class="nav navbar-nav navbar-right"> - <li ng-hide="ctrl.loggedin"> - <a href="login" target="_self" class="navbar-link"><i class="fa fa-sign-in"></i> - <span translate="SIGN_IN"></span></a> - </li> - <li ng-show="ctrl.loggedin"> - <a href="logout" target="_self" class="navbar-link"> - <span translate="SIGN_OUT"></span> <i class="fa fa-sign-out"></i></a> - </li> - </ul> - </div> -</nav> +<md-toolbar layout="row" layout-align="center center" ng-controller="NavbarCtrl as ctrl" class="md-toolbar-tools"> + <md-select ng-model="ctrl.selectedLang" + placeholder="Language" + ng-change="ctrl.changeLanguage(ctrl.selectedLang)"> + <md-option ng-repeat="lang in ctrl.langs" value="{{lang.key}}">{{lang.language}}</md-option> + </md-select> + <md-button href="https://meta.wikimedia.org/wiki/Crosswatch" target="_blank" class="md-flat"> + {{'DOCUMENTATION' | translate}} + </md-button> + <md-button href="https://meta.wikimedia.org/wiki/Talk:Crosswatch" target="_blank" class="md-flat"> + {{'TALK' | translate}} + </md-button> + <md-button href="https://phabricator.wikimedia.org/project/view/1224/" target="_blank" class="md-flat"> + {{'REPORT_BUGS' | translate}} + </md-button> + <md-button href="login" target="_self" class="md-flat" ng-hide="ctrl.loggedin"> + {{'SIGN_IN' | translate}} + </md-button> + <md-button href="logout" target="_self" class="md-flat" ng-show="ctrl.loggedin"> + {{'SIGN_OUT' | translate}} + </md-button> +</md-toolbar> diff --git a/frontend/src/components/settings/settings.controller.js b/frontend/src/components/settings/settings.controller.js new file mode 100644 index 0000000..ddaceef --- /dev/null +++ b/frontend/src/components/settings/settings.controller.js @@ -0,0 +1,34 @@ +'use strict'; + +angular.module('crosswatch') + .controller('SettingsCtrl', function ($translate, socket, $log, dataService, $rootScope) { + var vm = this; + vm.config = dataService.config; + vm.saveConfig = dataService.saveConfig; + + updatePeriodList(); + $rootScope.$on('$translateChangeSuccess', updatePeriodList); + + function updatePeriodList () { + var hours = [0.5, 1, 1.5]; + var days = [2, 3, 7, 14, 21, 30]; + + vm.periodList = []; + var addItem = function (value) { + return function (label) { + vm.periodList.push({value: value, label: label}) + } + }; + for (var i=0; i<hours.length; i++) { + $translate('SHOW_LAST_HOURS', {number: 24*hours[i]}).then(addItem(hours[i])); + } + for (var j=0; j<days.length; j++) { + $translate('SHOW_LAST_DAYS', {number: days[j]}).then(addItem(days[j])); + } + } + + vm.resetWatchlist = function () { + $log.info('resetting watchlist'); + dataService.resetWatchlist(); + }; + }); diff --git a/frontend/src/components/settings/settings.html b/frontend/src/components/settings/settings.html new file mode 100644 index 0000000..6526e93 --- /dev/null +++ b/frontend/src/components/settings/settings.html @@ -0,0 +1,61 @@ +<div ng-controller="SettingsCtrl as ctrl" layout="row" layout-align="center start"> + <md-whitefram class="md-whiteframe-z5 whitebox"> + <div layout="row"> + <div flex="50"> + <div layout="column" class="md-padding"> + <md-list> + <md-subheader class="md-no-sticky">{{'WIKIS' | translate}}</md-subheader> + <md-item id="projects"> + <md-select ng-model="ctrl.config.projectsSelected" multiple="true" placeholder="Loading …" + ng-change="ctrl.saveConfig()"> + <md-option ng-repeat="item in ctrl.config.projectsList" value="{{item}}">{{item}}</md-option> + </md-select> + </md-item> + <md-subheader class="md-no-sticky">{{'FILTER' | translate}}</md-subheader> + <md-item layout="row"> + <md-list flex="50"> + <md-list-item> + <p translate="MINOR_EDITS"></p> + <md-checkbox ng-model="ctrl.config.editflags.minor" ng-change="ctrl.saveConfig()"></md-checkbox> + </md-list-item> + <md-list-item> + <p translate="BOT_EDITS"></p> + <md-checkbox ng-model="ctrl.config.editflags.bot" ng-change="ctrl.saveConfig()"></md-checkbox> + </md-list-item> + </md-list> + <md-list flex="50"> + <md-list-item> + <p translate="ANON_EDITS"></p> + <md-checkbox ng-model="ctrl.config.editflags.anon" ng-change="ctrl.saveConfig()"></md-checkbox> + </md-list-item> + <md-list-item> + <p translate="USER_EDITS"></p> + <md-checkbox ng-model="ctrl.config.editflags.registered" ng-change="ctrl.saveConfig()"></md-checkbox> + </md-list-item> + </md-list> + </md-item> + </md-list> + </div> + </div> + <md-divider role="vertical"></md-divider> + <md-list flex="50"> + <div layout="column" class="md-padding"> + <md-subheader class="md-no-sticky">{{'PREFERENCES' | translate}}</md-subheader> + <md-list-item> + <md-select ng-model="ctrl.config.watchlistperiod" placeholder="timeperiod" ng-change="ctrl.resetWatchlist()"> + <md-option ng-repeat="item in ctrl.periodList" value="{{item.value}}">{{item.label}}</md-option> + </md-select> + </md-list-item> + <md-list-item> + <p translate="LASTREVONLY"></p> + <md-checkbox ng-model="ctrl.config.lastrevonly" ng-change="ctrl.resetWatchlist()"></md-checkbox> + </md-list-item> + <md-list-item> + <p translate="FLAGS"></p> + <md-checkbox ng-model="ctrl.config.flagsenable" ng-change="ctrl.saveConfig()"></md-checkbox> + </md-list-item> + </div> + </md-list> + </div> + </md-whitefram> +</div> diff --git a/frontend/src/components/watchlist/entry.directive.js b/frontend/src/components/watchlist/entry.directive.js index 097781b..54d4105 100644 --- a/frontend/src/components/watchlist/entry.directive.js +++ b/frontend/src/components/watchlist/entry.directive.js @@ -4,13 +4,9 @@ function watchlistEntry() { var directive = { - link: link, + scope: true, templateUrl: 'components/watchlist/entry.directive.html', restrict: 'EA' }; return directive; - - function link(scope, element, attrs) { - /* */ - } } diff --git a/frontend/src/components/watchlist/watchlist.controller.js b/frontend/src/components/watchlist/watchlist.controller.js index 999dbaf..e8cc97a 100644 --- a/frontend/src/components/watchlist/watchlist.controller.js +++ b/frontend/src/components/watchlist/watchlist.controller.js @@ -4,34 +4,30 @@ .controller('WatchlistCtrl', function ($translate, socket, $log, dataService, $rootScope) { var vm = this; vm.watchlist = dataService.watchlist; - vm.echolist = dataService.echolist; - vm.icons = dataService.icons; - vm.flags = dataService.flags; vm.config = dataService.config; - vm.saveConfig = dataService.saveConfig; - updatePeriodList(); - $rootScope.$on('$translateChangeSuccess', updatePeriodList); + 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"; - function updatePeriodList () { - var hours = [0.5, 1, 1.5]; - var days = [2, 3, 7, 14, 21, 30]; - - vm.periodList = []; - var addItem = function (value) { - return function (label) { - vm.periodList.push({value: value, label: label}) - } - }; - for (var i=0; i<hours.length; i++) { - $translate('HOURS', {number: 24*hours[i]}).then(addItem(hours[i])); - } - for (var j=0; j<days.length; j++) { - $translate('DAYS', {number: days[j]}).then(addItem(days[j])); - } - } + 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.resetWatchlist = function () { + $log.info('resetting watchlist'); dataService.resetWatchlist(); vm.watchlist = dataService.watchlist; }; diff --git a/frontend/src/components/watchlist/watchlist.html b/frontend/src/components/watchlist/watchlist.html index 6c0d8bf..3fde4b2 100644 --- a/frontend/src/components/watchlist/watchlist.html +++ b/frontend/src/components/watchlist/watchlist.html @@ -1,84 +1,31 @@ -<div ng-controller="WatchlistCtrl as ctrl"> - <form class="form-horizontal" role="form"> - <div class="form-group"> - <label class="control-label col-md-1">{{'SEARCH' | translate}}:</label> - <div class="col-md-2"> - <input type="text" class="form-control" ng-model="ctrl.searchText"> +<div ng-controller="WatchlistCtrl as ctrl" layout="row" layout-align="center center" class="md-padding"> + <md-whiteframe class="md-whiteframe-z5 whitebox"> + <md-toolbar class='md-small-tall'> + <div class="md-toolbar-tools" ng-hide="showSearch"> + <h1> + <span translate="WATCHLIST"></span> + </h1> + <div flex></div> + <md-button class="md-icon-button" title="{{'SEARCH_WATCHLIST' | translate}}" ng-click="showSearch = !showSearch"> + <md-icon md-font-library="material-icons">search</md-icon> + </md-button> </div> - <label class="control-label col-md-2">{{'TIMEPERIOD' | translate}}:</label> - <div class="col-md-1"> - <button type="button" class="btn btn-default" - ng-model="ctrl.config.watchlistperiod" - bs-options="item.value as item.label for item in ctrl.periodList" - ng-change="ctrl.resetWatchlist()" - bs-select></button> + <div class="md-toolbar-tools" ng-show="showSearch"> + <md-input-container flex="flex" md-no-float> + <input type="text" placeholder="{{'SEARCH_WATCHLIST' | translate}}" ng-model="ctrl.searchText"> + </md-input-container> </div> - </div> - <div class="form-group"> - <label class="control-label col-md-1">Wikis:</label> - <div class="col-md-2"> - <button type="button" class="btn btn-default" - ng-model="ctrl.config.projectsSelected" - bs-options="item for item in ctrl.config.projectsList" - ng-change="ctrl.saveConfig()" - data-multiple="1" - ng-model-options="{ debounce: 333 }" - bs-select></button> - </div> - </div> - <div class="form-group"> - <label class="control-label col-md-1">{{'OPTIONS' | translate}}:</label> - <div class="col-md-4"> - <div class="checkbox"> - <label data-placement="bottom-left" data-type="info" data-animation="am-fade-and-scale" - bs-tooltip="tooltip" bs-enabled="ctrl.config.lastrevonly"> - <input type="checkbox" ng-model="ctrl.config.lastrevonly" ng-change="ctrl.resetWatchlist()"> - {{'LASTREVONLY' | translate}} - </label> - </div> - <div class="checkbox"> - <label data-placement="bottom-left" data-type="info" data-animation="am-fade-and-scale" - bs-tooltip="tooltip" bs-enabled="ctrl.config.flagsenable"> - <input type="checkbox" ng-model="ctrl.config.flagsenable" ng-change="ctrl.saveConfig()"> - Show flags instead of language names - </label> - </div> - <div class="checkbox"> - <label data-placement="bottom-left" data-type="info" data-animation="am-fade-and-scale" - bs-tooltip="tooltip" bs-enabled="ctrl.config.editflags.minor"> - <input type="checkbox" ng-model="ctrl.config.editflags.minor" ng-change="ctrl.saveConfig()"> - {{'MINOR_EDITS' | translate}} - </label> - </div> - <div class="checkbox"> - <label data-placement="bottom-left" data-type="info" data-animation="am-fade-and-scale" - bs-tooltip="tooltip" bs-enabled="ctrl.config.editflags.bot"> - <input type="checkbox" ng-model="ctrl.config.editflags.bot" ng-change="ctrl.saveConfig()"> - {{'BOT_EDITS' | translate}} - </label> - </div> - <div class="checkbox"> - <label data-placement="bottom-left" data-type="info" data-animation="am-fade-and-scale" - bs-tooltip="tooltip" bs-enabled="ctrl.config.editflags.anon"> - <input type="checkbox" ng-model="ctrl.config.editflags.anon" ng-change="ctrl.saveConfig()"> - {{'ANON_EDITS' | translate}} - </label> - </div> - <div class="checkbox"> - <label data-placement="bottom-left" data-type="info" data-animation="am-fade-and-scale" - bs-tooltip="tooltip" bs-enabled="ctrl.config.editflags.registered"> - <input type="checkbox" ng-model="ctrl.config.editflags.registered" ng-change="ctrl.saveConfig()"> - {{'USER_EDITS' | translate}} - </label> - </div> - </div> - </div> - </form> - <hr> - <ul class="example-animate-container"> - <li class="watchlist" - ng-repeat="event in ctrl.watchlist | projects:ctrl.config.projectsSelected | editflags:ctrl.config.editflags | filter:ctrl.searchText | orderBy:'-timestamp'"> - <watchlist-entry></watchlist-entry> - </li> - </ul> + </md-toolbar> + <md-list> + <md-list-item ng-show="ctrl.watchlist.length === 0" layout="row" layout-align="center center"> + <md-progress-circular md-mode="indeterminate"></md-progress-circular> + </md-list-item> + <md-list-item layout="row" + class="watchlist" id="watchlist" + ng-repeat="event in ctrl.watchlist | projects:ctrl.config.projectsSelected | editflags:ctrl.config.editflags | filter:ctrl.searchText | orderBy:'-timestamp'"> + <watchlist-entry></watchlist-entry> + <md-divider></md-divider> + </md-list-item> + </md-list> + </md-whiteframe> </div> diff --git a/frontend/src/i18n/locale-de.json b/frontend/src/i18n/locale-de.json index fce36d1..a88f7bc 100644 --- a/frontend/src/i18n/locale-de.json +++ b/frontend/src/i18n/locale-de.json @@ -6,16 +6,12 @@ "SIGN_OUT": "Abmelden", "DESCRIPTION": "Eine erweiterte Beobachtungsliste für Wikimedia Projekte.", "SIGN_IN_BUTTON": "Mit OAuth anmelden", - "SEARCH": "Suchen", - "SIGNED_IN_MSG": "Willkommen {{username}}, du bist angemeldet. Unter Umständen dauert es kurz, bis deine Beobachtungsliste vollständig geladen ist.", "MINOREDIT_FLAG": "K", "BOTEDIT_FLAG": "B", "NEWPAGE_FLAG": "N", - "OPTIONS": "Optionen", "LASTREVONLY": "Zeige nur die letzte Änderung pro Seite", - "TIMEPERIOD": "Zeige letzte", - "HOURS": "{{number}} Stunden", - "DAYS": "{{number}} Tage", + "SHOW_LAST_HOURS": "Zeige letzte {{number}} Stunden", + "SHOW_LAST_DAYS": "Zeige letzte {{number}} Tage", "LOGEVENT_DELETE_DELETE": "<user> löschte Seite <page>", "LOGEVENT_DELETE_REVISION": "<user> änderte die Sichtbarkeit von Versionen der Seite <page>", "LOGEVENT_DELETE_RESTORE": "<user> stellte Seite <page> wieder her", diff --git a/frontend/src/i18n/locale-en.json b/frontend/src/i18n/locale-en.json index c6cf1c6..ea99aab 100644 --- a/frontend/src/i18n/locale-en.json +++ b/frontend/src/i18n/locale-en.json @@ -6,20 +6,22 @@ "SIGN_OUT": "Logout", "DESCRIPTION": "An enhanced watchlist for Wikimedia projects.", "SIGN_IN_BUTTON": "Sign in with OAuth", - "SEARCH": "Search", - "SIGNED_IN_MSG": "Welcome {{username}}, you are signed in. It may take a bit until your watchlist is fully loaded.", "MINOREDIT_FLAG": "m", "BOTEDIT_FLAG": "b", "NEWPAGE_FLAG": "N", - "OPTIONS": "Options", + "PREFERENCES": "Preferences", + "FILTER": "Filter", + "WIKIS": "Wikis", "LASTREVONLY": "Show only the latest change per page", - "TIMEPERIOD": "Show last", - "HOURS": "{{number}} hours", - "DAYS": "{{number}} days", - "MINOR_EDITS": "Show minor edits", - "BOT_EDITS": "Show bot edits", - "ANON_EDITS": "Show anonymous user edits", - "USER_EDITS": "Show registered user edits", + "SHOW_LAST_HOURS": "Show last {{number}} hours", + "SHOW_LAST_DAYS": "Show last {{number}} days", + "MINOR_EDITS": "minor edits", + "BOT_EDITS": "bot edits", + "ANON_EDITS": "anonymous user edits", + "USER_EDITS": "registered user edits", + "FLAGS": "Show flags instead of language names", + "WATCHLIST": "Watchlist", + "SEARCH_WATCHLIST": "Search watchlist", "LOGEVENT_DELETE_DELETE": "<user> deleted page <page>", "LOGEVENT_DELETE_REVISION": "<user> changed visibility of a revision on page <page>", "LOGEVENT_DELETE_RESTORE": "<user> restored <page>", @@ -42,5 +44,10 @@ "LOGEVENT_NEWUSERS_CREATE": "User account <user name={{title}}> was created", "LOGEVENT_NEWUSERS_CREATE2": "User account <user name={{title}}> was created by <user>", "LOGEVENT_NEWUSERS_BYEMAIL": "User account <user name={{title}}> was created by <user> and password was sent by email", - "LOGEVENT_RENAMEUSER": "<user> renamed user <user name={{olduser}}> ({{edits}} edits) to <user name={{newuser}}>" + "LOGEVENT_RENAMEUSER": "<user> renamed user <user name={{olduser}}> ({{edits}} edits) to <user name={{newuser}}>", + "OAUTH_FAILURE_TITLE": "OAuth authorization failure", + "OAUTH_FAILURE_CONTENT": "You have revoked the authorization for this application. Please logout and sign in again.", + "CLOSE": "Close", + "SERVER_ERROR_TITLE": "Sorry!", + "SERVER_ERROR_CONTENT": "No watchlist could be retrieved. Either you have no watchlist events for the selected time period, or this is an internal server error." } diff --git a/frontend/src/i18n/locale-pt.json b/frontend/src/i18n/locale-pt.json index 1b90f32..18f8afc 100644 --- a/frontend/src/i18n/locale-pt.json +++ b/frontend/src/i18n/locale-pt.json @@ -6,8 +6,6 @@ "SIGN_OUT": "Sair", "DESCRIPTION": "Uma lista de páginas vigiadas aprimorada para os projetos Wikimedia.", "SIGN_IN_BUTTON": "Enrar com OAuth", - "SEARCH": "Pesquisa", - "SIGNED_IN_MSG": "Bem-vindo(a) {{username}}, você já está conectado. Pode levar algum tempo até que a sua lista de páginas vigiadas seja completamente carregada.", "MINOREDIT_FLAG": "m", "BOTEDIT_FLAG": "b", "NEWPAGE_FLAG": "N" diff --git a/frontend/src/index.html b/frontend/src/index.html index 65d6ab0..7a2b0a0 100644 --- a/frontend/src/index.html +++ b/frontend/src/index.html @@ -4,7 +4,7 @@ <meta charset="utf-8"> <title>crosswatch</title> <meta name="description" content=""> - <meta name="viewport" content="width=device-width"> + <meta name="viewport" content="initial-scale=1"> <base href="/" /> <!-- Place favicon.ico and apple-touch-icon.png in the root directory --> -- To view, visit https://gerrit.wikimedia.org/r/218006 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: merged Gerrit-Change-Id: I3fa2dba3aac6da73e2f6e8fc03169d4f1e71feeb Gerrit-PatchSet: 5 Gerrit-Project: labs/tools/crosswatch Gerrit-Branch: master Gerrit-Owner: Sitic <jan.leb...@online.de> Gerrit-Reviewer: Sitic <jan.leb...@online.de> _______________________________________________ MediaWiki-commits mailing list MediaWiki-commits@lists.wikimedia.org https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits