Skip to content
Snippets Groups Projects
Verified Commit fcc71af1 authored by Volker Schukai's avatar Volker Schukai :alien:
Browse files

chore: commit save point

parent ac97ce5a
Branches
Tags 3.102.2
No related merge requests found
Showing
with 6841 additions and 39 deletions
...@@ -44,6 +44,8 @@ include $(MAKEFILE_IMPORT_PATH)s3.mk ...@@ -44,6 +44,8 @@ include $(MAKEFILE_IMPORT_PATH)s3.mk
include $(MAKEFILE_IMPORT_PATH)nodejs.mk include $(MAKEFILE_IMPORT_PATH)nodejs.mk
include $(MAKEFILE_IMPORT_PATH)color.mk include $(MAKEFILE_IMPORT_PATH)color.mk
include $(MAKEFILE_IMPORT_PATH)terminal.mk include $(MAKEFILE_IMPORT_PATH)terminal.mk
include $(MAKEFILE_IMPORT_PATH)jsdoc-json.mk
include $(MAKEFILE_IMPORT_PATH)jsdoc.mk
include $(MAKEFILE_IMPORT_PATH)output.mk include $(MAKEFILE_IMPORT_PATH)output.mk
include $(MAKEFILE_IMPORT_PATH)version.mk include $(MAKEFILE_IMPORT_PATH)version.mk
include $(MAKEFILE_IMPORT_PATH)gitignore.mk include $(MAKEFILE_IMPORT_PATH)gitignore.mk
...@@ -54,6 +56,7 @@ include $(MAKEFILE_IMPORT_PATH)target-update-makefiles.mk ...@@ -54,6 +56,7 @@ include $(MAKEFILE_IMPORT_PATH)target-update-makefiles.mk
include $(MAKEFILE_IMPORT_PATH)target-deploy-tool.mk include $(MAKEFILE_IMPORT_PATH)target-deploy-tool.mk
include $(MAKEFILE_IMPORT_PATH)target-build-nodejs.mk include $(MAKEFILE_IMPORT_PATH)target-build-nodejs.mk
include $(MAKEFILE_IMPORT_PATH)target-git.mk include $(MAKEFILE_IMPORT_PATH)target-git.mk
include $(MAKEFILE_IMPORT_PATH)target-build-jsdoc.mk
include $(MAKEFILE_IMPORT_PATH)terminal-check.mk include $(MAKEFILE_IMPORT_PATH)terminal-check.mk
......
{
"name": "@schukai/monster",
"version": "0.1.15",
"description": "Monster is a simple library for creating fast, robust and lightweight websites.",
"keywords": [
"framework",
"web",
"dom",
"css",
"sass",
"mobile-first",
"app",
"front-end",
"templates",
"schukai",
"core",
"shopcloud",
"alvine",
"monster",
"buildmap",
"stack",
"observer",
"observable",
"uuid",
"node",
"nodelist",
"css-in-js",
"logger",
"log",
"theme"
],
"type": "module",
"homepage": "https://monsterjs.org",
"repository": {
"type": "git",
"url": "https://gitlab.schukai.com/oss/libraries/javascript/monster.git"
},
"author": "schukai GmbH",
"license": "AGPL 3.0"
}
...@@ -12,7 +12,8 @@ import {DELIMITER, Pathfinder, WILDCARD} from "./pathfinder.js"; ...@@ -12,7 +12,8 @@ import {DELIMITER, Pathfinder, WILDCARD} from "./pathfinder.js";
/** /**
* @type {string} parent symbol * @type {string}
* @memberOf Monster.Data
*/ */
export const PARENT = '^'; export const PARENT = '^';
......
...@@ -666,7 +666,7 @@ function runUpdateContent(container, parts, subject) { ...@@ -666,7 +666,7 @@ function runUpdateContent(container, parts, subject) {
} }
/** /**
* @type {HTMLElement} element * @type {HTMLElement}
*/ */
for (const [element] of iterator.entries()) { for (const [element] of iterator.entries()) {
......
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
import './constants.js'; import './constants.js';
// find packages/monster/source/ -type f -name "*.js" -not -name "*namespace*" -not -iname "monster.js" // find packages/monster/source/ -type f -name "*.js" -not -name "*namespace*" -not -iname "monster.js"
import './constraints/isobject.js import './constraints/isobject.js';
import './constraints/valid.js'; import './constraints/valid.js';
import './constraints/invalid.js'; import './constraints/invalid.js';
import './constraints/abstractoperator.js'; import './constraints/abstractoperator.js';
...@@ -92,3 +92,14 @@ import './data/datasource/storage/localstorage.js'; ...@@ -92,3 +92,14 @@ import './data/datasource/storage/localstorage.js';
import './data/datasource/restapi/writeerror.js'; import './data/datasource/restapi/writeerror.js';
import './math/random.js'; import './math/random.js';
/**
* This class has no other purpose than to exist.
*
* @since 2.0.0
* @copyright schukai GmbH
* @memberOf Monster
*/
export class Monster {
}
{
"tags": {
"allowUnknownTags": true
},
"source": {
"include": "application/source/",
"includePattern": "\\.js$",
"excludePattern": ""
},
"plugins": [
"plugins/markdown",
"jsdoc-plantuml"
],
"opts": {
"template": "node_modules/clean-jsdoc-theme",
"encoding": "utf8",
"destination": "deployment/build/docs/",
"recurse": true,
"verbose": true,
"theme_opts": {
"theme": "light",
"title": "Monster ",
"meta": [
{
"name": "author",
"content": "schukai GmbH"
},
{
"name": "description",
"content": "javascript library for optimal use on fast and flexible pages."
}
],
"search": true,
"add_style_path": [
{
"href": "https://monsterjs.org/assets/prettify.css",
"crossorigin": "anonymous",
"rel": "stylesheet"
}
],
"add_script_path": [
{
"src": "https://code.jquery.com/jquery-3.5.1.js",
"integrity": "sha256-QWo7LDvxbWT2tbbQ97B53yJnYU3WhH/C8ycbRAkjPDc=",
"crossorigin": "anonymous"
},
{
"src": "https://monsterjs.org/js/doc/monster-versions.js",
"crossorigin": "anonymous"
}
],
"footer": "<a href='https://about.schukai.com/de/impressum/'>Imprint</a>",
"overlay_scrollbar": {
"options": {
}
},
"resizeable": {
"navbar": {
"min": "300",
"max": "600"
}
},
"codepen": {
"enable_for": [
"examples"
],
"options": {
"title": "awaken the monster in you; javascript library by schukai GmbH",
"description": "the example ...",
"editors": "0012"
}
},
"sections": [
"Tutorials",
"Modules",
"Classes",
"Externals",
"Events",
"Namespaces",
"Mixins",
"Interfaces",
"Global"
]
}
},
"templates": {
"cleverLinks": true,
"monospaceLinks": false,
"default": {
"outputSourceFiles": true
}
},
"plantuml": {
"puml": {
"create": true,
"destination": "deployment/build/docs/puml"
},
"images": {
"create": true,
"destination": "deployment/build/docs/images",
"defaultFormat": "svg"
}
}
}
#############################################################################################
#############################################################################################
##
## INSTALL jsdoc.json
##
#############################################################################################
#############################################################################################
define JSDOCJSON
{
"tags": {
"allowUnknownTags": true
},
"source": {
"include": "$(SOURCE_PATH)",
"includePattern": "\\.js$$",
"excludePattern": ""
},
"plugins": [
"plugins/markdown",
"jsdoc-plantuml"
],
"opts": {
"template": "node_modules/clean-jsdoc-theme",
"encoding": "utf8",
"destination": "$(BUILD_PATH)/docs/",
"recurse": true,
"verbose": true,
"theme_opts": {
"theme": "light",
"title": "$(COMPONENT_NAME)",
"meta": [
{
"name": "author",
"content": "schukai GmbH"
},
{
"name": "description",
"content": "javascript library for ..."
}
],
"search": true,
"add_style_path": [
{
"href": "https://monsterjs.org/assets/prettify.css",
"crossorigin": "anonymous",
"rel": "stylesheet"
}
],
"add_script_path": [
{
"src": "https://code.jquery.com/jquery-3.5.1.js",
"integrity": "sha256-QWo7LDvxbWT2tbbQ97B53yJnYU3WhH/C8ycbRAkjPDc=",
"crossorigin": "anonymous"
},
],
"footer": "<a href='https://about.schukai.com/de/impressum/'>Imprint</a>",
"overlay_scrollbar": {
"options": {
}
},
"resizeable": {
"navbar": {
"min": "300",
"max": "600"
}
},
"codepen": {
"enable_for": [
"examples"
],
"options": {
"title": "javascript library by schukai GmbH",
"description": "the example ...",
"editors": "0012"
}
},
"sections": [
"Tutorials",
"Namespaces",
"Classes",
"Modules",
"Externals",
"Events",
"Mixins",
"Interfaces",
"Global",
"Menu"
]
}
},
"templates": {
"cleverLinks": true,
"monospaceLinks": false,
"default": {
"outputSourceFiles": false
}
},
"plantuml": {
"puml": {
"create": true,
"destination": "$(BUILD_PATH)/docs/puml"
},
"images": {
"create": true,
"destination": "$(BUILD_PATH)/docs/images",
"defaultFormat": "svg"
}
}
}
endef
export JSDOCJSON
$(DEPLOYMENT_PATH)jsdoc.json:
$(QUITE) $(ECHO) "$$JSDOCJSON" >> $@
\ No newline at end of file
#############################################################################################
#############################################################################################
##
## COMMANDS JSDOC
##
#############################################################################################
#############################################################################################
# path and binaries
JSDOC ?= $(NODE_MODULES_BIN_DIR)jsdoc
EXECUTABLES = $(EXECUTABLES:-) $(JSDOC);
\ No newline at end of file
...@@ -22,15 +22,12 @@ BABEL ?= $(NODE_MODULES_BIN_DIR)babel ...@@ -22,15 +22,12 @@ BABEL ?= $(NODE_MODULES_BIN_DIR)babel
UGLIFYJS ?= $(NODE_MODULES_BIN_DIR)uglifyjs UGLIFYJS ?= $(NODE_MODULES_BIN_DIR)uglifyjs
C8 ?= $(NODE_MODULES_BIN_DIR)c8 C8 ?= $(NODE_MODULES_BIN_DIR)c8
MOCHA ?= $(NODE_MODULES_BIN_DIR)mocha MOCHA ?= $(NODE_MODULES_BIN_DIR)mocha
JSDOC ?= $(NODE_MODULES_BIN_DIR)jsdoc
FIXBROKENPLANTUML := $(NODE_MODULES_DIR)jsdoc-plantuml/fixBrokenNodeJS.js FIXBROKENPLANTUML := $(NODE_MODULES_DIR)jsdoc-plantuml/fixBrokenNodeJS.js
PACKAGE_MODIFIED := $(NODE_MODULES_DIR).modified PACKAGE_MODIFIED := $(NODE_MODULES_DIR).modified
PACKAGE_JSON ?= $(NODE_ROOT_DIR)package.json PACKAGE_JSON ?= $(NODE_ROOT_DIR)package.json
$(PACKAGE_MODIFIED): $(PACKAGE_JSON) $(PACKAGE_MODIFIED): $(PACKAGE_JSON)
$(QUIET) $(NPM) install $(QUIET) $(NPM) install
$(QUIET) $(TEST) -s $(FIXBROKENPLANTUML) || $(NODE) $(FIXBROKENPLANTUML) $(QUIET) $(TEST) -s $(FIXBROKENPLANTUML) || $(NODE) $(FIXBROKENPLANTUML)
......
#############################################################################################
#############################################################################################
##
## JSDOC-TARGETS
##
#############################################################################################
#############################################################################################
.PHONY: build-jsdoc
## generate js api docs
build-jsdoc: $(DEPLOYMENT_PATH)jsdoc.json
$(QUIET) $(ECHOMARKER) "create doc"
$(QUIET) cd $(DEPLOYMENT_PATH); $(JSDOC) --tutorials $(DOCUMENTATION_PATH)tutorials/ -c $(DEPLOYMENT_PATH)jsdoc.json $(DOCUMENTATION_PATH)README.md ; cd --
# $(QUIET) $(GREP) -rl 'Documentation generated by' $(THIS_DIR)docs/ | $(XARGS) sed -i '/Documentation generated by/d'
This diff is collapsed.
{ {
"name": "@schukai/monster", "name": "monster",
"version": "0.1.15", "version": "x.x.x",
"description": "Monster is a simple library for creating fast, robust and lightweight websites.", "description": "monster",
"keywords": [
"framework",
"web",
"dom",
"css",
"sass",
"mobile-first",
"app",
"front-end",
"templates",
"schukai",
"core",
"shopcloud",
"alvine",
"monster",
"buildmap",
"stack",
"observer",
"observable",
"uuid",
"node",
"nodelist",
"css-in-js",
"logger",
"log",
"theme"
],
"type": "module",
"homepage": "https://monsterjs.org",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://gitlab.schukai.com/oss/libraries/javascript/monster.git" "url": "https://gitlab.schukai.com/oss/libraries/javascript/monster.git"
}, },
"keywords": [],
"author": "schukai GmbH", "author": "schukai GmbH",
"license": "AGPL 3.0" "license": "see LICENSE file",
"devDependencies": {
"@peculiar/webcrypto": "^1.4.0",
"btoa": "^1.2.1",
"c8": "^7.12.0",
"chai": "^4.3.6",
"chai-dom": "^1.11.0",
"clean-jsdoc-theme": "^4.1.6",
"crypt": "^0.0.2",
"flow-bin": "^0.184.0",
"fs": "^0.0.1-security",
"jsdoc": "^3.6.11",
"jsdoc-plantuml": "^1.0.2",
"jsdom": "^20.0.0",
"jsdom-global": "^3.0.2",
"node-plantuml": "^0.9.0",
"sinon": "^14.0.0",
"url": "^0.11.0",
"url-exist": "3.0.0",
"util": "^0.12.4"
}
} }
# Monster JS
## Introduction
Monster JS is a JavaScript library for creating interactive web applications.
## Installation
npm install @schukai/monster
## Usage
import Monster from '@schukai/monster';
## License
AGPL-3.0
You'll need basic HTML, CSS, and JavaScript skills for Monster. If you're just
starting out with frontend development, you should have a good beginner's
guide open alongside this one.
You can also post your questions on [Stack Overflow](https://stackoverflow.com/questions/ask?tags=javascript,monster).
To get started with Monster, all you need is a text editor and a browser.
The best way is to copy the following example into a file and save it as
index.html.
```
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>awaken the monster in you!</title>
</head>
<body>
<div>your version is
<spay id="version"></spay>
</div>
<script type="module">
import {Version} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@1.31.0/dist/modules/types/version.js';
document.getElementById('version').innerText = new Version('1.0.0').toString();
</script>
</body>
</html>
```
Now open this file with your browser.
What did you do? you used the class 'Version' of monster and created a Version object. They then output this in a span.
**Voila!**
As seen above in the version example, each monster class or function can be used independently.
Alternatively, Monster can be used as a collection of many useful classes and functions via
the `Monster` namespace as a single javascript file.
```
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>awaken the monster in you!</title>
<script src="https://cdn.jsdelivr.net/npm/@schukai/monster@1.31.0/dist/monster.js"></script>
</head>
<body>
<div>your version is
<spay id="version"></spay>
</div>
<script>
document.getElementById('version').innerText = new Monster.Types.Version('1.0.0').toString();
</script>
</body>
</html>
```
Besides the CDN [jsdelivr](https://www.jsdelivr.com/package/npm/@schukai/monster),
Monster can also be obtained via [NPM](https://www.npmjs.com/package/@schukai/monster)
or the [git repos](https://gitlab.schukai.com/oss/libraries/javascript/monster).
Monster itself has no dependencies and does not dictate anything to you,
you can use Monster with Bootstrap, jQuery or other cool frameworks.
Monster is a lightweight, robust and easy-to-use library with modest ambitions.
Monster integrates easily with your existing websites without taking over everything.
Here's ({@tutorial what-is-monster}) what Monster is all about. In section {@tutorial getting-started}
you can read how to integrate Monster into your own web projects.
Monster has a free license and therefore you are free to customize Monster to your needs.
Monster is a collection of functions and classes that can help in the daily work
with Javascript to get faster to the goal. Monster does not require you to be the
only library, nor does it require you to use only Monster.
Monster itself has no dependencies and works perfectly with other frameworks
like jQuery or Bootstrap.
**The design goals of Monster's core library are:**
* Easy integration with existing user interfaces.
* Robust interfaces
* Tested code and good code coverage.
* No dependencies on other libraries
\ No newline at end of file
The DOM classes, functions and constants help to perform various tasks in the browser.
Among other things, the [form components](https://monsterjs.org/en/doc/components/form/latest/) were
developed on the basis of this functionality.
|
The localization classes and functions support you in dealing with
translations and country-specific settings.
\ No newline at end of file
In the monster library, we use current functions and objects for the implementation.
Some functions may not run on older runtimes.
There are several ways to increase compatibility.
One way is [Babel](https://babeljs.io/) and the `@babel/preset-env ` preset.
More information about the browsers supported by Babel can be found in the [browserslist](https://github.com/browserslist/browserslist) project.
## Polyfills
A polyfill can be assembled using the [create-polyfill-service-url](https://www.npmjs.com/package/create-polyfill-service-url) tool.
@startuml
autonumber
Script -> DOM: element = document.createElement('my-element')
create CustomElement
DOM -> CustomElement: constructor()
activate CustomElement
CustomElement -> CustomElement: [initMethodSymbol]()
create ProxyObserver
CustomElement -> ProxyObserver: new
activate CustomElement
CustomElement -> CustomElement: [initMethodSymbol]()
CustomElement --> DOM: Element
DOM --> Script : element
Script -> DOM: document.querySelector('body').append(element)
DOM -> CustomElement : connectedCallback()
note right CustomElement: is only called at\nthe first connection
CustomElement -> CustomElement : [assembleMethodSymbol]()
activate CustomElement
CustomElement -> CustomElement: initShadowRoot()
activate CustomElement
CustomElement -> CustomElement: attachShadow()
CustomElement -> CustomElement: appendChild()
CustomElement --> CustomElement
deactivate CustomElement
CustomElement -> CustomElement: initCSSStylesheet()
create UpdaterSet
CustomElement -> UpdaterSet : new
loop every element
create Updater
CustomElement -> Updater : new
CustomElement -> UpdaterSet:add(Updater)
CustomElement -> Updater : run
end
CustomElement -> CustomElement: assignUpdaterToElement()
activate CustomElement
create MutationObserver
CustomElement -> MutationObserver: new
activate MutationObserver
MutationObserver -> MutationObserver : observe
MutationObserver --> CustomElement
deactivate CustomElement
deactivate CustomElement
deactivate CustomElement
deactivate CustomElement
deactivate CustomElement
... ...
autonumber
Script -> DOM: document.querySelector('monster-confirm-button').parentNode.removeChild(element)
DOM -> CustomElement: disconnectedCallback()
@enduml
\ No newline at end of file
Monster's updater uses a DOM-based approach. The configuration and the template system are valid and parsable HTML.
The configuration is done via some special attributes with a `data-monster-` prefix.
Code is always the most informative. So let's take a look at a complete example right away.
```
// The first thing to do is to include the Updater class.
import {Updater} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@1.31.0/dist/modules/dom/updater.js';
// Now we prepare the html document.
// This is done here via script, but can also be inserted into the document as pure html.
// To do this, simply insert the tag <h1 data-monster-replace="path:headline"></h1>.
const body = document.querySelector('body');
const headline = document.createElement('h1');
headline.setAttribute('data-monster-replace','path:headline')
body.appendChild(headline);
// the data structure
let obj = {
headline: "Go!",
};
// Now comes the real magic. we pass the updater the parent HTMLElement
// and the desired data structure.
const updater = new Updater(body, obj);
// now we get the used data structure. why can't we take the original structure?
// the updater puts a proxy over the data structure and thus allows to monitor changes.
// We would not see changes to the original object.
const subject = updater.getSubject();
// now start the updater
updater.run();
// Now you can change the data structure and the HTML will follow these changes.
// to illustrate, let's put the change into a timer call.
setTimeout(function(){
console.log(obj);
subject['headline'] = "Hello!"
},1000);
```
We have seen how we can change the content of an htm element. now let's look at what options are available.
## Replace
The simplest manipulation is to replace the content of a HTMLElement.
To do this, simply use the `data-monster-replace` attribute (see example).
The syntax is quite simple. The result of the attribute pipe is inserted as content of the
HTMLElement. For the processing the [Pipe](Monster_Data.Pipe.html)<!-- @IGNORE PREVIOUS: link --> and [Transformer class](Monster_Data.Transformer.html)<!-- @IGNORE PREVIOUS: link -->
is used.
If, for example, you have an object `x` with the structure listed below and want to insert the value of the key b, you write: `path:a.b`.
The pipe can then be used to apply operators. For example, `tolower` can be used to convert everything to lowercase.
```
let x = {
a: {
b: "EXAMPLE"
}
}
```
this is how it looks as an attribute:
```
<div data-monster-replace="static:HELLO | tolower"></div>
```
The result is then the following html:
```
<div data-monster-replace="static:hello">hello</div>
```
A full example looks like this:
```
import {Updater} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@1.31.0/dist/modules/dom/updater.js';
const body = document.querySelector('body');
const headline = document.createElement('h1');
headline.setAttribute('data-monster-replace','static:hello')
body.appendChild(headline);
// result in ↦ <div data-monster-replace="static:hello"></div>
new Updater(body).run();
```
## Attributes
Attributes can be added via the `data-monster-attributes` attribute. The syntax is attribute name followed by
a space and the desired pipe definition.
```
<div data-monster-attributes="id static:myid">hello</div>
```
The result is then the following html:
```
<div id="myid" data-monster-attributes="id static:myid">hello</div>
```
Multiple attributes can be separated by commas.
```
<div data-monster-attributes="id static:myid, class static:myclass">hello</div>
```
## Remove
The `data-monster-remove` attribute can be used to remove html elements. it is important to
note that this cannot be undone. Once removed, nodes will not be reinserted.
This tag is removed via the updater
```
<div data-monster-remove></div>
```
## Insert
The strongest feature is adding elements to a node.
For this feature you need a template and the `data-monster-insert` attribute.
The syntax of the attribute is first an id followed by a space. This is then followed by the pipe command.
```
<ol data-monster-insert="myid path:a"></ol>
```
Furthermore, you need a template. The template must have the same string as id.
```
<template id="myid">
<li data-monster-replace="path:myid | index:id | tostring | prefix:x"></li>
</template>
```
In this template, we define the structure of the new elements. In this case, the id is taken
from the dataset `index:id`, converted to a string `tostring`, and an x is placed in front of it `prefix:x`.
The values for the corresponding data must be available as an array.
```
let obj = {
a: [
{"id": 1},
{"id": 2},
{"id": 3},
{"id": 4}
]
};
```
Below we have a complete example. Instead of specifying the template `<template />` in HTML, it is constructed via
javascript `document.createElement('template')`. But it is essentially the same.
```
const body = document.querySelector('body');
const li = document.createElement('li');
li.innerHTML="-/-";
li.setAttribute('data-monster-replace','path:myid | index:id | tostring | prefix:x');
const template = document.createElement('template');
template.setAttribute('id','myid');
template.content.appendChild(li);
body.appendChild(template);
const list = document.createElement('ul');
list.setAttribute('data-monster-insert', 'myid path:a')
body.appendChild(list);
let obj = {
a: [
{"id": 1},
{"id": 2},
{"id": 3},
{"id": 4}
]
};
const updater = new Updater(body, obj)
updater.run();
```
The result will be
```
<ul data-monster-insert="myid path:a">
<li data-monster-replace="path:a.0 | index:id | tostring | prefix:x" data-monster-insert-reference="myid-0">x1</li>
<li data-monster-replace="path:a.1 | index:id | tostring | prefix:x" data-monster-insert-reference="myid-1">x2</li>
<li data-monster-replace="path:a.2 | index:id | tostring | prefix:x" data-monster-insert-reference="myid-2">x3</li>
<li data-monster-replace="path:a.3 | index:id | tostring | prefix:x" data-monster-insert-reference="myid-3">x4</li>
</ul>
```
You can easily add and delete values to the array. The DOM will be adjusted accordingly.
The attribute `data-monster-insert-reference` identifies if the entry already exists.
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment