diff --git a/CHANGELOG.md b/CHANGELOG.md
index 57472cad21ba770edb8f7a2eb0894936d1be0ed8..a08df41984aecb0f2a0f32bb5f9b3b05da096e22 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,11 +8,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 ### Added (for new features, dependecies etc.)
 
-* Displaying and interacting with the entity state.
-* Change password functionality for users of the internal user source.
-* Visually highlighted drop zones for properties and parents in the edit_mode.
-* two new field types for the form_elements module, `file` and `select`. See
-  the module documentation for more information.
+- `ext_applicable` module for building fancy features which append
+  functionality to entities (EXPERIMENTAL).
+- `ext_cosmetics` module which converts http(s) uris in property values into
+  clickable links.
 - Added a menu/toc for the tour
 - Added a previous and next buttons for pages in the tour
 - Added warnings to inform about minimum width when accessing tour and
@@ -20,10 +19,58 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 - Added a tutorial for the edit mode to the documentation
 
 ### Changed (for changes in existing functionality)
-- The heading attributes datatype, path, checksum and size are now placed 
+
+- Updated from bootstrap 3 to bootstrap 5. This is a major change which will
+  possibly break existing custom implementations (e.g. a custom welcome page)
+  since a lot of classes where renamed by bootstrap an other classes have been
+  dropped entirely (e.g. "jumbotron"). Please have a look at
+    * https://getbootstrap.com/docs/5.0/migration/
+    * https://getbootstrap.com/docs/4.6/migration/
+
+### Deprecated (for soon-to-be removed features)
+
+- css-class `.caosdb-property-text-value`. Please use
+  `.caosdb-f-property-text-value` or `.caosdb-v-property-text-value` instead.
+
+### Removed (for now removed features)
+
+* `#subnav` element from navbar which was previously used for spacing
+* `caosdb.form.ready` event
+
+### Fixed
+
+- #212 - form_elements: Drop-down menu shows wrong value after clicking "None"
+- #202 - Make filter fields in edit mode toolbox visible
+- #117 - Reload data model after adding an RT or a Property
+- #214 - Paging panel is hidden.
+- Displaying issues with long lists in property values
+- An issue whereby a grey container would appear above the map when
+  changing the map view.
+- #200 - Re-enabled the file-upload button
+
+### Security (in case of vulnerabilities)
+
+## [v0.3.1]- 2021-06-15
+
+This is the last Bootstrap-3 compatible release.
+
+### Added (for new features, dependecies etc.)
+
+* Displaying and interacting with the entity state.
+* Change password functionality for users of the internal user source. Disable
+  with `BUILD_MODULE_USER_MANAGEMENT=DISABLED` and set the user realm with
+  `BUILD_MODULE_USER_MANAGEMENT_CHANGE_OWN_PASSWORD_REALM=CaosDB`.
+* Visually highlighted drop zones for properties and parents in the edit_mode.
+* two new field types for the form_elements module, `file` and `select`. See
+  the module documentation for more information.
+
+### Changed (for changes in existing functionality)
+- The heading attributes datatype, path, checksum and size are now placed
   in a `details` html element.
 
-### Deprecated (for soon-to-be removed features) 
+### Deprecated (for soon-to-be removed features)
+
+* Any bootstrap-3 dependencies. Please prepare upgrading to bootstrap-5 with the next release.
 
 ### Removed (for now removed features)
 
@@ -31,23 +78,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
   used for versioning functionality before the native versioning was
   implemented. Also, the `BUILD_MODULE_EXT_REVISIONS` is no longer used and can
   be removed from the config files in `build.properties.d/`
-* `#subnav` element from navbar which was previously used for spacing
-* `caosdb.form.ready` event
 
 ### Fixed
 
-* #214 - Paging panel is hidden.
 * #156 - Edit mode for Safari 11
 * #160 - Entity preview for Safari 11
 * Several minor cosmetic flaws
 * Fixed edit mode for Safari 11.
-* Displaying issues with long lists in property values
-* An issue whereby a grey container would appear above the map when
-  changing the map view.
 
 ### Security (in case of vulnerabilities)
 
-## [0.3.0] - 2021-02-10
+## [v0.3.0] - 2021-02-10
 
 ### Added (for new features, dependecies etc.)
 
@@ -77,7 +118,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 - enabled and enhanced autocompletion
 * Login form is hidden behind another button.
 
-### Deprecated (for soon-to-be removed features) 
+### Deprecated (for soon-to-be removed features)
 
 ### Removed (for now removed features)
 
@@ -102,7 +143,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 ### Changed (for changes in existing functionality)
 
-### Deprecated (for soon-to-be removed features) 
+### Deprecated (for soon-to-be removed features)
 
 ### Removed (for now removed features)
 
@@ -140,7 +181,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
   See the doc string of that method for more information.
 - added a layout argument to the create_plot function of ext_bottom_line
 
-### Deprecated (for soon-to-be removed features) 
+### Deprecated (for soon-to-be removed features)
 
 * css class `caosdb-property-text-value` is deprecated because different
   functionality interpreted it differently and most of the uses of this class
@@ -230,20 +271,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 * updated QUnit test framework to 2.9.2
 
 
-### Deprecated (for soon-to-be removed features) 
+### Deprecated (for soon-to-be removed features)
 
 * Image Preview in the FileSystem. The functionality is to be replaced by real
   thumbnails, which cover also non-image data-formats. The thumbnails resource
   is part of the new file system API of the CaosDB Server which is currently
   under development.
 
-### Removed (for now removed features) 
+### Removed (for now removed features)
 
 * Removed non-informative tests for webcaosdb.css
 * Hard-coded image and video preview in the entity panel. The preview of images
   and videos is controlled by the `ext_bottom_line` module now.
 
-### Fixed (for any bug fixes) 
+### Fixed (for any bug fixes)
 
 * #95 - Edit Mode removes property values of reference properties when server
   response for possible reference targets is empty.
diff --git a/Makefile b/Makefile
index 2c12ee4e67becfd7713ae305c4ab7432bda84391..0aa47756d72dcfcaff88efb673b5fa5044f8e05a 100644
--- a/Makefile
+++ b/Makefile
@@ -43,7 +43,8 @@ LIBS_DIR = $(abspath libs)
 TEST_CORE_DIR = $(abspath test/core/)
 TEST_EXT_DIR = $(abspath test/ext)
 TEST_SSS_DIR =$(abspath test/server_side_scripting)
-LIBS = fonts css/fonts css/bootstrap.css css/bootstrap-icons.css js/state-machine.js js/jquery.js js/showdown.js js/dropzone.js css/dropzone.css js/loglevel.js js/leaflet.js css/leaflet.css css/images js/leaflet-latlng-graticule.js js/proj4.js js/proj4leaflet.js js/leaflet-coordinates.js css/leaflet-coordinates.css js/leaflet-graticule.js js/bootstrap-select.js css/bootstrap-select.css js/bootstrap-autocomplete.min.js js/plotly.js js/pako.js js/utif.js js/bootstrap.bundle.min.js
+LIBS = fonts css/fonts css/bootstrap.css css/bootstrap-icons.css js/state-machine.js js/jquery.js js/showdown.js js/dropzone.js css/dropzone.css js/loglevel.js js/leaflet.js css/leaflet.css css/images js/leaflet-latlng-graticule.js js/proj4.js js/proj4leaflet.js js/leaflet-coordinates.js css/leaflet-coordinates.css js/leaflet-graticule.js js/bootstrap-select.js css/bootstrap-select.css js/bootstrap-autocomplete.min.js js/plotly.js js/pako.js js/utif.js js/bootstrap.js
+
 
 TEST_LIBS = $(LIBS) js/qunit.js css/qunit.css $(subst $(TEST_CORE_DIR)/,,$(shell find $(TEST_CORE_DIR)/))
 
@@ -52,9 +53,9 @@ LIBS_SUBDIRS = $(addprefix $(LIBS_DIR)/, js css fonts)
 
 ALL: install
 
-install: clean-ignore-zips install-sss cp-src cp-ext cp-conf $(addprefix $(PUBLIC_DIR)/, $(LIBS)) build_properties merge_xsl merge_js
+install: clean-ignore-zips install-sss cp-src cp-ext cp-conf $(addprefix $(PUBLIC_DIR)/, $(LIBS)) merge_js build_properties merge_xsl
 
-test: clean-ignore-zips install-sss cp-src cp-ext cp-ext-test cp-conf $(addprefix $(PUBLIC_DIR)/, $(TEST_LIBS)) build_properties merge_xsl merge_js
+test: clean-ignore-zips install-sss cp-src cp-ext cp-ext-test cp-conf $(addprefix $(PUBLIC_DIR)/, $(TEST_LIBS)) merge_js build_properties merge_xsl
 	@for f in $(shell find $(TEST_EXT_DIR) -type f -iname *.js) ; do \
 		sed -i "/EXTENSIONS/a \<script src=\"$${f#$(TEST_EXT_DIR)/}\" ></script>" $(PUBLIC_DIR)/index.html ; \
 		echo include $$f; \
@@ -69,7 +70,7 @@ merge_xsl:
 
 merge_js:
 	for f in ${BUILDFILELIST} ; do source "$$f" ; done ; \
-	misc/merge_js.sh $${MODULE_DEPENDENCIES[*]}
+	JS_DIST_BUNDLE=$${JS_DIST_BUNDLE} AUTO_DISCOVER_MODULES=$$AUTO_DISCOVER_MODULES misc/merge_js.sh $${MODULE_DEPENDENCIES[*]}
 
 EXCLUDE_EXPR = %~ %.backup
 BUILDFILELIST = $(filter-out $(EXCLUDE_EXPR),$(wildcard build.properties.d/*))
@@ -95,24 +96,24 @@ build_properties:
 	@ln -s $(PUBLIC_DIR) $(PUBLIC_DIR)/$(BUILD_NUMBER)
 	@ln -s $(PUBLIC_DIR) $(PUBLIC_DIR)/webinterface
 
-PORT = 8000
+TEST_PORT ?= 8000
 TIMEOUT = 60
 run-test-server: test
-	$(MISC_DIR)/unit_test_http_server.py $(PORT) $(TIMEOUT) False $(PUBLIC_DIR)
+	$(MISC_DIR)/unit_test_http_server.py $(TEST_PORT) $(TIMEOUT) False $(PUBLIC_DIR)
 
 keep-test-server: test
-	$(MISC_DIR)/unit_test_http_server.py $(PORT) -1 True $(PUBLIC_DIR)
+	$(MISC_DIR)/unit_test_http_server.py $(TEST_PORT) -1 True $(PUBLIC_DIR)
 
 run-qunit: test
 	$(foreach exec, firefox Xvfb xwd,\
 	    $(if $(shell which $(exec)),echo "found $(exec)",$(error "No $(exec) in PATH")))
 
 	# start server in background
-	$(MISC_DIR)/unit_test_http_server.py $(PORT) $(TIMEOUT) False $(PUBLIC_DIR) &
+	$(MISC_DIR)/unit_test_http_server.py $(TEST_PORT) $(TIMEOUT) False $(PUBLIC_DIR) &
 
 	# start firefox with virtual xserver
 	Xvfb :1 -screen 0 1024x768x24 &
-	DISPLAY=:1 firefox "http://localhost:$(PORT)/?hidepassed" &
+	DISPLAY=:1 firefox "http://localhost:$(TEST_PORT)/?hidepassed" &
 
 	while [ 1 -eq 1 ]; do \
 		sleep 5 ; \
@@ -218,10 +219,10 @@ $(LIBS_DIR)/fonts: unzip
 	ln -s $(LIBS_DIR)/bootstrap-icons-1.4.1/fonts/ $@
 
 $(LIBS_DIR)/js/bootstrap.js: unzip $(LIBS_DIR)/js
-	ln -s $(LIBS_DIR)/bootstrap-5.0.0-beta3-dist/js/bootstrap.min.js $@
+	ln -s $(LIBS_DIR)/bootstrap-5.0.1-dist/js/bootstrap.bundle.min.js $@
 
 $(LIBS_DIR)/css/bootstrap.css: unzip $(LIBS_DIR)/css
-	ln -s $(LIBS_DIR)/bootstrap-5.0.0-beta3-dist/css/bootstrap.min.css $@
+	ln -s $(LIBS_DIR)/bootstrap-5.0.1-dist/css/bootstrap.min.css $@
 
 $(LIBS_DIR)/css/bootstrap-icons.css: unzip $(LIBS_DIR)/css
 	ln -s $(LIBS_DIR)/bootstrap-icons-1.4.1/bootstrap-icons.css $@
diff --git a/README.md b/README.md
index 5abbfafe8f45ad430e91d66ddd50f055cad61a25..dca645c4244573ae31c7e0072208b53a47cec1d2 100644
--- a/README.md
+++ b/README.md
@@ -35,7 +35,9 @@ when creating the merge request. This allows our team to work with you on your r
 - If you have a suggestion for the [documentation](https://docs.indiscale.com/caosdb-webui/),
 the preferred way is also a merge request as describe above (the documentation resides in `src/doc`).
 However, you can also create an issue for it.
-- You can also contact us at **info (AT) caosdb.de**.
+- You can also contact us at **info (AT) caosdb.de**  and join the
+  CaosDB community on
+  [#caosdb:matrix.org](https://matrix.to/#/!unwwlTfOznjEnMMXxf:matrix.org).
 
 ## License
 
diff --git a/build.properties.d/00_default.properties b/build.properties.d/00_default.properties
index 36d296789f6058d106b450f405af726bf0273b9d..58d20c521100bbd43e094c2da402abc38ea555bc 100644
--- a/build.properties.d/00_default.properties
+++ b/build.properties.d/00_default.properties
@@ -50,6 +50,7 @@ BUILD_MODULE_EXT_BOTTOM_LINE=ENABLED
 BUILD_MODULE_EXT_BOTTOM_LINE_TABLE_PREVIEW=DISABLED
 BUILD_MODULE_EXT_BOTTOM_LINE_TIFF_PREVIEW=DISABLED
 BUILD_MODULE_EXT_BOOKMARKS=ENABLED
+BUILD_MODULE_EXT_ANNOTATION=ENABLED
 
 BUILD_MODULE_USER_MANAGEMENT=ENABLED
 BUILD_MODULE_USER_MANAGEMENT_CHANGE_OWN_PASSWORD_REALM=CaosDB
@@ -91,6 +92,18 @@ BUILD_CUSTOM_IMPRINT='<p> Put an imprint note here </p>'
 ##############################################################################
 BUILD_MODULE_EXT_TRIGGER_CRAWLER_FORM_TOOLBOX="Tools"
 
+##############################################################################
+# Build a dist file containing all JS code from the files in the
+# MODULE_DEPENDENCIES array.
+##############################################################################
+JS_DIST_BUNDLE=TRUE
+##############################################################################
+# TRUE means that all javascript sources which are no mentioned in the
+# MODULE_DEPENDENCIES array will be added in no particular order into the
+# build. If you need to guarantee a specific order (in which the are loaded or
+# appear in the dit file) you need to add them to the MODULE_DEPENDENCIES.
+##############################################################################
+AUTO_DISCOVER_MODULES=TRUE
 ##############################################################################
 # Module dependencies
 # Override or extend to specify the order of js files in the resulting
@@ -98,7 +111,7 @@ BUILD_MODULE_EXT_TRIGGER_CRAWLER_FORM_TOOLBOX="Tools"
 ##############################################################################
 MODULE_DEPENDENCIES=(
     jquery.js
-    bootstrap.bundle.min.js
+    bootstrap.js
     bootstrap-autocomplete.min.js
     bootstrap-select.js
     state-machine.js
@@ -114,6 +127,7 @@ MODULE_DEPENDENCIES=(
     ext_autocomplete.js
     preview.js
     ext_references.js
+    ext_applicable.js
     ext_table_preview.js
     ext_xls_download.js
     query_shortcuts.js
diff --git a/libs/bootstrap-5.0.0-beta3-dist.zip b/libs/bootstrap-5.0.0-beta3-dist.zip
deleted file mode 100644
index c10719c59c4b76cf71ad04985feee6b5cbf73bcd..0000000000000000000000000000000000000000
Binary files a/libs/bootstrap-5.0.0-beta3-dist.zip and /dev/null differ
diff --git a/libs/bootstrap-5.0.1-dist.zip b/libs/bootstrap-5.0.1-dist.zip
new file mode 100644
index 0000000000000000000000000000000000000000..196a8212a471170f120de352455dbcf9f3466639
Binary files /dev/null and b/libs/bootstrap-5.0.1-dist.zip differ
diff --git a/libs/bootstrap.bundle.min.js b/libs/bootstrap.bundle.min.js
deleted file mode 100644
index 2168d63301800ff5696abcb4ba522a6a0a5a7533..0000000000000000000000000000000000000000
--- a/libs/bootstrap.bundle.min.js
+++ /dev/null
@@ -1,7 +0,0 @@
-/*!
-  * Bootstrap v5.0.0-beta3 (https://getbootstrap.com/)
-  * Copyright 2011-2021 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
-  * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
-  */
-!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).bootstrap=e()}(this,(function(){"use strict";const t=t=>{do{t+=Math.floor(1e6*Math.random())}while(document.getElementById(t));return t},e=t=>{let e=t.getAttribute("data-bs-target");if(!e||"#"===e){let i=t.getAttribute("href");if(!i||!i.includes("#")&&!i.startsWith("."))return null;i.includes("#")&&!i.startsWith("#")&&(i="#"+i.split("#")[1]),e=i&&"#"!==i?i.trim():null}return e},i=t=>{const i=e(t);return i&&document.querySelector(i)?i:null},s=t=>{const i=e(t);return i?document.querySelector(i):null},n=t=>{if(!t)return 0;let{transitionDuration:e,transitionDelay:i}=window.getComputedStyle(t);const s=Number.parseFloat(e),n=Number.parseFloat(i);return s||n?(e=e.split(",")[0],i=i.split(",")[0],1e3*(Number.parseFloat(e)+Number.parseFloat(i))):0},o=t=>{t.dispatchEvent(new Event("transitionend"))},r=t=>(t[0]||t).nodeType,a=(t,e)=>{let i=!1;const s=e+5;t.addEventListener("transitionend",(function e(){i=!0,t.removeEventListener("transitionend",e)})),setTimeout(()=>{i||o(t)},s)},l=(t,e,i)=>{Object.keys(i).forEach(s=>{const n=i[s],o=e[s],a=o&&r(o)?"element":null==(l=o)?""+l:{}.toString.call(l).match(/\s([a-z]+)/i)[1].toLowerCase();var l;if(!new RegExp(n).test(a))throw new TypeError(t.toUpperCase()+": "+`Option "${s}" provided type "${a}" `+`but expected type "${n}".`)})},c=t=>{if(!t)return!1;if(t.style&&t.parentNode&&t.parentNode.style){const e=getComputedStyle(t),i=getComputedStyle(t.parentNode);return"none"!==e.display&&"none"!==i.display&&"hidden"!==e.visibility}return!1},d=t=>!t||t.nodeType!==Node.ELEMENT_NODE||!!t.classList.contains("disabled")||(void 0!==t.disabled?t.disabled:t.hasAttribute("disabled")&&"false"!==t.getAttribute("disabled")),h=t=>{if(!document.documentElement.attachShadow)return null;if("function"==typeof t.getRootNode){const e=t.getRootNode();return e instanceof ShadowRoot?e:null}return t instanceof ShadowRoot?t:t.parentNode?h(t.parentNode):null},f=()=>function(){},u=t=>t.offsetHeight,p=()=>{const{jQuery:t}=window;return t&&!document.body.hasAttribute("data-bs-no-jquery")?t:null},g=()=>"rtl"===document.documentElement.dir,m=(t,e)=>{var i;i=()=>{const i=p();if(i){const s=i.fn[t];i.fn[t]=e.jQueryInterface,i.fn[t].Constructor=e,i.fn[t].noConflict=()=>(i.fn[t]=s,e.jQueryInterface)}},"loading"===document.readyState?document.addEventListener("DOMContentLoaded",i):i()},_=new Map;var b={set(t,e,i){_.has(t)||_.set(t,new Map);const s=_.get(t);s.has(e)||0===s.size?s.set(e,i):console.error(`Bootstrap doesn't allow more than one instance per element. Bound instance: ${Array.from(s.keys())[0]}.`)},get:(t,e)=>_.has(t)&&_.get(t).get(e)||null,remove(t,e){if(!_.has(t))return;const i=_.get(t);i.delete(e),0===i.size&&_.delete(t)}};const v=/[^.]*(?=\..*)\.|.*/,y=/\..*/,w=/::\d+$/,E={};let T=1;const A={mouseenter:"mouseover",mouseleave:"mouseout"},L=new Set(["click","dblclick","mouseup","mousedown","contextmenu","mousewheel","DOMMouseScroll","mouseover","mouseout","mousemove","selectstart","selectend","keydown","keypress","keyup","orientationchange","touchstart","touchmove","touchend","touchcancel","pointerdown","pointermove","pointerup","pointerleave","pointercancel","gesturestart","gesturechange","gestureend","focus","blur","change","reset","select","submit","focusin","focusout","load","unload","beforeunload","resize","move","DOMContentLoaded","readystatechange","error","abort","scroll"]);function O(t,e){return e&&`${e}::${T++}`||t.uidEvent||T++}function k(t){const e=O(t);return t.uidEvent=e,E[e]=E[e]||{},E[e]}function D(t,e,i=null){const s=Object.keys(t);for(let n=0,o=s.length;n<o;n++){const o=t[s[n]];if(o.originalHandler===e&&o.delegationSelector===i)return o}return null}function x(t,e,i){const s="string"==typeof e,n=s?i:e;let o=t.replace(y,"");const r=A[o];return r&&(o=r),L.has(o)||(o=t),[s,n,o]}function C(t,e,i,s,n){if("string"!=typeof e||!t)return;i||(i=s,s=null);const[o,r,a]=x(e,i,s),l=k(t),c=l[a]||(l[a]={}),d=D(c,r,o?i:null);if(d)return void(d.oneOff=d.oneOff&&n);const h=O(r,e.replace(v,"")),f=o?function(t,e,i){return function s(n){const o=t.querySelectorAll(e);for(let{target:e}=n;e&&e!==this;e=e.parentNode)for(let r=o.length;r--;)if(o[r]===e)return n.delegateTarget=e,s.oneOff&&N.off(t,n.type,i),i.apply(e,[n]);return null}}(t,i,s):function(t,e){return function i(s){return s.delegateTarget=t,i.oneOff&&N.off(t,s.type,e),e.apply(t,[s])}}(t,i);f.delegationSelector=o?i:null,f.originalHandler=r,f.oneOff=n,f.uidEvent=h,c[h]=f,t.addEventListener(a,f,o)}function S(t,e,i,s,n){const o=D(e[i],s,n);o&&(t.removeEventListener(i,o,Boolean(n)),delete e[i][o.uidEvent])}const N={on(t,e,i,s){C(t,e,i,s,!1)},one(t,e,i,s){C(t,e,i,s,!0)},off(t,e,i,s){if("string"!=typeof e||!t)return;const[n,o,r]=x(e,i,s),a=r!==e,l=k(t),c=e.startsWith(".");if(void 0!==o){if(!l||!l[r])return;return void S(t,l,r,o,n?i:null)}c&&Object.keys(l).forEach(i=>{!function(t,e,i,s){const n=e[i]||{};Object.keys(n).forEach(o=>{if(o.includes(s)){const s=n[o];S(t,e,i,s.originalHandler,s.delegationSelector)}})}(t,l,i,e.slice(1))});const d=l[r]||{};Object.keys(d).forEach(i=>{const s=i.replace(w,"");if(!a||e.includes(s)){const e=d[i];S(t,l,r,e.originalHandler,e.delegationSelector)}})},trigger(t,e,i){if("string"!=typeof e||!t)return null;const s=p(),n=e.replace(y,""),o=e!==n,r=L.has(n);let a,l=!0,c=!0,d=!1,h=null;return o&&s&&(a=s.Event(e,i),s(t).trigger(a),l=!a.isPropagationStopped(),c=!a.isImmediatePropagationStopped(),d=a.isDefaultPrevented()),r?(h=document.createEvent("HTMLEvents"),h.initEvent(n,l,!0)):h=new CustomEvent(e,{bubbles:l,cancelable:!0}),void 0!==i&&Object.keys(i).forEach(t=>{Object.defineProperty(h,t,{get:()=>i[t]})}),d&&h.preventDefault(),c&&t.dispatchEvent(h),h.defaultPrevented&&void 0!==a&&a.preventDefault(),h}};class j{constructor(t){(t="string"==typeof t?document.querySelector(t):t)&&(this._element=t,b.set(this._element,this.constructor.DATA_KEY,this))}dispose(){b.remove(this._element,this.constructor.DATA_KEY),this._element=null}static getInstance(t){return b.get(t,this.DATA_KEY)}static get VERSION(){return"5.0.0-beta3"}}class P extends j{static get DATA_KEY(){return"bs.alert"}close(t){const e=t?this._getRootElement(t):this._element,i=this._triggerCloseEvent(e);null===i||i.defaultPrevented||this._removeElement(e)}_getRootElement(t){return s(t)||t.closest(".alert")}_triggerCloseEvent(t){return N.trigger(t,"close.bs.alert")}_removeElement(t){if(t.classList.remove("show"),!t.classList.contains("fade"))return void this._destroyElement(t);const e=n(t);N.one(t,"transitionend",()=>this._destroyElement(t)),a(t,e)}_destroyElement(t){t.parentNode&&t.parentNode.removeChild(t),N.trigger(t,"closed.bs.alert")}static jQueryInterface(t){return this.each((function(){let e=b.get(this,"bs.alert");e||(e=new P(this)),"close"===t&&e[t](this)}))}static handleDismiss(t){return function(e){e&&e.preventDefault(),t.close(this)}}}N.on(document,"click.bs.alert.data-api",'[data-bs-dismiss="alert"]',P.handleDismiss(new P)),m("alert",P);class I extends j{static get DATA_KEY(){return"bs.button"}toggle(){this._element.setAttribute("aria-pressed",this._element.classList.toggle("active"))}static jQueryInterface(t){return this.each((function(){let e=b.get(this,"bs.button");e||(e=new I(this)),"toggle"===t&&e[t]()}))}}function M(t){return"true"===t||"false"!==t&&(t===Number(t).toString()?Number(t):""===t||"null"===t?null:t)}function R(t){return t.replace(/[A-Z]/g,t=>"-"+t.toLowerCase())}N.on(document,"click.bs.button.data-api",'[data-bs-toggle="button"]',t=>{t.preventDefault();const e=t.target.closest('[data-bs-toggle="button"]');let i=b.get(e,"bs.button");i||(i=new I(e)),i.toggle()}),m("button",I);const B={setDataAttribute(t,e,i){t.setAttribute("data-bs-"+R(e),i)},removeDataAttribute(t,e){t.removeAttribute("data-bs-"+R(e))},getDataAttributes(t){if(!t)return{};const e={};return Object.keys(t.dataset).filter(t=>t.startsWith("bs")).forEach(i=>{let s=i.replace(/^bs/,"");s=s.charAt(0).toLowerCase()+s.slice(1,s.length),e[s]=M(t.dataset[i])}),e},getDataAttribute:(t,e)=>M(t.getAttribute("data-bs-"+R(e))),offset(t){const e=t.getBoundingClientRect();return{top:e.top+document.body.scrollTop,left:e.left+document.body.scrollLeft}},position:t=>({top:t.offsetTop,left:t.offsetLeft})},H={find:(t,e=document.documentElement)=>[].concat(...Element.prototype.querySelectorAll.call(e,t)),findOne:(t,e=document.documentElement)=>Element.prototype.querySelector.call(e,t),children:(t,e)=>[].concat(...t.children).filter(t=>t.matches(e)),parents(t,e){const i=[];let s=t.parentNode;for(;s&&s.nodeType===Node.ELEMENT_NODE&&3!==s.nodeType;)s.matches(e)&&i.push(s),s=s.parentNode;return i},prev(t,e){let i=t.previousElementSibling;for(;i;){if(i.matches(e))return[i];i=i.previousElementSibling}return[]},next(t,e){let i=t.nextElementSibling;for(;i;){if(i.matches(e))return[i];i=i.nextElementSibling}return[]}},W={interval:5e3,keyboard:!0,slide:!1,pause:"hover",wrap:!0,touch:!0},U={interval:"(number|boolean)",keyboard:"boolean",slide:"(boolean|string)",pause:"(string|boolean)",wrap:"boolean",touch:"boolean"},$="next",F="prev",z="left",K="right";class Y extends j{constructor(t,e){super(t),this._items=null,this._interval=null,this._activeElement=null,this._isPaused=!1,this._isSliding=!1,this.touchTimeout=null,this.touchStartX=0,this.touchDeltaX=0,this._config=this._getConfig(e),this._indicatorsElement=H.findOne(".carousel-indicators",this._element),this._touchSupported="ontouchstart"in document.documentElement||navigator.maxTouchPoints>0,this._pointerEvent=Boolean(window.PointerEvent),this._addEventListeners()}static get Default(){return W}static get DATA_KEY(){return"bs.carousel"}next(){this._isSliding||this._slide($)}nextWhenVisible(){!document.hidden&&c(this._element)&&this.next()}prev(){this._isSliding||this._slide(F)}pause(t){t||(this._isPaused=!0),H.findOne(".carousel-item-next, .carousel-item-prev",this._element)&&(o(this._element),this.cycle(!0)),clearInterval(this._interval),this._interval=null}cycle(t){t||(this._isPaused=!1),this._interval&&(clearInterval(this._interval),this._interval=null),this._config&&this._config.interval&&!this._isPaused&&(this._updateInterval(),this._interval=setInterval((document.visibilityState?this.nextWhenVisible:this.next).bind(this),this._config.interval))}to(t){this._activeElement=H.findOne(".active.carousel-item",this._element);const e=this._getItemIndex(this._activeElement);if(t>this._items.length-1||t<0)return;if(this._isSliding)return void N.one(this._element,"slid.bs.carousel",()=>this.to(t));if(e===t)return this.pause(),void this.cycle();const i=t>e?$:F;this._slide(i,this._items[t])}dispose(){N.off(this._element,".bs.carousel"),this._items=null,this._config=null,this._interval=null,this._isPaused=null,this._isSliding=null,this._activeElement=null,this._indicatorsElement=null,super.dispose()}_getConfig(t){return t={...W,...t},l("carousel",t,U),t}_handleSwipe(){const t=Math.abs(this.touchDeltaX);if(t<=40)return;const e=t/this.touchDeltaX;this.touchDeltaX=0,e&&this._slide(e>0?K:z)}_addEventListeners(){this._config.keyboard&&N.on(this._element,"keydown.bs.carousel",t=>this._keydown(t)),"hover"===this._config.pause&&(N.on(this._element,"mouseenter.bs.carousel",t=>this.pause(t)),N.on(this._element,"mouseleave.bs.carousel",t=>this.cycle(t))),this._config.touch&&this._touchSupported&&this._addTouchEventListeners()}_addTouchEventListeners(){const t=t=>{!this._pointerEvent||"pen"!==t.pointerType&&"touch"!==t.pointerType?this._pointerEvent||(this.touchStartX=t.touches[0].clientX):this.touchStartX=t.clientX},e=t=>{this.touchDeltaX=t.touches&&t.touches.length>1?0:t.touches[0].clientX-this.touchStartX},i=t=>{!this._pointerEvent||"pen"!==t.pointerType&&"touch"!==t.pointerType||(this.touchDeltaX=t.clientX-this.touchStartX),this._handleSwipe(),"hover"===this._config.pause&&(this.pause(),this.touchTimeout&&clearTimeout(this.touchTimeout),this.touchTimeout=setTimeout(t=>this.cycle(t),500+this._config.interval))};H.find(".carousel-item img",this._element).forEach(t=>{N.on(t,"dragstart.bs.carousel",t=>t.preventDefault())}),this._pointerEvent?(N.on(this._element,"pointerdown.bs.carousel",e=>t(e)),N.on(this._element,"pointerup.bs.carousel",t=>i(t)),this._element.classList.add("pointer-event")):(N.on(this._element,"touchstart.bs.carousel",e=>t(e)),N.on(this._element,"touchmove.bs.carousel",t=>e(t)),N.on(this._element,"touchend.bs.carousel",t=>i(t)))}_keydown(t){/input|textarea/i.test(t.target.tagName)||("ArrowLeft"===t.key?(t.preventDefault(),this._slide(z)):"ArrowRight"===t.key&&(t.preventDefault(),this._slide(K)))}_getItemIndex(t){return this._items=t&&t.parentNode?H.find(".carousel-item",t.parentNode):[],this._items.indexOf(t)}_getItemByOrder(t,e){const i=t===$,s=t===F,n=this._getItemIndex(e),o=this._items.length-1;if((s&&0===n||i&&n===o)&&!this._config.wrap)return e;const r=(n+(s?-1:1))%this._items.length;return-1===r?this._items[this._items.length-1]:this._items[r]}_triggerSlideEvent(t,e){const i=this._getItemIndex(t),s=this._getItemIndex(H.findOne(".active.carousel-item",this._element));return N.trigger(this._element,"slide.bs.carousel",{relatedTarget:t,direction:e,from:s,to:i})}_setActiveIndicatorElement(t){if(this._indicatorsElement){const e=H.findOne(".active",this._indicatorsElement);e.classList.remove("active"),e.removeAttribute("aria-current");const i=H.find("[data-bs-target]",this._indicatorsElement);for(let e=0;e<i.length;e++)if(Number.parseInt(i[e].getAttribute("data-bs-slide-to"),10)===this._getItemIndex(t)){i[e].classList.add("active"),i[e].setAttribute("aria-current","true");break}}}_updateInterval(){const t=this._activeElement||H.findOne(".active.carousel-item",this._element);if(!t)return;const e=Number.parseInt(t.getAttribute("data-bs-interval"),10);e?(this._config.defaultInterval=this._config.defaultInterval||this._config.interval,this._config.interval=e):this._config.interval=this._config.defaultInterval||this._config.interval}_slide(t,e){const i=this._directionToOrder(t),s=H.findOne(".active.carousel-item",this._element),o=this._getItemIndex(s),r=e||this._getItemByOrder(i,s),l=this._getItemIndex(r),c=Boolean(this._interval),d=i===$,h=d?"carousel-item-start":"carousel-item-end",f=d?"carousel-item-next":"carousel-item-prev",p=this._orderToDirection(i);if(r&&r.classList.contains("active"))this._isSliding=!1;else if(!this._triggerSlideEvent(r,p).defaultPrevented&&s&&r){if(this._isSliding=!0,c&&this.pause(),this._setActiveIndicatorElement(r),this._activeElement=r,this._element.classList.contains("slide")){r.classList.add(f),u(r),s.classList.add(h),r.classList.add(h);const t=n(s);N.one(s,"transitionend",()=>{r.classList.remove(h,f),r.classList.add("active"),s.classList.remove("active",f,h),this._isSliding=!1,setTimeout(()=>{N.trigger(this._element,"slid.bs.carousel",{relatedTarget:r,direction:p,from:o,to:l})},0)}),a(s,t)}else s.classList.remove("active"),r.classList.add("active"),this._isSliding=!1,N.trigger(this._element,"slid.bs.carousel",{relatedTarget:r,direction:p,from:o,to:l});c&&this.cycle()}}_directionToOrder(t){return[K,z].includes(t)?g()?t===K?F:$:t===K?$:F:t}_orderToDirection(t){return[$,F].includes(t)?g()?t===$?z:K:t===$?K:z:t}static carouselInterface(t,e){let i=b.get(t,"bs.carousel"),s={...W,...B.getDataAttributes(t)};"object"==typeof e&&(s={...s,...e});const n="string"==typeof e?e:s.slide;if(i||(i=new Y(t,s)),"number"==typeof e)i.to(e);else if("string"==typeof n){if(void 0===i[n])throw new TypeError(`No method named "${n}"`);i[n]()}else s.interval&&s.ride&&(i.pause(),i.cycle())}static jQueryInterface(t){return this.each((function(){Y.carouselInterface(this,t)}))}static dataApiClickHandler(t){const e=s(this);if(!e||!e.classList.contains("carousel"))return;const i={...B.getDataAttributes(e),...B.getDataAttributes(this)},n=this.getAttribute("data-bs-slide-to");n&&(i.interval=!1),Y.carouselInterface(e,i),n&&b.get(e,"bs.carousel").to(n),t.preventDefault()}}N.on(document,"click.bs.carousel.data-api","[data-bs-slide], [data-bs-slide-to]",Y.dataApiClickHandler),N.on(window,"load.bs.carousel.data-api",()=>{const t=H.find('[data-bs-ride="carousel"]');for(let e=0,i=t.length;e<i;e++)Y.carouselInterface(t[e],b.get(t[e],"bs.carousel"))}),m("carousel",Y);const q={toggle:!0,parent:""},V={toggle:"boolean",parent:"(string|element)"};class X extends j{constructor(t,e){super(t),this._isTransitioning=!1,this._config=this._getConfig(e),this._triggerArray=H.find(`[data-bs-toggle="collapse"][href="#${this._element.id}"],[data-bs-toggle="collapse"][data-bs-target="#${this._element.id}"]`);const s=H.find('[data-bs-toggle="collapse"]');for(let t=0,e=s.length;t<e;t++){const e=s[t],n=i(e),o=H.find(n).filter(t=>t===this._element);null!==n&&o.length&&(this._selector=n,this._triggerArray.push(e))}this._parent=this._config.parent?this._getParent():null,this._config.parent||this._addAriaAndCollapsedClass(this._element,this._triggerArray),this._config.toggle&&this.toggle()}static get Default(){return q}static get DATA_KEY(){return"bs.collapse"}toggle(){this._element.classList.contains("show")?this.hide():this.show()}show(){if(this._isTransitioning||this._element.classList.contains("show"))return;let t,e;this._parent&&(t=H.find(".show, .collapsing",this._parent).filter(t=>"string"==typeof this._config.parent?t.getAttribute("data-bs-parent")===this._config.parent:t.classList.contains("collapse")),0===t.length&&(t=null));const i=H.findOne(this._selector);if(t){const s=t.find(t=>i!==t);if(e=s?b.get(s,"bs.collapse"):null,e&&e._isTransitioning)return}if(N.trigger(this._element,"show.bs.collapse").defaultPrevented)return;t&&t.forEach(t=>{i!==t&&X.collapseInterface(t,"hide"),e||b.set(t,"bs.collapse",null)});const s=this._getDimension();this._element.classList.remove("collapse"),this._element.classList.add("collapsing"),this._element.style[s]=0,this._triggerArray.length&&this._triggerArray.forEach(t=>{t.classList.remove("collapsed"),t.setAttribute("aria-expanded",!0)}),this.setTransitioning(!0);const o="scroll"+(s[0].toUpperCase()+s.slice(1)),r=n(this._element);N.one(this._element,"transitionend",()=>{this._element.classList.remove("collapsing"),this._element.classList.add("collapse","show"),this._element.style[s]="",this.setTransitioning(!1),N.trigger(this._element,"shown.bs.collapse")}),a(this._element,r),this._element.style[s]=this._element[o]+"px"}hide(){if(this._isTransitioning||!this._element.classList.contains("show"))return;if(N.trigger(this._element,"hide.bs.collapse").defaultPrevented)return;const t=this._getDimension();this._element.style[t]=this._element.getBoundingClientRect()[t]+"px",u(this._element),this._element.classList.add("collapsing"),this._element.classList.remove("collapse","show");const e=this._triggerArray.length;if(e>0)for(let t=0;t<e;t++){const e=this._triggerArray[t],i=s(e);i&&!i.classList.contains("show")&&(e.classList.add("collapsed"),e.setAttribute("aria-expanded",!1))}this.setTransitioning(!0),this._element.style[t]="";const i=n(this._element);N.one(this._element,"transitionend",()=>{this.setTransitioning(!1),this._element.classList.remove("collapsing"),this._element.classList.add("collapse"),N.trigger(this._element,"hidden.bs.collapse")}),a(this._element,i)}setTransitioning(t){this._isTransitioning=t}dispose(){super.dispose(),this._config=null,this._parent=null,this._triggerArray=null,this._isTransitioning=null}_getConfig(t){return(t={...q,...t}).toggle=Boolean(t.toggle),l("collapse",t,V),t}_getDimension(){return this._element.classList.contains("width")?"width":"height"}_getParent(){let{parent:t}=this._config;r(t)?void 0===t.jquery&&void 0===t[0]||(t=t[0]):t=H.findOne(t);const e=`[data-bs-toggle="collapse"][data-bs-parent="${t}"]`;return H.find(e,t).forEach(t=>{const e=s(t);this._addAriaAndCollapsedClass(e,[t])}),t}_addAriaAndCollapsedClass(t,e){if(!t||!e.length)return;const i=t.classList.contains("show");e.forEach(t=>{i?t.classList.remove("collapsed"):t.classList.add("collapsed"),t.setAttribute("aria-expanded",i)})}static collapseInterface(t,e){let i=b.get(t,"bs.collapse");const s={...q,...B.getDataAttributes(t),..."object"==typeof e&&e?e:{}};if(!i&&s.toggle&&"string"==typeof e&&/show|hide/.test(e)&&(s.toggle=!1),i||(i=new X(t,s)),"string"==typeof e){if(void 0===i[e])throw new TypeError(`No method named "${e}"`);i[e]()}}static jQueryInterface(t){return this.each((function(){X.collapseInterface(this,t)}))}}N.on(document,"click.bs.collapse.data-api",'[data-bs-toggle="collapse"]',(function(t){("A"===t.target.tagName||t.delegateTarget&&"A"===t.delegateTarget.tagName)&&t.preventDefault();const e=B.getDataAttributes(this),s=i(this);H.find(s).forEach(t=>{const i=b.get(t,"bs.collapse");let s;i?(null===i._parent&&"string"==typeof e.parent&&(i._config.parent=e.parent,i._parent=i._getParent()),s="toggle"):s=e,X.collapseInterface(t,s)})})),m("collapse",X);var Q="top",G="bottom",Z="right",J="left",tt=[Q,G,Z,J],et=tt.reduce((function(t,e){return t.concat([e+"-start",e+"-end"])}),[]),it=[].concat(tt,["auto"]).reduce((function(t,e){return t.concat([e,e+"-start",e+"-end"])}),[]),st=["beforeRead","read","afterRead","beforeMain","main","afterMain","beforeWrite","write","afterWrite"];function nt(t){return t?(t.nodeName||"").toLowerCase():null}function ot(t){if(null==t)return window;if("[object Window]"!==t.toString()){var e=t.ownerDocument;return e&&e.defaultView||window}return t}function rt(t){return t instanceof ot(t).Element||t instanceof Element}function at(t){return t instanceof ot(t).HTMLElement||t instanceof HTMLElement}function lt(t){return"undefined"!=typeof ShadowRoot&&(t instanceof ot(t).ShadowRoot||t instanceof ShadowRoot)}var ct={name:"applyStyles",enabled:!0,phase:"write",fn:function(t){var e=t.state;Object.keys(e.elements).forEach((function(t){var i=e.styles[t]||{},s=e.attributes[t]||{},n=e.elements[t];at(n)&&nt(n)&&(Object.assign(n.style,i),Object.keys(s).forEach((function(t){var e=s[t];!1===e?n.removeAttribute(t):n.setAttribute(t,!0===e?"":e)})))}))},effect:function(t){var e=t.state,i={popper:{position:e.options.strategy,left:"0",top:"0",margin:"0"},arrow:{position:"absolute"},reference:{}};return Object.assign(e.elements.popper.style,i.popper),e.styles=i,e.elements.arrow&&Object.assign(e.elements.arrow.style,i.arrow),function(){Object.keys(e.elements).forEach((function(t){var s=e.elements[t],n=e.attributes[t]||{},o=Object.keys(e.styles.hasOwnProperty(t)?e.styles[t]:i[t]).reduce((function(t,e){return t[e]="",t}),{});at(s)&&nt(s)&&(Object.assign(s.style,o),Object.keys(n).forEach((function(t){s.removeAttribute(t)})))}))}},requires:["computeStyles"]};function dt(t){return t.split("-")[0]}function ht(t){var e=t.getBoundingClientRect();return{width:e.width,height:e.height,top:e.top,right:e.right,bottom:e.bottom,left:e.left,x:e.left,y:e.top}}function ft(t){var e=ht(t),i=t.offsetWidth,s=t.offsetHeight;return Math.abs(e.width-i)<=1&&(i=e.width),Math.abs(e.height-s)<=1&&(s=e.height),{x:t.offsetLeft,y:t.offsetTop,width:i,height:s}}function ut(t,e){var i=e.getRootNode&&e.getRootNode();if(t.contains(e))return!0;if(i&&lt(i)){var s=e;do{if(s&&t.isSameNode(s))return!0;s=s.parentNode||s.host}while(s)}return!1}function pt(t){return ot(t).getComputedStyle(t)}function gt(t){return["table","td","th"].indexOf(nt(t))>=0}function mt(t){return((rt(t)?t.ownerDocument:t.document)||window.document).documentElement}function _t(t){return"html"===nt(t)?t:t.assignedSlot||t.parentNode||(lt(t)?t.host:null)||mt(t)}function bt(t){return at(t)&&"fixed"!==pt(t).position?t.offsetParent:null}function vt(t){for(var e=ot(t),i=bt(t);i&&gt(i)&&"static"===pt(i).position;)i=bt(i);return i&&("html"===nt(i)||"body"===nt(i)&&"static"===pt(i).position)?e:i||function(t){for(var e=-1!==navigator.userAgent.toLowerCase().indexOf("firefox"),i=_t(t);at(i)&&["html","body"].indexOf(nt(i))<0;){var s=pt(i);if("none"!==s.transform||"none"!==s.perspective||"paint"===s.contain||-1!==["transform","perspective"].indexOf(s.willChange)||e&&"filter"===s.willChange||e&&s.filter&&"none"!==s.filter)return i;i=i.parentNode}return null}(t)||e}function yt(t){return["top","bottom"].indexOf(t)>=0?"x":"y"}var wt=Math.max,Et=Math.min,Tt=Math.round;function At(t,e,i){return wt(t,Et(e,i))}function Lt(t){return Object.assign({},{top:0,right:0,bottom:0,left:0},t)}function Ot(t,e){return e.reduce((function(e,i){return e[i]=t,e}),{})}var kt={name:"arrow",enabled:!0,phase:"main",fn:function(t){var e,i=t.state,s=t.name,n=t.options,o=i.elements.arrow,r=i.modifiersData.popperOffsets,a=dt(i.placement),l=yt(a),c=[J,Z].indexOf(a)>=0?"height":"width";if(o&&r){var d=function(t,e){return Lt("number"!=typeof(t="function"==typeof t?t(Object.assign({},e.rects,{placement:e.placement})):t)?t:Ot(t,tt))}(n.padding,i),h=ft(o),f="y"===l?Q:J,u="y"===l?G:Z,p=i.rects.reference[c]+i.rects.reference[l]-r[l]-i.rects.popper[c],g=r[l]-i.rects.reference[l],m=vt(o),_=m?"y"===l?m.clientHeight||0:m.clientWidth||0:0,b=p/2-g/2,v=d[f],y=_-h[c]-d[u],w=_/2-h[c]/2+b,E=At(v,w,y),T=l;i.modifiersData[s]=((e={})[T]=E,e.centerOffset=E-w,e)}},effect:function(t){var e=t.state,i=t.options.element,s=void 0===i?"[data-popper-arrow]":i;null!=s&&("string"!=typeof s||(s=e.elements.popper.querySelector(s)))&&ut(e.elements.popper,s)&&(e.elements.arrow=s)},requires:["popperOffsets"],requiresIfExists:["preventOverflow"]},Dt={top:"auto",right:"auto",bottom:"auto",left:"auto"};function xt(t){var e,i=t.popper,s=t.popperRect,n=t.placement,o=t.offsets,r=t.position,a=t.gpuAcceleration,l=t.adaptive,c=t.roundOffsets,d=!0===c?function(t){var e=t.x,i=t.y,s=window.devicePixelRatio||1;return{x:Tt(Tt(e*s)/s)||0,y:Tt(Tt(i*s)/s)||0}}(o):"function"==typeof c?c(o):o,h=d.x,f=void 0===h?0:h,u=d.y,p=void 0===u?0:u,g=o.hasOwnProperty("x"),m=o.hasOwnProperty("y"),_=J,b=Q,v=window;if(l){var y=vt(i),w="clientHeight",E="clientWidth";y===ot(i)&&"static"!==pt(y=mt(i)).position&&(w="scrollHeight",E="scrollWidth"),y=y,n===Q&&(b=G,p-=y[w]-s.height,p*=a?1:-1),n===J&&(_=Z,f-=y[E]-s.width,f*=a?1:-1)}var T,A=Object.assign({position:r},l&&Dt);return a?Object.assign({},A,((T={})[b]=m?"0":"",T[_]=g?"0":"",T.transform=(v.devicePixelRatio||1)<2?"translate("+f+"px, "+p+"px)":"translate3d("+f+"px, "+p+"px, 0)",T)):Object.assign({},A,((e={})[b]=m?p+"px":"",e[_]=g?f+"px":"",e.transform="",e))}var Ct={name:"computeStyles",enabled:!0,phase:"beforeWrite",fn:function(t){var e=t.state,i=t.options,s=i.gpuAcceleration,n=void 0===s||s,o=i.adaptive,r=void 0===o||o,a=i.roundOffsets,l=void 0===a||a,c={placement:dt(e.placement),popper:e.elements.popper,popperRect:e.rects.popper,gpuAcceleration:n};null!=e.modifiersData.popperOffsets&&(e.styles.popper=Object.assign({},e.styles.popper,xt(Object.assign({},c,{offsets:e.modifiersData.popperOffsets,position:e.options.strategy,adaptive:r,roundOffsets:l})))),null!=e.modifiersData.arrow&&(e.styles.arrow=Object.assign({},e.styles.arrow,xt(Object.assign({},c,{offsets:e.modifiersData.arrow,position:"absolute",adaptive:!1,roundOffsets:l})))),e.attributes.popper=Object.assign({},e.attributes.popper,{"data-popper-placement":e.placement})},data:{}},St={passive:!0},Nt={name:"eventListeners",enabled:!0,phase:"write",fn:function(){},effect:function(t){var e=t.state,i=t.instance,s=t.options,n=s.scroll,o=void 0===n||n,r=s.resize,a=void 0===r||r,l=ot(e.elements.popper),c=[].concat(e.scrollParents.reference,e.scrollParents.popper);return o&&c.forEach((function(t){t.addEventListener("scroll",i.update,St)})),a&&l.addEventListener("resize",i.update,St),function(){o&&c.forEach((function(t){t.removeEventListener("scroll",i.update,St)})),a&&l.removeEventListener("resize",i.update,St)}},data:{}},jt={left:"right",right:"left",bottom:"top",top:"bottom"};function Pt(t){return t.replace(/left|right|bottom|top/g,(function(t){return jt[t]}))}var It={start:"end",end:"start"};function Mt(t){return t.replace(/start|end/g,(function(t){return It[t]}))}function Rt(t){var e=ot(t);return{scrollLeft:e.pageXOffset,scrollTop:e.pageYOffset}}function Bt(t){return ht(mt(t)).left+Rt(t).scrollLeft}function Ht(t){var e=pt(t),i=e.overflow,s=e.overflowX,n=e.overflowY;return/auto|scroll|overlay|hidden/.test(i+n+s)}function Wt(t,e){var i;void 0===e&&(e=[]);var s=function t(e){return["html","body","#document"].indexOf(nt(e))>=0?e.ownerDocument.body:at(e)&&Ht(e)?e:t(_t(e))}(t),n=s===(null==(i=t.ownerDocument)?void 0:i.body),o=ot(s),r=n?[o].concat(o.visualViewport||[],Ht(s)?s:[]):s,a=e.concat(r);return n?a:a.concat(Wt(_t(r)))}function Ut(t){return Object.assign({},t,{left:t.x,top:t.y,right:t.x+t.width,bottom:t.y+t.height})}function $t(t,e){return"viewport"===e?Ut(function(t){var e=ot(t),i=mt(t),s=e.visualViewport,n=i.clientWidth,o=i.clientHeight,r=0,a=0;return s&&(n=s.width,o=s.height,/^((?!chrome|android).)*safari/i.test(navigator.userAgent)||(r=s.offsetLeft,a=s.offsetTop)),{width:n,height:o,x:r+Bt(t),y:a}}(t)):at(e)?function(t){var e=ht(t);return e.top=e.top+t.clientTop,e.left=e.left+t.clientLeft,e.bottom=e.top+t.clientHeight,e.right=e.left+t.clientWidth,e.width=t.clientWidth,e.height=t.clientHeight,e.x=e.left,e.y=e.top,e}(e):Ut(function(t){var e,i=mt(t),s=Rt(t),n=null==(e=t.ownerDocument)?void 0:e.body,o=wt(i.scrollWidth,i.clientWidth,n?n.scrollWidth:0,n?n.clientWidth:0),r=wt(i.scrollHeight,i.clientHeight,n?n.scrollHeight:0,n?n.clientHeight:0),a=-s.scrollLeft+Bt(t),l=-s.scrollTop;return"rtl"===pt(n||i).direction&&(a+=wt(i.clientWidth,n?n.clientWidth:0)-o),{width:o,height:r,x:a,y:l}}(mt(t)))}function Ft(t){return t.split("-")[1]}function zt(t){var e,i=t.reference,s=t.element,n=t.placement,o=n?dt(n):null,r=n?Ft(n):null,a=i.x+i.width/2-s.width/2,l=i.y+i.height/2-s.height/2;switch(o){case Q:e={x:a,y:i.y-s.height};break;case G:e={x:a,y:i.y+i.height};break;case Z:e={x:i.x+i.width,y:l};break;case J:e={x:i.x-s.width,y:l};break;default:e={x:i.x,y:i.y}}var c=o?yt(o):null;if(null!=c){var d="y"===c?"height":"width";switch(r){case"start":e[c]=e[c]-(i[d]/2-s[d]/2);break;case"end":e[c]=e[c]+(i[d]/2-s[d]/2)}}return e}function Kt(t,e){void 0===e&&(e={});var i=e,s=i.placement,n=void 0===s?t.placement:s,o=i.boundary,r=void 0===o?"clippingParents":o,a=i.rootBoundary,l=void 0===a?"viewport":a,c=i.elementContext,d=void 0===c?"popper":c,h=i.altBoundary,f=void 0!==h&&h,u=i.padding,p=void 0===u?0:u,g=Lt("number"!=typeof p?p:Ot(p,tt)),m="popper"===d?"reference":"popper",_=t.elements.reference,b=t.rects.popper,v=t.elements[f?m:d],y=function(t,e,i){var s="clippingParents"===e?function(t){var e=Wt(_t(t)),i=["absolute","fixed"].indexOf(pt(t).position)>=0&&at(t)?vt(t):t;return rt(i)?e.filter((function(t){return rt(t)&&ut(t,i)&&"body"!==nt(t)})):[]}(t):[].concat(e),n=[].concat(s,[i]),o=n[0],r=n.reduce((function(e,i){var s=$t(t,i);return e.top=wt(s.top,e.top),e.right=Et(s.right,e.right),e.bottom=Et(s.bottom,e.bottom),e.left=wt(s.left,e.left),e}),$t(t,o));return r.width=r.right-r.left,r.height=r.bottom-r.top,r.x=r.left,r.y=r.top,r}(rt(v)?v:v.contextElement||mt(t.elements.popper),r,l),w=ht(_),E=zt({reference:w,element:b,strategy:"absolute",placement:n}),T=Ut(Object.assign({},b,E)),A="popper"===d?T:w,L={top:y.top-A.top+g.top,bottom:A.bottom-y.bottom+g.bottom,left:y.left-A.left+g.left,right:A.right-y.right+g.right},O=t.modifiersData.offset;if("popper"===d&&O){var k=O[n];Object.keys(L).forEach((function(t){var e=[Z,G].indexOf(t)>=0?1:-1,i=[Q,G].indexOf(t)>=0?"y":"x";L[t]+=k[i]*e}))}return L}function Yt(t,e){void 0===e&&(e={});var i=e,s=i.placement,n=i.boundary,o=i.rootBoundary,r=i.padding,a=i.flipVariations,l=i.allowedAutoPlacements,c=void 0===l?it:l,d=Ft(s),h=d?a?et:et.filter((function(t){return Ft(t)===d})):tt,f=h.filter((function(t){return c.indexOf(t)>=0}));0===f.length&&(f=h);var u=f.reduce((function(e,i){return e[i]=Kt(t,{placement:i,boundary:n,rootBoundary:o,padding:r})[dt(i)],e}),{});return Object.keys(u).sort((function(t,e){return u[t]-u[e]}))}var qt={name:"flip",enabled:!0,phase:"main",fn:function(t){var e=t.state,i=t.options,s=t.name;if(!e.modifiersData[s]._skip){for(var n=i.mainAxis,o=void 0===n||n,r=i.altAxis,a=void 0===r||r,l=i.fallbackPlacements,c=i.padding,d=i.boundary,h=i.rootBoundary,f=i.altBoundary,u=i.flipVariations,p=void 0===u||u,g=i.allowedAutoPlacements,m=e.options.placement,_=dt(m),b=l||(_!==m&&p?function(t){if("auto"===dt(t))return[];var e=Pt(t);return[Mt(t),e,Mt(e)]}(m):[Pt(m)]),v=[m].concat(b).reduce((function(t,i){return t.concat("auto"===dt(i)?Yt(e,{placement:i,boundary:d,rootBoundary:h,padding:c,flipVariations:p,allowedAutoPlacements:g}):i)}),[]),y=e.rects.reference,w=e.rects.popper,E=new Map,T=!0,A=v[0],L=0;L<v.length;L++){var O=v[L],k=dt(O),D="start"===Ft(O),x=[Q,G].indexOf(k)>=0,C=x?"width":"height",S=Kt(e,{placement:O,boundary:d,rootBoundary:h,altBoundary:f,padding:c}),N=x?D?Z:J:D?G:Q;y[C]>w[C]&&(N=Pt(N));var j=Pt(N),P=[];if(o&&P.push(S[k]<=0),a&&P.push(S[N]<=0,S[j]<=0),P.every((function(t){return t}))){A=O,T=!1;break}E.set(O,P)}if(T)for(var I=function(t){var e=v.find((function(e){var i=E.get(e);if(i)return i.slice(0,t).every((function(t){return t}))}));if(e)return A=e,"break"},M=p?3:1;M>0&&"break"!==I(M);M--);e.placement!==A&&(e.modifiersData[s]._skip=!0,e.placement=A,e.reset=!0)}},requiresIfExists:["offset"],data:{_skip:!1}};function Vt(t,e,i){return void 0===i&&(i={x:0,y:0}),{top:t.top-e.height-i.y,right:t.right-e.width+i.x,bottom:t.bottom-e.height+i.y,left:t.left-e.width-i.x}}function Xt(t){return[Q,Z,G,J].some((function(e){return t[e]>=0}))}var Qt={name:"hide",enabled:!0,phase:"main",requiresIfExists:["preventOverflow"],fn:function(t){var e=t.state,i=t.name,s=e.rects.reference,n=e.rects.popper,o=e.modifiersData.preventOverflow,r=Kt(e,{elementContext:"reference"}),a=Kt(e,{altBoundary:!0}),l=Vt(r,s),c=Vt(a,n,o),d=Xt(l),h=Xt(c);e.modifiersData[i]={referenceClippingOffsets:l,popperEscapeOffsets:c,isReferenceHidden:d,hasPopperEscaped:h},e.attributes.popper=Object.assign({},e.attributes.popper,{"data-popper-reference-hidden":d,"data-popper-escaped":h})}},Gt={name:"offset",enabled:!0,phase:"main",requires:["popperOffsets"],fn:function(t){var e=t.state,i=t.options,s=t.name,n=i.offset,o=void 0===n?[0,0]:n,r=it.reduce((function(t,i){return t[i]=function(t,e,i){var s=dt(t),n=[J,Q].indexOf(s)>=0?-1:1,o="function"==typeof i?i(Object.assign({},e,{placement:t})):i,r=o[0],a=o[1];return r=r||0,a=(a||0)*n,[J,Z].indexOf(s)>=0?{x:a,y:r}:{x:r,y:a}}(i,e.rects,o),t}),{}),a=r[e.placement],l=a.x,c=a.y;null!=e.modifiersData.popperOffsets&&(e.modifiersData.popperOffsets.x+=l,e.modifiersData.popperOffsets.y+=c),e.modifiersData[s]=r}},Zt={name:"popperOffsets",enabled:!0,phase:"read",fn:function(t){var e=t.state,i=t.name;e.modifiersData[i]=zt({reference:e.rects.reference,element:e.rects.popper,strategy:"absolute",placement:e.placement})},data:{}},Jt={name:"preventOverflow",enabled:!0,phase:"main",fn:function(t){var e=t.state,i=t.options,s=t.name,n=i.mainAxis,o=void 0===n||n,r=i.altAxis,a=void 0!==r&&r,l=i.boundary,c=i.rootBoundary,d=i.altBoundary,h=i.padding,f=i.tether,u=void 0===f||f,p=i.tetherOffset,g=void 0===p?0:p,m=Kt(e,{boundary:l,rootBoundary:c,padding:h,altBoundary:d}),_=dt(e.placement),b=Ft(e.placement),v=!b,y=yt(_),w="x"===y?"y":"x",E=e.modifiersData.popperOffsets,T=e.rects.reference,A=e.rects.popper,L="function"==typeof g?g(Object.assign({},e.rects,{placement:e.placement})):g,O={x:0,y:0};if(E){if(o||a){var k="y"===y?Q:J,D="y"===y?G:Z,x="y"===y?"height":"width",C=E[y],S=E[y]+m[k],N=E[y]-m[D],j=u?-A[x]/2:0,P="start"===b?T[x]:A[x],I="start"===b?-A[x]:-T[x],M=e.elements.arrow,R=u&&M?ft(M):{width:0,height:0},B=e.modifiersData["arrow#persistent"]?e.modifiersData["arrow#persistent"].padding:{top:0,right:0,bottom:0,left:0},H=B[k],W=B[D],U=At(0,T[x],R[x]),$=v?T[x]/2-j-U-H-L:P-U-H-L,F=v?-T[x]/2+j+U+W+L:I+U+W+L,z=e.elements.arrow&&vt(e.elements.arrow),K=z?"y"===y?z.clientTop||0:z.clientLeft||0:0,Y=e.modifiersData.offset?e.modifiersData.offset[e.placement][y]:0,q=E[y]+$-Y-K,V=E[y]+F-Y;if(o){var X=At(u?Et(S,q):S,C,u?wt(N,V):N);E[y]=X,O[y]=X-C}if(a){var tt="x"===y?Q:J,et="x"===y?G:Z,it=E[w],st=it+m[tt],nt=it-m[et],ot=At(u?Et(st,q):st,it,u?wt(nt,V):nt);E[w]=ot,O[w]=ot-it}}e.modifiersData[s]=O}},requiresIfExists:["offset"]};function te(t,e,i){void 0===i&&(i=!1);var s,n,o=mt(e),r=ht(t),a=at(e),l={scrollLeft:0,scrollTop:0},c={x:0,y:0};return(a||!a&&!i)&&(("body"!==nt(e)||Ht(o))&&(l=(s=e)!==ot(s)&&at(s)?{scrollLeft:(n=s).scrollLeft,scrollTop:n.scrollTop}:Rt(s)),at(e)?((c=ht(e)).x+=e.clientLeft,c.y+=e.clientTop):o&&(c.x=Bt(o))),{x:r.left+l.scrollLeft-c.x,y:r.top+l.scrollTop-c.y,width:r.width,height:r.height}}var ee={placement:"bottom",modifiers:[],strategy:"absolute"};function ie(){for(var t=arguments.length,e=new Array(t),i=0;i<t;i++)e[i]=arguments[i];return!e.some((function(t){return!(t&&"function"==typeof t.getBoundingClientRect)}))}function se(t){void 0===t&&(t={});var e=t,i=e.defaultModifiers,s=void 0===i?[]:i,n=e.defaultOptions,o=void 0===n?ee:n;return function(t,e,i){void 0===i&&(i=o);var n,r,a={placement:"bottom",orderedModifiers:[],options:Object.assign({},ee,o),modifiersData:{},elements:{reference:t,popper:e},attributes:{},styles:{}},l=[],c=!1,d={state:a,setOptions:function(i){h(),a.options=Object.assign({},o,a.options,i),a.scrollParents={reference:rt(t)?Wt(t):t.contextElement?Wt(t.contextElement):[],popper:Wt(e)};var n,r,c=function(t){var e=function(t){var e=new Map,i=new Set,s=[];return t.forEach((function(t){e.set(t.name,t)})),t.forEach((function(t){i.has(t.name)||function t(n){i.add(n.name),[].concat(n.requires||[],n.requiresIfExists||[]).forEach((function(s){if(!i.has(s)){var n=e.get(s);n&&t(n)}})),s.push(n)}(t)})),s}(t);return st.reduce((function(t,i){return t.concat(e.filter((function(t){return t.phase===i})))}),[])}((n=[].concat(s,a.options.modifiers),r=n.reduce((function(t,e){var i=t[e.name];return t[e.name]=i?Object.assign({},i,e,{options:Object.assign({},i.options,e.options),data:Object.assign({},i.data,e.data)}):e,t}),{}),Object.keys(r).map((function(t){return r[t]}))));return a.orderedModifiers=c.filter((function(t){return t.enabled})),a.orderedModifiers.forEach((function(t){var e=t.name,i=t.options,s=void 0===i?{}:i,n=t.effect;if("function"==typeof n){var o=n({state:a,name:e,instance:d,options:s});l.push(o||function(){})}})),d.update()},forceUpdate:function(){if(!c){var t=a.elements,e=t.reference,i=t.popper;if(ie(e,i)){a.rects={reference:te(e,vt(i),"fixed"===a.options.strategy),popper:ft(i)},a.reset=!1,a.placement=a.options.placement,a.orderedModifiers.forEach((function(t){return a.modifiersData[t.name]=Object.assign({},t.data)}));for(var s=0;s<a.orderedModifiers.length;s++)if(!0!==a.reset){var n=a.orderedModifiers[s],o=n.fn,r=n.options,l=void 0===r?{}:r,h=n.name;"function"==typeof o&&(a=o({state:a,options:l,name:h,instance:d})||a)}else a.reset=!1,s=-1}}},update:(n=function(){return new Promise((function(t){d.forceUpdate(),t(a)}))},function(){return r||(r=new Promise((function(t){Promise.resolve().then((function(){r=void 0,t(n())}))}))),r}),destroy:function(){h(),c=!0}};if(!ie(t,e))return d;function h(){l.forEach((function(t){return t()})),l=[]}return d.setOptions(i).then((function(t){!c&&i.onFirstUpdate&&i.onFirstUpdate(t)})),d}}var ne=se(),oe=se({defaultModifiers:[Nt,Zt,Ct,ct]}),re=se({defaultModifiers:[Nt,Zt,Ct,ct,Gt,qt,Jt,kt,Qt]}),ae=Object.freeze({__proto__:null,popperGenerator:se,detectOverflow:Kt,createPopperBase:ne,createPopper:re,createPopperLite:oe,top:Q,bottom:G,right:Z,left:J,auto:"auto",basePlacements:tt,start:"start",end:"end",clippingParents:"clippingParents",viewport:"viewport",popper:"popper",reference:"reference",variationPlacements:et,placements:it,beforeRead:"beforeRead",read:"read",afterRead:"afterRead",beforeMain:"beforeMain",main:"main",afterMain:"afterMain",beforeWrite:"beforeWrite",write:"write",afterWrite:"afterWrite",modifierPhases:st,applyStyles:ct,arrow:kt,computeStyles:Ct,eventListeners:Nt,flip:qt,hide:Qt,offset:Gt,popperOffsets:Zt,preventOverflow:Jt});const le=new RegExp("ArrowUp|ArrowDown|Escape"),ce=g()?"top-end":"top-start",de=g()?"top-start":"top-end",he=g()?"bottom-end":"bottom-start",fe=g()?"bottom-start":"bottom-end",ue=g()?"left-start":"right-start",pe=g()?"right-start":"left-start",ge={offset:[0,2],boundary:"clippingParents",reference:"toggle",display:"dynamic",popperConfig:null},me={offset:"(array|string|function)",boundary:"(string|element)",reference:"(string|element|object)",display:"string",popperConfig:"(null|object|function)"};class _e extends j{constructor(t,e){super(t),this._popper=null,this._config=this._getConfig(e),this._menu=this._getMenuElement(),this._inNavbar=this._detectNavbar(),this._addEventListeners()}static get Default(){return ge}static get DefaultType(){return me}static get DATA_KEY(){return"bs.dropdown"}toggle(){if(this._element.disabled||this._element.classList.contains("disabled"))return;const t=this._element.classList.contains("show");_e.clearMenus(),t||this.show()}show(){if(this._element.disabled||this._element.classList.contains("disabled")||this._menu.classList.contains("show"))return;const t=_e.getParentFromElement(this._element),e={relatedTarget:this._element};if(!N.trigger(this._element,"show.bs.dropdown",e).defaultPrevented){if(this._inNavbar)B.setDataAttribute(this._menu,"popper","none");else{if(void 0===ae)throw new TypeError("Bootstrap's dropdowns require Popper (https://popper.js.org)");let e=this._element;"parent"===this._config.reference?e=t:r(this._config.reference)?(e=this._config.reference,void 0!==this._config.reference.jquery&&(e=this._config.reference[0])):"object"==typeof this._config.reference&&(e=this._config.reference);const i=this._getPopperConfig(),s=i.modifiers.find(t=>"applyStyles"===t.name&&!1===t.enabled);this._popper=re(e,this._menu,i),s&&B.setDataAttribute(this._menu,"popper","static")}"ontouchstart"in document.documentElement&&!t.closest(".navbar-nav")&&[].concat(...document.body.children).forEach(t=>N.on(t,"mouseover",null,(function(){}))),this._element.focus(),this._element.setAttribute("aria-expanded",!0),this._menu.classList.toggle("show"),this._element.classList.toggle("show"),N.trigger(this._element,"shown.bs.dropdown",e)}}hide(){if(this._element.disabled||this._element.classList.contains("disabled")||!this._menu.classList.contains("show"))return;const t={relatedTarget:this._element};N.trigger(this._element,"hide.bs.dropdown",t).defaultPrevented||(this._popper&&this._popper.destroy(),this._menu.classList.toggle("show"),this._element.classList.toggle("show"),B.removeDataAttribute(this._menu,"popper"),N.trigger(this._element,"hidden.bs.dropdown",t))}dispose(){N.off(this._element,".bs.dropdown"),this._menu=null,this._popper&&(this._popper.destroy(),this._popper=null),super.dispose()}update(){this._inNavbar=this._detectNavbar(),this._popper&&this._popper.update()}_addEventListeners(){N.on(this._element,"click.bs.dropdown",t=>{t.preventDefault(),this.toggle()})}_getConfig(t){if(t={...this.constructor.Default,...B.getDataAttributes(this._element),...t},l("dropdown",t,this.constructor.DefaultType),"object"==typeof t.reference&&!r(t.reference)&&"function"!=typeof t.reference.getBoundingClientRect)throw new TypeError("dropdown".toUpperCase()+': Option "reference" provided type "object" without a required "getBoundingClientRect" method.');return t}_getMenuElement(){return H.next(this._element,".dropdown-menu")[0]}_getPlacement(){const t=this._element.parentNode;if(t.classList.contains("dropend"))return ue;if(t.classList.contains("dropstart"))return pe;const e="end"===getComputedStyle(this._menu).getPropertyValue("--bs-position").trim();return t.classList.contains("dropup")?e?de:ce:e?fe:he}_detectNavbar(){return null!==this._element.closest(".navbar")}_getOffset(){const{offset:t}=this._config;return"string"==typeof t?t.split(",").map(t=>Number.parseInt(t,10)):"function"==typeof t?e=>t(e,this._element):t}_getPopperConfig(){const t={placement:this._getPlacement(),modifiers:[{name:"preventOverflow",options:{boundary:this._config.boundary}},{name:"offset",options:{offset:this._getOffset()}}]};return"static"===this._config.display&&(t.modifiers=[{name:"applyStyles",enabled:!1}]),{...t,..."function"==typeof this._config.popperConfig?this._config.popperConfig(t):this._config.popperConfig}}static dropdownInterface(t,e){let i=b.get(t,"bs.dropdown");if(i||(i=new _e(t,"object"==typeof e?e:null)),"string"==typeof e){if(void 0===i[e])throw new TypeError(`No method named "${e}"`);i[e]()}}static jQueryInterface(t){return this.each((function(){_e.dropdownInterface(this,t)}))}static clearMenus(t){if(t){if(2===t.button||"keyup"===t.type&&"Tab"!==t.key)return;if(/input|select|textarea|form/i.test(t.target.tagName))return}const e=H.find('[data-bs-toggle="dropdown"]');for(let i=0,s=e.length;i<s;i++){const s=b.get(e[i],"bs.dropdown"),n={relatedTarget:e[i]};if(t&&"click"===t.type&&(n.clickEvent=t),!s)continue;const o=s._menu;if(e[i].classList.contains("show")){if(t){if([s._element].some(e=>t.composedPath().includes(e)))continue;if("keyup"===t.type&&"Tab"===t.key&&o.contains(t.target))continue}N.trigger(e[i],"hide.bs.dropdown",n).defaultPrevented||("ontouchstart"in document.documentElement&&[].concat(...document.body.children).forEach(t=>N.off(t,"mouseover",null,(function(){}))),e[i].setAttribute("aria-expanded","false"),s._popper&&s._popper.destroy(),o.classList.remove("show"),e[i].classList.remove("show"),B.removeDataAttribute(o,"popper"),N.trigger(e[i],"hidden.bs.dropdown",n))}}}static getParentFromElement(t){return s(t)||t.parentNode}static dataApiKeydownHandler(t){if(/input|textarea/i.test(t.target.tagName)?"Space"===t.key||"Escape"!==t.key&&("ArrowDown"!==t.key&&"ArrowUp"!==t.key||t.target.closest(".dropdown-menu")):!le.test(t.key))return;if(t.preventDefault(),t.stopPropagation(),this.disabled||this.classList.contains("disabled"))return;const e=_e.getParentFromElement(this),i=this.classList.contains("show");if("Escape"===t.key)return(this.matches('[data-bs-toggle="dropdown"]')?this:H.prev(this,'[data-bs-toggle="dropdown"]')[0]).focus(),void _e.clearMenus();if(!i&&("ArrowUp"===t.key||"ArrowDown"===t.key))return void(this.matches('[data-bs-toggle="dropdown"]')?this:H.prev(this,'[data-bs-toggle="dropdown"]')[0]).click();if(!i||"Space"===t.key)return void _e.clearMenus();const s=H.find(".dropdown-menu .dropdown-item:not(.disabled):not(:disabled)",e).filter(c);if(!s.length)return;let n=s.indexOf(t.target);"ArrowUp"===t.key&&n>0&&n--,"ArrowDown"===t.key&&n<s.length-1&&n++,n=-1===n?0:n,s[n].focus()}}N.on(document,"keydown.bs.dropdown.data-api",'[data-bs-toggle="dropdown"]',_e.dataApiKeydownHandler),N.on(document,"keydown.bs.dropdown.data-api",".dropdown-menu",_e.dataApiKeydownHandler),N.on(document,"click.bs.dropdown.data-api",_e.clearMenus),N.on(document,"keyup.bs.dropdown.data-api",_e.clearMenus),N.on(document,"click.bs.dropdown.data-api",'[data-bs-toggle="dropdown"]',(function(t){t.preventDefault(),_e.dropdownInterface(this)})),m("dropdown",_e);const be={backdrop:!0,keyboard:!0,focus:!0},ve={backdrop:"(boolean|string)",keyboard:"boolean",focus:"boolean"};class ye extends j{constructor(t,e){super(t),this._config=this._getConfig(e),this._dialog=H.findOne(".modal-dialog",this._element),this._backdrop=null,this._isShown=!1,this._isBodyOverflowing=!1,this._ignoreBackdropClick=!1,this._isTransitioning=!1,this._scrollbarWidth=0}static get Default(){return be}static get DATA_KEY(){return"bs.modal"}toggle(t){return this._isShown?this.hide():this.show(t)}show(t){if(this._isShown||this._isTransitioning)return;this._isAnimated()&&(this._isTransitioning=!0);const e=N.trigger(this._element,"show.bs.modal",{relatedTarget:t});this._isShown||e.defaultPrevented||(this._isShown=!0,this._checkScrollbar(),this._setScrollbar(),this._adjustDialog(),this._setEscapeEvent(),this._setResizeEvent(),N.on(this._element,"click.dismiss.bs.modal",'[data-bs-dismiss="modal"]',t=>this.hide(t)),N.on(this._dialog,"mousedown.dismiss.bs.modal",()=>{N.one(this._element,"mouseup.dismiss.bs.modal",t=>{t.target===this._element&&(this._ignoreBackdropClick=!0)})}),this._showBackdrop(()=>this._showElement(t)))}hide(t){if(t&&t.preventDefault(),!this._isShown||this._isTransitioning)return;if(N.trigger(this._element,"hide.bs.modal").defaultPrevented)return;this._isShown=!1;const e=this._isAnimated();if(e&&(this._isTransitioning=!0),this._setEscapeEvent(),this._setResizeEvent(),N.off(document,"focusin.bs.modal"),this._element.classList.remove("show"),N.off(this._element,"click.dismiss.bs.modal"),N.off(this._dialog,"mousedown.dismiss.bs.modal"),e){const t=n(this._element);N.one(this._element,"transitionend",t=>this._hideModal(t)),a(this._element,t)}else this._hideModal()}dispose(){[window,this._element,this._dialog].forEach(t=>N.off(t,".bs.modal")),super.dispose(),N.off(document,"focusin.bs.modal"),this._config=null,this._dialog=null,this._backdrop=null,this._isShown=null,this._isBodyOverflowing=null,this._ignoreBackdropClick=null,this._isTransitioning=null,this._scrollbarWidth=null}handleUpdate(){this._adjustDialog()}_getConfig(t){return t={...be,...t},l("modal",t,ve),t}_showElement(t){const e=this._isAnimated(),i=H.findOne(".modal-body",this._dialog);this._element.parentNode&&this._element.parentNode.nodeType===Node.ELEMENT_NODE||document.body.appendChild(this._element),this._element.style.display="block",this._element.removeAttribute("aria-hidden"),this._element.setAttribute("aria-modal",!0),this._element.setAttribute("role","dialog"),this._element.scrollTop=0,i&&(i.scrollTop=0),e&&u(this._element),this._element.classList.add("show"),this._config.focus&&this._enforceFocus();const s=()=>{this._config.focus&&this._element.focus(),this._isTransitioning=!1,N.trigger(this._element,"shown.bs.modal",{relatedTarget:t})};if(e){const t=n(this._dialog);N.one(this._dialog,"transitionend",s),a(this._dialog,t)}else s()}_enforceFocus(){N.off(document,"focusin.bs.modal"),N.on(document,"focusin.bs.modal",t=>{document===t.target||this._element===t.target||this._element.contains(t.target)||this._element.focus()})}_setEscapeEvent(){this._isShown?N.on(this._element,"keydown.dismiss.bs.modal",t=>{this._config.keyboard&&"Escape"===t.key?(t.preventDefault(),this.hide()):this._config.keyboard||"Escape"!==t.key||this._triggerBackdropTransition()}):N.off(this._element,"keydown.dismiss.bs.modal")}_setResizeEvent(){this._isShown?N.on(window,"resize.bs.modal",()=>this._adjustDialog()):N.off(window,"resize.bs.modal")}_hideModal(){this._element.style.display="none",this._element.setAttribute("aria-hidden",!0),this._element.removeAttribute("aria-modal"),this._element.removeAttribute("role"),this._isTransitioning=!1,this._showBackdrop(()=>{document.body.classList.remove("modal-open"),this._resetAdjustments(),this._resetScrollbar(),N.trigger(this._element,"hidden.bs.modal")})}_removeBackdrop(){this._backdrop.parentNode.removeChild(this._backdrop),this._backdrop=null}_showBackdrop(t){const e=this._isAnimated();if(this._isShown&&this._config.backdrop){if(this._backdrop=document.createElement("div"),this._backdrop.className="modal-backdrop",e&&this._backdrop.classList.add("fade"),document.body.appendChild(this._backdrop),N.on(this._element,"click.dismiss.bs.modal",t=>{this._ignoreBackdropClick?this._ignoreBackdropClick=!1:t.target===t.currentTarget&&("static"===this._config.backdrop?this._triggerBackdropTransition():this.hide())}),e&&u(this._backdrop),this._backdrop.classList.add("show"),!e)return void t();const i=n(this._backdrop);N.one(this._backdrop,"transitionend",t),a(this._backdrop,i)}else if(!this._isShown&&this._backdrop){this._backdrop.classList.remove("show");const i=()=>{this._removeBackdrop(),t()};if(e){const t=n(this._backdrop);N.one(this._backdrop,"transitionend",i),a(this._backdrop,t)}else i()}else t()}_isAnimated(){return this._element.classList.contains("fade")}_triggerBackdropTransition(){if(N.trigger(this._element,"hidePrevented.bs.modal").defaultPrevented)return;const t=this._element.scrollHeight>document.documentElement.clientHeight;t||(this._element.style.overflowY="hidden"),this._element.classList.add("modal-static");const e=n(this._dialog);N.off(this._element,"transitionend"),N.one(this._element,"transitionend",()=>{this._element.classList.remove("modal-static"),t||(N.one(this._element,"transitionend",()=>{this._element.style.overflowY=""}),a(this._element,e))}),a(this._element,e),this._element.focus()}_adjustDialog(){const t=this._element.scrollHeight>document.documentElement.clientHeight;(!this._isBodyOverflowing&&t&&!g()||this._isBodyOverflowing&&!t&&g())&&(this._element.style.paddingLeft=this._scrollbarWidth+"px"),(this._isBodyOverflowing&&!t&&!g()||!this._isBodyOverflowing&&t&&g())&&(this._element.style.paddingRight=this._scrollbarWidth+"px")}_resetAdjustments(){this._element.style.paddingLeft="",this._element.style.paddingRight=""}_checkScrollbar(){const t=document.body.getBoundingClientRect();this._isBodyOverflowing=Math.round(t.left+t.right)<window.innerWidth,this._scrollbarWidth=this._getScrollbarWidth()}_setScrollbar(){this._isBodyOverflowing&&(this._setElementAttributes(".fixed-top, .fixed-bottom, .is-fixed, .sticky-top","paddingRight",t=>t+this._scrollbarWidth),this._setElementAttributes(".sticky-top","marginRight",t=>t-this._scrollbarWidth),this._setElementAttributes("body","paddingRight",t=>t+this._scrollbarWidth)),document.body.classList.add("modal-open")}_setElementAttributes(t,e,i){H.find(t).forEach(t=>{if(t!==document.body&&window.innerWidth>t.clientWidth+this._scrollbarWidth)return;const s=t.style[e],n=window.getComputedStyle(t)[e];B.setDataAttribute(t,e,s),t.style[e]=i(Number.parseFloat(n))+"px"})}_resetScrollbar(){this._resetElementAttributes(".fixed-top, .fixed-bottom, .is-fixed, .sticky-top","paddingRight"),this._resetElementAttributes(".sticky-top","marginRight"),this._resetElementAttributes("body","paddingRight")}_resetElementAttributes(t,e){H.find(t).forEach(t=>{const i=B.getDataAttribute(t,e);void 0===i&&t===document.body?t.style[e]="":(B.removeDataAttribute(t,e),t.style[e]=i)})}_getScrollbarWidth(){const t=document.createElement("div");t.className="modal-scrollbar-measure",document.body.appendChild(t);const e=t.getBoundingClientRect().width-t.clientWidth;return document.body.removeChild(t),e}static jQueryInterface(t,e){return this.each((function(){let i=b.get(this,"bs.modal");const s={...be,...B.getDataAttributes(this),..."object"==typeof t&&t?t:{}};if(i||(i=new ye(this,s)),"string"==typeof t){if(void 0===i[t])throw new TypeError(`No method named "${t}"`);i[t](e)}}))}}N.on(document,"click.bs.modal.data-api",'[data-bs-toggle="modal"]',(function(t){const e=s(this);"A"!==this.tagName&&"AREA"!==this.tagName||t.preventDefault(),N.one(e,"show.bs.modal",t=>{t.defaultPrevented||N.one(e,"hidden.bs.modal",()=>{c(this)&&this.focus()})});let i=b.get(e,"bs.modal");if(!i){const t={...B.getDataAttributes(e),...B.getDataAttributes(this)};i=new ye(e,t)}i.toggle(this)})),m("modal",ye);const we=()=>{const t=document.documentElement.clientWidth;return Math.abs(window.innerWidth-t)},Ee=(t,e,i)=>{const s=we();H.find(t).forEach(t=>{if(t!==document.body&&window.innerWidth>t.clientWidth+s)return;const n=t.style[e],o=window.getComputedStyle(t)[e];B.setDataAttribute(t,e,n),t.style[e]=i(Number.parseFloat(o))+"px"})},Te=(t,e)=>{H.find(t).forEach(t=>{const i=B.getDataAttribute(t,e);void 0===i&&t===document.body?t.style.removeProperty(e):(B.removeDataAttribute(t,e),t.style[e]=i)})},Ae={backdrop:!0,keyboard:!0,scroll:!1},Le={backdrop:"boolean",keyboard:"boolean",scroll:"boolean"};class Oe extends j{constructor(t,e){super(t),this._config=this._getConfig(e),this._isShown=!1,this._addEventListeners()}static get Default(){return Ae}static get DATA_KEY(){return"bs.offcanvas"}toggle(t){return this._isShown?this.hide():this.show(t)}show(t){this._isShown||N.trigger(this._element,"show.bs.offcanvas",{relatedTarget:t}).defaultPrevented||(this._isShown=!0,this._element.style.visibility="visible",this._config.backdrop&&document.body.classList.add("offcanvas-backdrop"),this._config.scroll||((t=we())=>{document.body.style.overflow="hidden",Ee(".fixed-top, .fixed-bottom, .is-fixed","paddingRight",e=>e+t),Ee(".sticky-top","marginRight",e=>e-t),Ee("body","paddingRight",e=>e+t)})(),this._element.classList.add("offcanvas-toggling"),this._element.removeAttribute("aria-hidden"),this._element.setAttribute("aria-modal",!0),this._element.setAttribute("role","dialog"),this._element.classList.add("show"),setTimeout(()=>{this._element.classList.remove("offcanvas-toggling"),N.trigger(this._element,"shown.bs.offcanvas",{relatedTarget:t}),this._enforceFocusOnElement(this._element)},n(this._element)))}hide(){this._isShown&&(N.trigger(this._element,"hide.bs.offcanvas").defaultPrevented||(this._element.classList.add("offcanvas-toggling"),N.off(document,"focusin.bs.offcanvas"),this._element.blur(),this._isShown=!1,this._element.classList.remove("show"),setTimeout(()=>{this._element.setAttribute("aria-hidden",!0),this._element.removeAttribute("aria-modal"),this._element.removeAttribute("role"),this._element.style.visibility="hidden",this._config.backdrop&&document.body.classList.remove("offcanvas-backdrop"),this._config.scroll||(document.body.style.overflow="auto",Te(".fixed-top, .fixed-bottom, .is-fixed","paddingRight"),Te(".sticky-top","marginRight"),Te("body","paddingRight")),N.trigger(this._element,"hidden.bs.offcanvas"),this._element.classList.remove("offcanvas-toggling")},n(this._element))))}_getConfig(t){return t={...Ae,...B.getDataAttributes(this._element),..."object"==typeof t?t:{}},l("offcanvas",t,Le),t}_enforceFocusOnElement(t){N.off(document,"focusin.bs.offcanvas"),N.on(document,"focusin.bs.offcanvas",e=>{document===e.target||t===e.target||t.contains(e.target)||t.focus()}),t.focus()}_addEventListeners(){N.on(this._element,"click.dismiss.bs.offcanvas",'[data-bs-dismiss="offcanvas"]',()=>this.hide()),N.on(document,"keydown",t=>{this._config.keyboard&&"Escape"===t.key&&this.hide()}),N.on(document,"click.bs.offcanvas.data-api",t=>{const e=H.findOne(i(t.target));this._element.contains(t.target)||e===this._element||this.hide()})}static jQueryInterface(t){return this.each((function(){const e=b.get(this,"bs.offcanvas")||new Oe(this,"object"==typeof t?t:{});if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}N.on(document,"click.bs.offcanvas.data-api",'[data-bs-toggle="offcanvas"]',(function(t){const e=s(this);if(["A","AREA"].includes(this.tagName)&&t.preventDefault(),d(this))return;N.one(e,"hidden.bs.offcanvas",()=>{c(this)&&this.focus()});const i=H.findOne(".offcanvas.show, .offcanvas-toggling");i&&i!==e||(b.get(e,"bs.offcanvas")||new Oe(e)).toggle(this)})),N.on(window,"load.bs.offcanvas.data-api",()=>{H.find(".offcanvas.show").forEach(t=>(b.get(t,"bs.offcanvas")||new Oe(t)).show())}),m("offcanvas",Oe);const ke=new Set(["background","cite","href","itemtype","longdesc","poster","src","xlink:href"]),De=/^(?:(?:https?|mailto|ftp|tel|file):|[^#&/:?]*(?:[#/?]|$))/i,xe=/^data:(?:image\/(?:bmp|gif|jpeg|jpg|png|tiff|webp)|video\/(?:mpeg|mp4|ogg|webm)|audio\/(?:mp3|oga|ogg|opus));base64,[\d+/a-z]+=*$/i,Ce=(t,e)=>{const i=t.nodeName.toLowerCase();if(e.includes(i))return!ke.has(i)||Boolean(De.test(t.nodeValue)||xe.test(t.nodeValue));const s=e.filter(t=>t instanceof RegExp);for(let t=0,e=s.length;t<e;t++)if(s[t].test(i))return!0;return!1};function Se(t,e,i){if(!t.length)return t;if(i&&"function"==typeof i)return i(t);const s=(new window.DOMParser).parseFromString(t,"text/html"),n=Object.keys(e),o=[].concat(...s.body.querySelectorAll("*"));for(let t=0,i=o.length;t<i;t++){const i=o[t],s=i.nodeName.toLowerCase();if(!n.includes(s)){i.parentNode.removeChild(i);continue}const r=[].concat(...i.attributes),a=[].concat(e["*"]||[],e[s]||[]);r.forEach(t=>{Ce(t,a)||i.removeAttribute(t.nodeName)})}return s.body.innerHTML}const Ne=new RegExp("(^|\\s)bs-tooltip\\S+","g"),je=new Set(["sanitize","allowList","sanitizeFn"]),Pe={animation:"boolean",template:"string",title:"(string|element|function)",trigger:"string",delay:"(number|object)",html:"boolean",selector:"(string|boolean)",placement:"(string|function)",offset:"(array|string|function)",container:"(string|element|boolean)",fallbackPlacements:"array",boundary:"(string|element)",customClass:"(string|function)",sanitize:"boolean",sanitizeFn:"(null|function)",allowList:"object",popperConfig:"(null|object|function)"},Ie={AUTO:"auto",TOP:"top",RIGHT:g()?"left":"right",BOTTOM:"bottom",LEFT:g()?"right":"left"},Me={animation:!0,template:'<div class="tooltip" role="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>',trigger:"hover focus",title:"",delay:0,html:!1,selector:!1,placement:"top",offset:[0,0],container:!1,fallbackPlacements:["top","right","bottom","left"],boundary:"clippingParents",customClass:"",sanitize:!0,sanitizeFn:null,allowList:{"*":["class","dir","id","lang","role",/^aria-[\w-]*$/i],a:["target","href","title","rel"],area:[],b:[],br:[],col:[],code:[],div:[],em:[],hr:[],h1:[],h2:[],h3:[],h4:[],h5:[],h6:[],i:[],img:["src","srcset","alt","title","width","height"],li:[],ol:[],p:[],pre:[],s:[],small:[],span:[],sub:[],sup:[],strong:[],u:[],ul:[]},popperConfig:null},Re={HIDE:"hide.bs.tooltip",HIDDEN:"hidden.bs.tooltip",SHOW:"show.bs.tooltip",SHOWN:"shown.bs.tooltip",INSERTED:"inserted.bs.tooltip",CLICK:"click.bs.tooltip",FOCUSIN:"focusin.bs.tooltip",FOCUSOUT:"focusout.bs.tooltip",MOUSEENTER:"mouseenter.bs.tooltip",MOUSELEAVE:"mouseleave.bs.tooltip"};class Be extends j{constructor(t,e){if(void 0===ae)throw new TypeError("Bootstrap's tooltips require Popper (https://popper.js.org)");super(t),this._isEnabled=!0,this._timeout=0,this._hoverState="",this._activeTrigger={},this._popper=null,this.config=this._getConfig(e),this.tip=null,this._setListeners()}static get Default(){return Me}static get NAME(){return"tooltip"}static get DATA_KEY(){return"bs.tooltip"}static get Event(){return Re}static get EVENT_KEY(){return".bs.tooltip"}static get DefaultType(){return Pe}enable(){this._isEnabled=!0}disable(){this._isEnabled=!1}toggleEnabled(){this._isEnabled=!this._isEnabled}toggle(t){if(this._isEnabled)if(t){const e=this._initializeOnDelegatedTarget(t);e._activeTrigger.click=!e._activeTrigger.click,e._isWithActiveTrigger()?e._enter(null,e):e._leave(null,e)}else{if(this.getTipElement().classList.contains("show"))return void this._leave(null,this);this._enter(null,this)}}dispose(){clearTimeout(this._timeout),N.off(this._element,this.constructor.EVENT_KEY),N.off(this._element.closest(".modal"),"hide.bs.modal",this._hideModalHandler),this.tip&&this.tip.parentNode&&this.tip.parentNode.removeChild(this.tip),this._isEnabled=null,this._timeout=null,this._hoverState=null,this._activeTrigger=null,this._popper&&this._popper.destroy(),this._popper=null,this.config=null,this.tip=null,super.dispose()}show(){if("none"===this._element.style.display)throw new Error("Please use show on visible elements");if(!this.isWithContent()||!this._isEnabled)return;const e=N.trigger(this._element,this.constructor.Event.SHOW),i=h(this._element),s=null===i?this._element.ownerDocument.documentElement.contains(this._element):i.contains(this._element);if(e.defaultPrevented||!s)return;const o=this.getTipElement(),r=t(this.constructor.NAME);o.setAttribute("id",r),this._element.setAttribute("aria-describedby",r),this.setContent(),this.config.animation&&o.classList.add("fade");const l="function"==typeof this.config.placement?this.config.placement.call(this,o,this._element):this.config.placement,c=this._getAttachment(l);this._addAttachmentClass(c);const d=this._getContainer();b.set(o,this.constructor.DATA_KEY,this),this._element.ownerDocument.documentElement.contains(this.tip)||(d.appendChild(o),N.trigger(this._element,this.constructor.Event.INSERTED)),this._popper?this._popper.update():this._popper=re(this._element,o,this._getPopperConfig(c)),o.classList.add("show");const f="function"==typeof this.config.customClass?this.config.customClass():this.config.customClass;f&&o.classList.add(...f.split(" ")),"ontouchstart"in document.documentElement&&[].concat(...document.body.children).forEach(t=>{N.on(t,"mouseover",(function(){}))});const u=()=>{const t=this._hoverState;this._hoverState=null,N.trigger(this._element,this.constructor.Event.SHOWN),"out"===t&&this._leave(null,this)};if(this.tip.classList.contains("fade")){const t=n(this.tip);N.one(this.tip,"transitionend",u),a(this.tip,t)}else u()}hide(){if(!this._popper)return;const t=this.getTipElement(),e=()=>{this._isWithActiveTrigger()||("show"!==this._hoverState&&t.parentNode&&t.parentNode.removeChild(t),this._cleanTipClass(),this._element.removeAttribute("aria-describedby"),N.trigger(this._element,this.constructor.Event.HIDDEN),this._popper&&(this._popper.destroy(),this._popper=null))};if(!N.trigger(this._element,this.constructor.Event.HIDE).defaultPrevented){if(t.classList.remove("show"),"ontouchstart"in document.documentElement&&[].concat(...document.body.children).forEach(t=>N.off(t,"mouseover",f)),this._activeTrigger.click=!1,this._activeTrigger.focus=!1,this._activeTrigger.hover=!1,this.tip.classList.contains("fade")){const i=n(t);N.one(t,"transitionend",e),a(t,i)}else e();this._hoverState=""}}update(){null!==this._popper&&this._popper.update()}isWithContent(){return Boolean(this.getTitle())}getTipElement(){if(this.tip)return this.tip;const t=document.createElement("div");return t.innerHTML=this.config.template,this.tip=t.children[0],this.tip}setContent(){const t=this.getTipElement();this.setElementContent(H.findOne(".tooltip-inner",t),this.getTitle()),t.classList.remove("fade","show")}setElementContent(t,e){if(null!==t)return"object"==typeof e&&r(e)?(e.jquery&&(e=e[0]),void(this.config.html?e.parentNode!==t&&(t.innerHTML="",t.appendChild(e)):t.textContent=e.textContent)):void(this.config.html?(this.config.sanitize&&(e=Se(e,this.config.allowList,this.config.sanitizeFn)),t.innerHTML=e):t.textContent=e)}getTitle(){let t=this._element.getAttribute("data-bs-original-title");return t||(t="function"==typeof this.config.title?this.config.title.call(this._element):this.config.title),t}updateAttachment(t){return"right"===t?"end":"left"===t?"start":t}_initializeOnDelegatedTarget(t,e){const i=this.constructor.DATA_KEY;return(e=e||b.get(t.delegateTarget,i))||(e=new this.constructor(t.delegateTarget,this._getDelegateConfig()),b.set(t.delegateTarget,i,e)),e}_getOffset(){const{offset:t}=this.config;return"string"==typeof t?t.split(",").map(t=>Number.parseInt(t,10)):"function"==typeof t?e=>t(e,this._element):t}_getPopperConfig(t){const e={placement:t,modifiers:[{name:"flip",options:{altBoundary:!0,fallbackPlacements:this.config.fallbackPlacements}},{name:"offset",options:{offset:this._getOffset()}},{name:"preventOverflow",options:{boundary:this.config.boundary}},{name:"arrow",options:{element:`.${this.constructor.NAME}-arrow`}},{name:"onChange",enabled:!0,phase:"afterWrite",fn:t=>this._handlePopperPlacementChange(t)}],onFirstUpdate:t=>{t.options.placement!==t.placement&&this._handlePopperPlacementChange(t)}};return{...e,..."function"==typeof this.config.popperConfig?this.config.popperConfig(e):this.config.popperConfig}}_addAttachmentClass(t){this.getTipElement().classList.add("bs-tooltip-"+this.updateAttachment(t))}_getContainer(){return!1===this.config.container?document.body:r(this.config.container)?this.config.container:H.findOne(this.config.container)}_getAttachment(t){return Ie[t.toUpperCase()]}_setListeners(){this.config.trigger.split(" ").forEach(t=>{if("click"===t)N.on(this._element,this.constructor.Event.CLICK,this.config.selector,t=>this.toggle(t));else if("manual"!==t){const e="hover"===t?this.constructor.Event.MOUSEENTER:this.constructor.Event.FOCUSIN,i="hover"===t?this.constructor.Event.MOUSELEAVE:this.constructor.Event.FOCUSOUT;N.on(this._element,e,this.config.selector,t=>this._enter(t)),N.on(this._element,i,this.config.selector,t=>this._leave(t))}}),this._hideModalHandler=()=>{this._element&&this.hide()},N.on(this._element.closest(".modal"),"hide.bs.modal",this._hideModalHandler),this.config.selector?this.config={...this.config,trigger:"manual",selector:""}:this._fixTitle()}_fixTitle(){const t=this._element.getAttribute("title"),e=typeof this._element.getAttribute("data-bs-original-title");(t||"string"!==e)&&(this._element.setAttribute("data-bs-original-title",t||""),!t||this._element.getAttribute("aria-label")||this._element.textContent||this._element.setAttribute("aria-label",t),this._element.setAttribute("title",""))}_enter(t,e){e=this._initializeOnDelegatedTarget(t,e),t&&(e._activeTrigger["focusin"===t.type?"focus":"hover"]=!0),e.getTipElement().classList.contains("show")||"show"===e._hoverState?e._hoverState="show":(clearTimeout(e._timeout),e._hoverState="show",e.config.delay&&e.config.delay.show?e._timeout=setTimeout(()=>{"show"===e._hoverState&&e.show()},e.config.delay.show):e.show())}_leave(t,e){e=this._initializeOnDelegatedTarget(t,e),t&&(e._activeTrigger["focusout"===t.type?"focus":"hover"]=e._element.contains(t.relatedTarget)),e._isWithActiveTrigger()||(clearTimeout(e._timeout),e._hoverState="out",e.config.delay&&e.config.delay.hide?e._timeout=setTimeout(()=>{"out"===e._hoverState&&e.hide()},e.config.delay.hide):e.hide())}_isWithActiveTrigger(){for(const t in this._activeTrigger)if(this._activeTrigger[t])return!0;return!1}_getConfig(t){const e=B.getDataAttributes(this._element);return Object.keys(e).forEach(t=>{je.has(t)&&delete e[t]}),t&&"object"==typeof t.container&&t.container.jquery&&(t.container=t.container[0]),"number"==typeof(t={...this.constructor.Default,...e,..."object"==typeof t&&t?t:{}}).delay&&(t.delay={show:t.delay,hide:t.delay}),"number"==typeof t.title&&(t.title=t.title.toString()),"number"==typeof t.content&&(t.content=t.content.toString()),l("tooltip",t,this.constructor.DefaultType),t.sanitize&&(t.template=Se(t.template,t.allowList,t.sanitizeFn)),t}_getDelegateConfig(){const t={};if(this.config)for(const e in this.config)this.constructor.Default[e]!==this.config[e]&&(t[e]=this.config[e]);return t}_cleanTipClass(){const t=this.getTipElement(),e=t.getAttribute("class").match(Ne);null!==e&&e.length>0&&e.map(t=>t.trim()).forEach(e=>t.classList.remove(e))}_handlePopperPlacementChange(t){const{state:e}=t;e&&(this.tip=e.elements.popper,this._cleanTipClass(),this._addAttachmentClass(this._getAttachment(e.placement)))}static jQueryInterface(t){return this.each((function(){let e=b.get(this,"bs.tooltip");const i="object"==typeof t&&t;if((e||!/dispose|hide/.test(t))&&(e||(e=new Be(this,i)),"string"==typeof t)){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}m("tooltip",Be);const He=new RegExp("(^|\\s)bs-popover\\S+","g"),We={...Be.Default,placement:"right",offset:[0,8],trigger:"click",content:"",template:'<div class="popover" role="tooltip"><div class="popover-arrow"></div><h3 class="popover-header"></h3><div class="popover-body"></div></div>'},Ue={...Be.DefaultType,content:"(string|element|function)"},$e={HIDE:"hide.bs.popover",HIDDEN:"hidden.bs.popover",SHOW:"show.bs.popover",SHOWN:"shown.bs.popover",INSERTED:"inserted.bs.popover",CLICK:"click.bs.popover",FOCUSIN:"focusin.bs.popover",FOCUSOUT:"focusout.bs.popover",MOUSEENTER:"mouseenter.bs.popover",MOUSELEAVE:"mouseleave.bs.popover"};class Fe extends Be{static get Default(){return We}static get NAME(){return"popover"}static get DATA_KEY(){return"bs.popover"}static get Event(){return $e}static get EVENT_KEY(){return".bs.popover"}static get DefaultType(){return Ue}isWithContent(){return this.getTitle()||this._getContent()}setContent(){const t=this.getTipElement();this.setElementContent(H.findOne(".popover-header",t),this.getTitle());let e=this._getContent();"function"==typeof e&&(e=e.call(this._element)),this.setElementContent(H.findOne(".popover-body",t),e),t.classList.remove("fade","show")}_addAttachmentClass(t){this.getTipElement().classList.add("bs-popover-"+this.updateAttachment(t))}_getContent(){return this._element.getAttribute("data-bs-content")||this.config.content}_cleanTipClass(){const t=this.getTipElement(),e=t.getAttribute("class").match(He);null!==e&&e.length>0&&e.map(t=>t.trim()).forEach(e=>t.classList.remove(e))}static jQueryInterface(t){return this.each((function(){let e=b.get(this,"bs.popover");const i="object"==typeof t?t:null;if((e||!/dispose|hide/.test(t))&&(e||(e=new Fe(this,i),b.set(this,"bs.popover",e)),"string"==typeof t)){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}m("popover",Fe);const ze={offset:10,method:"auto",target:""},Ke={offset:"number",method:"string",target:"(string|element)"};class Ye extends j{constructor(t,e){super(t),this._scrollElement="BODY"===this._element.tagName?window:this._element,this._config=this._getConfig(e),this._selector=`${this._config.target} .nav-link, ${this._config.target} .list-group-item, ${this._config.target} .dropdown-item`,this._offsets=[],this._targets=[],this._activeTarget=null,this._scrollHeight=0,N.on(this._scrollElement,"scroll.bs.scrollspy",()=>this._process()),this.refresh(),this._process()}static get Default(){return ze}static get DATA_KEY(){return"bs.scrollspy"}refresh(){const t=this._scrollElement===this._scrollElement.window?"offset":"position",e="auto"===this._config.method?t:this._config.method,s="position"===e?this._getScrollTop():0;this._offsets=[],this._targets=[],this._scrollHeight=this._getScrollHeight(),H.find(this._selector).map(t=>{const n=i(t),o=n?H.findOne(n):null;if(o){const t=o.getBoundingClientRect();if(t.width||t.height)return[B[e](o).top+s,n]}return null}).filter(t=>t).sort((t,e)=>t[0]-e[0]).forEach(t=>{this._offsets.push(t[0]),this._targets.push(t[1])})}dispose(){super.dispose(),N.off(this._scrollElement,".bs.scrollspy"),this._scrollElement=null,this._config=null,this._selector=null,this._offsets=null,this._targets=null,this._activeTarget=null,this._scrollHeight=null}_getConfig(e){if("string"!=typeof(e={...ze,..."object"==typeof e&&e?e:{}}).target&&r(e.target)){let{id:i}=e.target;i||(i=t("scrollspy"),e.target.id=i),e.target="#"+i}return l("scrollspy",e,Ke),e}_getScrollTop(){return this._scrollElement===window?this._scrollElement.pageYOffset:this._scrollElement.scrollTop}_getScrollHeight(){return this._scrollElement.scrollHeight||Math.max(document.body.scrollHeight,document.documentElement.scrollHeight)}_getOffsetHeight(){return this._scrollElement===window?window.innerHeight:this._scrollElement.getBoundingClientRect().height}_process(){const t=this._getScrollTop()+this._config.offset,e=this._getScrollHeight(),i=this._config.offset+e-this._getOffsetHeight();if(this._scrollHeight!==e&&this.refresh(),t>=i){const t=this._targets[this._targets.length-1];this._activeTarget!==t&&this._activate(t)}else{if(this._activeTarget&&t<this._offsets[0]&&this._offsets[0]>0)return this._activeTarget=null,void this._clear();for(let e=this._offsets.length;e--;)this._activeTarget!==this._targets[e]&&t>=this._offsets[e]&&(void 0===this._offsets[e+1]||t<this._offsets[e+1])&&this._activate(this._targets[e])}}_activate(t){this._activeTarget=t,this._clear();const e=this._selector.split(",").map(e=>`${e}[data-bs-target="${t}"],${e}[href="${t}"]`),i=H.findOne(e.join(","));i.classList.contains("dropdown-item")?(H.findOne(".dropdown-toggle",i.closest(".dropdown")).classList.add("active"),i.classList.add("active")):(i.classList.add("active"),H.parents(i,".nav, .list-group").forEach(t=>{H.prev(t,".nav-link, .list-group-item").forEach(t=>t.classList.add("active")),H.prev(t,".nav-item").forEach(t=>{H.children(t,".nav-link").forEach(t=>t.classList.add("active"))})})),N.trigger(this._scrollElement,"activate.bs.scrollspy",{relatedTarget:t})}_clear(){H.find(this._selector).filter(t=>t.classList.contains("active")).forEach(t=>t.classList.remove("active"))}static jQueryInterface(t){return this.each((function(){let e=b.get(this,"bs.scrollspy");if(e||(e=new Ye(this,"object"==typeof t&&t)),"string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}N.on(window,"load.bs.scrollspy.data-api",()=>{H.find('[data-bs-spy="scroll"]').forEach(t=>new Ye(t,B.getDataAttributes(t)))}),m("scrollspy",Ye);class qe extends j{static get DATA_KEY(){return"bs.tab"}show(){if(this._element.parentNode&&this._element.parentNode.nodeType===Node.ELEMENT_NODE&&this._element.classList.contains("active")||d(this._element))return;let t;const e=s(this._element),i=this._element.closest(".nav, .list-group");if(i){const e="UL"===i.nodeName||"OL"===i.nodeName?":scope > li > .active":".active";t=H.find(e,i),t=t[t.length-1]}const n=t?N.trigger(t,"hide.bs.tab",{relatedTarget:this._element}):null;if(N.trigger(this._element,"show.bs.tab",{relatedTarget:t}).defaultPrevented||null!==n&&n.defaultPrevented)return;this._activate(this._element,i);const o=()=>{N.trigger(t,"hidden.bs.tab",{relatedTarget:this._element}),N.trigger(this._element,"shown.bs.tab",{relatedTarget:t})};e?this._activate(e,e.parentNode,o):o()}_activate(t,e,i){const s=(!e||"UL"!==e.nodeName&&"OL"!==e.nodeName?H.children(e,".active"):H.find(":scope > li > .active",e))[0],o=i&&s&&s.classList.contains("fade"),r=()=>this._transitionComplete(t,s,i);if(s&&o){const t=n(s);s.classList.remove("show"),N.one(s,"transitionend",r),a(s,t)}else r()}_transitionComplete(t,e,i){if(e){e.classList.remove("active");const t=H.findOne(":scope > .dropdown-menu .active",e.parentNode);t&&t.classList.remove("active"),"tab"===e.getAttribute("role")&&e.setAttribute("aria-selected",!1)}t.classList.add("active"),"tab"===t.getAttribute("role")&&t.setAttribute("aria-selected",!0),u(t),t.classList.contains("fade")&&t.classList.add("show"),t.parentNode&&t.parentNode.classList.contains("dropdown-menu")&&(t.closest(".dropdown")&&H.find(".dropdown-toggle").forEach(t=>t.classList.add("active")),t.setAttribute("aria-expanded",!0)),i&&i()}static jQueryInterface(t){return this.each((function(){const e=b.get(this,"bs.tab")||new qe(this);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}N.on(document,"click.bs.tab.data-api",'[data-bs-toggle="tab"], [data-bs-toggle="pill"], [data-bs-toggle="list"]',(function(t){t.preventDefault(),(b.get(this,"bs.tab")||new qe(this)).show()})),m("tab",qe);const Ve={animation:"boolean",autohide:"boolean",delay:"number"},Xe={animation:!0,autohide:!0,delay:5e3};class Qe extends j{constructor(t,e){super(t),this._config=this._getConfig(e),this._timeout=null,this._setListeners()}static get DefaultType(){return Ve}static get Default(){return Xe}static get DATA_KEY(){return"bs.toast"}show(){if(N.trigger(this._element,"show.bs.toast").defaultPrevented)return;this._clearTimeout(),this._config.animation&&this._element.classList.add("fade");const t=()=>{this._element.classList.remove("showing"),this._element.classList.add("show"),N.trigger(this._element,"shown.bs.toast"),this._config.autohide&&(this._timeout=setTimeout(()=>{this.hide()},this._config.delay))};if(this._element.classList.remove("hide"),u(this._element),this._element.classList.add("showing"),this._config.animation){const e=n(this._element);N.one(this._element,"transitionend",t),a(this._element,e)}else t()}hide(){if(!this._element.classList.contains("show"))return;if(N.trigger(this._element,"hide.bs.toast").defaultPrevented)return;const t=()=>{this._element.classList.add("hide"),N.trigger(this._element,"hidden.bs.toast")};if(this._element.classList.remove("show"),this._config.animation){const e=n(this._element);N.one(this._element,"transitionend",t),a(this._element,e)}else t()}dispose(){this._clearTimeout(),this._element.classList.contains("show")&&this._element.classList.remove("show"),N.off(this._element,"click.dismiss.bs.toast"),super.dispose(),this._config=null}_getConfig(t){return t={...Xe,...B.getDataAttributes(this._element),..."object"==typeof t&&t?t:{}},l("toast",t,this.constructor.DefaultType),t}_setListeners(){N.on(this._element,"click.dismiss.bs.toast",'[data-bs-dismiss="toast"]',()=>this.hide())}_clearTimeout(){clearTimeout(this._timeout),this._timeout=null}static jQueryInterface(t){return this.each((function(){let e=b.get(this,"bs.toast");if(e||(e=new Qe(this,"object"==typeof t&&t)),"string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}return m("toast",Qe),{Alert:P,Button:I,Carousel:Y,Collapse:X,Dropdown:_e,Modal:ye,Offcanvas:Oe,Popover:Fe,ScrollSpy:Ye,Tab:qe,Toast:Qe,Tooltip:Be}}));
-//# sourceMappingURL=bootstrap.bundle.min.js.map
\ No newline at end of file
diff --git a/misc/merge_js.sh b/misc/merge_js.sh
index 375529fe472a05caafb12981a6fb17aef0ae8db9..506a65866db42c329b4e19c197842f04bdfc4aca 100755
--- a/misc/merge_js.sh
+++ b/misc/merge_js.sh
@@ -33,10 +33,61 @@
 
 
 CORE_MODULES=$@
-SOURCE_DIR=public/js/
-TARGET=public/webcaosdb.dist.js
+PUBLIC_JS_DIR=public/js/
+DIST_BUNDLE=webcaosdb.dist.js
+DIST_BUNDLE_TARGET=public/${DIST_BUNDLE}
+JSHEADER_TARGET=public/xsl/jsheader.xsl
+ALL_SOURCES=()
+
+_create_jsheader () {
+    _JS_INCLUDE=
+    if [ "$JS_DIST_BUNDLE" == "TRUE" ] ; then
+        _SIZE=$(( $(wc -c ${DIST_BUNDLE_TARGET} | awk '{print $1}')/1024))
+        echo "including ${DIST_BUNDLE} (${_SIZE}kB) into ${JSHEADER_TARGET}"
+        _JS_INCLUDE="
+    <xsl:element name=\"script\">
+      <xsl:attribute name=\"src\">
+        <xsl:value-of select=\"concat(\$basepath,'webinterface/\${BUILD_NUMBER}/${DIST_BUNDLE}')\"/>
+      </xsl:attribute>
+    </xsl:element>
+    "
+        [[ -f "public/index.html" ]] && sed -i "s|^\(.*JS_INCLUDE.*\)$|    <script src=\"${DIST_BUNDLE}\"><\/script>\n\1|g" public/index.html ;
+    else
+        _ALL_SOURCES=$@
+        echo "${_ALL_SOURCES}"
+        for _SOURCE in ${_ALL_SOURCES[@]} ; do
+            _SIZE=$(( $(wc -c ${_SOURCE} | awk '{print $1}')/1024))
+            _SOURCE=js/${_SOURCE/${PUBLIC_JS_DIR}/}
+            echo "including ${_SOURCE} (${_SIZE}kB) into ${JSHEADER_TARGET}"
+            _JS_INCLUDE="${_JS_INCLUDE}
+    <xsl:element name=\"script\">
+      <xsl:attribute name=\"src\">
+        <xsl:value-of select=\"concat(\$basepath,'webinterface/\${BUILD_NUMBER}/${_SOURCE}')\"/>
+      </xsl:attribute>
+    </xsl:element>
+    "
+            sed -i "s|^\(.*JS_INCLUDE.*\)$|    <script src=\"${_SOURCE}\"><\/script>\n\1|g" public/index.html ;
+        done
+    fi
+
+
+    echo "<?xml version=\"1.0\" encoding=\"UTF-8\"?>
+<!-- THIS FILE IS AUTO-GENERATED BY THE merge_js.sh SCRIPT -->
+<xsl:stylesheet version=\"1.0\" xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\">
+  <xsl:output method=\"html\"/>
+  <xsl:template name=\"caosdb-head-js\">
+    <script>
+        window.sessionStorage.caosdbBasePath = \"<xsl:value-of select=\"\$basepath\"/>\";
+    </script>
+    ${_JS_INCLUDE}
+  </xsl:template>
+</xsl:stylesheet>" > ${JSHEADER_TARGET}
+}
 
 function _merge () {
+    if [ "$JS_DIST_BUNDLE" != "TRUE" ] ; then
+        return 0
+    fi
     _SOURCE=$2
     _TARGET=$3
 
@@ -51,20 +102,26 @@ function _merge () {
 }
 
 # clean up old
-rm $TARGET || true
-touch $TARGET
+rm $DIST_BUNDLE_TARGET || true
+touch $DIST_BUNDLE_TARGET
 
 for _SOURCE in ${CORE_MODULES[@]} ; do
-    _merge "core" "${SOURCE_DIR}${_SOURCE}" $TARGET
+    [[ ! " ${ALL_SOURCES[@]} " =~ " ${_SOURCE} " ]] && ALL_SOURCES+=(${PUBLIC_JS_DIR}${_SOURCE})
+    _merge "core" "${PUBLIC_JS_DIR}${_SOURCE}" $DIST_BUNDLE_TARGET
 done
 
-# load other js files but exclude any subdirectory
-for _SOURCE in $(find ${SOURCE_DIR}* -prune -iname "*.js") ; do
-    _merge "extension" ${_SOURCE} $TARGET
-done
+if [ "$AUTO_DISCOVER_MODULES" == "TRUE" ] ; then
+    # load other js files but exclude any subdirectory
+    for _SOURCE in $(find ${PUBLIC_JS_DIR}* -prune -iname "*.js") ; do
+        [[ ! " ${ALL_SOURCES[@]} " =~ " ${_SOURCE} " ]] && ALL_SOURCES+=(${_SOURCE})
+        _merge "extension" ${_SOURCE} $DIST_BUNDLE_TARGET
+    done
+fi
 
 # for `make test`
-for _SOURCE in $(find ${SOURCE_DIR} -ipath "${SOURCE_DIR}modules/*.js") ; do
-    _merge "extension" ${_SOURCE} $TARGET
+for _SOURCE in $(find ${PUBLIC_JS_DIR} -ipath "${PUBLIC_JS_DIR}modules/*.js") ; do
+    [[ ! " ${ALL_SOURCES[@]} " =~ " ${_SOURCE} " ]] && ALL_SOURCES+=(${_SOURCE})
+    _merge "extension" ${_SOURCE} $DIST_BUNDLE_TARGET
 done
 
+_create_jsheader ${ALL_SOURCES[@]}
diff --git a/misc/merge_xsl.sh b/misc/merge_xsl.sh
index 07cc6d7ce8dc690eaa58ad8a42e6c883e5eda149..abf95444de1ca550f5cd66ae1f02e2f0587503f0 100755
--- a/misc/merge_xsl.sh
+++ b/misc/merge_xsl.sh
@@ -30,7 +30,7 @@
 
 
 SOURCE_DIR=public/
-MERGE="xsl/footer.xsl xsl/filesystem.xsl xsl/entity.xsl xsl/query.xsl xsl/messages.xsl xsl/navbar.xsl xsl/main.xsl xsl/welcome.xsl xsl/common.xsl"
+MERGE="xsl/jsheader.xsl xsl/footer.xsl xsl/filesystem.xsl xsl/entity.xsl xsl/query.xsl xsl/messages.xsl xsl/navbar.xsl xsl/main.xsl xsl/welcome.xsl xsl/common.xsl"
 TARGET=public/webcaosdb.xsl
 
 
diff --git a/misc/unit_test_http_server.py b/misc/unit_test_http_server.py
index 68e6a7434a584a26a242e5891329f3fb6c6d159f..56205df8d34491b865698742f6b24b1679894df8 100755
--- a/misc/unit_test_http_server.py
+++ b/misc/unit_test_http_server.py
@@ -153,5 +153,5 @@ class UnitTestHTTPServer(HTTPServer):
         os._exit(self._exit_code)#pylint: disable=protected-access
 
 
-UnitTestHTTPServer(server_address=('127.0.0.1', int(sys.argv[1])),
+UnitTestHTTPServer(server_address=('0.0.0.0', int(sys.argv[1])),
                    timeout=float(sys.argv[2]), ignore_done=(sys.argv[3] == "True")).start()
diff --git a/src/core/css/webcaosdb.css b/src/core/css/webcaosdb.css
index fb86c816a22ebfcf22a865f0b86d840d80e26b1f..b76a5fe9ecc9f6d866dc159429ef7401ac3ddd5d 100644
--- a/src/core/css/webcaosdb.css
+++ b/src/core/css/webcaosdb.css
@@ -31,7 +31,7 @@ body {
 }
 
 .background {
-    background-color: #1a4548;
+    background-color: white;
     min-height: 60vh;
 }
 
@@ -159,9 +159,9 @@ tbody:not(:hover) tr .caosdb-v-entity-version-hint-cur {
 /* DEPRECATED css class .caosdb-property-text-value - Use
 * .caosdb-f-property-single-raw-value or introduce new
 * .caosdb-v-property-text-value */
-.caosdb-property-text-value {
+.caosdb-property-text-value,
+.caosdb-v-property-text-value {
     white-space: pre-line;
-    background-color: transparent !important;
 }
 
 .caosdb-clickable:hover {
@@ -295,7 +295,7 @@ h5 {
 }
 .caosdb-value-list > .btn-group,
 .caosdb-value-list > ol {
-	display: inline-block;
+    display: inline-block;
     float: none;
     white-space: nowrap;
     margin-bottom: 0px;
@@ -303,16 +303,21 @@ h5 {
 }
 
 .caosdb-value-list > .list-inline > li {
-    border-left-width: 0px; 
+    background-color: white;
+    padding: 0.2rem 0.5rem;
+    margin: auto 0;
+    border: 1px solid #212529;
+    border-left-width: 0px;
+    font-size: 0.875rem;
 }
 
 .caosdb-value-list > .list-inline > li:first-child {
-	border-radius: 4px 0px 0px 4px;
-	border-left-width: 1px;
+    border-radius: 0.2rem 0px 0px 0.2rem;
+    border-left-width: 1px;
 }
 
 .caosdb-value-list > .list-inline > li:last-child {
-    border-radius: 0px 4px 4px 0px;
+    border-radius: 0px 0.2rem 0.2rem 0px;
 }
 
 /* single boolean values */
@@ -486,7 +491,10 @@ h5 {
 }
 
 .caosdb-logo {
+    margin-top: auto;
+    margin-bottom: auto;
     margin-right: 8px;
+    max-height: 30px;
 }
 
 .caosdb-comment-action-item {
@@ -514,6 +522,10 @@ h5 {
     padding-right: 1rem;
     padding-top: 0.3ex;
     padding-bottom: 0.3ex;
+    border-radius: none;
+    border-left: none;
+    border-right: none;
+
 }
 
 @keyframes appear {
@@ -742,3 +754,20 @@ details p {
 .caosdb-f-map-panel .leaflet-container {
     height: 500px;
 }
+
+.caosdb-v-field .bootstrap-select button {
+    border: 1px solid #ced4da;
+    background-color: #FFF;
+}
+
+.caosdb-v-field > div {
+    margin-top: auto;
+    margin-bottom: auto;
+}
+
+.caosdb-v-property-href-value {
+    text-decoration: none;
+    font-weight: 600;
+    font-size: 0.875rem;
+    color: #5E6762;
+}
diff --git a/src/core/css/webcaosdb.less b/src/core/css/webcaosdb.less
deleted file mode 100644
index 54e9113df34232234ed6814a4383cf7ba954e121..0000000000000000000000000000000000000000
--- a/src/core/css/webcaosdb.less
+++ /dev/null
@@ -1,401 +0,0 @@
-/*
- * ** header v3.0
- * This file is a part of the CaosDB Project.
- *
- * Copyright (C) 2018 Research Group Biomedical Physics,
- * Max-Planck-Institute for Dynamics and Self-Organization Göttingen
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- *
- * ** end header
- */
-
-@radius_normal: 4px;
-@margin_normal: 8px;
-@border1: 1px solid #e7e7e7;
-
-.caosdb-v-show-only-child {
-    display: none;
-}
-
-.caosdb-v-show-only-child:only-child {
-    display: initial;
-}
-
-.caosdb-comment-annotation-text h1 {
-    font-size: 24px;
-}
-
-.caosdb-comment-annotation-text h2 {
-    font-size: 20px;
-}
-
-.caosdb-comment-annotation-text h3 {
-    font-size: 18px;
-}
-
-.caosdb-comment-annotation-text h4 {
-    font-size: 16px;
-}
-
-.caosdb-show-preview-button, .caosdb-hide-preview-button {
-    position: absolute;
-    top: 5px;
-    left: -5px;
-}
-
-.caosdb-entity-heading-attr {
-    overflow-x: auto;
-}
-
-a.label.caosdb-id-button:hover {
-    background-color: #5E6762;
-}
-
-.caosdb-entity-preview .caosdb-entity-panel-body {
-    overflow-y: auto;
-    max-height: 250px;
-}
-
-.caosdb-preview-carousel-nav > .caosdb-value-list > .btn-group > .btn:first-child {
-    margin-left: 34px;
-}
-.caosdb-preview-carousel-nav > .caosdb-value-list > .btn-group > .btn:last-child {
-    margin-right: 34px;
-}
-
-.caosdb-preview-carousel-nav {
-    position: relative;
-}
-.caosdb-query-panel {
-	padding-top: 20px;
-	padding-bottom: 20px;
-}
-
-.navbar-light .btn-link:hover {
-	text-decoration: none;
-}
-
-.navbar-light .btn-link:focus {
-    text-decoration: none;
-}
-
-.caosdb-property-name {
-	font-weight: bold;
-	margin-left: @margin-normal;
-}
-
-.caosdb-square {
-	position: relative;
-	overflow: hidden;
-}
-
-.caosdb-square::before {
-	content: "";
-	display: block;
-	padding-top: 100%;
-}
-
-.caosdb-square-content {
-	position: absolute;
-	top: 0px;
-	right: 0px;
-	bottom: 0px;
-	left: 0px;
-}
-
-.caosdb-parent-name {
-    font-weight: bold;
-    margin-right: 4px;	
-}
-
-/* lists of values */
-/* INLINE (with default scroll-bar) */
-.caosdb-value-list {	
-	overflow-x: auto;
-}
-
-.caosdb-value-list > .btn-group > .btn {
-    float: none;
-}
-.caosdb-value-list > .btn-group,
-.caosdb-value-list > ol {
-	display: inline-block;
-    float: none;
-    white-space: nowrap;
-    margin-bottom: 0px;
-    margin-left: 0px;
-}
-
-.caosdb-value-list > .list-inline > li {
-    border-left-width: 0px; 
-}
-
-.caosdb-value-list > .list-inline > li:first-child {
-	border-radius: @radius_normal 0px 0px @radius_normal;
-	border-left-width: 1px;
-}
-
-.caosdb-value-list > .list-inline > li:last-child {
-    border-radius: 0px @radius_normal @radius_normal 0px;
-}
-
-/* single boolean values */
-.caosdb-boolean-true {
-	font-weight: bold;
-	font-size: 90%;
-	border: 1px solid #bbb;
-	padding: 2px 8px;
-	border-radius: 8px;
-}
-
-.caosdb-boolean-false {
-    .caosdb-boolean-true();
-}
-
-.caosdb-entity-panel-heading {
-    border-bottom-left-radius: 3px;
-    border-bottom-right-radius: 3px;  
-}
-
-.caosdb-entity-panel-body {
-    padding: 0px 15px;	
-}
-
-.caosdb-entity-panel-body > :first-child {
-	margin-top: 15px;
-}
-
-.caosdb-entity-heading-attr-name {
-	color: #6c6c6c;
-    font-size: 90%;
-    margin-right: 0.3em; 
-}
-
-.caosdb-subproperty-divider {
-    margin: 4px 0px;
-    border-top: 1px solid #dddddd
-}
-
-.caosdb-prop-label {
-    background-color: #f5f5f5;
-    font-weight: bold;
-    padding: 8px 15px;
-}
-
-.caosdb-prop-value {
-    background-color: #ffffff;
-    padding: 8px 15px;
-}
-
-.caosdb-prop-list-group .row {
-    margin-left: 0px;
-    margin-right: 0px;
-}
-
-.caosdb-prop-list-group>.list-group-item {
-    padding: 0px
-}
-
-.caosdb-prop-list-group>.list-group-item:first-child .caosdb-prop-label
-    {
-    border-top-left-radius: @radius_normal;
-    border-top-right-radius: @radius_normal;
-}
-
-.caosdb-prop-list-group>.list-group-item:first-child .caosdb-prop-value
-    {
-    border-top-right-radius: @radius_normal;
-}
-
-.caosdb-prop-list-group>.list-group-item:last-child .caosdb-prop-label {
-    border-bottom-left-radius: @radius_normal;
-}
-
-.caosdb-prop-list-group>.list-group-item:last-child .caosdb-prop-value {
-    border-bottom-left-radius: @radius_normal;
-    border-bottom-right-radius: @radius_normal;
-}
-
-.caosdb-label-record {
-    background-color: #F92108;
-    margin-right: @margin-normal;
-}
-
-.caosdb-label-recordtype {
-    background-color: #00A32E;
-    margin-right: @margin-normal;
-}
-
-.caosdb-label-property {
-    background-color: #496DAB;
-    margin-right: @margin-normal;
-}
-
-.caosdb-label-file {
-    background-color: #C92E86;
-    margin-right: @margin-normal;
-}
-
-.label.caosdb-id-button {
-    background-color: #4E5752;
-}
-
-
-.caosdb-properties-heading {
-    padding-top: 2px;
-    padding-bottom: 2px;
-    background-color: #f5f5f5;
-    color: #7c7c7c
-}
-
-.caosdb-parents-heading {
-    .caosdb-properties-heading();
-}
-
-.caosdb-comments-heading {
-    .caosdb-properties-heading();
-}
-
-.caosdb-parent-item {
-    padding-left: 40px;
-    text-indent: -40px
-}
-
-.caosdb-unit {
-    color: #8c8c8c;
-    font-size: 80%;
-    margin-left: 0.3em;
-}
-
-.navbar-brand {
-    display: flex;
-    align-items: center;
-}
-
-.navbar-brand>img {
-    padding: 0px 0px;
-    height: 100%
-}
-
-.caosdb-fs-cwd::before {
-    content: " > ";
-}
-
-.caosdb-fs-dir>.glyphicon::before {
-    content: "\e117";
-}
-
-.caosdb-fs-dir>.glyphicon {
-    margin-right: @margin-normal;
-}
-
-.caosdb-fs-dir:hover>.glyphicon::before {
-    content: "\e118";
-}
-
-.caosdb-fs-file>.glyphicon::before {
-    content: "\e022";
-}
-
-.caosdb-fs-file>.glyphicon {
-    margin-right: @margin-normal;
-}
-
-.caosdb-fs-file:hover>.glyphicon::before {
-    content: "\e025";
-}
-
-.caosdb-fs-btn-file {
-    padding: 0px;
-    background-color: transparent;
-    border: 0px;
-}
-
-.caosdb-fs-btn-file:hover .caosdb-label-file {
-    background-color: #F96EB6;
-}
-
-.caosdb-fs-btn-file:hover .caosdb-label-id {
-    background-color: #6E8782;
-}
-
-.back-to-top {
-    cursor: pointer;
-    position: fixed;
-    bottom: 10px;
-    right: 15px;
-    display: none;
-    z-index: 1050;
-}
-
-.caosdb-logo {
-    margin-right: @margin-normal;
-}
-
-.caosdb-comment-action-item {
-    padding-left: 4px;
-    padding-right: 4px;
-    border-left: 1px solid #7c7c7c;
-}
-
-.caosdb-comment-action>.caosdb-comment-action-item:first-child {
-    border-left: 0px solid #7c7c7c;
-}
-
-.caosdb-pagination {
-    margin: 5px 15px;
-}
-
-.caosdb-pagination-navbar {
-    padding-bottom: 5px;
-    position: fixed;
-    bottom: 0px;
-    width: 100%;
-    border: 0px;
-    border-top: @border1;
-    z-index: 1000;
-    position: fixed;
-}
-
-.caosdb-heading {
-    color: #5e5e5e;
-    background-color: #f8f8f8;
-    border-bottom: @border1;
-}
-
-.caosdb-heading>.container {
-    padding: 20px 0px;
-}
-
-.spinning {
-    animation: spin 2s linear infinite;
-}
-
-@keyframes spin {
-    0% { transform: rotate(0deg); }
-    100% { transform: rotate(360deg); }
-}
-
-.flipped-horiz-icon {
-    transform: scaleX(-1);
-}
-
-.spacer {
-    margin-left: 12px;
-}
-
-input[type="file"] {
-    display: none;
-}
diff --git a/src/core/js/annotation.js b/src/core/js/annotation.js
index dd75a81180d2081fd992d168ff3e1b5f43492f06..42b4217cf2a7c2cd1214d0384ba09a23e2cddaf3 100644
--- a/src/core/js/annotation.js
+++ b/src/core/js/annotation.js
@@ -434,7 +434,7 @@ this.annotation = new function() {
 };
 
 $(document).ready(function() {
-    //if ("${BUILD_MODULE_EXT_ANNOTATION}" == "ENABLED") {
+    if ("${BUILD_MODULE_EXT_ANNOTATION}" == "ENABLED") {
         caosdb_modules.register(annotation);
-    //}
+    }
 });
diff --git a/src/core/js/caosdb.js b/src/core/js/caosdb.js
index 0f7e631a04eabf9db59c2df2c19f2c82cd176b01..1637426d61c0e7d12748f9cf78c85e064de917d0 100644
--- a/src/core/js/caosdb.js
+++ b/src/core/js/caosdb.js
@@ -826,10 +826,7 @@ function setPropertySafe(valueelement, property, propold) {
             preview.init();
         }
     } else {
-        /* DEPRECATED css class .caosdb-property-text-value - Use
-         * .caosdb-f-property-single-raw-value or introduce new
-         * .caosdb-v-property-text-value */
-        valueelement.innerHTML = "<span class='caosdb-property-text-value'>" + property.value + "</span>";
+        valueelement.innerHTML = "<span class='caosdb-f-property-single-raw-value caosdb-f-property-text-value caosdb-v-property-text-value'>" + property.value + "</span>";
     }
 }
 
@@ -1160,22 +1157,6 @@ async function retrieve_dragged_property(id) {
     return transformation.transformProperty(entities);
 }
 
-/**
- * Retrieve all properties and record types and convert them into
- * a web page using a specialized XSLT named entity palette.
- * @return An array of entities.
- */
-async function retrieve_data_model() {
-    // TODO possibly allow single query
-    let props = await connection.get("Entity/?query=FIND Property");
-    let rts = await connection.get("Entity/?query=FIND RecordType");
-    for (var p of props.children[0].children) {
-        rts.children[0].appendChild(p.cloneNode());
-    }
-    return transformation.transformEntityPalette(rts);
-}
-
-
 /**
  * Update an entity using its xml representation.
  * @param xml The xml of the entity which will be automatically wrapped with an Update.
diff --git a/src/core/js/edit_mode.js b/src/core/js/edit_mode.js
index 8644fff20894cc775919c8dfe76f49cec96460fb..85645aabaf7d05cb7abe928b4792b5de6ef9e352 100644
--- a/src/core/js/edit_mode.js
+++ b/src/core/js/edit_mode.js
@@ -5,6 +5,8 @@
  * Copyright (C) 2018 Research Group Biomedical Physics,
  * Max-Planck-Institute for Dynamics and Self-Organization Göttingen
  * Copyright (C) 2019 Henrik tom Wörden
+ * Copyright (C) 2019-2021 IndiScale GmbH <info@indiscale.com>
+ * Copyright (C) 2019-2021 Timm Fitschen <t.fitschen@indiscale.com>
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU Affero General Public License as
@@ -27,7 +29,7 @@
 /**
  * Edit mode module
  */
-var edit_mode = new function() {
+var edit_mode = new function () {
 
     var logger = log.getLogger("edit_mode");
 
@@ -66,7 +68,7 @@ var edit_mode = new function() {
     /**
      * Initialize this module
      */
-    this.init = function() {
+    this.init = function () {
         if (isAuthenticated()) {
             this._init();
         } else {
@@ -89,15 +91,15 @@ var edit_mode = new function() {
     }
 
 
-    this.dragstart = function(e) {
+    this.dragstart = function (e) {
         e.dataTransfer.setData("text/plain", e.target.id);
     }
 
-    this.dragleave = function(e) {
+    this.dragleave = function (e) {
         edit_mode.unhighlight();
     }
 
-    this.dragover = function(e) {
+    this.dragover = function (e) {
         e.preventDefault();
         e.dataTransfer.dropEffect = "copy";
         edit_mode.highlight(this);
@@ -110,7 +112,7 @@ var edit_mode = new function() {
      * @param new_prop
      * @param make_property_editable_cb
      */
-    this.add_new_property = function(entity, new_prop, make_property_editable_cb = edit_mode.make_property_editable) {
+    this.add_new_property = function (entity, new_prop, make_property_editable_cb = edit_mode.make_property_editable) {
         if (typeof entity === "undefined" || !(entity instanceof HTMLElement)) {
             throw new TypeError("entity must instantiate HTMLElement");
         }
@@ -130,7 +132,7 @@ var edit_mode = new function() {
      * @param {Event} e - the drop event.
      * @param {HTMLElement} entity - the entity.
      */
-    this.add_dropped_property = function(e, entity) {
+    this.add_dropped_property = function (e, entity) {
         var propsrcid = e.dataTransfer.getData("text/plain");
         var tmp_id = propsrcid.split("-");
         var prop_id = tmp_id[tmp_id.length - 1];
@@ -155,7 +157,7 @@ var edit_mode = new function() {
      * @param {Event} e - the drop event.
      * @param {HTMLElement} entity - the entity.
      */
-    this.add_dropped_parent = function(e, entity) {
+    this.add_dropped_parent = function (e, entity) {
         var propsrcid = e.dataTransfer.getData("text/plain");
         var parent_list = entity.getElementsByClassName("caosdb-f-parent-list")[0]
         var tmp_id = propsrcid.split("-");
@@ -174,22 +176,22 @@ var edit_mode = new function() {
         }
     }
 
-    this.property_drop_listener = function(e) {
+    this.property_drop_listener = function (e) {
         edit_mode._drop_listener.call(this, e, edit_mode.add_dropped_property);
     }
 
-    this.parent_drop_listener = function(e) {
+    this.parent_drop_listener = function (e) {
         edit_mode._drop_listener.call(this, e, edit_mode.add_dropped_parent);
     }
 
-    this._drop_listener = function(e, add_cb) {
+    this._drop_listener = function (e, add_cb) {
         e.preventDefault();
         edit_mode.unhighlight();
 
         const app = edit_mode.app;
         const state = app.state;
 
-        if(state === "initial"){
+        if (state === "initial") {
             var entity = $(this).parent();
             app.startEdit(entity[0]);
         } else if (state !== "changed") {
@@ -202,7 +204,7 @@ var edit_mode = new function() {
 
     // Dropping a RecordType in the heading will add it as a parent.
     // This is done by this function. 
-    this.parent_drop = function(e) {
+    this.parent_drop = function (e) {
         logger.assert(edit_mode.app.state === "changed", "state should be changed. Current state: ", edit_mode.app.state, edit_mode.app, e);
         e.preventDefault();
         edit_mode.unhighlight();
@@ -210,7 +212,7 @@ var edit_mode = new function() {
     }
 
 
-    this.set_entity_dropable = function(entity, dragover, dragleave, parent_drop, property_drop) {
+    this.set_entity_dropable = function (entity, dragover, dragleave, parent_drop, property_drop) {
         if (getEntityRole(entity) === "Property") {
             // currently no parents and subproperties for properties.
             return;
@@ -230,7 +232,7 @@ var edit_mode = new function() {
 
     }
 
-    this.unset_entity_dropable = function(entity, dragover, dragleave, parent_drop, property_drop) {
+    this.unset_entity_dropable = function (entity, dragover, dragleave, parent_drop, property_drop) {
         var rts = entity.getElementsByClassName("caosdb-entity-panel-body");
         for (var rel of rts) {
             rel.removeEventListener("dragleave", dragleave);
@@ -245,11 +247,11 @@ var edit_mode = new function() {
         }
     }
 
-    this.remove_save_button = function(ent) {
+    this.remove_save_button = function (ent) {
         $(ent).find('.caosdb-f-entity-save-button').remove();
     }
 
-    this.add_save_button = function(ent, callback) {
+    this.add_save_button = function (ent, callback) {
         var save_btn = $('<button class="btn btn-link caosdb-update-entity-button caosdb-f-entity-save-button">Save</button>');
 
         $(ent).find(".caosdb-f-edit-mode-entity-actions-panel").append(save_btn);
@@ -284,7 +286,7 @@ var edit_mode = new function() {
      * last trash button for the last parent if the header belongs to a
      * record).
      */
-    this.add_parent_delete_buttons = function(header) {
+    this.add_parent_delete_buttons = function (header) {
         $(header).find(".caosdb-f-parent-trash-button").remove();
         var parents = $(header).find(".caosdb-parent-item");
         if ((parents.length > 1) || getEntityRole(header) != "Record") {
@@ -300,7 +302,7 @@ var edit_mode = new function() {
      * Append a trash button with class "caosdb-f-parent-trash-button", bind a
      * remove on the deletable, and bind a callback function to click.
      */
-    this.add_parent_trash_button = function(appendable, deletable, callback = undefined) {
+    this.add_parent_trash_button = function (appendable, deletable, callback = undefined) {
         edit_mode.add_trash_button(appendable, deletable, "caosdb-f-parent-trash-button", callback);
     }
 
@@ -308,7 +310,7 @@ var edit_mode = new function() {
      * Append a trash button with class "caosdb-f-property-trash-button" and
      * bind a remove on the deletable.
      */
-    this.add_property_trash_button = function(appendable, deletable) {
+    this.add_property_trash_button = function (appendable, deletable) {
         edit_mode.add_trash_button(appendable, deletable, "caosdb-f-property-trash-button", undefined, "Remove this property");
     }
 
@@ -324,9 +326,9 @@ var edit_mode = new function() {
      * @param {string} [title] - optional title for the button element.
      * @return {undefined}
      */
-    this.add_trash_button = function(appendable, deletable, className, callback = undefined, title = undefined) {
+    this.add_trash_button = function (appendable, deletable, className, callback = undefined, title = undefined) {
         var button = $('<button class="btn btn-link p-0 ' + className + ' caosdb-f-entity-trash-button"><i class="bi-trash"></i></button>');
-        if(title) {
+        if (title) {
             button.attr("title", title);
         }
         $(appendable).append(button);
@@ -371,7 +373,11 @@ var edit_mode = new function() {
             atomic = "REFERENCE";
         }
 
-        return { "atomic_datatype": atomic, "reference_scope": ref, "is_list": is_list};
+        return {
+            "atomic_datatype": atomic,
+            "reference_scope": ref,
+            "is_list": is_list
+        };
     }
 
     /**
@@ -412,10 +418,10 @@ var edit_mode = new function() {
      *        of entities in HTML representation.
      * @returns {XMLDocument} the unprocessed server response.
      */
-    this.insert_entity = async function(ent_elements) {
+    this.insert_entity = async function (ent_elements) {
         ent_elements = caosdb_utils.assert_array(ent_elements, "param `ent_elements`", true);
         var xmls = [];
-        for ( const ent_element of ent_elements ) {
+        for (const ent_element of ent_elements) {
             xmls.push(edit_mode.form_to_xml(ent_element));
         }
         return await insert(xmls);
@@ -428,7 +434,7 @@ var edit_mode = new function() {
      * @returns {Document|DocumentFragment} - An xml document containing the
      *     entity in XML representation.
      */
-    this.form_to_xml = function(entity_form) {
+    this.form_to_xml = function (entity_form) {
         const obj = form_elements.form_to_object($(entity_form).find("form")[0])[0];
         var entityRole = getEntityRole(entity_form);
         var file_path = undefined;
@@ -459,11 +465,11 @@ var edit_mode = new function() {
     /**
      * TODO merge with getPropertyFromElement in caosdb.js
      */
-    this.getPropertyFromElement = function(element) {
+    this.getPropertyFromElement = function (element) {
         var editfield = $(element).find(".caosdb-f-property-value");
         var property = getPropertyFromElement(element);
 
-        var _parse_single_datetime = function(field) {
+        var _parse_single_datetime = function (field) {
             let time = $(field).find(":input[type='time']").val()
             let date = $(field).find(":input[type='date']").val();
             if (time) {
@@ -476,7 +482,7 @@ var edit_mode = new function() {
         // LISTs need to be handled here
         if (property.list == true) {
             property.value = [];
-            if (["TEXT","DOUBLE","INTEGER"].includes(property.listDatatype)) {
+            if (["TEXT", "DOUBLE", "INTEGER"].includes(property.listDatatype)) {
                 // LOOP over elements of editfield.find(":input")
                 for (var singleelement of $(editfield).find(":not(.caosdb-f-list-item-button):input:not(.caosdb-unit)")) {
                     property.value.push($(singleelement).val());
@@ -494,7 +500,7 @@ var edit_mode = new function() {
                 throw ("This property's data type is not supported by the webui. Please issue a feature request for support for `" + property.datatype + "`.");
             }
         } else {
-            if (["TEXT","DOUBLE","INTEGER"].includes(property.datatype)) {
+            if (["TEXT", "DOUBLE", "INTEGER"].includes(property.datatype)) {
                 property.value = editfield.find(":input:not(.caosdb-unit)").val();
             } else if (property.datatype == "DATETIME") {
                 property.value = _parse_single_datetime(editfield);
@@ -516,7 +522,7 @@ var edit_mode = new function() {
      *
      * ent_element : {HTMLElement} entity in view mode
      */
-    this.getProperties = function(ent_element) {
+    this.getProperties = function (ent_element) {
         const properties = [];
         if (ent_element) {
             const prop_elements = getPropertyElements(ent_element);
@@ -530,14 +536,14 @@ var edit_mode = new function() {
 
     }
 
-    this.update_entity = async function(ent_element) {
+    this.update_entity = async function (ent_element) {
         var xml = edit_mode.form_to_xml(ent_element);
         return await edit_mode.update(xml);
     }
 
     this.update = update;
 
-    this.add_edit_mode_button = function(target, toggle_function) {
+    this.add_edit_mode_button = function (target, toggle_function) {
         var edit_mode_li = $('<li class="nav-item"><a class="nav-link caosdb-f-btn-toggle-edit-mode" role="button">Edit Mode</a></li>');
         $(target).append(edit_mode_li);
         $(".caosdb-f-btn-toggle-edit-mode").click(toggle_function);
@@ -548,7 +554,7 @@ var edit_mode = new function() {
         return edit_mode_li[0];
     }
 
-    this.toggle_edit_mode = async function() {
+    this.toggle_edit_mode = async function () {
         edit_mode.toggle_edit_panel();
         if (edit_mode.is_edit_mode()) {
             await edit_mode.leave_edit_mode();
@@ -561,21 +567,20 @@ var edit_mode = new function() {
      * To be overridden by an instance of `leave_edit_mode_template` during the
      * `enter_edit_mode()` execution.
      */
-    this.leave_edit_mode = function() {}
+    this.leave_edit_mode = function () {}
 
 
-    this.enter_edit_mode = async function(editApp = undefined) {
+    /**
+     * Initializes the edit mode and loads the tool box.
+     */
+    this.enter_edit_mode = async function (editApp = undefined) {
         window.localStorage.edit_mode = "true";
 
-        var editPanel = edit_mode.get_edit_panel();
-        removeAllWaitingNotifications(editPanel);
-        this.add_wait_datamodel_info();
-
         try {
-            const model = await edit_mode.retrieve_data_model();
             $(".caosdb-f-btn-toggle-edit-mode").text("Leave Edit Mode");
 
-            edit_mode.init_tool_box(model);
+            edit_mode.init_tool_box();
+            edit_mode.init_dragable();
 
             var nextEditApp = editApp;
             if (typeof nextEditApp == "undefined") {
@@ -583,7 +588,7 @@ var edit_mode = new function() {
             }
 
             edit_mode.init_new_buttons(nextEditApp);
-            edit_mode.leave_edit_mode = function() {
+            edit_mode.leave_edit_mode = function () {
                 edit_mode.leave_edit_mode_template(nextEditApp);
             };
 
@@ -593,18 +598,57 @@ var edit_mode = new function() {
         }
     }
 
+    /**
+     * (Re-)load the toolbox, i.e. retrieve Properties and RecordTypes
+     */
+    this.load_edit_mode_toolbox = async function () {}
+
+    /**
+     * Retrieve all properties and record types and convert them into
+     * a web page using a specialized XSLT named entity palette.
+     *
+     * @return {Promise<XMLDocument>} An array of entities in xml representation.
+     */
+    this.retrieve_data_model = async function () {
+        const props_prom = connection.get("Entity/?query=FIND Property");
+        const rts_prom = connection.get("Entity/?query=FIND RecordType");
+
+        const [props, rts] = await Promise.all([props_prom, rts_prom]);
+
+        // add all properties to rts
+        for (var p of props.children[0].children) {
+            rts.documentElement.appendChild(p.cloneNode());
+        }
+        return rts;
+    }
 
-    this.retrieve_data_model = retrieve_data_model;
+    /**
+     * @param {Promise<XMLElement[]>} xml - an array of XMLElements.
+     * @return {Promise<HTMLDocument>} an HTMLElements.
+     */
+    this.transform_entity_palette = async function _tME(xml) {
+        var xsl = transformation.retrieveXsltScript("entity_palette.xsl");
+        let html = await asyncXslt(xml, xsl);
+        return html;
+    }
 
 
-    this.init_tool_box = function(model) {
 
-        var editPanel = edit_mode.get_edit_panel();
-        removeAllWaitingNotifications(editPanel);
+    /**
+     * Remove all warnings and set the HTML of the toolbox panel to
+     * the model given by model.
+     */
+    this.init_tool_box = async function () {
+        // remove previously added model
+        $(".caosdb-f-edit-mode-existing").remove()
 
-        editPanel.innerHTML = xml2str(model);
-        edit_mode.init_dragable();
+        const editPanel = $(edit_mode.get_edit_panel());
+        removeAllWaitingNotifications(editPanel[0]);
+        editPanel.append(createWaitingNotification("Please wait."));
+        const model = await edit_mode.transform_entity_palette(edit_mode.retrieve_data_model());
 
+        removeAllWaitingNotifications(editPanel[0]);
+        editPanel.children()[0].appendChild(model);
     }
 
 
@@ -615,9 +659,9 @@ var edit_mode = new function() {
      *
      * @param {HTMLElement} entity
      */
-    this.init_actions_panels = function(entity) {
+    this.init_actions_panels = function (entity) {
         this.reset_actions_panels([entity]);
-        $(entity).find(".caosdb-entity-actions-panel").each(function(index) {
+        $(entity).find(".caosdb-entity-actions-panel").each(function (index) {
             var clone = $(this).clone(true)[0];
             $(clone).removeClass("caosdb-entity-actions-panel").addClass("caosdb-f-edit-mode-entity-actions-panel").insertBefore(this);
             $(clone).children().remove();
@@ -630,26 +674,26 @@ var edit_mode = new function() {
      *
      * @param {HTMLElements[]} array of entities in HTML representation.
      */
-    this.reset_actions_panels = function(entities) {
+    this.reset_actions_panels = function (entities) {
         $(entities).find(".caosdb-f-edit-mode-entity-actions-panel").remove();
         $(entities).find(".caosdb-entity-actions-panel").show();
     }
 
-    this.make_header_editable = function(entity) {
+    this.make_header_editable = function (entity) {
         var header = $(entity).find('.caosdb-entity-panel-heading');
         var roleElem = $(header).find('.caosdb-f-entity-role');
         roleElem.detach();
         var parentsElem = $(header).find('.caosdb-f-parent-list');
         parentsElem.detach();
         const parentsSection = $(`
-        <div class="row">
-          <div class="col-2 text-end">
-            <label class="col-form-label">parents</label>
-          </div>
-          <div class="col caosdb-f-parents-form-element">
-          </div>
+      <div class="row">
+        <div class="col-2 text-end">
+          <label class="col-form-label">parents</label>
+        </div>
+        <div class="col caosdb-f-parents-form-element">
         </div>
-        `);
+      </div>
+      `);
         parentsSection.find("div.caosdb-f-parents-form-element").append(parentsElem);
 
         header.attr("title", "Drop parents from the right panel here.");
@@ -687,11 +731,11 @@ var edit_mode = new function() {
         edit_mode.add_parent_delete_buttons(header[0]);
     }
 
-    this.isListDatatype = function(datatype) {
+    this.isListDatatype = function (datatype) {
         return (typeof datatype !== 'undefined' && datatype.substring(0, 5) == "LIST<");
     }
 
-    this.unListDatatype = function(datatype) {
+    this.unListDatatype = function (datatype) {
         return datatype.substring(5, datatype.length - 1);
     }
 
@@ -710,7 +754,7 @@ var edit_mode = new function() {
      *
      * @param {HTMLElement} form - The form containing the input fields.
      */
-    this.make_datatype_input_logic = function(form) {
+    this.make_datatype_input_logic = function (form) {
         const datatype = form_elements.get_fields(form, "atomic_datatype");
 
         $(datatype).find("select").change(function () {
@@ -754,13 +798,13 @@ var edit_mode = new function() {
      * @param {string} unit - the initial value of the input element.
      * @returns {HTMLElement} - a labeled form field.
      */
-    this.make_unit_input = function(unit) {
+    this.make_unit_input = function (unit) {
         const unit_input = $(form_elements
-                .make_text_input({
-                    name: "unit",
-                    label: "unit",
-                    value: unit,
-                }));
+            .make_text_input({
+                name: "unit",
+                label: "unit",
+                value: unit,
+            }));
         unit_input.toggleClass("form-control", true);
         unit_input.find(".col-sm-3").toggleClass("col-sm-2", true).toggleClass("col-sm-3", false);
         unit_input.find(".col-sm-9").toggleClass("col-sm-2", true).toggleClass("col-sm-9", false);
@@ -768,14 +812,14 @@ var edit_mode = new function() {
     }
 
     this._known_atomic_datatypes = [
-            "TEXT",
-            "DOUBLE",
-            "INTEGER",
-            "DATETIME",
-            "BOOLEAN",
-            "FILE",
-            "REFERENCE",
-        ];
+        "TEXT",
+        "DOUBLE",
+        "INTEGER",
+        "DATETIME",
+        "BOOLEAN",
+        "FILE",
+        "REFERENCE",
+    ];
 
     /**
      * Make three input elements which contain all necessary parts of a datatype.
@@ -785,7 +829,7 @@ var edit_mode = new function() {
      * @param {string} [datatype] - defaults to TEXT if undefined.
      * @returns {HTMLElement}
      */
-    this.make_datatype_input = function(datatype) {
+    this.make_datatype_input = function (datatype) {
         var _datatype = datatype || "TEXT";
 
         // split/convert datatype string into more practical variables.
@@ -822,7 +866,7 @@ var edit_mode = new function() {
             make_value: getEntityName,
         };
         const ref_selector = form_elements
-                .make_reference_drop_down(reference_config);
+            .make_reference_drop_down(reference_config);
 
 
         // generate the checkbox ([ ] list)
@@ -832,11 +876,11 @@ var edit_mode = new function() {
             checked: is_list,
         }
         const list_checkbox = form_elements
-                .make_checkbox_input(list_checkbox_config);
+            .make_checkbox_input(list_checkbox_config);
 
         // styling
         //$(list_checkbox).children().toggleClass("col-sm-3",false).toggleClass("col-sm-9", false).toggleClass("col-sm-1", true);
-        $(list_checkbox).find(".caosdb-f-property-value").toggleClass("my-auto",true)
+        $(list_checkbox).find(".caosdb-f-property-value").toggleClass("my-auto", true)
         const form_group = $('<div class="">').append([datatype_selector, ref_selector, list_checkbox]);
         form_group.find(".col-sm-3").toggleClass("text-end", true).toggleClass("col-sm-2", true).toggleClass("col-sm-3", false);
         form_group.find(".col-sm-9").toggleClass("col-sm-3", true).toggleClass("col-sm-9", false);
@@ -845,11 +889,11 @@ var edit_mode = new function() {
         return form_group[0];
     }
 
-    this.make_input = function(label, value) {
-        return $('<div class="row"> <div class="col-2 text-end"> <label class="col-form-label">' + label +' </label> </div> <div class="col caosdb-f-parents-form-element"> <input type="text" class="form-control caosdb-f-entity-' + label + '" value="' + (typeof value == 'undefined' ? "" : value) + '"></input> </div> </div>')[0];
+    this.make_input = function (label, value) {
+        return $('<div class="row"> <div class="col-2 text-end"> <label class="col-form-label">' + label + ' </label> </div> <div class="col caosdb-f-parents-form-element"> <input type="text" class="form-control caosdb-f-entity-' + label + '" value="' + (typeof value == 'undefined' ? "" : value) + '"></input> </div> </div>')[0];
     }
 
-    this.smooth_replace = function(from, to) {
+    this.smooth_replace = function (from, to) {
         $(to).hide();
         $(from).fadeOut();
         $(from).after(to);
@@ -858,22 +902,22 @@ var edit_mode = new function() {
     }
 
     /**
-      * Create an input element for a single property's value.
-      *
-      * @param {object} property - entity property object.
-      * @param {HTMLElements[]} [options] - an array of OPTION elements
-      *     which represent possible candidates for reference values. The
-      *     options will be appended to the SELECT input. This parameter is
-      *     optional and only used for reference properties. This parameter
-      *     might as well be a Promise for such an array.
-      */
-    this.createElementForProperty = function(property, options) {
+     * Create an input element for a single property's value.
+     *
+     * @param {object} property - entity property object.
+     * @param {HTMLElements[]} [options] - an array of OPTION elements
+     *     which represent possible candidates for reference values. The
+     *     options will be appended to the SELECT input. This parameter is
+     *     optional and only used for reference properties. This parameter
+     *     might as well be a Promise for such an array.
+     */
+    this.createElementForProperty = function (property, options) {
         var result;
         if (property.datatype == "TEXT") {
             result = `<textarea>${property.value || ""}</textarea>`;
         } else if (property.datatype == "DATETIME") {
             var dateandtime = [""];
-            if(property.value) {
+            if (property.value) {
                 dateandtime = caosdb2InputDate(property.value);
             }
             let date = dateandtime[0];
@@ -910,10 +954,10 @@ var edit_mode = new function() {
      * @param {object} property - a property object.
      * @returns {HTMLElement} a SPAN element.
      */
-    this.generate_list_item_control_panel = function(property, options) {
+    this.generate_list_item_control_panel = function (property, options) {
         // Add list delete buttons:
         var deleteButton = $('<button title="Delete this list element." class="btn btn-link caosdb-update-entity-button caosdb-f-list-item-button"><i class="bi-trash"></i></button>');
-        $(deleteButton).click(function() {
+        $(deleteButton).click(function () {
             var ol = this.parentElement.parentElement.parentElement;
 
             $(this.parentElement.parentElement).remove();
@@ -923,7 +967,7 @@ var edit_mode = new function() {
 
         // Add list insert buttons:
         var insertButton = $('<button title="Insert a new list element before this element." class="btn btn-link caosdb-update-entity-button caosdb-f-list-item-button"><i class="bi-plus"></i></button>');
-        $(insertButton).click(function() {
+        $(insertButton).click(function () {
             // TODO MERGE WITH OTHER PLACES WHERE THIS STRING APPEARS
             var proptemp = {
                 list: false,
@@ -952,7 +996,7 @@ var edit_mode = new function() {
     /**
      *
      */
-    this.create_unit_field = function(unit) { 
+    this.create_unit_field = function (unit) {
         return $(`<input class='caosdb-unit' title='unit' style='width: 60px;' placeholder='unit' value='${unit||""}' type='text'></input>`)[0];
     }
 
@@ -967,10 +1011,10 @@ var edit_mode = new function() {
      * @param {object} property - a property object.
      * @return {HTMLElement[]}
      */
-    this.create_value_inputs = function(property) {
+    this.create_value_inputs = function (property) {
         logger.trace("enter create_value_inputs", arguments);
 
-        var result = [ ];
+        var result = [];
         if (!property.list) {
             var options = property.reference ? edit_mode.retrieve_datatype_list(property.datatype) : undefined;
             result.push(edit_mode.createElementForProperty(property, options));
@@ -986,7 +1030,7 @@ var edit_mode = new function() {
                 result.push(edit_mode.create_unit_field(property.unit));
             }
 
-            for (var i=0; i<property.value.length; i++) {
+            for (var i = 0; i < property.value.length; i++) {
                 // TODO MERGE WITH OTHER PLACES WHERE THIS STRING APPEARS
                 var proptemp = {
                     list: false,
@@ -1005,7 +1049,7 @@ var edit_mode = new function() {
 
             // PLUS-button for appending inputs to the list.
             var insertButton = $('<button title="Append a new field at the end." class="btn btn-link caosdb-update-entity-button caosdb-f-list-item-button"><i class="bi-plus"></i></button>');
-            $(insertButton).click(function() {
+            $(insertButton).click(function () {
                 // TODO MERGE WITH OTHER PLACES WHERE THIS STRING APPEARS
                 var proptemp = {
                     list: false,
@@ -1027,7 +1071,8 @@ var edit_mode = new function() {
 
             result = result.concat([inputs[0],
                 $("<span>Insert element at the end of the list: </span>")
-                    .append(insertButton)[0]]);
+                .append(insertButton)[0]
+            ]);
         }
         return result;
 
@@ -1053,11 +1098,11 @@ var edit_mode = new function() {
      *
      * @see {@link _toggle_list_property} which is the most prominent callee.
      */
-    this._toggle_list_property_object = function(element, toList) {
+    this._toggle_list_property_object = function (element, toList) {
         const property = edit_mode.getPropertyFromElement(element);
 
         // property.list XAND toList
-        if(property.list ? toList : !toList) {
+        if (property.list ? toList : !toList) {
             // already in desired state.
             return undefined;
         }
@@ -1070,7 +1115,7 @@ var edit_mode = new function() {
         if (!toList && $.isArray(property.value) && property.value.length < 2) {
             property.value = property.value[0] || "";
         } else if (toList && !$.isArray(property.value)) {
-            property.value = [ property.value ]
+            property.value = [property.value]
         } else {
             throw new Error(`Could not toggle to list=${toList} with value=${property.value}.`);
         }
@@ -1090,12 +1135,12 @@ var edit_mode = new function() {
      * @see {@link _toggle_list_property_object} which does the actual
      * conversion work.
      */
-    this._toggle_list_property = function(element, toList) {
+    this._toggle_list_property = function (element, toList) {
         logger.trace("enter _toggle_list_property", arguments);
 
         let property = edit_mode._toggle_list_property_object(element, toList);
 
-        if(!property) {
+        if (!property) {
             // already in desired state.
             return;
         }
@@ -1114,7 +1159,7 @@ var edit_mode = new function() {
     }
 
 
-    this.change_property_data_type = function(element, datatype) {
+    this.change_property_data_type = function (element, datatype) {
         $(element).find(".caosdb-property-datatype").text(datatype);
         element.dispatchEvent(edit_mode.property_data_type_changed);
     }
@@ -1146,7 +1191,7 @@ var edit_mode = new function() {
         // disable checkbox when list has more than 1 element
         let disabled = editfield.find("li").length > 1;
         checkbox.prop("disabled", disabled);
-        const listDatatype = list ? datatype.substring(5, datatype.length-1) : datatype;
+        const listDatatype = list ? datatype.substring(5, datatype.length - 1) : datatype;
         if (disabled) {
             checkbox.attr("title", `You must remove all elements of the list but one by clicking the trash buttons (to the left) before you can toggle the LIST<${listDatatype}>`);
         } else {
@@ -1157,23 +1202,23 @@ var edit_mode = new function() {
         // list has more than one element.
         editfield[0].addEventListener(
             edit_mode.list_value_input_added.type, (e) => {
-            let disabled = editfield.find("li").length > 1;
-            checkbox.prop("disabled", disabled);
-            if (disabled) {
-                checkbox.attr("title", `You must remove all elements of the list but one by clicking the trash buttons (to the left) before you can toggle the LIST<${listDatatype}>`);
-            }
-        }, true);
+                let disabled = editfield.find("li").length > 1;
+                checkbox.prop("disabled", disabled);
+                if (disabled) {
+                    checkbox.attr("title", `You must remove all elements of the list but one by clicking the trash buttons (to the left) before you can toggle the LIST<${listDatatype}>`);
+                }
+            }, true);
 
         // enable the checkbox when elements removed to the list and the number
         // of elements is smaller than 2.
         editfield[0].addEventListener(
             edit_mode.list_value_input_removed.type, (e) => {
-            let disabled = editfield.find("li").length > 1;
-            checkbox.prop("disabled", disabled);
-            if (!disabled) {
-                checkbox.attr("title", `Toggle LIST<${listDatatype}> data type of this property.`);
-            }
-        }, true);
+                let disabled = editfield.find("li").length > 1;
+                checkbox.prop("disabled", disabled);
+                if (!disabled) {
+                    checkbox.attr("title", `Toggle LIST<${listDatatype}> data type of this property.`);
+                }
+            }, true);
     }
 
     /**
@@ -1192,14 +1237,16 @@ var edit_mode = new function() {
      *
      * @param {HTMLElement} element - entity property in HTML representation.
      */
-    this.make_property_editable = function(element) {
+    this.make_property_editable = function (element) {
         caosdb_utils.assert_html_element(element, "param 'element'");
 
         var editfield = $(element).find(".caosdb-f-property-value")
-            .removeClass("col-sm-8")
-            .addClass("col-sm-6")
+            .removeClass("col-sm-6")
+            .removeClass("col-md-8")
+            .addClass("col-sm-4")
+            .addClass("col-md-6")
             .addClass("caosdb-v-property-value-inputs")
-            .after(`<div class="col-sm-2 caosdb-v-property-other-inputs caosdb-property-edit" style="text-align: right;"/>`);
+            .after(`<div class="col-sm-2 col-sm-2 caosdb-v-property-other-inputs caosdb-property-edit" style="text-align: right;"/>`);
         var property = getPropertyFromElement(element);
 
 
@@ -1212,10 +1259,17 @@ var edit_mode = new function() {
         edit_mode.add_toggle_list_checkbox(element, property.list, property.datatype);
 
         // TRASH BUTTON
-        edit_mode.add_property_trash_button($(element).find(".caosdb-property-edit")[0],element);
+        edit_mode.add_property_trash_button($(element).find(".caosdb-property-edit")[0], element);
     }
 
-    this.create_new_record = async function(recordtype_id, name = undefined) {
+
+    /**
+     * Create a new Record using an existing RecordType.
+     * The RecordType can be specified using recordtype_id.
+     *
+     * Currently name is ignored. TODO: check whether that behavior is intended.
+     */
+    this.create_new_record = async function (recordtype_id, name = undefined) {
         var rt = await retrieve(recordtype_id);
         var newrecord = createEntityXML("Record", undefined, undefined,
             getProperties(rt[0]),
@@ -1229,20 +1283,26 @@ var edit_mode = new function() {
         return x[0];
     }
 
-
-    this.init_dragable = function() {
-        var props = document.getElementsByClassName("caosdb-f-edit-drag");
-        for (var pel of props) {
-            pel.addEventListener("dragstart", edit_mode.dragstart);
-            pel.setAttribute("draggable", true);
-        }
+    /**
+     * All draggable elements (that are those which have a css class
+     * caosdb-f-edit-drag are added the correct drag listener and the attribute draggable.
+     *
+     * This is now done using bubbling so that the listeners are also dynamically updated.
+     */
+    this.init_dragable = function () {
+        // Bubbling: Add the listener to the document and check whether the class is present.
+        document.addEventListener("dragstart", function (event) {
+            if (event.target.classList.contains("caosdb-f-edit-drag")) {
+                edit_mode.dragstart(event);
+            }
+        });
     }
 
-    /*
+    /**
      * This function treats the deletion of entities, i.e. when the "delete"
      * button is clicked.
      */
-    this.delete_action = async function(entity) {
+    this.delete_action = async function (entity) {
         var app = edit_mode.app;
 
         // show waiting notification
@@ -1286,33 +1346,19 @@ var edit_mode = new function() {
     }
 
 
-    this.disable_new_buttons = function() {
+    this.disable_new_buttons = function () {
         var new_buttons = $('.caosdb-f-edit-panel-new-button');
         new_buttons.attr("disabled", true);
     }
 
-    this.enable_new_buttons = function() {
+    this.enable_new_buttons = function () {
         var new_buttons = $('.caosdb-f-edit-panel-new-button');
         new_buttons.attr("disabled", false);
     }
 
-    this.init_new_buttons = function(app) {
+    this.init_new_buttons = function (app) {
         var new_buttons = $('.caosdb-f-edit-panel-new-button');
 
-        // Show a button "+" to create a new property when filter results in empty list.
-        new_buttons.filter('.caosdb-f-hide-on-empty-input').parent().each(function(index) {
-            var button = $(this);
-            button.hide();
-            var input = button.parent().find("input");
-            input.on("input", function(e) {
-                if (input.val() == '') {
-                    button.fadeOut();
-                } else {
-                    button.fadeIn();
-                }
-            });
-        });
-
         // handler for new property button
         // calls newEntity transition of state machine
         new_buttons.filter('.new-property').click(() => {
@@ -1363,7 +1409,7 @@ var edit_mode = new function() {
      *
      * `finish` is triggered automatically when leaving the edit mode and does a little cleanup.
      */
-    this.init_edit_app = function() {
+    this.init_edit_app = function () {
 
 
         var app = new StateMachine({
@@ -1403,9 +1449,9 @@ var edit_mode = new function() {
         });
 
 
-        var init_drag_n_drop = function() {
+        var init_drag_n_drop = function () {
             const state = app.state;
-            $('.caosdb-entity-panel').each(function(index) {
+            $('.caosdb-entity-panel').each(function (index) {
                 let entity = this;
                 if ($(entity).is("[data-version-successor]")) {
                     // not the latest version -> ignore
@@ -1428,24 +1474,24 @@ var edit_mode = new function() {
         }
 
         // Define the error handler for the state machine.
-        app.errorHandler = function(fn) {
+        app.errorHandler = function (fn) {
             try {
                 fn();
             } catch (e) {
                 edit_mode.handle_error(e);
             }
         };
-        app.onAfterTransition = function(e) {
+        app.onAfterTransition = function (e) {
             init_drag_n_drop();
         }
-        app.onEnterInitial = async function(e) {
+        app.onEnterInitial = async function (e) {
             $(".caosdb-f-edit-mode-existing").toggleClass("d-none", true);
             $(".caosdb-f-edit-mode-create-buttons").toggleClass("d-none", false);
             app.old = undefined;
             app.errorHandler(() => {
                 // make entities dropable and freezable
                 edit_mode.enable_new_buttons();
-                $('.caosdb-entity-panel').each(function(index) {
+                $('.caosdb-entity-panel').each(function (index) {
                     let entity = this;
                     if ($(entity).is("[data-version-successor]")) {
                         // not the latest version -> ignore
@@ -1475,9 +1521,9 @@ var edit_mode = new function() {
                 });
             });
         };
-        app.onLeaveInitial = function(e) {
+        app.onLeaveInitial = function (e) {
             app.errorHandler(() => {
-                $('.caosdb-entity-panel').each(function(index) {
+                $('.caosdb-entity-panel').each(function (index) {
                     // add the save button an so on
                     edit_mode.remove_start_edit_button(this);
                     edit_mode.remove_new_record_button(this);
@@ -1486,7 +1532,7 @@ var edit_mode = new function() {
             });
             edit_mode.disable_new_buttons();
         };
-        app.onBeforeStartEdit = function(e, entity) {
+        app.onBeforeStartEdit = function (e, entity) {
             edit_mode.unhighlight();
             app.old = entity;
             app.entity = $(entity).clone(true)[0];
@@ -1499,11 +1545,11 @@ var edit_mode = new function() {
 
             edit_mode.freeze_but(app.entity);
         };
-        app.onBeforeCancel = function(e) {
+        app.onBeforeCancel = function (e) {
             edit_mode.smooth_replace(app.entity, app.old);
             edit_mode.unfreeze();
         };
-        app.onUpdate = function(e, entity) {
+        app.onUpdate = function (e, entity) {
             edit_mode.update_entity(entity).then(response => {
                 return transformation.transformEntities(response);
             }, edit_mode.handle_error).then(entities => {
@@ -1512,7 +1558,7 @@ var edit_mode = new function() {
                 app.showResults();
             }, edit_mode.handle_error);
         };
-        app.onEnterChanged = function(e) {
+        app.onEnterChanged = function (e) {
             // show existing entities in toolbox
             $(".caosdb-f-edit-mode-existing").toggleClass("d-none", false);
             $(".caosdb-f-edit-mode-create-buttons").toggleClass("d-none", true);
@@ -1529,18 +1575,18 @@ var edit_mode = new function() {
             for (var element of prop_elements) {
                 edit_mode.make_property_editable(element);
             }
-            if(getEntityRole(app.entity) != "Property") {
-              edit_mode.add_property_dropzone(app.entity);
+            if (getEntityRole(app.entity) != "Property") {
+                edit_mode.add_property_dropzone(app.entity);
             }
             app.entity.dispatchEvent(edit_mode.start_edit);
         }
-        app.onEnterWait = function(e) {
+        app.onEnterWait = function (e) {
             edit_mode.smooth_replace(app.entity, app.waiting);
         }
-        app.onLeaveWait = function(e) {
+        app.onLeaveWait = function (e) {
             edit_mode.smooth_replace(app.waiting, app.entity);
         }
-        app.onBeforeNewEntity = function(e, entity) {
+        app.onBeforeNewEntity = function (e, entity) {
             if (typeof entity == "undefined") {
                 throw new TypeError("entity is undefined");
             }
@@ -1568,7 +1614,7 @@ var edit_mode = new function() {
 
             app.old = $('<div/>')[0];
         }
-        app.onInsert = function(e, entity) {
+        app.onInsert = function (e, entity) {
             edit_mode.insert_entity(entity).then(response => {
                 return transformation.transformEntities(response);
             }, edit_mode.handle_error).then(entities => {
@@ -1581,7 +1627,7 @@ var edit_mode = new function() {
                 app.showResults();
             }, edit_mode.handle_error);
         }
-        app.onFinish = function(e) {
+        app.onFinish = function (e) {
             // this transition is triggerd on leaving the edit mode.
             edit_mode.unhighlight();
             if (app.old) {
@@ -1589,7 +1635,7 @@ var edit_mode = new function() {
             }
             edit_mode.unfreeze();
         }
-        app.onShowResults = function(e) {
+        app.onShowResults = function (e) {
             // this transition is triggered by the response of the server on a
             // previous insert/update request.
 
@@ -1601,6 +1647,7 @@ var edit_mode = new function() {
             app.entity.dispatchEvent(edit_mode.end_edit);
             resolve_references.init();
             preview.init();
+            edit_mode.init_tool_box();
         }
         app.waiting = createWaitingNotification("Please wait.");
         $(app.waiting).hide();
@@ -1613,12 +1660,12 @@ var edit_mode = new function() {
         return app;
     }
 
-    this.has_errors = function(entity) {
+    this.has_errors = function (entity) {
         return $(entity).find(".alert.alert-danger").length > 0;
     }
 
-    this.freeze_but = function(element) {
-        $('.caosdb-f-main-entities').children().each(function(index) {
+    this.freeze_but = function (element) {
+        $('.caosdb-f-main-entities').children().each(function (index) {
             if (element != this) {
                 edit_mode.freeze_entity(this);
             }
@@ -1637,14 +1684,14 @@ var edit_mode = new function() {
             .prepend('<div>Drag and drop RecordTypes from the Edit Mode Toolbox here.</div>');
     }
 
-    this.unfreeze = function() {
-        $('.caosdb-f-main-entities').children().each(function(index) {
+    this.unfreeze = function () {
+        $('.caosdb-f-main-entities').children().each(function (index) {
             edit_mode.unfreeze_entity(this);
         });
     }
 
 
-    this._create_reference_options = function(entities) {
+    this._create_reference_options = function (entities) {
         var results = [];
 
         for (var i = 0; i < entities.length; i++) {
@@ -1680,7 +1727,7 @@ var edit_mode = new function() {
     this.fill_reference_drop_down = async function (drop_down, options) {
         var resolved_options = await options;
         var old = $(drop_down).find("option.caosdb-f-option-default");
-        if(old.length == 0) {
+        if (old.length == 0) {
             // no option selected, append all
             $(drop_down).append($(resolved_options).clone());
             return;
@@ -1707,7 +1754,7 @@ var edit_mode = new function() {
      * @returns {HTMLElement[]} array of OPTION element, representing an entity
      *     which can be referenced by the property.
      */
-    this.retrieve_datatype_list = async function(datatype) {
+    this.retrieve_datatype_list = async function (datatype) {
         var find_entity = ["FILE", "REFERENCE"].includes(datatype) ? "" : datatype;
         var entities = datatype !== "FILE" ? await edit_mode.query(`FIND Record ${find_entity}`) : [];
         var files = await edit_mode.query(`FIND File ${find_entity}`);
@@ -1721,36 +1768,32 @@ var edit_mode = new function() {
 
     }
 
-    this.highlight = function(entity) {
+    this.highlight = function (entity) {
         $(entity).find(".caosdb-v-edit-mode-dropzone")
             .addClass("caosdb-v-edit-mode-highlight");
     }
 
-    this.unhighlight = function() {
+    this.unhighlight = function () {
         $('.caosdb-v-edit-mode-highlight')
             .removeClass("caosdb-v-edit-mode-highlight");
     }
 
-    this.handle_error = function(err) {
+    this.handle_error = function (err) {
         globalError(err);
     }
 
-    this.get_edit_panel = function() {
+    this.get_edit_panel = function () {
         return $('.caosdb-f-edit-panel-body')[0];
     }
 
-    this.add_wait_datamodel_info = function() {
-        $(this.get_edit_panel()).append(createWaitingNotification("Please wait."));
-    }
-
-    this.toggle_edit_panel = function() {
+    this.toggle_edit_panel = function () {
         //$(".caosdb-f-main").toggleClass("container-fluid").toggleClass("container");
         $(".caosdb-f-main-entities").toggleClass("caosdb-f-main-entities-edit");
-        $(".caosdb-f-edit").toggle();//.toggleClass("col-xs-4");
+        $(".caosdb-f-edit").toggle(); //.toggleClass("col-xs-4");
         this._toggle_min_width_warning();
     }
 
-    this._toggle_min_width_warning = function() {
+    this._toggle_min_width_warning = function () {
         // Somewhat counter-intuitive, but when we're not in edit mode
         // and toggle the panel, we're entering and the warning should
         // be shown on small screens and vice-versa.
@@ -1758,18 +1801,18 @@ var edit_mode = new function() {
         $(".caosdb-edit-min-width-warning").toggleClass("d-block", !(edit_mode.is_edit_mode()));
     }
 
-    this.leave_edit_mode_template = function(app) {
+    this.leave_edit_mode_template = function (app) {
         app.finish();
         $(".caosdb-f-btn-toggle-edit-mode").text("Edit Mode");
         edit_mode.reset_actions_panels($(".caosdb-f-main").toArray());
         window.localStorage.removeItem("edit_mode");
     }
 
-    this.is_edit_mode = function() {
+    this.is_edit_mode = function () {
         return window.localStorage.edit_mode !== undefined;
     }
 
-    this.add_cancel_button = function(ent, callback) {
+    this.add_cancel_button = function (ent, callback) {
         var cancel_btn = $('<button class="btn btn-link caosdb-update-entity-button caosdb-f-entity-cancel-button">Cancel</button>');
 
         $(ent).find(".caosdb-f-edit-mode-entity-actions-panel").append(cancel_btn);
@@ -1777,33 +1820,33 @@ var edit_mode = new function() {
         $(cancel_btn).click(callback);
     }
 
-    this.create_new_entity = async function(role) {
+    this.create_new_entity = async function (role) {
         var empty_entity = str2xml('<Response><' + role + '/></Response>');
         return (await transformation.transformEntities(empty_entity))[0];
     }
 
-    this.remove_cancel_button = function(entity) {
+    this.remove_cancel_button = function (entity) {
         $(entity).find('.caosdb-f-entity-cancel-button').remove()
     }
 
     /**
      * entity : HTMLElement
      */
-    this.freeze_entity = function(entity) {
+    this.freeze_entity = function (entity) {
         $(entity).css("pointer-events", "none").css("filter", "blur(10px)");
     }
 
     /**
      * entity : HTMLElement
      */
-    this.unfreeze_entity = function(entity) {
+    this.unfreeze_entity = function (entity) {
         $(entity).css("pointer-events", "").css("filter", "");
     }
 
     /**
      * this function is called in entity_palette.xsl
      */
-    this.filter = function(ent_type) {
+    this.filter = function (ent_type) {
         var text = $("#caosdb-f-filter-" + ent_type).val();
         if (ent_type == "properties") {
             var short_type = "p";
@@ -1844,14 +1887,14 @@ var edit_mode = new function() {
      * @return {HTMLElement} the entity form
      */
     this.edit = function (entity) {
-      if (!this.is_edit_mode()) {
-        throw Error("edit_mode is not active");
-      }
-      if (!edit_mode.app.can("startEdit")) {
-        throw Error("edit_mode.app does not allow to start the edit");
-      }
-      edit_mode.app.startEdit(entity);
-      return edit_mode.app.entity;
+        if (!this.is_edit_mode()) {
+            throw Error("edit_mode is not active");
+        }
+        if (!edit_mode.app.can("startEdit")) {
+            throw Error("edit_mode.app does not allow to start the edit");
+        }
+        edit_mode.app.startEdit(entity);
+        return edit_mode.app.entity;
     }
 
     /**
@@ -1859,19 +1902,19 @@ var edit_mode = new function() {
      * visible.
      */
     const UPDATE_PERMISSIONS = [
-       "UPDATE:DESCRIPTION",
-       "UPDATE:VALUE",
-       "UPDATE:ROLE",
-       "UPDATE:PARENT:REMOVE",
-       "UPDATE:PARENT:ADD",
-       "UPDATE:PROPERTY:REMOVE",
-       "UPDATE:PROPERTY:ADD",
-       "UPDATE:NAME",
-       "UPDATE:DATA_TYPE",
-       "UPDATE:FILE:REMOVE",
-       "UPDATE:FILE:ADD",
-       "UPDATE:FILE:MOVE",
-       "UPDATE:QUERY_TEMPLATE_DEFINITION",
+        "UPDATE:DESCRIPTION",
+        "UPDATE:VALUE",
+        "UPDATE:ROLE",
+        "UPDATE:PARENT:REMOVE",
+        "UPDATE:PARENT:ADD",
+        "UPDATE:PROPERTY:REMOVE",
+        "UPDATE:PROPERTY:ADD",
+        "UPDATE:NAME",
+        "UPDATE:DATA_TYPE",
+        "UPDATE:FILE:REMOVE",
+        "UPDATE:FILE:ADD",
+        "UPDATE:FILE:MOVE",
+        "UPDATE:QUERY_TEMPLATE_DEFINITION",
     ];
 
     /**
@@ -1885,7 +1928,7 @@ var edit_mode = new function() {
      * @parma {function} callback - the function which initializes and opens
      *     the edit form.
      */
-    this.add_start_edit_button = function(entity, callback) {
+    this.add_start_edit_button = function (entity, callback) {
         var has_any_update_permission = false;
         for (let permission of UPDATE_PERMISSIONS) {
             if (hasEntityPermission(entity, permission)) {
@@ -1904,14 +1947,14 @@ var edit_mode = new function() {
         $(button).click(callback);
     }
 
-    this.remove_start_edit_button = function(entity) {
+    this.remove_start_edit_button = function (entity) {
         $(entity).find(".caosdb-f-entity-start-edit-button").remove();
     }
 
 
-    this.add_new_record_button = function(entity, callback) {
+    this.add_new_record_button = function (entity, callback) {
         if (!hasEntityPermission(entity, "USE:AS_PARENT")) {
-          return;
+            return;
         }
         edit_mode.remove_new_record_button(entity);
         var button = $('<button title="Create a new Record from this RecordType." class="btn btn-link caosdb-update-entity-button caosdb-f-entity-new-record-button">+Record</button>');
@@ -1921,13 +1964,13 @@ var edit_mode = new function() {
         $(button).click(callback);
     }
 
-    this.remove_new_record_button = function(entity) {
+    this.remove_new_record_button = function (entity) {
         $(entity).find(".caosdb-f-entity-new-record-button").remove();
     }
 
-    this.add_delete_button = function(entity, callback) {
+    this.add_delete_button = function (entity, callback) {
         if (!hasEntityPermission(entity, "DELETE")) {
-          return;
+            return;
         }
         edit_mode.remove_delete_button(entity);
         var button = $('<button title="Delete this ' + getEntityRole(entity) + '." class="btn btn-link caosdb-update-entity-button caosdb-f-entity-delete-button">Delete</button>');
@@ -1937,17 +1980,17 @@ var edit_mode = new function() {
         $(button).click(() => {
             // hide other elements
             const _alert = form_elements.make_alert({
-              title: "Warning",
-              message: "You are going to delete this entity permanently. This cannot be undone.",
-              proceed_callback: () => {
-                  edit_mode.delete_action(entity)
-              },
-              cancel_callback: () => {
-                  $(_alert).remove();
-                  $(entity).find(".caosdb-f-edit-mode-entity-actions-panel").show();
-              },
-              proceed_text: "Yes, delete!",
-              remember_my_decision_id : "delete_entity",
+                title: "Warning",
+                message: "You are going to delete this entity permanently. This cannot be undone.",
+                proceed_callback: () => {
+                    edit_mode.delete_action(entity)
+                },
+                cancel_callback: () => {
+                    $(_alert).remove();
+                    $(entity).find(".caosdb-f-edit-mode-entity-actions-panel").show();
+                },
+                proceed_text: "Yes, delete!",
+                remember_my_decision_id: "delete_entity",
             });
             $(_alert).addClass("text-end");
 
@@ -1956,7 +1999,7 @@ var edit_mode = new function() {
         });
     }
 
-    this.remove_delete_button = function(entity) {
+    this.remove_delete_button = function (entity) {
         $(entity).find(".caosdb-f-entity-delete-button").remove();
     }
 
@@ -1968,6 +2011,6 @@ var edit_mode = new function() {
 /**
  * Add the extensions to the webui.
  */
-$(document).ready(function() {
+$(document).ready(function () {
     edit_mode.init();
 });
diff --git a/src/core/js/ext_applicable.js b/src/core/js/ext_applicable.js
new file mode 100644
index 0000000000000000000000000000000000000000..6e4ae94f742e00e46f6cff609f4217100c9f4292
--- /dev/null
+++ b/src/core/js/ext_applicable.js
@@ -0,0 +1,342 @@
+/*
+ * ** header v3.0
+ * This file is a part of the CaosDB Project.
+ *
+ * Copyright (C) 2020 IndiScale GmbH <info@indiscale.com>
+ * Copyright (C) 2020 Timm Fitschen <t.fitschen@indiscale.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * ** end header
+ */
+
+'use strict';
+
+/**
+ * Useful helpers for is_applicable functionality.
+ */
+var _helpers = function(getEntityPath) {
+
+  /**
+   * Check if an entity has a path attribute and one of a set of extensions.
+   *
+   * Note: the array of extensions must contain only lower-case strings.
+   *
+   * @param {HTMLElement} entity
+   * @param {string[]} extensions - an array of file extesions, e.g. `jpg`.
+   * @return {boolean} true iff the entity has a path with one of the
+   *     extensionss.
+   */
+  const path_has_file_extension = function(entity, extensions) {
+      const path = getEntityPath(entity);
+      if (path) {
+          for (var ext of extensions) {
+              if (path.toLowerCase().endsWith(ext)) {
+                  return true;
+              }
+          }
+      }
+      return false;
+  }
+
+  return {
+    path_has_file_extension: path_has_file_extension
+  };
+}(getEntityPath)
+
+var ext_applicable = function($, logger, is_in_view_port, load_config, getEntityPath, connection, helpers, createWaitingNotification) {
+
+  const version = "0.1";
+
+  /**
+   * Run through all creators and call the "create" function of the first
+   * creator which returns true from is_applicable(entity).
+   *
+   * @param {HTMLElement} entity
+   * @param {Creator[]} creators - list of creators
+   * @return {HTMLElement|String} content - the result of the first matching
+   *     creator.
+   */
+  const root_creator = async function (entity, creators) {
+    for (let c of creators) {
+      var is_applicable = false;
+      try {
+        is_applicable = await c.is_applicable(entity);
+      } catch (err) {
+        logger.error(`error in is_applicable function of creator`, c, err);
+        continue;
+      }
+      if (is_applicable) {
+        const content = await c.create(entity);
+        return content;
+      }
+    }
+    return undefined;
+  }
+
+  var _set_content = function (container, content) {
+    const _container = $(container);
+    _container.empty();
+
+    if (content) {
+        _container.append(content);
+    }
+  }
+
+  /**
+   * Replace the old content (if any) of the container by the new the content
+   * (created by the creators).
+   *
+   * Dispatch contentReadyEvent or noContentEvent if given.
+   *
+   * @param {HTMLElement} container
+   * @param {HTMLElement|string} [content]
+   * @param {Event} [contentReadyEvent]
+   * @param {Event} [noContentEvent]
+   */
+  const set_content = async function (container, content, contentReadyEvent, noContentEvent) {
+    try {
+      const wait = createWaitingNotification("Please wait...");
+      _set_content(container, wait);
+      const result = await content;
+      _set_content(container, result);
+      if (result && contentReadyEvent) {
+        container.dispatchEvent(contentReadyEvent);
+      } else if (!result && noContentEvent) {
+        container.dispatchEvent(noContentEvent);
+      }
+
+    } catch (err) {
+      logger.error(err);
+      const err_msg = "An error occured while loading this content.";
+      _set_content(container, err_msg);
+    }
+  }
+
+
+  /**
+   * @param {HTMLElement} entity
+   * @param {get_container_cb} get_container_cb
+   * @param {Creator[]} creators
+   * @param {Event} [contentReadyEvent]
+   * @param {Event} [noContentEvent]
+   */
+  const _root_handler = function (entity, get_container_cb, creators, contentReadyEvent, noContentEvent) {
+    const _container = get_container_cb(entity);
+    if (_container) {
+      const content = root_creator(entity, creators);
+      set_content(_container, content, contentReadyEvent, noContentEvent);
+    }
+  }
+
+
+  /**
+   * The root handler trigger call the root_handler callback on every
+   * .caosdb-entity-panel and every .caosdb-entity-preview in the viewport.
+   *
+   * @param {function} root_handler - the root handler callback.
+   */
+  var root_handler_trigger = function(root_handler) {
+    var entities = $(".caosdb-entity-panel,.caosdb-entity-preview");
+    for (let entity of entities) {
+
+      // TODO viewport + 1000 px for earlier loading
+      if (is_in_view_port(entity)) {
+        root_handler(entity);
+      }
+    }
+  }
+
+
+  /**
+   * Initialize the scroll watcher which listens on the scroll event of the
+   * window and triggers the root handler with a delay after the last
+   * scroll event.
+   *
+   * @param {integer} delay - timeout in milliseconds after the last scroll
+   *     event. After this timeout the trigger is called.
+   * @param {function} trigger - the root handler callback which is called.
+   */
+  var init_watcher = function(delay, trigger) {
+    var scroll_timeout = undefined;
+    $(window).scroll(() => {
+        if (scroll_timeout) {
+            clearTimeout(scroll_timeout);
+        }
+        scroll_timeout = setTimeout(trigger, delay);
+    });
+
+    var preview_timeout = undefined;
+
+    // init watcher on newly loaded entity previews.
+    window.addEventListener(
+      preview.previewReadyEvent.type,
+      () => {
+        if (preview_timeout) {
+          clearTimeout(preview_timeout);
+        }
+        preview_timeout = setTimeout(trigger, delay);
+      },
+      true);
+
+    // trigger for the first time
+    trigger();
+  };
+
+  /**
+   * @callback get_container_cb
+   * @param {HTMLElement} entity - the entity for which this callback shall
+   *     return the is_applicable container.
+   * @returns {HTMLElement} child of entity which is a container for the
+   *     is_applicable_app
+   */
+
+  /**
+   * @type {IsApplicableApp}
+   * @property {IsApplicableConfig} config
+   * @property {Creator[]} creators
+   * @property {function} root_handler
+   */
+
+  /**
+   * @type {IsApplicableConfig}
+   * @property {string|HTMLElement|function} fallback - Fallback content or
+   *     callback if none of the creators are applicable.
+   * @property {string} version - the version of the configuration which must
+   *     match this module's version.
+   * @property {CreatorConfig[]} creators - an array of creators.
+   */
+
+  /**
+   * make a fallback creator
+   *
+   * @return {Creator}
+   */
+  const _make_fallback_creator = function(fallback) {
+    if (fallback) {
+      return {
+        id: "_generated_fallback_creator",
+        is_applicable: (entity) => true, // always applicable
+        create: typeof fallback === "function" ? fallback : (entity) => fallback,
+      };
+    }
+    return undefined;
+  }
+
+  const _make_creator = function (c) {
+    return {
+      id: c.id,
+      is_applicable: typeof c.is_applicable === "function" ?
+                         c.is_applicable : eval(c.is_applicable),
+      create: typeof c.create === "function" ? c.create : eval(c.create)
+    };
+  }
+
+
+  /**
+   * @param {IsApplicableConfig}
+   * @return {Creator[]} creators
+   */
+  const _make_creators = function(config) {
+    const creators = [];
+    for (let c of config.creators) {
+      creators.push(_make_creator(c));
+    }
+    const fallback_creator = _make_fallback_creator(config.fallback);
+    if (fallback_creator) {
+      creators.push(fallback_creator);
+    }
+    return creators;
+  }
+
+  /**
+   * @type {CreatorConfig}
+   * @property {string} [id] - a unique id for the creator. optional, for
+   *     debuggin purposes.
+   * @property {function|string} is_applicable - If this is a string this has
+   *     to be valid javascript! An asynchronous function which accepts one
+   *     parameter, an entity in html representation, and which returns true
+   *     iff this creator is applicable for the given entity.
+   * @property {string} create - This has to be valid javascript! An
+   *     asynchronous function which accepts one parameter, an entity in html
+   *     representation. It returns a HTMLElement or text node which will be
+   *     shown in the bottom line container iff the creator is applicable.
+   */
+
+  /**
+   * @param {string} app_name - the name of this app.
+   * @param {get_container_cb} get_container_cb
+   * @return {get_container_cb} wrapped function which also checks if the
+   *     container has already been filled with the created content.
+   */
+  const _make_get_container_wrapper = function (get_container_cb, app_name) {
+    const _wrapper = function (entity) {
+      const container = get_container_cb(entity);
+      const app_done = $(container).data(app_name);
+      if(!app_done) {
+        // mark container as used
+        $(container).data(app_name, "done");
+        return container
+      }
+      // don't return the container if already used by this app
+      return undefined;
+    }
+    return _wrapper;
+  }
+
+  /**
+   * @param {string} that_version - a version string.
+   * @throws {Error} if that_version doesn't match this modules version.
+   */
+  const _check_version = function(that_version) {
+    if(that_version != version) {
+      throw new Error(`Wrong version in config. Was '${that_version}', should be '${version}'.`);
+    }
+  }
+
+  /**
+   * @param {string} app_name
+   * @param {IsApplicableConfig} config
+   * @param {get_container_cb} get_container
+   * @param {Event} [contentReadyEvent]
+   * @param {Event} [noContentEvent]
+   * returns {IsApplicableApp}
+   */
+  const create_is_applicable_app = function(app_name, config, get_container, contentReadyEvent, noContentEvent) {
+    logger.debug("create_is_applicable_app", config, get_container);
+    _check_version(config["version"])
+    const creators = _make_creators(config)
+    const get_container_wrapper = _make_get_container_wrapper(get_container, app_name);
+
+    const root_handler = (entity) => _root_handler(entity, get_container_wrapper, creators, contentReadyEvent, noContentEvent);
+
+    if (config.init_watcher) {
+      init_watcher(config.delay || 500, () => {root_handler_trigger(root_handler);});
+    }
+    return {
+      config: config,
+      creators: creators,
+      root_handler: root_handler,
+    }
+  }
+
+  return {
+    create_is_applicable_app: create_is_applicable_app,
+    root_handler_trigger: root_handler_trigger,
+    init_watcher: init_watcher,
+    helpers: helpers,
+    version: version,
+  };
+
+}($, log.getLogger("ext_applicable"), resolve_references.is_in_viewport_vertically, load_config, getEntityPath, connection, _helpers, createWaitingNotification);
diff --git a/src/core/js/ext_autocomplete.js b/src/core/js/ext_autocomplete.js
index cd145ee9e29bf370fd44f035f26d477a9cc0d478..9a639fb4387a32bad128406bb7bd1f036bed5fda 100644
--- a/src/core/js/ext_autocomplete.js
+++ b/src/core/js/ext_autocomplete.js
@@ -42,12 +42,47 @@ var ext_autocomplete = new function () {
         "WHICH",
         "WITH",
         "CREATED BY",
+        "CREATED BY ME",
+        "CREATED AT",
         "CREATED ON",
+        "CREATED IN",
+        "CREATED BEFORE",
+        "CREATED UNTIL",
+        "CREATED AFTER",
+        "CREATED SINCE",
         "SOMEONE",
         "STORED AT",
         "HAS A PROPERTY",
         "HAS BEEN",
         "ANY VERSION OF",
+        "FROM",
+        "INSERTED AT",
+        "INSERTED ON",
+        "INSERTED IN",
+        "INSERTED BY",
+        "INSERTED BY ME",
+        "INSERTED BEFORE",
+        "INSERTED UNTIL",
+        "INSERTED AFTER",
+        "INSERTED SINCE",
+        "UPDATED AT",
+        "UPDATED ON",
+        "UPDATED IN",
+        "UPDATED BY",
+        "UPDATED BY ME",
+        "UPDATED BEFORE",
+        "UPDATED UNTIL",
+        "UPDATED AFTER",
+        "UPDATED SINCE",
+        "SINCE",
+        "BEFORE",
+        "ON",
+        "IN",
+        "AFTER",
+        "UNTIL",
+        "AT",
+        "BY",
+        "BY ME",
     ];
     this.version = "0.1";
 
@@ -146,6 +181,9 @@ var ext_autocomplete = new function () {
             },
             noResultsText: 'No autocompletion suggestions',
             bootstrapVersion: "4",
+            /* this should be updated to 5 when
+                     bootstrap-autocomplete releases official support for bootstrap 5.
+                     Until then: let's hope the best.*/
 
         });
 
@@ -158,4 +196,4 @@ $(document).ready(function () {
     if ("${BUILD_MODULE_EXT_AUTOCOMPLETE}" == "ENABLED") {
         caosdb_modules.register(ext_autocomplete);
     }
-});
+});
\ No newline at end of file
diff --git a/src/core/js/ext_cosmetics.js b/src/core/js/ext_cosmetics.js
index e70f8e5779bb75ebd08a47ec080fc57dc1348dc4..4d935a2a5afecd699fee1a24416c06b30d1adc46 100644
--- a/src/core/js/ext_cosmetics.js
+++ b/src/core/js/ext_cosmetics.js
@@ -1,26 +1,70 @@
-var cosmetics = new function() {
-    this.init = function() {
-        this.linkify();
-    }
+/*
+ * This file is a part of the CaosDB Project.
+ *
+ * Copyright (C) 2021 IndiScale GmbH <info@indiscale.com>
+ * Copyright (C) 2021 Timm Fitschen <t.fitschen@indiscale.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+"use strict";
+
+/**
+ * Cosmetics module is a collection of small look-and-feel tweaks for the
+ * caosdb webui.
+ *
+ * @author Timm Fitschen
+ */
+var cosmetics = new function () {
 
-    this.linkify = function() {
-        /* DEPRECATED css class .caosdb-property-text-value - Use
-        * .caosdb-f-property-single-raw-value or introduce new
-        * .caosdb-v-property-text-value */
-        $('.caosdb-property-text-value').each(function(index) {
+    var _linkify = function () {
+        $('.caosdb-f-property-text-value').each(function (index) {
+            // TODO also extract and convert links surrounded by other text
             if (/^https?:\/\//.test(this.innerText)) {
                 var uri = this.innerText;
                 var text = uri
 
                 $(this).parent().css("overflow", "hidden");
                 $(this).parent().css("text-overflow", "ellipsis");
-                $(this).html('<a href="' + uri + '"><i class="bi-window"></i> ' + text + '</a>');
+                $(this).html(`<a class="caosdb-v-property-href-value" href="${uri}">${text} <i class="bi bi-box-arrow-up-right"></i></a>`);
             }
         });
     }
+
+    /**
+     * Convert any text-value beginning with 'http(s)://' into a link.
+     *
+     * A listener detects edit-mode changes and previews
+     */
+    var linkify = function () {
+        _linkify();
+
+        // edit-mode-listener
+        document.body.addEventListener(edit_mode.end_edit.type, _linkify, true);
+        // preview listener
+        document.body.addEventListener(preview.previewReadyEvent.type, _linkify, true);
+    }
+
+    this.init = function () {
+        if ("${BUILD_MODULE_EXT_COSMETICS_LINKIFY}" == "ENABLED") {
+            linkify();
+        }
+    }
+
 }
 
 
-$(document).ready(function() {
-    cosmetics.init();
-});
+$(document).ready(function () {
+    caosdb_modules.register(cosmetics);
+});
\ No newline at end of file
diff --git a/src/core/js/form_elements.js b/src/core/js/form_elements.js
index 94607ab01a906ded9999bbfef00340d5818838f4..d01b45ee9148febc592d615a8fa947d0d53656d3 100644
--- a/src/core/js/form_elements.js
+++ b/src/core/js/form_elements.js
@@ -519,7 +519,7 @@ var form_elements = new function () {
         let label = this._make_input_label_str(config);
         let loading = $(createWaitingNotification("loading..."))
             .addClass("caosdb-f-field-not-ready");
-        let input_col = $('<div class="col-sm-9"/>');
+        let input_col = $('<div class="caosdb-f-property-value col-sm-9"/>');
 
         input_col.append(loading);
         this._query(config.query).then(async function (entities) {
@@ -559,7 +559,6 @@ var form_elements = new function () {
             select_picker_options["liveSearchNormalize"] = true;
             select_picker_options["liveSearchPlaceholder"] = "search...";
         }
-        console.log(select)
         $(select).selectpicker(select_picker_options);
         $(select).selectpicker("val", value);
         this.init_actions_box(field);
@@ -577,7 +576,7 @@ var form_elements = new function () {
         if (actions_box.length === 0) {
             actions_box = $(`<div class="bs-actionsbox">
                         <div class="btn-group btn-group-sm d-grid">
-                            <button type="button" class="actions-btn btn-secondary bs-deselect-all btn btn-light">None</button>
+                            <button type="button" class="actions-btn bs-deselect-all btn btn-light">None</button>
                         </div>
                     </div>`)
                 .hide();
@@ -599,9 +598,11 @@ var form_elements = new function () {
             actions_box
                 .find(".bs-deselect-all")
                 .click((e) => {
-                    select.val(null)
-                        .selectpicker("render")
-                        .parent().toggleClass("open", false);
+                    select
+                        .selectpicker("val", null);
+                    select
+                        .selectpicker("render");
+                    select.dropdown("hide");
                     select[0].dispatchEvent(form_elements.field_changed_event);
                 });
         }
@@ -1297,7 +1298,7 @@ var form_elements = new function () {
      */
     this._make_field_wrapper = function (name) {
         caosdb_utils.assert_string(name, "param `name`");
-        return $('<div class="row caosdb-f-field" data-field-name="' + name + '" />')[0];
+        return $('<div class="row caosdb-f-field caosdb-v-field" data-field-name="' + name + '" />')[0];
     }
 
     /**
diff --git a/src/core/js/query_shortcuts.js b/src/core/js/query_shortcuts.js
index af75516767b7a354562d7fdea744bc458047e500..a0a7f877555dc6e2c2617892540c900be12a6a19 100644
--- a/src/core/js/query_shortcuts.js
+++ b/src/core/js/query_shortcuts.js
@@ -931,14 +931,6 @@ var query_shortcuts = new function() {
 
     this._cache_visibility_key = "caosdb.query-shortcuts-panel.visible";
 
-    /* TODO hide the toolbox when not visible
-    this.toggle_shortcuts_panel = function(panel) {
-        $(panel).find(".caosdb-f-shortcuts-toolbox-button").toggle();
-        $(panel).find(".caosdb-f-shortcuts-panel-body").toggle()
-    }
-    */
-
-
     // deps
     this._updateEntities = update;
     this._insertEntities = insert;
diff --git a/src/core/js/webcaosdb.js b/src/core/js/webcaosdb.js
index 221b8b5a12da92395efcc4e52bfec0d7d6561ce9..74dc62be15551f987707253ca201777f7c3929ac 100644
--- a/src/core/js/webcaosdb.js
+++ b/src/core/js/webcaosdb.js
@@ -122,17 +122,6 @@ this.navbar = new function () {
         // menu defaults to the navbar
         const menu = _options["menu"] || this.get_navbar();
 
-        /* TODO is this still needed?
-        if ($(menu).is("ul.caosdb-navbar")) {
-            // special styling for buttons which are added directly to the
-            // navbar
-            $(button_elem)
-                .toggleClass("navbar-btn", true)
-                .toggleClass("btn", true)
-                .toggleClass("btn-link", true);
-        }
-        */
-
         logger.debug("add", wrapper, "to", menu);
         $(menu).append(wrapper);
 
@@ -153,7 +142,7 @@ this.navbar = new function () {
 
 
     /**
-     * Initialize the hiding/showing of the input form.
+     * Initialize the hiding/showing of the login form.
      *
      * If the viewport is xs (width <= 768px) the login form is hidden in the
      * a menu anyways.
@@ -176,7 +165,6 @@ this.navbar = new function () {
         // show form and hide the show_button
         const _in = () => {
           // xs means viewport <= 768px
-            // TODO is there a better option
           form.removeClass("d-none");
           form.addClass("d-xs-inline-block");
           show_button.removeClass("d-inline-block");
@@ -185,7 +173,6 @@ this.navbar = new function () {
         // hide form and show the show_button
         const _out = () => {
           // xs means viewport <= 768px
-            // TODO is there a better option
           form.removeClass("d-xs-inline-block");
           form.addClass("d-none");
           show_button.removeClass("d-none");
@@ -601,7 +588,6 @@ this.transformation = new function () {
         var xsl = await transformation.retrieveXsltScript("property.xsl");
         insertParam(xsl, "filesystempath", connection.getBasePath() + "FileSystem/");
         insertParam(xsl, "entitypath", connection.getBasePath() + "Entity/");
-        insertParam(xsl, "close-char", '×');
         var entityXsl = await transformation.retrieveXsltScript('entity.xsl');
         var messageXsl = await transformation.retrieveXsltScript('messages.xsl');
         var commonXsl = await transformation.retrieveXsltScript('common.xsl');
@@ -609,15 +595,6 @@ this.transformation = new function () {
         let html = await asyncXslt(xml, xslt);
         return html;
     }
-    /**
-     * @param {XMLDocument} xml
-     * @return {HTMLElement[]} an array of HTMLElements.
-     */
-    this.transformEntityPalette = async function _tME(xml) {
-        var xsl = await transformation.retrieveXsltScript("entity_palette.xsl");
-        let html = await asyncXslt(xml, xsl);
-        return html;
-    }
 
     /**
      * Retrieve the entity.xsl script and modify it such that we can use it
@@ -637,7 +614,6 @@ this.transformation = new function () {
         var xslt = transformation.mergeXsltScripts(entityXsl, [errorXsl, commonXsl]);
         insertParam(xslt, "filesystempath", connection.getBasePath() + "FileSystem/");
         insertParam(xslt, "entitypath", connection.getBasePath() + "Entity/");
-        insertParam(xslt, "close-char", '×');
         xslt = injectTemplate(xslt, _root);
         return xslt;
     }
diff --git a/src/core/webcaosdb.xsl b/src/core/webcaosdb.xsl
index 9d0dd2e235ca59ce6f4e7b75acd59e063e322751..9a0d6769f1901e860c7a2318fce28957d6461496 100644
--- a/src/core/webcaosdb.xsl
+++ b/src/core/webcaosdb.xsl
@@ -27,6 +27,7 @@
 <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
   <xsl:output method="html" />
 
+  <xsl:include href="xsl/jsheader.xsl" />
   <xsl:include href="xsl/main.xsl" />
   <xsl:include href="xsl/navbar.xsl" />
   <xsl:include href="xsl/messages.xsl" />
@@ -45,10 +46,6 @@
       <h3>Tour</h3>
     </div>
     <div class="caosdb-f-tour-toc-body"></div>
-    <!--<button class="btn btn-light caosdb-f-leave-tour">Leave Tour</button>-->
-    <!--<div class="caosdb-v-tour-footer">-->
-        <!--<button class="caosdb-f-tour-toc-toggle btn btn-light">Hide</button>-->
-    <!--</div>-->
     </div>
   </xsl:template>
 
diff --git a/src/core/xsl/entity.xsl b/src/core/xsl/entity.xsl
index 95f5031975d99b9252474ac7469c214b22b5ef68..d562bf99f7611b976e10d5349d0c99972d6d8fdd 100644
--- a/src/core/xsl/entity.xsl
+++ b/src/core/xsl/entity.xsl
@@ -309,7 +309,7 @@
                 <!-- DEPRECATED css class .caosdb-property-text-value - Use
                      .caosdb-f-property-single-raw-value or introduce new 
                      .caosdb-v-property-text-value -->
-                <xsl:value-of select="'caosdb-f-property-single-raw-value caosdb-property-text-value'"/>
+                <xsl:value-of select="'caosdb-f-property-single-raw-value caosdb-property-text-value caosdb-f-property-text-value caosdb-v-property-text-value'"/>
               </xsl:attribute>
               <xsl:call-template name="trim">
                 <xsl:with-param name="str">
@@ -324,7 +324,7 @@
         <!-- DEPRECATED css class .caosdb-property-text-value - Use
              .caosdb-f-property-single-raw-value or introduce new 
              .caosdb-v-property-text-value -->
-        <span class="caosdb-f-property-single-raw-value caosdb-property-text-value"/>
+        <span class="caosdb-f-property-single-raw-value caosdb-property-text-value caosdb-f-property-text-value caosdb-v-property-text-value"/>
       </xsl:otherwise>
     </xsl:choose>
   </xsl:template>
diff --git a/src/core/xsl/entity_palette.xsl b/src/core/xsl/entity_palette.xsl
index e45364c80a4ba3849d5b9abaf29c5b85c1b76af1..9d3a13f1c96a7ce864554b78b9d9e0dbebfb3c5f 100644
--- a/src/core/xsl/entity_palette.xsl
+++ b/src/core/xsl/entity_palette.xsl
@@ -3,19 +3,13 @@
   <xsl:output method="html"/>
 
   <xsl:template match="/Response">
-    <div class="list-group list-group-flush">
-    <div class="list-group-item btn-group-vertical caosdb-v-editmode-btngroup caosdb-f-edit-mode-create-buttons">
-      <button type="button" class="btn btn-secondary caosdb-f-edit-panel-new-button new-property">Create Property</button>
-      <button type="button" class="btn btn-secondary caosdb-f-edit-panel-new-button new-recordtype">Create RecordType</button>
-    </div>
     <div title="Drag and drop Properties from this panel to the Entities on the left." class="caosdb-v-editmode-existing caosdb-f-edit-mode-existing d-none">
       <div class="card-header">
-        <h6>Existing Properties</h6>
+        <span class="card-title">Existing Properties</span>
       </div>
       <div class="card">
         <div class="input-group" style="width: 100%;">
           <input class="form-control" placeholder="filter..." title="Type a name (full or partial)." oninput="edit_mode.filter('properties');" id="caosdb-f-filter-properties" type="text"/>
-            <button class="btn btn-secondary caosdb-f-edit-panel-new-button new-property caosdb-f-hide-on-empty-input" title="Create this Property." ><i class="bi-plus"></i></button>
         </div>
         <ul class="caosdb-v-edit-list list-group">
           <xsl:apply-templates select="./Property"/>
@@ -24,24 +18,22 @@
     </div>
     <div title="Drag and drop RecordTypes from this panel to the Entities on the left." class="caosdb-v-editmode-existing caosdb-f-edit-mode-existing d-none">
       <div class="card-header">
-        <h6>Existing RecordTypes</h6>
+        <span class="card-title">Existing RecordTypes</span>
       </div>
       <div class="card">
         <div class="input-group" style="width: 100%;">
           <input class="form-control" placeholder="filter..." title="Type a name (full or partial)." oninput="edit_mode.filter('recordtypes');" id="caosdb-f-filter-recordtypes" type="text"/>
-              <button class="btn btn-secondary caosdb-f-edit-panel-new-button new-recordtype caosdb-f-hide-on-empty-input" title="Create this RecordType"><i class="bi-plus"></i></button>
         </div>
         <ul class="caosdb-v-edit-list list-group">
           <xsl:apply-templates select="./RecordType"/>
         </ul>
     </div>
-    </div>
   </div>
   </xsl:template>
 
   <xsl:template match="RecordType">
     <xsl:if test="string-length(@name)>0">
-        <li class="caosdb-f-edit-drag list-group-item caosdb-v-edit-drag">
+        <li draggable="true" class="caosdb-f-edit-drag list-group-item caosdb-v-edit-drag">
         <xsl:attribute name="id">caosdb-f-edit-rt-<xsl:value-of select="@id"/></xsl:attribute>
         <xsl:value-of select="@name"/>
         </li>
@@ -60,7 +52,7 @@
           <!-- ignore unit property -->
       </xsl:when>
       <xsl:otherwise>
-        <li class="caosdb-f-edit-drag list-group-item caosdb-v-edit-drag">
+        <li draggable="true" class="caosdb-f-edit-drag list-group-item caosdb-v-edit-drag">
           <xsl:attribute name="id">caosdb-f-edit-p-<xsl:value-of select="@id"/></xsl:attribute>
           <xsl:value-of select="@name"/>
         </li>
diff --git a/src/core/xsl/main.xsl b/src/core/xsl/main.xsl
index 238f790fdcd93816d36adf35886e162575df9aa3..1a0bdb812afb18ad6ab291acb975de994c5d3e47 100644
--- a/src/core/xsl/main.xsl
+++ b/src/core/xsl/main.xsl
@@ -93,16 +93,6 @@
     </xsl:element>
     <!--CSS_EXTENSIONS-->
   </xsl:template>
-  <xsl:template name="caosdb-head-js">
-    <script>
-        window.sessionStorage.caosdbBasePath = "<xsl:value-of select="$basepath"/>";
-    </script>
-    <xsl:element name="script">
-      <xsl:attribute name="src">
-        <xsl:value-of select="concat($basepath,'webinterface/${BUILD_NUMBER}/webcaosdb.dist.js')"/>
-      </xsl:attribute>
-    </xsl:element>
-  </xsl:template>
   <xsl:template name="caosdb-data-container">
     <div class="container d-flex flex-column-reverse flex-lg-row caosdb-f-main">
         <div class="flex-grow-1 caosdb-f-main-entities">
@@ -120,14 +110,20 @@
             <div class="card-header">
               <span class="card-title">Edit Mode Toolbox</span>
             </div>
-            <div class="caosdb-f-edit-panel-body"></div>
+            <div class="caosdb-f-edit-panel-body">
+              <div class="list-group list-group-flush">
+              <div class="list-group-item btn-group-vertical caosdb-v-editmode-btngroup caosdb-f-edit-mode-create-buttons">
+                <button type="button" class="btn btn-secondary caosdb-f-edit-panel-new-button new-property">Create Property</button>
+                <button type="button" class="btn btn-secondary caosdb-f-edit-panel-new-button new-recordtype">Create RecordType</button>
+              </div>
+              </div>
+            </div>
           </div>
         </div>
     </div>
   </xsl:template>
   <xsl:template match="*" mode="entities"/>
   <xsl:template match="*" mode="top-level-data"/>
-  <xsl:variable name="close-char" select="'×'"/>
   <!-- assure that this uri ends with a '/' -->
   <xsl:template name="uri_ends_with_slash">
     <xsl:param name="uri"/>
diff --git a/src/core/xsl/welcome.xsl b/src/core/xsl/welcome.xsl
index 249c2f8e873e83dc4db3132fc22152211fd87965..c3c80222492abaf243cbf227b143a9c13546a3f1 100644
--- a/src/core/xsl/welcome.xsl
+++ b/src/core/xsl/welcome.xsl
@@ -25,7 +25,7 @@
 <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
   <xsl:output method="html"/>
   <xsl:template name="welcome">
-    <div class="caosdb-v-welcome-panel bg-light">
+    <div class="caosdb-v-welcome-panel bg-light container">
       <h1>Welcome</h1>
       <p>This is CaosDB.</p>
               <p>This is the default welcome message. If you are an administrator you can override it. Just copy <code>src/core/xsl/welcome.xsl</code> to <code>src/ext/xsl/welcome.xsl</code> and change this content. Then run <code>make</code> again in CaosdDB's web interface's root directory.</p>
diff --git a/src/doc/tutorials/edit_mode.rst b/src/doc/tutorials/edit_mode.rst
index 01502f55d5d76959a17bf74c71632d0d902f625e..3e7d9ffb4472ab8ff0ec9a4539ab9ab11563ffd2 100644
--- a/src/doc/tutorials/edit_mode.rst
+++ b/src/doc/tutorials/edit_mode.rst
@@ -183,11 +183,3 @@ stored within ``/uploaded.by/<REALM>/<USER>/``.
 The same is true for properties with data type ``REFERENCE``, too. In
 that case, the Record of the file that is uploaded will be assigned the
 RecordType of value of the original reference property.
-
-.. warning::
-
-   Until `this bug
-   <https://gitlab.indiscale.com/caosdb/src/caosdb-webui/-/issues/200>`_
-   has been fixed, the upload button is broken and does not open the
-   upload dialogue.
-
diff --git a/src/ext/js/fileupload.js b/src/ext/js/fileupload.js
index 4e9f7cd02a4f3c5e63080f1e520c10a9328bfe22..31d86589286f1761f481cc9d9bb6a557a63cbce1 100644
--- a/src/ext/js/fileupload.js
+++ b/src/ext/js/fileupload.js
@@ -25,18 +25,31 @@ var fileupload = new function() {
 
     // TODO * action to config * upload-path id -> class * message configurable
     // * style path input
-    const _modal_str = ` <div class="modal fade" tabindex="-1" role="dialog">
-    <div class="modal-dialog modal-lg" role="document"> <div
-    class="modal-content"> <div class="modal-header"> <button type="button"
-    class="btn-close" data-bs-dismiss="modal">&times;</button> <h4
-    class="modal-title">File Upload</h4> </div> <div class="modal-body"> <form
-    action="/Entity/" class="dropzone dz-clickable" > <label>path</label><input
-    id="upload-path" type="text" value="/"/> <div class="dz-message">
-    Drag'n'drop files to this area or click to upload.  </div> </form> </div>
-    <div class="modal-footer"> <button type="button" class="btn btn-secondary
-    caosdb-f-file-upload-submit-button">Ok</button> <button type="button"
-    class="btn btn-secondary" data-bs-dismiss="modal">Close</button> </div> </div>
-    </div> </div>`;
+    const _modal_str = `
+<div class="modal fade" tabindex="-1" role="dialog">
+    <div class="modal-dialog modal-lg" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h4 class="modal-title">File Upload</h4>
+                <button type="button" class="btn-close" data-bs-dismiss="modal">
+                </button>
+            </div>
+            <div class="modal-body">
+                <form action="/Entity/" class="dropzone dz-clickable" >
+                    <label>path</label>
+                    <input id="upload-path" type="text" value="/"/>
+                    <div class="dz-message">
+                        Drag'n'drop files to this area or click to upload.
+                    </div>
+                </form>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-secondary caosdb-f-file-upload-submit-button">Ok</button>
+                <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
+            </div>
+        </div>
+    </div>
+</div>`;
 
     /** Create a dropzone.js form for the file upload.
      */
@@ -122,7 +135,7 @@ var fileupload = new function() {
                 input.after(`<div class="alert alert-danger alert-dismissible"
     role="alert">
     <button type="button" class="btn-close" data-bs-dismiss="alert"
-        aria-label="Close"><span aria-hidden="true">&times;</span></button>
+        aria-label="Close"></button>
     <strong>Error!</strong> You are not logged in!.</div>`);
             } else {
                 globalError(event, error, xhr);
@@ -188,7 +201,7 @@ var fileupload = new function() {
             input.after(`<div class="alert alert-success alert-dismissible"
   role="alert">
   <button type="button" class="btn-close" data-bs-dismiss="alert"
-    aria-label="Close"><span aria-hidden="true">&times;</span></button>
+    aria-label="Close"></button>
   <strong>Success!</strong>
   The file <code class="caosdb-f-file-upload-file-name">` +
                 getEntityName(entity) + `</code> has been uploaded.</div>`);
@@ -308,7 +321,7 @@ var fileupload = new function() {
             error_handler,
             atom_par);
         var toggle_function = function() {
-            $(modal).modal()
+            $(modal).modal("toggle");
         };
 
         this.add_file_upload_button(edit_menu, button, toggle_function);
diff --git a/test/core/index.html b/test/core/index.html
index 834684b2e737be2e5f241eed3faaea73b3c4020c..0dfea85580fe64d0f7a4676f1d919ec5662dee09 100644
--- a/test/core/index.html
+++ b/test/core/index.html
@@ -35,6 +35,6 @@
     <script>
       var _caosdb_modules_auto_init = false;
     </script>
-    <script src="webcaosdb.dist.js"></script>
+    <!--JS_INCLUDE-->
   </body>
 </html>
diff --git a/test/core/js/modules/edit_mode.js.js b/test/core/js/modules/edit_mode.js.js
index 905b57ef0947f652e5d4959acd802244f0d4998d..3a87ee73d167d114d0b7db2b0abe50e4a643f8fa 100644
--- a/test/core/js/modules/edit_mode.js.js
+++ b/test/core/js/modules/edit_mode.js.js
@@ -420,10 +420,6 @@ QUnit.test("get_edit_panel", function (assert) {
     assert.ok(edit_mode.get_edit_panel);
 });
 
-QUnit.test("add_wait_datamodel_info", function (assert) {
-    assert.ok(edit_mode.add_wait_datamodel_info);
-});
-
 QUnit.test("toggle_edit_panel", function (assert) {
     assert.ok(edit_mode.toggle_edit_panel);
 });
@@ -485,97 +481,90 @@ QUnit.test("remove_delete_button", function (assert) {
 });
 
 
-{
-
-    const datamodel = `
-<div><div class=\"btn-group-vertical\"><button type=\"button\" class=\"btn btn-secondary caosdb-f-edit-panel-new-button new-property\">Create new Property</button><button type=\"button\" class=\"btn btn-secondary caosdb-f-edit-panel-new-button new-recordtype\">Create new RecordType</button></div><div title=\"Drag and drop Properties from this panel to the Entities on the left.\" class=\"card\"><div class=\"card-header\"><h5>Existing Properties</h5></div><div class=\"card-body\"><div class=\"input-group\" style=\"width: 100%;\"><input class=\"form-control\" placeholder=\"filter...\" title=\"Type a name (full or partial).\" oninput=\"edit_mode.filter('properties');\" id=\"caosdb-f-filter-properties\" type=\"text\" /><span class=\"input-group-btn\"><button class=\"btn btn-secondary caosdb-f-edit-panel-new-button new-property caosdb-f-hide-on-empty-input\" title=\"Create this Property.\"><span class=\"glyphicon glyphicon-plus\"></span></button></span></div><ul class=\"caosdb-v-edit-list\"><li class=\"caosdb-f-edit-drag list-group-item caosdb-v-edit-drag\" id=\"caosdb-f-edit-p-20\">name</li><li class=\"caosdb-f-edit-drag list-group-item caosdb-v-edit-drag\" id=\"caosdb-f-edit-p-21\">unit</li><li class=\"caosdb-f-edit-drag list-group-item caosdb-v-edit-drag\" id=\"caosdb-f-edit-p-24\">description</li></ul></div></div><div title=\"Drag and drop RecordTypes from this panel to the Entities on the left.\" class=\"card\"><div class=\"card-header\"><h5>Existing RecordTypes</h5></div><div class=\"card-body\"><div class=\"input-group\" style=\"width: 100%;\"><input class=\"form-control\" placeholder=\"filter...\" title=\"Type a name (full or partial).\" oninput=\"edit_mode.filter('recordtypes');\" id=\"caosdb-f-filter-recordtypes\" type=\"text\" /><span class=\"input-group-btn\"><button class=\"btn btn-secondary caosdb-f-edit-panel-new-button new-recordtype caosdb-f-hide-on-empty-input\" title=\"Create this RecordType\"><span class=\"glyphicon glyphicon-plus\"></span></button></span></div><ul class=\"caosdb-v-edit-list\"><li class=\"caosdb-f-edit-drag list-group-item caosdb-v-edit-drag\" id=\"caosdb-f-edit-rt-30992\">Test</li><li class=\"caosdb-f-edit-drag list-group-item caosdb-v-edit-drag\" id=\"caosdb-f-edit-rt-31015\">Test2</li></ul></div></div></div>`;
-
-    edit_mode.query = async function(q) {
-        return [];
-    }
+edit_mode.query = async function(q) {
+    return [];
+}
 
-    QUnit.test("test case 1 - insert property", async function (assert) {
+QUnit.test("test case 1 - insert property", async function (assert) {
 
-        // here lives the test tool box
-        const test_tool_box = $('<div class="caosdb-f-edit-panel-body" />');
+    // here lives the test tool box
+    const test_tool_box = $('<div class="caosdb-f-edit-panel-body" > <div class="list-group list-group-flush"> <div class="list-group-item btn-group-vertical caosdb-v-editmode-btngroup caosdb-f-edit-mode-create-buttons"> <button type="button" class="btn btn-secondary caosdb-f-edit-panel-new-button new-property">Create Property</button> <button type="button" class="btn btn-secondary caosdb-f-edit-panel-new-button new-recordtype">Create RecordType</button> </div> </div>');
 
-        // here live the entities
-        const main_panel = $('<div class="caosdb-f-main-entities"/>');
-        assert.equal($(".caosdb-f-main-entities").length, 0);
+    // here live the entities
+    const main_panel = $('<div class="caosdb-f-main-entities"/>');
+    assert.equal($(".caosdb-f-main-entities").length, 0);
 
-        $(document.body).append(test_tool_box).append(main_panel);
+    $(document.body).append(test_tool_box).append(main_panel);
 
 
-        // ENTER EDIT MODE
-        assert.equal(edit_mode.is_edit_mode(), false, "edit_mode should not be active");
-        // fake server response
-        edit_mode.retrieve_data_model = async function () {
-            return str2xml(datamodel);
-        }
-        var app = await edit_mode.enter_edit_mode();
-        assert.equal(edit_mode.is_edit_mode(), true, "now, edit_mode should be active");
+    // ENTER EDIT MODE
+    assert.equal(edit_mode.is_edit_mode(), false, "edit_mode should not be active");
+    // fake server response
+    edit_mode.retrieve_data_model = async function () {
+        return str2xml("<Response/>");
+    }
+    var app = await edit_mode.enter_edit_mode();
+    assert.equal(edit_mode.is_edit_mode(), true, "now, edit_mode should be active");
 
 
-        // NEW PROPERTY
-        assert.equal($(".caosdb-f-edit-panel-new-button.new-property").length, 2, "two new-property buttons should be present");
-        assert.equal($(".caosdb-entity-panel").length, 0, "no entities");
-        assert.equal(app.state, "initial", "initial state");
-        // click on "new property"
-        $(".caosdb-f-edit-panel-new-button.new-property").first().click();
+    // NEW PROPERTY
+    assert.equal($(".caosdb-f-edit-panel-new-button.new-property").length, 1, "one new-property button should be present");
+    assert.equal($(".caosdb-entity-panel").length, 0, "no entities");
+    assert.equal(app.state, "initial", "initial state");
+    // click on "new property"
+    $(".caosdb-f-edit-panel-new-button.new-property").first().click();
 
-        while (app.state === "initial") {
-            await sleep(500);
-        }
-
-        // EDIT PROPERTY
-        assert.equal(app.state, "changed", "changed state");
-        var entity = $(".caosdb-entity-panel");
-        assert.equal(entity.length, 1, "entity added");
-        // set name
-        $(".caosdb-entity-panel .caosdb-f-entity-name").val("TestProperty");
-
-        // SAVE
-        var save_button = $(".caosdb-f-entity-save-button");
-        assert.equal(save_button.length, 1, "save button available");
-        // fake server response
-        connection.post = async function (uri, data) {
-            await sleep(500);
-            assert.equal(xml2str(data), "<Request><Property name=\"TestProperty\" datatype=\"TEXT\"/></Request>");
-            assert.equal(app.state, "wait", "in wait state");
-            return str2xml("<Response><Property id=\"newId\" name=\"TestProperty\" datatype=\"TEXT\"/></Response>");
-        }
-        // click save button
-        var updated_entity = main_panel.find(".caosdb-entity-panel .caosdb-id:contains('newId')");
-        assert.equal(updated_entity.length, 0, "entity with id not yet in main panel");
-        save_button.click();
+    while (app.state === "initial") {
+        await sleep(500);
+    }
 
-        while (app.state === "changed" || app.state === "wait") {
-            await sleep(500);
-        }
+    // EDIT PROPERTY
+    assert.equal(app.state, "changed", "changed state");
+    var entity = $(".caosdb-entity-panel");
+    assert.equal(entity.length, 1, "entity added");
+    // set name
+    $(".caosdb-entity-panel .caosdb-f-entity-name").val("TestProperty");
+
+    // SAVE
+    var save_button = $(".caosdb-f-entity-save-button");
+    assert.equal(save_button.length, 1, "save button available");
+    // fake server response
+    connection.post = async function (uri, data) {
+        await sleep(500);
+        assert.equal(xml2str(data), "<Request><Property name=\"TestProperty\" datatype=\"TEXT\"/></Request>");
+        assert.equal(app.state, "wait", "in wait state");
+        return str2xml("<Response><Property id=\"newId\" name=\"TestProperty\" datatype=\"TEXT\"/></Response>");
+    }
+    // click save button
+    var updated_entity = main_panel.find(".caosdb-entity-panel .caosdb-id:contains('newId')");
+    assert.equal(updated_entity.length, 0, "entity with id not yet in main panel");
+    save_button.click();
 
-        // SEE RESPONSE
-        assert.equal(app.state, "initial", "initial state");
+    while (app.state === "changed" || app.state === "wait") {
+        await sleep(500);
+    }
 
-        var response = $("#newId");
-        assert.equal(response.length, 1, "entity added");
+    // SEE RESPONSE
+    assert.equal(app.state, "initial", "initial state");
 
-        // entity has been added to main panel
-        updated_entity = main_panel.find(".caosdb-entity-panel .caosdb-id:contains('newId')");
-        assert.equal(updated_entity.length, 1, "entity with new id now in main panel");
+    var response = $("#newId");
+    assert.equal(response.length, 1, "entity added");
 
-        // tests for closed issue https://gitlab.com/caosdb/caosdb-webui/issues/47
-        assert.equal(main_panel.find(".caosdb-entity-panel .caosdb-entity-actions-panel").length, 1, "general actions panel there");
-        assert.equal(main_panel.find(".caosdb-entity-panel .caosdb-f-edit-mode-entity-actions-panel").length, 1, "edit_mode actions panel there (BUG caosdb-webui#47)");
+    // entity has been added to main panel
+    updated_entity = main_panel.find(".caosdb-entity-panel .caosdb-id:contains('newId')");
+    assert.equal(updated_entity.length, 1, "entity with new id now in main panel");
 
-        main_panel.remove();
-        test_tool_box.remove();
+    // tests for closed issue https://gitlab.com/caosdb/caosdb-webui/issues/47
+    assert.equal(main_panel.find(".caosdb-entity-panel .caosdb-entity-actions-panel").length, 1, "general actions panel there");
+    assert.equal(main_panel.find(".caosdb-entity-panel .caosdb-f-edit-mode-entity-actions-panel").length, 1, "edit_mode actions panel there (BUG caosdb-webui#47)");
 
-        edit_mode.leave_edit_mode();
-        assert.equal(edit_mode.is_edit_mode(), false, "edit_mode should not be active");
+    main_panel.remove();
+    test_tool_box.remove();
 
-    });
+    edit_mode.leave_edit_mode();
+    assert.equal(edit_mode.is_edit_mode(), false, "edit_mode should not be active");
 
-}
+});
 
 
 var transformProperty = async function (xml_str) {
diff --git a/test/core/js/modules/entity.xsl.js b/test/core/js/modules/entity.xsl.js
index 21ce97ac3536b325ba77d6bc2d482b926619ae5d..bbc1cac0c7eff6bb06c6826c6efb7054827e82c3 100644
--- a/test/core/js/modules/entity.xsl.js
+++ b/test/core/js/modules/entity.xsl.js
@@ -154,7 +154,7 @@ QUnit.test("single-value template with reference property.", function(assert) {
         'value': '',
         'reference': 'true',
         'boolean': 'false'
-    })), "<span xmlns=\"http://www.w3.org/1999/xhtml\" class=\"caosdb-f-property-single-raw-value caosdb-property-text-value\"></span>", "empty value produces empty span.");
+    })), "<span xmlns=\"http://www.w3.org/1999/xhtml\" class=\"caosdb-f-property-single-raw-value caosdb-property-text-value caosdb-f-property-text-value caosdb-v-property-text-value\"></span>", "empty value produces empty span.");
     let link = callTemplate(this.entityXSL, 'single-value', {
         'value': '1234',
         'reference': 'true',
diff --git a/test/core/js/modules/ext_applicable.js.js b/test/core/js/modules/ext_applicable.js.js
new file mode 100644
index 0000000000000000000000000000000000000000..596f41c9c9a80e47f4a8ebd8268194af4eba1236
--- /dev/null
+++ b/test/core/js/modules/ext_applicable.js.js
@@ -0,0 +1,30 @@
+/*
+ * ** header v3.0
+ * This file is a part of the CaosDB Project.
+ *
+ * Copyright (C) 2020 IndiScale GmbH <info@indiscale.com>
+ * Copyright (C) 2020 Timm Fitschen <t.fitschen@indiscale.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * ** end header
+ */
+
+'use strict';
+
+QUnit.module("ext_applicable", {});
+
+QUnit.test("availability", function(assert) {
+  assert.equal(ext_applicable.version, "0.1");
+});
diff --git a/test/docker/Dockerfile b/test/docker/Dockerfile
index 92889dc526229475463ddcdfe6a2080a669b9d25..f90db2f9b2b869e12f52de595b7a7677f52374b5 100644
--- a/test/docker/Dockerfile
+++ b/test/docker/Dockerfile
@@ -13,8 +13,8 @@ RUN  apt-get update \
     && apt-get install -f
 
 RUN pip3 install pylint pytest
-RUN pip3 install caosdb==0.5.1
-RUN pip3 install pandas xlrd==1.2.0
+RUN pip3 install caosdb>=0.5.2
+RUN pip3 install pandas
 RUN pip3 install git+https://gitlab.com/caosdb/caosdb-advanced-user-tools.git@dev
 # For automatic documentation
 #RUN npm install -g jsdoc