diff --git a/.babelrc b/.babelrc index 857e7ba32..c72dfe479 100644 --- a/.babelrc +++ b/.babelrc @@ -4,6 +4,7 @@ ], "plugins": [ ["@babel/plugin-proposal-decorators", { "legacy": true }], - ["@babel/plugin-proposal-class-properties", { "loose": true }] + ["@babel/plugin-proposal-class-properties", { "loose": true }], + ["@babel/transform-runtime"] ] } diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b2b815ec..1c7122941 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,117 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +## [6.4.0](https://gitlab.cip.ifi.lmu.de/jost/UniWorX/compare/v6.3.0...v6.4.0) (2019-09-05) + + +### Bug Fixes + +* **allocations:** don't show all allocation information to lecturers ([ad6c503](https://gitlab.cip.ifi.lmu.de/jost/UniWorX/commit/ad6c503)) + + +### Features + +* **changelog:** prettify date formatting ([2b3aef7](https://gitlab.cip.ifi.lmu.de/jost/UniWorX/commit/2b3aef7)) + + + +## [6.3.0](https://gitlab.cip.ifi.lmu.de/jost/UniWorX/compare/v6.2.1...v6.3.0) (2019-09-05) + + +### Bug Fixes + +* fix build ([1a66716](https://gitlab.cip.ifi.lmu.de/jost/UniWorX/commit/1a66716)) + + +### Features + +* **allocations:** notifications ([6d52ed5](https://gitlab.cip.ifi.lmu.de/jost/UniWorX/commit/6d52ed5)) + + + +### [6.2.1](https://gitlab.cip.ifi.lmu.de/jost/UniWorX/compare/v6.2.0...v6.2.1) (2019-09-04) + + +### Bug Fixes + +* **course-edit:** show old allocation ([fc53497](https://gitlab.cip.ifi.lmu.de/jost/UniWorX/commit/fc53497)), closes [#450](https://gitlab.cip.ifi.lmu.de/jost/UniWorX/issues/450) + + + +## [6.2.0](https://gitlab.cip.ifi.lmu.de/jost/UniWorX/compare/v6.1.0...v6.2.0) (2019-09-02) + + +### Bug Fixes + +* **datepicker:** removes idle cancel and submit buttons ([805676f](https://gitlab.cip.ifi.lmu.de/jost/UniWorX/commit/805676f)) + + +### Features + +* **users:** ldap-synchronise arbitrary subsets of users ([0789536](https://gitlab.cip.ifi.lmu.de/jost/UniWorX/commit/0789536)) + + + +## [6.1.0](https://gitlab.cip.ifi.lmu.de/jost/UniWorX/compare/v6.0.0...v6.1.0) (2019-08-30) + + +### Bug Fixes + +* **async-table:** update legacy call to datepicker ([d56e12d](https://gitlab.cip.ifi.lmu.de/jost/UniWorX/commit/d56e12d)) + + +### Features + +* **ldap:** manually trigger ldap sync ([83afb6f](https://gitlab.cip.ifi.lmu.de/jost/UniWorX/commit/83afb6f)) + + + +## [6.0.0](https://gitlab.cip.ifi.lmu.de/jost/UniWorX/compare/v5.5.0...v6.0.0) (2019-08-30) + + +### Bug Fixes + +* **datepicker:** fix selecting date from manual input in internal format ([8bdcc92](https://gitlab.cip.ifi.lmu.de/jost/UniWorX/commit/8bdcc92)) +* **datepicker:** format time on copy paste as well ([99d9efa](https://gitlab.cip.ifi.lmu.de/jost/UniWorX/commit/99d9efa)) + + +### Features + +* **allocations:** additional info and explanation for participants ([38949cf](https://gitlab.cip.ifi.lmu.de/jost/UniWorX/commit/38949cf)) +* **crontab:** cronjob for pruning expired invitations ([a9c5276](https://gitlab.cip.ifi.lmu.de/jost/UniWorX/commit/a9c5276)) +* **datepicker:** add option to change the position of the datepicker ([85f46ef](https://gitlab.cip.ifi.lmu.de/jost/UniWorX/commit/85f46ef)) +* **datepicker:** also parse manual input in internal format ([8a3ac72](https://gitlab.cip.ifi.lmu.de/jost/UniWorX/commit/8a3ac72)) +* **datepicker:** close datepicker on click outside ([88a6b85](https://gitlab.cip.ifi.lmu.de/jost/UniWorX/commit/88a6b85)) +* **datepicker:** close datepicker on escape keydown ([0e5707a](https://gitlab.cip.ifi.lmu.de/jost/UniWorX/commit/0e5707a)) +* **datepicker:** currently broken version using tail.datetime instead ([4282554](https://gitlab.cip.ifi.lmu.de/jost/UniWorX/commit/4282554)) +* **datepicker:** define instance collection singleton ([f5636b8](https://gitlab.cip.ifi.lmu.de/jost/UniWorX/commit/f5636b8)) +* **datepicker:** display datepicker on the right ([cbb7e95](https://gitlab.cip.ifi.lmu.de/jost/UniWorX/commit/cbb7e95)) +* **datepicker:** do not replace value if input is no valid date ([ecab0ac](https://gitlab.cip.ifi.lmu.de/jost/UniWorX/commit/ecab0ac)) +* **datepicker:** format according to input type; position datepicker ([db345ee](https://gitlab.cip.ifi.lmu.de/jost/UniWorX/commit/db345ee)) +* **datepicker:** format any dates before submission ([1eccb0e](https://gitlab.cip.ifi.lmu.de/jost/UniWorX/commit/1eccb0e)) +* **datepicker:** format time on submit ([9f8749c](https://gitlab.cip.ifi.lmu.de/jost/UniWorX/commit/9f8749c)) +* **datepicker:** formatting dates for mass-inputs ([b9fd4d7](https://gitlab.cip.ifi.lmu.de/jost/UniWorX/commit/b9fd4d7)) +* **datepicker:** helper functions and updated tail.datetime fork ([2512d69](https://gitlab.cip.ifi.lmu.de/jost/UniWorX/commit/2512d69)) +* **datepicker:** more sane datetime config ([5a44263](https://gitlab.cip.ifi.lmu.de/jost/UniWorX/commit/5a44263)) +* **datepicker:** new approach stub for formatting dates in formdata ([9ea7b2e](https://gitlab.cip.ifi.lmu.de/jost/UniWorX/commit/9ea7b2e)) +* **datepicker:** only update datepicker date if date is valid ([d857af3](https://gitlab.cip.ifi.lmu.de/jost/UniWorX/commit/d857af3)) +* **datepicker:** switch to tail.datetime fork to fix time selection ([863971f](https://gitlab.cip.ifi.lmu.de/jost/UniWorX/commit/863971f)) +* **datepicker:** update dependencies ([427ffbf](https://gitlab.cip.ifi.lmu.de/jost/UniWorX/commit/427ffbf)) +* **invitations:** save expiresAt to DB ([1c2f2b7](https://gitlab.cip.ifi.lmu.de/jost/UniWorX/commit/1c2f2b7)) +* **ldap:** automatically synchronise user data from ldap ([b39ba8b](https://gitlab.cip.ifi.lmu.de/jost/UniWorX/commit/b39ba8b)) +* **navigate-away-prompt:** prompt on actual value change only ([293ab6d](https://gitlab.cip.ifi.lmu.de/jost/UniWorX/commit/293ab6d)) +* **schools:** implement cru ([18ae28a](https://gitlab.cip.ifi.lmu.de/jost/UniWorX/commit/18ae28a)) +* **user-schools:** allow users to override automatic school assoc' ([7d927fd](https://gitlab.cip.ifi.lmu.de/jost/UniWorX/commit/7d927fd)) +* **user-schools:** automatically assign users to schools ([12067de](https://gitlab.cip.ifi.lmu.de/jost/UniWorX/commit/12067de)) +* **users:** generalise UserLecturer and UserAdmin to UserFunction ([76f8da5](https://gitlab.cip.ifi.lmu.de/jost/UniWorX/commit/76f8da5)), closes [#320](https://gitlab.cip.ifi.lmu.de/jost/UniWorX/issues/320) + + +### BREAKING CHANGES + +* **users:** Remove UserLecturer and UserAdmin + + + ## [5.5.0](https://gitlab.cip.ifi.lmu.de/jost/UniWorX/compare/v5.4.0...v5.5.0) (2019-08-27) diff --git a/config/settings.yml b/config/settings.yml index ca2520708..e4568d03f 100644 --- a/config/settings.yml +++ b/config/settings.yml @@ -41,6 +41,9 @@ health-check-delay-notify: "_env:HEALTHCHECK_DELAY_NOTIFY:true" health-check-http: "_env:HEALTHCHECK_HTTP:true" # Can we assume, that we can reach ourselves under APPROOT via HTTP (reverse proxies or firewalls might prevent this)? health-check-active-job-executors-timeout: "_env:HEALTHCHECK_ACTIVE_JOB_EXECUTORS_TIMEOUT:5" +synchronise-ldap-users-within: "_env:SYNCHRONISE_LDAP_WITHIN:1209600" +synchronise-ldap-users-interval: "_env:SYNCHRONISE_LDAP_INTERVAL:3600" + log-settings: detailed: "_env:DETAILED_LOGGING:false" all: "_env:LOG_ALL:false" diff --git a/frontend/polyfills/fetch.js b/frontend/polyfills/fetch.js deleted file mode 100644 index ac9a4fd87..000000000 --- a/frontend/polyfills/fetch.js +++ /dev/null @@ -1,466 +0,0 @@ -(function(self) { - 'use strict'; - - if (self.fetch) { - return - } - - var support = { - searchParams: 'URLSearchParams' in self, - iterable: 'Symbol' in self && 'iterator' in Symbol, - blob: 'FileReader' in self && 'Blob' in self && (function() { - try { - new Blob() - return true - } catch(e) { - return false - } - })(), - formData: 'FormData' in self, - arrayBuffer: 'ArrayBuffer' in self - } - - if (support.arrayBuffer) { - var viewClasses = [ - '[object Int8Array]', - '[object Uint8Array]', - '[object Uint8ClampedArray]', - '[object Int16Array]', - '[object Uint16Array]', - '[object Int32Array]', - '[object Uint32Array]', - '[object Float32Array]', - '[object Float64Array]' - ] - - var isDataView = function(obj) { - return obj && DataView.prototype.isPrototypeOf(obj) - } - - var isArrayBufferView = ArrayBuffer.isView || function(obj) { - return obj && viewClasses.indexOf(Object.prototype.toString.call(obj)) > -1 - } - } - - function normalizeName(name) { - if (typeof name !== 'string') { - name = String(name) - } - if (/[^a-z0-9\-#$%&'*+.\^_`|~]/i.test(name)) { - throw new TypeError('Invalid character in header field name') - } - return name.toLowerCase() - } - - function normalizeValue(value) { - if (typeof value !== 'string') { - value = String(value) - } - return value - } - - // Build a destructive iterator for the value list - function iteratorFor(items) { - var iterator = { - next: function() { - var value = items.shift() - return {done: value === undefined, value: value} - } - } - - if (support.iterable) { - iterator[Symbol.iterator] = function() { - return iterator - } - } - - return iterator - } - - function Headers(headers) { - this.map = {} - - if (headers instanceof Headers) { - headers.forEach(function(value, name) { - this.append(name, value) - }, this) - } else if (Array.isArray(headers)) { - headers.forEach(function(header) { - this.append(header[0], header[1]) - }, this) - } else if (headers) { - Object.getOwnPropertyNames(headers).forEach(function(name) { - this.append(name, headers[name]) - }, this) - } - } - - Headers.prototype.append = function(name, value) { - name = normalizeName(name) - value = normalizeValue(value) - var oldValue = this.map[name] - this.map[name] = oldValue ? oldValue+','+value : value - } - - Headers.prototype['delete'] = function(name) { - delete this.map[normalizeName(name)] - } - - Headers.prototype.get = function(name) { - name = normalizeName(name) - return this.has(name) ? this.map[name] : null - } - - Headers.prototype.has = function(name) { - return this.map.hasOwnProperty(normalizeName(name)) - } - - Headers.prototype.set = function(name, value) { - this.map[normalizeName(name)] = normalizeValue(value) - } - - Headers.prototype.forEach = function(callback, thisArg) { - for (var name in this.map) { - if (this.map.hasOwnProperty(name)) { - callback.call(thisArg, this.map[name], name, this) - } - } - } - - Headers.prototype.keys = function() { - var items = [] - this.forEach(function(value, name) { items.push(name) }) - return iteratorFor(items) - } - - Headers.prototype.values = function() { - var items = [] - this.forEach(function(value) { items.push(value) }) - return iteratorFor(items) - } - - Headers.prototype.entries = function() { - var items = [] - this.forEach(function(value, name) { items.push([name, value]) }) - return iteratorFor(items) - } - - if (support.iterable) { - Headers.prototype[Symbol.iterator] = Headers.prototype.entries - } - - function consumed(body) { - if (body.bodyUsed) { - return Promise.reject(new TypeError('Already read')) - } - body.bodyUsed = true - } - - function fileReaderReady(reader) { - return new Promise(function(resolve, reject) { - reader.onload = function() { - resolve(reader.result) - } - reader.onerror = function() { - reject(reader.error) - } - }) - } - - function readBlobAsArrayBuffer(blob) { - var reader = new FileReader() - var promise = fileReaderReady(reader) - reader.readAsArrayBuffer(blob) - return promise - } - - function readBlobAsText(blob) { - var reader = new FileReader() - var promise = fileReaderReady(reader) - reader.readAsText(blob) - return promise - } - - function readArrayBufferAsText(buf) { - var view = new Uint8Array(buf) - var chars = new Array(view.length) - - for (var i = 0; i < view.length; i++) { - chars[i] = String.fromCharCode(view[i]) - } - return chars.join('') - } - - function bufferClone(buf) { - if (buf.slice) { - return buf.slice(0) - } else { - var view = new Uint8Array(buf.byteLength) - view.set(new Uint8Array(buf)) - return view.buffer - } - } - - function Body() { - this.bodyUsed = false - - this._initBody = function(body) { - this._bodyInit = body - if (!body) { - this._bodyText = '' - } else if (typeof body === 'string') { - this._bodyText = body - } else if (support.blob && Blob.prototype.isPrototypeOf(body)) { - this._bodyBlob = body - } else if (support.formData && FormData.prototype.isPrototypeOf(body)) { - this._bodyFormData = body - } else if (support.searchParams && URLSearchParams.prototype.isPrototypeOf(body)) { - this._bodyText = body.toString() - } else if (support.arrayBuffer && support.blob && isDataView(body)) { - this._bodyArrayBuffer = bufferClone(body.buffer) - // IE 10-11 can't handle a DataView body. - this._bodyInit = new Blob([this._bodyArrayBuffer]) - } else if (support.arrayBuffer && (ArrayBuffer.prototype.isPrototypeOf(body) || isArrayBufferView(body))) { - this._bodyArrayBuffer = bufferClone(body) - } else { - throw new Error('unsupported BodyInit type') - } - - if (!this.headers.get('content-type')) { - if (typeof body === 'string') { - this.headers.set('content-type', 'text/plain;charset=UTF-8') - } else if (this._bodyBlob && this._bodyBlob.type) { - this.headers.set('content-type', this._bodyBlob.type) - } else if (support.searchParams && URLSearchParams.prototype.isPrototypeOf(body)) { - this.headers.set('content-type', 'application/x-www-form-urlencoded;charset=UTF-8') - } - } - } - - if (support.blob) { - this.blob = function() { - var rejected = consumed(this) - if (rejected) { - return rejected - } - - if (this._bodyBlob) { - return Promise.resolve(this._bodyBlob) - } else if (this._bodyArrayBuffer) { - return Promise.resolve(new Blob([this._bodyArrayBuffer])) - } else if (this._bodyFormData) { - throw new Error('could not read FormData body as blob') - } else { - return Promise.resolve(new Blob([this._bodyText])) - } - } - - this.arrayBuffer = function() { - if (this._bodyArrayBuffer) { - return consumed(this) || Promise.resolve(this._bodyArrayBuffer) - } else { - return this.blob().then(readBlobAsArrayBuffer) - } - } - } - - this.text = function() { - var rejected = consumed(this) - if (rejected) { - return rejected - } - - if (this._bodyBlob) { - return readBlobAsText(this._bodyBlob) - } else if (this._bodyArrayBuffer) { - return Promise.resolve(readArrayBufferAsText(this._bodyArrayBuffer)) - } else if (this._bodyFormData) { - throw new Error('could not read FormData body as text') - } else { - return Promise.resolve(this._bodyText) - } - } - - if (support.formData) { - this.formData = function() { - return this.text().then(decode) - } - } - - this.json = function() { - return this.text().then(JSON.parse) - } - - return this - } - - // HTTP methods whose capitalization should be normalized - var methods = ['DELETE', 'GET', 'HEAD', 'OPTIONS', 'POST', 'PUT'] - - function normalizeMethod(method) { - var upcased = method.toUpperCase() - return (methods.indexOf(upcased) > -1) ? upcased : method - } - - function Request(input, options) { - options = options || {} - var body = options.body - - if (input instanceof Request) { - if (input.bodyUsed) { - throw new TypeError('Already read') - } - this.url = input.url - this.credentials = input.credentials - if (!options.headers) { - this.headers = new Headers(input.headers) - } - this.method = input.method - this.mode = input.mode - if (!body && input._bodyInit != null) { - body = input._bodyInit - input.bodyUsed = true - } - } else { - this.url = String(input) - } - - this.credentials = options.credentials || this.credentials || 'omit' - if (options.headers || !this.headers) { - this.headers = new Headers(options.headers) - } - this.method = normalizeMethod(options.method || this.method || 'GET') - this.mode = options.mode || this.mode || null - this.referrer = null - - if ((this.method === 'GET' || this.method === 'HEAD') && body) { - throw new TypeError('Body not allowed for GET or HEAD requests') - } - this._initBody(body) - } - - Request.prototype.clone = function() { - return new Request(this, { body: this._bodyInit }) - } - - function decode(body) { - var form = new FormData() - body.trim().split('&').forEach(function(bytes) { - if (bytes) { - var split = bytes.split('=') - var name = split.shift().replace(/\+/g, ' ') - var value = split.join('=').replace(/\+/g, ' ') - form.append(decodeURIComponent(name), decodeURIComponent(value)) - } - }) - return form - } - - function parseHeaders(rawHeaders) { - var headers = new Headers() - // Replace instances of \r\n and \n followed by at least one space or horizontal tab with a space - // https://tools.ietf.org/html/rfc7230#section-3.2 - var preProcessedHeaders = rawHeaders.replace(/\r?\n[\t ]+/g, ' ') - preProcessedHeaders.split(/\r?\n/).forEach(function(line) { - var parts = line.split(':') - var key = parts.shift().trim() - if (key) { - var value = parts.join(':').trim() - headers.append(key, value) - } - }) - return headers - } - - Body.call(Request.prototype) - - function Response(bodyInit, options) { - if (!options) { - options = {} - } - - this.type = 'default' - this.status = options.status === undefined ? 200 : options.status - this.ok = this.status >= 200 && this.status < 300 - this.statusText = 'statusText' in options ? options.statusText : 'OK' - this.headers = new Headers(options.headers) - this.url = options.url || '' - this._initBody(bodyInit) - } - - Body.call(Response.prototype) - - Response.prototype.clone = function() { - return new Response(this._bodyInit, { - status: this.status, - statusText: this.statusText, - headers: new Headers(this.headers), - url: this.url - }) - } - - Response.error = function() { - var response = new Response(null, {status: 0, statusText: ''}) - response.type = 'error' - return response - } - - var redirectStatuses = [301, 302, 303, 307, 308] - - Response.redirect = function(url, status) { - if (redirectStatuses.indexOf(status) === -1) { - throw new RangeError('Invalid status code') - } - - return new Response(null, {status: status, headers: {location: url}}) - } - - self.Headers = Headers - self.Request = Request - self.Response = Response - - self.fetch = function(input, init) { - return new Promise(function(resolve, reject) { - var request = new Request(input, init) - var xhr = new XMLHttpRequest() - - xhr.onload = function() { - var options = { - status: xhr.status, - statusText: xhr.statusText, - headers: parseHeaders(xhr.getAllResponseHeaders() || '') - } - options.url = 'responseURL' in xhr ? xhr.responseURL : options.headers.get('X-Request-URL') - var body = 'response' in xhr ? xhr.response : xhr.responseText - resolve(new Response(body, options)) - } - - xhr.onerror = function() { - reject(new TypeError('Network request failed')) - } - - xhr.ontimeout = function() { - reject(new TypeError('Network request failed')) - } - - xhr.open(request.method, request.url, true) - - if (request.credentials === 'include') { - xhr.withCredentials = true - } else if (request.credentials === 'omit') { - xhr.withCredentials = false - } - - if ('responseType' in xhr && support.blob) { - xhr.responseType = 'blob' - } - - request.headers.forEach(function(value, name) { - xhr.setRequestHeader(name, value) - }) - - xhr.send(typeof request._bodyInit === 'undefined' ? null : request._bodyInit) - }) - } - self.fetch.polyfill = true -})(typeof self !== 'undefined' ? self : this); \ No newline at end of file diff --git a/frontend/polyfills/main.js b/frontend/polyfills/main.js deleted file mode 100644 index 7e4c554ea..000000000 --- a/frontend/polyfills/main.js +++ /dev/null @@ -1,2 +0,0 @@ -import './fetch'; -import './url-search-params'; diff --git a/frontend/polyfills/url-search-params.js b/frontend/polyfills/url-search-params.js deleted file mode 100644 index e38c12021..000000000 --- a/frontend/polyfills/url-search-params.js +++ /dev/null @@ -1,348 +0,0 @@ -(function(global) { - /** - * Polyfill URLSearchParams - * - * Inspired from : https://github.com/WebReflection/url-search-params/blob/master/src/url-search-params.js - */ - - var checkIfIteratorIsSupported = function() { - try { - return !!Symbol.iterator; - } catch(error) { - return false; - } - }; - - - var iteratorSupported = checkIfIteratorIsSupported(); - - var createIterator = function(items) { - var iterator = { - next: function() { - var value = items.shift(); - return { done: value === void 0, value: value }; - } - }; - - if(iteratorSupported) { - iterator[Symbol.iterator] = function() { - return iterator; - }; - } - - return iterator; - }; - - /** - * Search param name and values should be encoded according to https://url.spec.whatwg.org/#urlencoded-serializing - * encodeURIComponent() produces the same result except encoding spaces as `%20` instead of `+`. - */ - var serializeParam = function(value) { - return encodeURIComponent(value).replace(/%20/g, '+'); - }; - - var deserializeParam = function(value) { - return decodeURIComponent(value).replace(/\+/g, ' '); - }; - - var polyfillURLSearchParams= function() { - - var URLSearchParams = function(searchString) { - Object.defineProperty(this, '_entries', { value: {} }); - - if(typeof searchString === 'string') { - if(searchString !== '') { - searchString = searchString.replace(/^\?/, ''); - var attributes = searchString.split('&'); - var attribute; - for(var i = 0; i < attributes.length; i++) { - attribute = attributes[i].split('='); - this.append( - deserializeParam(attribute[0]), - (attribute.length > 1) ? deserializeParam(attribute[1]) : '' - ); - } - } - } else if(searchString instanceof URLSearchParams) { - var _this = this; - searchString.forEach(function(value, name) { - _this.append(value, name); - }); - } - }; - - var proto = URLSearchParams.prototype; - - proto.append = function(name, value) { - if(name in this._entries) { - this._entries[name].push(value.toString()); - } else { - this._entries[name] = [value.toString()]; - } - }; - - proto.delete = function(name) { - delete this._entries[name]; - }; - - proto.get = function(name) { - return (name in this._entries) ? this._entries[name][0] : null; - }; - - proto.getAll = function(name) { - return (name in this._entries) ? this._entries[name].slice(0) : []; - }; - - proto.has = function(name) { - return (name in this._entries); - }; - - proto.set = function(name, value) { - this._entries[name] = [value.toString()]; - }; - - proto.forEach = function(callback, thisArg) { - var entries; - for(var name in this._entries) { - if(this._entries.hasOwnProperty(name)) { - entries = this._entries[name]; - for(var i = 0; i < entries.length; i++) { - callback.call(thisArg, entries[i], name, this); - } - } - } - }; - - proto.keys = function() { - var items = []; - this.forEach(function(value, name) { items.push(name); }); - return createIterator(items); - }; - - proto.values = function() { - var items = []; - this.forEach(function(value) { items.push(value); }); - return createIterator(items); - }; - - proto.entries = function() { - var items = []; - this.forEach(function(value, name) { items.push([name, value]); }); - return createIterator(items); - }; - - if(iteratorSupported) { - proto[Symbol.iterator] = proto.entries; - } - - proto.toString = function() { - var searchString = ''; - this.forEach(function(value, name) { - if(searchString.length > 0) searchString+= '&'; - searchString += serializeParam(name) + '=' + serializeParam(value); - }); - return searchString; - }; - - global.URLSearchParams = URLSearchParams; - }; - - if(!('URLSearchParams' in global) || (new URLSearchParams('?a=1').toString() !== 'a=1')) { - polyfillURLSearchParams(); - } - - // HTMLAnchorElement - -})( - (typeof global !== 'undefined') ? global - : ((typeof window !== 'undefined') ? window - : ((typeof self !== 'undefined') ? self : this)) -); - -(function(global) { - /** - * Polyfill URL - * - * Inspired from : https://github.com/arv/DOM-URL-Polyfill/blob/master/src/url.js - */ - - var checkIfURLIsSupported = function() { - try { - var u = new URL('b', 'http://a'); - u.pathname = 'c%20d'; - return (u.href === 'http://a/c%20d') && u.searchParams; - } catch(e) { - return false; - } - }; - - - var polyfillURL = function() { - var _URL = global.URL; - - var URL = function(url, base) { - if(typeof url !== 'string') url = String(url); - - var doc = document.implementation.createHTMLDocument(''); - window.doc = doc; - if(base) { - var baseElement = doc.createElement('base'); - baseElement.href = base; - doc.head.appendChild(baseElement); - } - - var anchorElement = doc.createElement('a'); - anchorElement.href = url; - doc.body.appendChild(anchorElement); - anchorElement.href = anchorElement.href; // force href to refresh - - if(anchorElement.protocol === ':' || !/:/.test(anchorElement.href)) { - throw new TypeError('Invalid URL'); - } - - Object.defineProperty(this, '_anchorElement', { - value: anchorElement - }); - }; - - var proto = URL.prototype; - - var linkURLWithAnchorAttribute = function(attributeName) { - Object.defineProperty(proto, attributeName, { - get: function() { - return this._anchorElement[attributeName]; - }, - set: function(value) { - this._anchorElement[attributeName] = value; - }, - enumerable: true - }); - }; - - ['hash', 'host', 'hostname', 'port', 'protocol', 'search'] - .forEach(function(attributeName) { - linkURLWithAnchorAttribute(attributeName); - }); - - Object.defineProperties(proto, { - - 'toString': { - get: function() { - var _this = this; - return function() { - return _this.href; - }; - } - }, - - 'href' : { - get: function() { - return this._anchorElement.href.replace(/\?$/,''); - }, - set: function(value) { - this._anchorElement.href = value; - }, - enumerable: true - }, - - 'pathname' : { - get: function() { - return this._anchorElement.pathname.replace(/(^\/?)/,'/'); - }, - set: function(value) { - this._anchorElement.pathname = value; - }, - enumerable: true - }, - - 'origin': { - get: function() { - // get expected port from protocol - var expectedPort = {'http:': 80, 'https:': 443, 'ftp:': 21}[this._anchorElement.protocol]; - // add port to origin if, expected port is different than actual port - // and it is not empty f.e http://foo:8080 - // 8080 != 80 && 8080 != '' - var addPortToOrigin = this._anchorElement.port != expectedPort && - this._anchorElement.port !== '' - - return this._anchorElement.protocol + - '//' + - this._anchorElement.hostname + - (addPortToOrigin ? (':' + this._anchorElement.port) : ''); - }, - enumerable: true - }, - - 'password': { // TODO - get: function() { - return ''; - }, - set: function(value) { - }, - enumerable: true - }, - - 'username': { // TODO - get: function() { - return ''; - }, - set: function(value) { - }, - enumerable: true - }, - - 'searchParams': { - get: function() { - var searchParams = new URLSearchParams(this.search); - var _this = this; - ['append', 'delete', 'set'].forEach(function(methodName) { - var method = searchParams[methodName]; - searchParams[methodName] = function() { - method.apply(searchParams, arguments); - _this.search = searchParams.toString(); - }; - }); - return searchParams; - }, - enumerable: true - } - }); - - URL.createObjectURL = function(blob) { - return _URL.createObjectURL.apply(_URL, arguments); - }; - - URL.revokeObjectURL = function(url) { - return _URL.revokeObjectURL.apply(_URL, arguments); - }; - - global.URL = URL; - - }; - - if(!checkIfURLIsSupported()) { - polyfillURL(); - } - - if((global.location !== void 0) && !('origin' in global.location)) { - var getOrigin = function() { - return global.location.protocol + '//' + global.location.hostname + (global.location.port ? (':' + global.location.port) : ''); - }; - - try { - Object.defineProperty(global.location, 'origin', { - get: getOrigin, - enumerable: true - }); - } catch(e) { - setInterval(function() { - global.location.origin = getOrigin(); - }, 100); - } - } - -})( - (typeof global !== 'undefined') ? global - : ((typeof window !== 'undefined') ? window - : ((typeof self !== 'undefined') ? self : this)) -); diff --git a/frontend/src/app.js b/frontend/src/app.js index b2db86c31..bc153fb87 100644 --- a/frontend/src/app.js +++ b/frontend/src/app.js @@ -4,6 +4,9 @@ import { I18n } from './services/i18n/i18n'; import { UtilRegistry } from './services/util-registry/util-registry'; import { isValidUtility } from './core/utility'; +// load window.fetch polyfill +import 'whatwg-fetch'; + export class App { httpClient = new HttpClient(); htmlHelpers = new HtmlHelpers(); diff --git a/frontend/src/services/html-helpers/html-helpers.js b/frontend/src/services/html-helpers/html-helpers.js index b8bf7771b..5799fee24 100644 --- a/frontend/src/services/html-helpers/html-helpers.js +++ b/frontend/src/services/html-helpers/html-helpers.js @@ -24,7 +24,7 @@ export class HtmlHelpers { } _prefixIds(element, idPrefix) { - const idAttrs = ['id', 'for', 'data-conditional-input', 'data-modal-trigger']; + const idAttrs = ['id', 'for', 'list', 'data-conditional-input', 'data-modal-trigger']; idAttrs.forEach((attr) => { Array.from(element.querySelectorAll('[' + attr + ']')).forEach((input) => { diff --git a/frontend/src/utils/async-form/async-form.js b/frontend/src/utils/async-form/async-form.js index b731c81da..f366f9eae 100644 --- a/frontend/src/utils/async-form/async-form.js +++ b/frontend/src/utils/async-form/async-form.js @@ -1,4 +1,5 @@ import { Utility } from '../../core/utility'; +import { Datepicker } from '../form/datepicker'; import './async-form.scss'; const ASYNC_FORM_INITIALIZED_CLASS = 'check-all--initialized'; @@ -70,7 +71,9 @@ export class AsyncForm { const url = this._element.getAttribute('action'); const headers = { }; - const body = new FormData(this._element); + + // create new FormData and format any date values + const body = Datepicker.unformatAll(this._element, new FormData(this._element)); const isModal = this._element.closest(MODAL_SELECTOR); if (isModal) { diff --git a/frontend/src/utils/async-table/async-table.js b/frontend/src/utils/async-table/async-table.js index ae2db074c..b8330aa4b 100644 --- a/frontend/src/utils/async-table/async-table.js +++ b/frontend/src/utils/async-table/async-table.js @@ -1,4 +1,5 @@ import { Utility } from '../../core/utility'; +import { Datepicker } from '../form/datepicker'; import { HttpClient } from '../../services/http-client/http-client'; import * as debounce from 'lodash.debounce'; import './async-table-filter.scss'; @@ -238,7 +239,9 @@ export class AsyncTable { _serializeTableFilterToURL(tableFilterForm) { const url = new URL(getLocalStorageParameter('currentTableUrl') || window.location.href); - const formData = new FormData(tableFilterForm); + + // create new FormData and format any date values + const formData = Datepicker.unformatAll(this._massInputForm, new FormData(tableFilterForm)); for (var k of url.searchParams.keys()) { url.searchParams.delete(k); @@ -298,7 +301,9 @@ export class AsyncTable { _changePagesizeHandler = () => { const url = new URL(getLocalStorageParameter('currentTableUrl') || window.location.href); - const formData = new FormData(this._pagesizeForm); + + // create new FormData and format any date values + const formData = Datepicker.unformatAll(this._pagesizeForm, new FormData(this._pagesizeForm)); for (var k of url.searchParams.keys()) { url.searchParams.delete(k); diff --git a/frontend/src/utils/course-teaser/course-teaser.js b/frontend/src/utils/course-teaser/course-teaser.js index a729f3036..7f8c32630 100644 --- a/frontend/src/utils/course-teaser/course-teaser.js +++ b/frontend/src/utils/course-teaser/course-teaser.js @@ -7,7 +7,7 @@ var COURSE_TEASER_EXPANDED_CLASS = 'course-teaser__expanded'; var COURSE_TEASER_CHEVRON_CLASS = 'course-teaser__chevron'; @Utility({ - selector: '[uw-course-teaser]:not(.course-teaser__disabled)', + selector: '[uw-course-teaser]:not(.course-teaser__not-expandable)', }) export class CourseTeaser { @@ -28,7 +28,7 @@ export class CourseTeaser { var isLink = event.target.tagName.toLowerCase() === 'a'; var isChevron = event.target.classList.contains(COURSE_TEASER_CHEVRON_CLASS); var isExpanded = this._element.classList.contains(COURSE_TEASER_EXPANDED_CLASS); - + if ((!isExpanded && !isLink) || isChevron) { this._element.classList.toggle(COURSE_TEASER_EXPANDED_CLASS); } diff --git a/frontend/src/utils/course-teaser/course-teaser.scss b/frontend/src/utils/course-teaser/course-teaser.scss index a0a304f33..68576fb1e 100644 --- a/frontend/src/utils/course-teaser/course-teaser.scss +++ b/frontend/src/utils/course-teaser/course-teaser.scss @@ -1,140 +1,276 @@ [uw-course-teaser] { - display: grid; - grid-gap: 5px 7px; - grid-template-columns: 50px 120px 1fr; - padding: 10px; - /* background-color: var(--course-bg-color); */ - transition: background-color .1s ease-out; + --course-border-color: var(--color-grey); + --course-padding-hori: 10px; + --course-padding-vert: 12px; - &:not(.course-teaser__disabled) { - cursor: pointer; - } + display: grid; + position: relative; + grid-gap: 5px 7px; + grid-template-columns: 130px 30px 1fr 60px; + grid-template-areas: + 'shrthnd . title chevron' + 'shrthnd smstr school chevron' + '. . rgstrd . ' + 'tutor tutor name . ' + 'duedate duedate date . ' + 'dscrptn dscrptn dscrptn dscrptn'; + padding: var(--course-padding-vert) var(--course-padding-hori); + transition: background-color .1s ease-out; + cursor: pointer; &:hover { background-color: var(--course-bg-color); } + [uw-course-teaser] { - margin-top: 10px; + border-top: 1px solid var(--course-border-color); } + + @media (max-width: 768px) { + grid-template-columns: 140px 1fr 30px; + grid-template-areas: + 'shrthnd title chevron' + 'shrthnd title . ' + 'smstr school school ' + '. rgstrd rgstrd ' + 'tutor name name ' + 'duedate date date ' + 'dscrptn dscrptn dscrptn'; + } + + @media (max-width: 426px) { + grid-template-columns: 1fr; + grid-template-areas: + 'shrthnd' + 'title' + 'smstr' + 'school' + 'rgstrd' + 'tutor' + 'name' + 'duedate' + 'date' + 'dscrptnlbl' + 'dscrptn' + 'chevron'; + } +} + +.course-teaser__not-expandable { + cursor: initial; } /* chevron */ .course-teaser__chevron { - cursor: pointer; + position: relative; padding: 10px; - grid-column: 1; - grid-row: 2; + grid-area: chevron; justify-self: center; align-self: center; + width: 100%; + height: 100%; + cursor: pointer; &::before { content: ''; + position: absolute; display: block; - margin-top: -8px; + margin-top: -7.35px; + margin-left: -7.35px; /* visually centered */ border-width: 0 3px 3px 0; width: 8px; height: 8px; + top: 50%; + left: 50%; border-color: var(--color-fontsec); border-style: solid; - transform: rotate(45deg); - transition: transform .2s ease-out; + transform: rotate(135deg); + transform-origin: 7.25px 7.25px; /* rotate about visual center */ + transition: all .2s ease-out; + } + + &:hover::before { + transform: scale(1.4) rotate(45deg); + } + + @media (max-width: 768px) { + justify-self: end; + width: auto; + + &::before { + position: initial; + } + } + + @media (max-width: 426px) { + &::before { + transform: rotate(45deg); + margin-left: -7.35px; + } + + &:hover::before { + transform: scale(1.4) rotate(45deg); + } } } /* semester */ .course-teaser__semester { - grid-column: 2; - grid-row: 1; + grid-area: smstr; justify-self: end; - font-size: 1.1rem; a { color: var(--color-fontsec); } + + @media (max-width: 768px) { + justify-self: initial; + } } /* shorthand */ .course-teaser__shorthand { - grid-column: 2; - grid-row: 2; - justify-self: end; - font-size: 1.2rem; - font-weight: bold; - overflow-wrap: anywhere; + position: relative; + grid-area: shrthnd; + font-size: 2rem; + line-height: 1.25; + min-height: calc(2rem * 1.25); + + > a { + position: absolute; + height: 100%; + width: 100%; + overflow: hidden; + + text-overflow: ellipsis; + white-space: nowrap; + word-break: break-any; + + text-decoration: none !important; /* sry. */ + font-weight: 600; + color: var(--color-grey-medium); + } + + /* @media (max-width: 768px) { + * position: initial; + * } + */ } /* title */ .course-teaser__title { - grid-column: 3; - grid-row: 2; + grid-area: title; font-size: 1.2rem; align-self: baseline; } -/* registration */ + /* registration */ .course-teaser__registration { - grid-column: 4; - grid-row: 2; - justify-self: end; - align-self: baseline; + grid-area: rgstrd; color: var(--color-fontsec); + font-weight: bold; } /* school */ -.course-teaser__school-value { - grid-column: 3; - grid-row: 1; - align-self: end; +.course-teaser__school { + grid-area: school; a { color: var(--color-fontsec); } } -/* description */ -.course-teaser__description { - grid-column: 2 / 4; - max-height: 1000px; - overflow: auto; - /* color: var(--color-fontsec); */ +/* duedate */ +.course-teaser__duedate { + grid-area: date; +} + +/* lecturer */ +.course-teaser__lecturer { + grid-area: name; +} + +/* description */ +.course-teaser__description { + grid-area: dscrptn; + max-height: 75vh; + overflow: auto; +} + +/* show description only as dots (overflow text-overflow) and only show when expanded. No "hidden fiddling" */ + +/* labels */ +.course-teaser__lecturer-label { + grid-area: tutor; +} + +.course-teaser__duedate-label { + grid-area: duedate; +} + +.course-teaser__description-label { + grid-area: dscrptnlbl; } -/* subtitle */ .course-teaser__lecturer-label, -.course-teaser__duedate-label, -.course-teaser__school-label { - grid-column: 2; +.course-teaser__description-label, +.course-teaser__duedate-label { justify-self: end; color: var(--color-fontsec); font-style: italic; + + @media (max-width: 768px) { + justify-self: initial; + } + + @media (max-width: 426px) { + margin-top: 7px; + font-weight: bold; + font-style: initial; + } } /* hidden in closed state */ .course-teaser__description, -.course-teaser__registration { +.course-teaser__description-label { display: none; } -/* registered courses */ -.course-teaser__registered { - .course-teaser__registration { - display: block; +/* expanded courses */ +.course-teaser__expanded { + cursor: initial; + + .course-teaser__chevron { + &::before { + transform: rotate(45deg); + } + + &:hover::before { + transform: scale(1.4) rotate(135deg); + } + + @media (max-width: 426px) { + &::before { + transform: rotate(225deg); + } + + &:hover::before { + transform: scale(1.4) rotate(225deg); + } } } -/* expanded courses */ -.course-teaser__expanded { - - .course-teaser__chevron::before { - transform: translateY(7px) rotate(225deg); - } - .course-teaser__school-label, - .course-teaser__school-value, + .course-teaser__school, .course-teaser__duedate-label, - .course-teaser__duedate-value, + .course-teaser__duedate, .course-teaser__description { display: block; } + + @media (max-width: 426px) { + .course-teaser__description-label { + display: block; + } + } } /* @@ -161,12 +297,12 @@ course teaser: header styling text-align: left; border-radius: 20px 20px 20px 20px / 50% 50% 50% 50%; margin-right: 30px; - + .course-header-link { color: white; font-weight: bold; text-decoration: none; - + &:hover { color: inherit; } diff --git a/frontend/src/utils/form/datepicker.js b/frontend/src/utils/form/datepicker.js index 67ddea382..11d7394bc 100644 --- a/frontend/src/utils/form/datepicker.js +++ b/frontend/src/utils/form/datepicker.js @@ -1,30 +1,90 @@ -import flatpickr from 'flatpickr'; +import datetime from 'tail.datetime'; import { Utility } from '../../core/utility'; +import moment from 'moment'; + +const KEYCODE_ESCAPE = 27; + +// INTERNAL (Uni2work specific) formats for formatting dates and/or times +const FORM_DATE_FORMAT = { + 'date': moment.HTML5_FMT.DATE, + 'time': moment.HTML5_FMT.TIME_SECONDS, + 'datetime-local': moment.HTML5_FMT.DATETIME_LOCAL_SECONDS, +}; + +// FANCY (tail.datetime specific) formats for displaying dates and/or times +const FORM_DATE_FORMAT_DATE_DT = 'dd.mm.YYYY'; +const FORM_DATE_FORMAT_TIME_DT = 'HH:ii:ss'; + +// FANCY (moment specific) formats for displaying dates and/or times +const FORM_DATE_FORMAT_DATE_MOMENT = 'DD.MM.YYYY'; +const FORM_DATE_FORMAT_TIME_MOMENT = 'HH:mm:ss'; +const FORM_DATE_FORMAT_MOMENT = { + 'date': FORM_DATE_FORMAT_DATE_MOMENT, + 'time': FORM_DATE_FORMAT_TIME_MOMENT, + 'datetime-local': `${FORM_DATE_FORMAT_DATE_MOMENT} ${FORM_DATE_FORMAT_TIME_MOMENT}`, +}; + +/** + * Takes a string representation of a date and a format string and parses the given date to a Date object. + * If the date string is not valid (i.e. cannot be parsed with the given format string), returns undefined. + * @param {*} dateStr string representation of a date + * @param {*} dateFormat format string of the date + */ +function parseDateWithFormat(dateStr, dateFormat) { + const parsedMomentDate = moment(dateStr, dateFormat); + if (parsedMomentDate.isValid()) return parsedMomentDate.toDate(); +} + +/** + * Takes a string representation of a date, an input ('previous') format and a desired output format and returns a reformatted date string. + * If the date string is not valid (i.e. cannot be parsed with the given input format string), returns the original date string; + * @param {*} dateStr string representation of a date (needs to be in format formatIn) + * @param {*} formatIn input format string + * @param {*} formatOut format string of the desired output date string + */ +function reformatDateString(dateStr, formatIn, formatOut) { + const parsedMomentDate = moment(dateStr, formatIn); + return parsedMomentDate.isValid() ? parsedMomentDate.format(formatOut) : dateStr; +} const DATEPICKER_UTIL_SELECTOR = 'input[type="date"], input[type="time"], input[type="datetime-local"]'; const DATEPICKER_INITIALIZED_CLASS = 'datepicker--initialized'; const DATEPICKER_CONFIG = { - 'datetime-local': { - enableTime: true, - altInput: true, - altFormat: 'j. F Y, H:i', // maybe interpolate these formats for locale - dateFormat: 'Y-m-dTH:i', - time_24hr: true, + 'global': { + // minimize overlaps with other date inputs + position: 'right', + + // set default time to 00:00:00 + timeHours: 0, + timeMinutes: 0, + timeSeconds: 0, + + // german settings + // TODO: hardcoded, get from current language / settings + locale: 'de', + weekStart: 1, + dateFormat: FORM_DATE_FORMAT_DATE_DT, + timeFormat: FORM_DATE_FORMAT_TIME_DT, + + // prevent the instance from closing when selecting a date before selecting a time + stayOpen: true, + + // hide the close button (we handle closing the datepicker manually by clicking outside) + closeButton: false, + + // disable the decades view because nobody will ever need it (i.e. cap the switch to the more relevant year view) + viewDecades: false, }, + 'datetime-local': {}, 'date': { - altFormat: 'j. F Y', - dateFormat: 'Y-m-d', - altInput: true, + // disable date picker + timeFormat: false, }, 'time': { - enableTime: true, - noCalendar: true, - altFormat: 'H:i', - dateFormat: 'H:i', - altInput: true, - time_24hr: true, + // disable time picker + dateFormat: false, }, }; @@ -33,7 +93,12 @@ const DATEPICKER_CONFIG = { }) export class Datepicker { - flatpickrInstance; + // singleton Map that maps a formID to a Map of Datepicker objects + static datepickerCollections; + + datepickerInstance; + _element; + elementType; constructor(element) { if (!element) { @@ -44,19 +109,140 @@ export class Datepicker { return false; } - const flatpickrConfig = DATEPICKER_CONFIG[element.getAttribute('type')]; + // initialize datepickerCollections singleton if not already done + if (!Datepicker.datepickerCollections) { + Datepicker.datepickerCollections = new Map(); + } - if (!flatpickrConfig) { + this._element = element; + + // store the previously set type to select the input format + this.elementType = this._element.getAttribute('type'); + + // get all relevant config options for this datepicker type + const datepickerGlobalConfig = DATEPICKER_CONFIG['global']; + const datepickerConfig = DATEPICKER_CONFIG[this.elementType]; + + // manually set the type attribute to text because datepicker handles displaying the date + this._element.setAttribute('type', 'text'); + + // additional position config (optional data-datepicker-position attribute in html) that can specialize the global config + const datepickerPosition = this._element.dataset.datepickerPosition; + if (datepickerPosition) { + datepickerGlobalConfig.position = datepickerPosition; + } + + if (!datepickerConfig || !FORM_DATE_FORMAT[this.elementType]) { throw new Error('Datepicker utility called on unsupported element!'); } - this.flatpickrInstance = flatpickr(element, flatpickrConfig); + // initialize tail.datetime (datepicker) instance + this.datepickerInstance = datetime(this._element, { ...datepickerGlobalConfig, ...datepickerConfig }); - // mark initialized - element.classList.add(DATEPICKER_INITIALIZED_CLASS); + // register this datepicker instance with the formID of the given element in the datepicker collection + const formID = this._element.form.id; + const elemID = this._element.id; + if (!Datepicker.datepickerCollections.has(formID)) { + // insert a new key value pair if the formID key is not there already + Datepicker.datepickerCollections.set(formID, new Map([[elemID, this]])); + } else { + // otherwise, insert this instance into the Map + Datepicker.datepickerCollections.get(formID).set(elemID, this); + } + + // mark the form input element as initialized + this._element.classList.add(DATEPICKER_INITIALIZED_CLASS); + + const setDatepickerDate = () => { + // try to parse the current input element value with fancy and internal format string + const parsedMomentDate = moment(this._element.value, FORM_DATE_FORMAT_MOMENT[this.elementType]); + const parsedMomentDateInternal = moment(this._element.value, FORM_DATE_FORMAT[this.elementType]); + + // only set the datepicker date if the input is either in valid fancy format or in valid internal format + if (parsedMomentDate.isValid()) { + this.datepickerInstance.selectDate(parsedMomentDate.toDate()); + } else if (parsedMomentDateInternal.isValid()) { + this.datepickerInstance.selectDate(parsedMomentDateInternal.toDate()); + } + + // reregister change event to prevent event loop + this._element.addEventListener('change', setDatepickerDate, { once: true }); + }; + // change the selected date in the tail.datetime instance if the value of the input element is changed + this._element.addEventListener('change', setDatepickerDate, { once: true }); + + // close the instance if something other than the instance was clicked (i.e. if the target is not within the datepicker instance and if any previously clicked calendar view was replaced (is not in the window anymore) because it was clicked). YES, I KNOW + window.addEventListener('click', event => { + if (!this.datepickerInstance.dt.contains(event.target) && window.document.contains(event.target)) { + this.datepickerInstance.close(); + } + }); + + // close the datepicker on escape keydown events + this._element.addEventListener('keydown', event => { + if (event.keyCode === KEYCODE_ESCAPE) { + this.datepickerInstance.close(); + } + }); + + // format the date value of the form input element of this datepicker before form submission + this._element.form.addEventListener('submit', () => this.formatElementValue()); + + // format any existing dates to fancy display format on pageload + this.formatElementValue(true); } destroy() { - this.flatpickrInstance.destroy(); + this.datepickerInstance.remove(); } -} + + /** + * Formats the value of this input element from datepicker format (i.e. DATEPICKER_CONFIG.dateFormat + " " + datetime.defaults.timeFormat) to Uni2work internal date format (i.e. FORM_DATE_FORMAT) required for form submission + * @param {*} toFancy optional target format switch (boolean value; default is false). If set to a truthy value, formats the element value to fancy instead of internal date format. + */ + formatElementValue(toFancy) { + const dp = this.datepickerInstance; + if (this._element.value) { + if (toFancy) { + const parsedDate = parseDateWithFormat(this._element.value, FORM_DATE_FORMAT[this.elementType]); + if (parsedDate) dp.selectDate(); + } else { + this._element.value = this.unformat(); + } + } + } + + /** + * Returns a datestring in internal format from the current state of the input element value. + */ + unformat() { + return reformatDateString(this._element.value, FORM_DATE_FORMAT_MOMENT[this.elementType], FORM_DATE_FORMAT[this.elementType]); + } + + /** + * Takes a Form and a FormData and returns a new FormData with all dates formatted to uni2work date format. This function will not change the value of the date input elements. + * @param {*} form Form for which all dates will be formatted in the FormData + * @param {*} formData Initial FormData + */ + static unformatAll(form, formData) { + // only proceed if there are any datepickers and if both form and formData are defined + if (Datepicker.datepickerCollections && form && formData) { + // if the form has no id, assign one randomly + if (!form.id) { + form.id = `f${Math.floor(Math.random() * 100000)}`; + } + + const formId = form.id; + + if (Datepicker.datepickerCollections.has(formId)) { + const datepickerInstances = Datepicker.datepickerCollections.get(formId); + datepickerInstances.forEach(instance => { + formData.set(instance._element.name, instance.unformat()); + }); + } + } + + // return the (possibly changed) FormData + return formData; + } +} \ No newline at end of file diff --git a/frontend/src/utils/form/datepicker.md b/frontend/src/utils/form/datepicker.md index dfee3b3fa..52429b42a 100644 --- a/frontend/src/utils/form/datepicker.md +++ b/frontend/src/utils/form/datepicker.md @@ -6,3 +6,9 @@ Provides UI for entering dates and times ## Example usage: (any form that uses inputs of type date, time, or datetime-local) + +## Methods + +### static unformatAll(form, formData) + +Call this function on a form and its formData to get back a new FormData object with "unformatted" date values (i.e. all dates formatted from fancy format to backend format). \ No newline at end of file diff --git a/frontend/src/utils/form/interactive-fieldset.js b/frontend/src/utils/form/interactive-fieldset.js index 9c080e04f..0f6a7a395 100644 --- a/frontend/src/utils/form/interactive-fieldset.js +++ b/frontend/src/utils/form/interactive-fieldset.js @@ -60,7 +60,7 @@ export class InteractiveFieldset { // add event listener const observer = new MutationObserver(() => this._updateVisibility()); - observer.observe(this.conditionalInput, { attributes: true, attributeFilter: ['disabled'] }); + observer.observe(this.conditionalInput, { attributes: true, attributeFilter: ['data-interactive-fieldset-hidden'] }); this.conditionalInput.addEventListener('input', () => this._updateVisibility()); // initial visibility update @@ -76,18 +76,21 @@ export class InteractiveFieldset { } _updateVisibility() { - const active = this._matchesConditionalValue() && !this.conditionalInput.disabled; + const active = this._matchesConditionalValue() && !this.conditionalInput.dataset.interactiveFieldsetHidden; this.target.classList.toggle('hidden', !active); - this.childInputs.forEach((el) => { - el.disabled = !active; + this.childInputs.forEach((el) => this._updateChildVisibility(el, active)); + } - // disable input for flatpickrs added input as well if exists - if (el._flatpickr) { - el._flatpickr.altInput.disabled = !active; - } - }); + _updateChildVisibility(el, active) { + el.disabled = !active; + + if (active) { + delete el.dataset.interactiveFieldsetHidden; + } else { + el.dataset.interactiveFieldsetHidden = true; + } } _matchesConditionalValue() { diff --git a/frontend/src/utils/form/navigate-away-prompt.js b/frontend/src/utils/form/navigate-away-prompt.js index 21c4f4661..b0542ef9f 100644 --- a/frontend/src/utils/form/navigate-away-prompt.js +++ b/frontend/src/utils/form/navigate-away-prompt.js @@ -2,6 +2,16 @@ import { Utility } from '../../core/utility'; import { AUTO_SUBMIT_BUTTON_UTIL_SELECTOR } from './auto-submit-button'; import { AUTO_SUBMIT_INPUT_UTIL_SELECTOR } from './auto-submit-input'; +/** + * Key generator from an arbitrary number of FormData objects. + * @param {...any} formDatas FormData objects + */ +function* generatorFromFormDatas(...formDatas) { + for (let formData of formDatas) { + yield* formData.keys(); + } +} + const NAVIGATE_AWAY_PROMPT_INITIALIZED_CLASS = 'navigate-away-prompt--initialized'; const NAVIGATE_AWAY_PROMPT_UTIL_OPTOUT = '[uw-no-navigate-away-prompt]'; @@ -12,7 +22,7 @@ export class NavigateAwayPrompt { _element; - _touched = false; + _initFormData; _unloadDueToSubmit = false; constructor(element) { @@ -21,6 +31,7 @@ export class NavigateAwayPrompt { } this._element = element; + this._initFormData = new FormData(this._element); if (this._element.classList.contains(NAVIGATE_AWAY_PROMPT_INITIALIZED_CLASS)) { return false; @@ -40,10 +51,6 @@ export class NavigateAwayPrompt { this._element.addEventListener('submit', () => { this._unloadDueToSubmit = true; }); - this._element.addEventListener('change', () => { - this._touched = true; - this._unloadDueToSubmit = false; - }); // mark initialized this._element.classList.add(NAVIGATE_AWAY_PROMPT_INITIALIZED_CLASS); @@ -55,9 +62,20 @@ export class NavigateAwayPrompt { } _beforeUnloadHandler = (event) => { + // compare every value of the current FormData with every corresponding value of the initial FormData and set formDataHasChanged to true if there is at least one change + const currentFormData = new FormData(this._element); + var formDataHasChanged = false; + for (let key of generatorFromFormDatas(this._initFormData, currentFormData)) { + if (currentFormData.get(key) !== this._initFormData.get(key)) { + formDataHasChanged = true; + break; + } + } + // allow the event to happen if the form was not touched by the - // user or the unload event was initiated by a form submit - if (!this._touched || this._unloadDueToSubmit) { + // user (i.e. if the current FormData is equal to the initial FormData) + // or the unload event was initiated by a form submit + if (!formDataHasChanged || this._unloadDueToSubmit) { return false; } diff --git a/frontend/src/utils/inputs/radio.scss b/frontend/src/utils/inputs/radio.scss index 538d6d539..8e36edbda 100644 --- a/frontend/src/utils/inputs/radio.scss +++ b/frontend/src/utils/inputs/radio.scss @@ -64,9 +64,3 @@ border-bottom-right-radius: 4px; } } - -@media (max-width: 768px) { - .radio + .radio { - margin-left: 10px; - } -} diff --git a/frontend/src/utils/mass-input/mass-input.js b/frontend/src/utils/mass-input/mass-input.js index 87d17c8c3..f6c8357b2 100644 --- a/frontend/src/utils/mass-input/mass-input.js +++ b/frontend/src/utils/mass-input/mass-input.js @@ -1,4 +1,6 @@ import { Utility } from '../../core/utility'; +import { Datepicker } from '../form/datepicker'; +import './mass-input.scss'; const MASS_INPUT_CELL_SELECTOR = '.massinput__cell'; const MASS_INPUT_ADD_CELL_SELECTOR = '.massinput__cell--add'; @@ -157,7 +159,8 @@ export class MassInput { } _serializeForm(submitButton, enctype) { - const formData = new FormData(this._massInputForm); + // create new FormData and format any date values + const formData = Datepicker.unformatAll(this._massInputForm, new FormData(this._massInputForm)); // manually add name and value of submit button to formData formData.append(submitButton.name, submitButton.value); diff --git a/frontend/src/utils/mass-input/mass-input.scss b/frontend/src/utils/mass-input/mass-input.scss new file mode 100644 index 000000000..d8f006d36 --- /dev/null +++ b/frontend/src/utils/mass-input/mass-input.scss @@ -0,0 +1,18 @@ +.massinput-list__wrapper, .massinput-list__cell { + display: grid; + grid: auto / auto 50px; + max-width: 600px; + grid-gap: 7px; +} + +.massinput-list__field { + grid-column: 1; +} + +.massinput-list__add, .massinput-list__delete { + grid-column: 2; +} + +.massinput-list__cell { + grid-column: 1 / 3; +} diff --git a/frontend/vendor/datetime.css b/frontend/vendor/datetime.css new file mode 100644 index 000000000..7c1172da1 --- /dev/null +++ b/frontend/vendor/datetime.css @@ -0,0 +1,727 @@ +@charset "UTF-8"; +/* + | tail.datetime - The vanilla way to select dates and times! + | @file ./less/tail.datetime-default-green.less + | @author SamBrishes + | @version 0.4.13 - Beta + | + | @website https://github.com/pytesNET/tail.DateTime + | @license X11 / MIT License + | @copyright Copyright © 2018 - 2019 SamBrishes, pytesNET + */ + +/* @start MAIN CALENDAR */ +.tail-datetime-calendar, .tail-datetime-calendar *, .tail-datetime-calendar *:before, +.tail-datetime-calendar *:after{ + box-sizing: border-box; + -webkit-box-sizing: border-box; +} +.tail-datetime-calendar{ + top: 0; + left: 0; + width: 275px; + height: auto; + margin: 15px; + padding: 0; + z-index: 15; + display: block; + position: absolute; + visibility: hidden; + direction: ltr; + border-collapse: separate; + font-family: "Open Sans", Calibri, Arial, sans-serif; + background-color: white; + border-width: 0; + border-style: solid; + border-color: transparent; + border-radius: 3px; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3125); + -webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3125); +} +.tail-datetime-calendar:after{ + clear: both; + content: ""; + display: block; + font-size: 0; + visibility: hidden; +} +.tail-datetime-calendar.calendar-static{ + top: auto; + left: auto; + margin-left: auto; + margin-right: auto; + position: static; + visibility: visible; +} +.tail-datetime-calendar button.calendar-close{ + top: 100%; + right: 15px; + color: #303438; + width: 35px; + height: 25px; + margin: 1px 0 0 0; + padding: 5px 10px; + opacity: 0.5; + outline: none; + display: inline-block; + position: absolute; + font-size: 14px; + line-height: 1.125em; + text-shadow: none; + background-color: white; + background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC\ + 9zdmciIHdpZHRoPSIxMiIgaGVpZ2h0PSIxNiIgdmlld0JveD0iMCAwIDEyIDE2Ij48cGF0aCBmaWxsPSIjMzAzNDM4IiBkP\ + SJNNy40OCA4bDMuNzUgMy43NS0xLjQ4IDEuNDhMNiA5LjQ4bC0zLjc1IDMuNzUtMS40OC0xLjQ4TDQuNTIgOCAuNzcgNC4y\ + NWwxLjQ4LTEuNDhMNiA2LjUybDMuNzUtMy43NSAxLjQ4IDEuNDhMNy40OCA4eiIvPjwvc3ZnPg=="); + background-repeat: no-repeat; + background-position: center center; + border-width: 0; + border-style: solid; + border-color: transparent; + border-radius: 0 0 3px 3px; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3125); + -webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3125); + transition: opacity 142ms linear; + -webkit-transition: opacity 142ms linear; +} +.tail-datetime-calendar button.calendar-close:hover{ + opacity: 1; +} +/* @end MAIN CALENDAR */ + +/* @start CALENDAR TOOLTIP */ +.tail-datetime-calendar .calendar-tooltip{ + color: white; + width: auto; + margin: 0; + padding: 0; + display: block; + position: absolute; + background-color: #202428; + border-radius: 3px; +} +.tail-datetime-calendar .calendar-tooltip:before{ + top: -7px; + left: 50%; + width: 0; + height: 0; + margin: 0 0 0 -6px; + content: ""; + display: block; + position: absolute; + border-width: 0 7px 7px 7px; + border-style: solid; + border-color: transparent transparent #202428 transparent; +} +.tail-datetime-calendar .calendar-tooltip .tooltip-inner{ + width: auto; + margin: 0; + padding: 4px 7px; + display: block; + font-size: 12px; + line-height: 14px; +} +/* @end CALENDAR TOOLTIP */ + +/* @start CALENDAR ACTIONs */ +.tail-datetime-calendar .calendar-actions{ + color: white; + width: 100%; + height: 36px; + margin: 0; + padding: 0; + display: table; + overflow: hidden; + border-spacing: 0; + border-collapse: separate; + background-color: var(--color-primary); + border-width: 0; + border-style: solid; + border-color: transparent; + border-radius: 3px 3px 0 0; +} +.tail-datetime-calendar .calendar-actions span{ + margin: 0; + padding: 0; + display: table-cell; + position: relative; + text-align: center; + line-height: 36px; + text-shadow: -1px -1px 0 var(--color-dark); + background-repeat: no-repeat; + background-position: center center; +} +.tail-datetime-calendar .calendar-actions span[data-action]{ + cursor: pointer; +} +.tail-datetime-calendar .calendar-actions span.action{ + width: 36px; + font-size: 22px; +} +.tail-datetime-calendar .calendar-actions span.label{ + width: auto; +} +.tail-datetime-calendar .calendar-actions span:first-child:before{ + right: -1px; +} +.tail-datetime-calendar .calendar-actions span:last-child:before{ + left: -1px; +} +.tail-datetime-calendar .calendar-actions span:first-child:hover:before, +.tail-datetime-calendar .calendar-actions span:last-child:hover:before{ + display: none; +} +.tail-datetime-calendar .calendar-actions span[data-action]:hover{ + background-color: var(--color-dark); +} +.tail-datetime-calendar .calendar-actions span.action-prev{ + background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC\ + 9zdmciIHdpZHRoPSI2IiBoZWlnaHQ9IjE2IiB2aWV3Qm94PSIwIDAgNiAxNiI+PHBhdGggZmlsbD0iI2ZmZmZmZiIgZD0iT\ + TYgMkwwIDhsNiA2VjJ6Ii8+PC9zdmc+"); +} +.tail-datetime-calendar .calendar-actions span.action-next{ + background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC\ + 9zdmciIHdpZHRoPSI2IiBoZWlnaHQ9IjE2IiB2aWV3Qm94PSIwIDAgNiAxNiI+PHBhdGggZmlsbD0iI2ZmZmZmZiIgZD0iT\ + TAgMTRsNi02LTYtNnYxMnoiLz48L3N2Zz4="); +} +.tail-datetime-calendar .calendar-actions span.action-submit{ + background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC\ + 9zdmciIHdpZHRoPSIxMiIgaGVpZ2h0PSIxNiIgdmlld0JveD0iMCAwIDEyIDE2Ij48cGF0aCBmaWxsPSIjZmZmZmZmIiBkP\ + SJNMTIgNWwtOCA4LTQtNCAxLjUtMS41TDQgMTBsNi41LTYuNUwxMiA1eiIvPjwvc3ZnPg=="); +} +.tail-datetime-calendar .calendar-actions span.action-cancel{ + background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC\ + 9zdmciIHdpZHRoPSIxMiIgaGVpZ2h0PSIxNiIgdmlld0JveD0iMCAwIDEyIDE2Ij48cGF0aCBmaWxsPSIjZmZmZmZmIiBkP\ + SJNNy40OCA4bDMuNzUgMy43NS0xLjQ4IDEuNDhMNiA5LjQ4bC0zLjc1IDMuNzUtMS40OC0xLjQ4TDQuNTIgOCAuNzcgNC4y\ + NWwxLjQ4LTEuNDhMNiA2LjUybDMuNzUtMy43NSAxLjQ4IDEuNDhMNy40OCA4eiIvPjwvc3ZnPg=="); +} +/* @end CALENDAR ACTIONs */ + +/* @start CALENDAR DATEPICKER */ +.tail-datetime-calendar .calendar-datepicker{ + width: 100%; + margin: 0; + padding: 0; + display: block; + position: relative; +} +.tail-datetime-calendar .calendar-datepicker table{ + width: 100%; + margin: 0; + padding: 0; + border-spacing: 0; + border-collapse: separate; +} +.tail-datetime-calendar .calendar-datepicker table tr th, +.tail-datetime-calendar .calendar-datepicker table tr td{ + color: #303438; + height: 30px; + padding: 0; + position: relative; + font-size: 13px; + text-align: center; + font-weight: normal; + text-shadow: none; + line-height: 30px; + background-color: transparent; + border-width: 0; + border-style: solid; + border-color: transparent; + border-radius: 0px; +} +.tail-datetime-calendar .calendar-datepicker table tr th{ + color: white; + background-color: var(--color-lightblack); +} +.tail-datetime-calendar .calendar-datepicker table tr td{ + cursor: pointer; +} +.tail-datetime-calendar .calendar-datepicker table tr td span.inner{ + margin: 0; + padding: 0; + display: inline-block; +} +.tail-datetime-calendar .calendar-datepicker table tr td.date-disabled{ + cursor: not-allowed; + color: #909498; + background-color: #F0F0F0; +} +.tail-datetime-calendar .calendar-datepicker table tr td.date-disabled:after{ + left: 3px; + bottom: 3px; + width: 35px; + height: 1px; + margin: 0; + padding: 0; + content: ""; + display: inline-block; + position: absolute; + background-color: #bfbfbf; + transform-origin: 2px -5px; + transform: rotate(-45deg); + -moz-transform: rotate(-45deg); + -webkit-transform: rotate(-45deg); +} +.tail-datetime-calendar .calendar-datepicker table tr td.date-previous, +.tail-datetime-calendar .calendar-datepicker table tr td.date-next{ + color: #909498; + background-color: #F0F0F0; +} +.tail-datetime-calendar .calendar-datepicker table tr td.date-today:before, +.tail-datetime-calendar .calendar-datepicker table tr td .tooltip-tick{ + top: 5px; + width: 5px; + height: 5px; + margin: 0; + padding: 0; + z-index: 20; + content: ""; + display: inline-block; + position: absolute; + border-width: 0; + border-style: solid; + border-color: transparent; + border-radius: 50%; +} +.tail-datetime-calendar .calendar-datepicker table tr td.date-today:before{ + left: 5px; + background-color: #E67D1E; +} +.tail-datetime-calendar .calendar-datepicker table tr td .tooltip-tick{ + right: 5px; + background-color: #202428; +} +.tail-datetime-calendar .calendar-datepicker table tr td .tooltip-tick:before, +.tail-datetime-calendar .calendar-datepicker table tr td .tooltip-tick:after{ + display: none; +} +.tail-datetime-calendar .calendar-datepicker table tr th.calendar-week, +.tail-datetime-calendar .calendar-datepicker table tr td.calendar-day{ + width: 14.28571429%; + height: 35px; +} +.tail-datetime-calendar .calendar-datepicker table tr th.calendar-week span.inner, +.tail-datetime-calendar .calendar-datepicker table tr td.calendar-day span.inner{ + width: 31px; + height: 31px; + line-height: 29px; + border-width: 1px; + border-style: solid; + border-color: transparent; + border-radius: 50%; +} +.tail-datetime-calendar .calendar-datepicker table tr th.calendar-week:hover span.inner, +.tail-datetime-calendar .calendar-datepicker table tr td.calendar-day:hover span.inner{ + border-color: #cccccc; +} +.tail-datetime-calendar .calendar-datepicker table tr th.calendar-week.date-disabled span.inner, +.tail-datetime-calendar .calendar-datepicker table tr td.calendar-day.date-disabled span.inner, +.tail-datetime-calendar .calendar-datepicker table tr th.calendar-week.date-disabled:hover span.inner, +.tail-datetime-calendar .calendar-datepicker table tr td.calendar-day.date-disabled:hover span.inner{ + border-color: transparent; +} +.tail-datetime-calendar .calendar-datepicker table tr th.calendar-week.date-select span.inner, +.tail-datetime-calendar .calendar-datepicker table tr td.calendar-day.date-select span.inner, +.tail-datetime-calendar .calendar-datepicker table tr th.calendar-week.date-select:hover span.inner, +.tail-datetime-calendar .calendar-datepicker table tr td.calendar-day.date-select:hover span.inner{ + color: var(--color-fontsec); + border-color: var(--color-fontsec); +} +.tail-datetime-calendar .calendar-datepicker table tr td.calendar-month, +.tail-datetime-calendar .calendar-datepicker table tr td.calendar-year, +.tail-datetime-calendar .calendar-datepicker table tr td.calendar-decade{ + width: 33.33333333%; + height: 40px; + transition: color 142ms linear; + -webkit-transition: color 142ms linear; +} +.tail-datetime-calendar .calendar-datepicker table tr td.calendar-month.date-today:before, +.tail-datetime-calendar .calendar-datepicker table tr td.calendar-year.date-today:before, +.tail-datetime-calendar .calendar-datepicker table tr td.calendar-decade.date-today:before{ + left: 50%; + margin-left: -2.5px; +} +.tail-datetime-calendar .calendar-datepicker table tr td.calendar-month span.inner, +.tail-datetime-calendar .calendar-datepicker table tr td.calendar-year span.inner, +.tail-datetime-calendar .calendar-datepicker table tr td.calendar-decade span.inner{ + width: auto; + height: 31px; + line-height: 29px; +} +.tail-datetime-calendar .calendar-datepicker table tr td.calendar-month span.inner:before, +.tail-datetime-calendar .calendar-datepicker table tr td.calendar-year span.inner:before, +.tail-datetime-calendar .calendar-datepicker table tr td.calendar-decade span.inner:before, +.tail-datetime-calendar .calendar-datepicker table tr td.calendar-month span.inner:after, +.tail-datetime-calendar .calendar-datepicker table tr td.calendar-year span.inner:after, +.tail-datetime-calendar .calendar-datepicker table tr td.calendar-decade span.inner:after{ + width: 20px; + height: 20px; + content: ""; + z-index: 15; + display: inline-block; + position: absolute; + border-width: 1px; + border-style: solid; + border-color: transparent; + transition: all 142ms linear; + -webkit-transition: all 142ms linear; +} +.tail-datetime-calendar .calendar-datepicker table tr td.calendar-month span.inner:before, +.tail-datetime-calendar .calendar-datepicker table tr td.calendar-year span.inner:before, +.tail-datetime-calendar .calendar-datepicker table tr td.calendar-decade span.inner:before{ + top: 0; + left: 0; +} +.tail-datetime-calendar .calendar-datepicker table tr td.calendar-month:hover span.inner:before, +.tail-datetime-calendar .calendar-datepicker table tr td.calendar-year:hover span.inner:before, +.tail-datetime-calendar .calendar-datepicker table tr td.calendar-decade:hover span.inner:before{ + top: 6px; + left: 6px; + border-top-color: #cccccc; + border-left-color: #cccccc; +} +.tail-datetime-calendar .calendar-datepicker table tr td.calendar-month span.inner:after, +.tail-datetime-calendar .calendar-datepicker table tr td.calendar-year span.inner:after, +.tail-datetime-calendar .calendar-datepicker table tr td.calendar-decade span.inner:after{ + right: 0; + bottom: 0; +} +.tail-datetime-calendar .calendar-datepicker table tr td.calendar-month:hover span.inner:after, +.tail-datetime-calendar .calendar-datepicker table tr td.calendar-year:hover span.inner:after, +.tail-datetime-calendar .calendar-datepicker table tr td.calendar-decade:hover span.inner:after{ + right: 6px; + bottom: 6px; + border-right-color: #cccccc; + border-bottom-color: #cccccc; +} +.tail-datetime-calendar .calendar-datepicker table tr td.calendar-year, +.tail-datetime-calendar .calendar-datepicker table tr td.calendar-decade{ + width: 25%; +} +.tail-datetime-calendar .calendar-datepicker table tr td.calendar-decade span.inner{ + height: 54px; + padding: 7px 15px; + text-align: left; + line-height: 20px; +} +/* @end CALENDAR DATEPICKER */ + +/* @start CALENDAR TIMEPICKER */ +.tail-datetime-calendar .calendar-timepicker{ + width: 100%; + margin: 0; + padding: 0; + display: block; + text-align: center; + border-width: 1px 0 0 0; + border-style: solid; + border-color: #d9d9d9; +} +.tail-datetime-calendar .calendar-timepicker .timepicker-field{ + width: 28%; + margin: 0; + padding: 15px 0 7px 0; + display: inline-block; + position: relative; + text-align: center; +} +.tail-datetime-calendar .calendar-timepicker .timepicker-field:first-of-type{ + text-align: right; +} +.tail-datetime-calendar .calendar-timepicker .timepicker-field:last-of-type{ + text-align: left; +} +.tail-datetime-calendar .calendar-timepicker .timepicker-field input[type="text"]{ + color: #303438; + width: 100%; + height: 29px; + margin: 0; + z-index: 4; + padding: 3px 20px 3px 5px; + outline: 0; + display: inline-block; + position: relative; + font-size: 12px; + text-align: center; + line-height: 23px; + appearance: textfield; + -moz-appearance: textfield; + -webkit-appearance: textfield; + background-color: #F0F0F0; + border-width: 0; + border-style: solid; + border-color: transparent; + border-radius: 3px; + box-shadow: none; + -webkit-box-shadow: none; + transition: color 142ms linear, border 142ms linear, background 142ms linear; + -webkit-transition: color 142ms linear, border 142ms linear, background 142ms linear; +} +.tail-datetime-calendar .calendar-timepicker .timepicker-field input[type="text"]:hover{ + color: #303438; + background-color: #E0E0E0; +} +.tail-datetime-calendar .calendar-timepicker .timepicker-field input[type="text"]:focus{ + color: #303438; + background-color: #E0E0E0; +} +.tail-datetime-calendar .calendar-timepicker .timepicker-field input[type="text"]:disabled{ + cursor: not-allowed; + color: #A0A4A8; + background-color: #F6F6F6; +} +.tail-datetime-calendar .calendar-timepicker .timepicker-field button.picker-step{ + min-width: 0px; + width: 20px; + height: 15px; + right: 0; + margin: 0; + padding: 0; + z-index: 15; + display: inline-block; + position: absolute; + background-color: #F0F0F0; + box-shadow: none; + -webkit-box-shadow: none; + transition: border 142ms linear, background 142ms linear; + -webkit-transition: border 142ms linear, background 142ms linear; +} +.tail-datetime-calendar .calendar-timepicker .timepicker-field button.picker-step:before{ + top: 4px; + left: 50%; + width: 0; + height: 0; + margin: 0 0 0 -4px; + padding: 0; + content: ""; + display: inline-block; + position: absolute; + transition: border 142ms linear; + -webkit-transition: border 142ms linear; +} +.tail-datetime-calendar .calendar-timepicker .timepicker-field button.picker-step.step-up{ + top: 15px; + border-width: 0 0 1px 1px; + border-style: solid; + border-color: white; + border-radius: 0 2px 0 0; +} +.tail-datetime-calendar .calendar-timepicker .timepicker-field button.picker-step.step-up:hover{ + background-color: #E0E0E0; +} +.tail-datetime-calendar .calendar-timepicker .timepicker-field button.picker-step.step-up:before{ + border-width: 0 4px 5px 4px; + border-style: solid; + border-color: transparent transparent #303438 transparent; +} +.tail-datetime-calendar .calendar-timepicker .timepicker-field button.picker-step.step-down{ + top: 29px; + border-width: 1px 0 0 1px; + border-style: solid; + border-color: white; + border-radius: 0 0 2px 0; +} +.tail-datetime-calendar .calendar-timepicker .timepicker-field button.picker-step.step-down:hover{ + background-color: #E0E0E0; +} +.tail-datetime-calendar .calendar-timepicker .timepicker-field button.picker-step.step-down:before{ + border-width: 5px 4px 0 4px; + border-style: solid; + border-color: #303438 transparent transparent transparent; +} +.tail-datetime-calendar .calendar-timepicker .timepicker-field input:focus + button.step-up{ + border-color: rgba(255, 255, 255, 0.8); + background-color: var(--color-primary); +} +.tail-datetime-calendar .calendar-timepicker .timepicker-field input:focus + button.step-up:hover{ + background-color: var(--color-dark); +} +.tail-datetime-calendar .calendar-timepicker .timepicker-field input:focus + button.step-up:before{ + border-bottom-color: white; +} +.tail-datetime-calendar .calendar-timepicker .timepicker-field input:focus + button + button.step-down{ + border-color: rgba(255, 255, 255, 0.8); + background-color: var(--color-primary); +} +.tail-datetime-calendar .calendar-timepicker .timepicker-field input:focus + button + button.step-down:hover{ + background-color: var(--color-dark); +} +.tail-datetime-calendar .calendar-timepicker .timepicker-field input:focus + button + button.step-down:before{ + border-top-color: white; +} +.tail-datetime-calendar .calendar-timepicker .timepicker-field input:disabled + button.step-up{ + cursor: not-allowed; + border-color: rgba(255, 255, 255, 0.8); + background-color: #F6F6F6; +} +.tail-datetime-calendar .calendar-timepicker .timepicker-field input:disabled + button.step-up:hover{ + background-color: #F6F6F6; +} +.tail-datetime-calendar .calendar-timepicker .timepicker-field input:disabled + button.step-up:before{ + border-bottom-color: #A0A4A8; +} +.tail-datetime-calendar .calendar-timepicker .timepicker-field input:disabled + button + button.step-down{ + cursor: not-allowed; + border-color: rgba(255, 255, 255, 0.8); + background-color: #F6F6F6; +} +.tail-datetime-calendar .calendar-timepicker .timepicker-field input:disabled + button + button.step-down:hover{ + background-color: #F6F6F6; +} +.tail-datetime-calendar .calendar-timepicker .timepicker-field input:disabled + button + button.step-down:before{ + border-top-color: #A0A4A8; +} +.tail-datetime-calendar .calendar-timepicker .timepicker-field label{ + color: #303438; + margin: 0; + padding: 0; + display: block; + font-size: 12px; + text-align: center; +} +.tail-datetime-calendar .calendar-timepicker label.timepicker-switch{ + cursor: pointer; + margin: 15px 0 -5px 0; + display: block; + text-align: center; + vertical-align: top; +} +.tail-datetime-calendar .calendar-timepicker label.timepicker-switch:before, +.tail-datetime-calendar .calendar-timepicker label.timepicker-switch:after{ + width: auto; + margin: 0; + padding: 0 5px; + font-size: 12px; + line-height: 16px; + vertical-align: top; +} +.tail-datetime-calendar .calendar-timepicker label.timepicker-switch:before{ + content: attr(data-am); +} +.tail-datetime-calendar .calendar-timepicker label.timepicker-switch:after{ + content: attr(data-pm); +} +.tail-datetime-calendar .calendar-timepicker label.timepicker-switch input[type="checkbox"]{ + display: none; +} +.tail-datetime-calendar .calendar-timepicker label.timepicker-switch input[type="checkbox"] + span{ + display: inline-block; + position: relative; + vertical-align: top; +} +.tail-datetime-calendar .calendar-timepicker label.timepicker-switch input[type="checkbox"] + span:before{ + width: 50px; + height: 16px; + content: ""; + display: inline-block; + vertical-align: top; + border-width: 1px; + border-style: solid; + border-color: var(--color-primary); + border-radius: 14px; + transition: border 284ms linear; + -webkit-transition: border 284ms linear; +} +.tail-datetime-calendar .calendar-timepicker label.timepicker-switch input[type="checkbox"] + span:after{ + top: 3px; + left: 4px; + right: 30px; + width: auto; + height: 10px; + margin: 0; + padding: 0; + content: ""; + display: inline-block; + position: absolute; + background-color: var(--color-primary); + border-radius: 15px; + vertical-align: top; + transition: left 284ms linear, right 284ms linear 284ms, background 284ms linear; + -webkit-transition: left 284ms linear, right 284ms linear 284ms, background 284ms linear; +} +.tail-datetime-calendar .calendar-timepicker label.timepicker-switch input[type="checkbox"]:checked + span:before{ + border-color: #E67D1E; +} +.tail-datetime-calendar .calendar-timepicker label.timepicker-switch input[type="checkbox"]:checked + span:after{ + left: 30px; + right: 4px; + background-color: #E67D1E; + transition: right 284ms linear, left 284ms linear 284ms, background 284ms linear; + -webkit-transition: right 284ms linear, left 284ms linear 284ms, background 284ms linear; +} +.tail-datetime-calendar .calendar-actions + .calendar-timepicker{ + border-width: 0; +} +/* @end CALENDAR TIMEPICKER */ + +/* @start RTL */ +.tail-datetime-calendar.rtl{ + direction: rtl; +} +.tail-datetime-calendar.rtl .calendar-actions span.action-next, +.tail-datetime-calendar.rtl .calendar-actions span.action-prev{ + transform: rotate(180deg); + -moz-transform: rotate(180deg); + -webkit-transform: rotate(180deg); +} +.tail-datetime-calendar.rtl .calendar-datepicker table tr td.date-disabled:after{ + right: 3px; + transform: rotate(45deg); + -moz-transform: rotate(45deg); + -webkit-transform: rotate(45deg); +} +.tail-datetime-calendar.rtl .calendar-datepicker table tr td.date-today:before{ + right: 5px; +} +.tail-datetime-calendar.rtl .calendar-datepicker table tr td .tooltip-tick{ + left: 5px; +} +.tail-datetime-calendar.rtl .calendar-datepicker table tr td.calendar-month.date-today:before, +.tail-datetime-calendar.rtl .calendar-datepicker table tr td.calendar-year.date-today:before, +.tail-datetime-calendar.rtl .calendar-datepicker table tr td.calendar-decade.date-today:before{ + right: 50%; + margin-right: -2.5px; +} +.tail-datetime-calendar.rtl .calendar-datepicker table tr td.calendar-month:hover span.inner:before, +.tail-datetime-calendar.rtl .calendar-datepicker table tr td.calendar-year:hover span.inner:before, +.tail-datetime-calendar.rtl .calendar-datepicker table tr td.calendar-decade:hover span.inner:before{ + right: 6px; + border-right-color: #cccccc; +} +.tail-datetime-calendar.rtl .calendar-datepicker table tr td.calendar-month span.inner:after, +.tail-datetime-calendar.rtl .calendar-datepicker table tr td.calendar-year span.inner:after, +.tail-datetime-calendar.rtl .calendar-datepicker table tr td.calendar-decade span.inner:after{ + left: 0; +} +.tail-datetime-calendar.rtl .calendar-datepicker table tr td.calendar-month:hover span.inner:after, +.tail-datetime-calendar.rtl .calendar-datepicker table tr td.calendar-year:hover span.inner:after, +.tail-datetime-calendar.rtl .calendar-datepicker table tr td.calendar-decade:hover span.inner:after{ + left: 6px; + border-left-color: #cccccc; +} +.tail-datetime-calendar.rtl .calendar-datepicker table tr td.calendar-decade span.inner{ + text-align: right; +} +.tail-datetime-calendar.rtl .calendar-timepicker .timepicker-field:first-child{ + text-align: left; + padding-left: 0; + padding-right: 25px; +} +.tail-datetime-calendar.rtl .calendar-timepicker .timepicker-field:last-child{ + text-align: right; + padding-left: 25px; + padding-right: 0; +} +.tail-datetime-calendar.rtl .calendar-timepicker .timepicker-field:first-child input[type="text"]{ + margin-left: -1px; + margin-right: 0; + border-radius: 0 3px 3px 0; +} +.tail-datetime-calendar.rtl .calendar-timepicker .timepicker-field:last-child input[type="text"]{ + margin-left: 0; + margin-right: -1px; + border-radius: 3px 0 0 3px; +} +/* @end RTL */ + +/*# sourceMappingURL=tail.datetime-default-green.map */ \ No newline at end of file diff --git a/frontend/vendor/flatpickr.css b/frontend/vendor/flatpickr.css deleted file mode 100644 index ffab7ba7e..000000000 --- a/frontend/vendor/flatpickr.css +++ /dev/null @@ -1,755 +0,0 @@ -/* - custom code - hides the up/down arrows in time (number) inputs -*/ -/* webkit */ -.flatpickr-calendar input[type=number]::-webkit-inner-spin-button, -.flatpickr-calendar input[type=number]::-webkit-outer-spin-button { - -webkit-appearance: none; - margin: 0; -} -/* firefox */ -.flatpickr-calendar input[type=number] { - -moz-appearance:textfield; -} -/* vendor code */ -.flatpickr-calendar { - background: transparent; - opacity: 0; - display: none; - text-align: center; - visibility: hidden; - padding: 0; - -webkit-animation: none; - animation: none; - direction: ltr; - border: 0; - font-size: 14px; - line-height: 24px; - border-radius: 5px; - position: absolute; - width: 307.875px; - -webkit-box-sizing: border-box; - box-sizing: border-box; - -ms-touch-action: manipulation; - touch-action: manipulation; - background: #fff; - -webkit-box-shadow: 1px 0 0 #e6e6e6, -1px 0 0 #e6e6e6, 0 1px 0 #e6e6e6, 0 -1px 0 #e6e6e6, 0 3px 13px rgba(0,0,0,0.08); - box-shadow: 1px 0 0 #e6e6e6, -1px 0 0 #e6e6e6, 0 1px 0 #e6e6e6, 0 -1px 0 #e6e6e6, 0 3px 13px rgba(0,0,0,0.08); -} -.flatpickr-calendar.open, -.flatpickr-calendar.inline { - opacity: 1; - max-height: 640px; - visibility: visible; -} -.flatpickr-calendar.open { - display: inline-block; - z-index: 99999; -} -.flatpickr-calendar.animate.open { - -webkit-animation: fpFadeInDown 300ms cubic-bezier(0.23, 1, 0.32, 1); - animation: fpFadeInDown 300ms cubic-bezier(0.23, 1, 0.32, 1); -} -.flatpickr-calendar.inline { - display: block; - position: relative; - top: 2px; -} -.flatpickr-calendar.static { - position: absolute; - top: calc(100% + 2px); -} -.flatpickr-calendar.static.open { - z-index: 999; - display: block; -} -.flatpickr-calendar.multiMonth .flatpickr-days .dayContainer:nth-child(n+1) .flatpickr-day.inRange:nth-child(7n+7) { - -webkit-box-shadow: none !important; - box-shadow: none !important; -} -.flatpickr-calendar.multiMonth .flatpickr-days .dayContainer:nth-child(n+2) .flatpickr-day.inRange:nth-child(7n+1) { - -webkit-box-shadow: -2px 0 0 #e6e6e6, 5px 0 0 #e6e6e6; - box-shadow: -2px 0 0 #e6e6e6, 5px 0 0 #e6e6e6; -} -.flatpickr-calendar .hasWeeks .dayContainer, -.flatpickr-calendar .hasTime .dayContainer { - border-bottom: 0; - border-bottom-right-radius: 0; - border-bottom-left-radius: 0; -} -.flatpickr-calendar .hasWeeks .dayContainer { - border-left: 0; -} -.flatpickr-calendar.showTimeInput.hasTime .flatpickr-time { - height: 40px; - border-top: 1px solid #e6e6e6; -} -.flatpickr-calendar.noCalendar.hasTime .flatpickr-time { - height: auto; -} -.flatpickr-calendar:before, -.flatpickr-calendar:after { - position: absolute; - display: block; - pointer-events: none; - border: solid transparent; - content: ''; - height: 0; - width: 0; - left: 22px; -} -.flatpickr-calendar.rightMost:before, -.flatpickr-calendar.rightMost:after { - left: auto; - right: 22px; -} -.flatpickr-calendar:before { - border-width: 5px; - margin: 0 -5px; -} -.flatpickr-calendar:after { - border-width: 4px; - margin: 0 -4px; -} -.flatpickr-calendar.arrowTop:before, -.flatpickr-calendar.arrowTop:after { - bottom: 100%; -} -.flatpickr-calendar.arrowTop:before { - border-bottom-color: #e6e6e6; -} -.flatpickr-calendar.arrowTop:after { - border-bottom-color: #fff; -} -.flatpickr-calendar.arrowBottom:before, -.flatpickr-calendar.arrowBottom:after { - top: 100%; -} -.flatpickr-calendar.arrowBottom:before { - border-top-color: #e6e6e6; -} -.flatpickr-calendar.arrowBottom:after { - border-top-color: #fff; -} -.flatpickr-calendar:focus { - outline: 0; -} -.flatpickr-wrapper { - position: relative; - display: inline-block; -} -.flatpickr-months { - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; -} -.flatpickr-months .flatpickr-month { - background: transparent; - color: rgba(0,0,0,0.9); - fill: rgba(0,0,0,0.9); - height: 28px; - line-height: 1; - text-align: center; - position: relative; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - overflow: hidden; - -webkit-box-flex: 1; - -webkit-flex: 1; - -ms-flex: 1; - flex: 1; -} -.flatpickr-months .flatpickr-prev-month, -.flatpickr-months .flatpickr-next-month { - text-decoration: none; - cursor: pointer; - position: absolute; - top: 0px; - line-height: 16px; - height: 28px; - padding: 10px; - z-index: 3; -} -.flatpickr-months .flatpickr-prev-month.disabled, -.flatpickr-months .flatpickr-next-month.disabled { - display: none; -} -.flatpickr-months .flatpickr-prev-month i, -.flatpickr-months .flatpickr-next-month i { - position: relative; -} -.flatpickr-months .flatpickr-prev-month.flatpickr-prev-month, -.flatpickr-months .flatpickr-next-month.flatpickr-prev-month { -/* - /*rtl:begin:ignore*/ -/* - */ - left: 0; -/* - /*rtl:end:ignore*/ -/* - */ -} -/* - /*rtl:begin:ignore*/ -/* - /*rtl:end:ignore*/ -.flatpickr-months .flatpickr-prev-month.flatpickr-next-month, -.flatpickr-months .flatpickr-next-month.flatpickr-next-month { -/* - /*rtl:begin:ignore*/ -/* - */ - right: 0; -/* - /*rtl:end:ignore*/ -/* - */ -} -/* - /*rtl:begin:ignore*/ -/* - /*rtl:end:ignore*/ -.flatpickr-months .flatpickr-prev-month:hover, -.flatpickr-months .flatpickr-next-month:hover { - color: #959ea9; -} -.flatpickr-months .flatpickr-prev-month:hover svg, -.flatpickr-months .flatpickr-next-month:hover svg { - fill: #f64747; -} -.flatpickr-months .flatpickr-prev-month svg, -.flatpickr-months .flatpickr-next-month svg { - width: 14px; - height: 14px; -} -.flatpickr-months .flatpickr-prev-month svg path, -.flatpickr-months .flatpickr-next-month svg path { - -webkit-transition: fill 0.1s; - transition: fill 0.1s; - fill: inherit; -} -.numInputWrapper { - position: relative; - height: auto; -} -.numInputWrapper input, -.numInputWrapper span { - display: inline-block; -} -.numInputWrapper input { - width: 100%; - min-width: auto !important; -} -.numInputWrapper input::-ms-clear { - display: none; -} -.numInputWrapper span { - position: absolute; - right: 0; - width: 14px; - padding: 0 4px 0 2px; - height: 50%; - line-height: 50%; - opacity: 0; - cursor: pointer; - border: 1px solid rgba(57,57,57,0.15); - -webkit-box-sizing: border-box; - box-sizing: border-box; -} -.numInputWrapper span:hover { - background: rgba(0,0,0,0.1); -} -.numInputWrapper span:active { - background: rgba(0,0,0,0.2); -} -.numInputWrapper span:after { - display: block; - content: ''; - position: absolute; -} -.numInputWrapper span.arrowUp { - top: 0; - border-bottom: 0; -} -.numInputWrapper span.arrowUp:after { - border-left: 4px solid transparent; - border-right: 4px solid transparent; - border-bottom: 4px solid rgba(57,57,57,0.6); - top: 26%; -} -.numInputWrapper span.arrowDown { - top: 50%; -} -.numInputWrapper span.arrowDown:after { - border-left: 4px solid transparent; - border-right: 4px solid transparent; - border-top: 4px solid rgba(57,57,57,0.6); - top: 40%; -} -.numInputWrapper span svg { - width: inherit; - height: auto; -} -.numInputWrapper span svg path { - fill: rgba(0,0,0,0.5); -} -.numInputWrapper:hover { - background: rgba(0,0,0,0.05); -} -.numInputWrapper:hover span { - opacity: 1; -} -.flatpickr-current-month { - font-size: 135%; - line-height: inherit; - font-weight: 300; - color: inherit; - position: absolute; - width: 75%; - left: 12.5%; - padding: 6.16px 0 0 0; - line-height: 1; - height: 28px; - display: inline-block; - text-align: center; - -webkit-transform: translate3d(0px, 0px, 0px); - transform: translate3d(0px, 0px, 0px); -} -.flatpickr-current-month span.cur-month { - font-family: inherit; - font-weight: 700; - color: inherit; - display: inline-block; - margin-left: 0.5ch; - padding: 0; -} -.flatpickr-current-month span.cur-month:hover { - background: rgba(0,0,0,0.05); -} -.flatpickr-current-month .numInputWrapper { - width: 6ch; - width: 7ch\0; - display: inline-block; -} -.flatpickr-current-month .numInputWrapper span.arrowUp:after { - border-bottom-color: rgba(0,0,0,0.9); -} -.flatpickr-current-month .numInputWrapper span.arrowDown:after { - border-top-color: rgba(0,0,0,0.9); -} -.flatpickr-current-month input.cur-year { - background: transparent; - -webkit-box-sizing: border-box; - box-sizing: border-box; - color: inherit; - cursor: text; - padding: 0 0 0 0.5ch; - margin: 0; - display: inline-block; - font-size: inherit; - font-family: inherit; - font-weight: 300; - line-height: inherit; - height: auto; - border: 0; - border-radius: 0; - vertical-align: initial; -} -.flatpickr-current-month input.cur-year:focus { - outline: 0; -} -.flatpickr-current-month input.cur-year[disabled], -.flatpickr-current-month input.cur-year[disabled]:hover { - font-size: 100%; - color: rgba(0,0,0,0.5); - background: transparent; - pointer-events: none; -} -.flatpickr-weekdays { - background: transparent; - text-align: center; - overflow: hidden; - width: 100%; - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-box-align: center; - -webkit-align-items: center; - -ms-flex-align: center; - align-items: center; - height: 28px; -} -.flatpickr-weekdays .flatpickr-weekdaycontainer { - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-box-flex: 1; - -webkit-flex: 1; - -ms-flex: 1; - flex: 1; -} -span.flatpickr-weekday { - cursor: default; - font-size: 90%; - background: transparent; - color: rgba(0,0,0,0.54); - line-height: 1; - margin: 0; - text-align: center; - display: block; - -webkit-box-flex: 1; - -webkit-flex: 1; - -ms-flex: 1; - flex: 1; - font-weight: bolder; -} -.dayContainer, -.flatpickr-weeks { - padding: 1px 0 0 0; -} -.flatpickr-days { - position: relative; - overflow: hidden; - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-box-align: start; - -webkit-align-items: flex-start; - -ms-flex-align: start; - align-items: flex-start; - width: 307.875px; -} -.flatpickr-days:focus { - outline: 0; -} -.dayContainer { - padding: 0; - outline: 0; - text-align: left; - width: 307.875px; - min-width: 307.875px; - max-width: 307.875px; - -webkit-box-sizing: border-box; - box-sizing: border-box; - display: inline-block; - display: -ms-flexbox; - display: -webkit-box; - display: -webkit-flex; - display: flex; - -webkit-flex-wrap: wrap; - flex-wrap: wrap; - -ms-flex-wrap: wrap; - -ms-flex-pack: justify; - -webkit-justify-content: space-around; - justify-content: space-around; - -webkit-transform: translate3d(0px, 0px, 0px); - transform: translate3d(0px, 0px, 0px); - opacity: 1; -} -.dayContainer + .dayContainer { - -webkit-box-shadow: -1px 0 0 #e6e6e6; - box-shadow: -1px 0 0 #e6e6e6; -} -.flatpickr-day { - background: none; - border: 1px solid transparent; - border-radius: 150px; - -webkit-box-sizing: border-box; - box-sizing: border-box; - color: #393939; - cursor: pointer; - font-weight: 400; - width: 14.2857143%; - -webkit-flex-basis: 14.2857143%; - -ms-flex-preferred-size: 14.2857143%; - flex-basis: 14.2857143%; - max-width: 39px; - height: 39px; - line-height: 39px; - margin: 0; - display: inline-block; - position: relative; - -webkit-box-pack: center; - -webkit-justify-content: center; - -ms-flex-pack: center; - justify-content: center; - text-align: center; -} -.flatpickr-day.inRange, -.flatpickr-day.prevMonthDay.inRange, -.flatpickr-day.nextMonthDay.inRange, -.flatpickr-day.today.inRange, -.flatpickr-day.prevMonthDay.today.inRange, -.flatpickr-day.nextMonthDay.today.inRange, -.flatpickr-day:hover, -.flatpickr-day.prevMonthDay:hover, -.flatpickr-day.nextMonthDay:hover, -.flatpickr-day:focus, -.flatpickr-day.prevMonthDay:focus, -.flatpickr-day.nextMonthDay:focus { - cursor: pointer; - outline: 0; - background: #e6e6e6; - border-color: #e6e6e6; -} -.flatpickr-day.today { - border-color: #959ea9; -} -.flatpickr-day.today:hover, -.flatpickr-day.today:focus { - border-color: #959ea9; - background: #959ea9; - color: #fff; -} -.flatpickr-day.selected, -.flatpickr-day.startRange, -.flatpickr-day.endRange, -.flatpickr-day.selected.inRange, -.flatpickr-day.startRange.inRange, -.flatpickr-day.endRange.inRange, -.flatpickr-day.selected:focus, -.flatpickr-day.startRange:focus, -.flatpickr-day.endRange:focus, -.flatpickr-day.selected:hover, -.flatpickr-day.startRange:hover, -.flatpickr-day.endRange:hover, -.flatpickr-day.selected.prevMonthDay, -.flatpickr-day.startRange.prevMonthDay, -.flatpickr-day.endRange.prevMonthDay, -.flatpickr-day.selected.nextMonthDay, -.flatpickr-day.startRange.nextMonthDay, -.flatpickr-day.endRange.nextMonthDay { - background: #569ff7; - -webkit-box-shadow: none; - box-shadow: none; - color: #fff; - border-color: #569ff7; -} -.flatpickr-day.selected.startRange, -.flatpickr-day.startRange.startRange, -.flatpickr-day.endRange.startRange { - border-radius: 50px 0 0 50px; -} -.flatpickr-day.selected.endRange, -.flatpickr-day.startRange.endRange, -.flatpickr-day.endRange.endRange { - border-radius: 0 50px 50px 0; -} -.flatpickr-day.selected.startRange + .endRange, -.flatpickr-day.startRange.startRange + .endRange, -.flatpickr-day.endRange.startRange + .endRange { - -webkit-box-shadow: -10px 0 0 #569ff7; - box-shadow: -10px 0 0 #569ff7; -} -.flatpickr-day.selected.startRange.endRange, -.flatpickr-day.startRange.startRange.endRange, -.flatpickr-day.endRange.startRange.endRange { - border-radius: 50px; -} -.flatpickr-day.inRange { - border-radius: 0; - -webkit-box-shadow: -5px 0 0 #e6e6e6, 5px 0 0 #e6e6e6; - box-shadow: -5px 0 0 #e6e6e6, 5px 0 0 #e6e6e6; -} -.flatpickr-day.disabled, -.flatpickr-day.disabled:hover, -.flatpickr-day.prevMonthDay, -.flatpickr-day.nextMonthDay, -.flatpickr-day.notAllowed, -.flatpickr-day.notAllowed.prevMonthDay, -.flatpickr-day.notAllowed.nextMonthDay { - color: rgba(57,57,57,0.3); - background: transparent; - border-color: transparent; - cursor: default; -} -.flatpickr-day.disabled, -.flatpickr-day.disabled:hover { - cursor: not-allowed; - color: rgba(57,57,57,0.1); -} -.flatpickr-day.week.selected { - border-radius: 0; - -webkit-box-shadow: -5px 0 0 #569ff7, 5px 0 0 #569ff7; - box-shadow: -5px 0 0 #569ff7, 5px 0 0 #569ff7; -} -.flatpickr-day.hidden { - visibility: hidden; -} -.rangeMode .flatpickr-day { - margin-top: 1px; -} -.flatpickr-weekwrapper { - display: inline-block; - float: left; -} -.flatpickr-weekwrapper .flatpickr-weeks { - padding: 0 12px; - -webkit-box-shadow: 1px 0 0 #e6e6e6; - box-shadow: 1px 0 0 #e6e6e6; -} -.flatpickr-weekwrapper .flatpickr-weekday { - float: none; - width: 100%; - line-height: 28px; -} -.flatpickr-weekwrapper span.flatpickr-day, -.flatpickr-weekwrapper span.flatpickr-day:hover { - display: block; - width: 100%; - max-width: none; - color: rgba(57,57,57,0.3); - background: transparent; - cursor: default; - border: none; -} -.flatpickr-innerContainer { - display: block; - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-box-sizing: border-box; - box-sizing: border-box; - overflow: hidden; -} -.flatpickr-rContainer { - display: inline-block; - padding: 0; - -webkit-box-sizing: border-box; - box-sizing: border-box; -} -.flatpickr-time { - text-align: center; - outline: 0; - display: block; - height: 0; - line-height: 40px; - max-height: 40px; - -webkit-box-sizing: border-box; - box-sizing: border-box; - overflow: hidden; - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; -} -.flatpickr-time:after { - content: ''; - display: table; - clear: both; -} -.flatpickr-time .numInputWrapper { - -webkit-box-flex: 1; - -webkit-flex: 1; - -ms-flex: 1; - flex: 1; - width: 40%; - height: 40px; - float: left; -} -.flatpickr-time .numInputWrapper span.arrowUp:after { - border-bottom-color: #393939; -} -.flatpickr-time .numInputWrapper span.arrowDown:after { - border-top-color: #393939; -} -.flatpickr-time.hasSeconds .numInputWrapper { - width: 26%; -} -.flatpickr-time.time24hr .numInputWrapper { - width: 49%; -} -.flatpickr-time input { - background: transparent; - -webkit-box-shadow: none; - box-shadow: none; - border: 0; - border-radius: 0; - text-align: center; - margin: 0; - padding: 0; - height: inherit; - line-height: inherit; - cursor: pointer; - color: #393939; - font-size: 14px; - position: relative; - -webkit-box-sizing: border-box; - box-sizing: border-box; -} -.flatpickr-time input.flatpickr-hour { - font-weight: bold; -} -.flatpickr-time input.flatpickr-minute, -.flatpickr-time input.flatpickr-second { - font-weight: 400; -} -.flatpickr-time input:focus { - outline: 0; - border: 0; -} -.flatpickr-time .flatpickr-time-separator, -.flatpickr-time .flatpickr-am-pm { - height: inherit; - display: inline-block; - float: left; - line-height: inherit; - color: #393939; - font-weight: bold; - width: 2%; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - -webkit-align-self: center; - -ms-flex-item-align: center; - align-self: center; -} -.flatpickr-time .flatpickr-am-pm { - outline: 0; - width: 18%; - cursor: pointer; - text-align: center; - font-weight: 400; -} -.flatpickr-time .flatpickr-am-pm:hover, -.flatpickr-time .flatpickr-am-pm:focus { - background: #f0f0f0; -} -.flatpickr-input[readonly] { - cursor: pointer; - min-width: auto; -} -@-webkit-keyframes fpFadeInDown { - from { - opacity: 0; - -webkit-transform: translate3d(0, -20px, 0); - transform: translate3d(0, -20px, 0); - } - to { - opacity: 1; - -webkit-transform: translate3d(0, 0, 0); - transform: translate3d(0, 0, 0); - } -} -@keyframes fpFadeInDown { - from { - opacity: 0; - -webkit-transform: translate3d(0, -20px, 0); - transform: translate3d(0, -20px, 0); - } - to { - opacity: 1; - -webkit-transform: translate3d(0, 0, 0); - transform: translate3d(0, 0, 0); - } -} diff --git a/frontend/vendor/main.js b/frontend/vendor/main.js index 68daaa8fc..5e0bd5da4 100644 --- a/frontend/vendor/main.js +++ b/frontend/vendor/main.js @@ -1,2 +1,2 @@ import './fontawesome.css'; -import './flatpickr.css'; +import './datetime.css'; \ No newline at end of file diff --git a/jsconfig.json b/jsconfig.json new file mode 100644 index 000000000..af7dc41f7 --- /dev/null +++ b/jsconfig.json @@ -0,0 +1,5 @@ +{ + "compilerOptions": { + "experimentalDecorators": true + }, +} \ No newline at end of file diff --git a/messages/uniworx/de.msg b/messages/uniworx/de.msg index 8568ce0e3..7228f0612 100644 --- a/messages/uniworx/de.msg +++ b/messages/uniworx/de.msg @@ -465,6 +465,8 @@ CloseAlert: Schliessen Name: Name MatrikelNr: Matrikelnummer +LdapSynced: LDAP-Synchronisiert +LdapSyncedBefore: Letzte LDAP-Synchronisation vor NoMatrikelKnown: Keine Matrikelnummer Theme: Oberflächen Design Favoriten: Anzahl gespeicherter Favoriten @@ -581,7 +583,7 @@ RatingFilesUpdated: Korrigierte Dateien überschrieben RatingNotUnicode uexc@UnicodeException: Bewertungsdatei nicht in UTF-8 kodiert: #{tshow uexc} RatingMissingSeparator: Präambel der Bewertungsdatei konnte nicht identifziert werden RatingMultiple: Bewertungen enthält mehrere Punktzahlen für die gleiche Abgabe -RatingInvalid parseErr@String: Bewertungspunktzahl konnte nicht als Zahl verstanden werden: #{parseErr} +RatingInvalid parseErr@Text: Bewertungspunktzahl konnte nicht als Zahl verstanden werden: #{parseErr} RatingFileIsDirectory: Unerwarteter Fehler: Datei ist unerlaubterweise ein Verzeichnis RatingNegative: Bewertungspunkte dürfen nicht negativ sein RatingExceedsMax: Bewertung übersteigt die erlaubte Maximalpunktzahl @@ -615,7 +617,8 @@ TutorsFor n@Int: #{pluralDE n "Tutor" "Tutoren"} CorrectorsFor n@Int: #{pluralDE n "Korrektor" "Korrektoren"} ForSchools n@Int: für #{pluralDE n "Institut" "Institute"} UserListTitle: Komprehensive Benutzerliste -AccessRightsSaved: Berechtigungsänderungen wurden gespeichert. +AccessRightsSaved: Berechtigungen erfolgreich verändert +AccessRightsNotChanged: Berechtigungen wurden nicht verändert LecturersForN n@Int: #{pluralDE n "Dozent" "Dozenten"} @@ -628,6 +631,8 @@ DownloadFilesTip: Wenn gesetzt werden Dateien von Abgaben und Übungsblättern a WarningDays: Fristen-Vorschau WarningDaysTip: Wie viele Tage im Voraus sollen Fristen von Klausuren etc. auf Ihrer Startseite angezeigt werden? NotificationSettings: Erwünschte Benachrichtigungen +UserSchools: Relevante Institute +UserSchoolsTip: Sie erhalten nur institutweite Benachrichtigungen für Institute, die hier ausgewählt sind. FormNotifications: Benachrichtigungen FormBehaviour: Verhalten FormCosmetics: Oberfläche @@ -662,7 +667,8 @@ CampusUserInvalidGivenName: Konnte anhand des Campus-Logins keinen Vornamen ermi CampusUserInvalidSurname: Konnte anhand des Campus-Logins keinen Nachname ermitteln CampusUserInvalidTitle: Konnte anhand des Campus-Logins keinen akademischen Titel ermitteln CampusUserInvalidMatriculation: Konnte anhand des Campus-Logins keine Matrikelnummer ermitteln -CampusUserInvalidFeaturesOfStudy parseErr@String: Konnte anhand des Campus-Logins keine Matrikelnummer ermitteln: #{parseErr} +CampusUserInvalidFeaturesOfStudy parseErr@Text: Konnte anhand des Campus-Logins keine Studiengänge ermitteln +CampusUserInvalidAssociatedSchools parseErr@Text: Konnte anhand des Campus-Logins keine Institute ermitteln CorrectorNormal: Normal CorrectorMissing: Abwesend @@ -854,6 +860,11 @@ NotificationTriggerCorrectionsNotDistributed: Nicht alle Abgaben eines meiner Ü NotificationTriggerUserRightsUpdate: Meine Berechtigungen wurden geändert NotificationTriggerUserAuthModeUpdate: Mein Anmelde-Modus wurde geändert NotificationTriggerExamResult: Ich kann ein neues Prüfungsergebnis einsehen +NotificationTriggerAllocationStaffRegister: Ich kann Kurse bei einer neuen Zentralanmeldung eintragen +NotificationTriggerAllocationAllocation: Ich kann Zentralanmeldung-Bewerbungen für einen meiner Kurse bewerten +NotificationTriggerAllocationRegister: Ich kann mich bei einer neuen Zentralanmeldung bewerben +NotificationTriggerAllocationOutdatedRatings: Zentralanmeldung-Bewerbungen für einen meiner Kurse wurden verändert, nachdem sie bewertet wurden +NotificationTriggerAllocationUnratedApplications: Bewertungen zu Zentralanmeldung-Bewerbungen für einen meiner Kurse stehen aus NotificationTriggerKindAll: Für alle Benutzer NotificationTriggerKindCourseParticipant: Für Kursteilnehmer @@ -861,6 +872,9 @@ NotificationTriggerKindExamParticipant: Für Prüfungsteilnehmer NotificationTriggerKindCorrector: Für Korrektoren NotificationTriggerKindLecturer: Für Dozenten NotificationTriggerKindAdmin: Für Administratoren +NotificationTriggerKindExamOffice: Für das Prüfungsamt +NotificationTriggerKindEvaluation: Für Vorlesungsumfragen +NotificationTriggerKindAllocationStaff: Für Zentralanmeldungen CorrCreate: Abgaben erstellen UnknownPseudonymWord pseudonymWord@Text: Unbekanntes Pseudonym-Wort "#{pseudonymWord}" @@ -1031,6 +1045,8 @@ MenuExamAddMembers: Prüfungsteilnehmer hinzufügen MenuLecturerInvite: Dozenten hinzufügen MenuAllocationInfo: Hinweise zum Ablauf einer Zentralanmeldung MenuCourseApplicationsFiles: Dateien aller Bewerbungen +MenuSchoolList: Institute +MenuSchoolNew: Neues Institut anlegen AuthPredsInfo: Um eigene Veranstaltungen aus Sicht der Teilnehmer anzusehen, können Veranstalter und Korrektoren hier die Prüfung ihrer erweiterten Berechtigungen temporär deaktivieren. Abgewählte Prädikate schlagen immer fehl. Abgewählte Prädikate werden also nicht geprüft um Zugriffe zu gewähren, welche andernfalls nicht erlaubt wären. Diese Einstellungen gelten nur temporär bis Ihre Sitzung abgelaufen ist, d.h. bis ihr Browser-Cookie abgelaufen ist. Durch Abwahl von Prädikaten kann man sich höchstens temporär aussperren. AuthPredsActive: Aktive Authorisierungsprädikate @@ -1425,6 +1441,7 @@ CsvColumnApplicationsRating: Bewertung der Bewerbung; "1.0", "1.3", "1.7", ..., CsvColumnApplicationsComment: Kommentar zur Bewerbung; je nach Kurs-Einstellungen entweder nur als Notiz für die Kursverwalter oder Feedback für den Bewerber Action: Aktion +ActionNoUsersSelected: Keine Benutzer ausgewählt DBCsvDuplicateKey: Zwei Zeilen der CSV-Dateien referenzieren den selben internen Datensatz und können daher nicht verarbeitet werden. DBCsvDuplicateKeyTip: Entfernen Sie eine der unten aufgeführten Zeilen aus Ihren CSV-Dateien und versuchen Sie es erneut. @@ -1489,20 +1506,23 @@ PasswordRepeatInvalid: Wiederholung stimmt nicht mit neuem Passwort überein UserPasswordHeadingFor: Passwort ändern für PasswordChangedSuccess: Passwort erfolgreich geändert -LecturerInviteSchool: Institut -LecturerInviteField: Einzuladende EMail Addressen -LecturerInviteHeading: Dozenten hinzufügen +FunctionaryInviteFunction: Funktion +FunctionaryInviteSchool: Institut +FunctionaryInviteField: Einzuladende EMail Addressen +FunctionaryInviteHeading: Institut-Funktionäre hinzufügen -LecturersInvited n@Int: #{n} #{pluralDE n "Dozent" "Dozenten"} per EMail eingeladen -LecturersAdded n@Int: #{n} #{pluralDE n "Dozent" "Dozenten"} eingetragen +FunctionariesInvited n@Int: #{n} #{pluralDE n "Funktionär" "Funktionäre"} per EMail eingeladen +FunctionariesAdded n@Int: #{n} #{pluralDE n "Funktionär" "Funktionäre"} eingetragen -MailSubjectSchoolLecturerInvitation school@SchoolName: Einladung zum Dozent für „#{school}“ -MailSchoolLecturerInviteHeading school@SchoolName: Einladung zum Dozent für „#{school}“ -SchoolLecturerInviteExplanation: Sie wurden eingeladen, Dozent für ein Institut zu sein. Sie können, nachdem Sie die Einladung annehmen, eigenständig neue Kurse anlegen. -SchoolLecturerInvitationAccepted school@SchoolName: Einladung zum Dozent für „#{school}“ angenommen +MailSubjectSchoolFunctionInvitation school@SchoolName renderedFunction@Text: #{renderedFunction}-Einladung für „#{school}“ +MailSchoolFunctionInviteHeading school@SchoolName renderedFunction@Text: #{renderedFunction}-Einladung für „#{school}“ +SchoolFunctionInviteExplanation renderedFunction@Text: Sie wurden eingeladen, als #{renderedFunction} für ein Institut zu wirken. Sie erhalten, nachdem Sie die Einladung annehmen, erweiterte Rechte innerhalb des Instituts. +SchoolFunctionInvitationAccepted school@SchoolName renderedFunction@Text: #{renderedFunction}-Einladung zum Dozent für „#{school}“ angenommen AllocationActive: Aktiv AllocationName: Name +AllocationAvailableCourses: Kurse +AllocationAppliedCourses: Bewerbungen AllocationTitle termText@Text ssh'@SchoolShorthand allocation@AllocationName: #{termText} - #{ssh'}: #{allocation} AllocationShortTitle termText@Text ssh'@SchoolShorthand ash@AllocationShorthand: #{termText} - #{ssh'} - #{ash} AllocationDescription: Beschreibung @@ -1513,7 +1533,7 @@ AllocationRegister: Bewerbung AllocationRegisterClosed: Die Zentralanmeldung ist aktuell geschlossen. AllocationRegisterOpensIn difftime@Text: Die Zentralanmeldung öffnet voraussichtlich in #{difftime} AllocationStaffAllocationFrom: Bewertung der Bewerbungen ab -AllocationStaffAllocation: Bewerbungsbewertung +AllocationStaffAllocation: Bewertung der Bewerbungen AllocationProcess: Platzvergabe AllocationNoApplication: Keine Bewerbung AllocationPriority: Priorität @@ -1562,4 +1582,46 @@ CourseApplicationNoRatingPoints: Keine Bewertung CourseApplicationNoRatingComment: Kein Kommentar UserDisplayName: Voller Name -UserMatriculation: Matrikelnummer \ No newline at end of file +UserMatriculation: Matrikelnummer + +SchoolShort: Kürzel +SchoolName: Name +SchoolLdapOrganisations: Assoziierte LDAP-Fragmente +SchoolLdapOrganisationsTip: Beim Login via LDAP werden dem Nutzer alle Institute zugeordnet deren assoziierte LDAP-Fragmente im Eintrag des Nutzer gefunden werden + +SchoolUpdated ssh@SchoolId: #{ssh} erfolgreich angepasst +SchoolTitle ssh@SchoolId: Institut „#{ssh}“ +TitleSchoolNew: Neues Institut anlegen +SchoolCreated ssh@SchoolId: #{ssh} erfolgreich angelegt +SchoolExists ssh@SchoolId: Institut „#{ssh}“ existiert bereits + +SchoolAdmin: Admin +SchoolLecturer: Dozent +SchoolEvaluation: Kursumfragenverwaltung +SchoolExamOffice: Prüfungsamt + +ApplicationEditTip: Während des Bewerbungszeitraums können eigene Bewerbungen beliebig angepasst und auch wieder zurückgezogen werden. + +UserLdapSync: LDAP-Synchronisieren +SynchroniseLdapUserQueued n@Int: LDAP-Synchronisation von #{n} #{pluralDE n "Benutzer" "Benutzern"} angestoßen +UserHijack: Sitzung übernehmen + +MailSubjectAllocationStaffRegister allocation@AllocationName: Sie können nun Kurse für die Zentralameldung „#{allocation}“ registrieren +MailAllocationStaffRegisterNewCourse: Sie können auf der unten aufgeführten Seite neue Kurse in Uni2work anlegen. Hierbei haben Sie die Möglichkeit anzugeben, dass der Kurs an der Zentralanmeldung teilnimmt. +MailAllocationStaffRegisterDeadline deadline@Text: Bitte beachten Sie, dass alle Kurse, die an der Zentralanmeldung teilnehmen, bis #{deadline} eingetragen sein müssen. + +MailSubjectAllocationRegister allocation@AllocationName: Sie können sich nun für Kurse der Zentralameldung „#{allocation}“ bewerben +MailAllocationRegister: Sie können sich, auf der unten aufgeführten Seite, für alle Kurse der Zentralanmeldung jeweils einzeln bewerben. +MailAllocationRegisterDeadline deadline@Text: Bitte beachten Sie, dass alle Bewerbungen bis #{deadline} eingegangen sein müssen. + +MailSubjectAllocationAllocation allocation@AllocationName: Sie können nun Bewerbungen für ihre Kurse in der Zentralanmeldung „#{allocation}“ bewerten +MailAllocationAllocation: Sie können nun auf den unten aufgeführten Seiten Bewerbungen, die im Rahmen der Zentralanmeldung an ihre Kurse gestellt wurden, bewerten. Die Bewertungen werden bei der Vergabe der Plätze berücksichtigt. +MailAllocationApplicationsMayChange deadline@Text: Bitte beachten Sie, dass Studierende noch bis #{deadline} Bewerbungen stellen, verändern und zurückziehen können. Bewerbungen, die sich nach ihrer Bewertung noch verändern, müssen neu bewertet werden. +MailAllocationAllocationDeadline deadline@Text: Bitte beachten Sie, dass alle Bewertungen bis #{deadline} erfolgt sein müssen. + +MailSubjectAllocationUnratedApplications allocation@AllocationName: Es stehen noch Bewertungen zu Bewerbungen für ihre Kurse in der Zentralanmeldung „#{allocation}“ aus +MailAllocationUnratedApplications: Für die unten aufgeführten Kurse liegen Bewerbungen vor, die im Rahmen der Zentralanmeldung an den jeweiligen Kurs gestellt wurden, die noch nicht bewertet wurden. + +MailSubjectAllocationOutdatedRatings allocation@AllocationName: Bereits bewertete Bewerbungen für ihre Kurse in der Zentralanmeldung „#{allocation}“ haben sich geändert +MailAllocationOutdatedRatings: Für die unten aufgeführten Kurse liegen Bewerbungen vor, die im Rahmen der Zentralanmeldung an den jeweiligen Kurs gestellt wurden, die sich verändert haben, seit sie zuletzt bewertet wurden. +MailAllocationOutdatedRatingsWarning: Bewerbungen deren Bewertung veraltet ist (d.h. die Bewerbung wurde nach der Bewertung verändert) zählen als nicht bewertet. \ No newline at end of file diff --git a/models/invitations b/models/invitations index c1d15148c..c915d08e4 100644 --- a/models/invitations +++ b/models/invitations @@ -2,4 +2,5 @@ Invitation email UserEmail for Value data Value + expiresAt UTCTime Maybe UniqueInvitation email for \ No newline at end of file diff --git a/models/schools b/models/schools index f877a1aeb..2da425cf4 100644 --- a/models/schools +++ b/models/schools @@ -6,4 +6,11 @@ School json UniqueSchool name UniqueSchoolShorthand shorthand -- required for Normalisation of CI Text Primary shorthand -- newtype Key School = SchoolKey { unSchoolKey :: SchoolShorthand } - deriving Eq Show Generic + deriving Ord Eq Show Generic +SchoolLdap + school SchoolId Maybe + orgUnit (CI Text) + UniqueOrgUnit orgUnit +SchoolTerms + school SchoolId + terms StudyTermsId \ No newline at end of file diff --git a/models/users b/models/users index f66651dd5..223cd2b8a 100644 --- a/models/users +++ b/models/users @@ -14,6 +14,8 @@ User json -- Each Uni2work user has a corresponding row in this table; create ident (CI Text) -- Case-insensitive user-identifier authentication AuthenticationMode -- 'AuthLDAP' or ('AuthPWHash'+password-hash) lastAuthentication UTCTime Maybe -- last login date + created UTCTime default=now() + lastLdapSynchronisation UTCTime Maybe tokensIssuedAfter UTCTime Maybe -- do not accept bearer tokens issued before this time (accept all tokens if null) matrikelnummer UserMatriculation Maybe -- optional immatriculation-string; usually a number, but not always (e.g. lecturers, pupils, guests,...) firstName Text -- For export in tables, pre-split firstName from displayName @@ -30,14 +32,20 @@ User json -- Each Uni2work user has a corresponding row in this table; create UniqueAuthentication ident -- Column 'ident' can be used as a row-key in this table UniqueEmail email -- Column 'email' can be used as a row-key in this table deriving Show Eq Ord Generic -- Haskell-specific settings for runtime-value representing a row in memory -UserAdmin -- Each row in this table grants school-specific administrator-rights to a specific user - user UserId - school SchoolId - UniqueUserAdmin user school -- combination of user+school must be unique, i.e. no duplicate rows -UserLecturer -- Each row in this table grants school-specific lecturer-rights to a specific user - user UserId - school SchoolId - UniqueSchoolLecturer user school -- combination of user+school must be unique, i.e. no duplicate rows +UserFunction -- Administratively assigned functions (lecturer, admin, evaluation, ...) + user UserId + school SchoolId + function SchoolFunction + UniqueUserFunction user school function +UserExamOffice + user UserId + field StudyTermsId + UniqueUserExamOffice user field +UserSchool -- Managed by users themselves, encodes "schools of interest" + user UserId + school SchoolId + isOptOut Bool -- true if this a marker, that the user manually deleted this entry; it should not be recreated automatically + UniqueUserSchool user school StudyFeatures -- multiple entries possible for students pursuing several degrees at once, usually created upon LDAP login user UserId degree StudyDegreeId -- Abschluss, i.e. Master, Bachelor, etc. diff --git a/package-lock.json b/package-lock.json index 0ffdeb526..dd474e46f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,13 +1,13 @@ { "name": "uni2work", - "version": "5.5.0", + "version": "6.4.0", "lockfileVersion": 1, "requires": true, "dependencies": { "@babel/cli": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.4.4.tgz", - "integrity": "sha512-XGr5YjQSjgTa6OzQZY57FAJsdeVSAKR/u/KA5exWIz66IKtv/zXtHy+fIZcMry/EgYegwuHE7vzGnrFhjdIAsQ==", + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.5.5.tgz", + "integrity": "sha512-UHI+7pHv/tk9g6WXQKYz+kmXTI77YtuY3vqC59KIqcoWEjsJJSG6rAxKaLsgj3LDyadsPrCB929gVOKM6Hui0w==", "dev": true, "requires": { "chokidar": "^2.0.4", @@ -15,11 +15,19 @@ "convert-source-map": "^1.1.0", "fs-readdir-recursive": "^1.1.0", "glob": "^7.0.0", - "lodash": "^4.17.11", + "lodash": "^4.17.13", "mkdirp": "^0.5.1", "output-file-sync": "^2.0.0", "slash": "^2.0.0", "source-map": "^0.5.0" + }, + "dependencies": { + "lodash": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", + "dev": true + } } }, "@babel/code-frame": { @@ -32,27 +40,83 @@ } }, "@babel/core": { - "version": "7.4.5", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.4.5.tgz", - "integrity": "sha512-OvjIh6aqXtlsA8ujtGKfC7LYWksYSX8yQcM8Ay3LuvVeQ63lcOKgoZWVqcpFwkd29aYU9rVx7jxhfhiEDV9MZA==", + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.5.5.tgz", + "integrity": "sha512-i4qoSr2KTtce0DmkuuQBV4AuQgGPUcPXMr9L5MyYAtk06z068lQ10a4O009fe5OB/DfNV+h+qqT7ddNV8UnRjg==", "dev": true, "requires": { - "@babel/code-frame": "^7.0.0", - "@babel/generator": "^7.4.4", - "@babel/helpers": "^7.4.4", - "@babel/parser": "^7.4.5", + "@babel/code-frame": "^7.5.5", + "@babel/generator": "^7.5.5", + "@babel/helpers": "^7.5.5", + "@babel/parser": "^7.5.5", "@babel/template": "^7.4.4", - "@babel/traverse": "^7.4.5", - "@babel/types": "^7.4.4", + "@babel/traverse": "^7.5.5", + "@babel/types": "^7.5.5", "convert-source-map": "^1.1.0", "debug": "^4.1.0", "json5": "^2.1.0", - "lodash": "^4.17.11", + "lodash": "^4.17.13", "resolve": "^1.3.2", "semver": "^5.4.1", "source-map": "^0.5.0" }, "dependencies": { + "@babel/code-frame": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.5.5.tgz", + "integrity": "sha512-27d4lZoomVyo51VegxI20xZPuSHusqbQag/ztrBC7wegWoQ1nLREPVSKSW8byhTlzTKyNE4ifaTA6lCp7JjpFw==", + "dev": true, + "requires": { + "@babel/highlight": "^7.0.0" + } + }, + "@babel/generator": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.5.5.tgz", + "integrity": "sha512-ETI/4vyTSxTzGnU2c49XHv2zhExkv9JHLTwDAFz85kmcwuShvYG2H08FwgIguQf4JC75CBnXAUM5PqeF4fj0nQ==", + "dev": true, + "requires": { + "@babel/types": "^7.5.5", + "jsesc": "^2.5.1", + "lodash": "^4.17.13", + "source-map": "^0.5.0", + "trim-right": "^1.0.1" + } + }, + "@babel/parser": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.5.5.tgz", + "integrity": "sha512-E5BN68cqR7dhKan1SfqgPGhQ178bkVKpXTPEXnFJBrEt8/DKRZlybmy+IgYLTeN7tp1R5Ccmbm2rBk17sHYU3g==", + "dev": true + }, + "@babel/traverse": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.5.5.tgz", + "integrity": "sha512-MqB0782whsfffYfSjH4TM+LMjrJnhCNEDMDIjeTpl+ASaUvxcjoiVCo/sM1GhS1pHOXYfWVCYneLjMckuUxDaQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.5.5", + "@babel/generator": "^7.5.5", + "@babel/helper-function-name": "^7.1.0", + "@babel/helper-split-export-declaration": "^7.4.4", + "@babel/parser": "^7.5.5", + "@babel/types": "^7.5.5", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.13" + } + }, + "@babel/types": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.5.5.tgz", + "integrity": "sha512-s63F9nJioLqOlW3UkyMd+BYhXt44YuaFm/VV0VwuteqjYwRrObkU7ra9pY4wAJR3oXi8hJrMcrcJdO/HH33vtw==", + "dev": true, + "requires": { + "esutils": "^2.0.2", + "lodash": "^4.17.13", + "to-fast-properties": "^2.0.0" + } + }, "debug": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", @@ -62,10 +126,16 @@ "ms": "^2.1.1" } }, + "lodash": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", + "dev": true + }, "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true } } @@ -128,14 +198,33 @@ } }, "@babel/helper-define-map": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/helper-define-map/-/helper-define-map-7.4.4.tgz", - "integrity": "sha512-IX3Ln8gLhZpSuqHJSnTNBWGDE9kdkTEWl21A/K7PQ00tseBwbqCHTvNLHSBd9M0R5rER4h5Rsvj9vw0R5SieBg==", + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/helper-define-map/-/helper-define-map-7.5.5.tgz", + "integrity": "sha512-fTfxx7i0B5NJqvUOBBGREnrqbTxRh7zinBANpZXAVDlsZxYdclDp467G1sQ8VZYMnAURY3RpBUAgOYT9GfzHBg==", "dev": true, "requires": { "@babel/helper-function-name": "^7.1.0", - "@babel/types": "^7.4.4", - "lodash": "^4.17.11" + "@babel/types": "^7.5.5", + "lodash": "^4.17.13" + }, + "dependencies": { + "@babel/types": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.5.5.tgz", + "integrity": "sha512-s63F9nJioLqOlW3UkyMd+BYhXt44YuaFm/VV0VwuteqjYwRrObkU7ra9pY4wAJR3oXi8hJrMcrcJdO/HH33vtw==", + "dev": true, + "requires": { + "esutils": "^2.0.2", + "lodash": "^4.17.13", + "to-fast-properties": "^2.0.0" + } + }, + "lodash": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", + "dev": true + } } }, "@babel/helper-explode-assignable-expression": { @@ -196,17 +285,36 @@ } }, "@babel/helper-module-transforms": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.4.4.tgz", - "integrity": "sha512-3Z1yp8TVQf+B4ynN7WoHPKS8EkdTbgAEy0nU0rs/1Kw4pDgmvYH3rz3aI11KgxKCba2cn7N+tqzV1mY2HMN96w==", + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.5.5.tgz", + "integrity": "sha512-jBeCvETKuJqeiaCdyaheF40aXnnU1+wkSiUs/IQg3tB85up1LyL8x77ClY8qJpuRJUcXQo+ZtdNESmZl4j56Pw==", "dev": true, "requires": { "@babel/helper-module-imports": "^7.0.0", "@babel/helper-simple-access": "^7.1.0", "@babel/helper-split-export-declaration": "^7.4.4", "@babel/template": "^7.4.4", - "@babel/types": "^7.4.4", - "lodash": "^4.17.11" + "@babel/types": "^7.5.5", + "lodash": "^4.17.13" + }, + "dependencies": { + "@babel/types": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.5.5.tgz", + "integrity": "sha512-s63F9nJioLqOlW3UkyMd+BYhXt44YuaFm/VV0VwuteqjYwRrObkU7ra9pY4wAJR3oXi8hJrMcrcJdO/HH33vtw==", + "dev": true, + "requires": { + "esutils": "^2.0.2", + "lodash": "^4.17.13", + "to-fast-properties": "^2.0.0" + } + }, + "lodash": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", + "dev": true + } } }, "@babel/helper-optimise-call-expression": { @@ -225,12 +333,20 @@ "dev": true }, "@babel/helper-regex": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/helper-regex/-/helper-regex-7.4.4.tgz", - "integrity": "sha512-Y5nuB/kESmR3tKjU8Nkn1wMGEx1tjJX076HBMeL3XLQCu6vA/YRzuTW0bbb+qRnXvQGn+d6Rx953yffl8vEy7Q==", + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/helper-regex/-/helper-regex-7.5.5.tgz", + "integrity": "sha512-CkCYQLkfkiugbRDO8eZn6lRuR8kzZoGXCg3149iTk5se7g6qykSpy3+hELSwquhu+TgHn8nkLiBwHvNX8Hofcw==", "dev": true, "requires": { - "lodash": "^4.17.11" + "lodash": "^4.17.13" + }, + "dependencies": { + "lodash": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", + "dev": true + } } }, "@babel/helper-remap-async-to-generator": { @@ -290,14 +406,93 @@ } }, "@babel/helpers": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.4.4.tgz", - "integrity": "sha512-igczbR/0SeuPR8RFfC7tGrbdTbFL3QTvH6D+Z6zNxnTe//GyqmtHmDkzrqDmyZ3eSwPqB/LhyKoU5DXsp+Vp2A==", + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.5.5.tgz", + "integrity": "sha512-nRq2BUhxZFnfEn/ciJuhklHvFOqjJUD5wpx+1bxUF2axL9C+v4DE/dmp5sT2dKnpOs4orZWzpAZqlCy8QqE/7g==", "dev": true, "requires": { "@babel/template": "^7.4.4", - "@babel/traverse": "^7.4.4", - "@babel/types": "^7.4.4" + "@babel/traverse": "^7.5.5", + "@babel/types": "^7.5.5" + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.5.5.tgz", + "integrity": "sha512-27d4lZoomVyo51VegxI20xZPuSHusqbQag/ztrBC7wegWoQ1nLREPVSKSW8byhTlzTKyNE4ifaTA6lCp7JjpFw==", + "dev": true, + "requires": { + "@babel/highlight": "^7.0.0" + } + }, + "@babel/generator": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.5.5.tgz", + "integrity": "sha512-ETI/4vyTSxTzGnU2c49XHv2zhExkv9JHLTwDAFz85kmcwuShvYG2H08FwgIguQf4JC75CBnXAUM5PqeF4fj0nQ==", + "dev": true, + "requires": { + "@babel/types": "^7.5.5", + "jsesc": "^2.5.1", + "lodash": "^4.17.13", + "source-map": "^0.5.0", + "trim-right": "^1.0.1" + } + }, + "@babel/parser": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.5.5.tgz", + "integrity": "sha512-E5BN68cqR7dhKan1SfqgPGhQ178bkVKpXTPEXnFJBrEt8/DKRZlybmy+IgYLTeN7tp1R5Ccmbm2rBk17sHYU3g==", + "dev": true + }, + "@babel/traverse": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.5.5.tgz", + "integrity": "sha512-MqB0782whsfffYfSjH4TM+LMjrJnhCNEDMDIjeTpl+ASaUvxcjoiVCo/sM1GhS1pHOXYfWVCYneLjMckuUxDaQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.5.5", + "@babel/generator": "^7.5.5", + "@babel/helper-function-name": "^7.1.0", + "@babel/helper-split-export-declaration": "^7.4.4", + "@babel/parser": "^7.5.5", + "@babel/types": "^7.5.5", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.13" + } + }, + "@babel/types": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.5.5.tgz", + "integrity": "sha512-s63F9nJioLqOlW3UkyMd+BYhXt44YuaFm/VV0VwuteqjYwRrObkU7ra9pY4wAJR3oXi8hJrMcrcJdO/HH33vtw==", + "dev": true, + "requires": { + "esutils": "^2.0.2", + "lodash": "^4.17.13", + "to-fast-properties": "^2.0.0" + } + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "lodash": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", + "dev": true + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } } }, "@babel/highlight": { @@ -329,13 +524,127 @@ } }, "@babel/plugin-proposal-class-properties": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.4.4.tgz", - "integrity": "sha512-WjKTI8g8d5w1Bc9zgwSz2nfrsNQsXcCf9J9cdCvrJV6RF56yztwm4TmJC0MgJ9tvwO9gUA/mcYe89bLdGfiXFg==", + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.5.5.tgz", + "integrity": "sha512-AF79FsnWFxjlaosgdi421vmYG6/jg79bVD0dpD44QdgobzHKuLZ6S3vl8la9qIeSwGi8i1fS0O1mfuDAAdo1/A==", "dev": true, "requires": { - "@babel/helper-create-class-features-plugin": "^7.4.4", + "@babel/helper-create-class-features-plugin": "^7.5.5", "@babel/helper-plugin-utils": "^7.0.0" + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.5.5.tgz", + "integrity": "sha512-27d4lZoomVyo51VegxI20xZPuSHusqbQag/ztrBC7wegWoQ1nLREPVSKSW8byhTlzTKyNE4ifaTA6lCp7JjpFw==", + "dev": true, + "requires": { + "@babel/highlight": "^7.0.0" + } + }, + "@babel/generator": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.5.5.tgz", + "integrity": "sha512-ETI/4vyTSxTzGnU2c49XHv2zhExkv9JHLTwDAFz85kmcwuShvYG2H08FwgIguQf4JC75CBnXAUM5PqeF4fj0nQ==", + "dev": true, + "requires": { + "@babel/types": "^7.5.5", + "jsesc": "^2.5.1", + "lodash": "^4.17.13", + "source-map": "^0.5.0", + "trim-right": "^1.0.1" + } + }, + "@babel/helper-create-class-features-plugin": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.5.5.tgz", + "integrity": "sha512-ZsxkyYiRA7Bg+ZTRpPvB6AbOFKTFFK4LrvTet8lInm0V468MWCaSYJE+I7v2z2r8KNLtYiV+K5kTCnR7dvyZjg==", + "dev": true, + "requires": { + "@babel/helper-function-name": "^7.1.0", + "@babel/helper-member-expression-to-functions": "^7.5.5", + "@babel/helper-optimise-call-expression": "^7.0.0", + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-replace-supers": "^7.5.5", + "@babel/helper-split-export-declaration": "^7.4.4" + } + }, + "@babel/helper-member-expression-to-functions": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.5.5.tgz", + "integrity": "sha512-5qZ3D1uMclSNqYcXqiHoA0meVdv+xUEex9em2fqMnrk/scphGlGgg66zjMrPJESPwrFJ6sbfFQYUSa0Mz7FabA==", + "dev": true, + "requires": { + "@babel/types": "^7.5.5" + } + }, + "@babel/helper-replace-supers": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.5.5.tgz", + "integrity": "sha512-XvRFWrNnlsow2u7jXDuH4jDDctkxbS7gXssrP4q2nUD606ukXHRvydj346wmNg+zAgpFx4MWf4+usfC93bElJg==", + "dev": true, + "requires": { + "@babel/helper-member-expression-to-functions": "^7.5.5", + "@babel/helper-optimise-call-expression": "^7.0.0", + "@babel/traverse": "^7.5.5", + "@babel/types": "^7.5.5" + } + }, + "@babel/parser": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.5.5.tgz", + "integrity": "sha512-E5BN68cqR7dhKan1SfqgPGhQ178bkVKpXTPEXnFJBrEt8/DKRZlybmy+IgYLTeN7tp1R5Ccmbm2rBk17sHYU3g==", + "dev": true + }, + "@babel/traverse": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.5.5.tgz", + "integrity": "sha512-MqB0782whsfffYfSjH4TM+LMjrJnhCNEDMDIjeTpl+ASaUvxcjoiVCo/sM1GhS1pHOXYfWVCYneLjMckuUxDaQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.5.5", + "@babel/generator": "^7.5.5", + "@babel/helper-function-name": "^7.1.0", + "@babel/helper-split-export-declaration": "^7.4.4", + "@babel/parser": "^7.5.5", + "@babel/types": "^7.5.5", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.13" + } + }, + "@babel/types": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.5.5.tgz", + "integrity": "sha512-s63F9nJioLqOlW3UkyMd+BYhXt44YuaFm/VV0VwuteqjYwRrObkU7ra9pY4wAJR3oXi8hJrMcrcJdO/HH33vtw==", + "dev": true, + "requires": { + "esutils": "^2.0.2", + "lodash": "^4.17.13", + "to-fast-properties": "^2.0.0" + } + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "lodash": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", + "dev": true + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } } }, "@babel/plugin-proposal-decorators": { @@ -349,6 +658,16 @@ "@babel/plugin-syntax-decorators": "^7.2.0" } }, + "@babel/plugin-proposal-dynamic-import": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.5.0.tgz", + "integrity": "sha512-x/iMjggsKTFHYC6g11PL7Qy58IK8H5zqfm9e6hu4z1iH2IRyAp9u9dL80zA6R76yFovETFLKz2VJIC2iIPBuFw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-syntax-dynamic-import": "^7.2.0" + } + }, "@babel/plugin-proposal-json-strings": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.2.0.tgz", @@ -360,9 +679,9 @@ } }, "@babel/plugin-proposal-object-rest-spread": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.4.4.tgz", - "integrity": "sha512-dMBG6cSPBbHeEBdFXeQ2QLc5gUpg4Vkaz8octD4aoW/ISO+jBOcsuxYL7bsb5WSu8RLP6boxrBIALEHgoHtO9g==", + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.5.5.tgz", + "integrity": "sha512-F2DxJJSQ7f64FyTVl5cw/9MWn6naXGdk3Q3UhDbFEEHv+EilCPoeRD3Zh/Utx1CJz4uyKlQ4uH+bJPbEhMV7Zw==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0", @@ -408,6 +727,15 @@ "@babel/helper-plugin-utils": "^7.0.0" } }, + "@babel/plugin-syntax-dynamic-import": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.2.0.tgz", + "integrity": "sha512-mVxuJ0YroI/h/tbFTPGZR8cv6ai+STMKNBq0f8hFxsxWjl94qqhsb+wXbpNMDPU3cfR1TIsVFzU3nXyZMqyK4w==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, "@babel/plugin-syntax-json-strings": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.2.0.tgz", @@ -445,9 +773,9 @@ } }, "@babel/plugin-transform-async-to-generator": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.4.4.tgz", - "integrity": "sha512-YiqW2Li8TXmzgbXw+STsSqPBPFnGviiaSp6CYOq55X8GQ2SGVLrXB6pNid8HkqkZAzOH6knbai3snhP7v0fNwA==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.5.0.tgz", + "integrity": "sha512-mqvkzwIGkq0bEF1zLRRiTdjfomZJDV33AH3oQzHVGkI2VzEmXLpKKOBvEVaFZBJdN0XTyH38s9j/Kiqr68dggg==", "dev": true, "requires": { "@babel/helper-module-imports": "^7.0.0", @@ -465,29 +793,137 @@ } }, "@babel/plugin-transform-block-scoping": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.4.4.tgz", - "integrity": "sha512-jkTUyWZcTrwxu5DD4rWz6rDB5Cjdmgz6z7M7RLXOJyCUkFBawssDGcGh8M/0FTSB87avyJI1HsTwUXp9nKA1PA==", + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.5.5.tgz", + "integrity": "sha512-82A3CLRRdYubkG85lKwhZB0WZoHxLGsJdux/cOVaJCJpvYFl1LVzAIFyRsa7CvXqW8rBM4Zf3Bfn8PHt5DP0Sg==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0", - "lodash": "^4.17.11" + "lodash": "^4.17.13" + }, + "dependencies": { + "lodash": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", + "dev": true + } } }, "@babel/plugin-transform-classes": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.4.4.tgz", - "integrity": "sha512-/e44eFLImEGIpL9qPxSRat13I5QNRgBLu2hOQJCF7VLy/otSM/sypV1+XaIw5+502RX/+6YaSAPmldk+nhHDPw==", + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.5.5.tgz", + "integrity": "sha512-U2htCNK/6e9K7jGyJ++1p5XRU+LJjrwtoiVn9SzRlDT2KubcZ11OOwy3s24TjHxPgxNwonCYP7U2K51uVYCMDg==", "dev": true, "requires": { "@babel/helper-annotate-as-pure": "^7.0.0", - "@babel/helper-define-map": "^7.4.4", + "@babel/helper-define-map": "^7.5.5", "@babel/helper-function-name": "^7.1.0", "@babel/helper-optimise-call-expression": "^7.0.0", "@babel/helper-plugin-utils": "^7.0.0", - "@babel/helper-replace-supers": "^7.4.4", + "@babel/helper-replace-supers": "^7.5.5", "@babel/helper-split-export-declaration": "^7.4.4", "globals": "^11.1.0" + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.5.5.tgz", + "integrity": "sha512-27d4lZoomVyo51VegxI20xZPuSHusqbQag/ztrBC7wegWoQ1nLREPVSKSW8byhTlzTKyNE4ifaTA6lCp7JjpFw==", + "dev": true, + "requires": { + "@babel/highlight": "^7.0.0" + } + }, + "@babel/generator": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.5.5.tgz", + "integrity": "sha512-ETI/4vyTSxTzGnU2c49XHv2zhExkv9JHLTwDAFz85kmcwuShvYG2H08FwgIguQf4JC75CBnXAUM5PqeF4fj0nQ==", + "dev": true, + "requires": { + "@babel/types": "^7.5.5", + "jsesc": "^2.5.1", + "lodash": "^4.17.13", + "source-map": "^0.5.0", + "trim-right": "^1.0.1" + } + }, + "@babel/helper-member-expression-to-functions": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.5.5.tgz", + "integrity": "sha512-5qZ3D1uMclSNqYcXqiHoA0meVdv+xUEex9em2fqMnrk/scphGlGgg66zjMrPJESPwrFJ6sbfFQYUSa0Mz7FabA==", + "dev": true, + "requires": { + "@babel/types": "^7.5.5" + } + }, + "@babel/helper-replace-supers": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.5.5.tgz", + "integrity": "sha512-XvRFWrNnlsow2u7jXDuH4jDDctkxbS7gXssrP4q2nUD606ukXHRvydj346wmNg+zAgpFx4MWf4+usfC93bElJg==", + "dev": true, + "requires": { + "@babel/helper-member-expression-to-functions": "^7.5.5", + "@babel/helper-optimise-call-expression": "^7.0.0", + "@babel/traverse": "^7.5.5", + "@babel/types": "^7.5.5" + } + }, + "@babel/parser": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.5.5.tgz", + "integrity": "sha512-E5BN68cqR7dhKan1SfqgPGhQ178bkVKpXTPEXnFJBrEt8/DKRZlybmy+IgYLTeN7tp1R5Ccmbm2rBk17sHYU3g==", + "dev": true + }, + "@babel/traverse": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.5.5.tgz", + "integrity": "sha512-MqB0782whsfffYfSjH4TM+LMjrJnhCNEDMDIjeTpl+ASaUvxcjoiVCo/sM1GhS1pHOXYfWVCYneLjMckuUxDaQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.5.5", + "@babel/generator": "^7.5.5", + "@babel/helper-function-name": "^7.1.0", + "@babel/helper-split-export-declaration": "^7.4.4", + "@babel/parser": "^7.5.5", + "@babel/types": "^7.5.5", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.13" + } + }, + "@babel/types": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.5.5.tgz", + "integrity": "sha512-s63F9nJioLqOlW3UkyMd+BYhXt44YuaFm/VV0VwuteqjYwRrObkU7ra9pY4wAJR3oXi8hJrMcrcJdO/HH33vtw==", + "dev": true, + "requires": { + "esutils": "^2.0.2", + "lodash": "^4.17.13", + "to-fast-properties": "^2.0.0" + } + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "lodash": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", + "dev": true + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } } }, "@babel/plugin-transform-computed-properties": { @@ -500,9 +936,9 @@ } }, "@babel/plugin-transform-destructuring": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.4.4.tgz", - "integrity": "sha512-/aOx+nW0w8eHiEHm+BTERB2oJn5D127iye/SUQl7NjHy0lf+j7h4MKMMSOwdazGq9OxgiNADncE+SRJkCxjZpQ==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.5.0.tgz", + "integrity": "sha512-YbYgbd3TryYYLGyC7ZR+Tq8H/+bCmwoaxHfJHupom5ECstzbRLTch6gOQbhEY9Z4hiCNHEURgq06ykFv9JZ/QQ==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0" @@ -520,9 +956,9 @@ } }, "@babel/plugin-transform-duplicate-keys": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.2.0.tgz", - "integrity": "sha512-q+yuxW4DsTjNceUiTzK0L+AfQ0zD9rWaTLiUqHA8p0gxx7lu1EylenfzjeIWNkPy6e/0VG/Wjw9uf9LueQwLOw==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.5.0.tgz", + "integrity": "sha512-igcziksHizyQPlX9gfSjHkE2wmoCH3evvD2qR5w29/Dk0SMKE/eOI7f1HhBdNhR/zxJDqrgpoDTq5YSLH/XMsQ==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0" @@ -576,34 +1012,37 @@ } }, "@babel/plugin-transform-modules-amd": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.2.0.tgz", - "integrity": "sha512-mK2A8ucqz1qhrdqjS9VMIDfIvvT2thrEsIQzbaTdc5QFzhDjQv2CkJJ5f6BXIkgbmaoax3zBr2RyvV/8zeoUZw==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.5.0.tgz", + "integrity": "sha512-n20UsQMKnWrltocZZm24cRURxQnWIvsABPJlw/fvoy9c6AgHZzoelAIzajDHAQrDpuKFFPPcFGd7ChsYuIUMpg==", "dev": true, "requires": { "@babel/helper-module-transforms": "^7.1.0", - "@babel/helper-plugin-utils": "^7.0.0" + "@babel/helper-plugin-utils": "^7.0.0", + "babel-plugin-dynamic-import-node": "^2.3.0" } }, "@babel/plugin-transform-modules-commonjs": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.4.4.tgz", - "integrity": "sha512-4sfBOJt58sEo9a2BQXnZq+Q3ZTSAUXyK3E30o36BOGnJ+tvJ6YSxF0PG6kERvbeISgProodWuI9UVG3/FMY6iw==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.5.0.tgz", + "integrity": "sha512-xmHq0B+ytyrWJvQTc5OWAC4ii6Dhr0s22STOoydokG51JjWhyYo5mRPXoi+ZmtHQhZZwuXNN+GG5jy5UZZJxIQ==", "dev": true, "requires": { "@babel/helper-module-transforms": "^7.4.4", "@babel/helper-plugin-utils": "^7.0.0", - "@babel/helper-simple-access": "^7.1.0" + "@babel/helper-simple-access": "^7.1.0", + "babel-plugin-dynamic-import-node": "^2.3.0" } }, "@babel/plugin-transform-modules-systemjs": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.4.4.tgz", - "integrity": "sha512-MSiModfILQc3/oqnG7NrP1jHaSPryO6tA2kOMmAQApz5dayPxWiHqmq4sWH2xF5LcQK56LlbKByCd8Aah/OIkQ==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.5.0.tgz", + "integrity": "sha512-Q2m56tyoQWmuNGxEtUyeEkm6qJYFqs4c+XyXH5RAuYxObRNz9Zgj/1g2GMnjYp2EUyEy7YTrxliGCXzecl/vJg==", "dev": true, "requires": { "@babel/helper-hoist-variables": "^7.4.4", - "@babel/helper-plugin-utils": "^7.0.0" + "@babel/helper-plugin-utils": "^7.0.0", + "babel-plugin-dynamic-import-node": "^2.3.0" } }, "@babel/plugin-transform-modules-umd": { @@ -635,13 +1074,113 @@ } }, "@babel/plugin-transform-object-super": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.2.0.tgz", - "integrity": "sha512-VMyhPYZISFZAqAPVkiYb7dUe2AsVi2/wCT5+wZdsNO31FojQJa9ns40hzZ6U9f50Jlq4w6qwzdBB2uwqZ00ebg==", + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.5.5.tgz", + "integrity": "sha512-un1zJQAhSosGFBduPgN/YFNvWVpRuHKU7IHBglLoLZsGmruJPOo6pbInneflUdmq7YvSVqhpPs5zdBvLnteltQ==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0", - "@babel/helper-replace-supers": "^7.1.0" + "@babel/helper-replace-supers": "^7.5.5" + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.5.5.tgz", + "integrity": "sha512-27d4lZoomVyo51VegxI20xZPuSHusqbQag/ztrBC7wegWoQ1nLREPVSKSW8byhTlzTKyNE4ifaTA6lCp7JjpFw==", + "dev": true, + "requires": { + "@babel/highlight": "^7.0.0" + } + }, + "@babel/generator": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.5.5.tgz", + "integrity": "sha512-ETI/4vyTSxTzGnU2c49XHv2zhExkv9JHLTwDAFz85kmcwuShvYG2H08FwgIguQf4JC75CBnXAUM5PqeF4fj0nQ==", + "dev": true, + "requires": { + "@babel/types": "^7.5.5", + "jsesc": "^2.5.1", + "lodash": "^4.17.13", + "source-map": "^0.5.0", + "trim-right": "^1.0.1" + } + }, + "@babel/helper-member-expression-to-functions": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.5.5.tgz", + "integrity": "sha512-5qZ3D1uMclSNqYcXqiHoA0meVdv+xUEex9em2fqMnrk/scphGlGgg66zjMrPJESPwrFJ6sbfFQYUSa0Mz7FabA==", + "dev": true, + "requires": { + "@babel/types": "^7.5.5" + } + }, + "@babel/helper-replace-supers": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.5.5.tgz", + "integrity": "sha512-XvRFWrNnlsow2u7jXDuH4jDDctkxbS7gXssrP4q2nUD606ukXHRvydj346wmNg+zAgpFx4MWf4+usfC93bElJg==", + "dev": true, + "requires": { + "@babel/helper-member-expression-to-functions": "^7.5.5", + "@babel/helper-optimise-call-expression": "^7.0.0", + "@babel/traverse": "^7.5.5", + "@babel/types": "^7.5.5" + } + }, + "@babel/parser": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.5.5.tgz", + "integrity": "sha512-E5BN68cqR7dhKan1SfqgPGhQ178bkVKpXTPEXnFJBrEt8/DKRZlybmy+IgYLTeN7tp1R5Ccmbm2rBk17sHYU3g==", + "dev": true + }, + "@babel/traverse": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.5.5.tgz", + "integrity": "sha512-MqB0782whsfffYfSjH4TM+LMjrJnhCNEDMDIjeTpl+ASaUvxcjoiVCo/sM1GhS1pHOXYfWVCYneLjMckuUxDaQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.5.5", + "@babel/generator": "^7.5.5", + "@babel/helper-function-name": "^7.1.0", + "@babel/helper-split-export-declaration": "^7.4.4", + "@babel/parser": "^7.5.5", + "@babel/types": "^7.5.5", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.13" + } + }, + "@babel/types": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.5.5.tgz", + "integrity": "sha512-s63F9nJioLqOlW3UkyMd+BYhXt44YuaFm/VV0VwuteqjYwRrObkU7ra9pY4wAJR3oXi8hJrMcrcJdO/HH33vtw==", + "dev": true, + "requires": { + "esutils": "^2.0.2", + "lodash": "^4.17.13", + "to-fast-properties": "^2.0.0" + } + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "lodash": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", + "dev": true + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } } }, "@babel/plugin-transform-parameters": { @@ -682,6 +1221,18 @@ "@babel/helper-plugin-utils": "^7.0.0" } }, + "@babel/plugin-transform-runtime": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.5.5.tgz", + "integrity": "sha512-6Xmeidsun5rkwnGfMOp6/z9nSzWpHFNVr2Jx7kwoq4mVatQfQx5S56drBgEHF+XQbKOdIaOiMIINvp/kAwMN+w==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.0.0", + "@babel/helper-plugin-utils": "^7.0.0", + "resolve": "^1.8.1", + "semver": "^5.5.1" + } + }, "@babel/plugin-transform-shorthand-properties": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.2.0.tgz", @@ -741,43 +1292,45 @@ } }, "@babel/preset-env": { - "version": "7.4.5", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.4.5.tgz", - "integrity": "sha512-f2yNVXM+FsR5V8UwcFeIHzHWgnhXg3NpRmy0ADvALpnhB0SLbCvrCRr4BLOUYbQNLS+Z0Yer46x9dJXpXewI7w==", + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.5.5.tgz", + "integrity": "sha512-GMZQka/+INwsMz1A5UEql8tG015h5j/qjptpKY2gJ7giy8ohzU710YciJB5rcKsWGWHiW3RUnHib0E5/m3Tp3A==", "dev": true, "requires": { "@babel/helper-module-imports": "^7.0.0", "@babel/helper-plugin-utils": "^7.0.0", "@babel/plugin-proposal-async-generator-functions": "^7.2.0", + "@babel/plugin-proposal-dynamic-import": "^7.5.0", "@babel/plugin-proposal-json-strings": "^7.2.0", - "@babel/plugin-proposal-object-rest-spread": "^7.4.4", + "@babel/plugin-proposal-object-rest-spread": "^7.5.5", "@babel/plugin-proposal-optional-catch-binding": "^7.2.0", "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", "@babel/plugin-syntax-async-generators": "^7.2.0", + "@babel/plugin-syntax-dynamic-import": "^7.2.0", "@babel/plugin-syntax-json-strings": "^7.2.0", "@babel/plugin-syntax-object-rest-spread": "^7.2.0", "@babel/plugin-syntax-optional-catch-binding": "^7.2.0", "@babel/plugin-transform-arrow-functions": "^7.2.0", - "@babel/plugin-transform-async-to-generator": "^7.4.4", + "@babel/plugin-transform-async-to-generator": "^7.5.0", "@babel/plugin-transform-block-scoped-functions": "^7.2.0", - "@babel/plugin-transform-block-scoping": "^7.4.4", - "@babel/plugin-transform-classes": "^7.4.4", + "@babel/plugin-transform-block-scoping": "^7.5.5", + "@babel/plugin-transform-classes": "^7.5.5", "@babel/plugin-transform-computed-properties": "^7.2.0", - "@babel/plugin-transform-destructuring": "^7.4.4", + "@babel/plugin-transform-destructuring": "^7.5.0", "@babel/plugin-transform-dotall-regex": "^7.4.4", - "@babel/plugin-transform-duplicate-keys": "^7.2.0", + "@babel/plugin-transform-duplicate-keys": "^7.5.0", "@babel/plugin-transform-exponentiation-operator": "^7.2.0", "@babel/plugin-transform-for-of": "^7.4.4", "@babel/plugin-transform-function-name": "^7.4.4", "@babel/plugin-transform-literals": "^7.2.0", "@babel/plugin-transform-member-expression-literals": "^7.2.0", - "@babel/plugin-transform-modules-amd": "^7.2.0", - "@babel/plugin-transform-modules-commonjs": "^7.4.4", - "@babel/plugin-transform-modules-systemjs": "^7.4.4", + "@babel/plugin-transform-modules-amd": "^7.5.0", + "@babel/plugin-transform-modules-commonjs": "^7.5.0", + "@babel/plugin-transform-modules-systemjs": "^7.5.0", "@babel/plugin-transform-modules-umd": "^7.2.0", "@babel/plugin-transform-named-capturing-groups-regex": "^7.4.5", "@babel/plugin-transform-new-target": "^7.4.4", - "@babel/plugin-transform-object-super": "^7.2.0", + "@babel/plugin-transform-object-super": "^7.5.5", "@babel/plugin-transform-parameters": "^7.4.4", "@babel/plugin-transform-property-literals": "^7.2.0", "@babel/plugin-transform-regenerator": "^7.4.5", @@ -788,28 +1341,45 @@ "@babel/plugin-transform-template-literals": "^7.4.4", "@babel/plugin-transform-typeof-symbol": "^7.2.0", "@babel/plugin-transform-unicode-regex": "^7.4.4", - "@babel/types": "^7.4.4", + "@babel/types": "^7.5.5", "browserslist": "^4.6.0", "core-js-compat": "^3.1.1", "invariant": "^2.2.2", "js-levenshtein": "^1.1.3", "semver": "^5.5.0" + }, + "dependencies": { + "@babel/types": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.5.5.tgz", + "integrity": "sha512-s63F9nJioLqOlW3UkyMd+BYhXt44YuaFm/VV0VwuteqjYwRrObkU7ra9pY4wAJR3oXi8hJrMcrcJdO/HH33vtw==", + "dev": true, + "requires": { + "esutils": "^2.0.2", + "lodash": "^4.17.13", + "to-fast-properties": "^2.0.0" + } + }, + "lodash": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", + "dev": true + } } }, "@babel/runtime": { - "version": "7.4.5", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.4.5.tgz", - "integrity": "sha512-TuI4qpWZP6lGOGIuGWtp9sPluqYICmbk8T/1vpSysqJxRPkudh/ofFWyqdcMsDf2s7KvDL4/YHgKyvcS3g9CJQ==", - "dev": true, + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.5.5.tgz", + "integrity": "sha512-28QvEGyQyNkB0/m2B4FU7IEZGK2NUrcMtT6BZEFALTguLk+AUT6ofsHtPk5QyjAdUkpMJ+/Em+quwz4HOt30AQ==", "requires": { "regenerator-runtime": "^0.13.2" }, "dependencies": { "regenerator-runtime": { - "version": "0.13.2", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.2.tgz", - "integrity": "sha512-S/TQAZJO+D3m9xeN1WTI8dLKBBiRgXBlTJvbWjCThHWZj9EvHK70Ff50/tYj2J/fvBY6JtFVwRuazHN2E7M9BA==", - "dev": true + "version": "0.13.3", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.3.tgz", + "integrity": "sha512-naKIZz2GQ8JWh///G7L3X6LaQUAMp2lvb1rvwwsURe/VXwD6VMfr+/1NuNw3ag8v2kY1aQ/go5SNn79O9JU7yw==" } } }, @@ -1082,9 +1652,9 @@ } }, "@commitlint/config-conventional": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@commitlint/config-conventional/-/config-conventional-8.0.0.tgz", - "integrity": "sha512-umg1irroowOV+x8oZPBw8woCogZO5MFKUYQq+fRZvhowoSwDHXYILP3ETcdHUgvytw/K/a8Xvu7iCypK6oZQ+g==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@commitlint/config-conventional/-/config-conventional-8.1.0.tgz", + "integrity": "sha512-/JY+FNBnrT91qzDVIoV1Buiigvj7Le7ezFw+oRqu0nYREX03k7xnaG/7t7rUSvm7hM6dnLSOlaUsevjgMI9AEw==", "dev": true }, "@commitlint/ensure": { @@ -1327,9 +1897,9 @@ } }, "@types/node": { - "version": "12.6.8", - "resolved": "https://registry.npmjs.org/@types/node/-/node-12.6.8.tgz", - "integrity": "sha512-aX+gFgA5GHcDi89KG5keey2zf0WfZk/HAQotEamsK2kbey+8yGKcson0hbK8E+v0NArlCJQCqMP161YhV6ZXLg==", + "version": "12.7.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.7.2.tgz", + "integrity": "sha512-dyYO+f6ihZEtNPDcWNR1fkoTDf3zAK3lAABDze3mz6POyIercH0lEUawUFXlG8xaQZmm1yEBON/4TsYv/laDYg==", "dev": true }, "@types/normalize-package-data": { @@ -1564,12 +2134,6 @@ "integrity": "sha512-jPTiwtOxaHNaAPg/dmrJ/beuzLRnXtB0kQPQ8JpotKJgTB6rX6c8mlf315941pyjBSaPg8NHXS9fhP4u17DpGA==", "dev": true }, - "acorn-dynamic-import": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/acorn-dynamic-import/-/acorn-dynamic-import-4.0.0.tgz", - "integrity": "sha512-d3OEjQV4ROpoflsnUA8HozoIR504TFxNivYEUi6uwz0IYhBkTDXGuWlNdMtybRt3nqVx/L6XqMt0FxkXuWKZhw==", - "dev": true - }, "acorn-jsx": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.0.1.tgz", @@ -1853,9 +2417,9 @@ "dev": true }, "async-limiter": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", - "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", + "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==", "dev": true }, "asynckit": { @@ -1871,52 +2435,50 @@ "dev": true }, "autoprefixer": { - "version": "9.6.0", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.6.0.tgz", - "integrity": "sha512-kuip9YilBqhirhHEGHaBTZKXL//xxGnzvsD0FtBQa6z+A69qZD6s/BAX9VzDF1i9VKDquTJDQaPLSEhOnL6FvQ==", + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.6.1.tgz", + "integrity": "sha512-aVo5WxR3VyvyJxcJC3h4FKfwCQvQWb1tSI5VHNibddCVWrcD1NvlxEweg3TSgiPztMnWfjpy2FURKA2kvDE+Tw==", "dev": true, "requires": { - "browserslist": "^4.6.1", - "caniuse-lite": "^1.0.30000971", + "browserslist": "^4.6.3", + "caniuse-lite": "^1.0.30000980", "chalk": "^2.4.2", "normalize-range": "^0.1.2", "num2fraction": "^1.2.2", - "postcss": "^7.0.16", - "postcss-value-parser": "^3.3.1" + "postcss": "^7.0.17", + "postcss-value-parser": "^4.0.0" }, "dependencies": { - "browserslist": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.6.2.tgz", - "integrity": "sha512-2neU/V0giQy9h3XMPwLhEY3+Ao0uHSwHvU8Q1Ea6AgLVL1sXbX3dzPrJ8NWe5Hi4PoTkCYXOtVR9rfRLI0J/8Q==", + "postcss": { + "version": "7.0.17", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.17.tgz", + "integrity": "sha512-546ZowA+KZ3OasvQZHsbuEpysvwTZNGJv9EfyCQdsIDltPSWHAeTQ5fQy/Npi2ZDtLI3zs7Ps/p6wThErhm9fQ==", "dev": true, "requires": { - "caniuse-lite": "^1.0.30000974", - "electron-to-chromium": "^1.3.150", - "node-releases": "^1.1.23" - }, - "dependencies": { - "caniuse-lite": { - "version": "1.0.30000974", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000974.tgz", - "integrity": "sha512-xc3rkNS/Zc3CmpMKuczWEdY2sZgx09BkAxfvkxlAEBTqcMHeL8QnPqhKse+5sRTi3nrw2pJwToD2WvKn1Uhvww==", - "dev": true - } + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" } }, - "electron-to-chromium": { - "version": "1.3.164", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.164.tgz", - "integrity": "sha512-VLlalqUeduN4+fayVtRZvGP2Hl1WrRxlwzh2XVVMJym3IFrQUS29BFQ1GP/BxOJXJI1OFCrJ5BnFEsAe8NHtOg==", + "postcss-value-parser": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.0.2.tgz", + "integrity": "sha512-LmeoohTpp/K4UiyQCwuGWlONxXamGzCMtFxLq4W1nZVGIQLYvMCJx3yAF9qyyuFpflABI9yVdtJAqbihOsCsJQ==", "dev": true }, - "node-releases": { - "version": "1.1.23", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.23.tgz", - "integrity": "sha512-uq1iL79YjfYC0WXoHbC/z28q/9pOl8kSHaXdWmAAc8No+bDwqkZbzIJz55g/MUsPgSGm9LZ7QSUbzTcH5tz47w==", + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", "dev": true, "requires": { - "semver": "^5.3.0" + "has-flag": "^3.0.0" } } } @@ -2019,17 +2581,28 @@ } }, "babel-eslint": { - "version": "10.0.2", - "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-10.0.2.tgz", - "integrity": "sha512-UdsurWPtgiPgpJ06ryUnuaSXC2s0WoSZnQmEpbAH65XZSdwowgN5MvyP7e88nW07FYXv72erVtpBkxyDVKhH1Q==", + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-10.0.3.tgz", + "integrity": "sha512-z3U7eMY6r/3f3/JB9mTsLjyxrv0Yb1zb8PCWCLpguxfCzBIZUwy23R1t/XKewP+8mEN2Ck8Dtr4q20z6ce6SoA==", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", "@babel/parser": "^7.0.0", "@babel/traverse": "^7.0.0", "@babel/types": "^7.0.0", - "eslint-scope": "3.7.1", - "eslint-visitor-keys": "^1.0.0" + "eslint-visitor-keys": "^1.0.0", + "resolve": "^1.12.0" + }, + "dependencies": { + "resolve": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.12.0.tgz", + "integrity": "sha512-B/dOmuoAik5bKcD6s6nXDCjzUKnaDvdkRyAk6rsmsKLipWj4797iothd7jmmUhWTfinVMU+wc56rYKsit2Qy4w==", + "dev": true, + "requires": { + "path-parse": "^1.0.6" + } + } } }, "babel-generator": { @@ -2188,6 +2761,15 @@ "babel-runtime": "^6.22.0" } }, + "babel-plugin-dynamic-import-node": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.0.tgz", + "integrity": "sha512-o6qFkpeQEBxcqt0XYlWzAVxNCSCZdUgcR8IRlhD/8DylxjjO4foPcvTW0GGKa/cVt3rvxZ7o5ippJ+/0nvLhlQ==", + "dev": true, + "requires": { + "object.assign": "^4.1.0" + } + }, "babel-plugin-syntax-decorators": { "version": "6.13.0", "resolved": "https://registry.npmjs.org/babel-plugin-syntax-decorators/-/babel-plugin-syntax-decorators-6.13.0.tgz", @@ -2522,6 +3104,12 @@ "regenerator-runtime": "^0.10.5" }, "dependencies": { + "core-js": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.9.tgz", + "integrity": "sha512-HOpZf6eXmnl7la+cUdMnLvUxKNqLUzJvgIziQ0DiF3JwSImNphIqdGqzj6hIKyX04MmV0poclQ7+wjWvxQyR2A==", + "dev": true + }, "regenerator-runtime": { "version": "0.10.5", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz", @@ -2575,6 +3163,14 @@ "lodash": "^4.17.4", "mkdirp": "^0.5.1", "source-map-support": "^0.4.15" + }, + "dependencies": { + "core-js": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.9.tgz", + "integrity": "sha512-HOpZf6eXmnl7la+cUdMnLvUxKNqLUzJvgIziQ0DiF3JwSImNphIqdGqzj6hIKyX04MmV0poclQ7+wjWvxQyR2A==", + "dev": true + } } }, "babel-runtime": { @@ -2585,6 +3181,14 @@ "requires": { "core-js": "^2.4.0", "regenerator-runtime": "^0.11.0" + }, + "dependencies": { + "core-js": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.9.tgz", + "integrity": "sha512-HOpZf6eXmnl7la+cUdMnLvUxKNqLUzJvgIziQ0DiF3JwSImNphIqdGqzj6hIKyX04MmV0poclQ7+wjWvxQyR2A==", + "dev": true + } } }, "babel-template": { @@ -2725,9 +3329,9 @@ "dev": true }, "base64-js": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.0.tgz", - "integrity": "sha512-ccav/yGvoa80BQDljCxsmmQ3Xvx60/UpBIij5QN21W3wBi/hhIC9OoO+KLpu9IJTS9j4DRVJ3aDDF9cMSoa2lw==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", + "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==", "dev": true }, "base64id": { @@ -2928,14 +3532,14 @@ } }, "browserslist": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.6.0.tgz", - "integrity": "sha512-Jk0YFwXBuMOOol8n6FhgkDzn3mY9PYLYGk29zybF05SbRTsMgPqmTNeQQhOghCxq5oFqAXE3u4sYddr4C0uRhg==", + "version": "4.6.6", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.6.6.tgz", + "integrity": "sha512-D2Nk3W9JL9Fp/gIcWei8LrERCS+eXu9AM5cfXA8WEZ84lFks+ARnZ0q/R69m2SV3Wjma83QDDPxsNKXUwdIsyA==", "dev": true, "requires": { - "caniuse-lite": "^1.0.30000967", - "electron-to-chromium": "^1.3.133", - "node-releases": "^1.1.19" + "caniuse-lite": "^1.0.30000984", + "electron-to-chromium": "^1.3.191", + "node-releases": "^1.1.25" } }, "buffer": { @@ -2996,22 +3600,23 @@ "dev": true }, "cacache": { - "version": "11.3.2", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-11.3.2.tgz", - "integrity": "sha512-E0zP4EPGDOaT2chM08Als91eYnf8Z+eH1awwwVsngUmgppfM5jjJ8l3z5vO5p5w/I3LsiXawb1sW0VY65pQABg==", + "version": "12.0.2", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.2.tgz", + "integrity": "sha512-ifKgxH2CKhJEg6tNdAwziu6Q33EvuG26tYcda6PT3WKisZcYDXsnEdnRv67Po3yCzFfaSoMjGZzJyD2c3DT1dg==", "dev": true, "requires": { - "bluebird": "^3.5.3", + "bluebird": "^3.5.5", "chownr": "^1.1.1", "figgy-pudding": "^3.5.1", - "glob": "^7.1.3", + "glob": "^7.1.4", "graceful-fs": "^4.1.15", + "infer-owner": "^1.0.3", "lru-cache": "^5.1.1", "mississippi": "^3.0.0", "mkdirp": "^0.5.1", "move-concurrently": "^1.0.1", "promise-inflight": "^1.0.1", - "rimraf": "^2.6.2", + "rimraf": "^2.6.3", "ssri": "^6.0.1", "unique-filename": "^1.1.1", "y18n": "^4.0.0" @@ -3120,9 +3725,9 @@ } }, "caniuse-lite": { - "version": "1.0.30000971", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000971.tgz", - "integrity": "sha512-TQFYFhRS0O5rdsmSbF1Wn+16latXYsQJat66f7S7lizXW1PVpWJeZw9wqqVLIjuxDRz7s7xRUj13QCfd8hKn6g==", + "version": "1.0.30000989", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000989.tgz", + "integrity": "sha512-vrMcvSuMz16YY6GSVZ0dWDTJP8jqk3iFQ/Aq5iqblPwxSVVZI+zxDyTX0VPqtQsDnfdrBDcsmhgTEOh5R8Lbpw==", "dev": true }, "caseless": { @@ -3169,9 +3774,9 @@ } }, "chownr": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.1.tgz", - "integrity": "sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.2.tgz", + "integrity": "sha512-GkfeAQh+QNy3wquu9oIZr6SS5x7wGdSgNQvD10X3r+AZr1Oys22HW8kAmDMvNg2+Dm0TeGaEuO8gFwdBXxwO8A==", "dev": true }, "chrome-trace-event": { @@ -3319,15 +3924,14 @@ } }, "clone-deep": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-2.0.2.tgz", - "integrity": "sha512-SZegPTKjCgpQH63E+eN6mVEEPdQBOUzjyJm5Pora4lrwWRFS8I0QAxV/KD6vV/i0WuijHZWQC1fMsPEdxfdVCQ==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", "dev": true, "requires": { - "for-own": "^1.0.0", "is-plain-object": "^2.0.4", - "kind-of": "^6.0.0", - "shallow-clone": "^1.0.0" + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" } }, "code-point-at": { @@ -4565,36 +5169,28 @@ "dev": true }, "core-js": { - "version": "2.6.8", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.8.tgz", - "integrity": "sha512-RWlREFU74TEkdXzyl1bka66O3kYp8jeTXrvJZDzVVMH8AiHUSOFpL1yfhQJ+wHocAm1m+4971W1PPzfLuCv1vg==", - "dev": true + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.2.1.tgz", + "integrity": "sha512-Qa5XSVefSVPRxy2XfUC13WbvqkxhkwB3ve+pgCQveNgYzbM/UxZeu1dcOX/xr4UmfUd+muuvsaxilQzCyUurMw==" }, "core-js-compat": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.1.2.tgz", - "integrity": "sha512-X0Ch5f6itrHxhg5HSJucX6nNLNAGr+jq+biBh6nPGc3YAWz2a8p/ZIZY8cUkDzSRNG54omAuu3hoEF8qZbu/6Q==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.2.1.tgz", + "integrity": "sha512-MwPZle5CF9dEaMYdDeWm73ao/IflDH+FjeJCWEADcEgFSE9TLimFKwJsfmkwzI8eC0Aj0mgvMDjeQjrElkz4/A==", "dev": true, "requires": { - "browserslist": "^4.6.0", - "core-js-pure": "3.1.2", - "semver": "^6.0.0" + "browserslist": "^4.6.6", + "semver": "^6.3.0" }, "dependencies": { "semver": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.1.0.tgz", - "integrity": "sha512-kCqEOOHoBcFs/2Ccuk4Xarm/KiWRSLEX9CAZF8xkJ6ZPlIoTZ8V5f7J16vYLJqDbR7KrxTJpR2lqjIEm2Qx9cQ==", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", "dev": true } } }, - "core-js-pure": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.1.2.tgz", - "integrity": "sha512-5ckIdBF26B3ldK9PM177y2ZcATP2oweam9RskHSoqfZCrJ2As6wVg8zJ1zTriFsZf6clj/N1ThDFRGaomMsh9w==", - "dev": true - }, "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", @@ -4780,9 +5376,9 @@ "dev": true }, "date-format": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/date-format/-/date-format-2.0.0.tgz", - "integrity": "sha512-M6UqVvZVgFYqZL1SfHsRGIQSz3ZL+qgbsV5Lp1Vj61LZVYuEwcMXYay7DRDtYs2HQQBK5hQtQ0fD9aEJ89V0LA==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/date-format/-/date-format-2.1.0.tgz", + "integrity": "sha512-bYQuGLeFxhkxNOF3rcMtiZxvCBAquGzZm6oWA1oZ0g2THUzivaRhv8uOhdr19LmoobSOLoIAxeUK2RdbM8IFTA==", "dev": true }, "date-now": { @@ -5053,9 +5649,9 @@ "dev": true }, "electron-to-chromium": { - "version": "1.3.137", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.137.tgz", - "integrity": "sha512-kGi32g42a8vS/WnYE7ELJyejRT7hbr3UeOOu0WeuYuQ29gCpg9Lrf6RdcTQVXSt/v0bjCfnlb/EWOOsiKpTmkw==", + "version": "1.3.229", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.229.tgz", + "integrity": "sha512-N6pUbSuKFBeUifxBZp9hODS1N9jFobJYW47QT2VvZIr+G5AWnHK/iG3ON9RPRGH7lHDQ6KUDVhzpNkj4ZiznoA==", "dev": true }, "elegant-spinner": { @@ -5371,9 +5967,9 @@ } }, "eslint-scope": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-3.7.1.tgz", - "integrity": "sha1-PWPD7f2gLgbgGkUq2IyqzHzctug=", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz", + "integrity": "sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==", "dev": true, "requires": { "esrecurse": "^4.1.0", @@ -5381,10 +5977,13 @@ } }, "eslint-utils": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.3.1.tgz", - "integrity": "sha512-Z7YjnIldX+2XMcjr7ZkgEsOj/bREONV60qYeB/bjMAqqqZ4zxKyWX+BOUkdmRmA9riiIPVvo5x86m5elviOk0Q==", - "dev": true + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.2.tgz", + "integrity": "sha512-eAZS2sEUMlIeCjBeubdj45dmBHQwPHWyBcT1VSYB7o9x9WRRqKxyUoiXlRjyAwzN7YEzHJlYg0NmzDRWx6GP4Q==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^1.0.0" + } }, "eslint-visitor-keys": { "version": "1.0.0", @@ -5776,11 +6375,6 @@ "write": "1.0.3" } }, - "flatpickr": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/flatpickr/-/flatpickr-4.6.1.tgz", - "integrity": "sha512-3ULSxbXmcMIRzer/2jLNweoqHpwDvsjEawO2FUd9UFR8uPwLM+LruZcPDpuZStcEgbQKhuFOfXo4nYdGladSNw==" - }, "flatted": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.0.tgz", @@ -5804,12 +6398,12 @@ "dev": true }, "follow-redirects": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.7.0.tgz", - "integrity": "sha512-m/pZQy4Gj287eNy94nivy5wchN3Kp+Q5WgUPNy5lJSZ3sgkVKSYV/ZChMAQVIgx1SqfZ2zBZtPA2YlXIWxxJOQ==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.8.1.tgz", + "integrity": "sha512-micCIbldHioIegeKs41DoH0KS3AXfFzgS30qVkM6z/XOE/GJgvmsoc839NUqa1B9udYe9dQxgv7KFwng6+p/dw==", "dev": true, "requires": { - "debug": "^3.2.6" + "debug": "^3.0.0" }, "dependencies": { "debug": { @@ -5822,9 +6416,9 @@ } }, "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true } } @@ -5835,15 +6429,6 @@ "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", "dev": true }, - "for-own": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz", - "integrity": "sha1-xjMy9BXO3EsE2/5wz4NklMU8tEs=", - "dev": true, - "requires": { - "for-in": "^1.0.1" - } - }, "forever-agent": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", @@ -7341,9 +7926,9 @@ "dev": true }, "husky": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/husky/-/husky-2.4.1.tgz", - "integrity": "sha512-ZRwMWHr7QruR22dQ5l3rEGXQ7rAQYsJYqaeCd+NyOsIFczAtqaApZQP3P4HwLZjCtFbm3SUNYoKuoBXX3AYYfw==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/husky/-/husky-2.7.0.tgz", + "integrity": "sha512-LIi8zzT6PyFpcYKdvWRCn/8X+6SuG2TgYYMrM6ckEYhlp44UcEduVymZGIZNLiwOUjrEud+78w/AsAiqJA/kRg==", "dev": true, "requires": { "cosmiconfig": "^5.2.0", @@ -7529,6 +8114,12 @@ "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=", "dev": true }, + "infer-owner": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", + "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", + "dev": true + }, "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -8056,18 +8647,18 @@ } }, "karma": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/karma/-/karma-4.1.0.tgz", - "integrity": "sha512-xckiDqyNi512U4dXGOOSyLKPwek6X/vUizSy2f3geYevbLj+UIdvNwbn7IwfUIL2g1GXEPWt/87qFD1fBbl/Uw==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/karma/-/karma-4.3.0.tgz", + "integrity": "sha512-NSPViHOt+RW38oJklvYxQC4BSQsv737oQlr/r06pCM+slDOr4myuI1ivkRmp+3dVpJDfZt2DmaPJ2wkx+ZZuMQ==", "dev": true, "requires": { "bluebird": "^3.3.0", "body-parser": "^1.16.1", - "braces": "^2.3.2", - "chokidar": "^2.0.3", + "braces": "^3.0.2", + "chokidar": "^3.0.0", "colors": "^1.1.0", "connect": "^3.6.0", - "core-js": "^2.2.0", + "core-js": "^3.1.3", "di": "^0.0.1", "dom-serialize": "^2.2.0", "flatted": "^2.0.0", @@ -8075,7 +8666,7 @@ "graceful-fs": "^4.1.2", "http-proxy": "^1.13.0", "isbinaryfile": "^3.0.0", - "lodash": "^4.17.11", + "lodash": "^4.17.14", "log4js": "^4.0.0", "mime": "^2.3.1", "minimatch": "^3.0.2", @@ -8090,11 +8681,110 @@ "useragent": "2.3.0" }, "dependencies": { + "anymatch": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.0.3.tgz", + "integrity": "sha512-c6IvoeBECQlMVuYUjSwimnhmztImpErfxJzWZhIQinIvQWoGOnB0dLIgifbPHQt5heS6mNlaZG16f06H3C8t1g==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "binary-extensions": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.0.0.tgz", + "integrity": "sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==", + "dev": true + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "chokidar": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.0.2.tgz", + "integrity": "sha512-c4PR2egjNjI1um6bamCQ6bUNPDiyofNQruHvKgHQ4gDUP/ITSVSzNsiI5OWtHOsX323i5ha/kk4YmOZ1Ktg7KA==", + "dev": true, + "requires": { + "anymatch": "^3.0.1", + "braces": "^3.0.2", + "fsevents": "^2.0.6", + "glob-parent": "^5.0.0", + "is-binary-path": "^2.1.0", + "is-glob": "^4.0.1", + "normalize-path": "^3.0.0", + "readdirp": "^3.1.1" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "fsevents": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.0.7.tgz", + "integrity": "sha512-a7YT0SV3RB+DjYcppwVDLtn13UQnmg0SWZS7ezZD0UjnLwXmy8Zm21GMVGLaFGimIqcvyMQaOJBrop8MyOp1kQ==", + "dev": true, + "optional": true + }, + "glob-parent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.0.0.tgz", + "integrity": "sha512-Z2RwiujPRGluePM6j699ktJYxmPpJKCfpGA13jz2hmFZC7gKetzrWvg5KN3+OsIFmydGyZ1AVwERCq1w/ZZwRg==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "readdirp": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.1.2.tgz", + "integrity": "sha512-8rhl0xs2cxfVsqzreYCvs8EwBfn/DhVdqtoLmw19uI3SC5avYX9teCurlErfpPXGmYtMHReGaP2RsLnFvz/lnw==", + "dev": true, + "requires": { + "picomatch": "^2.0.4" + } + }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } } } }, @@ -8199,6 +8889,12 @@ "type-check": "~0.3.2" } }, + "lines-and-columns": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", + "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=", + "dev": true + }, "lint-staged": { "version": "8.2.1", "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-8.2.1.tgz", @@ -8457,12 +9153,6 @@ "integrity": "sha1-dWy1FQyjum8RCFp4hJZF8Yj4Xzc=", "dev": true }, - "lodash.tail": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.tail/-/lodash.tail-4.1.1.tgz", - "integrity": "sha1-0jM6NtnncXyK0vfKyv7HwytERmQ=", - "dev": true - }, "lodash.template": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-4.4.0.tgz", @@ -8503,16 +9193,16 @@ } }, "log4js": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/log4js/-/log4js-4.3.0.tgz", - "integrity": "sha512-ivqZBkBvWLJ8rXfhb4E0979olSwnYBPSZy/5WhLNXwntqRgUhxHnqcXGmVw0t+XmLNTr3GAWEzjqHMzu4KM7rA==", + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/log4js/-/log4js-4.5.1.tgz", + "integrity": "sha512-EEEgFcE9bLgaYUKuozyFfytQM2wDHtXn4tAN41pkaxpNjAykv11GVdeI4tHtmPWW4Xrgh9R/2d7XYghDVjbKKw==", "dev": true, "requires": { "date-format": "^2.0.0", "debug": "^4.1.1", "flatted": "^2.0.0", - "rfdc": "^1.1.2", - "streamroller": "^1.0.5" + "rfdc": "^1.1.4", + "streamroller": "^1.0.6" }, "dependencies": { "debug": { @@ -8525,9 +9215,9 @@ } }, "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true } } @@ -8838,24 +9528,6 @@ } } }, - "mixin-object": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/mixin-object/-/mixin-object-2.0.1.tgz", - "integrity": "sha1-T7lJRB2rGCVA8f4DW6YOGUel5X4=", - "dev": true, - "requires": { - "for-in": "^0.1.3", - "is-extendable": "^0.1.1" - }, - "dependencies": { - "for-in": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/for-in/-/for-in-0.1.8.tgz", - "integrity": "sha1-2Hc5COMSVhCZUrH9ubP6hn0ndeE=", - "dev": true - } - } - }, "mkdirp": { "version": "0.5.1", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", @@ -8871,6 +9543,11 @@ "integrity": "sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw==", "dev": true }, + "moment": { + "version": "2.24.0", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz", + "integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==" + }, "move-concurrently": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", @@ -9020,9 +9697,9 @@ } }, "node-releases": { - "version": "1.1.21", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.21.tgz", - "integrity": "sha512-TwnURTCjc8a+ElJUjmDqU6+12jhli1Q61xOQmdZ7ECZVBZuQpN/1UnembiIHDM1wCcfLvh5wrWXUF5H6ufX64Q==", + "version": "1.1.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.27.tgz", + "integrity": "sha512-9iXUqHKSGo6ph/tdXVbHFbhRVQln4ZDTIBJCzsa90HimnBYc5jw8RWYt4wBYFHehGyC3koIz5O4mb2fHrbPOuA==", "dev": true, "requires": { "semver": "^5.3.0" @@ -9130,9 +9807,9 @@ "dev": true }, "npm": { - "version": "6.10.1", - "resolved": "https://registry.npmjs.org/npm/-/npm-6.10.1.tgz", - "integrity": "sha512-ejR83c5aPTip5hPhziypqkJu06vb5tDIugCXx1c5+04RbMjtZeMA6BfsuGnV9EBdEwzKoaHkQ9sJWQAq+LjHYw==", + "version": "6.11.2", + "resolved": "https://registry.npmjs.org/npm/-/npm-6.11.2.tgz", + "integrity": "sha512-OAkXqI4bm5MUvqVvqe6rxCXmJqrln8VDlkdftpOoayHKazz8IOCJAiCuKmz0TchL224EAKeG86umuD6RYNpuEg==", "requires": { "JSONStream": "^1.3.5", "abbrev": "~1.1.1", @@ -9140,16 +9817,16 @@ "ansistyles": "~0.1.3", "aproba": "^2.0.0", "archy": "~1.0.0", - "bin-links": "^1.1.2", + "bin-links": "^1.1.3", "bluebird": "^3.5.5", "byte-size": "^5.0.1", - "cacache": "^11.3.3", + "cacache": "^12.0.3", "call-limit": "^1.1.1", "chownr": "^1.1.2", "ci-info": "^2.0.0", "cli-columns": "^3.1.2", "cli-table3": "^0.5.1", - "cmd-shim": "~2.0.2", + "cmd-shim": "^3.0.3", "columnify": "~1.5.4", "config-chain": "^1.1.12", "debuglog": "*", @@ -9161,13 +9838,14 @@ "find-npm-prefix": "^1.0.2", "fs-vacuum": "~1.2.10", "fs-write-stream-atomic": "~1.0.10", - "gentle-fs": "^2.0.1", + "gentle-fs": "^2.2.1", "glob": "^7.1.4", - "graceful-fs": "^4.2.0", + "graceful-fs": "^4.2.2", "has-unicode": "~2.0.1", - "hosted-git-info": "^2.7.1", + "hosted-git-info": "^2.8.2", "iferr": "^1.0.2", "imurmurhash": "*", + "infer-owner": "^1.0.4", "inflight": "~1.0.6", "inherits": "^2.0.4", "ini": "^1.3.5", @@ -9175,13 +9853,13 @@ "is-cidr": "^3.0.0", "json-parse-better-errors": "^1.0.2", "lazy-property": "~1.0.0", - "libcipm": "^4.0.0", - "libnpm": "^3.0.0", - "libnpmaccess": "*", - "libnpmhook": "^5.0.2", - "libnpmorg": "*", - "libnpmsearch": "^2.0.1", - "libnpmteam": "*", + "libcipm": "^4.0.3", + "libnpm": "^3.0.1", + "libnpmaccess": "^3.0.2", + "libnpmhook": "^5.0.3", + "libnpmorg": "^1.0.1", + "libnpmsearch": "^2.0.2", + "libnpmteam": "^1.0.2", "libnpx": "^10.2.0", "lock-verify": "^2.1.0", "lockfile": "^1.0.4", @@ -9201,33 +9879,33 @@ "mississippi": "^3.0.0", "mkdirp": "~0.5.1", "move-concurrently": "^1.0.1", - "node-gyp": "^5.0.2", + "node-gyp": "^5.0.3", "nopt": "~4.0.1", "normalize-package-data": "^2.5.0", "npm-audit-report": "^1.3.2", "npm-cache-filename": "~1.0.2", "npm-install-checks": "~3.0.0", - "npm-lifecycle": "^3.0.0", - "npm-package-arg": "^6.1.0", + "npm-lifecycle": "^3.1.3", + "npm-package-arg": "^6.1.1", "npm-packlist": "^1.4.4", - "npm-pick-manifest": "^2.2.3", - "npm-profile": "*", - "npm-registry-fetch": "^3.9.1", + "npm-pick-manifest": "^3.0.0", + "npm-profile": "^4.0.2", + "npm-registry-fetch": "^4.0.0", "npm-user-validate": "~1.0.0", "npmlog": "~4.1.2", "once": "~1.4.0", "opener": "^1.5.1", "osenv": "^0.1.5", - "pacote": "^9.5.1", + "pacote": "^9.5.8", "path-is-inside": "~1.0.2", "promise-inflight": "~1.0.1", "qrcode-terminal": "^0.12.0", - "query-string": "^6.8.1", + "query-string": "^6.8.2", "qw": "~1.0.1", "read": "~1.0.7", - "read-cmd-shim": "~1.0.1", + "read-cmd-shim": "^1.0.3", "read-installed": "~4.0.3", - "read-package-json": "^2.0.13", + "read-package-json": "^2.1.0", "read-package-tree": "^5.3.1", "readable-stream": "^3.4.0", "readdir-scoped-modules": "^1.1.0", @@ -9235,7 +9913,7 @@ "retry": "^0.12.0", "rimraf": "^2.6.3", "safe-buffer": "^5.1.2", - "semver": "^5.7.0", + "semver": "^5.7.1", "sha": "^3.0.0", "slide": "~1.1.6", "sorted-object": "~2.0.1", @@ -9271,14 +9949,14 @@ "bundled": true }, "agent-base": { - "version": "4.2.1", + "version": "4.3.0", "bundled": true, "requires": { "es6-promisify": "^5.0.0" } }, "agentkeepalive": { - "version": "3.4.1", + "version": "3.5.2", "bundled": true, "requires": { "humanize-ms": "^1.2.1" @@ -9398,13 +10076,13 @@ } }, "bin-links": { - "version": "1.1.2", + "version": "1.1.3", "bundled": true, "requires": { - "bluebird": "^3.5.0", - "cmd-shim": "^2.0.2", - "gentle-fs": "^2.0.0", - "graceful-fs": "^4.1.11", + "bluebird": "^3.5.3", + "cmd-shim": "^3.0.0", + "gentle-fs": "^2.0.1", + "graceful-fs": "^4.1.15", "write-file-atomic": "^2.3.0" } }, @@ -9450,7 +10128,7 @@ "bundled": true }, "cacache": { - "version": "11.3.3", + "version": "12.0.3", "bundled": true, "requires": { "bluebird": "^3.5.5", @@ -9458,6 +10136,7 @@ "figgy-pudding": "^3.5.1", "glob": "^7.1.4", "graceful-fs": "^4.1.15", + "infer-owner": "^1.0.3", "lru-cache": "^5.1.1", "mississippi": "^3.0.0", "mkdirp": "^0.5.1", @@ -9467,20 +10146,6 @@ "ssri": "^6.0.1", "unique-filename": "^1.1.1", "y18n": "^4.0.0" - }, - "dependencies": { - "glob": { - "version": "7.1.4", - "bundled": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - } } }, "call-limit": { @@ -9571,7 +10236,7 @@ "bundled": true }, "cmd-shim": { - "version": "2.0.2", + "version": "3.0.3", "bundled": true, "requires": { "graceful-fs": "^4.1.2", @@ -9925,7 +10590,7 @@ } }, "es6-promise": { - "version": "4.2.6", + "version": "4.2.8", "bundled": true }, "es6-promisify": { @@ -10156,13 +10821,15 @@ "bundled": true }, "gentle-fs": { - "version": "2.0.1", + "version": "2.2.1", "bundled": true, "requires": { "aproba": "^1.1.2", + "chownr": "^1.1.2", "fs-vacuum": "^1.2.10", "graceful-fs": "^4.1.11", "iferr": "^0.1.5", + "infer-owner": "^1.0.4", "mkdirp": "^0.5.1", "path-is-inside": "^1.0.2", "read-cmd-shim": "^1.0.1", @@ -10240,7 +10907,7 @@ } }, "graceful-fs": { - "version": "4.2.0", + "version": "4.2.2", "bundled": true }, "har-schema": { @@ -10275,8 +10942,11 @@ "bundled": true }, "hosted-git-info": { - "version": "2.7.1", - "bundled": true + "version": "2.8.2", + "bundled": true, + "requires": { + "lru-cache": "^5.1.1" + } }, "http-cache-semantics": { "version": "3.8.1", @@ -10300,10 +10970,10 @@ } }, "https-proxy-agent": { - "version": "2.2.1", + "version": "2.2.2", "bundled": true, "requires": { - "agent-base": "^4.1.0", + "agent-base": "^4.3.0", "debug": "^3.1.0" } }, @@ -10340,6 +11010,10 @@ "version": "0.1.4", "bundled": true }, + "infer-owner": { + "version": "1.0.4", + "bundled": true + }, "inflight": { "version": "1.0.6", "bundled": true, @@ -10536,7 +11210,7 @@ } }, "libcipm": { - "version": "4.0.0", + "version": "4.0.3", "bundled": true, "requires": { "bin-links": "^1.1.2", @@ -10557,45 +11231,39 @@ } }, "libnpm": { - "version": "3.0.0", + "version": "3.0.1", "bundled": true, "requires": { "bin-links": "^1.1.2", "bluebird": "^3.5.3", "find-npm-prefix": "^1.0.2", - "libnpmaccess": "^3.0.1", + "libnpmaccess": "^3.0.2", "libnpmconfig": "^1.2.1", - "libnpmhook": "^5.0.2", - "libnpmorg": "^1.0.0", - "libnpmpublish": "^1.1.0", - "libnpmsearch": "^2.0.0", - "libnpmteam": "^1.0.1", + "libnpmhook": "^5.0.3", + "libnpmorg": "^1.0.1", + "libnpmpublish": "^1.1.2", + "libnpmsearch": "^2.0.2", + "libnpmteam": "^1.0.2", "lock-verify": "^2.0.2", "npm-lifecycle": "^3.0.0", "npm-logical-tree": "^1.2.1", "npm-package-arg": "^6.1.0", - "npm-profile": "^4.0.1", - "npm-registry-fetch": "^3.8.0", + "npm-profile": "^4.0.2", + "npm-registry-fetch": "^4.0.0", "npmlog": "^4.1.2", - "pacote": "^9.2.3", + "pacote": "^9.5.3", "read-package-json": "^2.0.13", "stringify-package": "^1.0.0" } }, "libnpmaccess": { - "version": "3.0.1", + "version": "3.0.2", "bundled": true, "requires": { "aproba": "^2.0.0", "get-stream": "^4.0.0", "npm-package-arg": "^6.1.0", - "npm-registry-fetch": "^3.8.0" - }, - "dependencies": { - "aproba": { - "version": "2.0.0", - "bundled": true - } + "npm-registry-fetch": "^4.0.0" } }, "libnpmconfig": { @@ -10643,33 +11311,27 @@ } }, "libnpmhook": { - "version": "5.0.2", + "version": "5.0.3", "bundled": true, "requires": { "aproba": "^2.0.0", "figgy-pudding": "^3.4.1", "get-stream": "^4.0.0", - "npm-registry-fetch": "^3.8.0" + "npm-registry-fetch": "^4.0.0" } }, "libnpmorg": { - "version": "1.0.0", + "version": "1.0.1", "bundled": true, "requires": { "aproba": "^2.0.0", "figgy-pudding": "^3.4.1", "get-stream": "^4.0.0", - "npm-registry-fetch": "^3.8.0" - }, - "dependencies": { - "aproba": { - "version": "2.0.0", - "bundled": true - } + "npm-registry-fetch": "^4.0.0" } }, "libnpmpublish": { - "version": "1.1.1", + "version": "1.1.2", "bundled": true, "requires": { "aproba": "^2.0.0", @@ -10678,34 +11340,28 @@ "lodash.clonedeep": "^4.5.0", "normalize-package-data": "^2.4.0", "npm-package-arg": "^6.1.0", - "npm-registry-fetch": "^3.8.0", + "npm-registry-fetch": "^4.0.0", "semver": "^5.5.1", "ssri": "^6.0.1" } }, "libnpmsearch": { - "version": "2.0.1", + "version": "2.0.2", "bundled": true, "requires": { "figgy-pudding": "^3.5.1", "get-stream": "^4.0.0", - "npm-registry-fetch": "^3.8.0" + "npm-registry-fetch": "^4.0.0" } }, "libnpmteam": { - "version": "1.0.1", + "version": "1.0.2", "bundled": true, "requires": { "aproba": "^2.0.0", "figgy-pudding": "^3.4.1", "get-stream": "^4.0.0", - "npm-registry-fetch": "^3.8.0" - }, - "dependencies": { - "aproba": { - "version": "2.0.0", - "bundled": true - } + "npm-registry-fetch": "^4.0.0" } }, "libnpx": { @@ -10823,11 +11479,11 @@ } }, "make-fetch-happen": { - "version": "4.0.2", + "version": "5.0.0", "bundled": true, "requires": { "agentkeepalive": "^3.4.1", - "cacache": "^11.3.3", + "cacache": "^12.0.0", "http-cache-semantics": "^3.8.1", "http-proxy-agent": "^2.1.0", "https-proxy-agent": "^2.2.1", @@ -10956,7 +11612,7 @@ } }, "node-gyp": { - "version": "5.0.2", + "version": "5.0.3", "bundled": true, "requires": { "env-paths": "^1.0.0", @@ -11036,7 +11692,7 @@ } }, "npm-lifecycle": { - "version": "3.0.0", + "version": "3.1.3", "bundled": true, "requires": { "byline": "^5.0.0", @@ -11054,12 +11710,12 @@ "bundled": true }, "npm-package-arg": { - "version": "6.1.0", + "version": "6.1.1", "bundled": true, "requires": { - "hosted-git-info": "^2.6.0", + "hosted-git-info": "^2.7.1", "osenv": "^0.1.5", - "semver": "^5.5.0", + "semver": "^5.6.0", "validate-npm-package-name": "^3.0.0" } }, @@ -11072,7 +11728,7 @@ } }, "npm-pick-manifest": { - "version": "2.2.3", + "version": "3.0.0", "bundled": true, "requires": { "figgy-pudding": "^3.5.1", @@ -11081,43 +11737,24 @@ } }, "npm-profile": { - "version": "4.0.1", + "version": "4.0.2", "bundled": true, "requires": { "aproba": "^1.1.2 || 2", "figgy-pudding": "^3.4.1", - "npm-registry-fetch": "^3.8.0" + "npm-registry-fetch": "^4.0.0" } }, "npm-registry-fetch": { - "version": "3.9.1", + "version": "4.0.0", "bundled": true, "requires": { "JSONStream": "^1.3.4", "bluebird": "^3.5.1", "figgy-pudding": "^3.4.1", "lru-cache": "^5.1.1", - "make-fetch-happen": "^4.0.2", + "make-fetch-happen": "^5.0.0", "npm-package-arg": "^6.1.0" - }, - "dependencies": { - "make-fetch-happen": { - "version": "4.0.2", - "bundled": true, - "requires": { - "agentkeepalive": "^3.4.1", - "cacache": "^11.3.3", - "http-cache-semantics": "^3.8.1", - "http-proxy-agent": "^2.1.0", - "https-proxy-agent": "^2.2.1", - "lru-cache": "^5.1.1", - "mississippi": "^3.0.0", - "node-fetch-npm": "^2.0.2", - "promise-retry": "^1.1.1", - "socks-proxy-agent": "^4.0.0", - "ssri": "^6.0.0" - } - } } }, "npm-run-path": { @@ -11234,16 +11871,18 @@ } }, "pacote": { - "version": "9.5.1", + "version": "9.5.8", "bundled": true, "requires": { "bluebird": "^3.5.3", - "cacache": "^11.3.2", + "cacache": "^12.0.2", + "chownr": "^1.1.2", "figgy-pudding": "^3.5.1", "get-stream": "^4.1.0", "glob": "^7.1.3", + "infer-owner": "^1.0.4", "lru-cache": "^5.1.1", - "make-fetch-happen": "^4.0.1", + "make-fetch-happen": "^5.0.0", "minimatch": "^3.0.4", "minipass": "^2.3.5", "mississippi": "^3.0.0", @@ -11251,8 +11890,8 @@ "normalize-package-data": "^2.4.0", "npm-package-arg": "^6.1.0", "npm-packlist": "^1.1.12", - "npm-pick-manifest": "^2.2.3", - "npm-registry-fetch": "^3.8.0", + "npm-pick-manifest": "^3.0.0", + "npm-registry-fetch": "^4.0.0", "osenv": "^0.1.5", "promise-inflight": "^1.0.1", "promise-retry": "^1.1.1", @@ -11261,7 +11900,7 @@ "safe-buffer": "^5.1.2", "semver": "^5.6.0", "ssri": "^6.0.1", - "tar": "^4.4.8", + "tar": "^4.4.10", "unique-filename": "^1.1.1", "which": "^1.3.1" }, @@ -11431,7 +12070,7 @@ "bundled": true }, "query-string": { - "version": "6.8.1", + "version": "6.8.2", "bundled": true, "requires": { "decode-uri-component": "^0.2.0", @@ -11467,7 +12106,7 @@ } }, "read-cmd-shim": { - "version": "1.0.1", + "version": "1.0.3", "bundled": true, "requires": { "graceful-fs": "^4.1.2" @@ -11487,7 +12126,7 @@ } }, "read-package-json": { - "version": "2.0.13", + "version": "2.1.0", "bundled": true, "requires": { "glob": "^7.1.1", @@ -11611,7 +12250,7 @@ "bundled": true }, "semver": { - "version": "5.7.0", + "version": "5.7.1", "bundled": true }, "semver-diff": { @@ -11656,23 +12295,32 @@ "bundled": true }, "smart-buffer": { - "version": "4.0.1", + "version": "4.0.2", "bundled": true }, "socks": { - "version": "2.2.0", + "version": "2.3.2", "bundled": true, "requires": { "ip": "^1.1.5", - "smart-buffer": "^4.0.1" + "smart-buffer": "4.0.2" } }, "socks-proxy-agent": { - "version": "4.0.1", + "version": "4.0.2", "bundled": true, "requires": { - "agent-base": "~4.2.0", - "socks": "~2.2.0" + "agent-base": "~4.2.1", + "socks": "~2.3.2" + }, + "dependencies": { + "agent-base": { + "version": "4.2.1", + "bundled": true, + "requires": { + "es6-promisify": "^5.0.0" + } + } } }, "sorted-object": { @@ -12751,6 +13399,12 @@ "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", "dev": true }, + "picomatch": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.0.7.tgz", + "integrity": "sha512-oLHIdio3tZ0qH76NybpeneBhYVj0QFTfXEFTc/B3zKQspYfYYkWYgFsmzo+4kvId/bQRcNkVeguI3y+CD22BtA==", + "dev": true + }, "pidtree": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.3.0.tgz", @@ -13110,15 +13764,29 @@ } }, "read-pkg": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.1.1.tgz", - "integrity": "sha512-dFcTLQi6BZ+aFUaICg7er+/usEoqFdQxiEBsEMNGoipenihtxxtdrQuBXvyANCEI8VuUIVYFgeHGx9sLLvim4w==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", + "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", "dev": true, "requires": { "@types/normalize-package-data": "^2.4.0", "normalize-package-data": "^2.5.0", - "parse-json": "^4.0.0", - "type-fest": "^0.4.1" + "parse-json": "^5.0.0", + "type-fest": "^0.6.0" + }, + "dependencies": { + "parse-json": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.0.0.tgz", + "integrity": "sha512-OOY5b7PAEFV0E2Fir1KOkxchnZNCdowAJgQ5NuxjpBKTRP3pQhwkrkxqQjeoKJ+fO7bCpmIZaogI4eZGDMEGOw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1", + "lines-and-columns": "^1.1.6" + } + } } }, "read-pkg-up": { @@ -13232,9 +13900,9 @@ "dev": true }, "regenerator-transform": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.14.0.tgz", - "integrity": "sha512-rtOelq4Cawlbmq9xuMR5gdFmv7ku/sFoB7sRiywx7aq53bc52b4j6zvH7Te1Vt/X2YveDKnCGUbioieU7FEL3w==", + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.14.1.tgz", + "integrity": "sha512-flVuee02C3FKRISbxhXl9mGzdbWUVHubl1SMaknjxkFB1/iqpJhArQUvRxOOPEc/9tAiX0BaQ28FJH10E4isSQ==", "dev": true, "requires": { "private": "^0.1.6" @@ -13251,9 +13919,9 @@ } }, "regexp-tree": { - "version": "0.1.10", - "resolved": "https://registry.npmjs.org/regexp-tree/-/regexp-tree-0.1.10.tgz", - "integrity": "sha512-K1qVSbcedffwuIslMwpe6vGlj+ZXRnGkvjAtFHfDZZZuEdA/h0dxljAPu9vhUo6Rrx2U2AwJ+nSQ6hK+lrP5MQ==", + "version": "0.1.11", + "resolved": "https://registry.npmjs.org/regexp-tree/-/regexp-tree-0.1.11.tgz", + "integrity": "sha512-7/l/DgapVVDzZobwMCCgMlqiqyLFJ0cduo/j+3BcDJIB+yJdsYCfKuI3l/04NV+H/rfNRdPIDbXNZHM9XvQatg==", "dev": true }, "regexpp": { @@ -13263,13 +13931,13 @@ "dev": true }, "regexpu-core": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.5.4.tgz", - "integrity": "sha512-BtizvGtFQKGPUcTy56o3nk1bGRp4SZOTYrDtGNlqCQufptV5IkkLN6Emw+yunAJjzf+C9FQFtvq7IoA3+oMYHQ==", + "version": "4.5.5", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.5.5.tgz", + "integrity": "sha512-FpI67+ky9J+cDizQUJlIlNZFKual/lUkFr1AG6zOCpwZ9cLrg8UUVakyUQJD7fCDIe9Z2nwTQJNPyonatNmDFQ==", "dev": true, "requires": { "regenerate": "^1.4.0", - "regenerate-unicode-properties": "^8.0.2", + "regenerate-unicode-properties": "^8.1.0", "regjsgen": "^0.5.0", "regjsparser": "^0.6.0", "unicode-match-property-ecmascript": "^1.0.4", @@ -13564,23 +14232,22 @@ } }, "sass-loader": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-7.1.0.tgz", - "integrity": "sha512-+G+BKGglmZM2GUSfT9TLuEp6tzehHPjAMoRRItOojWIqIGPloVCMhNIQuG639eJ+y033PaGTSjLaTHts8Kw79w==", + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-7.3.1.tgz", + "integrity": "sha512-tuU7+zm0pTCynKYHpdqaPpe+MMTQ76I9TPZ7i4/5dZsigE350shQWe5EZNl5dBidM49TPET75tNqRbcsUZWeNA==", "dev": true, "requires": { - "clone-deep": "^2.0.1", + "clone-deep": "^4.0.1", "loader-utils": "^1.0.1", - "lodash.tail": "^4.1.1", "neo-async": "^2.5.0", - "pify": "^3.0.0", - "semver": "^5.5.0" + "pify": "^4.0.1", + "semver": "^6.3.0" }, "dependencies": { - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", "dev": true } } @@ -13687,22 +14354,12 @@ } }, "shallow-clone": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-1.0.0.tgz", - "integrity": "sha512-oeXreoKR/SyNJtRJMAKPDSvd28OqEwG4eR/xc856cRGBII7gX9lvAqDxusPm0846z/w/hWYjI1NpKwJ00NHzRA==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", "dev": true, "requires": { - "is-extendable": "^0.1.1", - "kind-of": "^5.0.0", - "mixin-object": "^2.0.1" - }, - "dependencies": { - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true - } + "kind-of": "^6.0.2" } }, "shebang-command": { @@ -14442,16 +15099,16 @@ "dev": true }, "streamroller": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-1.0.5.tgz", - "integrity": "sha512-iGVaMcyF5PcUY0cPbW3xFQUXnr9O4RZXNBBjhuLZgrjLO4XCLLGfx4T2sGqygSeylUjwgWRsnNbT9aV0Zb8AYw==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-1.0.6.tgz", + "integrity": "sha512-3QC47Mhv3/aZNFpDDVO44qQb9gwB9QggMEE0sQmkTAwBVYdBRWISdsywlkfm5II1Q5y/pmrHflti/IgmIzdDBg==", "dev": true, "requires": { "async": "^2.6.2", "date-format": "^2.0.0", "debug": "^3.2.6", "fs-extra": "^7.0.1", - "lodash": "^4.17.11" + "lodash": "^4.17.14" }, "dependencies": { "debug": { @@ -14464,9 +15121,9 @@ } }, "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true } } @@ -14659,6 +15316,10 @@ } } }, + "tail.datetime": { + "version": "git+https://github.com/uni2work/tail.DateTime.git#d3256920fcedbf32c84b8065e77628097c7f2410", + "from": "git+https://github.com/uni2work/tail.DateTime.git#master" + }, "tapable": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", @@ -14677,14 +15338,14 @@ } }, "terser": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/terser/-/terser-4.0.2.tgz", - "integrity": "sha512-IWLuJqTvx97KP3uTYkFVn93cXO+EtlzJu8TdJylq+H0VBDlPMIfQA9MBS5Vc5t3xTEUG1q0hIfHMpAP2R+gWTw==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-4.2.1.tgz", + "integrity": "sha512-cGbc5utAcX4a9+2GGVX4DsenG6v0x3glnDi5hx8816X1McEAwPlPgRtXPJzSBsbpILxZ8MQMT0KvArLuE0HP5A==", "dev": true, "requires": { - "commander": "^2.19.0", + "commander": "^2.20.0", "source-map": "~0.6.1", - "source-map-support": "~0.5.10" + "source-map-support": "~0.5.12" }, "dependencies": { "source-map": { @@ -14694,9 +15355,9 @@ "dev": true }, "source-map-support": { - "version": "0.5.12", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.12.tgz", - "integrity": "sha512-4h2Pbvyy15EE02G+JOZpUCmqWJuqrs+sEkzewTm++BPi7Hvn/HwcqLAcNxYAyI0x13CpPPn+kMjl+hplXMHITQ==", + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", "dev": true, "requires": { "buffer-from": "^1.0.0", @@ -14706,20 +15367,19 @@ } }, "terser-webpack-plugin": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.3.0.tgz", - "integrity": "sha512-W2YWmxPjjkUcOWa4pBEv4OP4er1aeQJlSo2UhtCFQCuRXEHjOFscO8VyWHj9JLlA0RzQb8Y2/Ta78XZvT54uGg==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.1.tgz", + "integrity": "sha512-ZXmmfiwtCLfz8WKZyYUuuHf3dMYEjg8NrjHMb0JqHVHVOSkzp3cW2/XG1fP3tRhqEqSzMwzzRQGtAPbs4Cncxg==", "dev": true, "requires": { - "cacache": "^11.3.2", - "find-cache-dir": "^2.0.0", + "cacache": "^12.0.2", + "find-cache-dir": "^2.1.0", "is-wsl": "^1.1.0", - "loader-utils": "^1.2.3", "schema-utils": "^1.0.0", "serialize-javascript": "^1.7.0", "source-map": "^0.6.1", - "terser": "^4.0.0", - "webpack-sources": "^1.3.0", + "terser": "^4.1.2", + "webpack-sources": "^1.4.0", "worker-farm": "^1.7.0" }, "dependencies": { @@ -14728,6 +15388,16 @@ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true + }, + "webpack-sources": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz", + "integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==", + "dev": true, + "requires": { + "source-list-map": "^2.0.0", + "source-map": "~0.6.1" + } } } }, @@ -14760,9 +15430,9 @@ } }, "timers-browserify": { - "version": "2.0.10", - "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.10.tgz", - "integrity": "sha512-YvC1SV1XdOUaL6gx5CoGroT3Gu49pK9+TZ38ErPldOWW4j49GI1HKs9DV+KGq/w6y+LZ72W1c8cKz2vzY+qpzg==", + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.11.tgz", + "integrity": "sha512-60aV6sgJ5YEbzUdn9c8kYGIqOubPoUdqQCul3SBAsRCZ40s6Y5cMcrW4dt3/k/EsbLVJNl9n6Vz3fTc+k2GeKQ==", "dev": true, "requires": { "setimmediate": "^1.0.4" @@ -14931,9 +15601,9 @@ } }, "type-fest": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.4.1.tgz", - "integrity": "sha512-IwzA/LSfD2vC1/YDYMv/zHP4rDF1usCwllsDpbolT3D4fUepIO7f9K70jjmUewU/LmGUKJcwcVtDCpnKk4BPMw==", + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", + "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", "dev": true }, "type-is": { @@ -14957,6 +15627,7 @@ "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.5.15.tgz", "integrity": "sha512-fe7aYFotptIddkwcm6YuA0HmknBZ52ZzOsUxZEdhhkSsz7RfjHDX2QDxwKTiv4JQ5t5NhfmpgAK+J7LiDhKSqg==", "dev": true, + "optional": true, "requires": { "commander": "~2.20.0", "source-map": "~0.6.1" @@ -14966,24 +15637,25 @@ "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true + "dev": true, + "optional": true } } }, "uglifyjs-webpack-plugin": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/uglifyjs-webpack-plugin/-/uglifyjs-webpack-plugin-2.1.3.tgz", - "integrity": "sha512-/lRkCaFbI6pT3CxsQHDhBcqB6tocOnqba0vJqJ2DzSWFLRgOIiip8q0nVFydyXk+n8UtF7ZuS6hvWopcYH5FuA==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/uglifyjs-webpack-plugin/-/uglifyjs-webpack-plugin-2.2.0.tgz", + "integrity": "sha512-mHSkufBmBuJ+KHQhv5H0MXijtsoA1lynJt1lXOaotja8/I0pR4L9oGaPIZw+bQBOFittXZg9OC1sXSGO9D9ZYg==", "dev": true, "requires": { - "cacache": "^11.3.2", + "cacache": "^12.0.2", "find-cache-dir": "^2.1.0", "is-wsl": "^1.1.0", "schema-utils": "^1.0.0", "serialize-javascript": "^1.7.0", "source-map": "^0.6.1", - "uglify-js": "^3.5.12", - "webpack-sources": "^1.3.0", + "uglify-js": "^3.6.0", + "webpack-sources": "^1.4.0", "worker-farm": "^1.7.0" }, "dependencies": { @@ -14992,6 +15664,26 @@ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true + }, + "uglify-js": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.6.0.tgz", + "integrity": "sha512-W+jrUHJr3DXKhrsS7NUVxn3zqMOFn0hL/Ei6v0anCIMoKC93TjcflTagwIHLW7SfMFfiQuktQyFVCFHGUE0+yg==", + "dev": true, + "requires": { + "commander": "~2.20.0", + "source-map": "~0.6.1" + } + }, + "webpack-sources": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz", + "integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==", + "dev": true, + "requires": { + "source-list-map": "^2.0.0", + "source-map": "~0.6.1" + } } } }, @@ -15057,9 +15749,9 @@ } }, "unique-slug": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.1.tgz", - "integrity": "sha512-n9cU6+gITaVu7VGj1Z8feKMmfAjEAQGhwD9fE3zvpRRa0wEIx8ODYkVGfSc94M2OX00tUFV8wH3zYbm1I8mxFg==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz", + "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==", "dev": true, "requires": { "imurmurhash": "^0.1.4" @@ -15256,53 +15948,82 @@ } }, "webpack": { - "version": "4.35.2", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.35.2.tgz", - "integrity": "sha512-TZAmorNymV4q66gAM/h90cEjG+N3627Q2MnkSgKlX/z3DlNVKUtqy57lz1WmZU2+FUZwzM+qm7cGaO95PyrX5A==", + "version": "4.39.3", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.39.3.tgz", + "integrity": "sha512-BXSI9M211JyCVc3JxHWDpze85CvjC842EvpRsVTc/d15YJGlox7GIDd38kJgWrb3ZluyvIjgenbLDMBQPDcxYQ==", "dev": true, "requires": { "@webassemblyjs/ast": "1.8.5", "@webassemblyjs/helper-module-context": "1.8.5", "@webassemblyjs/wasm-edit": "1.8.5", "@webassemblyjs/wasm-parser": "1.8.5", - "acorn": "^6.0.5", - "acorn-dynamic-import": "^4.0.0", - "ajv": "^6.1.0", - "ajv-keywords": "^3.1.0", - "chrome-trace-event": "^1.0.0", + "acorn": "^6.2.1", + "ajv": "^6.10.2", + "ajv-keywords": "^3.4.1", + "chrome-trace-event": "^1.0.2", "enhanced-resolve": "^4.1.0", - "eslint-scope": "^4.0.0", + "eslint-scope": "^4.0.3", "json-parse-better-errors": "^1.0.2", - "loader-runner": "^2.3.0", - "loader-utils": "^1.1.0", - "memory-fs": "~0.4.1", - "micromatch": "^3.1.8", - "mkdirp": "~0.5.0", - "neo-async": "^2.5.0", - "node-libs-browser": "^2.0.0", + "loader-runner": "^2.4.0", + "loader-utils": "^1.2.3", + "memory-fs": "^0.4.1", + "micromatch": "^3.1.10", + "mkdirp": "^0.5.1", + "neo-async": "^2.6.1", + "node-libs-browser": "^2.2.1", "schema-utils": "^1.0.0", - "tapable": "^1.1.0", - "terser-webpack-plugin": "^1.1.0", - "watchpack": "^1.5.0", - "webpack-sources": "^1.3.0" + "tapable": "^1.1.3", + "terser-webpack-plugin": "^1.4.1", + "watchpack": "^1.6.0", + "webpack-sources": "^1.4.1" }, "dependencies": { - "eslint-scope": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz", - "integrity": "sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==", + "acorn": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.3.0.tgz", + "integrity": "sha512-/czfa8BwS88b9gWQVhc8eknunSA2DoJpJyTQkhheIf5E48u1N0R4q/YxxsAeqRrmK9TQ/uYfgLDfZo91UlANIA==", + "dev": true + }, + "ajv": { + "version": "6.10.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz", + "integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==", "dev": true, "requires": { - "esrecurse": "^4.1.0", - "estraverse": "^4.1.1" + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ajv-keywords": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.4.1.tgz", + "integrity": "sha512-RO1ibKvd27e6FEShVFfPALuHI3WjSVNeK5FIsmme/LYRNxjKuNj+Dt7bucLa6NdSv3JcVTyMlm9kGR84z1XpaQ==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "webpack-sources": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz", + "integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==", + "dev": true, + "requires": { + "source-list-map": "^2.0.0", + "source-map": "~0.6.1" } } } }, "webpack-cli": { - "version": "3.3.5", - "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-3.3.5.tgz", - "integrity": "sha512-w0j/s42c5UhchwTmV/45MLQnTVwRoaUTu9fM5LuyOd/8lFoCNCELDogFoecx5NzRUndO0yD/gF2b02XKMnmAWQ==", + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-3.3.7.tgz", + "integrity": "sha512-OhTUCttAsr+IZSMVwGROGRHvT+QAs8H6/mHIl4SvhAwYywjiylYjpwybGx7WQ9Hkb45FhjtsymkwiRRbGJ1SZQ==", "dev": true, "requires": { "chalk": "2.4.2", @@ -15501,6 +16222,11 @@ } } }, + "whatwg-fetch": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz", + "integrity": "sha512-9GSJUgz1D4MfyKU7KRqwOjXCXTqWdFNvEr7eUBYchQiVc744mqK/MzXPNR2WsPkmkOa4ywfg8C2n8h+13Bey1Q==" + }, "which": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", diff --git a/package.json b/package.json index 29d364513..998a27ff6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "uni2work", - "version": "5.5.0", + "version": "6.4.0", "description": "", "keywords": [], "author": "", @@ -49,16 +49,17 @@ "defaults" ], "devDependencies": { - "@babel/cli": "^7.4.4", - "@babel/core": "^7.4.5", - "@babel/plugin-proposal-class-properties": "^7.4.4", + "@babel/cli": "^7.5.5", + "@babel/core": "^7.5.5", + "@babel/plugin-proposal-class-properties": "^7.5.5", "@babel/plugin-proposal-decorators": "^7.4.4", - "@babel/preset-env": "^7.4.5", + "@babel/plugin-transform-runtime": "^7.5.5", + "@babel/preset-env": "^7.5.5", "@commitlint/cli": "^8.1.0", - "@commitlint/config-conventional": "^8.0.0", - "autoprefixer": "^9.6.0", + "@commitlint/config-conventional": "^8.1.0", + "autoprefixer": "^9.6.1", "babel-core": "^6.26.3", - "babel-eslint": "^10.0.1", + "babel-eslint": "^10.0.3", "babel-loader": "^8.0.6", "babel-plugin-syntax-dynamic-import": "^6.18.0", "babel-plugin-transform-decorators-legacy": "^1.3.5", @@ -66,9 +67,9 @@ "css-loader": "^2.1.1", "eslint": "^5.16.0", "extract-text-webpack-plugin": "^4.0.0-beta.0", - "husky": "^2.4.1", + "husky": "^2.7.0", "jasmine-core": "^3.4.0", - "karma": "^4.1.0", + "karma": "^4.3.0", "karma-chrome-launcher": "^2.2.0", "karma-cli": "^2.0.0", "karma-jasmine": "^2.0.1", @@ -81,15 +82,19 @@ "npm-run-all": "^4.1.5", "null-loader": "^2.0.0", "postcss-loader": "^3.0.0", - "sass-loader": "^7.1.0", + "sass-loader": "^7.3.1", "standard-version": "^6.0.1", "style-loader": "^0.23.1", - "uglifyjs-webpack-plugin": "^2.1.3", - "webpack": "^4.34.0", - "webpack-cli": "^3.3.4" + "uglifyjs-webpack-plugin": "^2.2.0", + "webpack": "^4.39.3", + "webpack-cli": "^3.3.7" }, "dependencies": { - "flatpickr": "^4.5.7", - "npm": "^6.10.1" + "@babel/runtime": "^7.5.5", + "core-js": "^3.2.1", + "moment": "^2.24.0", + "npm": "^6.11.2", + "tail.datetime": "git+https://github.com/uni2work/tail.DateTime.git#master", + "whatwg-fetch": "^3.0.0" } } diff --git a/package.yaml b/package.yaml index cdeca6c8f..61be0c32e 100644 --- a/package.yaml +++ b/package.yaml @@ -1,5 +1,5 @@ name: uniworx -version: 5.5.0 +version: 6.4.0 dependencies: # Due to a bug in GHC 8.0.1, we block its usage @@ -135,6 +135,7 @@ dependencies: - cassava-conduit - constraints - memory + - pqueue other-extensions: - GeneralizedNewtypeDeriving diff --git a/routes b/routes index b8c14a9e7..293577bf9 100644 --- a/routes +++ b/routes @@ -43,14 +43,14 @@ /robots.txt RobotsR GET !free / HomeR GET !free -/users UsersR GET -- no tags, i.e. admins only +/users UsersR GET POST -- no tags, i.e. admins only /users/#CryptoUUIDUser AdminUserR GET POST /users/#CryptoUUIDUser/delete AdminUserDeleteR POST /users/#CryptoUUIDUser/hijack AdminHijackUserR POST !adminANDno-escalation /users/#CryptoUUIDUser/notifications UserNotificationR GET POST !self /users/#CryptoUUIDUser/password UserPasswordR GET POST !selfANDis-pw-hash -!/users/lecturer-invite/new AdminNewLecturerInviteR GET POST -!/users/lecturer-invite AdminLecturerInviteR GET POST +!/users/functionary-invite/new AdminNewFunctionaryInviteR GET POST +!/users/functionary-invite AdminFunctionaryInviteR GET POST /admin AdminR GET /admin/features AdminFeaturesR GET POST /admin/test AdminTestR GET POST @@ -78,8 +78,10 @@ !/term/#TermId TermCourseListR GET !free !/term/#TermId/#SchoolId TermSchoolCourseListR GET !free -/school SchoolListR GET !development -/school/#SchoolId SchoolShowR GET !development +/school SchoolListR GET +!/school/new SchoolNewR GET POST +/school/#SchoolId SchoolR: + / SchoolEditR GET POST /allocation/ AllocationListR GET !free /allocation/#TermId/#SchoolId/#AllocationShorthand AllocationR: diff --git a/src/Auth/LDAP.hs b/src/Auth/LDAP.hs index 406a3a2d4..74c669f3c 100644 --- a/src/Auth/LDAP.hs +++ b/src/Auth/LDAP.hs @@ -2,14 +2,15 @@ module Auth.LDAP ( apLdap , campusLogin , CampusUserException(..) - , campusUser + , campusUser, campusUser' , CampusMessage(..) , ldapUserPrincipalName, ldapUserEmail, ldapUserDisplayName , ldapUserMatriculation, ldapUserFirstName, ldapUserSurname , ldapUserTitle, ldapUserStudyFeatures, ldapUserFieldName + , ldapUserSchoolAssociation ) where -import Import.NoFoundation hiding (userEmail, userDisplayName) +import Import.NoFoundation import Network.Connection import Data.CaseInsensitive (CI) @@ -58,16 +59,17 @@ findUser LdapConf{..} ldap ident retAttrs = fromMaybe [] <$> findM (assertM (not , Ldap.derefAliases Ldap.DerefAlways ] -ldapUserPrincipalName, ldapUserEmail, ldapUserDisplayName, ldapUserMatriculation, ldapUserFirstName, ldapUserSurname, ldapUserTitle, ldapUserStudyFeatures, ldapUserFieldName :: Ldap.Attr -ldapUserPrincipalName = Ldap.Attr "userPrincipalName" -ldapUserEmail = Ldap.Attr "mail" -ldapUserDisplayName = Ldap.Attr "displayName" -ldapUserMatriculation = Ldap.Attr "LMU-Stud-Matrikelnummer" -ldapUserFirstName = Ldap.Attr "givenName" -ldapUserSurname = Ldap.Attr "sn" -ldapUserTitle = Ldap.Attr "title" -ldapUserStudyFeatures = Ldap.Attr "dfnEduPersonFeaturesOfStudy" -ldapUserFieldName = Ldap.Attr "dfnEduPersonFieldOfStudyString" +ldapUserPrincipalName, ldapUserEmail, ldapUserDisplayName, ldapUserMatriculation, ldapUserFirstName, ldapUserSurname, ldapUserTitle, ldapUserStudyFeatures, ldapUserFieldName, ldapUserSchoolAssociation :: Ldap.Attr +ldapUserPrincipalName = Ldap.Attr "userPrincipalName" +ldapUserEmail = Ldap.Attr "mail" +ldapUserDisplayName = Ldap.Attr "displayName" +ldapUserMatriculation = Ldap.Attr "LMU-Stud-Matrikelnummer" +ldapUserFirstName = Ldap.Attr "givenName" +ldapUserSurname = Ldap.Attr "sn" +ldapUserTitle = Ldap.Attr "title" +ldapUserStudyFeatures = Ldap.Attr "dfnEduPersonFeaturesOfStudy" +ldapUserFieldName = Ldap.Attr "dfnEduPersonFieldOfStudyString" +ldapUserSchoolAssociation = Ldap.Attr "LMU-IFI-eduPersonOrgUnitDNString" data CampusUserException = CampusUserLdapError LdapPoolError @@ -80,6 +82,8 @@ data CampusUserException = CampusUserLdapError LdapPoolError instance Exception CampusUserException +makePrisms ''CampusUserException + campusUser :: (MonadBaseControl IO m, MonadThrow m, MonadIO m) => LdapConf -> LdapPool -> Creds site -> m (Ldap.AttrList []) campusUser conf@LdapConf{..} pool Creds{..} = liftIO . (`catches` errHandlers) $ either (throwM . CampusUserLdapError) return <=< withLdap pool $ \ldap -> do Ldap.bind ldap ldapDn ldapPassword @@ -105,6 +109,10 @@ campusUser conf@LdapConf{..} pool Creds{..} = liftIO . (`catches` errHandlers) $ , Exc.Handler $ \(HostCannotConnect host excs) -> throwM $ CampusUserHostCannotConnect host excs ] +campusUser' :: (MonadBaseControl IO m, MonadCatch m, MonadIO m) => LdapConf -> LdapPool -> User -> m (Maybe (Ldap.AttrList [])) +campusUser' conf pool User{userIdent} + = runMaybeT . catchIfMaybeT (is _CampusUserNoResult) $ campusUser conf pool (Creds apLdap (CI.original userIdent) []) + campusForm :: ( RenderMessage site FormMessage , RenderMessage site CampusMessage diff --git a/src/Data/Time/Clock/Instances.hs b/src/Data/Time/Clock/Instances.hs index 9629800d1..88ad3c047 100644 --- a/src/Data/Time/Clock/Instances.hs +++ b/src/Data/Time/Clock/Instances.hs @@ -15,6 +15,7 @@ import qualified Data.Binary as Binary import Data.Time.Clock import Data.Time.Calendar.Instances () +import Web.PathPieces instance Hashable DiffTime where @@ -31,6 +32,10 @@ instance PersistFieldSql NominalDiffTime where deriving instance Generic UTCTime instance Hashable UTCTime +instance PathPiece UTCTime where + toPathPiece = pack . formatTime defaultTimeLocale "%0Y-%m-%dT%H:%M:%S%Q%z" + fromPathPiece = parseTimeM False defaultTimeLocale "%Y-%m-%dT%H:%M:%S%Q%z" . unpack + instance Binary DiffTime where get = fromRational <$> Binary.get diff --git a/src/Database/Esqueleto/Utils.hs b/src/Database/Esqueleto/Utils.hs index 74bfbb7d6..c038f2152 100644 --- a/src/Database/Esqueleto/Utils.hs +++ b/src/Database/Esqueleto/Utils.hs @@ -64,6 +64,8 @@ false = E.val False isJust :: (E.Esqueleto query expr backend, PersistField typ) => expr (E.Value (Maybe typ)) -> expr (E.Value Bool) isJust = E.not_ . E.isNothing +infix 4 `isInfixOf`, `hasInfix` + -- | Check if the first string is contained in the text derived from the second argument isInfixOf :: ( E.Esqueleto query expr backend , E.SqlString s1 diff --git a/src/Database/Persist/Class/Instances.hs b/src/Database/Persist/Class/Instances.hs index 23209a44b..2dbb2bfb0 100644 --- a/src/Database/Persist/Class/Instances.hs +++ b/src/Database/Persist/Class/Instances.hs @@ -21,3 +21,6 @@ instance PersistEntity record => Binary (Key record) where put = Binary.put . toPersistValue putList = Binary.putList . map toPersistValue get = either (fail . unpack) return . fromPersistValue =<< Binary.get + +instance PersistEntity record => NFData (Key record) where + rnf = rnf . keyToValues diff --git a/src/Database/Persist/Types/Instances.hs b/src/Database/Persist/Types/Instances.hs index eb02f5a22..0929a2886 100644 --- a/src/Database/Persist/Types/Instances.hs +++ b/src/Database/Persist/Types/Instances.hs @@ -20,3 +20,4 @@ deriving instance Typeable PersistValue instance Hashable PersistValue instance Binary PersistValue +instance NFData PersistValue diff --git a/src/Foundation.hs b/src/Foundation.hs index 1852150ac..eb0991496 100644 --- a/src/Foundation.hs +++ b/src/Foundation.hs @@ -65,6 +65,7 @@ import Control.Monad.Memo.Class (MonadMemo(..), for4) import qualified Control.Monad.Catch as C import Handler.Utils.StudyFeatures +import Handler.Utils.SchoolLdap import Utils.Form import Utils.Sheet import Utils.SystemMessage @@ -152,6 +153,7 @@ deriving instance Generic TutorialR deriving instance Generic ExamR deriving instance Generic CourseApplicationR deriving instance Generic AllocationR +deriving instance Generic SchoolR deriving instance Generic (Route UniWorX) -- | Convenient Type Synonyms: @@ -310,6 +312,7 @@ embedRenderMessage ''UniWorX ''SubmissionModeDescr embedRenderMessage ''UniWorX ''UploadModeDescr id embedRenderMessage ''UniWorX ''SecretJSONFieldException id embedRenderMessage ''UniWorX ''AFormMessage $ concat . drop 2 . splitCamel +embedRenderMessage ''UniWorX ''SchoolFunction id embedRenderMessage ''UniWorX ''AuthenticationMode id @@ -606,8 +609,9 @@ tagAccessPredicate AuthAdmin = APDB $ \mAuthId route _ -> case route of CourseR tid ssh csh _ -> $cachedHereBinary (mAuthId, tid, ssh, csh) . exceptT return return $ do authId <- maybeExceptT AuthenticationRequired $ return mAuthId isAdmin <- lift . E.selectExists . E.from $ \(course `E.InnerJoin` userAdmin) -> do - E.on $ course E.^. CourseSchool E.==. userAdmin E.^. UserAdminSchool - E.where_ $ userAdmin E.^. UserAdminUser E.==. E.val authId + E.on $ course E.^. CourseSchool E.==. userAdmin E.^. UserFunctionSchool + E.where_ $ userAdmin E.^. UserFunctionUser E.==. E.val authId + E.&&. userAdmin E.^. UserFunctionFunction E.==. E.val SchoolAdmin E.&&. course E.^. CourseTerm E.==. E.val tid E.&&. course E.^. CourseSchool E.==. E.val ssh E.&&. course E.^. CourseShorthand E.==. E.val csh @@ -617,17 +621,24 @@ tagAccessPredicate AuthAdmin = APDB $ \mAuthId route _ -> case route of AllocationR tid ssh ash _ -> $cachedHereBinary (mAuthId, tid, ssh, ash) . exceptT return return $ do authId <- maybeExceptT AuthenticationRequired $ return mAuthId isAdmin <- lift . E.selectExists . E.from $ \(allocation `E.InnerJoin` userAdmin) -> do - E.on $ allocation E.^. AllocationSchool E.==. userAdmin E.^. UserAdminSchool - E.where_ $ userAdmin E.^. UserAdminUser E.==. E.val authId + E.on $ allocation E.^. AllocationSchool E.==. userAdmin E.^. UserFunctionSchool + E.where_ $ userAdmin E.^. UserFunctionUser E.==. E.val authId + E.&&. userAdmin E.^. UserFunctionFunction E.==. E.val SchoolAdmin E.&&. allocation E.^. AllocationTerm E.==. E.val tid E.&&. allocation E.^. AllocationSchool E.==. E.val ssh E.&&. allocation E.^. AllocationShorthand E.==. E.val ash guardMExceptT isAdmin (unauthorizedI MsgUnauthorizedSchoolAdmin) return Authorized + -- Schools: access only to school admins + SchoolR ssh _ -> $cachedHereBinary (mAuthId, ssh) . exceptT return return $ do + authId <- maybeExceptT AuthenticationRequired $ return mAuthId + isAdmin <- lift $ exists [UserFunctionUser ==. authId, UserFunctionFunction ==. SchoolAdmin] + guardMExceptT isAdmin (unauthorizedI MsgUnauthorizedSchoolAdmin) + return Authorized -- other routes: access to any admin is granted here _other -> $cachedHereBinary mAuthId . exceptT return return $ do authId <- maybeExceptT AuthenticationRequired $ return mAuthId - adrights <- lift $ selectFirst [UserAdminUser ==. authId] [] + adrights <- lift $ selectFirst [UserFunctionUser ==. authId, UserFunctionFunction ==. SchoolAdmin] [] guardMExceptT (isJust adrights) (unauthorizedI MsgUnauthorizedSiteAdmin) return Authorized tagAccessPredicate AuthToken = APDB $ \mAuthId route isWrite -> exceptT return return $ @@ -636,10 +647,9 @@ tagAccessPredicate AuthNoEscalation = APDB $ \mAuthId route _ -> case route of AdminHijackUserR cID -> exceptT return return $ do myUid <- maybeExceptT AuthenticationRequired $ return mAuthId uid <- decrypt cID - otherSchoolsAdmin <- lift $ Set.fromList . map (userAdminSchool . entityVal) <$> selectList [UserAdminUser ==. uid] [] - otherSchoolsLecturer <- lift $ Set.fromList . map (userLecturerSchool . entityVal) <$> selectList [UserLecturerUser ==. uid] [] - mySchools <- lift $ Set.fromList . map (userAdminSchool . entityVal) <$> selectList [UserAdminUser ==. myUid] [] - guardMExceptT ((otherSchoolsAdmin `Set.union` otherSchoolsLecturer) `Set.isSubsetOf` mySchools) (unauthorizedI MsgUnauthorizedAdminEscalation) + otherSchoolsFunctions <- lift $ Set.fromList . map (userFunctionSchool . entityVal) <$> selectList [UserFunctionUser ==. uid] [] + mySchools <- lift $ Set.fromList . map (userFunctionSchool . entityVal) <$> selectList [UserFunctionUser ==. myUid, UserFunctionFunction ==. SchoolAdmin] [] + guardMExceptT (otherSchoolsFunctions `Set.isSubsetOf` mySchools) (unauthorizedI MsgUnauthorizedAdminEscalation) return Authorized r -> $unsupportedAuthPredicate AuthNoEscalation r tagAccessPredicate AuthDeprecated = APHandler $ \_ r _ -> do @@ -680,7 +690,7 @@ tagAccessPredicate AuthLecturer = APDB $ \mAuthId route _ -> case route of -- lecturer for any school will do _ -> $cachedHereBinary mAuthId . exceptT return return $ do authId <- maybeExceptT AuthenticationRequired $ return mAuthId - void . maybeMExceptT (unauthorizedI MsgUnauthorizedSchoolLecturer) $ selectFirst [UserLecturerUser ==. authId] [] + void . maybeMExceptT (unauthorizedI MsgUnauthorizedSchoolLecturer) $ selectFirst [UserFunctionUser ==. authId, UserFunctionFunction ==. SchoolLecturer] [] return Authorized tagAccessPredicate AuthCorrector = APDB $ \mAuthId route _ -> exceptT return return $ do authId <- maybeExceptT AuthenticationRequired $ return mAuthId @@ -734,6 +744,20 @@ tagAccessPredicate AuthTutor = APDB $ \mAuthId route _ -> exceptT return return guardMExceptT (not $ Map.null resMap) (unauthorizedI MsgUnauthorizedTutor) return Authorized tagAccessPredicate AuthTime = APDB $ \mAuthId route _ -> case route of + CApplicationR tid ssh csh _ _ -> maybeT (unauthorizedI MsgUnauthorizedApplicationTime) $ do + course <- $cachedHereBinary (tid, ssh, csh) . MaybeT . getKeyBy $ TermSchoolCourseShort tid ssh csh + allocationCourse <- $cachedHereBinary course . lift . getBy $ UniqueAllocationCourse course + allocation <- for allocationCourse $ \(Entity _ AllocationCourse{..}) -> $cachedHereBinary allocationCourseAllocation . MaybeT $ get allocationCourseAllocation + + case allocation of + Nothing -> return () + Just Allocation{..} -> do + cTime <- liftIO getCurrentTime + guard $ NTop allocationStaffAllocationFrom <= NTop (Just cTime) + guard $ NTop (Just cTime) <= NTop allocationStaffAllocationTo + + return Authorized + CExamR tid ssh csh examn subRoute -> maybeT (unauthorizedI MsgUnauthorizedExamTime) $ do course <- $cachedHereBinary (tid, ssh, csh) . MaybeT . getKeyBy $ TermSchoolCourseShort tid ssh csh Entity eId Exam{..} <- $cachedHereBinary (course, examn) . MaybeT . getBy $ UniqueExam course examn @@ -1679,7 +1703,6 @@ siteLayout' headingOverride widget = do addStylesheet $ StaticR bundles_css_vendor_css addStylesheet $ StaticR bundles_css_main_css -- JavaScript - addScript $ StaticR bundles_js_polyfills_js addScript $ StaticR bundles_js_vendor_js addScript $ StaticR bundles_js_main_js toWidget $(juliusFile "templates/i18n.julius") @@ -1723,6 +1746,10 @@ instance YesodBreadcrumbs UniWorX where breadcrumb AdminFeaturesR = return ("Test" , Just AdminR) breadcrumb AdminTestR = return ("Test" , Just AdminR) breadcrumb AdminErrMsgR = return ("Test" , Just AdminR) + + breadcrumb SchoolListR = return ("Institute" , Just AdminR) + breadcrumb (SchoolR ssh SchoolEditR) = return (original (unSchoolKey ssh), Just SchoolListR) + breadcrumb SchoolNewR = return ("Neu" , Just SchoolListR) breadcrumb InfoR = return ("Information" , Nothing) breadcrumb InfoLecturerR = return ("Veranstalter" , Just InfoR) @@ -1996,6 +2023,14 @@ pageActions (HomeR) = ] pageActions (AdminR) = [ MenuItem + { menuItemType = PageActionPrime + , menuItemLabel = MsgMenuSchoolList + , menuItemIcon = Nothing + , menuItemRoute = SomeRoute SchoolListR + , menuItemModal = False + , menuItemAccessCallback' = return True + } + , MenuItem { menuItemType = PageActionPrime , menuItemLabel = MsgAdminFeaturesHeading , menuItemIcon = Nothing @@ -2028,12 +2063,22 @@ pageActions (AdminR) = , menuItemAccessCallback' = return True } ] +pageActions (SchoolListR) = + [ MenuItem + { menuItemType = PageActionPrime + , menuItemLabel = MsgMenuSchoolNew + , menuItemIcon = Nothing + , menuItemRoute = SomeRoute SchoolNewR + , menuItemModal = False + , menuItemAccessCallback' = return True + } + ] pageActions (UsersR) = [ MenuItem { menuItemType = PageActionPrime , menuItemLabel = MsgMenuLecturerInvite , menuItemIcon = Nothing - , menuItemRoute = SomeRoute AdminNewLecturerInviteR + , menuItemRoute = SomeRoute AdminNewFunctionaryInviteR , menuItemModal = True , menuItemAccessCallback' = return True } @@ -2861,13 +2906,6 @@ pageHeading (TermSchoolCourseListR tid ssh) School{schoolName=school} <- handlerToWidget $ runDB $ get404 ssh i18nHeading $ MsgTermSchoolCourseListHeading tid school -pageHeading (SchoolListR) - = Just $ i18nHeading MsgSchoolListHeading -pageHeading (SchoolShowR ssh) - = Just $ do - School{schoolName=school} <- handlerToWidget $ runDB $ get404 ssh - i18nHeading $ MsgSchoolHeading school - pageHeading (CourseListR) = Just $ i18nHeading $ MsgCourseListTitle pageHeading CourseNewR @@ -3006,7 +3044,8 @@ data CampusUserConversionException | CampusUserInvalidSurname | CampusUserInvalidTitle | CampusUserInvalidMatriculation - | CampusUserInvalidFeaturesOfStudy String + | CampusUserInvalidFeaturesOfStudy Text + | CampusUserInvalidAssociatedSchools Text deriving (Eq, Ord, Read, Show, Generic, Typeable) instance Exception CampusUserConversionException @@ -3084,12 +3123,15 @@ upsertCampusUser ldapData Creds{..} = do , userNotificationSettings = def , userMailLanguages = def , userTokensIssuedAfter = Nothing + , userCreated = now + , userLastLdapSynchronisation = Just now , .. } - userUpdate = [ UserMatrikelnummer =. userMatrikelnummer - , UserDisplayName =. userDisplayName - , UserSurname =. userSurname - , UserEmail =. userEmail + userUpdate = [ UserMatrikelnummer =. userMatrikelnummer + , UserDisplayName =. userDisplayName + , UserSurname =. userSurname + , UserEmail =. userEmail + , UserLastLdapSynchronisation =. Just now ] ++ [ UserLastAuthentication =. Just now | not isDummy ] @@ -3111,7 +3153,7 @@ upsertCampusUser ldapData Creds{..} = do Right str <- return $ Text.decodeUtf8' v' return str - fs <- either (throwM . CampusUserInvalidFeaturesOfStudy . unpack) return userStudyFeatures + fs <- either (throwM . CampusUserInvalidFeaturesOfStudy . tshow) return userStudyFeatures let studyTermCandidates = Set.fromList $ do @@ -3141,13 +3183,56 @@ upsertCampusUser ldapData Creds{..} = do insertMaybe studyFeaturesDegree $ StudyDegree (unStudyDegreeKey studyFeaturesDegree) Nothing Nothing insertMaybe studyFeaturesField $ StudyTerms (unStudyTermsKey studyFeaturesField) Nothing Nothing void $ upsert f [StudyFeaturesUpdated =. now, StudyFeaturesValid =. True] + associateUserSchoolsByTerms userId + let + userAssociatedSchools = fmap concat $ forM userAssociatedSchools' parseLdapSchools + userAssociatedSchools' = do + (k, v) <- ldapData + guard $ k == ldapUserSchoolAssociation + v' <- v + Right str <- return $ Text.decodeUtf8' v' + return str + + ss <- either (throwM . CampusUserInvalidAssociatedSchools . tshow) return userAssociatedSchools + + forM_ ss $ \frag -> void . runMaybeT $ do + let + exactMatch = MaybeT . getBy $ UniqueOrgUnit frag + infixMatch = (hoistMaybe . preview _head =<<) . lift . E.select . E.from $ \schoolLdap -> do + E.where_ $ E.val frag `E.isInfixOf` schoolLdap E.^. SchoolLdapOrgUnit + E.&&. E.not_ (E.isNothing $ schoolLdap E.^. SchoolLdapSchool) + return schoolLdap + Entity _ SchoolLdap{..} <- exactMatch <|> infixMatch + ssh <- hoistMaybe schoolLdapSchool + + lift . void $ insertUnique UserSchool + { userSchoolUser = userId + , userSchoolSchool = ssh + , userSchoolIsOptOut = False + } + + forM_ ss $ void . insertUnique . SchoolLdap Nothing + return user where insertMaybe key val = get key >>= maybe (insert_ val) (\_ -> return ()) isDummy = credsPlugin == "dummy" isPWHash = credsPlugin == "PWHash" +associateUserSchoolsByTerms :: UserId -> DB () +associateUserSchoolsByTerms uid = do + sfs <- selectList [StudyFeaturesUser ==. uid] [] + + forM_ sfs $ \(Entity _ StudyFeatures{..}) -> do + schoolTerms <- selectList [SchoolTermsTerms ==. studyFeaturesField] [] + forM_ schoolTerms $ \(Entity _ SchoolTerms{..}) -> + void $ insertUnique UserSchool + { userSchoolUser = uid + , userSchoolSchool = schoolTermsSchool + , userSchoolIsOptOut = False + } + instance YesodAuth UniWorX where type AuthId UniWorX = UserId @@ -3209,6 +3294,11 @@ instance YesodAuth UniWorX where acceptExisting = do res <- maybe (UserError $ IdentifierNotFound credsIdent) (Authenticated . entityKey) <$> getBy uAuth + case res of + Authenticated uid + -> associateUserSchoolsByTerms uid + _other + -> return () case res of Authenticated uid | not isDummy -> res <$ update uid [ UserLastAuthentication =. Just now ] diff --git a/src/Handler/Admin.hs b/src/Handler/Admin.hs index db6096bec..9d8c03552 100644 --- a/src/Handler/Admin.hs +++ b/src/Handler/Admin.hs @@ -289,6 +289,7 @@ instance Button UniWorX ButtonAdminStudyTerms where getAdminFeaturesR, postAdminFeaturesR :: Handler Html getAdminFeaturesR = postAdminFeaturesR postAdminFeaturesR = do + uid <- requireAuthId ((btnResult, btnWdgt), btnEnctype) <- runFormPost $ identifyForm ("infer-button" :: Text) (buttonForm :: Form ButtonAdminStudyTerms) let btnForm = wrapForm btnWdgt def { formAction = Just $ SomeRoute AdminFeaturesR @@ -322,11 +323,21 @@ postAdminFeaturesR = do newStudyTermKeys <- fromMaybe [] <$> lookupSessionJson SessionNewStudyTerms ( (degreeResult,degreeTable) , (studyTermsResult,studytermsTable) - , ((), candidateTable)) <- runDB $ (,,) - <$> mkDegreeTable - <*> mkStudytermsTable (Set.fromList newStudyTermKeys) - (Set.fromList $ map entityKey infConflicts) - <*> mkCandidateTable + , ((), candidateTable) + , userSchools) <- runDB $ do + schools <- E.select . E.from $ \school -> do + E.where_ . E.exists . E.from $ \schoolFunction -> + E.where_ $ schoolFunction E.^. UserFunctionSchool E.==. school E.^. SchoolId + E.&&. schoolFunction E.^. UserFunctionUser E.==. E.val uid + E.&&. schoolFunction E.^. UserFunctionFunction E.==. E.val SchoolAdmin + return school + (,,,) + <$> mkDegreeTable + <*> mkStudytermsTable (Set.fromList newStudyTermKeys) + (Set.fromList $ map entityKey infConflicts) + (Set.fromList schools) + <*> mkCandidateTable + <*> pure schools -- This needs to happen after calls to `dbTable` so they can short-circuit correctly unless (null infConflicts) $ addMessageI Warning MsgStudyFeatureConflict @@ -341,12 +352,16 @@ postAdminFeaturesR = do void . runDB $ Map.traverseWithKey updateDegree res addMessageI Success MsgStudyDegreeChangeSuccess - let studyTermsResult' :: FormResult (Map (Key StudyTerms) (Maybe Text, Maybe Text)) + let studyTermsResult' :: FormResult (Map (Key StudyTerms) (Maybe Text, Maybe Text, Set SchoolId)) studyTermsResult' = studyTermsResult <&> getDBFormResult - (\row -> ( row ^. _dbrOutput . _entityVal . _studyTermsName - , row ^. _dbrOutput . _entityVal . _studyTermsShorthand + (\row -> ( row ^. _dbrOutput . _1 . _entityVal . _studyTermsName + , row ^. _dbrOutput . _1 . _entityVal . _studyTermsShorthand + , row ^. _dbrOutput . _2 )) - updateStudyTerms studyTermsKey (name,short) = update studyTermsKey [StudyTermsName =. name, StudyTermsShorthand =. short] + updateStudyTerms studyTermsKey (name,short,schools) = do + update studyTermsKey [StudyTermsName =. name, StudyTermsShorthand =. short] + forM_ schools $ \ssh -> void . insertUnique $ SchoolTerms ssh studyTermsKey + deleteWhere [SchoolTermsTerms ==. studyTermsKey, SchoolTermsSchool /<-. Set.toList schools, SchoolTermsSchool <-. toListOf (folded . _entityKey) userSchools] formResult studyTermsResult' $ \res -> do void . runDB $ Map.traverseWithKey updateStudyTerms res addMessageI Success MsgStudyTermsChangeSuccess @@ -355,24 +370,41 @@ postAdminFeaturesR = do setTitleI MsgAdminFeaturesHeading $(widgetFile "adminFeatures") where - textInputCell lensRes lensDefault = formCell id (return . view (_dbrOutput . _entityKey)) + textInputCell :: Ord i + => Lens' a (Maybe Text) + -> Getter (DBRow r) (Maybe Text) + -> Getter (DBRow r) i + -> DBRow r + -> DBCell (MForm (HandlerT UniWorX IO)) (FormResult (DBFormResult i a (DBRow r))) + textInputCell lensRes lensDefault lensIndex = formCell id (return . view lensIndex) (\row _mkUnique -> (\(res,fieldView) -> (set lensRes . assertM (not . Text.null) <$> res, fvInput fieldView)) <$> mopt textField "" (Just $ row ^. lensDefault) ) + + checkboxCell :: Ord i + => Lens' a Bool + -> Getter (DBRow r) Bool + -> Getter (DBRow r) i + -> DBRow r + -> DBCell (MForm (HandlerT UniWorX IO)) (FormResult (DBFormResult i a (DBRow r))) + checkboxCell lensRes lensDefault lensIndex = formCell id (return . view lensIndex) + ( \row _mkUnique -> (\(res, fieldView) -> (set lensRes <$> res, fvInput fieldView)) + <$> mpopt checkBoxField "" (Just $ row ^. lensDefault) + ) mkDegreeTable :: DB (FormResult (DBFormResult (Key StudyDegree) (Maybe Text, Maybe Text) (DBRow (Entity StudyDegree))), Widget) mkDegreeTable = let dbtIdent = "admin-studydegrees" :: Text dbtStyle = def - dbtSQLQuery :: E.SqlExpr (Entity StudyDegree) -> E.SqlQuery ( E.SqlExpr (Entity StudyDegree)) + dbtSQLQuery :: E.SqlExpr (Entity StudyDegree) -> E.SqlQuery (E.SqlExpr (Entity StudyDegree)) dbtSQLQuery = return dbtRowKey = (E.^. StudyDegreeKey) dbtProj = return dbtColonnade = formColonnade $ mconcat [ sortable (Just "key") (i18nCell MsgGenericKey) (numCell . view (_dbrOutput . _entityVal . _studyDegreeKey)) - , sortable (Just "name") (i18nCell MsgDegreeName) (textInputCell _1 (_dbrOutput . _entityVal . _studyDegreeName)) - , sortable (Just "short") (i18nCell MsgDegreeShort) (textInputCell _2 (_dbrOutput . _entityVal . _studyDegreeShorthand)) + , sortable (Just "name") (i18nCell MsgDegreeName) (textInputCell _1 (_dbrOutput . _entityVal . _studyDegreeName) (_dbrOutput . _entityKey)) + , sortable (Just "short") (i18nCell MsgDegreeShort) (textInputCell _2 (_dbrOutput . _entityVal . _studyDegreeShorthand) (_dbrOutput . _entityKey)) , dbRow ] dbtSorting = Map.fromList @@ -390,20 +422,29 @@ postAdminFeaturesR = do dbtCsvDecode = Nothing in dbTable psValidator DBTable{..} - mkStudytermsTable :: Set (Key StudyTerms) -> Set (Key StudyTerms) -> DB (FormResult (DBFormResult (Key StudyTerms) (Maybe Text, Maybe Text) (DBRow (Entity StudyTerms))), Widget) - mkStudytermsTable newKeys badKeys = + mkStudytermsTable :: Set (Key StudyTerms) -> Set (Key StudyTerms) -> Set (Entity School) -> DB (FormResult (DBFormResult (Key StudyTerms) (Maybe Text, Maybe Text, Set SchoolId) (DBRow (Entity StudyTerms, Set SchoolId))), Widget) + mkStudytermsTable newKeys badKeys schools = let dbtIdent = "admin-studyterms" :: Text dbtStyle = def - dbtSQLQuery :: E.SqlExpr (Entity StudyTerms) -> E.SqlQuery ( E.SqlExpr (Entity StudyTerms)) + dbtSQLQuery :: E.SqlExpr (Entity StudyTerms) -> E.SqlQuery (E.SqlExpr (Entity StudyTerms)) dbtSQLQuery = return dbtRowKey = (E.^. StudyTermsKey) - dbtProj = return + dbtProj field = do + fieldSchools <- fmap (setOf $ folded . _Value) . lift . E.select . E.from $ \school -> do + E.where_ . E.exists . E.from $ \schoolTerms -> + E.where_ $ schoolTerms E.^. SchoolTermsSchool E.==. school E.^. SchoolId + E.&&. schoolTerms E.^. SchoolTermsTerms E.==. E.val (field ^. _dbrOutput . _entityKey) + E.where_ $ school E.^. SchoolShorthand `E.in_` E.valList (toListOf (folded . _entityKey . _SchoolId) schools) + return $ school E.^. SchoolId + return $ field & _dbrOutput %~ (, fieldSchools) dbtColonnade = formColonnade $ mconcat - [ sortable (Just "key") (i18nCell MsgGenericKey) (numCell . view (_dbrOutput . _entityVal . _studyTermsKey)) - , sortable (Just "isnew") (i18nCell MsgGenericIsNew) (isNewCell . flip Set.member newKeys . view (_dbrOutput . _entityKey)) - , sortable (Just "isbad") (i18nCell MsgGenericHasConflict) (isBadCell . flip Set.member badKeys . view (_dbrOutput . _entityKey)) - , sortable (Just "name") (i18nCell MsgStudyTermsName) (textInputCell _1 (_dbrOutput . _entityVal . _studyTermsName)) - , sortable (Just "short") (i18nCell MsgStudyTermsShort) (textInputCell _2 (_dbrOutput . _entityVal . _studyTermsShorthand)) + [ sortable (Just "key") (i18nCell MsgGenericKey) (numCell . view (_dbrOutput . _1 . _entityVal . _studyTermsKey)) + , sortable (Just "isnew") (i18nCell MsgGenericIsNew) (isNewCell . flip Set.member newKeys . view (_dbrOutput . _1 . _entityKey)) + , sortable (Just "isbad") (i18nCell MsgGenericHasConflict) (isBadCell . flip Set.member badKeys . view (_dbrOutput . _1 . _entityKey)) + , sortable (Just "name") (i18nCell MsgStudyTermsName) (textInputCell _1 (_dbrOutput . _1 . _entityVal . _studyTermsName) (_dbrOutput . _1 . _entityKey)) + , sortable (Just "short") (i18nCell MsgStudyTermsShort) (textInputCell _2 (_dbrOutput . _1 . _entityVal . _studyTermsShorthand) (_dbrOutput . _1 . _entityKey)) + , flip foldMap schools $ \(Entity ssh School{schoolName}) -> + sortable Nothing (cell $ toWidget schoolName) (checkboxCell (_3 . at ssh . _Maybe) (_dbrOutput . _2 . at ssh . _Maybe) (_dbrOutput . _1 . _entityKey)) , dbRow ] dbtSorting = Map.fromList diff --git a/src/Handler/Allocation/List.hs b/src/Handler/Allocation/List.hs index 38069fc4c..016db93a0 100644 --- a/src/Handler/Allocation/List.hs +++ b/src/Handler/Allocation/List.hs @@ -5,12 +5,13 @@ module Handler.Allocation.List import Import import qualified Database.Esqueleto as E +import qualified Database.Esqueleto.Utils as E import Handler.Utils.Table.Columns import Handler.Utils.Table.Pagination type AllocationTableExpr = E.SqlExpr (Entity Allocation) -type AllocationTableData = DBRow (Entity Allocation) +type AllocationTableData = DBRow (Entity Allocation, Natural, Natural) allocationListIdent :: Text allocationListIdent = "allocations" @@ -18,8 +19,34 @@ allocationListIdent = "allocations" queryAllocation :: Getter AllocationTableExpr (E.SqlExpr (Entity Allocation)) queryAllocation = id + +countCourses :: (Num n, PersistField n) + => (E.SqlExpr (Entity AllocationCourse) -> E.SqlExpr (E.Value Bool)) + -> E.SqlExpr (Entity Allocation) + -> E.SqlExpr (E.Value n) +countCourses addWhere allocation = E.sub_select . E.from $ \allocationCourse -> do + E.where_ $ allocationCourse E.^. AllocationCourseAllocation E.==. allocation E.^. AllocationId + E.&&. addWhere allocationCourse + return E.countRows + +queryAvailable :: Getter AllocationTableExpr (E.SqlExpr (E.Value Natural)) +queryAvailable = queryAllocation . to (countCourses $ const E.true) + +queryApplied :: UserId -> Getter AllocationTableExpr (E.SqlExpr (E.Value Natural)) +queryApplied uid = queryAllocation . to (\allocation -> countCourses (addWhere allocation) allocation) + where + addWhere allocation allocationCourse + = E.exists . E.from $ \courseApplication -> + E.where_ $ courseApplication E.^. CourseApplicationCourse E.==. allocationCourse E.^. AllocationCourseCourse + E.&&. courseApplication E.^. CourseApplicationAllocation E.==. E.just (allocation E.^. AllocationId) + E.&&. courseApplication E.^. CourseApplicationUser E.==. E.val uid + resultAllocation :: Lens' AllocationTableData (Entity Allocation) -resultAllocation = _dbrOutput +resultAllocation = _dbrOutput . _1 + +resultAvailable, resultApplied :: Lens' AllocationTableData Natural +resultAvailable = _dbrOutput . _2 +resultApplied = _dbrOutput . _3 allocationTermLink :: TermId -> SomeRoute UniWorX allocationTermLink tid = SomeRoute (AllocationListR, [(dbFilterKey allocationListIdent "term", toPathPiece tid)]) @@ -32,13 +59,17 @@ allocationLink Allocation{..} = SomeRoute $ AllocationR allocationTerm allocatio getAllocationListR :: Handler Html getAllocationListR = do + muid <- maybeAuthId now <- liftIO getCurrentTime let dbtSQLQuery :: AllocationTableExpr -> E.SqlQuery _ - dbtSQLQuery = return + dbtSQLQuery = runReaderT $ (,,) + <$> view queryAllocation + <*> view queryAvailable + <*> view (maybe (to . const $ E.val 0) queryApplied muid) dbtProj :: DBRow _ -> MaybeT (YesodDB UniWorX) AllocationTableData - dbtProj = return + dbtProj = return . over (_dbrOutput . _2) E.unValue . over (_dbrOutput . _3) E.unValue dbtRowKey = view $ queryAllocation . to (E.^. AllocationId) @@ -47,12 +78,24 @@ getAllocationListR = do [ anchorColonnade (views (resultAllocation . _entityVal . _allocationTerm) allocationTermLink) $ colTermShort (resultAllocation . _entityVal . _allocationTerm) , anchorColonnade (views (resultAllocation . _entityVal . _allocationSchool) allocationSchoolLink) $ colSchoolShort (resultAllocation . _entityVal . _allocationSchool) , anchorColonnade (views (resultAllocation . _entityVal) allocationLink) $ colAllocationName (resultAllocation . _entityVal . _allocationName) + , sortable (Just "available") (i18nCell MsgAllocationAvailableCourses) $ views resultAvailable i18nCell + , if + | Just _ <- muid + -> sortable (Just "applied") (i18nCell MsgAllocationAppliedCourses) . views resultApplied $ maybe mempty i18nCell . assertM' (> 0) + | otherwise + -> mempty ] dbtSorting = mconcat [ sortTerm $ queryAllocation . to (E.^. AllocationTerm) , sortSchool $ queryAllocation . to (E.^. AllocationSchool) , sortAllocationName $ queryAllocation . to (E.^. AllocationName) + , singletonMap "available" . SortColumn $ view queryAvailable + , if + | Just uid <- muid + -> singletonMap "applied" . SortColumn . view $ queryApplied uid + | otherwise + -> mempty ] dbtFilter = mconcat diff --git a/src/Handler/Allocation/Show.hs b/src/Handler/Allocation/Show.hs index b31ae9273..bb6410ef0 100644 --- a/src/Handler/Allocation/Show.hs +++ b/src/Handler/Allocation/Show.hs @@ -46,8 +46,8 @@ getAShowR tid ssh ash = do let title = MsgAllocationTitle (mr . ShortTermIdentifier $ unTermKey allocationTerm) (unSchoolKey allocationSchool) allocationName shortTitle = MsgAllocationShortTitle (mr . ShortTermIdentifier $ unTermKey allocationTerm) (unSchoolKey allocationSchool) allocationShorthand - staffInformation <- anyM courses $ \(view $ resultCourse . _entityVal -> Course{..}) -> - hasReadAccessTo $ CourseR courseTerm courseSchool courseShorthand CApplicationsR + -- staffInformation <- anyM courses $ \(view $ resultCourse . _entityVal -> Course{..}) -> + -- hasReadAccessTo $ CourseR courseTerm courseSchool courseShorthand CApplicationsR mayRegister <- hasWriteAccessTo $ AllocationR tid ssh ash ARegisterR (registerForm, registerEnctype) <- generateFormPost . renderAForm FormStandard . allocationRegisterForm $ allocationUserToForm . entityVal <$> registration let diff --git a/src/Handler/Course/Application/Edit.hs b/src/Handler/Course/Application/Edit.hs index 281a21826..b9b51cb1d 100644 --- a/src/Handler/Course/Application/Edit.hs +++ b/src/Handler/Course/Application/Edit.hs @@ -13,27 +13,30 @@ getCAEditR = postCAEditR postCAEditR tid ssh csh cID = do uid <- requireAuthId appId <- decrypt cID - (mAlloc, Entity cid Course{..}, CourseApplication{..}, isAdmin, User{..}) <- runDB $ do + (mAlloc, Entity cid Course{..}, CourseApplication{..}, User{..}) <- runDB $ do course <- getBy404 $ TermSchoolCourseShort tid ssh csh app <- get404 appId mAlloc <- traverse getEntity404 $ courseApplicationAllocation app appUser <- get404 $ courseApplicationUser app - isAdmin <- case mAlloc of - Just alloc -> exists [UserAdminUser ==. uid, UserAdminSchool ==. alloc ^. _entityVal . _allocationSchool] - Nothing -> exists [UserAdminUser ==. uid, UserAdminSchool ==. course ^. _entityVal . _courseSchool] - return (mAlloc, course, app, isAdmin, appUser) + return (mAlloc, course, app, appUser) + isAdmin <- case mAlloc of + Just (Entity _ Allocation{..}) + -> hasWriteAccessTo $ SchoolR allocationSchool SchoolEditR + Nothing + -> hasWriteAccessTo $ SchoolR courseSchool SchoolEditR + let afmApplicant = uid == courseApplicationUser || isAdmin afmLecturer <- hasWriteAccessTo $ CourseR courseTerm courseSchool courseShorthand CEditR - afmApplicantEdit <- hasWriteAccessTo $ CApplicationR tid ssh csh cID CAEditR + mayEdit <- hasWriteAccessTo $ CApplicationR tid ssh csh cID CAEditR courseCID <- encrypt cid :: Handler CryptoUUIDCourse let afMode = ApplicationFormMode - { afmApplicant = uid == courseApplicationUser || isAdmin - , afmApplicantEdit + { afmApplicant + , afmApplicantEdit = afmApplicant && mayEdit , afmLecturer } - (ApplicationFormView{..}, appEnc) <- editApplicationR (entityKey <$> mAlloc) uid cid (Just appId) afMode (/= BtnAllocationApply) $ if + (ApplicationFormView{..}, appEnc) <- editApplicationR (entityKey <$> mAlloc) courseApplicationUser cid (Just appId) afMode (/= BtnAllocationApply) $ if | uid == courseApplicationUser , Just (Entity _ Allocation{..}) <- mAlloc -> SomeRoute $ AllocationR allocationTerm allocationSchool allocationShorthand AShowR :#: courseCID diff --git a/src/Handler/Course/Application/List.hs b/src/Handler/Course/Application/List.hs index a3faa9a89..23ddd7d60 100644 --- a/src/Handler/Course/Application/List.hs +++ b/src/Handler/Course/Application/List.hs @@ -221,6 +221,11 @@ postCApplicationsR tid ssh csh = do participantLink uid = do cID <- encrypt uid return . SomeRoute . CourseR tid ssh csh $ CUserR cID + + applicationLink :: MonadCrypto m => CourseApplicationId -> m (SomeRoute UniWorX) + applicationLink appId = do + cID <- encrypt appId + return . SomeRoute $ CApplicationR tid ssh csh cID CAEditR dbtSQLQuery :: CourseApplicationsTableExpr -> E.SqlQuery _ dbtSQLQuery = runReaderT $ do @@ -256,7 +261,7 @@ postCApplicationsR tid ssh csh = do dbtColonnade :: Colonnade Sortable _ _ dbtColonnade = mconcat [ emptyOpticColonnade (resultAllocation . _entityVal) $ \l -> anchorColonnade (views l allocationLink) $ colAllocationShorthand (l . _allocationShorthand) - , colApplicationId (resultCourseApplication . _entityKey) + , anchorColonnadeM (views (resultCourseApplication . _entityKey) applicationLink) $ colApplicationId (resultCourseApplication . _entityKey) , anchorColonnadeM (views (resultUser . _entityKey) participantLink) $ colUserDisplayName (resultUser . _entityVal . $(multifocusL 2) _userDisplayName _userSurname) , colUserMatriculation (resultUser . _entityVal . _userMatrikelnummer) , emptyOpticColonnade (resultStudyTerms . _entityVal) colStudyTerms @@ -525,6 +530,7 @@ postCApplicationsR tid ssh csh = do psValidator :: PSValidator _ _ psValidator = def + & defaultSorting [SortAscBy "user-name"] dbTableWidget' psValidator DBTable{..} diff --git a/src/Handler/Course/Edit.hs b/src/Handler/Course/Edit.hs index 248c17571..a9121edf4 100644 --- a/src/Handler/Course/Edit.hs +++ b/src/Handler/Course/Edit.hs @@ -20,6 +20,7 @@ import qualified Data.Map as Map import Control.Monad.Trans.Writer (execWriterT) import qualified Database.Esqueleto as E +import qualified Database.Esqueleto.Utils as E import Jobs.Queue @@ -105,10 +106,12 @@ makeCourseForm miButtonAction template = identifyForm FIDcourse $ \html -> do MsgRenderer mr <- getMsgRenderer uid <- liftHandlerT requireAuthId - (lecSchools, admSchools) <- liftHandlerT . runDB $ (,) - <$> (map (userLecturerSchool . entityVal) <$> selectList [UserLecturerUser ==. uid] [] ) - <*> (map (userAdminSchool . entityVal) <$> selectList [UserAdminUser ==. uid] [] ) - let userSchools = lecSchools ++ admSchools + (lecturerSchools, adminSchools) <- liftHandlerT . runDB $ do + lecturerSchools <- map (userFunctionSchool . entityVal) <$> selectList [UserFunctionUser ==. uid, UserFunctionFunction <-. [SchoolLecturer]] [] + protoAdminSchools <- map (userFunctionSchool . entityVal) <$> selectList [UserFunctionUser ==. uid, UserFunctionFunction <-. [SchoolAdmin]] [] + adminSchools <- filterM (hasWriteAccessTo . flip SchoolR SchoolEditR) protoAdminSchools + return (lecturerSchools, adminSchools) + let userSchools = nub $ lecturerSchools ++ adminSchools termsField <- case template of -- Change of term is only allowed if user may delete the course (i.e. no participants) or admin @@ -200,32 +203,55 @@ makeCourseForm miButtonAction template = identifyForm FIDcourse $ \html -> do allocationForm = wFormToAForm $ do availableAllocations' <- liftHandlerT . runDB . E.select . E.from $ \(allocation `E.InnerJoin` term) -> do E.on $ allocation E.^. AllocationTerm E.==. term E.^. TermId + + let alreadyParticipates = flip (maybe E.false) (template >>= cfCourseId) $ \cid -> + E.exists . E.from $ \allocationCourse -> + E.where_ $ allocationCourse E.^. AllocationCourseCourse E.==. E.val cid + E.&&. allocationCourse E.^. AllocationCourseAllocation E.==. allocation E.^. AllocationId + E.where_ $ term E.^. TermActive - return allocation + E.||. alreadyParticipates + E.||. allocation E.^. AllocationSchool `E.in_` E.valList adminSchools + return (allocation, alreadyParticipates) now <- liftIO getCurrentTime let allocationEnabled :: Entity Allocation -> Bool - allocationEnabled (Entity _ Allocation{..}) = NTop allocationStaffRegisterFrom <= NTop (Just now) - && NTop (Just now) <= NTop allocationStaffRegisterTo - availableAllocations = filter allocationEnabled availableAllocations' + allocationEnabled (Entity _ Allocation{..}) + = ( NTop allocationStaffRegisterFrom <= NTop (Just now) + && NTop (Just now) <= NTop allocationStaffRegisterTo + ) || allocationSchool `elem` adminSchools + availableAllocations = availableAllocations' ^.. folded . filtered (allocationEnabled . view _1) . _1 + activeAllocations = availableAllocations' ^.. folded . filtered ((&&) <$> (not <$> allocationEnabled . view _1) <*> view (_2 . _Value)) . _1 mkAllocationOption :: forall m. (MonadHandler m, HandlerSite m ~ UniWorX) => Entity Allocation -> m (Option AllocationId) mkAllocationOption (Entity aId Allocation{..}) = liftHandlerT $ do cID <- encrypt aId :: Handler CryptoUUIDAllocation return . Option (mr . MsgCourseAllocationOption (mr . ShortTermIdentifier $ unTermKey allocationTerm) $ CI.original allocationName) aId $ toPathPiece cID - case availableAllocations of - [] -> wforced (convertField (const Nothing) (const False) checkBoxField) (fslI MsgCourseAllocationParticipate & setTooltip MsgCourseNoAllocationsAvailable) Nothing + currentAllocationAvailable = (\alloc -> any ((== alloc) . entityKey) availableAllocations) . acfAllocation <$> (template >>= cfAllocation) + + case (currentAllocationAvailable, availableAllocations) of + (Nothing, []) -> wforced (convertField (const Nothing) (const False) checkBoxField) (fslI MsgCourseAllocationParticipate & setTooltip MsgCourseNoAllocationsAvailable) Nothing _ -> do - allocationOptions <- mkOptionList <$> mapM mkAllocationOption availableAllocations + allocationOptions <- mkOptionList <$> mapM mkAllocationOption (availableAllocations ++ activeAllocations) let - allocationForm' = AllocationCourseForm - <$> apreq (selectField' Nothing $ return allocationOptions) (fslI MsgCourseAllocation) (fmap acfAllocation $ template >>= cfAllocation) - <*> apreq (natFieldI MsgCourseAllocationMinCapacityMustBeNonNegative) (fslI MsgCourseAllocationMinCapacity & setTooltip MsgCourseAllocationMinCapacityTip) (fmap acfMinCapacity $ template >>= cfAllocation) - - optionalActionW allocationForm' (fslI MsgCourseAllocationParticipate & setTooltip MsgCourseAllocationParticipateTip) (is _Just . cfAllocation <$> template) + userAdmin = not $ null adminSchools + mayChange = fromMaybe True $ (|| userAdmin) <$> currentAllocationAvailable + + allocationForm' = + let ainp :: Field Handler a -> FieldSettings UniWorX -> Maybe a -> AForm Handler a + ainp + | mayChange + = apreq + | otherwise + = aforcedJust + in AllocationCourseForm + <$> ainp (selectField' Nothing $ return allocationOptions) (fslI MsgCourseAllocation) (fmap acfAllocation $ template >>= cfAllocation) + <*> ainp (natFieldI MsgCourseAllocationMinCapacityMustBeNonNegative) (fslI MsgCourseAllocationMinCapacity & setTooltip MsgCourseAllocationMinCapacityTip) (fmap acfMinCapacity $ template >>= cfAllocation) + + optionalActionW' (bool mforcedJust mpopt mayChange) allocationForm' (fslI MsgCourseAllocationParticipate & setTooltip MsgCourseAllocationParticipateTip) (is _Just . cfAllocation <$> template) (result, widget) <- flip (renderAForm FormStandard) html $ CourseForm <$> pure (cfCourseId =<< template) @@ -278,11 +304,11 @@ makeCourseForm miButtonAction template = identifyForm FIDcourse $ \html -> do _ -> (result, widget) -validateCourse :: (MonadHandler m, HandlerSite m ~ UniWorX) => CourseForm -> m [Text] +validateCourse :: (MonadLogger m, MonadHandler m, HandlerSite m ~ UniWorX) => CourseForm -> m [Text] validateCourse CourseForm{..} = do now <- liftIO getCurrentTime uid <- liftHandlerT requireAuthId - userAdmin <- liftHandlerT . runDB . getBy $ UniqueUserAdmin uid cfSchool -- FIXME: This /needs/ to be a call to `isAuthorized` on a route + userAdmin <- hasWriteAccessTo $ SchoolR cfSchool SchoolEditR MsgRenderer mr <- getMsgRenderer allocationTerm <- for (acfAllocation <$> cfAllocation) $ fmap allocationTerm . liftHandlerT . runDB . getJust @@ -291,7 +317,7 @@ validateCourse CourseForm{..} = do prevAllocation <- fmap join . traverse get $ allocationCourseAllocation . entityVal <$> prevAllocationCourse fmap join . for prevAllocation $ \Allocation{allocationStaffRegisterTo} -> if - | is _Just userAdmin + | userAdmin -> return Nothing | NTop allocationStaffRegisterTo <= NTop (Just now) -> Just . courseCapacity <$> getJust cid @@ -309,7 +335,7 @@ validateCourse CourseForm{..} = do ( NTop cfRegFrom <= NTop cfDeRegUntil , MsgCourseDeregistrationEndMustBeAfterStart ) - , ( maybe (anyOf (traverse . _Right . _1) (== uid) cfLecturers) (\(Entity _ UserAdmin{}) -> True) userAdmin + , ( bool (anyOf (traverse . _Right . _1) (== uid) cfLecturers) True userAdmin , MsgCourseUserMustBeLecturer ) , ( is _Nothing cfAllocation || is _Just cfCapacity @@ -357,8 +383,9 @@ getCourseNewR = do E.&&. lecturer E.^. LecturerCourse E.==. course E.^. CourseId let lecturersSchool = E.exists $ E.from $ \user -> - E.where_ $ user E.^. UserLecturerUser E.==. E.val uid - E.&&. user E.^. UserLecturerSchool E.==. course E.^. CourseSchool + E.where_ $ user E.^. UserFunctionUser E.==. E.val uid + E.&&. user E.^. UserFunctionSchool E.==. course E.^. CourseSchool + E.&&. user E.^. UserFunctionFunction E.==. E.val SchoolLecturer let courseCreated c = E.sub_select . E.from $ \edit -> do -- oldest edit must be creation E.where_ $ edit E.^. CourseEditCourse E.==. c E.^. CourseId @@ -527,17 +554,16 @@ courseEditHandler miButtonAction mbCourseForm = do , formEncoding = formEnctype } -upsertAllocationCourse :: (MonadHandler m, HandlerSite m ~ UniWorX) => CourseId -> Maybe AllocationCourseForm -> ReaderT SqlBackend m () +upsertAllocationCourse :: (MonadLogger m, MonadHandler m, HandlerSite m ~ UniWorX) => CourseId -> Maybe AllocationCourseForm -> ReaderT SqlBackend m () upsertAllocationCourse cid cfAllocation = do now <- liftIO getCurrentTime - uid <- liftHandlerT requireAuthId Course{..} <- getJust cid prevAllocationCourse <- getBy $ UniqueAllocationCourse cid prevAllocation <- fmap join . traverse get $ allocationCourseAllocation . entityVal <$> prevAllocationCourse - userAdmin <- liftHandlerT . runDB . getBy $ UniqueUserAdmin uid courseSchool -- FIXME: This /needs/ to be a call to `isAuthorized` on a route + userAdmin <- fromMaybe False <$> for prevAllocation (\Allocation{..} -> hasWriteAccessTo $ SchoolR allocationSchool SchoolEditR) doEdit <- if - | is _Just userAdmin + | userAdmin -> return True | Just Allocation{allocationStaffRegisterTo} <- prevAllocation , NTop allocationStaffRegisterTo <= NTop (Just now) diff --git a/src/Handler/Course/LecturerInvite.hs b/src/Handler/Course/LecturerInvite.hs index 7bc870396..696ba927b 100644 --- a/src/Handler/Course/LecturerInvite.hs +++ b/src/Handler/Course/LecturerInvite.hs @@ -61,7 +61,7 @@ lecturerInvitationConfig = InvitationConfig{..} getKeyBy404 $ TermSchoolCourseShort tid csh ssh invitationSubject (Entity _ Course{..}) _ = return . SomeMessage $ MsgMailSubjectLecturerInvitation courseTerm courseSchool courseShorthand invitationHeading (Entity _ Course{..}) _ = return . SomeMessage $ MsgCourseLecInviteHeading $ CI.original courseName - invitationExplanation _ _ = [ihamlet|_{SomeMessage MsgCourseLecInviteExplanation}|] + invitationExplanation _ _ = return [ihamlet|_{SomeMessage MsgCourseLecInviteExplanation}|] invitationTokenConfig _ _ = do itAuthority <- liftHandlerT requireAuthId return $ InvitationTokenConfig itAuthority Nothing Nothing Nothing diff --git a/src/Handler/Course/ParticipantInvite.hs b/src/Handler/Course/ParticipantInvite.hs index 97b79e54c..a54af6349 100644 --- a/src/Handler/Course/ParticipantInvite.hs +++ b/src/Handler/Course/ParticipantInvite.hs @@ -74,7 +74,7 @@ participantInvitationConfig = InvitationConfig{..} getKeyBy404 $ TermSchoolCourseShort tid csh ssh invitationSubject (Entity _ Course{..}) _ = return . SomeMessage $ MsgMailSubjectParticipantInvitation courseTerm courseSchool courseShorthand invitationHeading (Entity _ Course{..}) _ = return . SomeMessage $ MsgCourseParticipantInviteHeading $ CI.original courseName - invitationExplanation _ _ = [ihamlet|_{SomeMessage MsgCourseParticipantInviteExplanation}|] + invitationExplanation _ _ = return [ihamlet|_{SomeMessage MsgCourseParticipantInviteExplanation}|] invitationTokenConfig _ _ = do itAuthority <- liftHandlerT requireAuthId return $ InvitationTokenConfig itAuthority Nothing Nothing Nothing diff --git a/src/Handler/Course/Users.hs b/src/Handler/Course/Users.hs index 0549a0745..fc7b82a36 100644 --- a/src/Handler/Course/Users.hs +++ b/src/Handler/Course/Users.hs @@ -206,11 +206,10 @@ makeCourseUserTable cid restrict colChoices psValidator = do , dbParamsFormAction = Just $ SomeRoute currentRoute , dbParamsFormAttrs = [] , dbParamsFormSubmit = FormSubmit - , dbParamsFormAdditional = \csrf -> do - (res,vw) <- mreq (selectField optionsFinite) "" Nothing - let formWgt = toWidget csrf <> fvInput vw - formRes = (, mempty) . First . Just <$> res - return (formRes,formWgt) + , dbParamsFormAdditional + = renderAForm FormStandard + $ (, mempty) . First . Just + <$> areq (selectField optionsFinite) (fslI MsgAction) Nothing , dbParamsFormEvaluate = liftHandlerT . runFormPost , dbParamsFormResult = id , dbParamsFormIdent = def diff --git a/src/Handler/Exam/CorrectorInvite.hs b/src/Handler/Exam/CorrectorInvite.hs index f8398487a..738c2a3fb 100644 --- a/src/Handler/Exam/CorrectorInvite.hs +++ b/src/Handler/Exam/CorrectorInvite.hs @@ -61,7 +61,7 @@ examCorrectorInvitationConfig = InvitationConfig{..} Course{..} <- get404 examCourse return . SomeMessage $ MsgMailSubjectExamCorrectorInvitation courseTerm courseSchool courseShorthand examName invitationHeading (Entity _ Exam{..}) _ = return . SomeMessage $ MsgExamCorrectorInviteHeading examName - invitationExplanation _ _ = [ihamlet|_{SomeMessage MsgExamCorrectorInviteExplanation}|] + invitationExplanation _ _ = return [ihamlet|_{SomeMessage MsgExamCorrectorInviteExplanation}|] invitationTokenConfig _ _ = do itAuthority <- liftHandlerT requireAuthId return $ InvitationTokenConfig itAuthority Nothing Nothing Nothing diff --git a/src/Handler/Exam/Form.hs b/src/Handler/Exam/Form.hs index b2c51c946..503e066d4 100644 --- a/src/Handler/Exam/Form.hs +++ b/src/Handler/Exam/Form.hs @@ -162,8 +162,8 @@ examOccurrenceForm prev = wFormToAForm $ do (eofNameRes, eofNameView) <- mpreq ciField ("" & addName (nudge "name")) (eofName <$> mPrev) (eofRoomRes, eofRoomView) <- mpreq textField ("" & addName (nudge "room")) (eofRoom <$> mPrev) (eofCapacityRes, eofCapacityView) <- mpreq (natFieldI MsgExamRoomCapacityNegative) ("" & addName (nudge "capacity")) (eofCapacity <$> mPrev) - (eofStartRes, eofStartView) <- mpreq utcTimeField ("" & addName (nudge "start")) (eofStart <$> mPrev) - (eofEndRes, eofEndView) <- mopt utcTimeField ("" & addName (nudge "end")) (eofEnd <$> mPrev) + (eofStartRes, eofStartView) <- mpreq utcTimeField ("" & addName (nudge "start") & addDatepickerPositionAttr DPBottom) (eofStart <$> mPrev) + (eofEndRes, eofEndView) <- mopt utcTimeField ("" & addName (nudge "end") & addDatepickerPositionAttr DPBottom) (eofEnd <$> mPrev) (eofDescRes, eofDescView) <- mopt htmlFieldSmall ("" & addName (nudge "description")) (eofDescription <$> mPrev) return ( ExamOccurrenceForm diff --git a/src/Handler/Exam/RegistrationInvite.hs b/src/Handler/Exam/RegistrationInvite.hs index 2b41622b9..5810d3516 100644 --- a/src/Handler/Exam/RegistrationInvite.hs +++ b/src/Handler/Exam/RegistrationInvite.hs @@ -69,7 +69,7 @@ examRegistrationInvitationConfig = InvitationConfig{..} Course{..} <- get404 examCourse return . SomeMessage $ MsgMailSubjectExamRegistrationInvitation courseTerm courseSchool courseShorthand examName invitationHeading (Entity _ Exam{..}) _ = return . SomeMessage $ MsgExamRegistrationInviteHeading examName - invitationExplanation _ _ = [ihamlet|_{SomeMessage MsgExamRegistrationInviteExplanation}|] + invitationExplanation _ _ = return [ihamlet|_{SomeMessage MsgExamRegistrationInviteExplanation}|] invitationTokenConfig _ (InvDBDataExamRegistration{..}, _) = do itAuthority <- liftHandlerT requireAuthId let itExpiresAt = Just $ Just invDBExamRegistrationDeadline diff --git a/src/Handler/Profile.hs b/src/Handler/Profile.hs index 11b9728a3..b4a38f4f3 100644 --- a/src/Handler/Profile.hs +++ b/src/Handler/Profile.hs @@ -15,6 +15,7 @@ import qualified Database.Esqueleto as E import qualified Database.Esqueleto.Utils as E -- import Database.Esqueleto ((^.)) +import qualified Data.CaseInsensitive as CI data SettingsForm = SettingsForm @@ -25,15 +26,33 @@ data SettingsForm = SettingsForm , stgTime :: DateTimeFormat , stgDownloadFiles :: Bool , stgWarningDays :: NominalDiffTime + , stgSchools :: Set SchoolId , stgNotificationSettings :: NotificationSettings } -data NotificationTriggerKind = NTKAll | NTKCourseParticipant | NTKExamParticipant | NTKCorrector | NTKLecturer | NTKAdmin - deriving (Eq, Ord, Enum, Bounded, Generic, Typeable) -instance Universe NotificationTriggerKind -instance Finite NotificationTriggerKind +data NotificationTriggerKind + = NTKAll + | NTKCourseParticipant + | NTKExamParticipant + | NTKCorrector + | NTKAllocationStaff + | NTKFunctionary SchoolFunction + deriving (Eq, Ord, Generic, Typeable) +deriveFinite ''NotificationTriggerKind -embedRenderMessage ''UniWorX ''NotificationTriggerKind $ ("NotificationTriggerKind" <>) . mconcat . drop 1 . splitCamel +instance RenderMessage UniWorX NotificationTriggerKind where + renderMessage f ls = \case + NTKAll -> mr MsgNotificationTriggerKindAll + NTKCourseParticipant -> mr MsgNotificationTriggerKindCourseParticipant + NTKExamParticipant -> mr MsgNotificationTriggerKindExamParticipant + NTKCorrector -> mr MsgNotificationTriggerKindCorrector + NTKAllocationStaff -> mr MsgNotificationTriggerKindAllocationStaff + NTKFunctionary SchoolAdmin -> mr MsgNotificationTriggerKindAdmin + NTKFunctionary SchoolLecturer -> mr MsgNotificationTriggerKindLecturer + NTKFunctionary SchoolExamOffice -> mr MsgNotificationTriggerKindExamOffice + NTKFunctionary SchoolEvaluation -> mr MsgNotificationTriggerKindEvaluation + where + mr = renderMessage f ls makeSettingForm :: Maybe SettingsForm -> Form SettingsForm @@ -55,38 +74,36 @@ makeSettingForm template html = do & setTooltip MsgWarningDaysTip ) (stgWarningDays <$> template) <* aformSection MsgFormNotifications + <*> schoolsForm (stgSchools <$> template) <*> notificationForm (stgNotificationSettings <$> template) return (result, widget) -- no validation required here where themeList = [Option (toMessage t) t (toPathPiece t) | t <- universeF] --- --- Version with proper grouping: --- --- makeSettingForm :: Maybe SettingsForm -> Form SettingsForm --- makeSettingForm template = identForm FIDsettings $ \html -> do --- (result, widget) <- flip (renderAForm FormStandard) html $ settingsFormT5T2 --- <$> aFormGroup "Cosmetics" cosmeticsForm --- <*> aFormGroup "Notifications" notificationsForm --- <* submitButton --- return (result, widget) -- no validation required here --- where --- settingsFormT5T2 :: (Int,Theme,DateTimeFormat,DateTimeFormat,DateTimeFormat) -> (Bool,NotificationSettings) -> SettingsForm --- settingsFormT5T2 = $(uncurryN 2) . $(uncurryN 5) SettingsForm --- themeList = [Option (display t) t (toPathPiece t) | t <- universeF] --- cosmeticsForm = (,,,,) --- <$> areq (natFieldI $ MsgNatField "Favoriten") -- TODO: natFieldI not working here --- (fslpI MsgFavoriten "Anzahl Favoriten") (stgMaxFavourties <$> template) --- <*> areq (selectField . return $ mkOptionList themeList) --- (fslI MsgTheme) { fsId = Just "theme-select" } (stgTheme <$> template) --- <*> areq (selectField $ dateTimeFormatOptions SelFormatDateTime) (fslI MsgDateTimeFormat) (stgDateTime <$> template) --- <*> areq (selectField $ dateTimeFormatOptions SelFormatDate) (fslI MsgDateFormat) (stgDate <$> template) --- <*> areq (selectField $ dateTimeFormatOptions SelFormatTime) (fslI MsgTimeFormat) (stgTime <$> template) --- notificationsForm = (,) --- <$> areq checkBoxField (fslI MsgDownloadFiles --- & setTooltip MsgDownloadFilesTip --- ) (stgDownloadFiles <$> template) --- <*> (NotificationSettings <$> funcForm nsForm (fslI MsgNotificationSettings) True) --- nsForm nt = fromMaybe False <$> aopt checkBoxField (fslI nt) (Just $ flip notificationAllowed nt . stgNotificationSettings <$> template) + +schoolsForm :: Maybe (Set SchoolId) -> AForm Handler (Set SchoolId) +schoolsForm template = formToAForm $ schoolsFormView =<< renderWForm FormStandard schoolsForm' mempty + where + schoolsForm' :: WForm Handler (FormResult (Set SchoolId)) + schoolsForm' = do + allSchools <- liftHandlerT . runDB $ selectList [] [Asc SchoolName] + + let + schoolForm (Entity ssh School{schoolName}) + = fmap (bool Set.empty $ Set.singleton ssh) <$> wpopt checkBoxField (fsl $ CI.original schoolName) (Set.member ssh <$> template) + + fold <$> mapM schoolForm allSchools + + schoolsFormView :: (FormResult (Set SchoolId), Widget) -> MForm Handler (FormResult (Set SchoolId), [FieldView UniWorX]) + schoolsFormView (res, fvInput) = do + mr <- getMessageRender + let fvLabel = toHtml $ mr MsgUserSchools + fvTooltip = Just . toHtml $ mr MsgUserSchoolsTip + fvRequired = False + fvErrors + | FormFailure (err : _) <- res = Just $ toHtml err + | otherwise = Nothing + fvId <- newIdent + return (res, pure FieldView{..}) notificationForm :: Maybe NotificationSettings -> AForm Handler NotificationSettings notificationForm template = wFormToAForm $ do @@ -99,13 +116,10 @@ notificationForm template = wFormToAForm $ do | isAdmin = return False | Just uid <- mbUid - , NTKAdmin <- nt - = fmap not . E.selectExists . E.from $ \userAdmin -> - E.where_ $ userAdmin E.^. UserAdminUser E.==. E.val uid - | Just uid <- mbUid - , NTKLecturer <- nt - = fmap not . E.selectExists . E.from $ \userLecturer -> - E.where_ $ userLecturer E.^. UserLecturerUser E.==. E.val uid + , NTKFunctionary f <- nt + = fmap not . E.selectExists . E.from $ \userFunction -> + E.where_ $ userFunction E.^. UserFunctionUser E.==. E.val uid + E.&&. userFunction E.^. UserFunctionFunction E.==. E.val f | Just uid <- mbUid , NTKCorrector <- nt = fmap not . E.selectExists . E.from $ \sheetCorrector -> @@ -137,17 +151,22 @@ notificationForm template = wFormToAForm $ do = apopt checkBoxField (fslI nt) (flip notificationAllowed nt <$> template) ntSection = \case - NTSubmissionRatedGraded -> Just NTKCourseParticipant - NTSubmissionRated -> Just NTKCourseParticipant - NTSheetActive -> Just NTKCourseParticipant - NTSheetSoonInactive -> Just NTKCourseParticipant - NTSheetInactive -> Just NTKLecturer - NTCorrectionsAssigned -> Just NTKCorrector - NTCorrectionsNotDistributed -> Just NTKLecturer - NTUserRightsUpdate -> Just NTKAll - NTUserAuthModeUpdate -> Just NTKAll - NTExamResult -> Just NTKExamParticipant - -- _other -> Nothing + NTSubmissionRatedGraded -> Just NTKCourseParticipant + NTSubmissionRated -> Just NTKCourseParticipant + NTSheetActive -> Just NTKCourseParticipant + NTSheetSoonInactive -> Just NTKCourseParticipant + NTSheetInactive -> Just $ NTKFunctionary SchoolLecturer + NTCorrectionsAssigned -> Just NTKCorrector + NTCorrectionsNotDistributed -> Just $ NTKFunctionary SchoolLecturer + NTUserRightsUpdate -> Just NTKAll + NTUserAuthModeUpdate -> Just NTKAll + NTExamResult -> Just NTKExamParticipant + NTAllocationStaffRegister -> Just $ NTKFunctionary SchoolLecturer + NTAllocationAllocation -> Just NTKAllocationStaff + NTAllocationRegister -> Just NTKAll + NTAllocationOutdatedRatings -> Just NTKAllocationStaff + NTAllocationUnratedApplications -> Just NTKAllocationStaff + -- _other -> Nothing forcedTriggers = [NTUserRightsUpdate, NTUserAuthModeUpdate] @@ -177,6 +196,12 @@ getProfileR, postProfileR :: Handler Html getProfileR = postProfileR postProfileR = do (uid, User{..}) <- requireAuthPair + userSchools <- fmap (setOf $ folded . _Value) . runDB . E.select . E.from $ \school -> do + E.where_ . E.exists . E.from $ \userSchool -> + E.where_ $ E.not_ (userSchool E.^. UserSchoolIsOptOut) + E.&&. userSchool E.^. UserSchoolUser E.==. E.val uid + E.&&. userSchool E.^. UserSchoolSchool E.==. school E.^. SchoolId + return $ school E.^. SchoolId let settingsTemplate = Just SettingsForm { stgMaxFavourties = userMaxFavourites , stgTheme = userTheme @@ -184,6 +209,7 @@ postProfileR = do , stgDate = userDateFormat , stgTime = userTimeFormat , stgDownloadFiles = userDownloadFiles + , stgSchools = userSchools , stgNotificationSettings = userNotificationSettings , stgWarningDays = userWarningDays } @@ -207,6 +233,25 @@ postProfileR = do , OffsetBy stgMaxFavourties ] mapM_ delete oldFavs + let + symDiff = (stgSchools `Set.difference` userSchools) `Set.union` (userSchools `Set.difference` stgSchools) + forM_ symDiff $ \ssh -> if + | ssh `Set.member` stgSchools + -> void $ upsert UserSchool + { userSchoolSchool = ssh + , userSchoolUser = uid + , userSchoolIsOptOut = False + } + [ UserSchoolIsOptOut =. False + ] + | otherwise + -> void $ upsert UserSchool + { userSchoolSchool = ssh + , userSchoolUser = uid + , userSchoolIsOptOut = True + } + [ UserSchoolIsOptOut =. True + ] addMessageI Info MsgSettingsUpdate redirect $ ProfileR :#: ProfileSettings @@ -255,14 +300,7 @@ getProfileDataR = do makeProfileData :: Entity User -> DB Widget makeProfileData (Entity uid User{..}) = do -- MsgRenderer mr <- getMsgRenderer - admin_rights <- E.select $ E.from $ \(adright `E.InnerJoin` school) -> do - E.where_ $ adright E.^. UserAdminUser E.==. E.val uid - E.on $ adright E.^. UserAdminSchool E.==. school E.^. SchoolId - return (school E.^. SchoolShorthand) - lecturer_rights <- E.select $ E.from $ \(lecright `E.InnerJoin` school) -> do - E.where_ $ lecright E.^. UserLecturerUser E.==. E.val uid - E.on $ lecright E.^. UserLecturerSchool E.==. school E.^. SchoolId - return (school E.^. SchoolShorthand) + functions <- Map.fromListWith Set.union . map (\(Entity _ UserFunction{..}) -> (userFunctionFunction, Set.singleton userFunctionSchool)) <$> selectList [UserFunctionUser ==. uid] [] lecture_corrector <- E.select $ E.distinct $ E.from $ \(sheet `E.InnerJoin` corrector `E.InnerJoin` course) -> do E.on $ sheet E.^. SheetCourse E.==. course E.^. CourseId E.on $ sheet E.^. SheetId E.==. corrector E.^. SheetCorrectorSheet @@ -314,7 +352,7 @@ mkOwnedCoursesTable = return $ indicatorCell -- return True if one cell is produced here `mappend` termCell tid , sortable (Just "school") (i18nCell MsgCourseSchool) $ - schoolCell <$> view (_dbrOutput . _1 . re _Just) + schoolCell <$> view (_dbrOutput . _1) <*> view (_dbrOutput . _2 ) , sortable (Just "course") (i18nCell MsgCourse) $ courseCellCL <$> view _dbrOutput @@ -362,8 +400,8 @@ mkEnrolledCoursesTable = , sortable (Just "term") (i18nCell MsgTerm) $ termCell <$> view (_dbrOutput . _1 . _entityVal . _courseTerm) , sortable (Just "school") (i18nCell MsgCourseSchool) . magnify (_dbrOutput . _1 . _entityVal) $ - schoolCell <$> view ( _courseTerm . re _Just) - <*> view _courseSchool + schoolCell <$> view _courseTerm + <*> view _courseSchool , sortable (Just "course") (i18nCell MsgCourse) $ courseCell <$> view (_dbrOutput . _1 . _entityVal) , sortable (Just "time") (i18nCell MsgRegistered) $ do @@ -430,7 +468,7 @@ mkSubmissionTable = , sortable (Just "term") (i18nCell MsgTerm) $ termCell <$> view (_dbrOutput . _1 . _1) , sortable (Just "school") (i18nCell MsgCourseSchool) . magnify (_dbrOutput . _1 ) $ - schoolCell <$> view ( _1. re _Just) + schoolCell <$> view _1 <*> view _2 , sortable (Just "course") (i18nCell MsgCourse) $ courseCellCL <$> view (_dbrOutput . _1) @@ -512,7 +550,7 @@ mkSubmissionGroupTable = , sortable (Just "term") (i18nCell MsgTerm) $ termCell <$> view (_dbrOutput . _1 . _1) , sortable (Just "school") (i18nCell MsgCourseSchool) . magnify (_dbrOutput . _1 ) $ - schoolCell <$> view ( _1. re _Just) + schoolCell <$> view _1 <*> view _2 , sortable (Just "course") (i18nCell MsgCourse) $ courseCellCL <$> view (_dbrOutput . _1) diff --git a/src/Handler/School.hs b/src/Handler/School.hs index 9dad647e0..c743dfae2 100644 --- a/src/Handler/School.hs +++ b/src/Handler/School.hs @@ -1,10 +1,169 @@ module Handler.School where import Import +import Handler.Utils +import Handler.Utils.Table.Columns + +import qualified Database.Esqueleto as E + +import qualified Data.Set as Set +import qualified Data.CaseInsensitive as CI +import qualified Data.Text as Text + getSchoolListR :: Handler Html -getSchoolListR = error "getSchoolListR: Not implemented" +getSchoolListR = do + let + schoolLink :: SchoolId -> SomeRoute UniWorX + schoolLink ssh = SomeRoute $ SchoolR ssh SchoolEditR + + dbtSQLQuery :: E.SqlExpr (Entity School) -> E.SqlQuery _ + dbtSQLQuery = return -getSchoolShowR :: SchoolId -> Handler Html -getSchoolShowR = error "getSchoolShowR: Not implemented" + dbtProj :: DBRow _ -> MaybeT (YesodDB UniWorX) (DBRow (Entity School)) + dbtProj = return + dbtRowKey = (E.^. SchoolId) + + dbtColonnade :: Colonnade Sortable _ _ + dbtColonnade = mconcat + [ colSchoolShort $ _dbrOutput . _entityKey + , anchorColonnade (views (_dbrOutput . _entityKey) schoolLink) $ colSchoolName (_dbrOutput . _entityVal . _schoolName) + ] + + dbtSorting = mconcat + [ sortSchoolShort $ to (E.^. SchoolId) + , sortSchoolName $ to (E.^. SchoolName) + ] + + dbtFilter = mempty + dbtFilterUI = mempty + + dbtStyle = def + dbtParams = def + + dbtCsvEncode = noCsvEncode + dbtCsvDecode = Nothing + + dbtIdent :: Text + dbtIdent = "schools" + + psValidator = def + & defaultSorting [SortAscBy "school-name"] + + + table <- runDB $ dbTableWidget' psValidator DBTable{..} + + let title = MsgMenuSchoolList + siteLayoutMsg title $ do + setTitleI title + table + +data SchoolForm = SchoolForm + { sfShorthand :: CI Text + , sfName :: CI Text + , sfOrgUnits :: Set (CI Text) + } + +mkSchoolForm :: Maybe SchoolId -> Maybe SchoolForm -> Form SchoolForm +mkSchoolForm mSsh template = renderAForm FormStandard $ SchoolForm + <$> maybe (\f fs -> areq f fs (sfShorthand <$> template)) (\ssh f fs -> aforced f fs (unSchoolKey ssh)) mSsh ciField (fslI MsgSchoolShort) + <*> areq ciField (fslI MsgSchoolName) (sfName <$> template) + <*> (Set.fromList . mapMaybe (fmap CI.mk . assertM' (not . Text.null) . Text.strip) <$> massInputListA (textField & addDatalist ldapOrgs) (const "") (const Nothing) ("ldap-organisations" :: Text) (fslI MsgSchoolLdapOrganisations & setTooltip MsgSchoolLdapOrganisationsTip) False (fmap CI.original . Set.toList . sfOrgUnits <$> template)) + where + ldapOrgs :: WidgetT UniWorX IO (Set (CI Text)) + ldapOrgs = liftHandlerT . runDB $ + setOf (folded . _entityVal . _schoolLdapOrgUnit) <$> selectList [] [] + +schoolToForm :: SchoolId -> DB (Form SchoolForm) +schoolToForm ssh = do + School{..} <- get404 ssh + ldapFrags <- selectList [SchoolLdapSchool ==. Just ssh] [] + return . mkSchoolForm (Just ssh) $ Just SchoolForm + { sfShorthand = schoolShorthand + , sfName = schoolName + , sfOrgUnits = setOf (folded . _entityVal . _schoolLdapOrgUnit) ldapFrags + } + + +getSchoolEditR, postSchoolEditR :: SchoolId -> Handler Html +getSchoolEditR = postSchoolEditR +postSchoolEditR ssh = do + sForm <- runDB $ schoolToForm ssh + + ((sfResult, sfView), sfEnctype) <- runFormPost sForm + + formResult sfResult $ \SchoolForm{..} -> do + runDB $ do + update ssh [ SchoolName =. sfName ] + forM_ sfOrgUnits $ \schoolLdapOrgUnit -> + void $ upsert SchoolLdap + { schoolLdapSchool = Just ssh + , .. + } + [ SchoolLdapSchool =. Just ssh + ] + deleteWhere [SchoolLdapSchool ==. Just ssh, SchoolLdapOrgUnit /<-. Set.toList sfOrgUnits] + addMessageI Success $ MsgSchoolUpdated ssh + redirect $ SchoolR ssh SchoolEditR + + let sfView' = wrapForm sfView FormSettings + { formMethod = POST + , formAction = Just . SomeRoute $ SchoolR ssh SchoolEditR + , formEncoding = sfEnctype + , formAttrs = [] + , formSubmit = FormSubmit + , formAnchor = Nothing :: Maybe Text + } + + siteLayoutMsg (MsgSchoolTitle ssh) $ do + setTitleI $ MsgSchoolTitle ssh + sfView' + +getSchoolNewR, postSchoolNewR :: Handler Html +getSchoolNewR = postSchoolNewR +postSchoolNewR = do + uid <- requireAuthId + ((sfResult, sfView), sfEnctype) <- runFormPost $ mkSchoolForm Nothing Nothing + + formResult sfResult $ \SchoolForm{..} -> do + let ssh = SchoolKey sfShorthand + insertOkay <- runDB $ do + didInsert <- is _Just <$> insertUnique School + { schoolShorthand = sfShorthand + , schoolName = sfName + } + when didInsert $ do + insert_ UserFunction + { userFunctionUser = uid + , userFunctionSchool = ssh + , userFunctionFunction = SchoolAdmin + } + forM_ sfOrgUnits $ \schoolLdapOrgUnit -> + void $ upsert SchoolLdap + { schoolLdapSchool = Just ssh + , .. + } + [ SchoolLdapSchool =. Just ssh + ] + return didInsert + + if + | insertOkay -> do + addMessageI Success $ MsgSchoolCreated ssh + redirect $ SchoolR ssh SchoolEditR + | otherwise + -> addMessageI Error $ MsgSchoolExists ssh + + let sfView' = wrapForm sfView FormSettings + { formMethod = POST + , formAction = Just $ SomeRoute SchoolNewR + , formEncoding = sfEnctype + , formAttrs = [] + , formSubmit = FormSubmit + , formAnchor = Nothing :: Maybe Text + } + + siteLayoutMsg MsgTitleSchoolNew $ do + setTitleI MsgTitleSchoolNew + sfView' diff --git a/src/Handler/Sheet.hs b/src/Handler/Sheet.hs index 5ee6ba68f..96f2ec55f 100644 --- a/src/Handler/Sheet.hs +++ b/src/Handler/Sheet.hs @@ -902,7 +902,7 @@ correctorInvitationConfig = InvitationConfig{..} Course{..} <- get404 sheetCourse return . SomeMessage $ MsgMailSubjectCorrectorInvitation courseTerm courseSchool courseShorthand sheetName invitationHeading (Entity _ Sheet{..}) _ = return . SomeMessage $ MsgSheetCorrInviteHeading sheetName - invitationExplanation _ _ = [ihamlet|_{SomeMessage MsgSheetCorrInviteExplanation}|] + invitationExplanation _ _ = return [ihamlet|_{SomeMessage MsgSheetCorrInviteExplanation}|] invitationTokenConfig _ _ = do itAuthority <- liftHandlerT requireAuthId return $ InvitationTokenConfig itAuthority Nothing Nothing Nothing diff --git a/src/Handler/Submission.hs b/src/Handler/Submission.hs index b77e8c5f3..2cba120df 100644 --- a/src/Handler/Submission.hs +++ b/src/Handler/Submission.hs @@ -100,7 +100,7 @@ submissionUserInvitationConfig = InvitationConfig{..} invitationHeading (Entity _ Submission{..}) _ = do Sheet{..} <- getJust submissionSheet return . SomeMessage $ MsgSubmissionUserInviteHeading sheetName - invitationExplanation _ _ = [ihamlet|_{SomeMessage MsgSubmissionUserInviteExplanation}|] + invitationExplanation _ _ = return [ihamlet|_{SomeMessage MsgSubmissionUserInviteExplanation}|] invitationTokenConfig (Entity _ Submission{..}) _ = do Sheet{..} <- getJust submissionSheet Course{..} <- getJust sheetCourse diff --git a/src/Handler/Term.hs b/src/Handler/Term.hs index 26cba329a..ba4be993c 100644 --- a/src/Handler/Term.hs +++ b/src/Handler/Term.hs @@ -257,7 +257,14 @@ newTermForm template html = do = aforced termNewField (fslpI MsgTerm (mr MsgTermPlaceholder)) tid | otherwise = areq termNewField (fslpI MsgTerm (mr MsgTermPlaceholder)) Nothing - holidayForm = formToAForm . over (mapped._2) pure $ massInputList dayField (const $ "" & addPlaceholder (mr MsgTermHolidayPlaceholder)) (const Nothing) ("holidays" :: Text) (fslI MsgTermHolidays & setTooltip MsgMassInputTip) True (tftHolidays template) mempty + holidayForm = massInputListA + dayField + (const $ "" & addPlaceholder (mr MsgTermHolidayPlaceholder)) + (const Nothing) + ("holidays" :: Text) + (fslI MsgTermHolidays & setTooltip MsgMassInputTip) + True + (tftHolidays template) (result, widget) <- flip (renderAForm FormStandard) html $ Term <$> tidForm <*> areq dayField (fslI MsgTermStartDay & setTooltip MsgTermStartDayTooltip) (tftStart template) diff --git a/src/Handler/Tutorial.hs b/src/Handler/Tutorial.hs index 4cfc64503..4f3799955 100644 --- a/src/Handler/Tutorial.hs +++ b/src/Handler/Tutorial.hs @@ -259,7 +259,7 @@ tutorInvitationConfig = InvitationConfig{..} Course{..} <- get404 tutorialCourse return . SomeMessage $ MsgMailSubjectTutorInvitation courseTerm courseSchool courseShorthand tutorialName invitationHeading (Entity _ Tutorial{..}) _ = return . SomeMessage $ MsgTutorInviteHeading tutorialName - invitationExplanation _ _ = [ihamlet|_{SomeMessage MsgTutorInviteExplanation}|] + invitationExplanation _ _ = return [ihamlet|_{SomeMessage MsgTutorInviteExplanation}|] invitationTokenConfig _ _ = do itAuthority <- liftHandlerT requireAuthId return $ InvitationTokenConfig itAuthority Nothing Nothing Nothing diff --git a/src/Handler/Users.hs b/src/Handler/Users.hs index a8df63296..a9d46dfae 100644 --- a/src/Handler/Users.hs +++ b/src/Handler/Users.hs @@ -10,6 +10,7 @@ import Handler.Utils import Handler.Utils.Tokens import Handler.Utils.Users import Handler.Utils.Invitations +import Handler.Utils.Table.Cells import qualified Auth.LDAP as Auth @@ -31,11 +32,10 @@ import Text.Hamlet (ihamlet) import Data.Aeson hiding (Result(..)) -hijackUserForm :: CryptoUUIDUser -> Form () -hijackUserForm cID csrf = do - (uidResult, uidView) <- mforced hiddenField "" (cID :: CryptoUUIDUser) - (btnResult, btnView) <- mreq (buttonField BtnHijack) "" Nothing - return (() <$ uidResult <* btnResult, mconcat [toWidget csrf, fvInput uidView, fvInput btnView]) +hijackUserForm :: Form () +hijackUserForm csrf = do + (btnResult, btnView) <- mopt (buttonField BtnHijack) "" Nothing + return (btnResult >>= guard . is _Just, mconcat [toWidget csrf, fvInput btnView]) -- In case of refactoring, use this: -- instance HasEntity (DBRow (Entity User)) User where @@ -43,11 +43,21 @@ hijackUserForm cID csrf = do -- instance HasUser (DBRow (Entity USer)) where -- hasUser = _entityVal -getUsersR :: Handler Html -getUsersR = do +data UserAction = UserLdapSync | UserHijack + deriving (Eq, Ord, Enum, Bounded, Read, Show, Generic, Typeable) + +instance Universe UserAction +instance Finite UserAction +nullaryPathPiece ''UserAction $ camelToPathPiece' 1 +embedRenderMessage ''UniWorX ''UserAction id + +getUsersR, postUsersR :: Handler Html +getUsersR = postUsersR +postUsersR = do let - dbtColonnade = dbColonnade . mconcat $ + dbtColonnade = mconcat $ [ dbRow + , dbSelect (applying _2) id (return . view (_dbrOutput . _entityKey)) , sortable (Just "name") (i18nCell MsgName) $ \DBRow{ dbrOutput = Entity uid User{..} } -> anchorCellM (AdminUserR <$> encrypt uid) (nameWidget userDisplayName userSurname) @@ -58,53 +68,58 @@ getUsersR = do -- (AdminUserR <$> encrypt uid) -- (toWidget . display $ last $ impureNonNull $ words $ userDisplayName) , sortable (Just "auth-ldap") (i18nCell MsgAuthMode) $ \DBRow{ dbrOutput = Entity _ User{..} } -> i18nCell userAuthentication - , sortable Nothing (i18nCell MsgAdminFor) $ \DBRow{ dbrOutput = Entity uid _ } -> flip (set' cellContents) mempty $ do - schools <- lift . E.select . E.from $ \(school `E.InnerJoin` userAdmin) -> do - E.on $ school E.^. SchoolId E.==. userAdmin E.^. UserAdminSchool - E.where_ $ userAdmin E.^. UserAdminUser E.==. E.val uid - E.orderBy [E.asc $ school E.^. SchoolShorthand] - return $ school E.^. SchoolShorthand - return [whamlet| - $newline never -