Merge branch 'master' of gitlab2.rz.ifi.lmu.de:uni2work/uni2work
This commit is contained in:
commit
e4393972f8
4
.gitignore
vendored
4
.gitignore
vendored
@ -35,5 +35,7 @@ test.log
|
||||
/.stack-work.lock
|
||||
/.npmrc
|
||||
/config/webpack.yml
|
||||
static/wp-*/
|
||||
tunnel.log
|
||||
/static
|
||||
/well-known
|
||||
/**/tmp-*
|
||||
|
||||
@ -33,10 +33,13 @@ npm install:
|
||||
- n stable
|
||||
- npm install -g npm
|
||||
- hash -r
|
||||
- apt-get install openssh-client -y
|
||||
- apt-get -y install openssh-client exiftool
|
||||
- install -v -m 0700 -d ~/.ssh
|
||||
- install -v -T -m 0644 ${SSH_KNOWN_HOSTS} ~/.ssh/known_hosts
|
||||
- install -v -T -m 0400 ${SSH_DEPLOY_KEY} ~/.ssh/deploy && echo "IdentityFile ~/.ssh/deploy" >> ~/.ssh/config;
|
||||
after_script:
|
||||
- zip -qr node_modules.zip node_modules
|
||||
- du -hs node_modules node_modules.zip
|
||||
artifacts:
|
||||
paths:
|
||||
- node_modules/
|
||||
@ -54,6 +57,7 @@ frontend:build:
|
||||
artifacts:
|
||||
paths:
|
||||
- static
|
||||
- well-known
|
||||
- config/webpack.yml
|
||||
name: "${CI_JOB_NAME}"
|
||||
expire_in: "1 day"
|
||||
|
||||
39
CHANGELOG.md
39
CHANGELOG.md
@ -2,6 +2,45 @@
|
||||
|
||||
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.
|
||||
|
||||
## [10.3.0](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v10.2.0...v10.3.0) (2020-01-12)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix app frontend test ([49bafe1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/49bafe1))
|
||||
* improve exam occurrence ui ([83fa9c9](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/83fa9c9))
|
||||
* improve labeling of button to switch exam occurrence ([727b89b](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/727b89b))
|
||||
* tweak debouncing & canceling ([6b51cc5](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/6b51cc5))
|
||||
* **async-table:** bind callback in updateTableFrom call ([cd3e72c](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/cd3e72c))
|
||||
* **util-registry:** fix initAll and tests ([2620fb2](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/2620fb2))
|
||||
* **util-registry:** start setup instances and not all active instances ([ddf94bf](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/ddf94bf))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* support exam registration including room (ExamRoomFifo) ([14bb020](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/14bb020))
|
||||
* well known files ([068632b](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/068632b))
|
||||
* **async-table:** no submit on locked inputs ([22b3780](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/22b3780))
|
||||
* **frontend:** split up util registry ([67e472f](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/67e472f))
|
||||
* **util-registry:** more debug info for setup util instances ([00584f9](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/00584f9))
|
||||
|
||||
|
||||
|
||||
## [10.2.0](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v10.1.0...v10.2.0) (2020-01-07)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* divide by zero ([674b949](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/674b949))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* generate & include new favicon ([b78c484](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/b78c484))
|
||||
* **config:** improve configurability of VerpMode ([a7c3fe7](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/a7c3fe7))
|
||||
|
||||
|
||||
|
||||
## [10.1.0](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v10.0.1...v10.1.0) (2019-12-23)
|
||||
|
||||
|
||||
|
||||
29
assets/favicon.svg
Normal file
29
assets/favicon.svg
Normal file
@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
id="svg4"
|
||||
version="1.1"
|
||||
viewBox="0 0 576 512">
|
||||
<metadata
|
||||
id="metadata10">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs8" />
|
||||
<path
|
||||
style="stroke-width:0.89999998;fill:#0a9342;fill-opacity:1"
|
||||
id="path2"
|
||||
d="m 516.7975,54.444679 c -49.32,2.799 -147.348,12.987 -207.864,50.031001 -4.176,2.556 -6.543,7.101 -6.543,11.853 v 327.483 c 0,10.395 11.367,16.965 20.952,12.141 62.262,-31.338 152.307,-39.888 196.83,-42.228 15.201,-0.801 27.018,-12.987 27.018,-27.594 V 82.074679 c 0.009,-15.939 -13.815,-28.566 -30.393,-27.63 z M 267.0565,104.47568 C 206.5495,67.431679 108.5215,57.252679 59.2015,54.444679 c -16.578,-0.936 -30.402,11.691 -30.402,27.63 V 386.13968 c 0,14.616 11.817,26.802 27.018,27.594 44.541,2.34 134.631,10.899 196.893,42.255 9.558,4.815 20.889,-1.746 20.889,-12.114 v -327.708 c 0,-4.761 -2.358,-9.126 -6.543,-11.691 z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
77
config/favicon.json
Normal file
77
config/favicon.json
Normal file
@ -0,0 +1,77 @@
|
||||
{
|
||||
"masterPicture": "assets/favicon.svg",
|
||||
"design": {
|
||||
"desktop_browser": {},
|
||||
"ios": {
|
||||
"picture_aspect": "background_and_margin",
|
||||
"margin": "5%",
|
||||
"background_color": "#ffffff",
|
||||
"startup_image": {
|
||||
"background_color": "#ffffff"
|
||||
},
|
||||
"app_name": "Uni2work",
|
||||
"assets": {
|
||||
"ios6_and_prior_icons": false,
|
||||
"ios7_and_later_icons": true,
|
||||
"precomposed_icons": true,
|
||||
"declare_only_default_icon": true
|
||||
}
|
||||
},
|
||||
"windows": {
|
||||
"picture_aspect": "white_silhouette",
|
||||
"background_color": "#0a9342",
|
||||
"app_name": "Uni2work"
|
||||
},
|
||||
"firefox_app": {
|
||||
"picture_aspect": "circle",
|
||||
"keep_picture_in_circle": false,
|
||||
"circle_inner_margin": "5%",
|
||||
"background_color": "#ffffff",
|
||||
"overlay": false,
|
||||
"manifest": {
|
||||
"app_name": "Uni2work",
|
||||
"app_description": {
|
||||
"_i18n": true,
|
||||
"de-de-formal": "Ein webbasiertes Lehrverwaltungssystem der LMU München",
|
||||
"en-eu": "A web based teaching management system at LMU Munich"
|
||||
},
|
||||
"developer_name": "Uni2work-Team",
|
||||
"developer_url": "https://uni2work.ifi.lmu.de/info",
|
||||
"display": "browser",
|
||||
"start_url": "/"
|
||||
}
|
||||
},
|
||||
"android_chrome": {
|
||||
"picture_aspect": "shadow",
|
||||
"manifest": {
|
||||
"name": "Uni2work",
|
||||
"display": "browser",
|
||||
"orientation": "portrait",
|
||||
"start_url": "/"
|
||||
},
|
||||
"assets": {
|
||||
"legacy_icon": true,
|
||||
"low_resolution_icons": false
|
||||
}
|
||||
},
|
||||
"safari_pinned_tab": {
|
||||
"picture_aspect": "silhouette",
|
||||
"theme_color": "#0a9342"
|
||||
},
|
||||
"coast": {
|
||||
"picture_aspect": "background_and_margin",
|
||||
"background_color": "#ffffff",
|
||||
"margin": "10%"
|
||||
},
|
||||
"open_graph": {
|
||||
"picture_aspect": "background_and_margin",
|
||||
"background_color": "#ffffff",
|
||||
"margin": "10%",
|
||||
"ratio": "square"
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"html_code_file": true
|
||||
},
|
||||
"versioning": false
|
||||
}
|
||||
@ -43,6 +43,8 @@ application/java-vm class
|
||||
application/javascript js
|
||||
application/json json
|
||||
application/jsonml+json jsonml
|
||||
application/manifest+json webmanifest
|
||||
application/x-web-app-manifest+json webapp
|
||||
application/lost+xml lostxml
|
||||
application/mac-binhex40 hqx
|
||||
application/mac-compactpro cpt
|
||||
|
||||
@ -4,6 +4,9 @@
|
||||
# See https://github.com/yesodweb/yesod/wiki/Configuration#parsing-numeric-values-as-strings
|
||||
|
||||
static-dir: "_env:STATIC_DIR:static"
|
||||
well-known-dir: "_env:WELL_KNOWN_DIR:well-known"
|
||||
well-known-link-file: "html_code.html"
|
||||
|
||||
webpack-manifest: "_env:WEBPACK_MANIFEST:config/webpack.yml"
|
||||
host: "_env:HOST:*4" # any IPv4 host
|
||||
port: "_env:PORT:3000"
|
||||
@ -14,8 +17,8 @@ mail-from:
|
||||
email: "_env:MAILFROM_EMAIL:uniworx@localhost"
|
||||
mail-object-domain: "_env:MAILOBJECT_DOMAIN:localhost"
|
||||
mail-verp:
|
||||
separator: "+"
|
||||
at-replacement: "="
|
||||
separator: "_env:VERP_SEPARATOR:+"
|
||||
at-replacement: "_env:VERP_AT_REPLACEMENT:="
|
||||
mail-support:
|
||||
name: "_env:MAILSUPPORT_NAME:"
|
||||
email: "_env:MAILSUPPORT:uni2work@ifi.lmu.de"
|
||||
|
||||
@ -15,7 +15,7 @@ export class App {
|
||||
constructor() {
|
||||
this.utilRegistry.setApp(this);
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => this.utilRegistry.setupAll());
|
||||
document.addEventListener('DOMContentLoaded', () => this.utilRegistry.initAll());
|
||||
}
|
||||
|
||||
registerUtilities(utils) {
|
||||
|
||||
@ -23,10 +23,10 @@ describe('App', () => {
|
||||
expect(global.App).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should setup all utlites when page is done loading', () => {
|
||||
spyOn(global.App.utilRegistry, 'setupAll');
|
||||
it('should init all utlites when page is done loading', () => {
|
||||
spyOn(global.App.utilRegistry, 'initAll');
|
||||
document.dispatchEvent(new Event('DOMContentLoaded'));
|
||||
expect(global.App.utilRegistry.setupAll).toHaveBeenCalled();
|
||||
expect(global.App.utilRegistry.initAll).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
describe('provides services', () => {
|
||||
|
||||
@ -13,12 +13,17 @@ export class UtilRegistry {
|
||||
* name: string | utils name, e.g. 'example'
|
||||
* selector: string | utils selector, e.g. '[uw-example]'
|
||||
* setup: Function | utils setup function, see below
|
||||
*
|
||||
* optional util properties:
|
||||
* start: Function | utils start function, see below
|
||||
*
|
||||
* setup function must return instance object with at least these properties:
|
||||
* name: string | utils name
|
||||
* element: HTMLElement | element the util is applied to
|
||||
* destroy: Function | function to destroy the util and remove any listeners
|
||||
*
|
||||
* (optional) start function for registering event listeners
|
||||
*
|
||||
* @param util Object Utility that should be added to the registry
|
||||
*/
|
||||
register(util) {
|
||||
@ -45,13 +50,26 @@ export class UtilRegistry {
|
||||
this._appInstance = appInstance;
|
||||
}
|
||||
|
||||
setupAll(scope) {
|
||||
initAll(scope) {
|
||||
let startedInstances = [];
|
||||
const setupInstances = this._registeredUtils.map((util) => this.setup(util, scope)).flat();
|
||||
|
||||
setupInstances.forEach((utilInstance) => {
|
||||
if (utilInstance) {
|
||||
const instance = utilInstance.instance;
|
||||
if (instance && typeof instance.start === 'function') {
|
||||
instance.start();
|
||||
startedInstances.push(instance);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (DEBUG_MODE > 1) {
|
||||
console.info('registered js utilities:');
|
||||
console.table(this._registeredUtils);
|
||||
console.info('initialized js util instances:');
|
||||
console.table(setupInstances);
|
||||
}
|
||||
|
||||
this._registeredUtils.forEach((util) => this.setup(util, scope));
|
||||
return startedInstances;
|
||||
}
|
||||
|
||||
setup(util, scope = document.body) {
|
||||
@ -71,7 +89,7 @@ export class UtilRegistry {
|
||||
utilInstance = new util(element, this._appInstance);
|
||||
} catch(err) {
|
||||
if (DEBUG_MODE > 0) {
|
||||
console.warn('Error while trying to initialize a utility!', { util , element, err });
|
||||
console.error('Error while trying to initialize a utility!', { util , element, err });
|
||||
}
|
||||
}
|
||||
|
||||
@ -80,7 +98,7 @@ export class UtilRegistry {
|
||||
console.info('Got utility instance for utility "' + util.name + '"', { utilInstance });
|
||||
}
|
||||
|
||||
instances.push(utilInstance);
|
||||
instances.push({ util: util, scope: scope, element: element, instance: utilInstance });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -97,32 +97,80 @@ describe('UtilRegistry', () => {
|
||||
|
||||
const setupUtilities = utilRegistry.setup(TestUtil1, testScope);
|
||||
expect(setupUtilities).toBeTruthy();
|
||||
expect(setupUtilities[0].app).toBe(fakeApp);
|
||||
expect(setupUtilities[1].app).toBe(fakeApp);
|
||||
setupUtilities.forEach((setupUtility) => {
|
||||
expect(setupUtility).toBeTruthy();
|
||||
expect(setupUtility.instance).toBeTruthy();
|
||||
});
|
||||
expect(setupUtilities[0].instance.app).toBe(fakeApp);
|
||||
expect(setupUtilities[1].instance.app).toBe(fakeApp);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('setupAll()', () => {
|
||||
describe('initAll()', () => {
|
||||
it('should setup all the utilities', () => {
|
||||
spyOn(utilRegistry, 'setup');
|
||||
utilRegistry.register(TestUtil1);
|
||||
utilRegistry.register(TestUtil2);
|
||||
utilRegistry.setupAll();
|
||||
utilRegistry.register(TestUtil3);
|
||||
utilRegistry.initAll();
|
||||
|
||||
expect(utilRegistry.setup.calls.count()).toBe(2);
|
||||
expect(utilRegistry.setup.calls.count()).toBe(3);
|
||||
expect(utilRegistry.setup.calls.argsFor(0)).toEqual([TestUtil1, undefined]);
|
||||
expect(utilRegistry.setup.calls.argsFor(1)).toEqual([TestUtil2, undefined]);
|
||||
expect(utilRegistry.setup.calls.argsFor(2)).toEqual([TestUtil3, undefined]);
|
||||
});
|
||||
|
||||
it('should pass the given scope', () => {
|
||||
spyOn(utilRegistry, 'setup');
|
||||
utilRegistry.register(TestUtil1);
|
||||
const scope = document.createElement('div');
|
||||
utilRegistry.setupAll(scope);
|
||||
utilRegistry.initAll(scope);
|
||||
|
||||
expect(utilRegistry.setup).toHaveBeenCalledWith(TestUtil1, scope);
|
||||
});
|
||||
|
||||
describe('starts startable util instances', () => {
|
||||
let testScope,
|
||||
testElement1,
|
||||
testElement2,
|
||||
testElement3,
|
||||
testElement4;
|
||||
|
||||
beforeEach(() => {
|
||||
testScope = document.createElement('div');
|
||||
|
||||
testElement1 = document.createElement('div');
|
||||
testElement2 = document.createElement('div');
|
||||
testElement3 = document.createElement('div');
|
||||
testElement4 = document.createElement('div');
|
||||
|
||||
testElement1.classList.add('util1');
|
||||
testElement2.id = 'util2';
|
||||
testElement3.classList.add('util3');
|
||||
testElement4.classList.add('util3');
|
||||
|
||||
testScope.appendChild(testElement1);
|
||||
testScope.appendChild(testElement2);
|
||||
testScope.appendChild(testElement3);
|
||||
testScope.appendChild(testElement4);
|
||||
});
|
||||
|
||||
it('should start instances that provide a start function', () => {
|
||||
utilRegistry.register(TestUtil3);
|
||||
const initializedInstances = utilRegistry.initAll(testScope);
|
||||
|
||||
expect(initializedInstances.length).toBe(2);
|
||||
});
|
||||
|
||||
it('should not start instances that do not provide a start function', () => {
|
||||
utilRegistry.register(TestUtil1);
|
||||
utilRegistry.register(TestUtil2);
|
||||
const startedInstances = utilRegistry.initAll(testScope);
|
||||
|
||||
expect(startedInstances.length).toBe(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -138,6 +186,12 @@ class TestUtil1 {
|
||||
@Utility({ selector: '#util2' })
|
||||
class TestUtil2 { }
|
||||
|
||||
@Utility({ selector: '.util3' })
|
||||
class TestUtil3 {
|
||||
constructor() {}
|
||||
start() {}
|
||||
}
|
||||
|
||||
@Utility({ selector: '#throws' })
|
||||
class ThrowingUtil {
|
||||
constructor() {
|
||||
|
||||
@ -3,10 +3,14 @@ import { StorageManager, LOCATION } from '../../lib/storage-manager/storage-mana
|
||||
import { Datepicker } from '../form/datepicker';
|
||||
import { HttpClient } from '../../services/http-client/http-client';
|
||||
import * as debounce from 'lodash.debounce';
|
||||
import * as throttle from 'lodash.throttle';
|
||||
import './async-table-filter.sass';
|
||||
import './async-table.sass';
|
||||
|
||||
const ATTR_SUBMIT_LOCKED = 'submit-locked';
|
||||
|
||||
const INPUT_DEBOUNCE = 600;
|
||||
const FILTER_DEBOUNCE = 100;
|
||||
const HEADER_HEIGHT = 80;
|
||||
|
||||
const ASYNC_TABLE_LOCAL_STORAGE_KEY = 'ASYNC_TABLE';
|
||||
@ -33,6 +37,8 @@ export class AsyncTable {
|
||||
_scrollTable;
|
||||
_cssIdPrefix = '';
|
||||
|
||||
_cancelPendingUpdates = [];
|
||||
|
||||
_tableFilterInputs = {
|
||||
search: [],
|
||||
input: [],
|
||||
@ -58,7 +64,7 @@ export class AsyncTable {
|
||||
if (this._element.classList.contains(ASYNC_TABLE_INITIALIZED_CLASS)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// param asyncTableDbHeader
|
||||
if (this._element.dataset.asyncTableDbHeader !== undefined) {
|
||||
this._asyncTableHeader = this._element.dataset.asyncTableDbHeader;
|
||||
@ -79,9 +85,6 @@ export class AsyncTable {
|
||||
throw new Error('Async Table cannot be set up without a scrolltable element!');
|
||||
}
|
||||
|
||||
this._setupSortableHeaders();
|
||||
this._setupPagination();
|
||||
this._setupPageSizeSelect();
|
||||
this._setupTableFilter();
|
||||
|
||||
this._processStorage();
|
||||
@ -93,11 +96,18 @@ export class AsyncTable {
|
||||
this._element.classList.add(ASYNC_TABLE_INITIALIZED_CLASS);
|
||||
}
|
||||
|
||||
start() {
|
||||
this._startSortableHeaders();
|
||||
this._startPagination();
|
||||
this._startPageSizeSelect();
|
||||
this._startTableFilter();
|
||||
}
|
||||
|
||||
destroy() {
|
||||
console.log('TBD: Destroy AsyncTable');
|
||||
}
|
||||
|
||||
_setupSortableHeaders() {
|
||||
_startSortableHeaders() {
|
||||
this._ths = Array.from(this._scrollTable.querySelectorAll('th.sortable, .course-header'))
|
||||
.map((th) => ({ element: th }));
|
||||
|
||||
@ -110,7 +120,7 @@ export class AsyncTable {
|
||||
});
|
||||
}
|
||||
|
||||
_setupPagination() {
|
||||
_startPagination() {
|
||||
const pagination = this._element.querySelector('#' + this._cssIdPrefix + this._asyncTableId + '-pagination');
|
||||
if (pagination) {
|
||||
this._pageLinks = Array.from(pagination.querySelectorAll('.page-link'))
|
||||
@ -134,7 +144,7 @@ export class AsyncTable {
|
||||
}
|
||||
}
|
||||
|
||||
_setupPageSizeSelect() {
|
||||
_startPageSizeSelect() {
|
||||
// pagesize form
|
||||
this._pagesizeForm = this._element.querySelector('#' + this._cssIdPrefix + this._asyncTableId + '-pagesize-form');
|
||||
|
||||
@ -148,70 +158,88 @@ export class AsyncTable {
|
||||
const tableFilterForm = this._element.querySelector(ASYNC_TABLE_FILTER_FORM_SELECTOR);
|
||||
if (tableFilterForm) {
|
||||
this._gatherTableFilterInputs(tableFilterForm);
|
||||
}
|
||||
}
|
||||
|
||||
_startTableFilter() {
|
||||
const tableFilterForm = this._element.querySelector(ASYNC_TABLE_FILTER_FORM_SELECTOR);
|
||||
if (tableFilterForm) {
|
||||
this._addTableFilterEventListeners(tableFilterForm);
|
||||
}
|
||||
}
|
||||
|
||||
_gatherTableFilterInputs(tableFilterForm) {
|
||||
Array.from(tableFilterForm.querySelectorAll('input[type="search"]')).forEach((input) => {
|
||||
this._tableFilterInputs.search.push(input);
|
||||
Array.from(tableFilterForm.querySelectorAll('input')).forEach((input) => {
|
||||
const inputType = input.getAttribute('type');
|
||||
if (inputType === 'search') {
|
||||
this._tableFilterInputs.search.push(input);
|
||||
} else if (['text','date','time','datetime-local'].includes(inputType)) {
|
||||
this._tableFilterInputs.input.push(input);
|
||||
} else {
|
||||
this._tableFilterInputs.change.push(input);
|
||||
}
|
||||
});
|
||||
|
||||
Array.from(tableFilterForm.querySelectorAll('input[type="text"]')).forEach((input) => {
|
||||
this._tableFilterInputs.input.push(input);
|
||||
});
|
||||
|
||||
Array.from(tableFilterForm.querySelectorAll('input:not([type="text"]):not([type="search"])')).forEach((input) => {
|
||||
this._tableFilterInputs.change.push(input);
|
||||
});
|
||||
|
||||
Array.from(tableFilterForm.querySelectorAll('select')).forEach((input) => {
|
||||
this._tableFilterInputs.select.push(input);
|
||||
});
|
||||
Array.from(tableFilterForm.querySelectorAll('select')).forEach((input) => this._tableFilterInputs.select.push(input));
|
||||
}
|
||||
|
||||
_addTableFilterEventListeners(tableFilterForm) {
|
||||
this._tableFilterInputs.search.forEach((input) => {
|
||||
const debouncedInput = debounce(() => {
|
||||
if (input.value.length === 0 || input.value.length > 2) {
|
||||
this._updateFromTableFilter(tableFilterForm);
|
||||
}
|
||||
}, INPUT_DEBOUNCE);
|
||||
input.addEventListener('input', debouncedInput);
|
||||
input.addEventListener('input', () => {
|
||||
// set flag to ignore any currently pending requests (not debounced)
|
||||
this._ignoreRequest = true;
|
||||
});
|
||||
});
|
||||
const debouncedUpdateFromTableFilter = throttle((() => this._updateFromTableFilter(tableFilterForm)).bind(this), FILTER_DEBOUNCE, { leading: true, trailing: false });
|
||||
|
||||
[...this._tableFilterInputs.search, ...this._tableFilterInputs.input].forEach((input) => {
|
||||
const submitLockObserver = new MutationObserver((mutations, observer) => {
|
||||
for (const mutation of mutations) {
|
||||
// if the submit lock has been released, trigger an update and disconnect this observer
|
||||
if (mutation.target === input && mutation.attributeName === ATTR_SUBMIT_LOCKED && mutation.oldValue === 'true' && mutation.target.getAttribute(mutation.attributeName) === 'false') {
|
||||
debouncedUpdateFromTableFilter();
|
||||
observer.disconnect();
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
this._cancelPendingUpdates.push(() => { submitLockObserver.disconnect(); });
|
||||
|
||||
this._tableFilterInputs.input.forEach((input) => {
|
||||
const debouncedInput = debounce(() => {
|
||||
if (input.value.length === 0 || input.value.length > 2) {
|
||||
this._updateFromTableFilter(tableFilterForm);
|
||||
const submitLockedAttr = input.getAttribute(ATTR_SUBMIT_LOCKED);
|
||||
const submitLocked = submitLockedAttr === 'true';
|
||||
if (!submitLocked && (input.value.length === 0 || input.value.length > 2)) {
|
||||
debouncedUpdateFromTableFilter();
|
||||
} else if (submitLockedAttr === 'true') {
|
||||
// observe the submit lock of the input element
|
||||
submitLockObserver.observe(input, {
|
||||
attributes: true,
|
||||
attributeFilter: [ATTR_SUBMIT_LOCKED],
|
||||
attributeOldValue: true,
|
||||
});
|
||||
}
|
||||
}, INPUT_DEBOUNCE);
|
||||
input.addEventListener('input', debouncedInput);
|
||||
this._cancelPendingUpdates.push(debouncedInput.cancel);
|
||||
|
||||
input.addEventListener('input', () => {
|
||||
// set flag to ignore any currently pending requests (not debounced)
|
||||
this._ignoreRequest = true;
|
||||
debouncedInput();
|
||||
});
|
||||
});
|
||||
|
||||
this._tableFilterInputs.change.forEach((input) => {
|
||||
input.addEventListener('change', () => {
|
||||
this._updateFromTableFilter(tableFilterForm);
|
||||
//if (this._element.classList.contains(ASYNC_TABLE_LOADING_CLASS))
|
||||
this._ignoreRequest = true;
|
||||
debouncedUpdateFromTableFilter();
|
||||
});
|
||||
});
|
||||
|
||||
this._tableFilterInputs.select.forEach((input) => {
|
||||
input.addEventListener('change', () => {
|
||||
this._updateFromTableFilter(tableFilterForm);
|
||||
this._ignoreRequest = true;
|
||||
debouncedUpdateFromTableFilter();
|
||||
});
|
||||
});
|
||||
|
||||
tableFilterForm.addEventListener('submit', (event) =>{
|
||||
event.preventDefault();
|
||||
this._updateFromTableFilter(tableFilterForm);
|
||||
this._ignoreRequest = true;
|
||||
debouncedUpdateFromTableFilter();
|
||||
});
|
||||
}
|
||||
|
||||
@ -236,15 +264,14 @@ export class AsyncTable {
|
||||
}
|
||||
};
|
||||
}
|
||||
this._ignoreRequest = false;
|
||||
this._updateTableFrom(url, callback);
|
||||
this._updateTableFrom(url, callback && callback.bind(this));
|
||||
}
|
||||
|
||||
_serializeTableFilterToURL(tableFilterForm) {
|
||||
const url = new URL(this._storageManager.load('currentTableUrl') || window.location.href);
|
||||
|
||||
// create new FormData and format any date values
|
||||
const formData = Datepicker.unformatAll(this._massInputForm, new FormData(tableFilterForm));
|
||||
const formData = Datepicker.unformatAll(tableFilterForm, new FormData(tableFilterForm));
|
||||
|
||||
for (var k of url.searchParams.keys()) {
|
||||
url.searchParams.delete(k);
|
||||
@ -321,6 +348,12 @@ export class AsyncTable {
|
||||
|
||||
// fetches new sorted element from url with params and replaces contents of current element
|
||||
_updateTableFrom(url, callback) {
|
||||
const cancelPendingUpdates = (() => {
|
||||
this._cancelPendingUpdates.forEach(f => f());
|
||||
}).bind(this);
|
||||
[0, INPUT_DEBOUNCE * 1.5, FILTER_DEBOUNCE * 1.5].forEach(delay => window.setTimeout(cancelPendingUpdates, delay));
|
||||
this._ignoreRequest = false;
|
||||
|
||||
this._element.classList.add(ASYNC_TABLE_LOADING_CLASS);
|
||||
|
||||
const headers = {
|
||||
@ -346,7 +379,7 @@ export class AsyncTable {
|
||||
// update table with new
|
||||
this._element.innerHTML = response.element.innerHTML;
|
||||
|
||||
this._app.utilRegistry.setupAll(this._element);
|
||||
this._app.utilRegistry.initAll(this._element);
|
||||
|
||||
if (callback && typeof callback === 'function') {
|
||||
this._storageManager.save('cssIdPrefix', response.idPrefix);
|
||||
|
||||
@ -8,7 +8,7 @@ const AppTestMock = {
|
||||
parseResponse: () => {},
|
||||
},
|
||||
utilRegistry: {
|
||||
setupAll: () => {},
|
||||
initAll: () => {},
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@ -93,7 +93,7 @@ export class CheckAll {
|
||||
th.insertBefore(this._checkAllCheckbox, th.firstChild);
|
||||
|
||||
// set up new checkbox
|
||||
this._app.utilRegistry.setupAll(th);
|
||||
this._app.utilRegistry.initAll(th);
|
||||
|
||||
this._checkAllCheckbox.addEventListener('input', () => this._onCheckAllCheckboxInput());
|
||||
this._setupCheckboxListeners();
|
||||
|
||||
@ -2,7 +2,7 @@ import { CheckAll } from './check-all';
|
||||
|
||||
const MOCK_APP = {
|
||||
utilRegistry: {
|
||||
setupAll: () => {},
|
||||
initAll: () => {},
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@ -6,6 +6,10 @@ import moment from 'moment';
|
||||
const KEYCODE_ESCAPE = 27;
|
||||
const Z_INDEX_MODAL = 9999;
|
||||
|
||||
// should be the same as ATTR_SUBMIT_LOCKED in async-table util
|
||||
// TODO move to global config
|
||||
const ATTR_DATEPICKER_OPEN = 'submit-locked';
|
||||
|
||||
// INTERNAL (Uni2work specific) formats for formatting dates and/or times
|
||||
const FORM_DATE_FORMAT = {
|
||||
'date': moment.HTML5_FMT.DATE,
|
||||
@ -26,21 +30,10 @@ const FORM_DATE_FORMAT_MOMENT = {
|
||||
'datetime-local': `${FORM_DATE_FORMAT_DATE_MOMENT} ${FORM_DATE_FORMAT_TIME_MOMENT}`,
|
||||
};
|
||||
|
||||
/**
|
||||
* 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, formatOut]);
|
||||
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_OPEN_CLASS = 'calendar-open';
|
||||
|
||||
const DATEPICKER_CONFIG = {
|
||||
'global': {
|
||||
@ -107,13 +100,13 @@ export class Datepicker {
|
||||
// store the previously set type to select the input format
|
||||
this.elementType = this._element.getAttribute('type');
|
||||
|
||||
// manually set the type attribute to text because datepicker handles displaying the date
|
||||
this._element.setAttribute('type', 'text');
|
||||
|
||||
// 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) {
|
||||
@ -162,7 +155,9 @@ export class Datepicker {
|
||||
|
||||
// mark the form input element as initialized
|
||||
this._element.classList.add(DATEPICKER_INITIALIZED_CLASS);
|
||||
}
|
||||
|
||||
start() {
|
||||
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]);
|
||||
@ -181,6 +176,24 @@ export class Datepicker {
|
||||
// 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 });
|
||||
|
||||
|
||||
// create a mutation observer that observes the datepicker instance class and sets
|
||||
// the datepicker-open DOM attribute of the input element if the datepicker has been opened
|
||||
const datepickerInstanceObserver = new MutationObserver((mutations) => {
|
||||
for (const mutation of mutations) {
|
||||
if (!mutation.oldValue.includes(DATEPICKER_OPEN_CLASS) && this.datepickerInstance.dt.getAttribute('class').includes(DATEPICKER_OPEN_CLASS)) {
|
||||
this._element.setAttribute(ATTR_DATEPICKER_OPEN, true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
datepickerInstanceObserver.observe(this.datepickerInstance.dt, {
|
||||
attributes: true,
|
||||
attributeFilter: ['class'],
|
||||
attributeOldValue: true,
|
||||
});
|
||||
|
||||
|
||||
// close the instance on focusout of any element if another input is focussed that is neither the timepicker nor _element
|
||||
window.addEventListener('focusout', event => {
|
||||
const hasFocus = event.relatedTarget !== null;
|
||||
@ -188,7 +201,7 @@ export class Datepicker {
|
||||
const focussedIsNotElement = event.relatedTarget !== this._element;
|
||||
const focussedIsInDocument = window.document.contains(event.relatedTarget);
|
||||
if (hasFocus && focussedIsNotTimepicker && focussedIsNotElement && focussedIsInDocument)
|
||||
this.datepickerInstance.close();
|
||||
this.closeDatepickerInstance();
|
||||
});
|
||||
|
||||
// close the instance on click on any element outside of the datepicker (except the input element itself)
|
||||
@ -198,13 +211,13 @@ export class Datepicker {
|
||||
const targetIsInDocument = window.document.contains(event.target);
|
||||
const targetIsNotElement = event.target !== this._element;
|
||||
if (targetIsOutside && targetIsInDocument && targetIsNotElement)
|
||||
this.datepickerInstance.close();
|
||||
this.closeDatepickerInstance();
|
||||
});
|
||||
|
||||
// close the instance on escape keydown events
|
||||
this._element.addEventListener('keydown', event => {
|
||||
if (event.keyCode === KEYCODE_ESCAPE) {
|
||||
this.datepickerInstance.close();
|
||||
this.closeDatepickerInstance();
|
||||
}
|
||||
});
|
||||
|
||||
@ -216,6 +229,24 @@ export class Datepicker {
|
||||
this.datepickerInstance.remove();
|
||||
}
|
||||
|
||||
|
||||
// DATEPICKER INSTANCE CONTROL
|
||||
|
||||
/**
|
||||
* Closes the datepicker instance, releasing the lock on the input element.
|
||||
*/
|
||||
closeDatepickerInstance() {
|
||||
if (!this._element.datepicker-open) {
|
||||
throw new Error('Cannot close already closed datepicker instance!');
|
||||
}
|
||||
|
||||
this._element.setAttribute(ATTR_DATEPICKER_OPEN, false);
|
||||
this.datepickerInstance.close();
|
||||
}
|
||||
|
||||
|
||||
// FORMAT METHODS
|
||||
|
||||
/**
|
||||
* 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.
|
||||
@ -226,8 +257,6 @@ export class Datepicker {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Returns a datestring in internal format from the current state of the input element value.
|
||||
* @param {*} toFancy Format date from internal to fancy or vice versa. When omitted, toFancy is falsy and results in fancy -> internal
|
||||
@ -265,3 +294,18 @@ export class Datepicker {
|
||||
return formData;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// HELPER FUNCTIONS
|
||||
|
||||
/**
|
||||
* 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, formatOut]);
|
||||
return parsedMomentDate.isValid() ? parsedMomentDate.format(formatOut) : dateStr;
|
||||
}
|
||||
|
||||
@ -4,6 +4,8 @@ import './hide-columns.sass';
|
||||
|
||||
const HIDE_COLUMNS_CONTAINER_IDENT = 'uw-hide-columns';
|
||||
const TABLE_HEADER_IDENT = 'uw-hide-column-header';
|
||||
const HIDE_COLUMNS_HIDER_LABEL = 'uw-hide-columns--hider-label';
|
||||
const HIDE_COLUMNS_NO_HIDE = 'uw-hide-columns--no-hide';
|
||||
|
||||
const TABLE_UTILS_ATTR = 'table-utils';
|
||||
const TABLE_UTILS_CONTAINER_SELECTOR = `[${TABLE_UTILS_ATTR}]`;
|
||||
@ -21,7 +23,7 @@ const CELL_ORIGINAL_COLSPAN = 'uw-hide-column-original-colspan';
|
||||
})
|
||||
export class HideColumns {
|
||||
|
||||
_storageManager = new StorageManager('HIDE_COLUMNS', '1.0.0', { location: LOCATION.LOCAL });
|
||||
_storageManager = new StorageManager('HIDE_COLUMNS', '1.1.0', { location: LOCATION.LOCAL });
|
||||
|
||||
_element;
|
||||
_elementWrapper;
|
||||
@ -44,8 +46,8 @@ export class HideColumns {
|
||||
throw new Error('Hide Columns utility cannot be setup without an element!');
|
||||
}
|
||||
|
||||
// do not provide hide-column ability in tables inside modals or async forms with response
|
||||
if (element.closest('[uw-modal], .async-form__response')) {
|
||||
// do not provide hide-column ability in tables inside modals, async forms with response or tail.datetime instances
|
||||
if (element.closest('[uw-modal], .async-form__response, .tail-datetime-calendar')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -66,7 +68,7 @@ export class HideColumns {
|
||||
hideColumnsContainer.insertBefore(this._tableUtilContainer, tableContainer);
|
||||
}
|
||||
|
||||
this._element.querySelectorAll('th').forEach(th => this.setupHideButton(th));
|
||||
[...this._element.querySelectorAll('th')].filter(th => !th.hasAttribute(HIDE_COLUMNS_NO_HIDE)).forEach(th => this.setupHideButton(th));
|
||||
}
|
||||
|
||||
setupHideButton(th) {
|
||||
@ -80,7 +82,7 @@ export class HideColumns {
|
||||
|
||||
const hiderContent = document.createElement('span');
|
||||
hiderContent.classList.add('table-hider__label');
|
||||
hiderContent.innerHTML = th.innerText;
|
||||
hiderContent.innerHTML = th.getAttribute(HIDE_COLUMNS_HIDER_LABEL) || th.innerText;
|
||||
hider.appendChild(hiderContent);
|
||||
|
||||
this.addHeaderHider(th, hider);
|
||||
|
||||
@ -155,7 +155,7 @@ export class MassInput {
|
||||
|
||||
this._reset();
|
||||
|
||||
this._app.utilRegistry.setupAll(this._element);
|
||||
this._app.utilRegistry.initAll(this._element);
|
||||
}
|
||||
|
||||
_serializeForm(submitButton, enctype) {
|
||||
|
||||
@ -177,6 +177,6 @@ export class Modal {
|
||||
this._element.insertBefore(modalContent, null);
|
||||
|
||||
// setup any newly arrived utils
|
||||
this._app.utilRegistry.setupAll(this._element);
|
||||
this._app.utilRegistry.initAll(this._element);
|
||||
}
|
||||
}
|
||||
|
||||
@ -12,6 +12,8 @@ BtnCourseDeregister: Vom Kurs abmelden
|
||||
BtnCourseApply: Zum Kurs bewerben
|
||||
BtnCourseRetractApplication: Bewerbung zum Kurs zurückziehen
|
||||
BtnExamRegister: Anmelden zur Prüfung
|
||||
BtnExamRegisterOccurrence: Anmelden zum Prüfungstermin/-raum
|
||||
BtnExamSwitchOccurrence: Zu Prüfungstermin/-raum wechseln
|
||||
BtnExamDeregister: Von der Prüfung abmelden
|
||||
BtnHijack: Sitzung übernehmen
|
||||
BtnSave: Speichern
|
||||
@ -99,6 +101,7 @@ CourseCapacity: Kapazität
|
||||
CourseCapacityTip: Anzahl erlaubter Kursanmeldungen, leer lassen für unbeschränkte Kurskapazität
|
||||
CourseNoCapacity: In diesem Kurs sind keine Plätze mehr frei.
|
||||
TutorialNoCapacity: In dieser Übung sind keine Plätze mehr frei.
|
||||
ExamOccurrenceNoCapacity: Zu diesem Termin/Raum sind keine Plätze mehr frei.
|
||||
CourseNotEmpty: In diesem Kurs sind momentan Teilnehmer angemeldet.
|
||||
CourseRegistration: Kursanmeldung
|
||||
CourseRegisterOpen: Anmeldung möglich
|
||||
@ -409,6 +412,7 @@ UnauthorizedCorrectorAny: Sie sind nicht als Korrektor für eine Veranstaltung e
|
||||
UnauthorizedRegistered: Sie sind nicht als Teilnehmer für diese Veranstaltung registriert.
|
||||
UnauthorizedAllocationRegistered: Sie sind nicht als Teilnehmer für diese Zentralanmeldung registriert.
|
||||
UnauthorizedExamResult: Sie haben keine Ergebnisse in dieser Prüfung.
|
||||
UnauthorizedExamOccurrenceRegistration: Anmeldung zur Klausur erfolgt nicht inkl. Raum/Termin.
|
||||
UnauthorizedParticipant: Angegebener Benutzer ist nicht als Teilnehmer dieser Veranstaltung registriert.
|
||||
UnauthorizedParticipantSelf: Sie sind kein Teilnehmer dieser Veranstaltung.
|
||||
UnauthorizedApplicant: Angegebener Benutzer hat sich nicht für diese Veranstaltung beworben.
|
||||
@ -1208,8 +1212,7 @@ BreadcrumbTerm: Semester
|
||||
BreadcrumbSchool: Institut
|
||||
BreadcrumbUser: Benutzer
|
||||
BreadcrumbStatic: Statische Resource
|
||||
BreadcrumbFavicon: Favicon
|
||||
BreadcrumbRobots: robots.txt
|
||||
BreadcrumbWellKnown: Benannte statische Resource
|
||||
BreadcrumbMetrics: Metriken
|
||||
BreadcrumbLecturerInvite: Einladung zum Kursverwalter
|
||||
BreadcrumbExamOfficeUserInvite: Einladung bzgl. Prüfungsleistungen
|
||||
@ -1264,6 +1267,8 @@ AuthTagAllocationRegistered: Nutzer nimmt an der Zentralanmeldung teil
|
||||
AuthTagTutorialRegistered: Nutzer ist Tutoriumsteilnehmer
|
||||
AuthTagExamRegistered: Nutzer ist Prüfungsteilnehmer
|
||||
AuthTagExamResult: Nutzer hat Prüfungsergebnisse
|
||||
AuthTagExamOccurrenceRegistered: Nutzer ist für Prüfungsraum/-termin angemeldet
|
||||
AuthTagExamOccurrenceRegistration: Anmeldung zur Klausur erfolgt inkl. Raum/Termin
|
||||
AuthTagParticipant: Nutzer ist mit Kurs assoziiert
|
||||
AuthTagApplicant: Nutzer ist mit Bewerber zum Kurs
|
||||
AuthTagRegisterGroup: Nutzer ist nicht Mitglied eines anderen Tutoriums mit der selben Registrierungs-Gruppe
|
||||
@ -1523,6 +1528,8 @@ ExamNoBonus': Kein automatischer Bonus
|
||||
ExamBonusPoints': Umrechnung von Übungspunkten
|
||||
ExamBonusManual': Manuelle Berechnung
|
||||
|
||||
ExamRegisterForOccurrence: Anmeldung zur Klausur erfolgt durch Anmeldung zu einem Termin/Raum
|
||||
|
||||
ExamBonusAchieved: Bonuspunkte
|
||||
|
||||
ExamEditHeading examn@ExamName: #{examn} bearbeiten
|
||||
@ -1535,17 +1542,19 @@ ExamBonusRound: Bonus runden auf
|
||||
ExamBonusRoundNonPositive: Vielfaches, auf das gerundet werden soll, muss positiv und größer null sein
|
||||
ExamBonusRoundTip: Bonuspunkte werden kaufmännisch auf ein Vielfaches der angegeben Zahl gerundet.
|
||||
|
||||
ExamAutomaticOccurrenceAssignment: Automatische Termin- bzw. Raumzuteilung
|
||||
ExamAutomaticOccurrenceAssignmentTip: Sollen Prüfungsteilnehmer automatisch auf die zur Verfügung stehenden Räume bzw. Termine verteilt werden? Manuelle Umverteilung bzw. vorheriges Festlegen von Zuteilungen einzelner Teilnehmer ist trotzdem möglich.
|
||||
ExamAutomaticOccurrenceAssignment: Termin- bzw. Raumzuteilung
|
||||
ExamAutomaticOccurrenceAssignmentTip: Sollen Prüfungsteilnehmer automatisch auf die zur Verfügung stehenden Räume bzw. Termine verteilt werden, sich selbstständig einen Raum bzw. Termin aussuchen dürfen oder manuell durch Kursverwalter zugeteilt werden? Manuelle Umverteilung bzw. vorheriges Festlegen von Zuteilungen einzelner Teilnehmer ist trotzdem möglich.
|
||||
ExamOccurrenceRule: Verfahren
|
||||
ExamOccurrenceRuleParticipant: Termin- bzw. Raumzuteilungsverfahren
|
||||
ExamRoomManual': Keine automatische Zuteilung
|
||||
ExamRoomManual': Keine automatische bzw. selbstständige Zuteilung
|
||||
ExamRoomSurname': Nach Nachname
|
||||
ExamRoomMatriculation': Nach Matrikelnummer
|
||||
ExamRoomRandom': Zufällig pro Teilnehmer
|
||||
ExamRoomFifo': Auswahl durch Teilnehmer bei Anmeldung
|
||||
|
||||
ExamOccurrence: Termin/Raum
|
||||
ExamNoOccurrence: Kein Termin/Raum
|
||||
ExamNoSuchOccurrence: Termin/Raum existiert nicht (mehr)
|
||||
ExamOccurrences: Prüfungen
|
||||
ExamRooms: Räume
|
||||
ExamRoomAlreadyExists: Prüfung ist bereits eingetragen
|
||||
@ -1558,7 +1567,8 @@ ExamRoomStart: Beginn
|
||||
ExamRoomEnd: Ende
|
||||
ExamRoomDescription: Beschreibung
|
||||
ExamTimeTip: Nur zur Information der Studierenden, die tatsächliche Zeitangabe erfolgt pro Prüfung
|
||||
ExamRoomRegistered: Zugeteilt
|
||||
ExamRoomAssigned: Zugeteilt
|
||||
ExamRoomRegistered: Anmeldung
|
||||
|
||||
ExamOccurrenceStart: Prüfungsbeginn
|
||||
|
||||
@ -1604,6 +1614,7 @@ ExamDeregisteredSuccess exam@ExamName: Erfolgreich von der Prüfung #{exam} abge
|
||||
ExamRegistered: Zur Prüfung angemeldet
|
||||
ExamNotRegistered: Nicht zur Prüfung angemeldet
|
||||
ExamRegistration: Prüfungsanmeldung
|
||||
ExamLoginToRegister: Um sich zum Kurs anzumelden müssen Sie zunächst in Uni2work anmelden
|
||||
|
||||
ExamRegisterToMustBeAfterRegisterFrom: "Anmeldung ab" muss vor "Anmeldung bis" liegen
|
||||
ExamDeregisterUntilMustBeAfterRegisterFrom: "Abmeldung bis" muss nach "Anmeldung bis" liegen
|
||||
@ -1650,6 +1661,8 @@ ExamUserMarkedSynchronised n@Int: #{n} #{pluralDE n "Prüfungsleistung" "Prüfun
|
||||
|
||||
ExamOfficeExamUsersHeading: Prüfungsleistungen
|
||||
|
||||
ActionsHead: Aktionen
|
||||
|
||||
CsvFile: CSV-Datei
|
||||
CsvImport: CSV-Import
|
||||
CsvExport: CSV-Export
|
||||
@ -1669,6 +1682,7 @@ CsvImportAborted: CSV-Import abgebrochen
|
||||
CsvImportExplanationLabel: Hinweise zum CSV-Import
|
||||
|
||||
Proportion c@Text of@Text prop@Rational: #{c}/#{of} (#{rationalToFixed2 (100 * prop)}%)
|
||||
ProportionNoRatio c@Text of@Text: #{c}/#{of}
|
||||
|
||||
CourseUserCsvName tid@TermId ssh@SchoolId csh@CourseShorthand: #{foldCase (termToText (unTermKey tid))}-#{foldedCase (unSchoolKey ssh)}-#{foldedCase csh}-teilnehmer
|
||||
ExamUserCsvName tid@TermId ssh@SchoolId csh@CourseShorthand examn@ExamName: #{foldCase (termToText (unTermKey tid))}-#{foldedCase (unSchoolKey ssh)}-#{foldedCase csh}-#{foldedCase examn}-teilnehmer
|
||||
|
||||
@ -12,6 +12,8 @@ BtnCourseDeregister: Leave course
|
||||
BtnCourseApply: Apply for course
|
||||
BtnCourseRetractApplication: Retract application
|
||||
BtnExamRegister: Enrol for exam
|
||||
BtnExamRegisterOccurrence: Enrol for exam occurrence/room
|
||||
BtnExamSwitchOccurrence: Switch to exam occurrence/room
|
||||
BtnExamDeregister: Leave exam
|
||||
BtnHijack: Hijack session
|
||||
BtnSave: Save
|
||||
@ -99,6 +101,7 @@ CourseCapacity: Capacity
|
||||
CourseCapacityTip: Maximum permissable number of enrolments for this course; leave empty for unlimited capacity
|
||||
CourseNoCapacity: Course has reached maximum capacity
|
||||
TutorialNoCapacity: Tutorial has reached maximum capacity
|
||||
ExamOccurrenceNoCapacity: Occurrence/Room has reached maximum capacity
|
||||
CourseNotEmpty: There are currently no participants enrolled for this course.
|
||||
CourseRegistration: Enrolment
|
||||
CourseRegisterOpen: Enrolment is allowed
|
||||
@ -407,6 +410,7 @@ UnauthorizedCorrectorAny: You are no corrector for any course.
|
||||
UnauthorizedRegistered: You are no participant in this course.
|
||||
UnauthorizedAllocationRegistered: You are no participant in this central allocation.
|
||||
UnauthorizedExamResult: You have no results in this exam.
|
||||
UnauthorizedExamOccurrenceRegistration: Registration for exam is not done including occurrence/room.
|
||||
UnauthorizedParticipant: The specified user is no participant of this course.
|
||||
UnauthorizedParticipantSelf: You are no participant of this course.
|
||||
UnauthorizedApplicant: The specified user is no applicant for this course.
|
||||
@ -1207,8 +1211,7 @@ BreadcrumbTerm: Semester
|
||||
BreadcrumbSchool: Department
|
||||
BreadcrumbUser: User
|
||||
BreadcrumbStatic: Static resource
|
||||
BreadcrumbFavicon: Favicon
|
||||
BreadcrumbRobots: robots.txt
|
||||
BreadcrumbWellKnown: Named static resource
|
||||
BreadcrumbMetrics: Metrics
|
||||
BreadcrumbLecturerInvite: Invitation to be a course administrator
|
||||
BreadcrumbExamOfficeUserInvite: Invitation regarding exam achievements
|
||||
@ -1263,6 +1266,8 @@ AuthTagAllocationRegistered: User participates in central allocation
|
||||
AuthTagTutorialRegistered: User is tutorial participant
|
||||
AuthTagExamRegistered: User is exam participant
|
||||
AuthTagExamResult: User has an exam result
|
||||
AuthTagExamOccurrenceRegistered: User is registered for exam occurrence/room
|
||||
AuthTagExamOccurrenceRegistration: Registration for exam is done including occurrence/room
|
||||
AuthTagParticipant: User participates in course
|
||||
AuthTagApplicant: User is applicant for course
|
||||
AuthTagRegisterGroup: User is not participant in any tutorial of the same registration group
|
||||
@ -1521,6 +1526,8 @@ ExamNoBonus': No automatic exam bonus
|
||||
ExamBonusPoints': Compute from exercise achievements
|
||||
ExamBonusManual': Manual computation
|
||||
|
||||
ExamRegisterForOccurrence: Registration for this exam is done by registering for an occurrence/room
|
||||
|
||||
ExamBonusAchieved: Bonus points
|
||||
|
||||
ExamEditHeading examn: Edit #{examn}
|
||||
@ -1533,17 +1540,19 @@ ExamBonusRound: Round bonus to
|
||||
ExamBonusRoundNonPositive: Rounding multiple must be positive and greater than zero
|
||||
ExamBonusRoundTip: Bonus points are rounded commercially to a multiple of the given number
|
||||
|
||||
ExamAutomaticOccurrenceAssignment: Automatically assign occurrence/room
|
||||
ExamAutomaticOccurrenceAssignmentTip: Should exam participants be distributed automatically among the configured occurrences/rooms? Manipulation of the distribution and manually assigning participants remains possible.
|
||||
ExamAutomaticOccurrenceAssignment: Selection of occurrences/rooms for/by participants
|
||||
ExamAutomaticOccurrenceAssignmentTip: Should exam participants be distributed automatically among the configured occurrences/rooms, should they instead be permitted to autonomously choose an occurrence/a room, or should they be assigned to occurrences/rooms manually by course administrators? Manipulation of the distribution and manually assigning participants remains possible.
|
||||
ExamOccurrenceRule: Procedure
|
||||
ExamOccurrenceRuleParticipant: Occurrence/room assignment procedure
|
||||
ExamRoomManual': No automatic assignment
|
||||
ExamRoomManual': No automatic or autonomous assignment
|
||||
ExamRoomSurname': By surname
|
||||
ExamRoomMatriculation': By matriculation
|
||||
ExamRoomRandom': Randomly
|
||||
ExamRoomFifo': Selected by the participants when registering
|
||||
|
||||
ExamOccurrence: Occurrence/room
|
||||
ExamNoOccurrence: No occurrence/room
|
||||
ExamNoSuchOccurrence: Occurrence/Room does not exist (anymore)
|
||||
ExamOccurrences: Exams
|
||||
ExamRooms: Rooms
|
||||
ExamRoomAlreadyExists: Occurrence already configured
|
||||
@ -1556,7 +1565,8 @@ ExamRoomStart: Start
|
||||
ExamRoomEnd: End
|
||||
ExamRoomDescription: Description
|
||||
ExamTimeTip: Only for informational purposes. The actual times are set for each occurrence/room
|
||||
ExamRoomRegistered: Assigned
|
||||
ExamRoomAssigned: Assigned
|
||||
ExamRoomRegistered: Registration
|
||||
|
||||
ExamOccurrenceStart: Exam starts
|
||||
|
||||
@ -1602,6 +1612,7 @@ ExamDeregisteredSuccess exam: Successufly deregistered from the exam #{exam}
|
||||
ExamRegistered: Registered for the exam
|
||||
ExamNotRegistered: Not registered for the exam
|
||||
ExamRegistration: Exam registration
|
||||
ExamLoginToRegister: Your need to login to Uni2work before you can register for this course.
|
||||
|
||||
ExamRegisterToMustBeAfterRegisterFrom: "Register to" must be after "register from"
|
||||
ExamDeregisterUntilMustBeAfterRegisterFrom: "Deregister until" must be after "register from"
|
||||
@ -1648,6 +1659,8 @@ ExamUserMarkedSynchronised n: Successfully marked #{n} #{pluralEN n "exam achiev
|
||||
|
||||
ExamOfficeExamUsersHeading: Exam achievements
|
||||
|
||||
ActionsHead: Actions
|
||||
|
||||
CsvFile: CSV file
|
||||
CsvImport: CSV import
|
||||
CsvExport: CSV export
|
||||
@ -1667,6 +1680,7 @@ CsvImportAborted: CSV import aborted
|
||||
CsvImportExplanationLabel: Informating regarding CSV import
|
||||
|
||||
Proportion c of prop: #{c}/#{of} (#{rationalToFixed2 (100 * prop)}%)
|
||||
ProportionNoRatio c of: #{c}/#{of}
|
||||
|
||||
CourseUserCsvName tid ssh csh: #{foldCase (termToText (unTermKey tid))}-#{foldedCase (unSchoolKey ssh)}-#{foldedCase csh}-participants
|
||||
ExamUserCsvName tid ssh csh examn: #{foldCase (termToText (unTermKey tid))}-#{foldedCase (unSchoolKey ssh)}-#{foldedCase csh}-#{foldedCase examn}-participants
|
||||
|
||||
@ -3,7 +3,7 @@ Exam
|
||||
name ExamName
|
||||
gradingRule ExamGradingRule Maybe
|
||||
bonusRule ExamBonusRule Maybe
|
||||
occurrenceRule ExamOccurrenceRule Maybe
|
||||
occurrenceRule ExamOccurrenceRule
|
||||
visibleFrom UTCTime Maybe
|
||||
registerFrom UTCTime Maybe
|
||||
registerTo UTCTime Maybe
|
||||
|
||||
747
package-lock.json
generated
747
package-lock.json
generated
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "uni2work",
|
||||
"version": "10.1.0",
|
||||
"version": "10.3.0",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
@ -3383,6 +3383,42 @@
|
||||
"integrity": "sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug==",
|
||||
"dev": true
|
||||
},
|
||||
"axios": {
|
||||
"version": "0.18.1",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-0.18.1.tgz",
|
||||
"integrity": "sha512-0BfJq4NSfQXd+SkFdrvFbG7addhYSBA2mQwISr46pD6E5iqkWg02RAs8vyTT/j0RTnoYmeXauBuSv1qKwR179g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"follow-redirects": "1.5.10",
|
||||
"is-buffer": "^2.0.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"debug": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
|
||||
"integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
},
|
||||
"follow-redirects": {
|
||||
"version": "1.5.10",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz",
|
||||
"integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"debug": "=3.1.0"
|
||||
}
|
||||
},
|
||||
"is-buffer": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.4.tgz",
|
||||
"integrity": "sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"babel-code-frame": {
|
||||
"version": "6.26.0",
|
||||
"resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz",
|
||||
@ -4258,6 +4294,16 @@
|
||||
"integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==",
|
||||
"dev": true
|
||||
},
|
||||
"binary": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/binary/-/binary-0.3.0.tgz",
|
||||
"integrity": "sha1-n2BVO8XOjDOG87VTz/R0Yq3sqnk=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"buffers": "~0.1.1",
|
||||
"chainsaw": "~0.1.0"
|
||||
}
|
||||
},
|
||||
"binary-extensions": {
|
||||
"version": "1.13.1",
|
||||
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz",
|
||||
@ -4478,6 +4524,12 @@
|
||||
"integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=",
|
||||
"dev": true
|
||||
},
|
||||
"buffers": {
|
||||
"version": "0.1.1",
|
||||
"resolved": "https://registry.npmjs.org/buffers/-/buffers-0.1.1.tgz",
|
||||
"integrity": "sha1-skV5w77U1tOWru5tmorn9Ugqt7s=",
|
||||
"dev": true
|
||||
},
|
||||
"builtin-status-codes": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz",
|
||||
@ -4622,6 +4674,16 @@
|
||||
"integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
|
||||
"dev": true
|
||||
},
|
||||
"camel-case": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/camel-case/-/camel-case-3.0.0.tgz",
|
||||
"integrity": "sha1-yjw2iKTpzzpM2nd9xNy8cTJJz3M=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"no-case": "^2.2.0",
|
||||
"upper-case": "^1.1.1"
|
||||
}
|
||||
},
|
||||
"camelcase": {
|
||||
"version": "5.3.1",
|
||||
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
|
||||
@ -4746,6 +4808,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"chainsaw": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/chainsaw/-/chainsaw-0.1.0.tgz",
|
||||
"integrity": "sha1-XqtQsor+WAdNDVgpE4iCi15fvJg=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"traverse": ">=0.3.0 <0.4"
|
||||
}
|
||||
},
|
||||
"chalk": {
|
||||
"version": "2.4.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
|
||||
@ -4763,6 +4834,76 @@
|
||||
"integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==",
|
||||
"dev": true
|
||||
},
|
||||
"cheerio": {
|
||||
"version": "0.22.0",
|
||||
"resolved": "https://registry.npmjs.org/cheerio/-/cheerio-0.22.0.tgz",
|
||||
"integrity": "sha1-qbqoYKP5tZWmuBsahocxIe06Jp4=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"css-select": "~1.2.0",
|
||||
"dom-serializer": "~0.1.0",
|
||||
"entities": "~1.1.1",
|
||||
"htmlparser2": "^3.9.1",
|
||||
"lodash.assignin": "^4.0.9",
|
||||
"lodash.bind": "^4.1.4",
|
||||
"lodash.defaults": "^4.0.1",
|
||||
"lodash.filter": "^4.4.0",
|
||||
"lodash.flatten": "^4.2.0",
|
||||
"lodash.foreach": "^4.3.0",
|
||||
"lodash.map": "^4.4.0",
|
||||
"lodash.merge": "^4.4.0",
|
||||
"lodash.pick": "^4.2.1",
|
||||
"lodash.reduce": "^4.4.0",
|
||||
"lodash.reject": "^4.4.0",
|
||||
"lodash.some": "^4.4.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"css-select": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz",
|
||||
"integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"boolbase": "~1.0.0",
|
||||
"css-what": "2.1",
|
||||
"domutils": "1.5.1",
|
||||
"nth-check": "~1.0.1"
|
||||
}
|
||||
},
|
||||
"css-what": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.3.tgz",
|
||||
"integrity": "sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg==",
|
||||
"dev": true
|
||||
},
|
||||
"dom-serializer": {
|
||||
"version": "0.1.1",
|
||||
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.1.tgz",
|
||||
"integrity": "sha512-l0IU0pPzLWSHBcieZbpOKgkIn3ts3vAh7ZuFyXNwJxJXk/c4Gwj9xaTJwIDVQCXawWD0qb3IzMGH5rglQaO0XA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"domelementtype": "^1.3.0",
|
||||
"entities": "^1.1.1"
|
||||
}
|
||||
},
|
||||
"domutils": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz",
|
||||
"integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"dom-serializer": "0",
|
||||
"domelementtype": "1"
|
||||
}
|
||||
},
|
||||
"entities": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz",
|
||||
"integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"chokidar": {
|
||||
"version": "2.1.8",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz",
|
||||
@ -4843,6 +4984,23 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"clean-css": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.1.tgz",
|
||||
"integrity": "sha512-4ZxI6dy4lrY6FHzfiy1aEOXgu4LIsW2MhwG0VBKdcoGoH/XLFgaHSdLTGr4O8Be6A8r3MOphEiI8Gc1n0ecf3g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"source-map": "~0.6.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"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
|
||||
}
|
||||
}
|
||||
},
|
||||
"clean-stack": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz",
|
||||
@ -7193,6 +7351,15 @@
|
||||
"esutils": "^2.0.2"
|
||||
}
|
||||
},
|
||||
"dom-converter": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz",
|
||||
"integrity": "sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"utila": "~0.4"
|
||||
}
|
||||
},
|
||||
"dom-serialize": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/dom-serialize/-/dom-serialize-2.2.1.tgz",
|
||||
@ -7235,6 +7402,15 @@
|
||||
"integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==",
|
||||
"dev": true
|
||||
},
|
||||
"domhandler": {
|
||||
"version": "2.4.2",
|
||||
"resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz",
|
||||
"integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"domelementtype": "1"
|
||||
}
|
||||
},
|
||||
"domutils": {
|
||||
"version": "1.7.0",
|
||||
"resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz",
|
||||
@ -8085,6 +8261,17 @@
|
||||
"chardet": "^0.7.0",
|
||||
"iconv-lite": "^0.4.24",
|
||||
"tmp": "^0.0.33"
|
||||
},
|
||||
"dependencies": {
|
||||
"tmp": {
|
||||
"version": "0.0.33",
|
||||
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
|
||||
"integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"os-tmpdir": "~1.0.2"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"extglob": {
|
||||
@ -9030,6 +9217,18 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"fstream": {
|
||||
"version": "1.0.12",
|
||||
"resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz",
|
||||
"integrity": "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"graceful-fs": "^4.1.2",
|
||||
"inherits": "~2.0.0",
|
||||
"mkdirp": ">=0.5 0",
|
||||
"rimraf": "2"
|
||||
}
|
||||
},
|
||||
"function-bind": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
|
||||
@ -9499,9 +9698,9 @@
|
||||
}
|
||||
},
|
||||
"glob": {
|
||||
"version": "7.1.4",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz",
|
||||
"integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==",
|
||||
"version": "7.1.6",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
|
||||
"integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"fs.realpath": "^1.0.0",
|
||||
@ -9777,6 +9976,12 @@
|
||||
"minimalistic-assert": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"he": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
|
||||
"integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
|
||||
"dev": true
|
||||
},
|
||||
"hex-color-regex": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/hex-color-regex/-/hex-color-regex-1.1.0.tgz",
|
||||
@ -9837,6 +10042,133 @@
|
||||
"integrity": "sha512-P+M65QY2JQ5Y0G9KKdlDpo0zK+/OHptU5AaBwUfAIDJZk1MYf32Frm84EcOytfJE0t5JvkAnKlmjsXDnWzCJmQ==",
|
||||
"dev": true
|
||||
},
|
||||
"html-minifier": {
|
||||
"version": "3.5.21",
|
||||
"resolved": "https://registry.npmjs.org/html-minifier/-/html-minifier-3.5.21.tgz",
|
||||
"integrity": "sha512-LKUKwuJDhxNa3uf/LPR/KVjm/l3rBqtYeCOAekvG8F1vItxMUpueGd94i/asDDr8/1u7InxzFA5EeGjhhG5mMA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"camel-case": "3.0.x",
|
||||
"clean-css": "4.2.x",
|
||||
"commander": "2.17.x",
|
||||
"he": "1.2.x",
|
||||
"param-case": "2.1.x",
|
||||
"relateurl": "0.2.x",
|
||||
"uglify-js": "3.4.x"
|
||||
},
|
||||
"dependencies": {
|
||||
"commander": {
|
||||
"version": "2.17.1",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz",
|
||||
"integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==",
|
||||
"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
|
||||
},
|
||||
"uglify-js": {
|
||||
"version": "3.4.10",
|
||||
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.10.tgz",
|
||||
"integrity": "sha512-Y2VsbPVs0FIshJztycsO2SfPk7/KAF/T72qzv9u5EpQ4kB2hQoHlhNQTsNyy6ul7lQtqJN/AoWeS23OzEiEFxw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"commander": "~2.19.0",
|
||||
"source-map": "~0.6.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"commander": {
|
||||
"version": "2.19.0",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-2.19.0.tgz",
|
||||
"integrity": "sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"html-webpack-plugin": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-3.2.0.tgz",
|
||||
"integrity": "sha1-sBq71yOsqqeze2r0SS69oD2d03s=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"html-minifier": "^3.2.3",
|
||||
"loader-utils": "^0.2.16",
|
||||
"lodash": "^4.17.3",
|
||||
"pretty-error": "^2.0.2",
|
||||
"tapable": "^1.0.0",
|
||||
"toposort": "^1.0.0",
|
||||
"util.promisify": "1.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"big.js": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/big.js/-/big.js-3.2.0.tgz",
|
||||
"integrity": "sha512-+hN/Zh2D08Mx65pZ/4g5bsmNiZUuChDiQfTUQ7qJr4/kuopCr88xZsAXv6mBoZEsUI4OuGHlX59qE94K2mMW8Q==",
|
||||
"dev": true
|
||||
},
|
||||
"json5": {
|
||||
"version": "0.5.1",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz",
|
||||
"integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=",
|
||||
"dev": true
|
||||
},
|
||||
"loader-utils": {
|
||||
"version": "0.2.17",
|
||||
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-0.2.17.tgz",
|
||||
"integrity": "sha1-+G5jdNQyBabmxg6RlvF8Apm/s0g=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"big.js": "^3.1.3",
|
||||
"emojis-list": "^2.0.0",
|
||||
"json5": "^0.5.0",
|
||||
"object-assign": "^4.0.1"
|
||||
}
|
||||
},
|
||||
"toposort": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/toposort/-/toposort-1.0.7.tgz",
|
||||
"integrity": "sha1-LmhELZ9k7HILjMieZEOsbKqVACk=",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"htmlparser2": {
|
||||
"version": "3.10.1",
|
||||
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz",
|
||||
"integrity": "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"domelementtype": "^1.3.1",
|
||||
"domhandler": "^2.3.0",
|
||||
"domutils": "^1.5.1",
|
||||
"entities": "^1.1.1",
|
||||
"inherits": "^2.0.1",
|
||||
"readable-stream": "^3.1.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"entities": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz",
|
||||
"integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==",
|
||||
"dev": true
|
||||
},
|
||||
"readable-stream": {
|
||||
"version": "3.4.0",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.4.0.tgz",
|
||||
"integrity": "sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"inherits": "^2.0.3",
|
||||
"string_decoder": "^1.1.1",
|
||||
"util-deprecate": "^1.0.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"http-errors": {
|
||||
"version": "1.7.2",
|
||||
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz",
|
||||
@ -10812,6 +11144,15 @@
|
||||
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
|
||||
"dev": true
|
||||
},
|
||||
"tmp": {
|
||||
"version": "0.0.33",
|
||||
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
|
||||
"integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"os-tmpdir": "~1.0.2"
|
||||
}
|
||||
},
|
||||
"to-regex-range": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
||||
@ -11186,24 +11527,96 @@
|
||||
"integrity": "sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=",
|
||||
"dev": true
|
||||
},
|
||||
"lodash.assignin": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.assignin/-/lodash.assignin-4.2.0.tgz",
|
||||
"integrity": "sha1-uo31+4QesKPoBEIysOJjqNxqKKI=",
|
||||
"dev": true
|
||||
},
|
||||
"lodash.bind": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/lodash.bind/-/lodash.bind-4.2.1.tgz",
|
||||
"integrity": "sha1-euMBfpOWIqwxt9fX3LGzTbFpDTU=",
|
||||
"dev": true
|
||||
},
|
||||
"lodash.debounce": {
|
||||
"version": "4.0.8",
|
||||
"resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
|
||||
"integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=",
|
||||
"dev": true
|
||||
},
|
||||
"lodash.defaults": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz",
|
||||
"integrity": "sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw=",
|
||||
"dev": true
|
||||
},
|
||||
"lodash.filter": {
|
||||
"version": "4.6.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.filter/-/lodash.filter-4.6.0.tgz",
|
||||
"integrity": "sha1-ZosdSYFgOuHMWm+nYBQ+SAtMSs4=",
|
||||
"dev": true
|
||||
},
|
||||
"lodash.flatten": {
|
||||
"version": "4.4.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz",
|
||||
"integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=",
|
||||
"dev": true
|
||||
},
|
||||
"lodash.foreach": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.foreach/-/lodash.foreach-4.5.0.tgz",
|
||||
"integrity": "sha1-Gmo16s5AEoDH8G3d7DUWWrJ+PlM=",
|
||||
"dev": true
|
||||
},
|
||||
"lodash.ismatch": {
|
||||
"version": "4.4.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.ismatch/-/lodash.ismatch-4.4.0.tgz",
|
||||
"integrity": "sha1-dWy1FQyjum8RCFp4hJZF8Yj4Xzc=",
|
||||
"dev": true
|
||||
},
|
||||
"lodash.map": {
|
||||
"version": "4.6.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.map/-/lodash.map-4.6.0.tgz",
|
||||
"integrity": "sha1-dx7Hg540c9nEzeKLGTlMNWL09tM=",
|
||||
"dev": true
|
||||
},
|
||||
"lodash.memoize": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
|
||||
"integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=",
|
||||
"dev": true
|
||||
},
|
||||
"lodash.merge": {
|
||||
"version": "4.6.2",
|
||||
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
|
||||
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
|
||||
"dev": true
|
||||
},
|
||||
"lodash.pick": {
|
||||
"version": "4.4.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.pick/-/lodash.pick-4.4.0.tgz",
|
||||
"integrity": "sha1-UvBWEP/53tQiYRRB7R/BI6AwAbM=",
|
||||
"dev": true
|
||||
},
|
||||
"lodash.reduce": {
|
||||
"version": "4.6.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.reduce/-/lodash.reduce-4.6.0.tgz",
|
||||
"integrity": "sha1-8atrg5KZrUj3hKu/R2WW8DuRTTs=",
|
||||
"dev": true
|
||||
},
|
||||
"lodash.reject": {
|
||||
"version": "4.6.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.reject/-/lodash.reject-4.6.0.tgz",
|
||||
"integrity": "sha1-gNZJLcFHCGS79YNTO2UfQqn1JBU=",
|
||||
"dev": true
|
||||
},
|
||||
"lodash.some": {
|
||||
"version": "4.6.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.some/-/lodash.some-4.6.0.tgz",
|
||||
"integrity": "sha1-G7nzFO9ri63tE7VJFpsqlF62jk0=",
|
||||
"dev": true
|
||||
},
|
||||
"lodash.template": {
|
||||
"version": "4.4.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-4.4.0.tgz",
|
||||
@ -11223,6 +11636,11 @@
|
||||
"lodash._reinterpolate": "~3.0.0"
|
||||
}
|
||||
},
|
||||
"lodash.throttle": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz",
|
||||
"integrity": "sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ="
|
||||
},
|
||||
"lodash.uniq": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz",
|
||||
@ -11308,6 +11726,12 @@
|
||||
"signal-exit": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"lower-case": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz",
|
||||
"integrity": "sha1-miyr0bno4K6ZOkv31YdcOcQujqw=",
|
||||
"dev": true
|
||||
},
|
||||
"lru-cache": {
|
||||
"version": "4.1.5",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz",
|
||||
@ -11381,6 +11805,42 @@
|
||||
"object-visit": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"match-stream": {
|
||||
"version": "0.0.2",
|
||||
"resolved": "https://registry.npmjs.org/match-stream/-/match-stream-0.0.2.tgz",
|
||||
"integrity": "sha1-mesFAJOzTf+t5CG5rAtBCpz6F88=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"buffers": "~0.1.1",
|
||||
"readable-stream": "~1.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"isarray": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
|
||||
"integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=",
|
||||
"dev": true
|
||||
},
|
||||
"readable-stream": {
|
||||
"version": "1.0.34",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz",
|
||||
"integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"core-util-is": "~1.0.0",
|
||||
"inherits": "~2.0.1",
|
||||
"isarray": "0.0.1",
|
||||
"string_decoder": "~0.10.x"
|
||||
}
|
||||
},
|
||||
"string_decoder": {
|
||||
"version": "0.10.31",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
|
||||
"integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"matcher": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/matcher/-/matcher-1.1.1.tgz",
|
||||
@ -11518,6 +11978,18 @@
|
||||
"integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
|
||||
"dev": true
|
||||
},
|
||||
"metaparser": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/metaparser/-/metaparser-1.0.7.tgz",
|
||||
"integrity": "sha1-wGmaZoageovOGsBrYulGLC5mqso=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"async": "*",
|
||||
"cheerio": "*",
|
||||
"mkdirp": "*",
|
||||
"underscore": "*"
|
||||
}
|
||||
},
|
||||
"methods": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
|
||||
@ -11822,6 +12294,15 @@
|
||||
"integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==",
|
||||
"dev": true
|
||||
},
|
||||
"no-case": {
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/no-case/-/no-case-2.3.2.tgz",
|
||||
"integrity": "sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"lower-case": "^1.1.1"
|
||||
}
|
||||
},
|
||||
"node-libs-browser": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.2.1.tgz",
|
||||
@ -11878,6 +12359,46 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node-unzip-2": {
|
||||
"version": "0.2.8",
|
||||
"resolved": "https://registry.npmjs.org/node-unzip-2/-/node-unzip-2-0.2.8.tgz",
|
||||
"integrity": "sha512-fmJi73zTRW7RSo/1wyrKc2srKMwb3L6Ppke/7elzQ0QRt6sUjfiIcVsWdrqO5uEHAdvRKXjoySuo4HYe5BB0rw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"binary": "~0.3.0",
|
||||
"fstream": "~1.0.12",
|
||||
"match-stream": "~0.0.2",
|
||||
"pullstream": "~0.4.0",
|
||||
"readable-stream": "~1.0.0",
|
||||
"setimmediate": "~1.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"isarray": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
|
||||
"integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=",
|
||||
"dev": true
|
||||
},
|
||||
"readable-stream": {
|
||||
"version": "1.0.34",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz",
|
||||
"integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"core-util-is": "~1.0.0",
|
||||
"inherits": "~2.0.1",
|
||||
"isarray": "0.0.1",
|
||||
"string_decoder": "~0.10.x"
|
||||
}
|
||||
},
|
||||
"string_decoder": {
|
||||
"version": "0.10.31",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
|
||||
"integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"normalize-package-data": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz",
|
||||
@ -15466,6 +15987,12 @@
|
||||
"integrity": "sha512-Ue462G+UIFoyQmOzapGIKWS3d/9NHeD/018WGEDZIhN2/VaQpVXbofMcZX0socv1fw4/tmEn7Vd3McOdPZfKzQ==",
|
||||
"dev": true
|
||||
},
|
||||
"over": {
|
||||
"version": "0.0.5",
|
||||
"resolved": "https://registry.npmjs.org/over/-/over-0.0.5.tgz",
|
||||
"integrity": "sha1-8phS5w/X4l82DgE6jsRMgq7bVwg=",
|
||||
"dev": true
|
||||
},
|
||||
"p-defer": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz",
|
||||
@ -15531,6 +16058,15 @@
|
||||
"readable-stream": "^2.1.5"
|
||||
}
|
||||
},
|
||||
"param-case": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/param-case/-/param-case-2.1.1.tgz",
|
||||
"integrity": "sha1-35T9jPZTHs915r75oIWPvHK+Ikc=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"no-case": "^2.2.0"
|
||||
}
|
||||
},
|
||||
"parent-module": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
|
||||
@ -16723,6 +17259,16 @@
|
||||
"integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=",
|
||||
"dev": true
|
||||
},
|
||||
"pretty-error": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-2.1.1.tgz",
|
||||
"integrity": "sha1-X0+HyPkeWuPzuoerTPXgOxoX8aM=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"renderkid": "^2.0.1",
|
||||
"utila": "~0.4"
|
||||
}
|
||||
},
|
||||
"private": {
|
||||
"version": "0.1.8",
|
||||
"resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz",
|
||||
@ -16795,6 +17341,44 @@
|
||||
"safe-buffer": "^5.1.2"
|
||||
}
|
||||
},
|
||||
"pullstream": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/pullstream/-/pullstream-0.4.1.tgz",
|
||||
"integrity": "sha1-1vs79a7Wl+gxFQ6xACwlo/iuExQ=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"over": ">= 0.0.5 < 1",
|
||||
"readable-stream": "~1.0.31",
|
||||
"setimmediate": ">= 1.0.2 < 2",
|
||||
"slice-stream": ">= 1.0.0 < 2"
|
||||
},
|
||||
"dependencies": {
|
||||
"isarray": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
|
||||
"integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=",
|
||||
"dev": true
|
||||
},
|
||||
"readable-stream": {
|
||||
"version": "1.0.34",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz",
|
||||
"integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"core-util-is": "~1.0.0",
|
||||
"inherits": "~2.0.1",
|
||||
"isarray": "0.0.1",
|
||||
"string_decoder": "~0.10.x"
|
||||
}
|
||||
},
|
||||
"string_decoder": {
|
||||
"version": "0.10.31",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
|
||||
"integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"pump": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
|
||||
@ -17011,6 +17595,15 @@
|
||||
"readable-stream": "^2.0.2"
|
||||
}
|
||||
},
|
||||
"real-favicon-webpack-plugin": {
|
||||
"version": "0.2.3",
|
||||
"resolved": "https://registry.npmjs.org/real-favicon-webpack-plugin/-/real-favicon-webpack-plugin-0.2.3.tgz",
|
||||
"integrity": "sha512-w9CS4DdISimLk+hD1qAqVstWfkAXLpnU7a7UOmArHR1pDnmB4SYBO/fwfuu+ObWEcWtIkfsAhJaD6eKSN4Bq6A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"rfg-api": "^0.5.0"
|
||||
}
|
||||
},
|
||||
"redent": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz",
|
||||
@ -17121,12 +17714,67 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"relateurl": {
|
||||
"version": "0.2.7",
|
||||
"resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz",
|
||||
"integrity": "sha1-VNvzd+UUQKypCkzSdGANP/LYiKk=",
|
||||
"dev": true
|
||||
},
|
||||
"remove-files-webpack-plugin": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/remove-files-webpack-plugin/-/remove-files-webpack-plugin-1.1.3.tgz",
|
||||
"integrity": "sha512-r53wQ/IlTkmcv11wri71CZ27S+GhFI5SjHbTbaAJbisPC3qGwg87vlA2C5Z1PuVA+aMI8SgimnE4SqI+ZYzu6Q==",
|
||||
"dev": true
|
||||
},
|
||||
"remove-trailing-separator": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz",
|
||||
"integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=",
|
||||
"dev": true
|
||||
},
|
||||
"renderkid": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/renderkid/-/renderkid-2.0.3.tgz",
|
||||
"integrity": "sha512-z8CLQp7EZBPCwCnncgf9C4XAi3WR0dv+uWu/PjIyhhAb5d6IJ/QZqlHFprHeKT+59//V6BNUsLbvN8+2LarxGA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"css-select": "^1.1.0",
|
||||
"dom-converter": "^0.2",
|
||||
"htmlparser2": "^3.3.0",
|
||||
"strip-ansi": "^3.0.0",
|
||||
"utila": "^0.4.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"css-select": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz",
|
||||
"integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"boolbase": "~1.0.0",
|
||||
"css-what": "2.1",
|
||||
"domutils": "1.5.1",
|
||||
"nth-check": "~1.0.1"
|
||||
}
|
||||
},
|
||||
"css-what": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.3.tgz",
|
||||
"integrity": "sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg==",
|
||||
"dev": true
|
||||
},
|
||||
"domutils": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz",
|
||||
"integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"dom-serializer": "0",
|
||||
"domelementtype": "1"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"repeat-element": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz",
|
||||
@ -17373,6 +18021,19 @@
|
||||
"integrity": "sha512-5C9HXdzK8EAqN7JDif30jqsBzavB7wLpaubisuQIGHWf2gUXSpzy6ArX/+Da8RjFpagWsCn+pIgxTMAmKw9Zug==",
|
||||
"dev": true
|
||||
},
|
||||
"rfg-api": {
|
||||
"version": "0.5.0",
|
||||
"resolved": "https://registry.npmjs.org/rfg-api/-/rfg-api-0.5.0.tgz",
|
||||
"integrity": "sha512-wd6BcVoBsEHlbfsaB1WD4Z/Xis4uEP+Qctd3u2jxXR5yTkCYENaB/m3Jsk0G2qToKgAeE+6tbsN6T0n8DHcSaw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"axios": "^0.18.0",
|
||||
"fstream": "^1.0.2",
|
||||
"metaparser": "^1.0.7",
|
||||
"mkdirp": "^0.5.0",
|
||||
"node-unzip-2": "^0.2.7"
|
||||
}
|
||||
},
|
||||
"rgb-regex": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/rgb-regex/-/rgb-regex-1.0.1.tgz",
|
||||
@ -17770,6 +18431,41 @@
|
||||
"is-fullwidth-code-point": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"slice-stream": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/slice-stream/-/slice-stream-1.0.0.tgz",
|
||||
"integrity": "sha1-WzO9ZvATsaf4ZGCwPUY97DmtPqA=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"readable-stream": "~1.0.31"
|
||||
},
|
||||
"dependencies": {
|
||||
"isarray": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
|
||||
"integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=",
|
||||
"dev": true
|
||||
},
|
||||
"readable-stream": {
|
||||
"version": "1.0.34",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz",
|
||||
"integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"core-util-is": "~1.0.0",
|
||||
"inherits": "~2.0.1",
|
||||
"isarray": "0.0.1",
|
||||
"string_decoder": "~0.10.x"
|
||||
}
|
||||
},
|
||||
"string_decoder": {
|
||||
"version": "0.10.31",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
|
||||
"integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"snapdragon": {
|
||||
"version": "0.8.2",
|
||||
"resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz",
|
||||
@ -18970,12 +19666,12 @@
|
||||
"dev": true
|
||||
},
|
||||
"tmp": {
|
||||
"version": "0.0.33",
|
||||
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
|
||||
"integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==",
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.1.0.tgz",
|
||||
"integrity": "sha512-J7Z2K08jbGcdA1kkQpJSqLF6T0tdQqpR2pnSUXsIchbPdTI9v3e85cLW0d6WDhwuAleOV71j2xWs8qMPfK7nKw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"os-tmpdir": "~1.0.2"
|
||||
"rimraf": "^2.6.3"
|
||||
}
|
||||
},
|
||||
"to-array": {
|
||||
@ -19067,6 +19763,12 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"traverse": {
|
||||
"version": "0.3.9",
|
||||
"resolved": "https://registry.npmjs.org/traverse/-/traverse-0.3.9.tgz",
|
||||
"integrity": "sha1-cXuPIgzAu3tE5AUUwisui7xw2Lk=",
|
||||
"dev": true
|
||||
},
|
||||
"trim-newlines": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz",
|
||||
@ -19181,6 +19883,12 @@
|
||||
"integrity": "sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og==",
|
||||
"dev": true
|
||||
},
|
||||
"underscore": {
|
||||
"version": "1.9.2",
|
||||
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.9.2.tgz",
|
||||
"integrity": "sha512-D39qtimx0c1fI3ya1Lnhk3E9nONswSKhnffBI0gME9C99fYOkNi04xs8K6pePLhvl1frbDemkaBQ5ikWllR2HQ==",
|
||||
"dev": true
|
||||
},
|
||||
"unicode-canonical-property-names-ecmascript": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz",
|
||||
@ -19315,6 +20023,12 @@
|
||||
"integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==",
|
||||
"dev": true
|
||||
},
|
||||
"upper-case": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/upper-case/-/upper-case-1.1.3.tgz",
|
||||
"integrity": "sha1-9rRQHC7EzdJrp4vnIilh3ndiFZg=",
|
||||
"dev": true
|
||||
},
|
||||
"uri-js": {
|
||||
"version": "4.2.2",
|
||||
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz",
|
||||
@ -19368,6 +20082,17 @@
|
||||
"requires": {
|
||||
"lru-cache": "4.1.x",
|
||||
"tmp": "0.0.x"
|
||||
},
|
||||
"dependencies": {
|
||||
"tmp": {
|
||||
"version": "0.0.33",
|
||||
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
|
||||
"integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"os-tmpdir": "~1.0.2"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"util": {
|
||||
@ -19395,6 +20120,12 @@
|
||||
"object.getownpropertydescriptors": "^2.0.3"
|
||||
}
|
||||
},
|
||||
"utila": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz",
|
||||
"integrity": "sha1-ihagXURWV6Oupe7MWxKk+lN5dyw=",
|
||||
"dev": true
|
||||
},
|
||||
"utils-merge": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "uni2work",
|
||||
"version": "10.1.0",
|
||||
"version": "10.3.0",
|
||||
"description": "",
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
@ -74,6 +74,8 @@
|
||||
"css-loader": "^2.1.1",
|
||||
"eslint": "^5.16.0",
|
||||
"file-loader": "^5.0.2",
|
||||
"glob": "^7.1.6",
|
||||
"html-webpack-plugin": "^3.2.0",
|
||||
"husky": "^2.7.0",
|
||||
"jasmine-core": "^3.5.0",
|
||||
"js-yaml": "^3.13.1",
|
||||
@ -92,6 +94,8 @@
|
||||
"optimize-css-assets-webpack-plugin": "^5.0.3",
|
||||
"postcss-loader": "^3.0.0",
|
||||
"postcss-preset-env": "^6.7.0",
|
||||
"real-favicon-webpack-plugin": "^0.2.3",
|
||||
"remove-files-webpack-plugin": "^1.1.3",
|
||||
"resolve-url-loader": "^3.1.1",
|
||||
"sass": "^1.23.7",
|
||||
"sass-loader": "^7.3.1",
|
||||
@ -99,6 +103,7 @@
|
||||
"standard-version": "^6.0.1",
|
||||
"style-loader": "^0.23.1",
|
||||
"terser-webpack-plugin": "^2.2.3",
|
||||
"tmp": "^0.1.0",
|
||||
"typeface-roboto": "0.0.75",
|
||||
"typeface-source-sans-pro": "0.0.75",
|
||||
"webpack": "^4.41.2",
|
||||
@ -110,6 +115,7 @@
|
||||
"@babel/runtime": "^7.7.6",
|
||||
"@juggle/resize-observer": "^2.5.0",
|
||||
"core-js": "^3.4.8",
|
||||
"lodash.throttle": "^4.1.1",
|
||||
"moment": "^2.24.0",
|
||||
"npm": "^6.13.4",
|
||||
"tail.datetime": "git+ssh://git@gitlab2.rz.ifi.lmu.de/uni2work/tail.DateTime.git#master",
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
name: uniworx
|
||||
version: 10.1.0
|
||||
version: 10.3.0
|
||||
|
||||
dependencies:
|
||||
- base
|
||||
|
||||
26
records.json
26
records.json
@ -739,5 +739,31 @@
|
||||
"usedIds": []
|
||||
}
|
||||
}
|
||||
],
|
||||
"app-manifest-webpack-plugin for \"/home/gkleen/projects/uni2work/static/wp-4.41/iconstats-[hash].json\"": [
|
||||
{
|
||||
"modules": {
|
||||
"byIdentifier": {},
|
||||
"usedIds": {}
|
||||
},
|
||||
"chunks": {
|
||||
"byName": {},
|
||||
"bySource": {},
|
||||
"usedIds": []
|
||||
}
|
||||
}
|
||||
],
|
||||
"app-manifest-webpack-plugin for \"../../config/favicon.json\"": [
|
||||
{
|
||||
"modules": {
|
||||
"byIdentifier": {},
|
||||
"usedIds": {}
|
||||
},
|
||||
"chunks": {
|
||||
"byName": {},
|
||||
"bySource": {},
|
||||
"usedIds": []
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
6
routes
6
routes
@ -39,8 +39,6 @@
|
||||
/static StaticR EmbeddedStatic appStatic !free
|
||||
/auth AuthR Auth getAuth !free
|
||||
|
||||
/favicon.ico FaviconR GET !free
|
||||
/robots.txt RobotsR GET !free
|
||||
/metrics MetricsR GET
|
||||
|
||||
/ HomeR GET !free
|
||||
@ -175,6 +173,7 @@
|
||||
/users/new EAddUserR GET POST
|
||||
/users/invite EInviteR GET POST
|
||||
/register ERegisterR POST !timeANDcourse-registeredAND¬exam-registered !timeANDexam-registeredAND¬exam-result
|
||||
/register/#ExamOccurrenceName ERegisterOccR POST !exam-occurrence-registrationANDtimeANDcapacityANDcourse-registeredAND¬exam-occurrence-registered !exam-occurrence-registrationANDtimeANDexam-occurrence-registeredAND¬exam-result
|
||||
/grades EGradesR GET POST !exam-office
|
||||
/apps CApplicationsR GET POST
|
||||
!/apps/files CAppsFilesR GET
|
||||
@ -204,6 +203,7 @@
|
||||
/msgs MessageListR GET POST
|
||||
/msg/#{CryptoUUIDSystemMessage} MessageR GET POST !timeANDreadANDauthentication
|
||||
|
||||
|
||||
!/#UUID CryptoUUIDDispatchR GET !free -- just redirect
|
||||
-- !/*{CI FilePath} CryptoFileNameDispatchR GET !free -- Disabled until preliminary check for valid cID exists
|
||||
|
||||
!/*WellKnownFileName WellKnownR GET !free
|
||||
@ -19,7 +19,7 @@ let
|
||||
'';
|
||||
|
||||
override = oldAttrs: {
|
||||
nativeBuildInputs = oldAttrs.nativeBuildInputs ++ (with pkgs; [ nodejs-12_x postgresql openldap google-chrome ]) ++ (with haskellPackages; [ stack yesod-bin hlint cabal-install weeder ]);
|
||||
nativeBuildInputs = oldAttrs.nativeBuildInputs ++ (with pkgs; [ nodejs-12_x postgresql openldap google-chrome exiftool ]) ++ (with haskellPackages; [ stack yesod-bin hlint cabal-install weeder ]);
|
||||
shellHook = ''
|
||||
export PROMPT_INFO="${oldAttrs.name}"
|
||||
|
||||
|
||||
@ -101,7 +101,6 @@ import Data.List (cycle)
|
||||
|
||||
-- Import all relevant handler modules here.
|
||||
-- (HPack takes care to add new modules to our cabal file nowadays.)
|
||||
import Handler.Common
|
||||
import Handler.Home
|
||||
import Handler.Info
|
||||
import Handler.Help
|
||||
@ -194,7 +193,7 @@ makeFoundation appSettings'@AppSettings{..} = do
|
||||
|
||||
runAppLoggingT tempFoundation $ do
|
||||
$logInfoS "InstanceID" $ UUID.toText appInstanceID
|
||||
-- logDebugS "Configuration" $ tshow appSettings'
|
||||
$logDebugS "Configuration" $ tshow appSettings'
|
||||
|
||||
smtpPool <- for appSmtpConf $ \c -> do
|
||||
$logDebugS "setup" "SMTP-Pool"
|
||||
|
||||
@ -504,9 +504,9 @@ tagAccessPredicate AuthTime = APDB $ \mAuthId route _ -> case route of
|
||||
course <- $cachedHereBinary (tid, ssh, csh) . MaybeT . getKeyBy $ TermSchoolCourseShort tid ssh csh
|
||||
Entity eId Exam{..} <- $cachedHereBinary (course, examn) . MaybeT . getBy $ UniqueExam course examn
|
||||
cTime <- liftIO getCurrentTime
|
||||
registered <- case mAuthId of
|
||||
Just uid -> $cachedHereBinary (eId, uid) . lift . existsBy $ UniqueExamRegistration eId uid
|
||||
Nothing -> return False
|
||||
registration <- case mAuthId of
|
||||
Just uid -> $cachedHereBinary (eId, uid) . lift . getBy $ UniqueExamRegistration eId uid
|
||||
Nothing -> return Nothing
|
||||
|
||||
let visible = NTop examVisibleFrom <= NTop (Just cTime)
|
||||
|
||||
@ -515,11 +515,23 @@ tagAccessPredicate AuthTime = APDB $ \mAuthId route _ -> case route of
|
||||
EUsersR -> guard $ NTop examStart <= NTop (Just cTime)
|
||||
&& NTop (Just cTime) <= NTop examFinished
|
||||
ERegisterR
|
||||
| not registered -> guard $ visible
|
||||
&& NTop examRegisterFrom <= NTop (Just cTime)
|
||||
&& NTop (Just cTime) <= NTop examRegisterTo
|
||||
| otherwise -> guard $ visible
|
||||
&& NTop (Just cTime) <= NTop examDeregisterUntil
|
||||
| is _Nothing registration
|
||||
-> guard $ visible
|
||||
&& NTop examRegisterFrom <= NTop (Just cTime)
|
||||
&& NTop (Just cTime) <= NTop examRegisterTo
|
||||
| otherwise
|
||||
-> guard $ visible
|
||||
&& NTop (Just cTime) <= NTop examDeregisterUntil
|
||||
ERegisterOccR occn -> do
|
||||
occId <- (>>= hoistMaybe) . $cachedHereBinary (eId, occn) . lift . getKeyBy $ UniqueExamOccurrence eId occn
|
||||
if
|
||||
| (registration >>= examRegistrationOccurrence . entityVal) == Just occId
|
||||
-> guard $ visible
|
||||
&& NTop (Just cTime) <= NTop examDeregisterUntil
|
||||
| otherwise
|
||||
-> guard $ visible
|
||||
&& NTop examRegisterFrom <= NTop (Just cTime)
|
||||
&& NTop (Just cTime) <= NTop examRegisterTo
|
||||
_ -> return ()
|
||||
|
||||
return Authorized
|
||||
@ -758,6 +770,59 @@ tagAccessPredicate AuthTutorialRegistered = APDB $ \mAuthId route _ -> case rout
|
||||
guardMExceptT isRegistered (unauthorizedI MsgUnauthorizedRegistered)
|
||||
return Authorized
|
||||
r -> $unsupportedAuthPredicate AuthTutorialRegistered r
|
||||
tagAccessPredicate AuthExamOccurrenceRegistration = APDB $ \_ route _ -> case route of
|
||||
CExamR tid ssh csh examn _ -> exceptT return return $ do
|
||||
isOccurrenceRegistration <- $cachedHereBinary (tid, ssh, csh, examn) . lift . E.selectExists . E.from $ \(course `E.InnerJoin` exam) -> do
|
||||
E.on $ course E.^. CourseId E.==. exam E.^. ExamCourse
|
||||
E.where_ $ course E.^. CourseTerm E.==. E.val tid
|
||||
E.&&. course E.^. CourseSchool E.==. E.val ssh
|
||||
E.&&. course E.^. CourseShorthand E.==. E.val csh
|
||||
E.&&. exam E.^. ExamName E.==. E.val examn
|
||||
E.&&. exam E.^. ExamOccurrenceRule E.==. E.val ExamRoomFifo
|
||||
guardMExceptT isOccurrenceRegistration (unauthorizedI MsgUnauthorizedExamOccurrenceRegistration)
|
||||
return Authorized
|
||||
r -> $unsupportedAuthPredicate AuthExamOccurrenceRegistration r
|
||||
tagAccessPredicate AuthExamOccurrenceRegistered = APDB $ \mAuthId route _ -> case route of
|
||||
CExamR tid ssh csh examn (ERegisterOccR occn) -> exceptT return return $ do
|
||||
authId <- maybeExceptT AuthenticationRequired $ return mAuthId
|
||||
hasRegistration <- $cachedHereBinary (authId, tid, ssh, csh, examn, occn) . lift . E.selectExists . E.from $ \(course `E.InnerJoin` exam `E.InnerJoin` examRegistration `E.InnerJoin` examOccurrence) -> do
|
||||
E.on $ E.just (examOccurrence E.^. ExamOccurrenceId) E.==. examRegistration E.^. ExamRegistrationOccurrence
|
||||
E.on $ exam E.^. ExamId E.==. examRegistration E.^. ExamRegistrationExam
|
||||
E.on $ course E.^. CourseId E.==. exam E.^. ExamCourse
|
||||
E.where_ $ examRegistration E.^. ExamRegistrationUser E.==. E.val authId
|
||||
E.&&. examOccurrence E.^. ExamOccurrenceName E.==. E.val occn
|
||||
E.&&. course E.^. CourseTerm E.==. E.val tid
|
||||
E.&&. course E.^. CourseSchool E.==. E.val ssh
|
||||
E.&&. course E.^. CourseShorthand E.==. E.val csh
|
||||
E.&&. exam E.^. ExamName E.==. E.val examn
|
||||
guardMExceptT hasRegistration (unauthorizedI MsgUnauthorizedRegistered)
|
||||
return Authorized
|
||||
CExamR tid ssh csh examn _ -> exceptT return return $ do
|
||||
authId <- maybeExceptT AuthenticationRequired $ return mAuthId
|
||||
hasRegistration <- $cachedHereBinary (authId, tid, ssh, csh, examn) . lift . E.selectExists . E.from $ \(course `E.InnerJoin` exam `E.InnerJoin` examRegistration) -> do
|
||||
E.on $ exam E.^. ExamId E.==. examRegistration E.^. ExamRegistrationExam
|
||||
E.on $ course E.^. CourseId E.==. exam E.^. ExamCourse
|
||||
E.where_ $ examRegistration E.^. ExamRegistrationUser E.==. E.val authId
|
||||
E.&&. course E.^. CourseTerm E.==. E.val tid
|
||||
E.&&. course E.^. CourseSchool E.==. E.val ssh
|
||||
E.&&. course E.^. CourseShorthand E.==. E.val csh
|
||||
E.&&. exam E.^. ExamName E.==. E.val examn
|
||||
E.&&. E.not_ (E.isNothing $ examRegistration E.^. ExamRegistrationOccurrence)
|
||||
guardMExceptT hasRegistration (unauthorizedI MsgUnauthorizedRegistered)
|
||||
return Authorized
|
||||
CourseR tid ssh csh _ -> exceptT return return $ do
|
||||
authId <- maybeExceptT AuthenticationRequired $ return mAuthId
|
||||
hasRegistration <- $cachedHereBinary (authId, tid, ssh, csh) . lift . E.selectExists . E.from $ \(course `E.InnerJoin` exam `E.InnerJoin` examRegistration) -> do
|
||||
E.on $ exam E.^. ExamId E.==. examRegistration E.^. ExamRegistrationExam
|
||||
E.on $ course E.^. CourseId E.==. exam E.^. ExamCourse
|
||||
E.where_ $ examRegistration E.^. ExamRegistrationUser E.==. E.val authId
|
||||
E.&&. course E.^. CourseTerm E.==. E.val tid
|
||||
E.&&. course E.^. CourseSchool E.==. E.val ssh
|
||||
E.&&. course E.^. CourseShorthand E.==. E.val csh
|
||||
E.&&. E.not_ (E.isNothing $ examRegistration E.^. ExamRegistrationOccurrence)
|
||||
guardMExceptT hasRegistration (unauthorizedI MsgUnauthorizedRegistered)
|
||||
return Authorized
|
||||
r -> $unsupportedAuthPredicate AuthExamOccurrenceRegistered r
|
||||
tagAccessPredicate AuthExamRegistered = APDB $ \mAuthId route _ -> case route of
|
||||
CExamR tid ssh csh examn _ -> exceptT return return $ do
|
||||
authId <- maybeExceptT AuthenticationRequired $ return mAuthId
|
||||
@ -768,7 +833,7 @@ tagAccessPredicate AuthExamRegistered = APDB $ \mAuthId route _ -> case route of
|
||||
E.&&. course E.^. CourseTerm E.==. E.val tid
|
||||
E.&&. course E.^. CourseSchool E.==. E.val ssh
|
||||
E.&&. course E.^. CourseShorthand E.==. E.val csh
|
||||
E.&&. exam E.^. ExamName E.==. E.val examn
|
||||
E.&&. exam E.^. ExamName E.==. E.val examn
|
||||
guardMExceptT hasRegistration (unauthorizedI MsgUnauthorizedRegistered)
|
||||
return Authorized
|
||||
CourseR tid ssh csh _ -> exceptT return return $ do
|
||||
@ -933,6 +998,13 @@ tagAccessPredicate AuthApplicant = APDB $ \mAuthId route _ -> case route of
|
||||
E.&&. course E.^. CourseSchool E.==. E.val ssh
|
||||
E.&&. course E.^. CourseShorthand E.==. E.val csh
|
||||
tagAccessPredicate AuthCapacity = APDB $ \_ route _ -> case route of
|
||||
CExamR tid ssh csh examn (ERegisterOccR occn) -> maybeT (unauthorizedI MsgExamOccurrenceNoCapacity) $ do
|
||||
cid <- $cachedHereBinary (tid, ssh, csh) . MaybeT . getKeyBy $ TermSchoolCourseShort tid ssh csh
|
||||
eid <- $cachedHereBinary (cid, examn) . MaybeT . getKeyBy $ UniqueExam cid examn
|
||||
Entity occId ExamOccurrence{..} <- $cachedHereBinary (eid, occn) . MaybeT . getBy $ UniqueExamOccurrence eid occn
|
||||
registered <- $cachedHereBinary occId . lift $ fromIntegral <$> count [ ExamRegistrationOccurrence ==. Just occId, ExamRegistrationExam ==. eid ]
|
||||
guard $ examOccurrenceCapacity > registered
|
||||
return Authorized
|
||||
CTutorialR tid ssh csh tutn _ -> maybeT (unauthorizedI MsgTutorialNoCapacity) $ do
|
||||
cid <- $cachedHereBinary (tid, ssh, csh) . MaybeT . getKeyBy $ TermSchoolCourseShort tid ssh csh
|
||||
Entity tutId Tutorial{..} <- $cachedHereBinary (cid, tutn) . MaybeT . getBy $ UniqueTutorial cid tutn
|
||||
@ -1574,18 +1646,9 @@ siteLayout' headingOverride widget = do
|
||||
frontendDatetimeLocale <- toJSON <$> selectLanguage frontendDatetimeLocales
|
||||
|
||||
pc <- widgetToPageContent $ do
|
||||
$logDebugS "siteLayout" $ tshow webpackEntrypoint_main
|
||||
forM_ webpackEntrypoint_main $ \(sRoute, mime) ->
|
||||
let ctEq = (==) `on` simpleContentType
|
||||
in if
|
||||
| mime `ctEq` "text/css"
|
||||
-> addStylesheet $ StaticR sRoute
|
||||
| mime `ctEq` "application/javascript"
|
||||
-> addScript $ StaticR sRoute
|
||||
| otherwise
|
||||
-> $logErrorS "siteLayout" [st|Unknown mime type in webpack bundle: #{tshow mime}|]
|
||||
|
||||
webpackLinks_main StaticR
|
||||
toWidget $(juliusFile "templates/i18n.julius")
|
||||
wellKnownHtmlLinks
|
||||
|
||||
$(widgetFile "default-layout")
|
||||
withUrlRenderer $(hamletFile "templates/default-layout-wrapper.hamlet")
|
||||
@ -1636,8 +1699,7 @@ i18nCrumb msg mbR = do
|
||||
instance YesodBreadcrumbs UniWorX where
|
||||
breadcrumb (AuthR _) = i18nCrumb MsgMenuLogin $ Just HomeR
|
||||
breadcrumb (StaticR _) = i18nCrumb MsgBreadcrumbStatic Nothing
|
||||
breadcrumb FaviconR = i18nCrumb MsgBreadcrumbFavicon Nothing
|
||||
breadcrumb RobotsR = i18nCrumb MsgBreadcrumbRobots Nothing
|
||||
breadcrumb (WellKnownR _) = i18nCrumb MsgBreadcrumbWellKnown Nothing
|
||||
breadcrumb MetricsR = i18nCrumb MsgBreadcrumbMetrics Nothing
|
||||
|
||||
breadcrumb HomeR = i18nCrumb MsgMenuHome Nothing
|
||||
@ -1801,6 +1863,7 @@ instance YesodBreadcrumbs UniWorX where
|
||||
ECInviteR -> i18nCrumb MsgBreadcrumbExamCorrectorInvite . Just $ CExamR tid ssh csh examn EShowR
|
||||
EInviteR -> i18nCrumb MsgBreadcrumbExamParticipantInvite . Just $ CExamR tid ssh csh examn EShowR
|
||||
ERegisterR -> i18nCrumb MsgBreadcrumbExamRegister . Just $ CExamR tid ssh csh examn EShowR
|
||||
ERegisterOccR _occn -> i18nCrumb MsgBreadcrumbExamRegister . Just $ CExamR tid ssh csh examn EShowR
|
||||
|
||||
breadcrumb (CourseR tid ssh csh (TutorialR tutn sRoute)) = case sRoute of
|
||||
TUsersR -> maybeT (i18nCrumb MsgBreadcrumbTutorial . Just $ CourseR tid ssh csh CTutorialListR) $ do
|
||||
|
||||
@ -1,29 +0,0 @@
|
||||
-- | Common handler functions.
|
||||
module Handler.Common
|
||||
( getFaviconR
|
||||
, getRobotsR
|
||||
) where
|
||||
|
||||
import Data.FileEmbed (embedFile)
|
||||
import Import hiding (embedFile)
|
||||
|
||||
-- These handlers embed files in the executable at compile time to avoid a
|
||||
-- runtime dependency, and for efficiency.
|
||||
|
||||
getFaviconR :: Handler TypedContent
|
||||
getFaviconR = do
|
||||
let content = $(embedFile "static/favicon.ico")
|
||||
|
||||
setEtagHashable content
|
||||
|
||||
return $ TypedContent "image/x-icon"
|
||||
$ toContent content
|
||||
|
||||
getRobotsR :: Handler TypedContent
|
||||
getRobotsR = do
|
||||
let content = $(embedFile "static/robots.txt")
|
||||
|
||||
setEtagHashable content
|
||||
|
||||
return $ TypedContent typePlain
|
||||
$ toContent content
|
||||
@ -113,7 +113,9 @@ getCShowR tid ssh csh = do
|
||||
| otherwise
|
||||
-> return . modal $(widgetFile "course/login-to-register") . Left . SomeRoute $ AuthR LoginR
|
||||
registrationOpen <- hasWriteAccessTo $ CourseR tid ssh csh CRegisterR
|
||||
|
||||
|
||||
MsgRenderer mr <- getMsgRenderer
|
||||
|
||||
let
|
||||
tutorialDBTable = DBTable{..}
|
||||
where
|
||||
@ -151,7 +153,7 @@ getCShowR tid ssh csh = do
|
||||
E.where_ $ participant E.^. TutorialParticipantTutorial E.==. E.val tutid
|
||||
in return $ E.val tutorialCapacity' E.-. numParticipants
|
||||
return . toWidget $ tshow freeCapacity
|
||||
, sortable Nothing mempty $ \DBRow{ dbrOutput = Entity tutId Tutorial{..} } -> sqlCell $ do
|
||||
, sortable Nothing (mempty & cellAttrs <>~ pure ("uw-hide-columns--hider-label", mr MsgActionsHead)) $ \DBRow{ dbrOutput = Entity tutId Tutorial{..} } -> sqlCell $ do
|
||||
mayRegister <- (== Authorized) <$> evalAccessDB (CTutorialR tid ssh csh tutorialName TRegisterR) True
|
||||
isRegistered <- case mbAid of
|
||||
Nothing -> return False
|
||||
|
||||
@ -39,7 +39,7 @@ data ExamForm = ExamForm
|
||||
, efPublicStatistics :: Bool
|
||||
, efGradingRule :: Maybe ExamGradingRule
|
||||
, efBonusRule :: Maybe ExamBonusRule
|
||||
, efOccurrenceRule :: Maybe ExamOccurrenceRule
|
||||
, efOccurrenceRule :: ExamOccurrenceRule
|
||||
, efCorrectors :: Set (Either UserEmail UserId)
|
||||
, efExamParts :: Set ExamPartForm
|
||||
}
|
||||
@ -96,7 +96,7 @@ examForm template html = do
|
||||
<*> apopt checkBoxField (fslI MsgExamPublicStatistics & setTooltip MsgExamPublicStatisticsTip) (efPublicStatistics <$> template <|> Just True)
|
||||
<*> optionalActionA (examGradingRuleForm $ efGradingRule =<< template) (fslI MsgExamAutomaticGrading & setTooltip MsgExamAutomaticGradingTip) (is _Just . efGradingRule <$> template)
|
||||
<*> optionalActionA (examBonusRuleForm $ efBonusRule =<< template) (fslI MsgExamBonus) (is _Just . efBonusRule <$> template)
|
||||
<*> optionalActionA (examOccurrenceRuleForm $ efOccurrenceRule =<< template) (fslI MsgExamAutomaticOccurrenceAssignment & setTooltip MsgExamAutomaticOccurrenceAssignmentTip) (is _Just . efOccurrenceRule <$> template)
|
||||
<*> (examOccurrenceRuleForm $ efOccurrenceRule <$> template)
|
||||
<* aformSection MsgExamFormCorrection
|
||||
<*> examCorrectorsForm (efCorrectors <$> template)
|
||||
<* aformSection MsgExamFormParts
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
module Handler.Exam.Register
|
||||
( ButtonExamRegister(..)
|
||||
, postERegisterR
|
||||
, postERegisterOccR
|
||||
) where
|
||||
|
||||
import Import
|
||||
@ -8,45 +9,84 @@ import Import
|
||||
import Handler.Utils
|
||||
import Handler.Utils.Exam
|
||||
|
||||
import Database.Persist.Sql (deleteWhereCount)
|
||||
|
||||
|
||||
-- Dedicated ExamRegistrationButton
|
||||
data ButtonExamRegister = BtnExamRegister | BtnExamDeregister
|
||||
deriving (Enum, Eq, Ord, Bounded, Read, Show, Generic, Typeable)
|
||||
data ButtonExamRegister = BtnExamRegisterOccurrence
|
||||
| BtnExamSwitchOccurrence
|
||||
| BtnExamRegister
|
||||
| BtnExamDeregister
|
||||
deriving (Enum, Bounded, Eq, Ord, Read, Show, Generic, Typeable)
|
||||
instance Universe ButtonExamRegister
|
||||
instance Finite ButtonExamRegister
|
||||
nullaryPathPiece ''ButtonExamRegister $ camelToPathPiece' 1
|
||||
embedRenderMessage ''UniWorX ''ButtonExamRegister id
|
||||
nullaryPathPiece ''ButtonExamRegister $ camelToPathPiece' 2
|
||||
|
||||
instance Button UniWorX ButtonExamRegister where
|
||||
btnClasses BtnExamRegister = [BCIsButton, BCPrimary]
|
||||
btnClasses BtnExamDeregister = [BCIsButton, BCDanger]
|
||||
btnClasses BtnExamRegisterOccurrence = [BCIsButton, BCPrimary]
|
||||
btnClasses BtnExamSwitchOccurrence = [BCIsButton, BCPrimary]
|
||||
btnClasses BtnExamRegister = [BCIsButton, BCPrimary]
|
||||
btnClasses BtnExamDeregister = [BCIsButton, BCDanger]
|
||||
|
||||
btnLabel BtnExamRegister = [whamlet|#{iconExamRegister True} _{MsgBtnExamRegister}|]
|
||||
btnLabel BtnExamDeregister = [whamlet|#{iconExamRegister False} _{MsgBtnExamDeregister}|]
|
||||
btnLabel BtnExamRegisterOccurrence = [whamlet|#{iconExamRegister True } _{MsgBtnExamRegisterOccurrence}|]
|
||||
btnLabel BtnExamSwitchOccurrence = [whamlet|_{MsgBtnExamSwitchOccurrence}|]
|
||||
btnLabel BtnExamRegister = [whamlet|#{iconExamRegister True } _{MsgBtnExamRegister}|]
|
||||
btnLabel BtnExamDeregister = [whamlet|#{iconExamRegister False} _{MsgBtnExamDeregister}|]
|
||||
|
||||
|
||||
postERegisterR :: TermId -> SchoolId -> CourseShorthand -> ExamName -> Handler Html
|
||||
|
||||
postERegisterR tid ssh csh examn = do
|
||||
Entity uid User{..} <- requireAuth
|
||||
|
||||
Entity eId Exam{..} <- runDB $ fetchExam tid ssh csh examn
|
||||
|
||||
((btnResult, _), _) <- runFormPost buttonForm
|
||||
((btnResult, _), _) <- runFormPost $ buttonForm' [BtnExamRegister, BtnExamDeregister]
|
||||
|
||||
formResult btnResult $ \case
|
||||
BtnExamDeregister -> do
|
||||
runDB $ do
|
||||
deleted <- deleteWhereCount [ExamRegistrationExam ==. eId, ExamRegistrationUser ==. uid]
|
||||
unless (deleted <= 0) $
|
||||
audit $ TransactionExamDeregister eId uid
|
||||
addMessageIconI Success IconExamRegisterFalse $ MsgExamDeregisteredSuccess examn
|
||||
redirect $ CExamR tid ssh csh examn EShowR
|
||||
BtnExamRegister -> do
|
||||
runDB $ do
|
||||
now <- liftIO getCurrentTime
|
||||
insert_ $ ExamRegistration eId uid Nothing now
|
||||
void $ upsertBy (UniqueExamRegistration eId uid) (ExamRegistration eId uid Nothing now) [ExamRegistrationTime =. now]
|
||||
audit $ TransactionExamRegister eId uid
|
||||
addMessageIconI Success IconExamRegisterTrue $ MsgExamRegisteredSuccess examn
|
||||
addMessageIconI Success IconExamRegisterTrue $ MsgExamRegisteredSuccess examn
|
||||
redirect $ CExamR tid ssh csh examn EShowR
|
||||
_other -> error "Unexpected due to definition of buttonForm'"
|
||||
|
||||
redirect $ CExamR tid ssh csh examn EShowR
|
||||
|
||||
postERegisterOccR :: TermId -> SchoolId -> CourseShorthand -> ExamName -> ExamOccurrenceName -> Handler Html
|
||||
postERegisterOccR tid ssh csh examn occn = do
|
||||
Entity uid User{..} <- requireAuth
|
||||
(Entity eId Exam{..}, Entity occId ExamOccurrence{..}) <- runDB $ do
|
||||
eexam@(Entity eId _) <- fetchExam tid ssh csh examn
|
||||
occ <- getBy404 $ UniqueExamOccurrence eId occn
|
||||
return (eexam, occ)
|
||||
|
||||
((btnResult, _), _) <- runFormPost buttonForm
|
||||
|
||||
formResult btnResult $ \case
|
||||
BtnExamDeregister -> do
|
||||
runDB $ do
|
||||
deleteBy $ UniqueExamRegistration eId uid
|
||||
audit $ TransactionExamDeregister eId uid
|
||||
addMessageIconI Info IconExamRegisterFalse $ MsgExamDeregisteredSuccess examn
|
||||
-- yes, it's a success message, but it should be visually different from a positive success, since most will just note the positive green color! See discussion on commit 5f4925a4
|
||||
deleted <- deleteWhereCount [ExamRegistrationExam ==. eId, ExamRegistrationUser ==. uid]
|
||||
unless (deleted <= 0) $
|
||||
audit $ TransactionExamDeregister eId uid
|
||||
addMessageIconI Success IconExamRegisterFalse $ MsgExamDeregisteredSuccess examn
|
||||
redirect $ CExamR tid ssh csh examn EShowR
|
||||
btn | btn `elem` [BtnExamRegisterOccurrence, BtnExamSwitchOccurrence] -> do
|
||||
runDB $ do
|
||||
now <- liftIO getCurrentTime
|
||||
void $ upsertBy (UniqueExamRegistration eId uid) (ExamRegistration eId uid (Just occId) now) [ExamRegistrationOccurrence =. Just occId, ExamRegistrationTime =. now]
|
||||
audit $ TransactionExamRegister eId uid
|
||||
addMessageIconI Success IconExamRegisterTrue $ MsgExamRegisteredSuccess examn
|
||||
redirect $ CExamR tid ssh csh examn EShowR
|
||||
_other -> error "Unexpected due to definition of buttonForm'"
|
||||
|
||||
invalidArgs ["Register/Deregister button required"]
|
||||
redirect $ CExamR tid ssh csh examn EShowR
|
||||
|
||||
|
||||
@ -30,7 +30,7 @@ getEShowR tid ssh csh examn = do
|
||||
let gradingVisible = NTop (Just cTime) >= NTop examFinished
|
||||
gradingShown <- or2M (return gradingVisible) . hasReadAccessTo $ CExamR tid ssh csh examn EEditR
|
||||
|
||||
let occurrenceAssignmentsVisible = NTop (Just cTime) >= NTop examPublishOccurrenceAssignments
|
||||
let occurrenceAssignmentsVisible = NTop (Just cTime) >= NTop examPublishOccurrenceAssignments || examOccurrenceRule == ExamRoomFifo
|
||||
occurrenceAssignmentsShown <- or2M (return occurrenceAssignmentsVisible) . hasReadAccessTo $ CExamR tid ssh csh examn EEditR
|
||||
|
||||
examParts <- sortOn (view $ _entityVal . _examPartNumber) <$> selectList [ ExamPartExam ==. eId ] [ Asc ExamPartName ]
|
||||
@ -58,10 +58,16 @@ getEShowR tid ssh csh examn = do
|
||||
E.orderBy [E.desc registered, E.asc $ examOccurrence E.^. ExamOccurrenceStart, E.asc $ examOccurrence E.^. ExamOccurrenceRoom]
|
||||
return (examOccurrence, registered)
|
||||
|
||||
let occurrences = map (over _2 E.unValue) occurrencesRaw
|
||||
registered <- for mUid $ getBy . UniqueExamRegistration eId
|
||||
mayRegister <- if
|
||||
| examOccurrenceRule == ExamRoomFifo -> anyM occurrencesRaw $ \(Entity _ ExamOccurrence{..}, _) ->
|
||||
hasWriteAccessTo . CExamR tid ssh csh examName $ ERegisterOccR examOccurrenceName
|
||||
| otherwise -> hasWriteAccessTo $ CExamR tid ssh csh examName ERegisterR
|
||||
|
||||
registered <- for mUid $ existsBy . UniqueExamRegistration eId
|
||||
mayRegister <- (== Authorized) <$> evalAccessDB (CExamR tid ssh csh examName ERegisterR) True
|
||||
let occurrences = sortOn sortPred $ map (over _2 E.unValue) occurrencesRaw
|
||||
where
|
||||
sortPred (Entity _ ExamOccurrence{..}, registered')
|
||||
= (Down $ registered' && not mayRegister, examOccurrenceStart, examOccurrenceRoom)
|
||||
|
||||
lecturerInfoShown <- hasReadAccessTo $ CExamR tid ssh csh examn EEditR
|
||||
|
||||
@ -83,12 +89,18 @@ getEShowR tid ssh csh examn = do
|
||||
]
|
||||
|
||||
hasRegistration = any snd occurrences
|
||||
|
||||
|
||||
mayRegister' <- fmap ((Map.!) . Map.fromList) . for (Nothing : map Just occurrences) $ \case
|
||||
Nothing ->
|
||||
fmap (Nothing, ) . hasWriteAccessTo $ CExamR tid ssh csh examName ERegisterR
|
||||
Just (Entity occId ExamOccurrence{..}, _) ->
|
||||
fmap (Just occId, ) . hasWriteAccessTo . CExamR tid ssh csh examName $ ERegisterOccR examOccurrenceName
|
||||
|
||||
let examTimes = all (\(Entity _ ExamOccurrence{..}, _) -> Just examOccurrenceStart == examStart && examOccurrenceEnd == examEnd) occurrences
|
||||
registerWidget
|
||||
| Just isRegistered <- registered
|
||||
, mayRegister = Just $ do
|
||||
registerWidget mOcc
|
||||
| isRegistered <- is _Just $ join registered
|
||||
, examOccurrenceRule /= ExamRoomFifo || (isRegistered && not (any snd occurrences))
|
||||
, mayRegister' (entityKey <$> mOcc) = Just $ do
|
||||
(examRegisterForm, examRegisterEnctype) <- liftHandler . generateFormPost . buttonForm' $ bool [BtnExamRegister] [BtnExamDeregister] isRegistered
|
||||
[whamlet|
|
||||
<p>
|
||||
@ -102,11 +114,37 @@ getEShowR tid ssh csh examn = do
|
||||
, formEncoding = examRegisterEnctype
|
||||
, formSubmit = FormNoSubmit
|
||||
}
|
||||
| fromMaybe False registered = Just [whamlet|_{MsgExamRegistered}|]
|
||||
| examOccurrenceRule == ExamRoomFifo
|
||||
, Just (Entity occId ExamOccurrence{..}) <- mOcc
|
||||
, isRegistered <- (== Just occId) $ examRegistrationOccurrence . entityVal =<< join registered
|
||||
, mayRegister' (Just occId) = Just $ do
|
||||
(examRegisterForm, examRegisterEnctype) <- liftHandler . generateFormPost . buttonForm' $ bool [bool BtnExamRegisterOccurrence BtnExamSwitchOccurrence . is _Just $ join registered] [BtnExamDeregister] isRegistered
|
||||
wrapForm examRegisterForm def
|
||||
{ formAction = Just . SomeRoute . CExamR tid ssh csh examName $ ERegisterOccR examOccurrenceName
|
||||
, formEncoding = examRegisterEnctype
|
||||
, formSubmit = FormNoSubmit
|
||||
}
|
||||
| is _Nothing mOcc
|
||||
, is _Nothing registered
|
||||
= Just [whamlet|_{MsgExamLoginToRegister}|]
|
||||
| is _Nothing mOcc
|
||||
, isRegistered <- is _Just $ join registered
|
||||
= Just
|
||||
[whamlet|
|
||||
<p>
|
||||
$if isRegistered
|
||||
_{MsgExamRegistered}
|
||||
$else
|
||||
_{MsgExamNotRegistered}
|
||||
$if mayRegister
|
||||
^{messageTooltip =<< messageI Info MsgExamRegisterForOccurrence}
|
||||
|]
|
||||
| otherwise = Nothing
|
||||
|
||||
showMaxPoints = any (has $ _entityVal . _examPartMaxPoints . _Just) examParts
|
||||
showAchievedPoints = not $ null results
|
||||
showOccurrenceRegisterColumn = occurrenceAssignmentsShown || (mayRegister && examOccurrenceRule == ExamRoomFifo)
|
||||
markUnregisteredOccurrences mOcc = occurrenceAssignmentsShown && hasRegistration && isn't _Just (registerWidget mOcc)
|
||||
|
||||
let heading = prependCourseTitle tid ssh csh $ CI.original examName
|
||||
|
||||
|
||||
@ -104,6 +104,7 @@ getMaterialListR tid ssh csh = do
|
||||
|
||||
now <- liftIO getCurrentTime
|
||||
seeAllModificationTimestamps <- hasWriteAccessTo $ CourseR tid ssh csh MaterialNewR -- ordinary users should not see modification dates older than visibility
|
||||
MsgRenderer mr <- getMsgRenderer
|
||||
table <- runDB $ do
|
||||
cid <- getKeyBy404 $ TermSchoolCourseShort tid ssh csh
|
||||
let row2material = view $ _dbrOutput . _1 . _entityVal
|
||||
@ -127,9 +128,9 @@ getMaterialListR tid ssh csh = do
|
||||
$ foldMap (textCell . CI.original) . materialType . row2material
|
||||
, sortable (Just "name") (i18nCell MsgMaterialName)
|
||||
$ liftA2 anchorCell matLink toWgt . materialName . row2material
|
||||
, sortable (toNothingS "description") mempty
|
||||
, sortable (toNothingS "description") (mempty & cellAttrs <>~ pure ("uw-hide-columns--hider-label", mr MsgMaterialDescription))
|
||||
$ foldMap modalCell . materialDescription . row2material
|
||||
, sortable (toNothingS "zip-archive") mempty
|
||||
, sortable (toNothingS "zip-archive") (mempty & cellAttrs <>~ pure ("uw-hide-columns--hider-label", mr MsgMaterialFiles))
|
||||
$ \DBRow{ dbrOutput = (Entity _ Material{..}, E.Value fileNum) } -> if
|
||||
| fileNum == 0 -> mempty
|
||||
| otherwise -> fileCell $ filesLink materialName
|
||||
|
||||
@ -16,6 +16,7 @@ import qualified Data.CaseInsensitive as CI
|
||||
getCTutorialListR :: TermId -> SchoolId -> CourseShorthand -> Handler Html
|
||||
getCTutorialListR tid ssh csh = do
|
||||
Entity cid Course{..} <- runDB . getBy404 $ TermSchoolCourseShort tid ssh csh
|
||||
MsgRenderer mr <- getMsgRenderer
|
||||
|
||||
let
|
||||
tutorialDBTable = DBTable{..}
|
||||
@ -51,7 +52,7 @@ getCTutorialListR tid ssh csh = do
|
||||
, sortable (Just "register-from") (i18nCell MsgTutorialRegisterFrom) $ \DBRow{ dbrOutput = (Entity _ Tutorial{..}, _) } -> maybeDateTimeCell tutorialRegisterFrom
|
||||
, sortable (Just "register-to") (i18nCell MsgTutorialRegisterTo) $ \DBRow{ dbrOutput = (Entity _ Tutorial{..}, _) } -> maybeDateTimeCell tutorialRegisterTo
|
||||
, sortable (Just "deregister-until") (i18nCell MsgTutorialDeregisterUntil) $ \DBRow{ dbrOutput = (Entity _ Tutorial{..}, _) } -> maybeDateTimeCell tutorialDeregisterUntil
|
||||
, sortable Nothing mempty $ \DBRow{ dbrOutput = (Entity _ Tutorial{..}, _) } -> cell $ do
|
||||
, sortable Nothing (mempty & cellAttrs <>~ pure ("uw-hide-columns--hider-label", mr MsgActionsHead)) $ \DBRow{ dbrOutput = (Entity _ Tutorial{..}, _) } -> cell $ do
|
||||
linkButton mempty [whamlet|_{MsgTutorialEdit}|] [BCIsButton] . SomeRoute $ CTutorialR tid ssh csh tutorialName TEditR
|
||||
linkButton mempty [whamlet|_{MsgTutorialDelete}|] [BCIsButton, BCDanger] . SomeRoute $ CTutorialR tid ssh csh tutorialName TDeleteR
|
||||
]
|
||||
|
||||
@ -71,6 +71,7 @@ instance Button UniWorX AllUsersAction where
|
||||
getUsersR, postUsersR :: Handler Html
|
||||
getUsersR = postUsersR
|
||||
postUsersR = do
|
||||
MsgRenderer mr <- getMsgRenderer
|
||||
let
|
||||
dbtColonnade = mconcat $
|
||||
[ dbRow
|
||||
@ -100,7 +101,7 @@ postUsersR = do
|
||||
$forall (E.Value sh) <- schools
|
||||
<li>#{sh}
|
||||
|]
|
||||
, sortable Nothing mempty $ \inp@DBRow{ dbrOutput = Entity uid _ } -> FormCell
|
||||
, sortable Nothing (mempty & cellAttrs <>~ pure ("hide-columns--hider-label", mr MsgActionsHead)) $ \inp@DBRow{ dbrOutput = Entity uid _ } -> FormCell
|
||||
{ formCellAttrs = []
|
||||
, formCellLens = id
|
||||
, formCellContents = do
|
||||
|
||||
@ -554,7 +554,9 @@ examBonusRuleForm prev = multiActionA actions (fslI MsgExamBonusRule) $ classify
|
||||
)
|
||||
]
|
||||
|
||||
data ExamOccurrenceRule' = ExamRoomSurname'
|
||||
data ExamOccurrenceRule' = ExamRoomManual'
|
||||
| ExamRoomFifo'
|
||||
| ExamRoomSurname'
|
||||
| ExamRoomMatriculation'
|
||||
| ExamRoomRandom'
|
||||
deriving (Eq, Ord, Read, Show, Enum, Bounded, Generic, Typeable)
|
||||
@ -566,14 +568,18 @@ embedRenderMessage ''UniWorX ''ExamOccurrenceRule' id
|
||||
|
||||
classifyExamOccurrenceRule :: ExamOccurrenceRule -> ExamOccurrenceRule'
|
||||
classifyExamOccurrenceRule = \case
|
||||
ExamRoomManual -> ExamRoomManual'
|
||||
ExamRoomSurname -> ExamRoomSurname'
|
||||
ExamRoomMatriculation -> ExamRoomMatriculation'
|
||||
ExamRoomRandom -> ExamRoomRandom'
|
||||
ExamRoomFifo -> ExamRoomFifo'
|
||||
|
||||
examOccurrenceRuleForm :: Maybe ExamOccurrenceRule -> AForm Handler ExamOccurrenceRule
|
||||
examOccurrenceRuleForm = fmap reverseClassify . areq (selectField optionsFinite) (fslI MsgExamOccurrenceRule) . fmap classifyExamOccurrenceRule
|
||||
examOccurrenceRuleForm = fmap reverseClassify . areq (selectField optionsFinite) (fslI MsgExamAutomaticOccurrenceAssignment & setTooltip MsgExamAutomaticOccurrenceAssignmentTip) . fmap classifyExamOccurrenceRule
|
||||
where
|
||||
reverseClassify = \case
|
||||
ExamRoomManual' -> ExamRoomManual
|
||||
ExamRoomFifo' -> ExamRoomFifo
|
||||
ExamRoomSurname' -> ExamRoomSurname
|
||||
ExamRoomMatriculation' -> ExamRoomMatriculation
|
||||
ExamRoomRandom' -> ExamRoomRandom
|
||||
|
||||
@ -197,7 +197,9 @@ numCell :: (IsDBTable m a, ToMessage b) => b -> DBCell m a
|
||||
numCell = textCell . toMessage
|
||||
|
||||
propCell :: (IsDBTable m a, Real b, ToMessage b) => b -> b -> DBCell m a
|
||||
propCell curr max' = i18nCell $ MsgProportion (toMessage curr) (toMessage max') (toRational curr / toRational max')
|
||||
propCell curr max'
|
||||
| max' /= 0 = i18nCell $ MsgProportion (toMessage curr) (toMessage max') (toRational curr / toRational max')
|
||||
| otherwise = i18nCell $ MsgProportionNoRatio (toMessage curr) (toMessage max')
|
||||
|
||||
int64Cell :: IsDBTable m a => Int64-> DBCell m a
|
||||
int64Cell = numCell
|
||||
|
||||
@ -1382,7 +1382,7 @@ dbSelect :: forall x h r i a. (Headedness h, Monoid' x)
|
||||
-> (DBRow r -> MForm (HandlerFor UniWorX) i)
|
||||
-> Colonnade h (DBRow r) (DBCell (MForm (HandlerFor UniWorX)) x)
|
||||
-- dbSelect resLens selLens genIndex = Colonnade.singleton (headednessPure $ i18nCell MsgSelectColumn) $ formCell resLens genIndex genForm
|
||||
dbSelect resLens selLens genIndex = Colonnade.singleton (headednessPure $ mempty) $ formCell resLens genIndex genForm
|
||||
dbSelect resLens selLens genIndex = Colonnade.singleton (headednessPure $ mempty & cellAttrs <>~ pure ("uw-hide-columns--no-hide","")) $ formCell resLens genIndex genForm
|
||||
where
|
||||
genForm _ mkUnique = do
|
||||
(selResult, selWidget) <- mreq checkBoxField (fsUniq mkUnique "select") (Just False)
|
||||
|
||||
@ -12,8 +12,9 @@ import Utils.Tokens as Import
|
||||
import Utils.Frontend.Modal as Import
|
||||
import Utils.Lens as Import
|
||||
|
||||
import Settings as Import
|
||||
import Settings.StaticFiles as Import
|
||||
import Settings as Import
|
||||
import Settings.StaticFiles as Import
|
||||
import Settings.WellKnownFiles as Import
|
||||
|
||||
import CryptoID as Import
|
||||
import Audit as Import
|
||||
|
||||
@ -599,6 +599,13 @@ customMigrations = Map.fromListWith (>>)
|
||||
ALTER TABLE "study_features" DROP COLUMN "sub_field";
|
||||
|]
|
||||
)
|
||||
, ( AppliedMigrationKey [migrationVersion|29.0.0|] [version|30.0.0|]
|
||||
, whenM (tableExists "exam") $
|
||||
[executeQQ|
|
||||
UPDATE "exam" SET "occurrence_rule" = #{ExamRoomManual} WHERE "occurrence_rule" IS NULL;
|
||||
ALTER TABLE "exam" ALTER COLUMN "occurrence_rule" SET NOT NULL;
|
||||
|]
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
|
||||
@ -136,7 +136,9 @@ deriveJSON defaultOptions
|
||||
} ''ExamBonusRule
|
||||
derivePersistFieldJSON ''ExamBonusRule
|
||||
|
||||
data ExamOccurrenceRule = ExamRoomSurname
|
||||
data ExamOccurrenceRule = ExamRoomManual
|
||||
| ExamRoomFifo
|
||||
| ExamRoomSurname
|
||||
| ExamRoomMatriculation
|
||||
| ExamRoomRandom
|
||||
deriving (Show, Read, Eq, Ord, Generic, Typeable)
|
||||
|
||||
@ -54,6 +54,8 @@ data AuthTag -- sortiert nach gewünschter Reihenfolge auf /authpreds, d.h. Prä
|
||||
| AuthCourseRegistered
|
||||
| AuthTutorialRegistered
|
||||
| AuthExamRegistered
|
||||
| AuthExamOccurrenceRegistered
|
||||
| AuthExamOccurrenceRegistration
|
||||
| AuthExamResult
|
||||
| AuthParticipant
|
||||
| AuthApplicant
|
||||
|
||||
@ -78,6 +78,8 @@ data AppSettings = AppSettings
|
||||
{ appStaticDir :: FilePath
|
||||
-- ^ Directory from which to serve static files.
|
||||
, appWebpackEntrypoints :: FilePath
|
||||
, appWellKnownDir :: FilePath
|
||||
, appWellKnownLinkFile :: FilePath
|
||||
, appDatabaseConf :: PostgresConf
|
||||
-- ^ Configuration settings for accessing the database.
|
||||
, appAutoDbMigrate :: Bool
|
||||
@ -369,6 +371,8 @@ instance FromJSON AppSettings where
|
||||
False
|
||||
#endif
|
||||
appStaticDir <- o .: "static-dir"
|
||||
appWellKnownDir <- o .: "well-known-dir"
|
||||
appWellKnownLinkFile <- o .: "well-known-link-file"
|
||||
appWebpackEntrypoints <- o .: "webpack-manifest"
|
||||
appDatabaseConf <- o .: "database"
|
||||
appAutoDbMigrate <- o .: "auto-db-migrate"
|
||||
@ -393,7 +397,7 @@ instance FromJSON AppSettings where
|
||||
|
||||
appMailFrom <- o .: "mail-from"
|
||||
appMailObjectDomain <- o .: "mail-object-domain"
|
||||
appMailVerp <- o .: "mail-verp"
|
||||
appMailVerp <- fromMaybe VerpNone . join <$> (o .:? "mail-verp" <|> pure Nothing)
|
||||
appMailSupport <- o .: "mail-support"
|
||||
|
||||
appJobWorkers <- o .: "job-workers"
|
||||
|
||||
@ -1,9 +1,15 @@
|
||||
{-# OPTIONS_GHC -fno-warn-unused-top-binds #-}
|
||||
-- Listing only files directly used by consumers of this module
|
||||
-- prevents rebuilds if files change, that are not directly used (like
|
||||
-- webpack bundles)
|
||||
module Settings.StaticFiles
|
||||
( module Settings.StaticFiles
|
||||
( img_lmu_sigillum_svg
|
||||
, webpackLinks_main
|
||||
, embeddedStatic
|
||||
, module Yesod.EmbeddedStatic
|
||||
) where
|
||||
|
||||
import ClassyPrelude
|
||||
import ClassyPrelude.Yesod
|
||||
|
||||
import Settings (appStaticDir, appWebpackEntrypoints, compileTimeAppSettings)
|
||||
import Settings.StaticFiles.Generator
|
||||
|
||||
@ -12,7 +12,7 @@ import qualified Data.Yaml as Yaml
|
||||
|
||||
import qualified Data.Map as Map
|
||||
|
||||
import Yesod.Core (Route)
|
||||
import Yesod.Core (Route, MonadLogger, MonadWidget, HandlerSite, logDebugS, logErrorS)
|
||||
import Yesod.EmbeddedStatic (EmbeddedStatic)
|
||||
import Yesod.EmbeddedStatic.Types
|
||||
import Network.Mime (MimeType)
|
||||
@ -27,6 +27,8 @@ import Utils (nubOn)
|
||||
|
||||
import System.FilePath (makeRelative)
|
||||
|
||||
import Text.Shakespeare.Text (st)
|
||||
|
||||
|
||||
mkWebpackEntrypoints :: FilePath -- ^ Path to YAML-manifest
|
||||
-> [FilePath -> Generator]
|
||||
@ -54,11 +56,30 @@ mkWebpackEntrypoints manifest mkGen stDir = do
|
||||
Just n -> tell $ pure (n, ebMimeType entry)
|
||||
|
||||
let entryName = mkName $ "webpackEntrypoint_" <> entrypoint
|
||||
widgetName = mkName $ "webpackLinks_" <> entrypoint
|
||||
|
||||
staticR <- newName "staticR"
|
||||
sequence
|
||||
[ sigD entryName [t|[(Route EmbeddedStatic, MimeType)]|]
|
||||
, funD entryName
|
||||
[ clause [] (normalB . listE . map (\(n, mime) -> tupE [varE n, TH.lift mime]) $ nubOn fst entries) []
|
||||
]
|
||||
, sigD widgetName [t|forall m. (MonadLogger m, MonadWidget m) => (Route EmbeddedStatic -> Route (HandlerSite m)) -> m ()|]
|
||||
, funD widgetName
|
||||
[ clause [varP staticR] (normalB [e|
|
||||
do
|
||||
$logDebugS "siteLayout" $ tshow $(varE entryName)
|
||||
forM_ $(varE entryName) $ \(sRoute, mime) ->
|
||||
let ctEq = (==) `on` simpleContentType
|
||||
in if
|
||||
| mime `ctEq` "text/css"
|
||||
-> addStylesheet $ $(varE staticR) sRoute
|
||||
| mime `ctEq` "application/javascript"
|
||||
-> addScript $ $(varE staticR) sRoute
|
||||
| otherwise
|
||||
-> $logErrorS "siteLayout" [st|Unknown mime type in webpack bundle: #{tshow mime}|]
|
||||
|]) []
|
||||
]
|
||||
]
|
||||
where
|
||||
decodeManifest :: FilePath -> Q (Map String [FilePath])
|
||||
|
||||
11
src/Settings/WellKnownFiles.hs
Normal file
11
src/Settings/WellKnownFiles.hs
Normal file
@ -0,0 +1,11 @@
|
||||
module Settings.WellKnownFiles
|
||||
( WellKnownFileName(..)
|
||||
, getWellKnownR
|
||||
, wellKnownHtmlLinks
|
||||
) where
|
||||
|
||||
import Settings.WellKnownFiles.TH
|
||||
|
||||
import Settings (appWellKnownDir, appWellKnownLinkFile, compileTimeAppSettings)
|
||||
|
||||
mkWellKnown "de-de-formal" (appWellKnownDir compileTimeAppSettings) (appWellKnownLinkFile compileTimeAppSettings)
|
||||
192
src/Settings/WellKnownFiles/TH.hs
Normal file
192
src/Settings/WellKnownFiles/TH.hs
Normal file
@ -0,0 +1,192 @@
|
||||
module Settings.WellKnownFiles.TH
|
||||
( mkWellKnown
|
||||
) where
|
||||
|
||||
import ClassyPrelude.Yesod
|
||||
import Utils
|
||||
|
||||
import Language.Haskell.TH
|
||||
import Language.Haskell.TH.Syntax hiding (Lift(..))
|
||||
import qualified Language.Haskell.TH.Syntax as TH (Lift(..))
|
||||
|
||||
import System.Directory.Tree
|
||||
|
||||
import qualified Data.ByteString as BS
|
||||
|
||||
import Utils.Lens.TH
|
||||
import Control.Lens
|
||||
import Data.Set.Lens
|
||||
|
||||
import qualified Data.Text as Text
|
||||
import qualified Data.Text.Encoding as Text
|
||||
import Data.Char as Char (isAlphaNum, toUpper)
|
||||
|
||||
import qualified Data.Map as Map
|
||||
import qualified Data.Set as Set
|
||||
|
||||
import Data.List.NonEmpty (NonEmpty(..))
|
||||
|
||||
import Data.HashMap.Strict (HashMap)
|
||||
import qualified Data.HashMap.Strict as HashMap
|
||||
|
||||
import qualified Data.HashSet as HashSet
|
||||
|
||||
import System.FilePath ((</>), splitDirectories, makeRelative)
|
||||
|
||||
import Settings.Mime
|
||||
|
||||
import Text.Blaze.Html (preEscapedToHtml)
|
||||
|
||||
nWellKnownFileName :: Name
|
||||
nWellKnownFileName = mkName "WellKnownFileName"
|
||||
|
||||
nwellKnownFileNames :: Name
|
||||
nwellKnownFileNames = mkName "wellKnownFileNames"
|
||||
|
||||
ngetWellKnownR :: Name
|
||||
ngetWellKnownR = mkName "getWellKnownR"
|
||||
|
||||
nwellKnownHtmlLinks :: Name
|
||||
nwellKnownHtmlLinks = mkName "wellKnownHtmlLinks"
|
||||
|
||||
|
||||
|
||||
mkWellKnown :: Lang -- ^ Default language
|
||||
-> FilePath -- ^ Base directory
|
||||
-> FilePath -- ^ Link file (@html_code.html@)
|
||||
-> DecsQ
|
||||
mkWellKnown defLang wellKnownBase wellKnownLinks = do
|
||||
inputFiles <- fmap dirTree . liftIO $ readDirectoryWith (\f -> (f, ) <$> BS.readFile f) wellKnownBase
|
||||
|
||||
mapM_ qAddDependentFile $ inputFiles ^.. folded . _1
|
||||
|
||||
-- languageFiles :: Map Lang [(FilePath, ByteString)]
|
||||
languageFiles <- if
|
||||
| Dir{contents} <- inputFiles
|
||||
-> return . Map.fromList $ do
|
||||
(language, lContents) <- contents ^.. folded . $(multifocusL 2) _name id
|
||||
Dir{} <- pure lContents
|
||||
let
|
||||
lContents' :: [(FilePath, ByteString)]
|
||||
lContents' = flip mapMaybe (flattenDir lContents) $ \pFile -> do
|
||||
File{..} <- pure pFile
|
||||
guard $ name /= wellKnownLinks
|
||||
return $ file & _1 %~ makeRelative (wellKnownBase </> language)
|
||||
return (Text.pack language, lContents')
|
||||
| otherwise
|
||||
-> fail "wellKnownBase is not a directory"
|
||||
|
||||
fLanguages <- if
|
||||
| defLang `Set.member` Map.keysSet languageFiles
|
||||
, let languages' = Set.delete defLang $ Map.keysSet languageFiles
|
||||
-> return $ defLang :| Set.toList languages'
|
||||
| otherwise
|
||||
-> fail "default language is missing in wellKnownBase"
|
||||
|
||||
-- languageLinks :: Map Lang ByteString
|
||||
languageLinks <- if
|
||||
| Dir{contents} <- inputFiles
|
||||
-> return . Map.fromList $ do
|
||||
(language, lContents) <- contents ^.. folded . $(multifocusL 2) _name id
|
||||
Dir{} <- pure lContents
|
||||
let
|
||||
lContents' :: [ByteString]
|
||||
lContents' = flip mapMaybe (flattenDir lContents) $ \pFile -> do
|
||||
File{..} <- pure pFile
|
||||
guard $ name == wellKnownLinks
|
||||
return $ file ^. _2
|
||||
c <- lContents'
|
||||
return (Text.pack language, c)
|
||||
| otherwise
|
||||
-> fail "wellKnownBase is not a directory"
|
||||
|
||||
lLanguages <- if
|
||||
| defLang `Set.member` Map.keysSet languageLinks
|
||||
, let languages' = Set.delete defLang $ Map.keysSet languageLinks
|
||||
-> return $ defLang :| Set.toList languages'
|
||||
| otherwise
|
||||
-> fail "default language is missing in wellKnownBase"
|
||||
|
||||
|
||||
fVar <- newName "f"
|
||||
hVar <- newName "h"
|
||||
lVar <- newName "l"
|
||||
|
||||
let fileNames = setOf (folded . folded . _1) languageFiles
|
||||
fileContents = Map.fromListWith (<>) $ do
|
||||
(lang, fs) <- Map.toList languageFiles
|
||||
(fName, fContent) <- fs
|
||||
return ((fContent, mimeLookup $ Text.pack fName), Set.singleton (lang, fName))
|
||||
|
||||
wellKnownFileName = dataD
|
||||
(cxt [])
|
||||
nWellKnownFileName
|
||||
[]
|
||||
Nothing
|
||||
[ normalC (mkName $ fNameManip fName) []
|
||||
| fName <- Set.toList fileNames
|
||||
]
|
||||
(pure $ derivClause Nothing [[t|Eq|], [t|Ord|], [t|Bounded|], [t|Enum|], [t|Read|], [t|Show|], [t|Generic|], [t|Typeable|]])
|
||||
wellKnownFileNameMapSig = sigD
|
||||
nwellKnownFileNames
|
||||
[t|HashMap [Text] $(conT nWellKnownFileName)|]
|
||||
wellKnownFileNameMap = funD
|
||||
nwellKnownFileNames
|
||||
[ clause [] (normalB $ [e|HashMap.fromList|] `appE` listE [ [e|($(TH.lift . map Text.pack $ splitDirectories fName), $(conE . mkName $ fNameManip fName))|] | fName <- Set.toList fileNames ]) []
|
||||
]
|
||||
wellKnownFileNamePathMultiPiece = instanceD
|
||||
(cxt [])
|
||||
(conT ''PathMultiPiece `appT` conT nWellKnownFileName)
|
||||
[ funD 'toPathMultiPiece
|
||||
[ clause [conP (mkName $ fNameManip fName) []] (normalB . TH.lift . map Text.pack $ splitDirectories fName) []
|
||||
| fName <- Set.toList fileNames
|
||||
]
|
||||
, funD 'fromPathMultiPiece $
|
||||
[ clause [] (normalB [e|flip HashMap.lookup $(varE nwellKnownFileNames)|]) []
|
||||
]
|
||||
]
|
||||
wellKnownFileNameHashable = instanceD
|
||||
(cxt [])
|
||||
(conT ''Hashable `appT` conT nWellKnownFileName)
|
||||
[]
|
||||
|
||||
getWellKnownRSig = sigD
|
||||
ngetWellKnownR
|
||||
[t|forall m. MonadHandler m => $(conT nWellKnownFileName) -> m TypedContent|]
|
||||
getWellKnownR = funD
|
||||
ngetWellKnownR
|
||||
[ clause [varP fVar] (normalB [e|$(varE hVar) =<< selectLanguage fLanguages|])
|
||||
[ funD hVar $
|
||||
[ clause [varP lVar] (guardedB
|
||||
[ (,) <$> normalG [e|HashSet.member ($(varE lVar), $(varE fVar)) $ HashSet.fromList $(listE [ tupE [TH.lift l, conE . mkName $ fNameManip fName] | (l, fName) <- Set.toList xs ])|]
|
||||
<*> [e|TypedContent mime (toContent fContent) <$ setEtag $(TH.lift $ hashToText (mime, fContent))|]
|
||||
]) []
|
||||
| ((fContent, mime), xs) <- Map.toList fileContents
|
||||
] ++ pure (clause [wildP] (normalB [e|notFound|]) [])
|
||||
]
|
||||
]
|
||||
|
||||
wellKnownHtmlLinksSig = sigD
|
||||
nwellKnownHtmlLinks
|
||||
[t|forall m. MonadWidget m => m ()|]
|
||||
wellKnownHtmlLinks = funD
|
||||
nwellKnownHtmlLinks
|
||||
[ clause [] (normalB [e|toWidgetHead . preEscapedToHtml . $(varE hVar) =<< selectLanguage lLanguages|])
|
||||
[ sigD hVar [t|Text -> Text|]
|
||||
, funD hVar $
|
||||
[ clause [varP lVar] (guardedB
|
||||
[ (,) <$> normalG [|$(varE lVar) == lang|]
|
||||
<*> TH.lift (Text.filter (`notElem` ['\r', '\n']) $ Text.decodeUtf8 c)
|
||||
]) []
|
||||
| (lang, c) <- Map.toList languageLinks
|
||||
] ++ pure (clause [wildP] (normalB [e|mempty|]) [])
|
||||
]
|
||||
]
|
||||
|
||||
sequence
|
||||
[ wellKnownFileName, wellKnownFileNameMapSig, wellKnownFileNameMap, wellKnownFileNamePathMultiPiece, wellKnownFileNameHashable
|
||||
, getWellKnownRSig, getWellKnownR
|
||||
, wellKnownHtmlLinksSig, wellKnownHtmlLinks
|
||||
]
|
||||
where
|
||||
fNameManip = Text.unpack . mconcat . over (traverse . _head) Char.toUpper . filter (not . null) . Text.split (not . isAlphaNum) . Text.pack
|
||||
@ -913,11 +913,11 @@ cachedHereBinary = do
|
||||
[e| \k -> cachedByBinary (loc, k) |]
|
||||
|
||||
hashToText :: Hashable a => a -> Text
|
||||
hashToText = decodeUtf8 . Base64.encode . toStrict . Binary.encode . hash
|
||||
hashToText = Text.dropWhileEnd (== '=') . decodeUtf8 . Base64.encode . toStrict . Binary.encode . hash
|
||||
|
||||
setEtagHashable, setWeakEtagHashable :: (MonadHandler m, Hashable a) => a -> m ()
|
||||
setEtagHashable = setEtag . hashToText
|
||||
setWeakEtagHashable = setEtag . hashToText
|
||||
setWeakEtagHashable = setWeakEtag . hashToText
|
||||
|
||||
setLastModified :: MonadHandler m => UTCTime -> m ()
|
||||
setLastModified lastModified = do
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 99 KiB |
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 98 KiB |
@ -76,15 +76,15 @@ $maybe desc <- examDescription
|
||||
\ ^{isVisible False}
|
||||
<dd .deflist__dd>
|
||||
^{examBonusW bonusRule}
|
||||
$maybe occurrenceRule <- examOccurrenceRule
|
||||
$if examOccurrenceRule /= ExamRoomManual
|
||||
$if occurrenceAssignmentsShown
|
||||
<dt .deflist__dt>
|
||||
_{MsgExamOccurrenceRuleParticipant}
|
||||
$if not occurrenceAssignmentsVisible
|
||||
\ ^{isVisible False}
|
||||
<dd .deflist__dd>
|
||||
_{classifyExamOccurrenceRule occurrenceRule}
|
||||
$maybe registerWdgt <- registerWidget
|
||||
_{classifyExamOccurrenceRule examOccurrenceRule}
|
||||
$maybe registerWdgt <- registerWidget Nothing
|
||||
<dt .deflist__dt>_{MsgExamRegistration}
|
||||
<dd .deflist__dd>^{registerWdgt}
|
||||
|
||||
@ -103,31 +103,39 @@ $if not (null occurrences)
|
||||
<th .table__th>
|
||||
_{MsgExamRoomName}
|
||||
\ ^{isVisible False}
|
||||
$if occurrenceAssignmentsShown
|
||||
<th .table__th>
|
||||
_{MsgExamRoomRegistered}
|
||||
$if not occurrenceAssignmentsVisible
|
||||
\ ^{isVisible False}
|
||||
<th .table__th>_{MsgExamRoom}
|
||||
$if not examTimes
|
||||
<th .table__th>_{MsgExamRoomTime}
|
||||
$if showOccurrenceRegisterColumn
|
||||
<th .table__th>
|
||||
$if examOccurrenceRule == ExamRoomFifo
|
||||
_{MsgExamRoomRegistered}
|
||||
$else
|
||||
_{MsgExamRoomAssigned}
|
||||
$if not occurrenceAssignmentsVisible
|
||||
\ ^{isVisible False}
|
||||
<th .table__th>_{MsgExamRoomDescription}
|
||||
<tbody>
|
||||
$forall (Entity _occId ExamOccurrence{examOccurrenceName, examOccurrenceRoom, examOccurrenceStart, examOccurrenceEnd, examOccurrenceDescription}, registered) <- occurrences
|
||||
<tr .table__row :occurrenceAssignmentsShown && (not registered && hasRegistration):.occurrence--not-registered>
|
||||
$if occurrenceNamesShown
|
||||
<td .table__td #exam-occurrence__#{examOccurrenceName}>#{examOccurrenceName}
|
||||
$if occurrenceAssignmentsShown
|
||||
<td .table__td>
|
||||
$if registered
|
||||
#{iconOK}
|
||||
<td .table__td>#{examOccurrenceRoom}
|
||||
$if not examTimes
|
||||
<td .table__td>
|
||||
^{formatTimeRangeW SelFormatDateTime examOccurrenceStart examOccurrenceEnd}
|
||||
<td .table__td>
|
||||
$maybe desc <- examOccurrenceDescription
|
||||
#{desc}
|
||||
$forall (occurrence, registered) <- occurrences
|
||||
$with Entity _occId ExamOccurrence{examOccurrenceName, examOccurrenceRoom, examOccurrenceStart, examOccurrenceEnd, examOccurrenceDescription} <- occurrence
|
||||
$with registerWdgt <- registerWidget (Just occurrence)
|
||||
<tr .table__row :markUnregisteredOccurrences (Just occurrence) && not registered:.occurrence--not-registered>
|
||||
$if occurrenceNamesShown
|
||||
<td .table__td #exam-occurrence__#{examOccurrenceName}>#{examOccurrenceName}
|
||||
<td .table__td>#{examOccurrenceRoom}
|
||||
$if not examTimes
|
||||
<td .table__td>
|
||||
^{formatTimeRangeW SelFormatDateTime examOccurrenceStart examOccurrenceEnd}
|
||||
$if showOccurrenceRegisterColumn
|
||||
<td .table__td>
|
||||
$maybe registerWdgt' <- registerWdgt
|
||||
^{registerWdgt'}
|
||||
$nothing
|
||||
$if registered
|
||||
#{iconOK}
|
||||
<td .table__td>
|
||||
$maybe desc <- examOccurrenceDescription
|
||||
#{desc}
|
||||
|
||||
$if gradingShown && not (null examParts)
|
||||
<section>
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
$newline never
|
||||
|
||||
<div .tooltip :isInlineTooltip:.tooltip__inline>
|
||||
<div .tooltip__handle .#{urgency}>
|
||||
<span .tooltip :isInlineTooltip:.tooltip__inline>
|
||||
<span .tooltip__handle .#{urgency}>
|
||||
<i .fas .fa-^{ic}>
|
||||
<div .tooltip__content>
|
||||
<span .tooltip__content>
|
||||
^{tooltip}
|
||||
|
||||
@ -504,7 +504,7 @@ fillDb = do
|
||||
, examName = "Klausur"
|
||||
, examGradingRule = Nothing
|
||||
, examBonusRule = Nothing
|
||||
, examOccurrenceRule = Nothing
|
||||
, examOccurrenceRule = ExamRoomManual
|
||||
, examVisibleFrom = Just now
|
||||
, examRegisterFrom = Just now
|
||||
, examRegisterTo = Just $ addUTCTime (14 * nominalDay) now
|
||||
|
||||
@ -24,6 +24,11 @@ instance Arbitrary (Route EmbeddedStatic) where
|
||||
paramNum <- getNonNegative <$> arbitrary
|
||||
params <- replicateM paramNum $ (,) <$> printableText' <*> printableText
|
||||
return $ embeddedResourceR path params
|
||||
|
||||
|
||||
instance Arbitrary WellKnownFileName where
|
||||
arbitrary = genericArbitrary
|
||||
shrink = genericShrink
|
||||
|
||||
instance Arbitrary SchoolR where
|
||||
arbitrary = genericArbitrary
|
||||
|
||||
@ -6,12 +6,12 @@ spec :: Spec
|
||||
spec = withApp $ do
|
||||
describe "robots.txt" $ do
|
||||
it "gives a 200" $ do
|
||||
get RobotsR
|
||||
get $ WellKnownR RobotsTxt
|
||||
statusIs 200
|
||||
it "has correct User-agent" $ do
|
||||
get RobotsR
|
||||
get $ WellKnownR RobotsTxt
|
||||
bodyContains "User-agent: *"
|
||||
describe "favicon.ico" $ do
|
||||
it "gives a 200" $ do
|
||||
get FaviconR
|
||||
get $ WellKnownR FaviconIco
|
||||
statusIs 200
|
||||
|
||||
@ -53,6 +53,7 @@ import Control.Monad.Catch as X hiding (Handler(..))
|
||||
import Control.Monad.Trans.Resource (runResourceT)
|
||||
|
||||
import Settings
|
||||
import Settings.WellKnownFiles as X
|
||||
|
||||
import Data.CaseInsensitive as X (CI)
|
||||
import qualified Data.CaseInsensitive as CI
|
||||
|
||||
@ -1,5 +1,11 @@
|
||||
const webpack = require('webpack');
|
||||
const path = require('path');
|
||||
const tmp = require('tmp');
|
||||
tmp.setGracefulCleanup();
|
||||
const fs = require('fs');
|
||||
const glob = require('glob');
|
||||
const { execSync } = require('child_process');
|
||||
|
||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
||||
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
|
||||
const ManifestPlugin = require('webpack-manifest-plugin');
|
||||
@ -9,6 +15,8 @@ const TerserPlugin = require('terser-webpack-plugin');
|
||||
const yaml = require('js-yaml');
|
||||
const HashOutput = require('webpack-plugin-hash-output');
|
||||
const postcssPresetEnv = require('postcss-preset-env');
|
||||
const RemovePlugin = require('remove-files-webpack-plugin');
|
||||
const RealFaviconPlugin = require('real-favicon-webpack-plugin');
|
||||
|
||||
const webpackVersion = require('webpack/package.json').version.split('.').slice(0, 2).join('.');
|
||||
const packageVersion = require('./package.json').version;
|
||||
@ -17,57 +25,57 @@ module.exports = {
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
loader: 'babel-loader',
|
||||
loader: 'babel-loader',
|
||||
|
||||
options: {
|
||||
plugins: ['syntax-dynamic-import'],
|
||||
options: {
|
||||
plugins: ['syntax-dynamic-import'],
|
||||
|
||||
presets: [
|
||||
[
|
||||
'@babel/preset-env',
|
||||
{
|
||||
modules: false,
|
||||
targets: {
|
||||
edge: "17",
|
||||
firefox: "50",
|
||||
chrome: "60",
|
||||
safari: "11.1",
|
||||
ie: "11",
|
||||
},
|
||||
useBuiltIns: "usage",
|
||||
corejs: 3
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
test: /\.js$/i,
|
||||
exclude: /node_modules/,
|
||||
presets: [
|
||||
[
|
||||
'@babel/preset-env',
|
||||
{
|
||||
modules: false,
|
||||
targets: {
|
||||
edge: "17",
|
||||
firefox: "50",
|
||||
chrome: "60",
|
||||
safari: "11.1",
|
||||
ie: "11",
|
||||
},
|
||||
useBuiltIns: "usage",
|
||||
corejs: 3
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
test: /\.js$/i,
|
||||
exclude: /node_modules/,
|
||||
},
|
||||
{
|
||||
test: /\.css$/i,
|
||||
use: [ MiniCssExtractPlugin.loader,
|
||||
{ loader: 'css-loader', options: { sourceMap: true }},
|
||||
{ loader: 'postcss-loader', options: {
|
||||
sourceMap: true,
|
||||
plugins: () => [ postcssPresetEnv ]
|
||||
}},
|
||||
sourceMap: true,
|
||||
plugins: () => [ postcssPresetEnv ]
|
||||
}},
|
||||
{ loader: 'resolve-url-loader', options: { sourceMap: true }}
|
||||
]
|
||||
},
|
||||
{
|
||||
test: /\.s(c|a)ss$/i,
|
||||
test: /\.s(c|a)ss$/i,
|
||||
use: [ MiniCssExtractPlugin.loader,
|
||||
{ loader: 'css-loader', options: { sourceMap: true }},
|
||||
{ loader: 'postcss-loader', options: {
|
||||
sourceMap: true,
|
||||
plugins: () => [ postcssPresetEnv ]
|
||||
}},
|
||||
sourceMap: true,
|
||||
plugins: () => [ postcssPresetEnv ]
|
||||
}},
|
||||
{ loader: 'resolve-url-loader', options: { sourceMap: true }},
|
||||
{ loader: 'sass-loader', options: { implementation: require('sass'), sourceMap: true }}
|
||||
]
|
||||
},
|
||||
{
|
||||
test: /\.(woff(2)?|ttf|eot|svg)(\?v=\d+\.\d+\.\d+)?$/i,
|
||||
test: /\.(woff(2)?|ttf|eot|svg)(\?.*)?$/i,
|
||||
use: [
|
||||
{
|
||||
loader: 'file-loader',
|
||||
@ -103,7 +111,8 @@ module.exports = {
|
||||
if (chunk.name) {
|
||||
return chunk.name;
|
||||
}
|
||||
return chunk.modules.map(m => path.relative(m.context, m.request)).join("_");
|
||||
let modules = chunk.modules || [chunk.entryModule];
|
||||
return modules.map(m => path.relative(m.context, m.request)).join("_");
|
||||
}),
|
||||
new webpack.NamedModulesPlugin(),
|
||||
new ManifestPlugin({
|
||||
@ -113,7 +122,9 @@ module.exports = {
|
||||
serialize: yaml.safeDump
|
||||
}),
|
||||
new CleanWebpackPlugin({
|
||||
cleanOnceBeforeBuildPatterns: path.resolve(__dirname, 'static', 'wp-*')
|
||||
cleanOnceBeforeBuildPatterns: [ path.resolve(__dirname, 'static'),
|
||||
path.resolve(__dirname, 'well-known'),
|
||||
]
|
||||
}),
|
||||
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
|
||||
new CopyPlugin([
|
||||
@ -121,7 +132,65 @@ module.exports = {
|
||||
]),
|
||||
new webpack.DefinePlugin({
|
||||
VERSION: JSON.stringify(packageVersion)
|
||||
})
|
||||
}),
|
||||
...(() => {
|
||||
const faviconJson = require('./config/favicon.json');
|
||||
const langs = new Set();
|
||||
function findLangs(json) {
|
||||
if (json && json._i18n) {
|
||||
Object.keys(json).forEach(key => {
|
||||
if (key !== '_i18n') {
|
||||
langs.add(key);
|
||||
}
|
||||
})
|
||||
} else if (Array.isArray(json)) {
|
||||
json.forEach(elem => findLangs(elem));
|
||||
} else if (typeof json === 'object') {
|
||||
Object.keys(json).forEach(key => findLangs(json[key]));
|
||||
}
|
||||
}
|
||||
findLangs(faviconJson);
|
||||
|
||||
function selectLang(lang, json) {
|
||||
if (json && json._i18n) {
|
||||
return json[lang];
|
||||
} else if (Array.isArray(json)) {
|
||||
return json.map(elem => selectLang(lang, elem));
|
||||
} else if (typeof json === 'object') {
|
||||
return Object.fromEntries(Object.entries(json).map(([k, v]) => [k, selectLang(lang, v)]));
|
||||
} else {
|
||||
return json;
|
||||
}
|
||||
}
|
||||
|
||||
const langJsons = {};
|
||||
Array.from(langs).forEach(lang => {
|
||||
langJsons[lang] = selectLang(lang, faviconJson);
|
||||
});
|
||||
|
||||
return Array.from(langs).map(lang => {
|
||||
const tmpobj = tmp.fileSync({ dir: ".", postfix: ".json" });
|
||||
fs.writeSync(tmpobj.fd, JSON.stringify(langJsons[lang]));
|
||||
fs.close(tmpobj.fd);
|
||||
|
||||
return [
|
||||
new RealFaviconPlugin({
|
||||
faviconJson: `./${tmpobj.name}`,
|
||||
outputPath: path.resolve(__dirname, 'well-known', lang),
|
||||
inject: false
|
||||
}),
|
||||
new CopyPlugin([
|
||||
{ from: 'config/robots.txt', to: path.resolve(__dirname, 'well-known', lang, 'robots.txt') },
|
||||
])
|
||||
];
|
||||
}).flat(1);
|
||||
})(),
|
||||
{ apply: compiler => compiler.hooks.afterEmit.tap('AfterEmitPlugin', compilation => {
|
||||
const imgFiles = glob.sync(path.resolve(__dirname, 'well-known', '**', '*.@(png)'));
|
||||
const imgFilesArgs = Array.from(imgFiles).join(" ");
|
||||
execSync(`exiftool -overwrite_original -all= ${imgFilesArgs}`, { stdio: 'inherit' });
|
||||
})
|
||||
}
|
||||
],
|
||||
|
||||
output: {
|
||||
@ -142,7 +211,7 @@ module.exports = {
|
||||
sourceMap: true
|
||||
}),
|
||||
new OptimizeCSSAssetsPlugin({
|
||||
cssProcessorOptions: {
|
||||
cssProcessorOptions: {
|
||||
map: {
|
||||
inline: false
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user